diff --git a/CLAUDE.md b/CLAUDE.md index 64deac0a4..e47e1de49 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,3 +19,11 @@ Custom label mapping: `confirmed` = AFK-agent-ready (bugs); `approved-enhancemen ### Domain docs Single-context repo — `CONTEXT.md` + `docs/adr/` at the root. See `docs/agents/domain.md`. + +## Memory + +This project uses MemPalace. At the start of every session, call +`mempalace_status` to load the palace protocol. Before answering +questions about people, past work, or prior decisions in this +project, call `mempalace_search` or `mempalace_kg_query` first — +do not guess from context alone. diff --git a/tests/bug-2256-model-overrides-transport.test.cjs b/tests/bug-2256-model-overrides-transport.test.cjs index 5e0e04727..967d634bd 100644 --- a/tests/bug-2256-model-overrides-transport.test.cjs +++ b/tests/bug-2256-model-overrides-transport.test.cjs @@ -28,9 +28,8 @@ const { getCodexSkillAdapterHeader, } = require('../bin/install.js'); -function makeTmp(prefix) { - return fs.mkdtempSync(path.join(os.tmpdir(), `gsd-2256-${prefix}-`)); -} +const { createTempDir } = require('./helpers.cjs'); +const makeTmp = (prefix) => createTempDir(`gsd-2256-${prefix}-`); function writeJson(p, obj) { fs.mkdirSync(path.dirname(p), { recursive: true }); diff --git a/tests/bug-2775-sdk-shim-path-verify.test.cjs b/tests/bug-2775-sdk-shim-path-verify.test.cjs index 8f8adcef7..83c80c160 100644 --- a/tests/bug-2775-sdk-shim-path-verify.test.cjs +++ b/tests/bug-2775-sdk-shim-path-verify.test.cjs @@ -39,39 +39,7 @@ const installModule = require('../bin/install.js'); const isWindows = process.platform === 'win32'; const { installSdkIfNeeded } = installModule; -const { createTempDir, cleanup } = require('./helpers.cjs'); - -function captureConsole(fn) { - const stdout = []; - const stderr = []; - const origLog = console.log; - const origWarn = console.warn; - const origError = console.error; - console.log = (...a) => stdout.push(a.join(' ')); - console.warn = (...a) => stderr.push(a.join(' ')); - console.error = (...a) => stderr.push(a.join(' ')); - let threw = null; - try { - fn(); - } catch (e) { - threw = e; - } finally { - console.log = origLog; - console.warn = origWarn; - console.error = origError; - } - // Re-throw any captured exception AFTER restoring console so callers don't - // have to destructure-and-assert on `threw` (and a future regression that - // crashes before printing won't falsely pass `!hasReady`). (#2775 - // CodeRabbit follow-up) - if (threw) throw threw; - // strip ANSI for matching - const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, ''); - return { - stdout: stdout.map(strip).join('\n'), - stderr: stderr.map(strip).join('\n'), - }; -} +const { createTempDir, cleanup, captureConsole } = require('./helpers.cjs'); describe('bug #2775: installSdkIfNeeded must verify gsd-sdk on PATH before reporting ready', { skip: isWindows ? 'POSIX-only: asserts ~/.local/bin shebang shim with mode 0o755; Windows uses gsd-sdk.cmd + PATHEXT + registry Path' : false }, diff --git a/tests/bug-2794-opencode-model-profile-overrides.test.cjs b/tests/bug-2794-opencode-model-profile-overrides.test.cjs index 6d73f5825..adbcca1e1 100644 --- a/tests/bug-2794-opencode-model-profile-overrides.test.cjs +++ b/tests/bug-2794-opencode-model-profile-overrides.test.cjs @@ -35,9 +35,8 @@ const { install, } = require('../bin/install.js'); -function makeTmp(prefix) { - return fs.mkdtempSync(path.join(os.tmpdir(), `gsd-2794-${prefix}-`)); -} +const { createTempDir } = require('./helpers.cjs'); +const makeTmp = (prefix) => createTempDir(`gsd-2794-${prefix}-`); function writeJson(p, obj) { fs.mkdirSync(path.dirname(p), { recursive: true }); diff --git a/tests/bug-2829-local-install-sdk-path.test.cjs b/tests/bug-2829-local-install-sdk-path.test.cjs index d132d391e..4212f5cdd 100644 --- a/tests/bug-2829-local-install-sdk-path.test.cjs +++ b/tests/bug-2829-local-install-sdk-path.test.cjs @@ -29,37 +29,10 @@ const fs = require('fs'); const path = require('path'); const { installSdkIfNeeded } = require('../bin/install.js'); -const { createTempDir, cleanup } = require('./helpers.cjs'); +const { createTempDir, cleanup, captureConsole } = require('./helpers.cjs'); const isWindows = process.platform === 'win32'; -function captureConsole(fn) { - const stdout = []; - const stderr = []; - const origLog = console.log; - const origWarn = console.warn; - const origError = console.error; - console.log = (...a) => stdout.push(a.join(' ')); - console.warn = (...a) => stderr.push(a.join(' ')); - console.error = (...a) => stderr.push(a.join(' ')); - let threw = null; - try { - fn(); - } catch (e) { - threw = e; - } finally { - console.log = origLog; - console.warn = origWarn; - console.error = origError; - } - if (threw) throw threw; - const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, ''); - return { - stdout: stdout.map(strip).join('\n'), - stderr: stderr.map(strip).join('\n'), - }; -} - describe('bug #2829: local-mode install must materialize gsd-sdk on PATH', { skip: isWindows ? 'POSIX-only: asserts ~/.local/bin shebang shim; Windows uses gsd-sdk.cmd + USERPROFILE + PATHEXT' : false }, () => { diff --git a/tests/bug-3033-sdk-flag-wired.test.cjs b/tests/bug-3033-sdk-flag-wired.test.cjs index c628aebbb..04b4e3be9 100644 --- a/tests/bug-3033-sdk-flag-wired.test.cjs +++ b/tests/bug-3033-sdk-flag-wired.test.cjs @@ -26,37 +26,10 @@ const path = require('path'); const os = require('os'); const { installSdkIfNeeded } = require('../bin/install.js'); -const { createTempDir, cleanup } = require('./helpers.cjs'); +const { createTempDir, cleanup, captureConsole } = require('./helpers.cjs'); const isWindows = process.platform === 'win32'; -function captureConsole(fn) { - const stdout = []; - const stderr = []; - const origLog = console.log; - const origWarn = console.warn; - const origError = console.error; - console.log = (...a) => stdout.push(a.join(' ')); - console.warn = (...a) => stderr.push(a.join(' ')); - console.error = (...a) => stderr.push(a.join(' ')); - let threw = null; - try { - fn(); - } catch (e) { - threw = e; - } finally { - console.log = origLog; - console.warn = origWarn; - console.error = origError; - } - if (threw) throw threw; - const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, ''); - return { - stdout: stdout.map(strip).join('\n'), - stderr: stderr.map(strip).join('\n'), - }; -} - describe('bug #3033: --sdk flag (opts.forceSdk) must be wired into installSdkIfNeeded', { skip: isWindows ? 'POSIX-only: forces shebang gsd-sdk shim into ~/.local/bin and asserts mode 0o755' : false }, () => { diff --git a/tests/bug-3211-windows-sdk-not-found.test.cjs b/tests/bug-3211-windows-sdk-not-found.test.cjs index 0b09f6ef6..d69b1fcf4 100644 --- a/tests/bug-3211-windows-sdk-not-found.test.cjs +++ b/tests/bug-3211-windows-sdk-not-found.test.cjs @@ -50,6 +50,7 @@ const cp = require('node:child_process'); const ROOT = path.join(__dirname, '..'); const installModule = require(path.join(ROOT, 'bin', 'install.js')); +const { captureConsole } = require('./helpers.cjs'); const isWindows = process.platform === 'win32'; @@ -254,27 +255,6 @@ describe('bug #3211-D: installSdkIfNeeded — Windows _npx false-positive', () = let savedEnv; let origExecSync; - function captureConsole(fn) { - const stdout = []; - const stderr = []; - const origLog = console.log; - const origWarn = console.warn; - const origError = console.error; - console.log = (...a) => stdout.push(a.join(' ')); - console.warn = (...a) => stderr.push(a.join(' ')); - console.error = (...a) => stderr.push(a.join(' ')); - let threw = null; - try { fn(); } catch (e) { threw = e; } - finally { - console.log = origLog; - console.warn = origWarn; - console.error = origError; - } - if (threw) throw threw; - const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, ''); - return { stdout: stdout.map(strip).join('\n'), stderr: stderr.map(strip).join('\n') }; - } - function makeSdkDir(root) { const dir = path.join(root, 'sdk'); fs.mkdirSync(path.join(dir, 'dist'), { recursive: true }); diff --git a/tests/bug-3231-false-gsd-sdk-ready-linux.test.cjs b/tests/bug-3231-false-gsd-sdk-ready-linux.test.cjs index eb2e7f0dd..9223194ff 100644 --- a/tests/bug-3231-false-gsd-sdk-ready-linux.test.cjs +++ b/tests/bug-3231-false-gsd-sdk-ready-linux.test.cjs @@ -36,6 +36,7 @@ const os = require('node:os'); const path = require('node:path'); const installModule = require('../bin/install.js'); +const { captureConsole } = require('./helpers.cjs'); const isWindows = process.platform === 'win32'; @@ -49,33 +50,6 @@ const { // --------------------------------------------------------------------------- // Console capture helper (no ANSI) // --------------------------------------------------------------------------- -function captureConsole(fn) { - const stdout = []; - const stderr = []; - const origLog = console.log; - const origWarn = console.warn; - const origError = console.error; - console.log = (...a) => stdout.push(a.join(' ')); - console.warn = (...a) => stderr.push(a.join(' ')); - console.error = (...a) => stderr.push(a.join(' ')); - let threw = null; - try { - fn(); - } catch (e) { - threw = e; - } finally { - console.log = origLog; - console.warn = origWarn; - console.error = origError; - } - if (threw) throw threw; - const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, ''); - return { - stdout: stdout.map(strip).join('\n'), - stderr: stderr.map(strip).join('\n'), - }; -} - // --------------------------------------------------------------------------- // Shared fixture helpers // --------------------------------------------------------------------------- diff --git a/tests/bug-3288-model-catalog-install-path.test.cjs b/tests/bug-3288-model-catalog-install-path.test.cjs index 3b1aece35..1fa31bd11 100644 --- a/tests/bug-3288-model-catalog-install-path.test.cjs +++ b/tests/bug-3288-model-catalog-install-path.test.cjs @@ -39,13 +39,10 @@ const { install } = require('../bin/install.js'); // ─── helpers ───────────────────────────────────────────────────────────────── -function makeTmpDir(prefix) { - return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); -} +const { createTempDir, cleanup } = require('./helpers.cjs'); +const makeTmpDir = createTempDir; -function rmTmpDir(dir) { - fs.rmSync(dir, { recursive: true, force: true }); -} +const rmTmpDir = cleanup; /** * Silence console output during install to avoid noise in test output. diff --git a/tests/bug-3359-stale-gsd-sdk-path-version.test.cjs b/tests/bug-3359-stale-gsd-sdk-path-version.test.cjs index a1593d91f..786764462 100644 --- a/tests/bug-3359-stale-gsd-sdk-path-version.test.cjs +++ b/tests/bug-3359-stale-gsd-sdk-path-version.test.cjs @@ -19,37 +19,10 @@ const path = require('path'); const { installSdkIfNeeded, readGsdSdkVersion } = require('../bin/install.js'); const cp = require('node:child_process'); const pkg = require('../package.json'); -const { createTempDir, cleanup } = require('./helpers.cjs'); +const { createTempDir, cleanup, captureConsole } = require('./helpers.cjs'); const isWindows = process.platform === 'win32'; -function captureConsole(fn) { - const stdout = []; - const stderr = []; - const origLog = console.log; - const origWarn = console.warn; - const origError = console.error; - console.log = (...a) => stdout.push(a.join(' ')); - console.warn = (...a) => stderr.push(a.join(' ')); - console.error = (...a) => stderr.push(a.join(' ')); - let threw = null; - try { - fn(); - } catch (e) { - threw = e; - } finally { - console.log = origLog; - console.warn = origWarn; - console.error = origError; - } - if (threw) throw threw; - const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, ''); - return { - stdout: stdout.map(strip).join('\n'), - stderr: stderr.map(strip).join('\n'), - }; -} - describe('bug #3359: installer detects stale gsd-sdk earlier on PATH', { skip: isWindows ? 'POSIX-only: stages bare gsd-sdk shebang shims in a PATH dir; Windows uses .cmd + PATHEXT resolution' : false }, () => { diff --git a/tests/bug-3491-nested-git-worktree.test.cjs b/tests/bug-3491-nested-git-worktree.test.cjs index 3ccc5ccd2..bcc8ed7e4 100644 --- a/tests/bug-3491-nested-git-worktree.test.cjs +++ b/tests/bug-3491-nested-git-worktree.test.cjs @@ -42,11 +42,9 @@ const WORKFLOW_PATH = path.join( // ─── Helper: create outer git repo with a nested workstream subdir ───────── // On Windows the runtime emits forward slashes (git's convention) while -// path.join produces backslashes — normalize both sides before any -// equality comparison against the handler's git_worktree_root. -function normalizePath(p) { - return p == null ? p : p.split(path.sep).join('/'); -} +// path.join produces backslashes — normalize both sides via the shared +// toPosixPath helper before any equality comparison. +const { toPosixPath: normalizePath } = require('./helpers.cjs'); function createOuterRepoWithSubdir(prefix = 'bug-3491-') { const outer = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); diff --git a/tests/bug-3571-configuration-manifest-install-path.test.cjs b/tests/bug-3571-configuration-manifest-install-path.test.cjs index c5b9fd2ac..ab16aba89 100644 --- a/tests/bug-3571-configuration-manifest-install-path.test.cjs +++ b/tests/bug-3571-configuration-manifest-install-path.test.cjs @@ -20,9 +20,8 @@ const SDK_SHARED_DIR = path.join(REPO_ROOT, 'sdk', 'shared'); const { install } = require('../bin/install.js'); -function makeTmpDir() { - return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3571-')); -} +const { createTempDir } = require('./helpers.cjs'); +const makeTmpDir = () => createTempDir('gsd-3571-'); function silenceConsole(fn) { const original = { diff --git a/tests/feat-3023-phase-type-models.test.cjs b/tests/feat-3023-phase-type-models.test.cjs index d0b0f5051..056f3bb63 100644 --- a/tests/feat-3023-phase-type-models.test.cjs +++ b/tests/feat-3023-phase-type-models.test.cjs @@ -36,9 +36,8 @@ const { } = require('../get-shit-done/bin/lib/model-profiles.cjs'); const { isValidConfigKey } = require('../get-shit-done/bin/lib/config-schema.cjs'); -function makeTmp(prefix) { - return fs.mkdtempSync(path.join(os.tmpdir(), `gsd-3023-${prefix}-`)); -} +const { createTempDir } = require('./helpers.cjs'); +const makeTmp = (prefix) => createTempDir(`gsd-3023-${prefix}-`); function writeConfig(projectDir, config) { const planningDir = path.join(projectDir, '.planning'); diff --git a/tests/feat-3024-dynamic-routing.test.cjs b/tests/feat-3024-dynamic-routing.test.cjs index 22feb23d6..091d479fe 100644 --- a/tests/feat-3024-dynamic-routing.test.cjs +++ b/tests/feat-3024-dynamic-routing.test.cjs @@ -60,9 +60,8 @@ const { } = require('../get-shit-done/bin/lib/model-profiles.cjs'); const { isValidConfigKey } = require('../get-shit-done/bin/lib/config-schema.cjs'); -function makeTmp(prefix) { - return fs.mkdtempSync(path.join(os.tmpdir(), `gsd-3024-${prefix}-`)); -} +const { createTempDir } = require('./helpers.cjs'); +const makeTmp = (prefix) => createTempDir(`gsd-3024-${prefix}-`); function writeConfig(dir, config) { const planningDir = path.join(dir, '.planning'); fs.mkdirSync(planningDir, { recursive: true }); diff --git a/tests/feat-3595-fs-fault-injection-atomic-write.test.cjs b/tests/feat-3595-fs-fault-injection-atomic-write.test.cjs index cf1f585ef..a15bb371c 100644 --- a/tests/feat-3595-fs-fault-injection-atomic-write.test.cjs +++ b/tests/feat-3595-fs-fault-injection-atomic-write.test.cjs @@ -48,9 +48,8 @@ const { * Create a fresh real-fs scratch dir per test so no two faults share * state. Returns the directory; caller must clean up. */ -function mkScratch(name) { - return fs.mkdtempSync(path.join(os.tmpdir(), `fs-fault-${name}-`)); -} +const { createTempDir } = require('./helpers.cjs'); +const mkScratch = (name) => createTempDir(`fs-fault-${name}-`); /** * Enumerate orphan tmp files left behind by platformWriteSync. The diff --git a/tests/helpers.cjs b/tests/helpers.cjs index b2ee8ec8d..8aa5d58a8 100644 --- a/tests/helpers.cjs +++ b/tests/helpers.cjs @@ -175,4 +175,52 @@ function isUsageOutput(text) { return /Usage:\s*gsd-tools/.test(text) && /Commands:/.test(text); } -module.exports = { runGsdTools, createTempDir, createTempProject, createTempGitProject, cleanup, parseFrontmatter, isUsageOutput, TOOLS_PATH }; +/** + * Run `fn` with console.log/warn/error captured, returning {stdout, stderr} + * with ANSI colors stripped. Re-throws any exception fn threw AFTER restoring + * the real console so the caller's assertion path sees the failure (without + * this, a fn that crashes before printing would falsely pass !hasReady-style + * assertions). #2775 CR follow-up established this exact contract. + * + * Previously duplicated in bug-2775, bug-2829, bug-3033, bug-3211, bug-3231, + * bug-3359, and installer-migration-install-integration. + */ +function captureConsole(fn) { + const stdout = []; + const stderr = []; + const origLog = console.log; + const origWarn = console.warn; + const origError = console.error; + console.log = (...a) => stdout.push(a.join(' ')); + console.warn = (...a) => stderr.push(a.join(' ')); + console.error = (...a) => stderr.push(a.join(' ')); + let threw = null; + try { + fn(); + } catch (e) { + threw = e; + } finally { + console.log = origLog; + console.warn = origWarn; + console.error = origError; + } + if (threw) throw threw; + const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, ''); + return { + stdout: stdout.map(strip).join('\n'), + stderr: stderr.map(strip).join('\n'), + }; +} + +/** + * Normalize platform path separators to POSIX forward slashes. Use for + * cross-platform path comparisons in test assertions where the runtime + * emits the platform-native separator (\ on Windows) but the test + * fixture or expected literal is POSIX. Returns the input unchanged if + * null/undefined so it composes safely with optional chaining. + */ +function toPosixPath(p) { + return p == null ? p : p.split(path.sep).join('/'); +} + +module.exports = { runGsdTools, createTempDir, createTempProject, createTempGitProject, cleanup, parseFrontmatter, isUsageOutput, captureConsole, toPosixPath, TOOLS_PATH }; diff --git a/tests/project-root-generator.test.cjs b/tests/project-root-generator.test.cjs index f1c39a792..46302767b 100644 --- a/tests/project-root-generator.test.cjs +++ b/tests/project-root-generator.test.cjs @@ -27,9 +27,8 @@ before(async () => { // ── Fixture helpers ───────────────────────────────────────────────────────── -function makeTmp() { - return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-parity-')); -} +const { createTempDir } = require('./helpers.cjs'); +const makeTmp = () => createTempDir('gsd-parity-'); function writeConfig(dir, content) { fs.mkdirSync(path.join(dir, '.planning'), { recursive: true });