fix(#2587): resolve cursor hook workspace from workspace_roots, not cwd (#2680)

* fix(#2587): resolve cursor hook workspace from workspace_roots, not cwd

gsd-cursor-session-start.js and gsd-cursor-stop.js both resolved the project as
path.join(process.cwd(), '.planning', 'STATE.md'). Under the cursor-agent CLI,
hooks are invoked with cwd set to the Cursor config dir (~/.cursor), not the
workspace — so the lookup always missed. sessionStart could only ever emit the
"no .planning/ workflow found" nudge and stop's verify-work reminder could never
fire, even with .planning/STATE.md sitting in the workspace. Slash commands were
unaffected, which is why only the hook layer looked blind.

Both hooks already buffered stdin into `raw` and never parsed it; the payload's
workspace_roots carries the real path.

Multi-root was left open in the report ("first root vs any root"). Resolved
forward: prefer the first root that actually carries .planning/STATE.md, so a
workspace whose GSD project is not the first root still resolves — strictly
better than first-root-only and identical to it in the single-root CLI case.
Falls back to roots[0], then to cwd, keeping IDE behavior unchanged if the IDE
ever invokes hooks from the workspace.

The resolver is duplicated verbatim across the two scripts rather than shared via
hooks/lib/: these hooks ship standalone, and a new hooks/lib/ file must be
registered in the GENERATED installer's GSD_HOOK_LIB_FILES allowlist — the
installer-omits-shipped-file class that yields MODULE_NOT_FOUND at runtime. Per
CLAUDE.md "Generative Fix Divergence", the duplication carries a parity assertion
so the copies cannot drift.

Failing-first, demonstrated by direct invocation with cwd != workspace:
  pre-fix  sessionStart -> "no .planning/ workflow found"   stop -> {}
  post-fix sessionStart -> ".planning/STATE.md is present"  stop -> reminder

tests/fix-2587-cursor-hook-workspace-roots.test.cjs spawns the real scripts as
child processes with a cwd lacking .planning/ and workspace_roots pointing at it.
Boundary coverage on the roots array (0 / 1 / 2 entries), plus malformed-JSON
fail-open, junk-entry filtering, the parity assertion, and a guard that neither
script resolves .planning from cwd again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015TCwhbMuY37DzRMCfzTABJ

* fix(#2587): extend workspace_roots fix to subagentStart; keep cwd a candidate

Three findings from the isolated review, all fixed.

1. MISSED SITE (high). gsd-cursor-subagent-start.js carried the identical
   defect at line 43 — its own header documents workspace_roots in the input
   schema, but it resolved .planning/ from process.cwd() anyway. Under the
   cursor-agent CLI that meant every Cursor subagent (planner, executor,
   verifier) started with "no .planning/ workflow found" and no phase context.
   The report named only sessionStart and stop; the defect class was wider.
   Verified pre-fix vs post-fix by direct invocation with cwd != workspace.

2. SEMANTIC NARROWING (medium). The first cut searched only workspace_roots and
   fell back to cwd solely when the array was EMPTY. So when roots were supplied
   but none carried .planning/ while cwd did, the hook reported absent — where
   the pre-fix code, which always used cwd, reported present. That contradicted
   the fallback's own stated intent of preserving IDE behavior. cwd is now a
   CANDIDATE in the search (`[...roots, process.cwd()]`), so the fix is a strict
   superset of both the old behavior and the CLI fix, never a narrowing.

3. STALE GOLDEN FIXTURES (high, would have failed CI). The golden-install-parity
   fixtures store a content hash per installed file; these three hooks appear in
   13 of the 19 runtime fixtures. Regenerated via `npm run gen:golden` — the
   diff is exactly the three hook hashes in exactly those 13 runtimes.

Tests extended: subagentStart resolution via workspace_roots; the stop hook's
absent branch (previously only session-start's was covered); an explicit
regression guard that a project at cwd is still found when roots miss; parity now
asserts all THREE copies byte-identical; and the cwd guard sweeps the whole
RESOLVING_HOOKS list so a future hook in this family cannot be left on cwd.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015TCwhbMuY37DzRMCfzTABJ

* refactor(#2587): extract cursor workspace resolution to a shared hooks/lib module

The duplicate-plus-parity-test approach was the wrong call. The reported issue
named two hooks; a third (subagentStart) had the identical defect. That is the
signature of a systemic problem, and three copies of a resolver guarded by a
parity assertion is a divergence risk maintained by hand rather than a fix.

hooks/lib/cursor-workspace.js is now the single implementation. All three Cursor
hooks require it; none defines a local copy. Divergence is prevented
structurally instead of by asserting three copies stay byte-identical.

The reason duplication looked necessary was real, and is fixed properly here
rather than worked around: Cursor sets hostBehaviors.skipSharedHooksInstall
(#2089), so it never reaches the installer's bulk hooks/lib copy — it was the
ONE runtime shipping these hooks WITHOUT hooks/lib (verified against all 19
golden fixtures: cursor had the hook scripts, no lib). A naive require would
have thrown MODULE_NOT_FOUND at load, BEFORE each hook's own try/catch, wedging
every session on precisely the runtime this bug is about.

writeCursorHooksJson (src/runtime-hooks-surface.cts) now stages the hooks/lib
helpers the staged scripts actually require, discovered by scanning their
require('./lib/…') calls rather than a hardcoded name — so a future helper
cannot be silently omitted. This is narrower than flipping
skipSharedHooksInstall, which would wrongly pull in every shared hook.
cursor-workspace.js is also added to GSD_HOOK_LIB_FILES so uninstall and the
manifest manage it for the runtimes that do receive hooks/lib.

Verified against a REAL install (runMinimalInstall, cursor/global): the helper
is staged, and all three INSTALLED hooks resolve the workspace end-to-end from a
cwd that is not the project.

Also closes the review gap that the stop hook was excluded from the
cwd-candidate regression loop — it now sweeps RESOLVING_HOOKS. The byte-parity
test is replaced by a structural guard (every hook requires the shared module,
none redefines it) plus a new install test asserting the helper is staged and
the installed hook actually loads against it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015TCwhbMuY37DzRMCfzTABJ

* fix(#2587): fail loud on a missing hook lib source; drop unsubstituted version marker

Two findings from the installer-focused review.

H1 — the staging step's `if (!fs.existsSync(libSrc)) continue;` silently defeated
the very guarantee it was added for. Reproduced: delete hooks/lib/cursor-workspace.js
from source, run the cursor install — it exits 0, prints "Done!", and ships the
three hook scripts with an EMPTY hooks/lib/. The installed hook then throws
`Cannot find module './lib/cursor-workspace.js'` at load, before its own
try/catch, wedging every session — and nothing surfaces until a user hits it.
The scan protected against a required-but-UNLISTED helper while leaving
required-but-MISSING wide open (typo, bad rebase, an accidental delete).
It now throws: a missing helper source is a packaging bug and aborts the install.

M1 — hooks/lib/cursor-workspace.js carried a `gsd-hook-version: <placeholder>`
marker that NOTHING substitutes: copyLibDir stamps .sh files only, and
writeCursorHooksJson's staging applies just the colon-to-dash rewrite. Verified
the literal was reaching disk on both the bulk (--claude) and Cursor
(--cursor) paths. hooks/lib/git-cmd.js — the only pre-existing hooks/lib/*.js —
carries no such marker, so this was newly introduced, not inherited. Marker
removed, matching that precedent, with a note on why. (The explanatory comment
deliberately does not spell the token out, or it would reintroduce the literal.)

M2 — the require-scan regex demanded the exact compact form, so
`require( "./lib/x.js" )` would silently fail to stage its helper and compound
H1. Now tolerant of interior whitespace and either quote style.

Regression test added for H1 — the reviewer confirmed the invariant had zero
coverage repo-wide: a source tree carrying the hooks but no hooks/lib/ must make
writeCursorHooksJson throw rather than produce a broken install.

Re-verified end to end: the missing-source case throws, no unsubstituted literal
ships, and the installed hook still resolves the workspace from a foreign cwd.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015TCwhbMuY37DzRMCfzTABJ

* chore(#2587): backfill changeset pr number (#2680)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-07-26 19:57:58 -04:00
committed by GitHub
parent c87f6f358e
commit c7c2fe3c2b
34 changed files with 567 additions and 46 deletions

View File

@@ -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)

View File

@@ -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',

View File

@@ -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 }));

View File

@@ -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:

View File

@@ -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 }));

View File

@@ -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 <config>/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 };

View File

@@ -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<string>();
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<string, string | null> = {};
for (const ev of events) {

View File

@@ -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)`,
);
}
});
});

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",