diff --git a/.changeset/happy-geese-tumble.md b/.changeset/happy-geese-tumble.md new file mode 100644 index 000000000..dd528475f --- /dev/null +++ b/.changeset/happy-geese-tumble.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2680 +--- +**Cursor CLI sessions now detect `.planning/`** — the `sessionStart` and `stop` hooks resolved the project from `process.cwd()`, which under the `cursor-agent` CLI is the Cursor config dir (`~/.cursor`), not the workspace. Every CLI session therefore reported "no .planning/ workflow found" even with `.planning/STATE.md` present, and the stop hook's verify-work reminder could never fire. Both hooks now read `workspace_roots` from the hook payload they already buffered but never parsed, preferring the root that actually carries `.planning/STATE.md` (multi-root workspaces) and falling back to the first root, then `cwd` so IDE invocations are unchanged. (#2587) diff --git a/bin/install.js b/bin/install.js index 144e8ae54..d00f11bb1 100755 --- a/bin/install.js +++ b/bin/install.js @@ -325,7 +325,13 @@ const GSD_WINDSURF_HOOK_SCRIPTS = [ // GSD-managed files under hooks/lib/ (helpers required by gsd-*.sh hooks). // git-cmd.js does not start with "gsd-" (shared classifier for #3129), gsd-graphify-rebuild.sh does. -const GSD_HOOK_LIB_FILES = ['git-cmd.js', 'gsd-graphify-rebuild.sh']; +// cursor-workspace.js (#2587) is required by the Cursor lifecycle hooks. Those +// are staged individually by writeCursorHooksJson (Cursor sets +// hostBehaviors.skipSharedHooksInstall, so it never reaches the bulk hooks/lib +// copy below) — that function stages this helper alongside them. Listing it +// here keeps uninstall and the manifest managing it for every OTHER runtime +// that does receive hooks/lib. +const GSD_HOOK_LIB_FILES = ['git-cmd.js', 'gsd-graphify-rebuild.sh', 'cursor-workspace.js']; const CODEX_AGENT_SANDBOX = { 'gsd-executor': 'workspace-write', diff --git a/hooks/gsd-cursor-session-start.js b/hooks/gsd-cursor-session-start.js index dbc8c97ef..56f97ffef 100644 --- a/hooks/gsd-cursor-session-start.js +++ b/hooks/gsd-cursor-session-start.js @@ -23,13 +23,17 @@ 'use strict'; const fs = require('fs'); -const path = require('path'); const MSG_PRESENT = 'GSD: .planning/STATE.md is present — review the current phase and any blockers before acting.'; const MSG_ABSENT = 'GSD: no .planning/ workflow found — run /gsd:new-project to start a tracked workflow.'; +// Workspace resolution is shared across the Cursor hooks (#2587) — see +// hooks/lib/cursor-workspace.js. Staged next to these scripts by +// writeCursorHooksJson so the require always resolves post-install. +const { resolveStatePath } = require('./lib/cursor-workspace.js'); + let raw = ''; const stdinTimeout = setTimeout(() => { // Timeout guard: exit silently rather than hanging. @@ -41,7 +45,7 @@ process.stdin.on('data', (chunk) => { raw += chunk; }); process.stdin.on('end', () => { clearTimeout(stdinTimeout); try { - const statePath = path.join(process.cwd(), '.planning', 'STATE.md'); + const statePath = resolveStatePath(raw); const statePresent = fs.existsSync(statePath); const msg = statePresent ? MSG_PRESENT : MSG_ABSENT; process.stdout.write(JSON.stringify({ additional_context: msg })); diff --git a/hooks/gsd-cursor-stop.js b/hooks/gsd-cursor-stop.js index 4c69bbbaf..ac2bcdebe 100644 --- a/hooks/gsd-cursor-stop.js +++ b/hooks/gsd-cursor-stop.js @@ -21,7 +21,11 @@ 'use strict'; const fs = require('fs'); -const path = require('path'); + +// Workspace resolution is shared across the Cursor hooks (#2587) — see +// hooks/lib/cursor-workspace.js. Staged next to these scripts by +// writeCursorHooksJson so the require always resolves post-install. +const { resolveStatePath } = require('./lib/cursor-workspace.js'); let raw = ''; const stdinTimeout = setTimeout(() => { @@ -33,7 +37,7 @@ process.stdin.on('data', (chunk) => { raw += chunk; }); process.stdin.on('end', () => { clearTimeout(stdinTimeout); try { - const statePath = path.join(process.cwd(), '.planning', 'STATE.md'); + const statePath = resolveStatePath(raw); if (fs.existsSync(statePath)) { process.stdout.write(JSON.stringify({ additional_context: diff --git a/hooks/gsd-cursor-subagent-start.js b/hooks/gsd-cursor-subagent-start.js index ad1e3ca48..f98575421 100644 --- a/hooks/gsd-cursor-subagent-start.js +++ b/hooks/gsd-cursor-subagent-start.js @@ -23,13 +23,17 @@ 'use strict'; const fs = require('fs'); -const path = require('path'); const MSG_PRESENT = 'GSD: Subagent session started — review .planning/STATE.md for the current phase and any blockers before acting.'; const MSG_ABSENT = 'GSD: Subagent session started — no .planning/ workflow found.'; +// Workspace resolution is shared across the Cursor hooks (#2587) — see +// hooks/lib/cursor-workspace.js. Staged next to these scripts by +// writeCursorHooksJson so the require always resolves post-install. +const { resolveStatePath } = require('./lib/cursor-workspace.js'); + let raw = ''; const stdinTimeout = setTimeout(() => { process.exit(0); @@ -40,7 +44,7 @@ process.stdin.on('data', (chunk) => { raw += chunk; }); process.stdin.on('end', () => { clearTimeout(stdinTimeout); try { - const statePath = path.join(process.cwd(), '.planning', 'STATE.md'); + const statePath = resolveStatePath(raw); const statePresent = fs.existsSync(statePath); const msg = statePresent ? MSG_PRESENT : MSG_ABSENT; process.stdout.write(JSON.stringify({ additional_context: msg })); diff --git a/hooks/lib/cursor-workspace.js b/hooks/lib/cursor-workspace.js new file mode 100644 index 000000000..0a1e98a0d --- /dev/null +++ b/hooks/lib/cursor-workspace.js @@ -0,0 +1,74 @@ +// cursor-workspace.js — shared workspace resolution for Cursor lifecycle hooks (#2587). +// +// No `gsd-hook-version:` marker, deliberately — matching hooks/lib/git-cmd.js. +// Neither copy path that stages hooks/lib/*.js substitutes the version +// placeholder (copyLibDir stamps .sh files only), and the managed-hooks +// staleness scan covers top-level hooks/ names, not hooks/lib/. A marker here +// would ship to users as an unsubstituted literal. +// +// Cursor invokes hooks with cwd set to the Cursor config dir (~/.cursor) under +// the cursor-agent CLI, NOT the workspace. Every hook that resolves a project +// path from process.cwd() therefore missed: sessionStart could only emit its +// "no .planning/ workflow found" nudge, stop's verify-work reminder never fired, +// and subagentStart left every subagent without phase context. +// +// The hook payload carries the real path in `workspace_roots`. This module is +// the single place that turns that payload into a project root, so the three +// hooks cannot drift apart (they previously carried three copies of it). +// +// Consumers: gsd-cursor-session-start.js, gsd-cursor-stop.js, +// gsd-cursor-subagent-start.js. Staged into /hooks/lib/ by +// writeCursorHooksJson (src/runtime-hooks-surface.cts) alongside the scripts +// that require it, and registered in the installer's GSD_HOOK_LIB_FILES so +// uninstall and the manifest manage it. + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +/** + * Resolve the workspace root a Cursor hook should report on. + * + * Search order: + * 1. Each entry of `workspace_roots` that actually carries .planning/STATE.md + * — so a multi-root workspace whose GSD project is not the first root + * still resolves. + * 2. process.cwd(), if IT carries .planning/STATE.md. cwd is a CANDIDATE, not + * merely the empty-roots fallback: an IDE invocation can supply + * workspace_roots AND run from the project, and searching roots alone + * would report "absent" for a project sitting right at cwd — narrower than + * the pre-fix always-cwd behavior this replaces. + * 3. The first declared root, else cwd — so the "absent" message still names + * a sensible directory when there is genuinely no project. + * + * Never throws: a malformed payload degrades to cwd rather than wedging the + * session. (fs.existsSync itself does not throw — it returns false for NUL + * bytes, over-long components, and non-directory ancestors.) + * + * @param {string} rawInput Raw stdin payload as received by the hook. + * @returns {string} Absolute-or-relative directory to resolve .planning/ against. + */ +function resolveWorkspaceRoot(rawInput) { + let input = {}; + try { input = JSON.parse(rawInput || '{}'); } catch { /* fall back to cwd */ } + const roots = Array.isArray(input.workspace_roots) + ? input.workspace_roots.filter((r) => typeof r === 'string' && r.length > 0) + : []; + for (const root of [...roots, process.cwd()]) { + if (fs.existsSync(path.join(root, '.planning', 'STATE.md'))) return root; + } + return roots[0] || process.cwd(); +} + +/** + * Convenience: the .planning/STATE.md path for the resolved workspace. + * + * @param {string} rawInput Raw stdin payload as received by the hook. + * @returns {string} + */ +function resolveStatePath(rawInput) { + return path.join(resolveWorkspaceRoot(rawInput), '.planning', 'STATE.md'); +} + +module.exports = { resolveWorkspaceRoot, resolveStatePath }; diff --git a/src/runtime-hooks-surface.cts b/src/runtime-hooks-surface.cts index 20989a7a4..608a37047 100644 --- a/src/runtime-hooks-surface.cts +++ b/src/runtime-hooks-surface.cts @@ -1153,6 +1153,44 @@ function writeCursorHooksJson(targetDir: string, src: string, opts?: WriteCursor } } + // Stage the hooks/lib/ helpers the staged scripts require (#2587). Cursor sets + // hostBehaviors.skipSharedHooksInstall, so it never reaches the installer's + // bulk hooks/lib copy — without this, a script requiring './lib/…' would throw + // MODULE_NOT_FOUND at load, BEFORE its own try/catch, and wedge every Cursor + // session on the one runtime these hooks exist for. Driven off what the staged + // scripts actually require so a future helper cannot be silently omitted. + const requiredLibFiles = new Set(); + for (const script of installedScripts) { + const staged = fs.readFileSync(path.join(hooksDir, script), 'utf8'); + // Tolerant of interior whitespace and either quote style: a hook author + // writing `require( "./lib/x.js" )` must still get its helper staged, since + // a miss here surfaces as MODULE_NOT_FOUND at hook load, not at install. + const re = /require\(\s*['"]\.\/lib\/([A-Za-z0-9._-]+)['"]\s*\)/g; + let m: RegExpExecArray | null; + while ((m = re.exec(staged)) !== null) requiredLibFiles.add(m[1]); + } + if (requiredLibFiles.size > 0) { + const srcLibDir = path.join(srcHooksDir, 'lib'); + const destLibDir = path.join(hooksDir, 'lib'); + fs.mkdirSync(destLibDir, { recursive: true }); + for (const libFile of requiredLibFiles) { + const libSrc = path.join(srcLibDir, libFile); + if (!fs.existsSync(libSrc)) { + // FAIL LOUD. Skipping here would ship hook scripts whose top-level + // require() throws before their own try/catch, wedging every session — + // and the install would still exit 0, so nobody would know until a user + // hit it. A missing helper source is a packaging bug; surface it. + throw new Error( + `hooks/lib/${libFile} is required by a staged Cursor hook but is missing from ${srcLibDir}. ` + + 'Installing would ship a hook that throws MODULE_NOT_FOUND at load.', + ); + } + let libContent = fs.readFileSync(libSrc, 'utf8'); + libContent = libContent.replace(/gsd:/gi, 'gsd-'); + fs.writeFileSync(path.join(destLibDir, libFile), libContent); + } + } + const hookOpts: BuildHookCommandOpts = { runtime: 'cursor', platform: opts.platform || process.platform }; const commands: Record = {}; for (const ev of events) { diff --git a/tests/fix-2587-cursor-hook-workspace-roots.test.cjs b/tests/fix-2587-cursor-hook-workspace-roots.test.cjs new file mode 100644 index 000000000..695f89318 --- /dev/null +++ b/tests/fix-2587-cursor-hook-workspace-roots.test.cjs @@ -0,0 +1,360 @@ +/** + * #2587 — Cursor sessionStart/stop hooks resolved .planning/ from process.cwd(). + * + * Under the cursor-agent CLI, hooks are invoked with cwd set to the Cursor + * config dir (~/.cursor), NOT the workspace. Both hooks did: + * + * path.join(process.cwd(), '.planning', 'STATE.md') + * + * so the lookup always missed: gsd-cursor-session-start.js could only ever emit + * the "no .planning/ workflow found" nudge, and gsd-cursor-stop.js's verify-work + * reminder could never fire — even with .planning/STATE.md right there in the + * workspace. Both hooks already buffered stdin into `raw` but never parsed it; + * the payload's `workspace_roots` carries the real path. + * + * These are BEHAVIORAL tests: each spawns the real hook script as a child + * process with a cwd that does NOT contain .planning/ and a stdin payload whose + * workspace_roots does — exactly the CLI invocation shape from the report — and + * asserts on the emitted JSON contract. They fail against the pre-fix scripts. + */ + +// allow-test-rule: source-text-is-the-product #2587 — the parity check (T8) compares the shared +// resolver text across the two standalone hook scripts, which is what Cursor loads. + +'use strict'; + +process.env.GSD_TEST_MODE = '1'; + +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const { execFileSync } = require('node:child_process'); +const { createTempDir, cleanup } = require('./helpers.cjs'); + +const HOOKS = path.join(__dirname, '..', 'hooks'); +const SESSION_START = path.join(HOOKS, 'gsd-cursor-session-start.js'); +const STOP = path.join(HOOKS, 'gsd-cursor-stop.js'); +// subagentStart carried the identical defect — it was not named in the report +// but its cwd lookup meant every Cursor subagent (planner, executor, verifier) +// started without phase context under the CLI. +const SUBAGENT_START = path.join(HOOKS, 'gsd-cursor-subagent-start.js'); +// Every cursor hook that resolves .planning/ from the payload. Kept as one list +// so a future hook added to this family is not silently left on the old path. +const RESOLVING_HOOKS = [SESSION_START, STOP, SUBAGENT_START]; + +const MSG_PRESENT_FRAGMENT = '.planning/STATE.md is present'; +const MSG_ABSENT_FRAGMENT = 'no .planning/ workflow found'; +const STOP_REMINDER_FRAGMENT = 'Agent stopping'; + +/** Run a hook script with an explicit cwd and stdin payload; return parsed stdout JSON. */ +function runHook(script, { cwd, payload }) { + const stdout = execFileSync(process.execPath, [script], { + cwd, + input: typeof payload === 'string' ? payload : JSON.stringify(payload), + encoding: 'utf8', + timeout: 20000, + }); + return JSON.parse(stdout || '{}'); +} + +/** A directory containing .planning/STATE.md. */ +function makeWorkspace(withPlanning) { + const dir = createTempDir('gsd-2587-'); + if (withPlanning) { + fs.mkdirSync(path.join(dir, '.planning'), { recursive: true }); + fs.writeFileSync(path.join(dir, '.planning', 'STATE.md'), '# Project State\n'); + } + return dir; +} + +describe('#2587: cursor hooks resolve the workspace from workspace_roots, not cwd', () => { + test('sessionStart: cwd is the Cursor config dir, workspace_roots carries the project', () => { + const workspace = makeWorkspace(true); + const cursorConfigDir = makeWorkspace(false); // stands in for ~/.cursor + try { + const out = runHook(SESSION_START, { + cwd: cursorConfigDir, + payload: { + hook_event_name: 'sessionStart', + cursor_version: '2026.07.23-e383d2b', + is_background_agent: false, + workspace_roots: [workspace], + transcript_path: null, + }, + }); + assert.match( + out.additional_context || '', + new RegExp(MSG_PRESENT_FRAGMENT.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), + 'must report STATE.md present when workspace_roots points at the project', + ); + } finally { + cleanup(workspace); + cleanup(cursorConfigDir); + } + }); + + test('stop: verify-work reminder fires when workspace_roots carries the project', () => { + const workspace = makeWorkspace(true); + const cursorConfigDir = makeWorkspace(false); + try { + const out = runHook(STOP, { + cwd: cursorConfigDir, + payload: { hook_event_name: 'stop', workspace_roots: [workspace] }, + }); + assert.ok( + (out.additional_context || '').includes(STOP_REMINDER_FRAGMENT), + 'stop hook must emit its verify-work reminder for the real workspace', + ); + } finally { + cleanup(workspace); + cleanup(cursorConfigDir); + } + }); + + // Boundary coverage on the workspace_roots array: 0, 1, and 2 entries. + + test('zero roots: falls back to cwd (preserves IDE behavior)', () => { + const workspace = makeWorkspace(true); + try { + const out = runHook(SESSION_START, { + cwd: workspace, + payload: { hook_event_name: 'sessionStart', workspace_roots: [] }, + }); + assert.ok( + (out.additional_context || '').includes(MSG_PRESENT_FRAGMENT), + 'an empty workspace_roots must fall back to cwd, not break the IDE path', + ); + } finally { + cleanup(workspace); + } + }); + + test('one root, no .planning anywhere: reports absent', () => { + const workspace = makeWorkspace(false); + const cursorConfigDir = makeWorkspace(false); + try { + const out = runHook(SESSION_START, { + cwd: cursorConfigDir, + payload: { hook_event_name: 'sessionStart', workspace_roots: [workspace] }, + }); + assert.ok( + (out.additional_context || '').includes(MSG_ABSENT_FRAGMENT), + 'a genuinely project-less workspace must still nudge toward new-project', + ); + } finally { + cleanup(workspace); + cleanup(cursorConfigDir); + } + }); + + test('two roots: resolves the one that actually carries .planning/', () => { + const plain = makeWorkspace(false); + const withPlanning = makeWorkspace(true); + const cursorConfigDir = makeWorkspace(false); + try { + const out = runHook(SESSION_START, { + cwd: cursorConfigDir, + // GSD project is NOT the first root — first-root-only would miss it. + payload: { hook_event_name: 'sessionStart', workspace_roots: [plain, withPlanning] }, + }); + assert.ok( + (out.additional_context || '').includes(MSG_PRESENT_FRAGMENT), + 'multi-root: the root carrying .planning/ must win over mere ordering', + ); + } finally { + cleanup(plain); + cleanup(withPlanning); + cleanup(cursorConfigDir); + } + }); + + test('malformed stdin JSON: fails open to cwd instead of crashing', () => { + const workspace = makeWorkspace(true); + try { + const out = runHook(SESSION_START, { cwd: workspace, payload: '{not valid json' }); + assert.ok( + (out.additional_context || '').includes(MSG_PRESENT_FRAGMENT), + 'a malformed payload must degrade to cwd, never wedge the session', + ); + } finally { + cleanup(workspace); + } + }); + + test('non-string and empty root entries are ignored', () => { + const workspace = makeWorkspace(true); + const cursorConfigDir = makeWorkspace(false); + try { + const out = runHook(SESSION_START, { + cwd: cursorConfigDir, + payload: { + hook_event_name: 'sessionStart', + workspace_roots: [null, '', 42, workspace], + }, + }); + assert.ok( + (out.additional_context || '').includes(MSG_PRESENT_FRAGMENT), + 'junk entries must be filtered rather than resolved as paths', + ); + } finally { + cleanup(workspace); + cleanup(cursorConfigDir); + } + }); + + test('subagentStart: reminder resolves via workspace_roots (missed site)', () => { + const workspace = makeWorkspace(true); + const cursorConfigDir = makeWorkspace(false); + try { + const out = runHook(SUBAGENT_START, { + cwd: cursorConfigDir, + payload: { hook_event_name: 'subagentStart', workspace_roots: [workspace] }, + }); + assert.match( + out.additional_context || '', + /review \.planning\/STATE\.md/, + 'subagents must receive phase context, not the absent nudge', + ); + } finally { + cleanup(workspace); + cleanup(cursorConfigDir); + } + }); + + test('stop: absent branch still emits {} when no root and no cwd has .planning', () => { + const workspace = makeWorkspace(false); + const cursorConfigDir = makeWorkspace(false); + try { + const out = runHook(STOP, { + cwd: cursorConfigDir, + payload: { hook_event_name: 'stop', workspace_roots: [workspace] }, + }); + assert.deepEqual( + out, + {}, + 'stop must stay silent when there is genuinely no GSD project', + ); + } finally { + cleanup(workspace); + cleanup(cursorConfigDir); + } + }); + + test('cwd is a candidate, not just the empty-roots fallback', () => { + // Regression guard: resolving ONLY over workspace_roots would report absent + // whenever roots are supplied but the project actually sits at cwd — a + // NARROWING versus the pre-fix behavior, which always consulted cwd. + const projectAtCwd = makeWorkspace(true); + const unrelatedRoot = makeWorkspace(false); + try { + for (const hook of RESOLVING_HOOKS) { + const out = runHook(hook, { + cwd: projectAtCwd, + payload: { hook_event_name: 'sessionStart', workspace_roots: [unrelatedRoot] }, + }); + // stop's present-branch is its verify-work reminder, not a STATE.md phrase. + const ctx = out.additional_context || ''; + assert.ok( + /STATE\.md is present|review \.planning\/STATE\.md|Agent stopping/.test(ctx), + `${path.basename(hook)}: a project at cwd must still be found when roots miss`, + ); + } + } finally { + cleanup(projectAtCwd); + cleanup(unrelatedRoot); + } + }); + + test('single source: every hook requires the shared resolver, none redefines it', () => { + // The resolver lives in hooks/lib/cursor-workspace.js. Divergence is + // prevented structurally (one implementation) rather than by a parity + // assertion over copies, so this guards the structure: no hook may grow a + // local copy back. + for (const file of RESOLVING_HOOKS) { + const src = fs.readFileSync(file, 'utf8'); + assert.ok( + src.includes("require('./lib/cursor-workspace.js')"), + `${path.basename(file)} must use the shared resolver`, + ); + assert.ok( + !src.includes('function resolveWorkspaceRoot('), + `${path.basename(file)} must not redefine resolveWorkspaceRoot locally`, + ); + } + }); + + test('staging fails loudly if a required lib source is missing', () => { + // Previously this path did `continue`, so a helper missing from source + // (typo, bad rebase, accidental delete) produced an install that exits 0 and + // ships hooks whose top-level require() throws MODULE_NOT_FOUND at load — + // before their own try/catch — wedging every session, with nothing to + // indicate why. Packaging bugs must surface at install, not at the user. + const hooksSurface = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs'); + const fakeSrc = createTempDir('gsd-2587-src-'); + const target = createTempDir('gsd-2587-tgt-'); + try { + // A source tree with the hook scripts but NO hooks/lib/ backing them. + const srcHooks = path.join(fakeSrc, 'hooks'); + fs.mkdirSync(srcHooks, { recursive: true }); + for (const hook of RESOLVING_HOOKS) { + fs.copyFileSync(hook, path.join(srcHooks, path.basename(hook))); + } + assert.throws( + () => hooksSurface.writeCursorHooksJson(target, fakeSrc, {}), + /cursor-workspace\.js.*missing|missing.*cursor-workspace\.js/s, + 'a missing lib source must abort the install, not ship a broken hook', + ); + } finally { + cleanup(fakeSrc); + cleanup(target); + } + }); + + test('the shared resolver is staged next to the hooks that require it', () => { + // The MODULE_NOT_FOUND guard. Cursor sets skipSharedHooksInstall, so it + // never reaches the installer's bulk hooks/lib copy — every other runtime + // that ships these hooks does. If writeCursorHooksJson stopped staging the + // helper, each hook would throw at require time, BEFORE its own try/catch, + // and wedge every Cursor session on the one runtime this fix exists for. + const { runMinimalInstall } = require('./helpers/install-shared.cjs'); + const { configDir, root } = runMinimalInstall({ runtime: 'cursor', scope: 'global' }); + try { + const staged = path.join(configDir, 'hooks', 'lib', 'cursor-workspace.js'); + assert.ok( + fs.existsSync(staged), + 'cursor install must stage hooks/lib/cursor-workspace.js next to the hook scripts', + ); + // And the staged hook must actually load against it. + const hook = path.join(configDir, 'hooks', 'gsd-cursor-session-start.js'); + assert.ok(fs.existsSync(hook), 'cursor install must stage the sessionStart hook'); + const ws = makeWorkspace(true); + try { + const out = JSON.parse(execFileSync(process.execPath, [hook], { + cwd: root, + input: JSON.stringify({ workspace_roots: [ws] }), + encoding: 'utf8', + timeout: 20000, + }) || '{}'); + assert.ok( + (out.additional_context || '').includes('STATE.md is present'), + 'the INSTALLED hook must resolve the workspace, not crash on a missing helper', + ); + } finally { + cleanup(ws); + } + } finally { + cleanup(root); + } + }); + + test('no cursor hook resolves .planning from process.cwd() directly', () => { + for (const file of RESOLVING_HOOKS) { + const src = fs.readFileSync(file, 'utf8'); + assert.ok( + !/path\.join\(\s*process\.cwd\(\)\s*,\s*'\.planning'/.test(src), + `${path.basename(file)}: must not resolve .planning from cwd (#2587)`, + ); + } + }); +}); diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index 9e8dc317f..c64193e13 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -333,9 +333,9 @@ "hooks/gsd-context-monitor.js": "31a4d99fd3b260ad", "hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a", "hooks/gsd-cursor-pre-tool.js": "873998b25e308c29", - "hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375", - "hooks/gsd-cursor-stop.js": "bfaaf60f419e3238", - "hooks/gsd-cursor-subagent-start.js": "06d77fde5c1372b6", + "hooks/gsd-cursor-session-start.js": "c72f669cc24c72e3", + "hooks/gsd-cursor-stop.js": "80de723949cee847", + "hooks/gsd-cursor-subagent-start.js": "699a2da2e3c5763e", "hooks/gsd-cursor-subagent-stop.js": "4bbf22917da4d389", "hooks/gsd-ensure-canonical-path.js": "64d092d7e4a01211", "hooks/gsd-graphify-update.sh": "81d716df76159e60", @@ -351,6 +351,7 @@ "hooks/gsd-windsurf-pre-write.js": "92d4dbfbc36ab0cf", "hooks/gsd-workflow-guard.js": "56414206bc13b3dc", "hooks/gsd-worktree-path-guard.js": "5f64275e48fb8284", + "hooks/lib/cursor-workspace.js": "45061acd75d55a28", "hooks/lib/git-cmd.js": "268ba15992ca0b23", "hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9", "hooks/managed-hooks-registry.cjs": "82a4121cbcb82756", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index 117f49c79..71ebb9dd9 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -404,9 +404,9 @@ "hooks/gsd-context-monitor.js": "493e24c139c61129", "hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a", "hooks/gsd-cursor-pre-tool.js": "873998b25e308c29", - "hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375", - "hooks/gsd-cursor-stop.js": "bfaaf60f419e3238", - "hooks/gsd-cursor-subagent-start.js": "06d77fde5c1372b6", + "hooks/gsd-cursor-session-start.js": "c72f669cc24c72e3", + "hooks/gsd-cursor-stop.js": "80de723949cee847", + "hooks/gsd-cursor-subagent-start.js": "699a2da2e3c5763e", "hooks/gsd-cursor-subagent-stop.js": "4bbf22917da4d389", "hooks/gsd-ensure-canonical-path.js": "d569f5f3578e93e5", "hooks/gsd-graphify-update.sh": "81d716df76159e60", @@ -422,6 +422,7 @@ "hooks/gsd-windsurf-pre-write.js": "92d4dbfbc36ab0cf", "hooks/gsd-workflow-guard.js": "56414206bc13b3dc", "hooks/gsd-worktree-path-guard.js": "97c2f736d085fe8e", + "hooks/lib/cursor-workspace.js": "45061acd75d55a28", "hooks/lib/git-cmd.js": "268ba15992ca0b23", "hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9", "hooks/managed-hooks-registry.cjs": "29a8d4fa81378d7d", diff --git a/tests/fixtures/golden-install-parity/claude-local.json b/tests/fixtures/golden-install-parity/claude-local.json index ae857e645..b668f8989 100644 --- a/tests/fixtures/golden-install-parity/claude-local.json +++ b/tests/fixtures/golden-install-parity/claude-local.json @@ -403,9 +403,9 @@ "hooks/gsd-context-monitor.js": "f2ac6a1033d140de", "hooks/gsd-cursor-post-tool.js": "8a8a249c0642cc71", "hooks/gsd-cursor-pre-tool.js": "8cb8e8f895edaec9", - "hooks/gsd-cursor-session-start.js": "05a14e903c5edafa", - "hooks/gsd-cursor-stop.js": "d33be8ac96f4081d", - "hooks/gsd-cursor-subagent-start.js": "d773df8caa605de2", + "hooks/gsd-cursor-session-start.js": "c78754e3516853e4", + "hooks/gsd-cursor-stop.js": "c46dc9be21b75f50", + "hooks/gsd-cursor-subagent-start.js": "ec1ea84a9a589b0a", "hooks/gsd-cursor-subagent-stop.js": "8ee488d826bf3c37", "hooks/gsd-ensure-canonical-path.js": "b4b3b88a0e493b16", "hooks/gsd-graphify-update.sh": "81d716df76159e60", @@ -421,6 +421,7 @@ "hooks/gsd-windsurf-pre-write.js": "92d4dbfbc36ab0cf", "hooks/gsd-workflow-guard.js": "41c59d9765ac36ae", "hooks/gsd-worktree-path-guard.js": "f65356c80c9805b2", + "hooks/lib/cursor-workspace.js": "45061acd75d55a28", "hooks/lib/git-cmd.js": "268ba15992ca0b23", "hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9", "hooks/managed-hooks-registry.cjs": "1d955ec5d64e8a5f", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index 6a413b107..76b031bef 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -332,9 +332,9 @@ "hooks/gsd-context-monitor.js": "f2ac6a1033d140de", "hooks/gsd-cursor-post-tool.js": "8a8a249c0642cc71", "hooks/gsd-cursor-pre-tool.js": "8cb8e8f895edaec9", - "hooks/gsd-cursor-session-start.js": "05a14e903c5edafa", - "hooks/gsd-cursor-stop.js": "d33be8ac96f4081d", - "hooks/gsd-cursor-subagent-start.js": "d773df8caa605de2", + "hooks/gsd-cursor-session-start.js": "c78754e3516853e4", + "hooks/gsd-cursor-stop.js": "c46dc9be21b75f50", + "hooks/gsd-cursor-subagent-start.js": "ec1ea84a9a589b0a", "hooks/gsd-cursor-subagent-stop.js": "8ee488d826bf3c37", "hooks/gsd-ensure-canonical-path.js": "b4b3b88a0e493b16", "hooks/gsd-graphify-update.sh": "81d716df76159e60", @@ -350,6 +350,7 @@ "hooks/gsd-windsurf-pre-write.js": "92d4dbfbc36ab0cf", "hooks/gsd-workflow-guard.js": "41c59d9765ac36ae", "hooks/gsd-worktree-path-guard.js": "f65356c80c9805b2", + "hooks/lib/cursor-workspace.js": "45061acd75d55a28", "hooks/lib/git-cmd.js": "268ba15992ca0b23", "hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9", "hooks/managed-hooks-registry.cjs": "1d955ec5d64e8a5f", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index b776c1b75..63e7a3f4b 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -404,9 +404,9 @@ "hooks/gsd-context-monitor.js": "8d512ba0b08fc4e3", "hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a", "hooks/gsd-cursor-pre-tool.js": "873998b25e308c29", - "hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375", - "hooks/gsd-cursor-stop.js": "bfaaf60f419e3238", - "hooks/gsd-cursor-subagent-start.js": "06d77fde5c1372b6", + "hooks/gsd-cursor-session-start.js": "c72f669cc24c72e3", + "hooks/gsd-cursor-stop.js": "80de723949cee847", + "hooks/gsd-cursor-subagent-start.js": "699a2da2e3c5763e", "hooks/gsd-cursor-subagent-stop.js": "4bbf22917da4d389", "hooks/gsd-ensure-canonical-path.js": "434887487ae63ec5", "hooks/gsd-graphify-update.sh": "81d716df76159e60", @@ -422,6 +422,7 @@ "hooks/gsd-windsurf-pre-write.js": "92d4dbfbc36ab0cf", "hooks/gsd-workflow-guard.js": "56414206bc13b3dc", "hooks/gsd-worktree-path-guard.js": "a1a3d4618b41a804", + "hooks/lib/cursor-workspace.js": "45061acd75d55a28", "hooks/lib/git-cmd.js": "268ba15992ca0b23", "hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9", "hooks/managed-hooks-registry.cjs": "9c0d837594c7b772", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index 32a4b38c2..a1edbea77 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -400,10 +400,11 @@ "gsd-core/workflows/verify-work.md": "559c919ebcb1102c", "hooks/gsd-cursor-post-tool.js": "019d503aee8b4a3f", "hooks/gsd-cursor-pre-tool.js": "fe274720781fcbb5", - "hooks/gsd-cursor-session-start.js": "c6e04ed597ea7020", - "hooks/gsd-cursor-stop.js": "902a005c49e7660b", - "hooks/gsd-cursor-subagent-start.js": "693d38d298d2252c", + "hooks/gsd-cursor-session-start.js": "928fddd165e17b7b", + "hooks/gsd-cursor-stop.js": "cce2bf0e14e267a1", + "hooks/gsd-cursor-subagent-start.js": "bb004049f0e49aa9", "hooks/gsd-cursor-subagent-stop.js": "8da03aad6bb05d3f", + "hooks/lib/cursor-workspace.js": "45061acd75d55a28", "scripts/changeset/README.md": "86ff89331dfd94b2", "scripts/changeset/cli.cjs": "68f92a344b199271", "scripts/changeset/github-release-notes.cjs": "795677f0c009b132", diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index 256b4cf4c..8c3d6b3b5 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -333,9 +333,9 @@ "hooks/gsd-context-monitor.js": "b829f618e82406de", "hooks/gsd-cursor-post-tool.js": "8a8a249c0642cc71", "hooks/gsd-cursor-pre-tool.js": "8cb8e8f895edaec9", - "hooks/gsd-cursor-session-start.js": "05a14e903c5edafa", - "hooks/gsd-cursor-stop.js": "d33be8ac96f4081d", - "hooks/gsd-cursor-subagent-start.js": "d773df8caa605de2", + "hooks/gsd-cursor-session-start.js": "c78754e3516853e4", + "hooks/gsd-cursor-stop.js": "c46dc9be21b75f50", + "hooks/gsd-cursor-subagent-start.js": "ec1ea84a9a589b0a", "hooks/gsd-cursor-subagent-stop.js": "8ee488d826bf3c37", "hooks/gsd-ensure-canonical-path.js": "7d116d7d65c50b4b", "hooks/gsd-graphify-update.sh": "81d716df76159e60", @@ -351,6 +351,7 @@ "hooks/gsd-windsurf-pre-write.js": "92d4dbfbc36ab0cf", "hooks/gsd-workflow-guard.js": "41c59d9765ac36ae", "hooks/gsd-worktree-path-guard.js": "37dd85a1591a4d8c", + "hooks/lib/cursor-workspace.js": "45061acd75d55a28", "hooks/lib/git-cmd.js": "268ba15992ca0b23", "hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9", "hooks/managed-hooks-registry.cjs": "bc58c3a7609d7eae", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index 24b7d6870..201441977 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -404,9 +404,9 @@ "hooks/gsd-context-monitor.js": "6578822a701b2a94", "hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a", "hooks/gsd-cursor-pre-tool.js": "873998b25e308c29", - "hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375", - "hooks/gsd-cursor-stop.js": "bfaaf60f419e3238", - "hooks/gsd-cursor-subagent-start.js": "06d77fde5c1372b6", + "hooks/gsd-cursor-session-start.js": "c72f669cc24c72e3", + "hooks/gsd-cursor-stop.js": "80de723949cee847", + "hooks/gsd-cursor-subagent-start.js": "699a2da2e3c5763e", "hooks/gsd-cursor-subagent-stop.js": "4bbf22917da4d389", "hooks/gsd-ensure-canonical-path.js": "3ce09b366839d324", "hooks/gsd-graphify-update.sh": "81d716df76159e60", @@ -422,6 +422,7 @@ "hooks/gsd-windsurf-pre-write.js": "92d4dbfbc36ab0cf", "hooks/gsd-workflow-guard.js": "56414206bc13b3dc", "hooks/gsd-worktree-path-guard.js": "9734e43bee1d7d2e", + "hooks/lib/cursor-workspace.js": "45061acd75d55a28", "hooks/lib/git-cmd.js": "268ba15992ca0b23", "hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9", "hooks/managed-hooks-registry.cjs": "b63a879b8b3436bf", diff --git a/tests/fixtures/golden-install-parity/kimi-code.json b/tests/fixtures/golden-install-parity/kimi-code.json index 969e54cce..e79431089 100644 --- a/tests/fixtures/golden-install-parity/kimi-code.json +++ b/tests/fixtures/golden-install-parity/kimi-code.json @@ -7,9 +7,9 @@ ".kimi/hooks/gsd-context-monitor.js": "4c0535bc269e130e", ".kimi/hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a", ".kimi/hooks/gsd-cursor-pre-tool.js": "873998b25e308c29", - ".kimi/hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375", - ".kimi/hooks/gsd-cursor-stop.js": "bfaaf60f419e3238", - ".kimi/hooks/gsd-cursor-subagent-start.js": "06d77fde5c1372b6", + ".kimi/hooks/gsd-cursor-session-start.js": "c72f669cc24c72e3", + ".kimi/hooks/gsd-cursor-stop.js": "80de723949cee847", + ".kimi/hooks/gsd-cursor-subagent-start.js": "699a2da2e3c5763e", ".kimi/hooks/gsd-cursor-subagent-stop.js": "4bbf22917da4d389", ".kimi/hooks/gsd-ensure-canonical-path.js": "353a5d7bf6697acb", ".kimi/hooks/gsd-graphify-update.sh": "81d716df76159e60", @@ -25,6 +25,7 @@ ".kimi/hooks/gsd-windsurf-pre-write.js": "92d4dbfbc36ab0cf", ".kimi/hooks/gsd-workflow-guard.js": "56414206bc13b3dc", ".kimi/hooks/gsd-worktree-path-guard.js": "d00a75505429355c", + ".kimi/hooks/lib/cursor-workspace.js": "45061acd75d55a28", ".kimi/hooks/lib/git-cmd.js": "268ba15992ca0b23", ".kimi/hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9", ".kimi/hooks/managed-hooks-registry.cjs": "08ec2585a3f8f132", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index 062b101ce..d04b3f703 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -7,9 +7,9 @@ ".kimi/hooks/gsd-context-monitor.js": "4c0535bc269e130e", ".kimi/hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a", ".kimi/hooks/gsd-cursor-pre-tool.js": "873998b25e308c29", - ".kimi/hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375", - ".kimi/hooks/gsd-cursor-stop.js": "bfaaf60f419e3238", - ".kimi/hooks/gsd-cursor-subagent-start.js": "06d77fde5c1372b6", + ".kimi/hooks/gsd-cursor-session-start.js": "c72f669cc24c72e3", + ".kimi/hooks/gsd-cursor-stop.js": "80de723949cee847", + ".kimi/hooks/gsd-cursor-subagent-start.js": "699a2da2e3c5763e", ".kimi/hooks/gsd-cursor-subagent-stop.js": "4bbf22917da4d389", ".kimi/hooks/gsd-ensure-canonical-path.js": "8bd016237c88b738", ".kimi/hooks/gsd-graphify-update.sh": "81d716df76159e60", @@ -25,6 +25,7 @@ ".kimi/hooks/gsd-windsurf-pre-write.js": "92d4dbfbc36ab0cf", ".kimi/hooks/gsd-workflow-guard.js": "56414206bc13b3dc", ".kimi/hooks/gsd-worktree-path-guard.js": "d00a75505429355c", + ".kimi/hooks/lib/cursor-workspace.js": "45061acd75d55a28", ".kimi/hooks/lib/git-cmd.js": "268ba15992ca0b23", ".kimi/hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9", ".kimi/hooks/managed-hooks-registry.cjs": "08ec2585a3f8f132", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index 23575b547..cbef8fd61 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -404,9 +404,9 @@ "hooks/gsd-context-monitor.js": "91ce47cd2dc3bbe8", "hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a", "hooks/gsd-cursor-pre-tool.js": "873998b25e308c29", - "hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375", - "hooks/gsd-cursor-stop.js": "bfaaf60f419e3238", - "hooks/gsd-cursor-subagent-start.js": "06d77fde5c1372b6", + "hooks/gsd-cursor-session-start.js": "c72f669cc24c72e3", + "hooks/gsd-cursor-stop.js": "80de723949cee847", + "hooks/gsd-cursor-subagent-start.js": "699a2da2e3c5763e", "hooks/gsd-cursor-subagent-stop.js": "4bbf22917da4d389", "hooks/gsd-ensure-canonical-path.js": "2801ae3fef9579bf", "hooks/gsd-graphify-update.sh": "81d716df76159e60", @@ -422,6 +422,7 @@ "hooks/gsd-windsurf-pre-write.js": "92d4dbfbc36ab0cf", "hooks/gsd-workflow-guard.js": "56414206bc13b3dc", "hooks/gsd-worktree-path-guard.js": "bfef1802d491bdab", + "hooks/lib/cursor-workspace.js": "45061acd75d55a28", "hooks/lib/git-cmd.js": "268ba15992ca0b23", "hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9", "hooks/managed-hooks-registry.cjs": "bd57cc72f482a14f", diff --git a/tests/fixtures/golden-install-parity/pi.json b/tests/fixtures/golden-install-parity/pi.json index c78ce0cb4..85f741d81 100644 --- a/tests/fixtures/golden-install-parity/pi.json +++ b/tests/fixtures/golden-install-parity/pi.json @@ -300,9 +300,9 @@ "hooks/gsd-context-monitor.js": "e72cba19eeab9036", "hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a", "hooks/gsd-cursor-pre-tool.js": "873998b25e308c29", - "hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375", - "hooks/gsd-cursor-stop.js": "bfaaf60f419e3238", - "hooks/gsd-cursor-subagent-start.js": "06d77fde5c1372b6", + "hooks/gsd-cursor-session-start.js": "c72f669cc24c72e3", + "hooks/gsd-cursor-stop.js": "80de723949cee847", + "hooks/gsd-cursor-subagent-start.js": "699a2da2e3c5763e", "hooks/gsd-cursor-subagent-stop.js": "4bbf22917da4d389", "hooks/gsd-ensure-canonical-path.js": "62d0819a51b55fc4", "hooks/gsd-graphify-update.sh": "81d716df76159e60", @@ -318,6 +318,7 @@ "hooks/gsd-windsurf-pre-write.js": "92d4dbfbc36ab0cf", "hooks/gsd-workflow-guard.js": "56414206bc13b3dc", "hooks/gsd-worktree-path-guard.js": "ea37324fe8dfaf12", + "hooks/lib/cursor-workspace.js": "45061acd75d55a28", "hooks/lib/git-cmd.js": "268ba15992ca0b23", "hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9", "hooks/managed-hooks-registry.cjs": "ac720a2b548ba200", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index dfa65d0af..b86b9531f 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -333,9 +333,9 @@ "hooks/gsd-context-monitor.js": "b71785540277e9d7", "hooks/gsd-cursor-post-tool.js": "8a8a249c0642cc71", "hooks/gsd-cursor-pre-tool.js": "8cb8e8f895edaec9", - "hooks/gsd-cursor-session-start.js": "05a14e903c5edafa", - "hooks/gsd-cursor-stop.js": "d33be8ac96f4081d", - "hooks/gsd-cursor-subagent-start.js": "d773df8caa605de2", + "hooks/gsd-cursor-session-start.js": "c78754e3516853e4", + "hooks/gsd-cursor-stop.js": "c46dc9be21b75f50", + "hooks/gsd-cursor-subagent-start.js": "ec1ea84a9a589b0a", "hooks/gsd-cursor-subagent-stop.js": "8ee488d826bf3c37", "hooks/gsd-ensure-canonical-path.js": "2df5e295b36c3334", "hooks/gsd-graphify-update.sh": "81d716df76159e60", @@ -351,6 +351,7 @@ "hooks/gsd-windsurf-pre-write.js": "92d4dbfbc36ab0cf", "hooks/gsd-workflow-guard.js": "41c59d9765ac36ae", "hooks/gsd-worktree-path-guard.js": "d55016621969e923", + "hooks/lib/cursor-workspace.js": "45061acd75d55a28", "hooks/lib/git-cmd.js": "268ba15992ca0b23", "hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9", "hooks/managed-hooks-registry.cjs": "08741ed76f1d8970", diff --git a/tests/fixtures/install-tree/antigravity.json b/tests/fixtures/install-tree/antigravity.json index efeb7b365..7c04b58d2 100644 --- a/tests/fixtures/install-tree/antigravity.json +++ b/tests/fixtures/install-tree/antigravity.json @@ -351,6 +351,7 @@ "hooks/gsd-windsurf-pre-write.js", "hooks/gsd-workflow-guard.js", "hooks/gsd-worktree-path-guard.js", + "hooks/lib/cursor-workspace.js", "hooks/lib/git-cmd.js", "hooks/lib/gsd-graphify-rebuild.sh", "hooks/managed-hooks-registry.cjs", diff --git a/tests/fixtures/install-tree/augment.json b/tests/fixtures/install-tree/augment.json index 547e28f63..087edac29 100644 --- a/tests/fixtures/install-tree/augment.json +++ b/tests/fixtures/install-tree/augment.json @@ -422,6 +422,7 @@ "hooks/gsd-windsurf-pre-write.js", "hooks/gsd-workflow-guard.js", "hooks/gsd-worktree-path-guard.js", + "hooks/lib/cursor-workspace.js", "hooks/lib/git-cmd.js", "hooks/lib/gsd-graphify-rebuild.sh", "hooks/managed-hooks-registry.cjs", diff --git a/tests/fixtures/install-tree/claude-local.json b/tests/fixtures/install-tree/claude-local.json index 795eb7ed7..198119e20 100644 --- a/tests/fixtures/install-tree/claude-local.json +++ b/tests/fixtures/install-tree/claude-local.json @@ -421,6 +421,7 @@ "hooks/gsd-windsurf-pre-write.js", "hooks/gsd-workflow-guard.js", "hooks/gsd-worktree-path-guard.js", + "hooks/lib/cursor-workspace.js", "hooks/lib/git-cmd.js", "hooks/lib/gsd-graphify-rebuild.sh", "hooks/managed-hooks-registry.cjs", diff --git a/tests/fixtures/install-tree/claude.json b/tests/fixtures/install-tree/claude.json index d1cdceeef..462646883 100644 --- a/tests/fixtures/install-tree/claude.json +++ b/tests/fixtures/install-tree/claude.json @@ -350,6 +350,7 @@ "hooks/gsd-windsurf-pre-write.js", "hooks/gsd-workflow-guard.js", "hooks/gsd-worktree-path-guard.js", + "hooks/lib/cursor-workspace.js", "hooks/lib/git-cmd.js", "hooks/lib/gsd-graphify-rebuild.sh", "hooks/managed-hooks-registry.cjs", diff --git a/tests/fixtures/install-tree/codebuddy.json b/tests/fixtures/install-tree/codebuddy.json index d95772f46..961fea930 100644 --- a/tests/fixtures/install-tree/codebuddy.json +++ b/tests/fixtures/install-tree/codebuddy.json @@ -422,6 +422,7 @@ "hooks/gsd-windsurf-pre-write.js", "hooks/gsd-workflow-guard.js", "hooks/gsd-worktree-path-guard.js", + "hooks/lib/cursor-workspace.js", "hooks/lib/git-cmd.js", "hooks/lib/gsd-graphify-rebuild.sh", "hooks/managed-hooks-registry.cjs", diff --git a/tests/fixtures/install-tree/cursor.json b/tests/fixtures/install-tree/cursor.json index e905d9832..ada754e49 100644 --- a/tests/fixtures/install-tree/cursor.json +++ b/tests/fixtures/install-tree/cursor.json @@ -404,6 +404,7 @@ "hooks/gsd-cursor-stop.js", "hooks/gsd-cursor-subagent-start.js", "hooks/gsd-cursor-subagent-stop.js", + "hooks/lib/cursor-workspace.js", "scripts/changeset/README.md", "scripts/changeset/cli.cjs", "scripts/changeset/github-release-notes.cjs", diff --git a/tests/fixtures/install-tree/hermes.json b/tests/fixtures/install-tree/hermes.json index baf3fb08a..6ae9455a1 100644 --- a/tests/fixtures/install-tree/hermes.json +++ b/tests/fixtures/install-tree/hermes.json @@ -351,6 +351,7 @@ "hooks/gsd-windsurf-pre-write.js", "hooks/gsd-workflow-guard.js", "hooks/gsd-worktree-path-guard.js", + "hooks/lib/cursor-workspace.js", "hooks/lib/git-cmd.js", "hooks/lib/gsd-graphify-rebuild.sh", "hooks/managed-hooks-registry.cjs", diff --git a/tests/fixtures/install-tree/kilo.json b/tests/fixtures/install-tree/kilo.json index a4867235a..5a17d20e4 100644 --- a/tests/fixtures/install-tree/kilo.json +++ b/tests/fixtures/install-tree/kilo.json @@ -422,6 +422,7 @@ "hooks/gsd-windsurf-pre-write.js", "hooks/gsd-workflow-guard.js", "hooks/gsd-worktree-path-guard.js", + "hooks/lib/cursor-workspace.js", "hooks/lib/git-cmd.js", "hooks/lib/gsd-graphify-rebuild.sh", "hooks/managed-hooks-registry.cjs", diff --git a/tests/fixtures/install-tree/kimi-code.json b/tests/fixtures/install-tree/kimi-code.json index a3905629d..c0f0e5032 100644 --- a/tests/fixtures/install-tree/kimi-code.json +++ b/tests/fixtures/install-tree/kimi-code.json @@ -25,6 +25,7 @@ ".kimi/hooks/gsd-windsurf-pre-write.js", ".kimi/hooks/gsd-workflow-guard.js", ".kimi/hooks/gsd-worktree-path-guard.js", + ".kimi/hooks/lib/cursor-workspace.js", ".kimi/hooks/lib/git-cmd.js", ".kimi/hooks/lib/gsd-graphify-rebuild.sh", ".kimi/hooks/managed-hooks-registry.cjs", diff --git a/tests/fixtures/install-tree/kimi.json b/tests/fixtures/install-tree/kimi.json index 15d2bd4a2..9547457d0 100644 --- a/tests/fixtures/install-tree/kimi.json +++ b/tests/fixtures/install-tree/kimi.json @@ -25,6 +25,7 @@ ".kimi/hooks/gsd-windsurf-pre-write.js", ".kimi/hooks/gsd-workflow-guard.js", ".kimi/hooks/gsd-worktree-path-guard.js", + ".kimi/hooks/lib/cursor-workspace.js", ".kimi/hooks/lib/git-cmd.js", ".kimi/hooks/lib/gsd-graphify-rebuild.sh", ".kimi/hooks/managed-hooks-registry.cjs", diff --git a/tests/fixtures/install-tree/opencode.json b/tests/fixtures/install-tree/opencode.json index e5a9af99f..237c1f12a 100644 --- a/tests/fixtures/install-tree/opencode.json +++ b/tests/fixtures/install-tree/opencode.json @@ -422,6 +422,7 @@ "hooks/gsd-windsurf-pre-write.js", "hooks/gsd-workflow-guard.js", "hooks/gsd-worktree-path-guard.js", + "hooks/lib/cursor-workspace.js", "hooks/lib/git-cmd.js", "hooks/lib/gsd-graphify-rebuild.sh", "hooks/managed-hooks-registry.cjs", diff --git a/tests/fixtures/install-tree/pi.json b/tests/fixtures/install-tree/pi.json index 8d910bf75..dd9b91d95 100644 --- a/tests/fixtures/install-tree/pi.json +++ b/tests/fixtures/install-tree/pi.json @@ -318,6 +318,7 @@ "hooks/gsd-windsurf-pre-write.js", "hooks/gsd-workflow-guard.js", "hooks/gsd-worktree-path-guard.js", + "hooks/lib/cursor-workspace.js", "hooks/lib/git-cmd.js", "hooks/lib/gsd-graphify-rebuild.sh", "hooks/managed-hooks-registry.cjs", diff --git a/tests/fixtures/install-tree/qwen.json b/tests/fixtures/install-tree/qwen.json index 2add04b98..72cff8197 100644 --- a/tests/fixtures/install-tree/qwen.json +++ b/tests/fixtures/install-tree/qwen.json @@ -351,6 +351,7 @@ "hooks/gsd-windsurf-pre-write.js", "hooks/gsd-workflow-guard.js", "hooks/gsd-worktree-path-guard.js", + "hooks/lib/cursor-workspace.js", "hooks/lib/git-cmd.js", "hooks/lib/gsd-graphify-rebuild.sh", "hooks/managed-hooks-registry.cjs",