gsd-pi 2.78.1-dev.b0759e59b → 2.78.1-dev.e9d88a536
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/dist/headless-recover.d.ts +23 -0
- package/dist/headless-recover.js +93 -0
- package/dist/headless.js +9 -0
- package/dist/help-text.js +1 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/tools/intent.js +8 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +4 -56
- package/dist/resources/extensions/gsd/auto-post-unit.js +7 -27
- package/dist/resources/extensions/gsd/auto-start.js +1 -8
- package/dist/resources/extensions/gsd/auto-worktree.js +59 -176
- package/dist/resources/extensions/gsd/auto.js +24 -6
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -77
- package/dist/resources/extensions/gsd/commands-codebase.js +2 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +5 -5
- package/dist/resources/extensions/gsd/commands-logs.js +2 -2
- package/dist/resources/extensions/gsd/commands-scan.js +2 -2
- package/dist/resources/extensions/gsd/commands-ship.js +2 -2
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +5 -5
- package/dist/resources/extensions/gsd/db-writer.js +16 -85
- package/dist/resources/extensions/gsd/dispatch-guard.js +6 -10
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -2
- package/dist/resources/extensions/gsd/gsd-db.js +74 -8
- package/dist/resources/extensions/gsd/guided-flow.js +31 -8
- package/dist/resources/extensions/gsd/markdown-renderer.js +14 -51
- package/dist/resources/extensions/gsd/parallel-merge.js +14 -13
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +5 -2
- package/dist/resources/extensions/gsd/paths.js +35 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
- package/dist/resources/extensions/gsd/queue-order.js +6 -1
- package/dist/resources/extensions/gsd/rethink.js +2 -2
- package/dist/resources/extensions/gsd/state.js +91 -372
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -5
- package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -12
- package/dist/resources/extensions/gsd/tools/complete-task.js +19 -31
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -5
- package/dist/resources/extensions/gsd/workflow-manifest.js +2 -1
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -21
- package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -3
- package/dist/resources/extensions/gsd/worktree-command.js +4 -3
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/chunks/6336.js +1 -0
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +6 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +56 -2
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/parse-workflow-args.test.ts +80 -0
- package/packages/mcp-server/src/workflow-tools.ts +61 -2
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/browser-tools/tools/intent.ts +13 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +4 -60
- package/src/resources/extensions/gsd/auto-post-unit.ts +7 -26
- package/src/resources/extensions/gsd/auto-start.ts +1 -8
- package/src/resources/extensions/gsd/auto-worktree.ts +61 -204
- package/src/resources/extensions/gsd/auto.ts +23 -6
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +9 -84
- package/src/resources/extensions/gsd/commands-codebase.ts +2 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +5 -5
- package/src/resources/extensions/gsd/commands-logs.ts +2 -2
- package/src/resources/extensions/gsd/commands-scan.ts +2 -2
- package/src/resources/extensions/gsd/commands-ship.ts +2 -2
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +5 -5
- package/src/resources/extensions/gsd/db-writer.ts +16 -83
- package/src/resources/extensions/gsd/dispatch-guard.ts +6 -11
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -2
- package/src/resources/extensions/gsd/gsd-db.ts +85 -8
- package/src/resources/extensions/gsd/guided-flow.ts +35 -8
- package/src/resources/extensions/gsd/markdown-renderer.ts +13 -64
- package/src/resources/extensions/gsd/parallel-merge.ts +14 -13
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +5 -2
- package/src/resources/extensions/gsd/paths.ts +55 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
- package/src/resources/extensions/gsd/queue-order.ts +6 -1
- package/src/resources/extensions/gsd/rethink.ts +2 -2
- package/src/resources/extensions/gsd/state.ts +91 -389
- package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +21 -34
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +6 -7
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +8 -6
- package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +12 -27
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +18 -5
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +14 -16
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +10 -38
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +136 -56
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +119 -61
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +6 -20
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +4 -5
- package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +14 -15
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +11 -16
- package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +15 -36
- package/src/resources/extensions/gsd/tests/handler-worktree-write-isolation.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +15 -15
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +15 -5
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +14 -8
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +25 -16
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +184 -0
- package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +4 -0
- package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +3 -4
- package/src/resources/extensions/gsd/tests/slice-disk-reconcile.test.ts +10 -56
- package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +15 -16
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +23 -27
- package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +13 -14
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +10 -33
- package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +12 -7
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +65 -71
- package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +26 -151
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -5
- package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -14
- package/src/resources/extensions/gsd/tools/complete-task.ts +19 -34
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +7 -5
- package/src/resources/extensions/gsd/workflow-manifest.ts +4 -1
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -18
- package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -3
- package/src/resources/extensions/gsd/worktree-command.ts +4 -3
- package/dist/web/standalone/.next/server/chunks/8527.js +0 -1
- /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → oZGTPvJBQX_IDKKnuV8Bt}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → oZGTPvJBQX_IDKKnuV8Bt}/_ssgManifest.js +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* gsdroot-worktree-detection.test.ts — Regression test for #2594.
|
|
3
3
|
*
|
|
4
|
-
* gsdRoot() must return the
|
|
5
|
-
* is inside a .gsd/worktrees/<name>/ structure
|
|
6
|
-
*
|
|
4
|
+
* gsdRoot() must return the canonical project .gsd directory when basePath
|
|
5
|
+
* is inside a .gsd/worktrees/<name>/ structure. Worktree-local .gsd folders
|
|
6
|
+
* are legacy projection roots only; runtime state is DB-authoritative at the
|
|
7
|
+
* project .gsd.
|
|
7
8
|
*
|
|
8
9
|
* The bug: when a git worktree lives at /project/.gsd/worktrees/M008/,
|
|
9
10
|
* probeGsdRoot() runs `git rev-parse --show-toplevel` which can return the
|
|
@@ -20,7 +21,7 @@ import { mkdtempSync, realpathSync } from "node:fs";
|
|
|
20
21
|
import { tmpdir } from "node:os";
|
|
21
22
|
import { spawnSync } from "node:child_process";
|
|
22
23
|
|
|
23
|
-
import { gsdRoot, _clearGsdRootCache } from "../paths.ts";
|
|
24
|
+
import { gsdRoot, resolveGsdPathContract, _clearGsdRootCache } from "../paths.ts";
|
|
24
25
|
|
|
25
26
|
describe("gsdRoot() worktree detection (#2594)", () => {
|
|
26
27
|
let projectRoot: string;
|
|
@@ -61,7 +62,7 @@ describe("gsdRoot() worktree detection (#2594)", () => {
|
|
|
61
62
|
rmSync(projectRoot, { recursive: true, force: true });
|
|
62
63
|
});
|
|
63
64
|
|
|
64
|
-
test("returns
|
|
65
|
+
test("returns project .gsd when basePath is a worktree with its own .gsd", () => {
|
|
65
66
|
// Simulates a worktree that already had copyPlanningArtifacts() run,
|
|
66
67
|
// so it has its own .gsd/ directory.
|
|
67
68
|
const worktreeBase = join(projectGsd, "worktrees", "M008");
|
|
@@ -71,41 +72,26 @@ describe("gsdRoot() worktree detection (#2594)", () => {
|
|
|
71
72
|
const result = gsdRoot(worktreeBase);
|
|
72
73
|
assert.equal(
|
|
73
74
|
result,
|
|
74
|
-
|
|
75
|
-
`Expected
|
|
76
|
-
"gsdRoot() should use the fast path for an existing worktree .gsd.",
|
|
75
|
+
projectGsd,
|
|
76
|
+
`Expected canonical project .gsd (${projectGsd}), got ${result}.`,
|
|
77
77
|
);
|
|
78
|
+
assert.equal(resolveGsdPathContract(worktreeBase).worktreeGsd, worktreeGsd);
|
|
78
79
|
});
|
|
79
80
|
|
|
80
|
-
test("returns
|
|
81
|
-
// This is the core #2594 bug: the worktree directory exists but its .gsd
|
|
82
|
-
// subdirectory hasn't been created yet. Without the fix, probeGsdRoot()
|
|
83
|
-
// walks up from the worktree path, finds /project/.gsd, and returns it.
|
|
84
|
-
// With the fix, it detects the .gsd/worktrees/<name>/ pattern and returns
|
|
85
|
-
// the worktree-local .gsd path as the creation fallback.
|
|
81
|
+
test("returns project .gsd when worktree .gsd does not exist yet", () => {
|
|
86
82
|
const worktreeBase = join(projectGsd, "worktrees", "M008");
|
|
87
83
|
mkdirSync(worktreeBase, { recursive: true });
|
|
88
84
|
// NOTE: no .gsd/ inside worktreeBase
|
|
89
85
|
|
|
90
86
|
const result = gsdRoot(worktreeBase);
|
|
91
|
-
const expected = join(worktreeBase, ".gsd");
|
|
92
|
-
|
|
93
|
-
// Without the fix, this returns projectGsd (/project/.gsd) because the
|
|
94
|
-
// walk-up from worktreeBase finds it. With the fix, it returns the
|
|
95
|
-
// worktree-local path.
|
|
96
|
-
assert.notEqual(
|
|
97
|
-
result,
|
|
98
|
-
projectGsd,
|
|
99
|
-
"gsdRoot() must NOT return the project root .gsd when basePath is inside .gsd/worktrees/",
|
|
100
|
-
);
|
|
101
87
|
assert.equal(
|
|
102
88
|
result,
|
|
103
|
-
|
|
104
|
-
`Expected
|
|
89
|
+
projectGsd,
|
|
90
|
+
`Expected canonical project .gsd (${projectGsd}), got ${result}.`,
|
|
105
91
|
);
|
|
106
92
|
});
|
|
107
93
|
|
|
108
|
-
test("returns
|
|
94
|
+
test("returns project .gsd when basePath is a real git worktree inside .gsd/worktrees/", () => {
|
|
109
95
|
// Create a real git worktree at .gsd/worktrees/M010
|
|
110
96
|
const worktreeName = "M010";
|
|
111
97
|
const worktreeBase = join(projectGsd, "worktrees", worktreeName);
|
|
@@ -125,17 +111,10 @@ describe("gsdRoot() worktree detection (#2594)", () => {
|
|
|
125
111
|
|
|
126
112
|
// The real git worktree exists at worktreeBase but has NO .gsd/ subdir yet
|
|
127
113
|
const gsdResult = gsdRoot(worktreeBase);
|
|
128
|
-
const expected = join(worktreeBase, ".gsd");
|
|
129
|
-
|
|
130
|
-
assert.notEqual(
|
|
131
|
-
gsdResult,
|
|
132
|
-
projectGsd,
|
|
133
|
-
"gsdRoot() must NOT escape to project root .gsd from inside a git worktree",
|
|
134
|
-
);
|
|
135
114
|
assert.equal(
|
|
136
115
|
gsdResult,
|
|
137
|
-
|
|
138
|
-
`Expected
|
|
116
|
+
projectGsd,
|
|
117
|
+
`Expected canonical project .gsd (${projectGsd}), got ${gsdResult}`,
|
|
139
118
|
);
|
|
140
119
|
|
|
141
120
|
// Cleanup worktree
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression: when /gsd handlers run with cwd inside a worktree, writes must
|
|
3
|
+
* land in the worktree's .gsd/, not the parent project's .gsd/.
|
|
4
|
+
*
|
|
5
|
+
* The fix in 01464a97 replaced `process.cwd()` with `projectRoot()` to block
|
|
6
|
+
* $HOME pollution, but `projectRoot()` walks UP from a worktree path to the
|
|
7
|
+
* outer project root — breaking the worktree isolation invariant agents rely
|
|
8
|
+
* on. The corrected pattern: handlers use `currentDirectoryRoot()`, which
|
|
9
|
+
* preserves the active cwd (worktree or project) and still throws when cwd
|
|
10
|
+
* is $HOME.
|
|
11
|
+
*/
|
|
12
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { mkdtempSync, mkdirSync, rmSync, realpathSync } from "node:fs";
|
|
15
|
+
import { tmpdir, homedir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
|
|
18
|
+
import { currentDirectoryRoot, projectRoot, withCommandCwd, GSDNoProjectError } from "../commands/context.ts";
|
|
19
|
+
|
|
20
|
+
describe("handlers preserve worktree cwd via currentDirectoryRoot()", () => {
|
|
21
|
+
let project: string;
|
|
22
|
+
let worktree: string;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
project = realpathSync(mkdtempSync(join(tmpdir(), "gsd-wt-iso-")));
|
|
26
|
+
mkdirSync(join(project, ".gsd"), { recursive: true });
|
|
27
|
+
mkdirSync(join(project, ".git"), { recursive: true });
|
|
28
|
+
worktree = join(project, ".gsd", "worktrees", "M001");
|
|
29
|
+
mkdirSync(join(worktree, ".gsd"), { recursive: true });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
rmSync(project, { recursive: true, force: true });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("currentDirectoryRoot() returns the worktree path when cwd is the worktree", async () => {
|
|
37
|
+
const resolved = await withCommandCwd(worktree, async () => currentDirectoryRoot());
|
|
38
|
+
assert.equal(resolved, worktree, "must keep worktree path so writes isolate");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("projectRoot() walks UP from worktree to the project root (legacy semantics)", async () => {
|
|
42
|
+
const resolved = await withCommandCwd(worktree, async () => projectRoot());
|
|
43
|
+
assert.equal(resolved, project, "projectRoot intentionally returns project, not worktree");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("currentDirectoryRoot() throws GSDNoProjectError when cwd is $HOME", async () => {
|
|
47
|
+
await assert.rejects(
|
|
48
|
+
withCommandCwd(homedir(), async () => currentDirectoryRoot()),
|
|
49
|
+
(err: unknown) => err instanceof GSDNoProjectError,
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("currentDirectoryRoot() returns project root when cwd is project root", async () => {
|
|
54
|
+
const resolved = await withCommandCwd(project, async () => currentDirectoryRoot());
|
|
55
|
+
assert.equal(resolved, project);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -473,26 +473,26 @@ test("mergeAllCompleted — by-completion order respects startedAt", async () =>
|
|
|
473
473
|
});
|
|
474
474
|
|
|
475
475
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
476
|
-
// Bug #2812 — determineMergeOrder should use
|
|
476
|
+
// Bug #2812 — determineMergeOrder should use DB state as source of truth
|
|
477
477
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
478
478
|
|
|
479
|
-
/** Set up
|
|
480
|
-
function
|
|
481
|
-
|
|
482
|
-
mkdirSync(
|
|
483
|
-
const dbPath = join(
|
|
479
|
+
/** Set up canonical DB with a milestone marked complete and a worktree marker dir */
|
|
480
|
+
function setupCanonicalDbWithWorktree(basePath: string, mid: string): void {
|
|
481
|
+
mkdirSync(join(basePath, ".gsd", "worktrees", mid), { recursive: true });
|
|
482
|
+
mkdirSync(join(basePath, ".gsd"), { recursive: true });
|
|
483
|
+
const dbPath = join(basePath, ".gsd", "gsd.db");
|
|
484
484
|
openDatabase(dbPath);
|
|
485
485
|
insertMilestone({ id: mid, title: `Milestone ${mid}`, status: "complete" });
|
|
486
486
|
updateMilestoneStatus(mid, "complete", new Date().toISOString());
|
|
487
487
|
closeDatabase();
|
|
488
488
|
}
|
|
489
489
|
|
|
490
|
-
test("determineMergeOrder — finds milestones completed in
|
|
490
|
+
test("determineMergeOrder — finds milestones completed in canonical DB even when worker state is 'error' (#2812)", () => {
|
|
491
491
|
const base = realpathSync(mkdtempSync(join(tmpdir(), "merge-db-bug-")));
|
|
492
492
|
try {
|
|
493
493
|
// Simulate the bug scenario: orchestrator has stale "error" state
|
|
494
|
-
// but the
|
|
495
|
-
|
|
494
|
+
// but the canonical DB shows milestone is actually complete.
|
|
495
|
+
setupCanonicalDbWithWorktree(base, "M011");
|
|
496
496
|
|
|
497
497
|
const workers = [
|
|
498
498
|
makeWorker({ milestoneId: "M010", state: "error" }),
|
|
@@ -502,12 +502,12 @@ test("determineMergeOrder — finds milestones completed in worktree DB even whe
|
|
|
502
502
|
|
|
503
503
|
const order = determineMergeOrder(workers, "sequential", base);
|
|
504
504
|
|
|
505
|
-
// M011 should be included because
|
|
505
|
+
// M011 should be included because the canonical DB says status='complete'
|
|
506
506
|
assert.ok(
|
|
507
507
|
order.includes("M011"),
|
|
508
|
-
`Expected M011 in merge order (
|
|
508
|
+
`Expected M011 in merge order (canonical DB says complete), got: [${order}]`,
|
|
509
509
|
);
|
|
510
|
-
// M010 and M012 should NOT be included (no
|
|
510
|
+
// M010 and M012 should NOT be included (no canonical complete status)
|
|
511
511
|
assert.ok(!order.includes("M010"), "M010 should not be in merge order (error, no DB)");
|
|
512
512
|
assert.ok(!order.includes("M012"), "M012 should not be in merge order (running, no DB)");
|
|
513
513
|
} finally {
|
|
@@ -529,7 +529,7 @@ test("determineMergeOrder — combines stopped workers and DB-complete milestone
|
|
|
529
529
|
const base = realpathSync(mkdtempSync(join(tmpdir(), "merge-dedup-")));
|
|
530
530
|
try {
|
|
531
531
|
// M001 is stopped in orchestrator AND complete in worktree DB
|
|
532
|
-
|
|
532
|
+
setupCanonicalDbWithWorktree(base, "M001");
|
|
533
533
|
|
|
534
534
|
const workers = [
|
|
535
535
|
makeWorker({ milestoneId: "M001", state: "stopped" }),
|
|
@@ -555,8 +555,8 @@ test("mergeAllCompleted — discovers DB-complete milestones when workers show e
|
|
|
555
555
|
]);
|
|
556
556
|
setupRoadmap(repo, "M011", "Feature System", ["S01: Feature module"]);
|
|
557
557
|
|
|
558
|
-
// Set up
|
|
559
|
-
|
|
558
|
+
// Set up canonical DB showing M011 is complete
|
|
559
|
+
setupCanonicalDbWithWorktree(repo, "M011");
|
|
560
560
|
|
|
561
561
|
// Orchestrator thinks M011 is in error (stale state)
|
|
562
562
|
const workers = [
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
updateTaskStatus,
|
|
47
47
|
updateSliceStatus,
|
|
48
48
|
updateMilestoneStatus,
|
|
49
|
+
insertAssessment,
|
|
49
50
|
insertReplanHistory,
|
|
50
51
|
getReplanHistory,
|
|
51
52
|
insertGateRow,
|
|
@@ -363,13 +364,13 @@ describe("state derivation failures", () => {
|
|
|
363
364
|
const state2 = await deriveState(base);
|
|
364
365
|
assert.equal(state2.phase, "executing", "cached result should still show executing");
|
|
365
366
|
|
|
366
|
-
// After explicit invalidation,
|
|
367
|
-
//
|
|
367
|
+
// After explicit invalidation, DB rows remain authoritative; PLAN.md is a
|
|
368
|
+
// projection and must not import missing task rows.
|
|
368
369
|
invalidateStateCache();
|
|
369
370
|
const state3 = await deriveState(base);
|
|
370
|
-
assert.equal(state3.phase, "
|
|
371
|
-
assert.equal(state3.activeTask
|
|
372
|
-
assert.deepEqual(state3.progress?.tasks, { done: 1, total:
|
|
371
|
+
assert.equal(state3.phase, "summarizing", "after cache invalidation should follow DB tasks only");
|
|
372
|
+
assert.equal(state3.activeTask, null, "disk-only plan task T02 should not be imported");
|
|
373
|
+
assert.deepEqual(state3.progress?.tasks, { done: 1, total: 1 });
|
|
373
374
|
});
|
|
374
375
|
|
|
375
376
|
test("corrupt ROADMAP: binary content does not crash deriveState", async () => {
|
|
@@ -486,12 +487,14 @@ describe("transition boundary failures", () => {
|
|
|
486
487
|
writeFileSync(join(mDir, "M001-CONTEXT-DRAFT.md"), "# Draft\nSome draft.\n");
|
|
487
488
|
|
|
488
489
|
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
490
|
+
insertMilestone({ id: "M001", title: "Draft", status: "needs-discussion" });
|
|
489
491
|
invalidateAllCaches();
|
|
490
492
|
const state1 = await deriveState(base);
|
|
491
493
|
assert.equal(state1.phase, "needs-discussion");
|
|
492
494
|
|
|
493
495
|
// Now write the full CONTEXT (simulates discussion completion)
|
|
494
496
|
writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001: Resolved\n\n## Purpose\nDone.\n");
|
|
497
|
+
updateMilestoneStatus("M001", "active");
|
|
495
498
|
|
|
496
499
|
invalidateAllCaches();
|
|
497
500
|
const state2 = await deriveState(base);
|
|
@@ -1148,6 +1151,13 @@ describe("completion and verification failures", () => {
|
|
|
1148
1151
|
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", status: "complete" });
|
|
1149
1152
|
insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", status: "complete" });
|
|
1150
1153
|
insertTask({ id: "T01", sliceId: "S02", milestoneId: "M001", status: "complete" });
|
|
1154
|
+
insertAssessment({
|
|
1155
|
+
path: "milestones/M001/M001-VALIDATION.md",
|
|
1156
|
+
milestoneId: "M001",
|
|
1157
|
+
status: "pass",
|
|
1158
|
+
scope: "milestone-validation",
|
|
1159
|
+
fullContent: "verdict: pass",
|
|
1160
|
+
});
|
|
1151
1161
|
|
|
1152
1162
|
invalidateAllCaches();
|
|
1153
1163
|
const state = await deriveStateFromDb(base);
|
|
@@ -856,10 +856,10 @@ test('── markdown-renderer: renderAllFromDb produces all files ──', asyn
|
|
|
856
856
|
});
|
|
857
857
|
|
|
858
858
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
859
|
-
//
|
|
859
|
+
// DB-authoritative regeneration
|
|
860
860
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
861
861
|
|
|
862
|
-
test('── markdown-renderer:
|
|
862
|
+
test('── markdown-renderer: missing artifact regenerates from DB without importing disk projection ──', async () => {
|
|
863
863
|
const tmpDir = makeTmpDir();
|
|
864
864
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
865
865
|
openDatabase(dbPath);
|
|
@@ -874,7 +874,7 @@ test('── markdown-renderer: graceful fallback reads from disk when artifact
|
|
|
874
874
|
// Write roadmap to disk but NOT in artifacts DB
|
|
875
875
|
const roadmapContent = makeRoadmapContent([
|
|
876
876
|
{ id: 'S01', title: 'Core', done: false },
|
|
877
|
-
]);
|
|
877
|
+
]) + '\n\nDISK_ONLY_SENTINEL';
|
|
878
878
|
const roadmapPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'M001-ROADMAP.md');
|
|
879
879
|
fs.writeFileSync(roadmapPath, roadmapContent);
|
|
880
880
|
clearAllCaches();
|
|
@@ -883,14 +883,20 @@ test('── markdown-renderer: graceful fallback reads from disk when artifact
|
|
|
883
883
|
const before = getArtifact('milestones/M001/M001-ROADMAP.md');
|
|
884
884
|
assert.deepStrictEqual(before, null, 'artifact not in DB before render');
|
|
885
885
|
|
|
886
|
-
// Render — should
|
|
886
|
+
// Render — should regenerate from DB rows, not import/patch disk content.
|
|
887
887
|
const ok = await renderRoadmapCheckboxes(tmpDir, 'M001');
|
|
888
|
-
assert.ok(ok, 'render succeeds
|
|
888
|
+
assert.ok(ok, 'render succeeds by regenerating from DB');
|
|
889
889
|
|
|
890
|
-
// Verify artifact now in DB
|
|
890
|
+
// Verify artifact now exists in DB but does not contain disk-only content.
|
|
891
891
|
const after = getArtifact('milestones/M001/M001-ROADMAP.md');
|
|
892
|
-
assert.ok(after !== null, 'artifact
|
|
893
|
-
assert.ok(after!.full_content.includes('
|
|
892
|
+
assert.ok(after !== null, 'artifact regenerated in DB');
|
|
893
|
+
assert.ok(!after!.full_content.includes('DISK_ONLY_SENTINEL'), 'disk projection content was not imported');
|
|
894
|
+
assert.ok(after!.full_content.includes('S01'), 'DB artifact reflects DB slice state');
|
|
895
|
+
|
|
896
|
+
assert.ok(fs.existsSync(roadmapPath), 'roadmap projection regenerated on disk');
|
|
897
|
+
const diskAfter = fs.readFileSync(roadmapPath, 'utf-8');
|
|
898
|
+
assert.ok(!diskAfter.includes('DISK_ONLY_SENTINEL'), 'disk projection was rewritten from DB');
|
|
899
|
+
assert.ok(diskAfter.includes('S01'), 'disk projection reflects DB slice state');
|
|
894
900
|
} finally {
|
|
895
901
|
closeDatabase();
|
|
896
902
|
cleanupDir(tmpDir);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getRequirementById,
|
|
10
10
|
getActiveRequirements,
|
|
11
11
|
insertArtifact,
|
|
12
|
+
SCHEMA_VERSION,
|
|
12
13
|
_getAdapter,
|
|
13
14
|
} from '../gsd-db.ts';
|
|
14
15
|
import {
|
|
@@ -363,7 +364,7 @@ test('md-importer: schema v1→v2 migration', () => {
|
|
|
363
364
|
openDatabase(':memory:');
|
|
364
365
|
const adapter = _getAdapter();
|
|
365
366
|
const version = adapter?.prepare('SELECT MAX(version) as v FROM schema_version').get();
|
|
366
|
-
assert.deepStrictEqual(version?.v,
|
|
367
|
+
assert.deepStrictEqual(version?.v, SCHEMA_VERSION, `new DB should be at schema version ${SCHEMA_VERSION}`);
|
|
367
368
|
|
|
368
369
|
// Artifacts table should exist
|
|
369
370
|
const tableCheck = adapter?.prepare("SELECT count(*) as c FROM sqlite_master WHERE type='table' AND name='artifacts'").get();
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
openDatabase,
|
|
3
3
|
closeDatabase,
|
|
4
4
|
isDbAvailable,
|
|
5
|
+
SCHEMA_VERSION,
|
|
5
6
|
_getAdapter,
|
|
6
7
|
} from '../gsd-db.ts';
|
|
7
8
|
import {
|
|
@@ -328,9 +329,9 @@ test('memory-store: schema includes memories table', () => {
|
|
|
328
329
|
const viewCount = adapter.prepare('SELECT count(*) as cnt FROM active_memories').get();
|
|
329
330
|
assert.deepStrictEqual(viewCount?.['cnt'], 0, 'active_memories view should exist');
|
|
330
331
|
|
|
331
|
-
// Verify schema version is
|
|
332
|
+
// Verify schema version is current (includes quality_gates DDL fix and later migrations)
|
|
332
333
|
const version = adapter.prepare('SELECT MAX(version) as v FROM schema_version').get();
|
|
333
|
-
assert.deepStrictEqual(version?.["v"],
|
|
334
|
+
assert.deepStrictEqual(version?.["v"], SCHEMA_VERSION, `schema version should be ${SCHEMA_VERSION}`);
|
|
334
335
|
|
|
335
336
|
closeDatabase();
|
|
336
337
|
});
|
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
} from "../gsd-db.ts";
|
|
21
21
|
import { createWorktree } from "../worktree-manager.ts";
|
|
22
22
|
|
|
23
|
+
// This suite exercises the explicit legacy markdown derivation path.
|
|
24
|
+
process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK = '1';
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
@@ -6,6 +6,7 @@ import assert from "node:assert/strict";
|
|
|
6
6
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { tmpdir } from "node:os";
|
|
9
|
+
import { performance } from "node:perf_hooks";
|
|
9
10
|
|
|
10
11
|
import {
|
|
11
12
|
openDatabase,
|
|
@@ -99,21 +100,21 @@ test("ADR-011: sketch slice + progressive_planning ON → phase='refining'", asy
|
|
|
99
100
|
assert.equal(state.phase, "refining", "sketch slice with flag ON must yield refining phase");
|
|
100
101
|
});
|
|
101
102
|
|
|
102
|
-
test("ADR-011: sketch slice + progressive_planning OFF →
|
|
103
|
+
test("ADR-011: sketch slice + progressive_planning OFF → DB sketch metadata still yields refining", async (t) => {
|
|
103
104
|
const originalCwd = process.cwd();
|
|
104
105
|
const base = makeFixtureBase();
|
|
105
106
|
t.after(() => cleanup(base, originalCwd));
|
|
106
107
|
|
|
107
108
|
seedMilestoneWithSketchedS02(base);
|
|
108
109
|
writeS01Artifacts(base);
|
|
109
|
-
// Write a PREFERENCES.md without the flag
|
|
110
|
-
//
|
|
110
|
+
// Write a PREFERENCES.md without the flag. DB slice metadata remains
|
|
111
|
+
// authoritative for whether this slice needs refinement.
|
|
111
112
|
writePreferences(base, "phases:\n skip_research: false");
|
|
112
113
|
process.chdir(base);
|
|
113
114
|
|
|
114
115
|
const state = await deriveStateFromDb(base);
|
|
115
116
|
assert.equal(state.activeSlice?.id, "S02");
|
|
116
|
-
assert.equal(state.phase, "
|
|
117
|
+
assert.equal(state.phase, "refining", "flag absent must not override DB sketch metadata");
|
|
117
118
|
});
|
|
118
119
|
|
|
119
120
|
test("ADR-011: dispatch rule maps refining → refine-slice unit", async (t) => {
|
|
@@ -516,24 +517,32 @@ test("ADR-011 P3 #26: refine-slice dispatch latency is bounded vs plan-slice bas
|
|
|
516
517
|
await buildPlanSlicePrompt("M001", "Test", "S02", "Feature", base);
|
|
517
518
|
await buildRefineSlicePrompt("M001", "Test", "S02", "Feature", base);
|
|
518
519
|
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
520
|
+
const measure = async (fn: () => Promise<string>): Promise<number> => {
|
|
521
|
+
const start = performance.now();
|
|
522
|
+
await fn();
|
|
523
|
+
return performance.now() - start;
|
|
524
|
+
};
|
|
522
525
|
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
+
const planSamples: number[] = [];
|
|
527
|
+
const refineSamples: number[] = [];
|
|
528
|
+
for (let i = 0; i < 5; i++) {
|
|
529
|
+
planSamples.push(await measure(() => buildPlanSlicePrompt("M001", "Test", "S02", "Feature", base)));
|
|
530
|
+
refineSamples.push(await measure(() => buildRefineSlicePrompt("M001", "Test", "S02", "Feature", base)));
|
|
531
|
+
}
|
|
532
|
+
const bestPlan = Math.min(...planSamples);
|
|
533
|
+
const bestRefine = Math.min(...refineSamples);
|
|
526
534
|
|
|
527
535
|
assert.ok(
|
|
528
|
-
|
|
529
|
-
`refine-slice prompt build must complete under 500ms (
|
|
536
|
+
bestRefine < 500,
|
|
537
|
+
`refine-slice prompt build must complete under 500ms (best=${bestRefine.toFixed(1)}ms, samples=${refineSamples.map(n => n.toFixed(1)).join(",")})`,
|
|
530
538
|
);
|
|
531
539
|
// Guard the ratio only when the baseline is large enough to be meaningful —
|
|
532
|
-
// if plan-slice measures
|
|
533
|
-
|
|
540
|
+
// if plan-slice measures in single-digit milliseconds, the ratio is dominated
|
|
541
|
+
// by scheduler and filesystem noise under the concurrent test runner.
|
|
542
|
+
if (bestPlan >= 20) {
|
|
534
543
|
assert.ok(
|
|
535
|
-
|
|
536
|
-
`refine-slice must not exceed 3x plan-slice baseline (refine=${
|
|
544
|
+
bestRefine < bestPlan * 3,
|
|
545
|
+
`refine-slice must not exceed 3x plan-slice baseline (refine=${bestRefine.toFixed(1)}ms, plan=${bestPlan.toFixed(1)}ms)`,
|
|
537
546
|
);
|
|
538
547
|
}
|
|
539
548
|
});
|