* test(#2974): migrate 8 test files to typed-IR assertions Replaces raw stdout/stderr substring matching with structured-field assertions per CONTRIBUTING.md "Prohibited: Raw Text Matching on Test Outputs". Adds shared infrastructure for typed error emission so this pattern is the easy path going forward. Shared infrastructure: - core.cjs: ERROR_REASON frozen enum + setJsonErrorMode/getJsonErrorMode - gsd-tools.cjs: --json-errors CLI flag, parsed before subcommand dispatch - config.cjs: typed reasons at all 7 error sites - graphify.cjs: GRAPHIFY_REASON enum + reason/timeout_ms in execGraphify result - bin/install.js: pure buildSdkFailFastReport() IR builder + renderer - hooks/gsd-session-state.sh, gsd-phase-boundary.sh: emit Claude Code hookSpecificOutput JSON envelope with typed state_present/config_mode/ planning_modified/file_path fields (no-op when hooks.community is off) Test migrations (all pass, 171 tests across the 8 files): - bug-2649-sdk-fail-fast: assert on ir.reason / ir.context / ir.fix_command - bug-2687-config-read-warning-parity: assert.equal stderr === '' - bug-2796-arg-parsing-regression: assert on result.json.updated/.phase - bug-2838-summary-rescue: parse rescue footer, assert mtime invariant - bug-2943-config-get-context-window: parse JSON, assert ERROR_REASON.CONFIG_KEY_NOT_FOUND - graphify: assert reason === GRAPHIFY_REASON.ENOENT/TIMEOUT - hooks-opt-in: parse hookSpecificOutput, assert typed fields - security-scan: reclassified as source-text-is-the-product (scan label output and CI workflow YAML ARE the deployed contract) Verification: lint-no-source-grep clean (0 violations), full suite 6741/6741 pass. Closes #2974 * test(#2974): address CR feedback — typed code field, robust idempotency Two CodeRabbit findings on #3016 addressed: 1. tests/hooks-opt-in.test.cjs:355 (Minor, inline) — parsed.reason.includes('Conventional Commits') was still substring matching after the typed-IR migration. Fixed at the source: the gsd-validate-commit hook now emits a typed `code` field ('CONVENTIONAL_COMMITS_VIOLATION', 'COMMIT_SUBJECT_TOO_LONG') alongside the human-readable `reason`. Test asserts strictEqual on the code; the prose copy is no longer part of the test contract. 2. tests/bug-2838-summary-rescue-gitignored-planning.test.cjs:224-250 (Outside-diff) — mtimeMs alone can stay unchanged on coarse-grained filesystems (HFS+, FAT) when two rewrites land within the same timestamp tick, falsely passing the idempotency assertion. Replaced with a full snapshot (mtimeMs, ctimeMs, size, ino, sha256 of contents) compared via assert.deepStrictEqual — the hash catches any rewrite the timestamp would miss. Verification: 30/30 pass on the two affected files; lint-no-source-grep clean (0 violations across 368 test files).
This commit is contained in:
121
bin/install.js
121
bin/install.js
@@ -8944,6 +8944,85 @@ function classifySdkInstall(sdkDir) {
|
||||
return { mode, npxCache, readOnly };
|
||||
}
|
||||
|
||||
/**
|
||||
* #2974: pure builder for the SDK fail-fast report. Returns a structured IR
|
||||
* with everything the renderer needs PLUS everything tests need to assert
|
||||
* on. Tests can call `buildSdkFailFastReport(sdkDir, sdkCliPath)` directly
|
||||
* and assert on `report.reason`, `report.context`, `report.fix_command`
|
||||
* etc. without intercepting console.error or matching against rendered
|
||||
* text.
|
||||
*
|
||||
* Shape (frozen contract — extending requires a new test):
|
||||
* {
|
||||
* ok: false,
|
||||
* reason: 'sdk_fail_fast', // ERROR_REASON.SDK_FAIL_FAST
|
||||
* context: 'npx-cache' | 'tarball' | 'dev-clone',
|
||||
* missing_path: '<path>/sdk/dist/cli.js',
|
||||
* missing_artifact: 'sdk/dist',
|
||||
* fix_command: 'npm install -g get-shit-done-cc@latest' | 'cd sdk && npm install && npm run build',
|
||||
* attempted_nested_install: false, // contract: never true
|
||||
* }
|
||||
*/
|
||||
function buildSdkFailFastReport(sdkDir, sdkCliPath) {
|
||||
const ctx = classifySdkInstall(sdkDir);
|
||||
let context, fix_command;
|
||||
if (ctx.mode === 'tarball') {
|
||||
context = ctx.npxCache ? 'npx-cache' : 'tarball';
|
||||
fix_command = 'npm install -g get-shit-done-cc@latest';
|
||||
} else {
|
||||
context = 'dev-clone';
|
||||
fix_command = 'cd sdk && npm install && npm run build';
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
reason: 'sdk_fail_fast',
|
||||
context,
|
||||
missing_path: sdkCliPath,
|
||||
missing_artifact: 'sdk/dist',
|
||||
fix_command,
|
||||
attempted_nested_install: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderer for the structured fail-fast report. Text formatting only —
|
||||
* tests never call this. Splits the IR fields back into the same human-
|
||||
* readable lines the previous shape produced.
|
||||
*/
|
||||
function renderSdkFailFastReport(ir) {
|
||||
const bar = '━'.repeat(72);
|
||||
const redBold = `${red}${bold}`;
|
||||
console.error('');
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error(`${redBold} ✗ GSD SDK dist not found — /gsd-* commands will not work${reset}`);
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error(` ${red}Reason:${reset} ${ir.missing_artifact}/cli.js not found at ${ir.missing_path}`);
|
||||
console.error('');
|
||||
if (ir.context === 'npx-cache') {
|
||||
console.error(` Detected read-only npx cache install (${dim}${path.dirname(ir.missing_path).replace(/\/dist$/, '')}${reset}).`);
|
||||
console.error(` The installer will ${bold}not${reset} attempt \`npm install\` inside the npx cache.`);
|
||||
console.error('');
|
||||
console.error(` Fix: install a version that ships sdk/dist/ globally:`);
|
||||
console.error(` ${cyan}${ir.fix_command}${reset}`);
|
||||
console.error(` Or, if you prefer a one-shot run, clear the npx cache first:`);
|
||||
console.error(` ${cyan}npx --yes get-shit-done-cc@latest${reset}`);
|
||||
console.error(` Or build from source (git clone):`);
|
||||
console.error(` ${cyan}git clone https://github.com/gsd-build/get-shit-done && cd get-shit-done/sdk && npm install && npm run build${reset}`);
|
||||
} else if (ir.context === 'tarball') {
|
||||
console.error(` The published tarball appears to be missing sdk/dist/ (see #2647).`);
|
||||
console.error('');
|
||||
console.error(` Fix: install a version that ships sdk/dist/ globally:`);
|
||||
console.error(` ${cyan}${ir.fix_command}${reset}`);
|
||||
console.error(` Or build from source (git clone):`);
|
||||
console.error(` ${cyan}git clone https://github.com/gsd-build/get-shit-done && cd get-shit-done/sdk && npm install && npm run build${reset}`);
|
||||
} else {
|
||||
console.error(` Running from a git clone — build the SDK first:`);
|
||||
console.error(` ${cyan}${ir.fix_command}${reset}`);
|
||||
}
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error('');
|
||||
}
|
||||
|
||||
function installSdkIfNeeded(opts) {
|
||||
opts = opts || {};
|
||||
if (hasNoSdk && !opts.sdkDir) {
|
||||
@@ -8971,44 +9050,8 @@ function installSdkIfNeeded(opts) {
|
||||
}
|
||||
|
||||
if (!fs.existsSync(sdkCliPath)) {
|
||||
const ctx = classifySdkInstall(sdkDir);
|
||||
const bar = '━'.repeat(72);
|
||||
const redBold = `${red}${bold}`;
|
||||
console.error('');
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error(`${redBold} ✗ GSD SDK dist not found — /gsd-* commands will not work${reset}`);
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error(` ${red}Reason:${reset} sdk/dist/cli.js not found at ${sdkCliPath}`);
|
||||
console.error('');
|
||||
|
||||
if (ctx.mode === 'tarball') {
|
||||
// User install (including `npx get-shit-done-cc@latest`, which stages
|
||||
// a read-only tarball under the npx cache). The sdk/dist/ artifact
|
||||
// should ship in the published tarball. If it's missing, the only
|
||||
// sane fix from the user's side is a fresh global install of a
|
||||
// version that includes dist/. Do NOT attempt a nested `npm install`
|
||||
// inside the (read-only) npx cache — that's the #2649 failure mode.
|
||||
if (ctx.npxCache) {
|
||||
console.error(` Detected read-only npx cache install (${dim}${sdkDir}${reset}).`);
|
||||
console.error(` The installer will ${bold}not${reset} attempt \`npm install\` inside the npx cache.`);
|
||||
console.error('');
|
||||
} else {
|
||||
console.error(` The published tarball appears to be missing sdk/dist/ (see #2647).`);
|
||||
console.error('');
|
||||
}
|
||||
console.error(` Fix: install a version that ships sdk/dist/ globally:`);
|
||||
console.error(` ${cyan}npm install -g get-shit-done-cc@latest${reset}`);
|
||||
console.error(` Or, if you prefer a one-shot run, clear the npx cache first:`);
|
||||
console.error(` ${cyan}npx --yes get-shit-done-cc@latest${reset}`);
|
||||
console.error(` Or build from source (git clone):`);
|
||||
console.error(` ${cyan}git clone https://github.com/gsd-build/get-shit-done && cd get-shit-done/sdk && npm install && npm run build${reset}`);
|
||||
} else {
|
||||
// Dev clone: keep the existing build-from-source hint.
|
||||
console.error(` Running from a git clone — build the SDK first:`);
|
||||
console.error(` ${cyan}cd sdk && npm install && npm run build${reset}`);
|
||||
}
|
||||
console.error(`${redBold}${bar}${reset}`);
|
||||
console.error('');
|
||||
const ir = buildSdkFailFastReport(sdkDir, sdkCliPath);
|
||||
renderSdkFailFastReport(ir);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -9370,6 +9413,8 @@ if (process.env.GSD_TEST_MODE) {
|
||||
install,
|
||||
uninstall,
|
||||
installSdkIfNeeded,
|
||||
buildSdkFailFastReport,
|
||||
renderSdkFailFastReport,
|
||||
classifySdkInstall,
|
||||
convertClaudeCommandToCodexSkill,
|
||||
convertClaudeToOpencodeFrontmatter,
|
||||
|
||||
Reference in New Issue
Block a user