Batch 12 of the ad hoc timeout literal migration (epic #4445). Replaces every bare numeric timeout/timeoutMs object-literal property in tests/check-env.test.cjs, tests/config-get-default.test.cjs, tests/federated-config.test.cjs, tests/gsd-check-update-worker-platform-gate.test.cjs, tests/gsd-mcp-server-bin.test.cjs, tests/health-validation.test.cjs, tests/locking-bugs-1909-1916-1925-1927.test.cjs, tests/perf-316-state-lock-buffer-alloc.test.cjs, tests/perf-317-context-monitor-fs.test.cjs, and tests/pi-config-dir-env-override.test.cjs with a named constant, per eslint-rules/no-adhoc-timeout-literal.cjs. Removes these 10 files from the rule's allowlist. Ground truth via eslint matched the issue's stated 26 sites across 10 files exactly. Reuses PROBE_TIMEOUT_MS, GENERATOR_SCRIPT_TIMEOUT_MS, GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS, and INSTALL_TIMEOUT_MS across 4 files. No new shared constants needed -- every recurring value across files was independently verified to be a genuinely different operation class, per this migration's standing rule that numeric coincidence is never identity. Adds 11 new file-local constants, three of which are not real subprocess timeouts at all (a config-merge fixture value, and two node:test per-test timeout options bounding ReDoS/lock-retry regression backstops). Per the issue's explicit mandate, gsd-check-update-worker-platform-gate.test.cjs now imports (read-only) NPM_VIEW_TIMEOUT_MS from gsd-core/bin/check-latest-version.cjs for disclosure -- this file and that production module once independently guessed the same 15000ms value, causing the PR #4428 Windows double-SIGKILL collision. No site in this file's current bare literals actually wraps a live npm-view call needing margin arithmetic, so the import documents the historical relationship honestly rather than fabricating a computation. No src/bin file edited (only a read-only import added), no numeric value changed anywhere. Co-authored-by: sim <sim@local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
130 lines
5.8 KiB
JavaScript
130 lines
5.8 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* pi PI_CODING_AGENT_DIR override (#3023).
|
|
*
|
|
* pi's real source (`earendil-works/pi`, `packages/coding-agent/src/config.ts`)
|
|
* reads `PI_CODING_AGENT_DIR` to override its GLOBAL agent dir outright:
|
|
*
|
|
* export function getAgentDir(): string {
|
|
* const envDir = process.env[ENV_AGENT_DIR]; // PI_CODING_AGENT_DIR
|
|
* if (envDir) return expandTildePath(envDir);
|
|
* return join(homedir(), CONFIG_DIR_NAME, "agent");
|
|
* }
|
|
*
|
|
* `capabilities/pi/capability.json`'s `runtime.configHome` previously declared
|
|
* `env: []` (empty), so a user who had set `PI_CODING_AGENT_DIR` got GSD
|
|
* installed to the DEFAULT `~/.pi/agent` — a path pi never reads. The fix adds
|
|
* `PI_CODING_AGENT_DIR` to that array; `resolveConfigHomeFromDescriptor`'s
|
|
* existing `dot-home-nested` env-override branch (already exercised by
|
|
* antigravity/windsurf) requires no new code.
|
|
*
|
|
* Unit-level (`resolveConfigHomeFromDescriptor`) coverage lives in
|
|
* tests/runtime-homes-descriptor-drive.test.cjs, describe block
|
|
* "#3023: pi PI_CODING_AGENT_DIR". This file drives the real, spawned
|
|
* installer end-to-end so the env var is proven to redirect the actual
|
|
* install output, not just the pure resolver function.
|
|
*
|
|
* `capitalConfigDir`/`piConfig.configDir` (pi's OTHER override — a
|
|
* project-`package.json` field that renames the `.pi` segment itself, for
|
|
* both local and global scope) is NOT implemented here: GSD's descriptor
|
|
* vocabulary has no existing mechanism for a runtime whose config-dir name is
|
|
* sourced from a project's own `package.json` (reported to the orchestrator
|
|
* separately; out of scope for this change).
|
|
*/
|
|
|
|
const { test, describe, before } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { spawnSync } = require('node:child_process');
|
|
|
|
const { INSTALL_SCRIPT, installerEnv, BUILD_SCRIPT } = require('./helpers/install-shared.cjs');
|
|
const { cleanup, createTempDir } = require('./helpers.cjs');
|
|
const { INSTALL_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
|
|
|
|
/**
|
|
* Spawns scripts/build-hooks.js (the same script BUILD_TIMEOUT_MS
|
|
* documents, at 30000ms) but at four times that constant's bound, used
|
|
* here as a prep step before this file's own real installer run below.
|
|
* Not equalized to the lighter shared norm without bench data -- the
|
|
* identical pattern (and identical value) to epic #4445 batch 9's
|
|
* file-local BUILD_HOOKS_UNDER_LOAD_TIMEOUT_MS in a different file
|
|
* (tests/emitted-attribution.test.cjs, not importable) -- disclosed as
|
|
* coincidence, not merged.
|
|
*/
|
|
const BUILD_HOOKS_PREP_TIMEOUT_MS = 120_000;
|
|
|
|
// hooks/dist is gitignored and built (DEFECT.HOOKS-DIST-SCOPED-CI): a scoped CI
|
|
// lane does not run build:hooks first, so a real --pi --global install there
|
|
// would emit no gsd-hooks/ dir. Build idempotently before spawning, exactly as
|
|
// the golden/emitted-attribution harnesses do.
|
|
function ensureHooksBuilt() {
|
|
spawnSync(process.execPath, [BUILD_SCRIPT], { encoding: 'utf-8', stdio: 'pipe', timeout: BUILD_HOOKS_PREP_TIMEOUT_MS });
|
|
}
|
|
|
|
/** Spawn the real installer for --pi --global against a fresh sandbox HOME,
|
|
* WITHOUT --config-dir, so the runtime's own configHome resolution (default
|
|
* or env-var override) is exactly what places the install. */
|
|
function runPiGlobalInstall(home, extraEnv = {}) {
|
|
const result = spawnSync(process.execPath, [INSTALL_SCRIPT, '--pi', '--global'], {
|
|
cwd: home,
|
|
encoding: 'utf8',
|
|
timeout: INSTALL_TIMEOUT_MS,
|
|
env: installerEnv({ HOME: home, USERPROFILE: home, ...extraEnv }),
|
|
});
|
|
assert.strictEqual(result.status, 0,
|
|
`installer exited with status ${result.status}\nstdout: ${result.stdout}\nstderr: ${result.stderr}`);
|
|
return result;
|
|
}
|
|
|
|
function sandboxHome(t, prefix = 'gsd-3023-pi-envdir-') {
|
|
const dir = createTempDir(prefix);
|
|
t.after(() => cleanup(dir));
|
|
return dir;
|
|
}
|
|
|
|
describe('#3023: --pi --global honors PI_CODING_AGENT_DIR', () => {
|
|
before(() => ensureHooksBuilt());
|
|
|
|
test('PI_CODING_AGENT_DIR unset -> installs at the default ~/.pi/agent', (t) => {
|
|
const home = sandboxHome(t);
|
|
runPiGlobalInstall(home);
|
|
|
|
const defaultDir = path.join(home, '.pi', 'agent');
|
|
assert.ok(fs.existsSync(path.join(defaultDir, 'gsd-file-manifest.json')),
|
|
'default install must land under ~/.pi/agent when the env var is unset');
|
|
});
|
|
|
|
test('PI_CODING_AGENT_DIR set -> installs at the overridden path, not ~/.pi/agent', (t) => {
|
|
const home = sandboxHome(t);
|
|
const altAgentDir = sandboxHome(t, 'gsd-3023-pi-envdir-alt-');
|
|
runPiGlobalInstall(home, { PI_CODING_AGENT_DIR: altAgentDir });
|
|
|
|
assert.ok(fs.existsSync(path.join(altAgentDir, 'gsd-file-manifest.json')),
|
|
'install must land at the PI_CODING_AGENT_DIR override');
|
|
assert.ok(!fs.existsSync(path.join(home, '.pi')),
|
|
'the default ~/.pi tree must not be created when the env var redirects the install');
|
|
});
|
|
|
|
test('PI_CODING_AGENT_DIR with a tilde expands against the sandbox HOME', (t) => {
|
|
const home = sandboxHome(t);
|
|
runPiGlobalInstall(home, { PI_CODING_AGENT_DIR: '~/pi-alt-agent' });
|
|
|
|
const expanded = path.join(home, 'pi-alt-agent');
|
|
assert.ok(fs.existsSync(path.join(expanded, 'gsd-file-manifest.json')),
|
|
'a tilde-prefixed PI_CODING_AGENT_DIR must expand against HOME, matching pi\'s own expandTildePath');
|
|
assert.ok(!fs.existsSync(path.join(home, '.pi')),
|
|
'the default ~/.pi tree must not be created when the env var redirects the install');
|
|
});
|
|
|
|
test('an empty-string PI_CODING_AGENT_DIR falls back to the default, never a bogus path', (t) => {
|
|
const home = sandboxHome(t);
|
|
runPiGlobalInstall(home, { PI_CODING_AGENT_DIR: '' });
|
|
|
|
const defaultDir = path.join(home, '.pi', 'agent');
|
|
assert.ok(fs.existsSync(path.join(defaultDir, 'gsd-file-manifest.json')),
|
|
'an empty-string override must be treated as unset and fall back to ~/.pi/agent');
|
|
});
|
|
});
|