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
|
@@ -40,6 +40,12 @@ test("auto.ts validates milestone before restoring paused session (#1664)", () =
|
|
|
40
40
|
"auto.ts must check for SUMMARY file to detect completed milestones",
|
|
41
41
|
);
|
|
42
42
|
|
|
43
|
+
assert.ok(
|
|
44
|
+
source.includes("await ensureDbOpen(base)") &&
|
|
45
|
+
source.indexOf("await ensureDbOpen(base)") < source.indexOf('resolveMilestoneFile(base, meta.milestoneId, "SUMMARY")'),
|
|
46
|
+
"auto.ts must open the canonical DB before using SUMMARY as a paused-session fallback",
|
|
47
|
+
);
|
|
48
|
+
|
|
43
49
|
// Resume path must sanitize paused session file metadata before unlink/recovery.
|
|
44
50
|
assert.ok(
|
|
45
51
|
source.includes("normalizeSessionFilePath(meta.sessionFile ?? null)"),
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Regression test for
|
|
2
|
+
* Regression test for DB-authoritative rogue detection.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* re-dispatch. The fix calls updateSliceStatus() to sync the DB.
|
|
4
|
+
* A SUMMARY.md on disk is a projection/diagnostic. Runtime post-unit checks
|
|
5
|
+
* must not use it to mark the DB slice complete; explicit import/recovery
|
|
6
|
+
* commands own markdown-to-DB behavior.
|
|
8
7
|
*
|
|
9
|
-
* This structural test verifies
|
|
10
|
-
*
|
|
8
|
+
* This structural test verifies the complete-slice rogue branch reports the
|
|
9
|
+
* stale projection without calling updateSliceStatus().
|
|
11
10
|
*/
|
|
12
11
|
|
|
13
12
|
import { describe, test } from 'node:test';
|
|
@@ -22,38 +21,26 @@ const __dirname = dirname(__filename);
|
|
|
22
21
|
|
|
23
22
|
const source = readFileSync(join(__dirname, '..', 'auto-post-unit.ts'), 'utf-8');
|
|
24
23
|
|
|
25
|
-
describe('
|
|
26
|
-
test('updateSliceStatus is imported
|
|
27
|
-
assert.
|
|
28
|
-
'
|
|
24
|
+
describe('DB-authoritative slice rogue detection', () => {
|
|
25
|
+
test('updateSliceStatus is not imported for post-unit rogue reconciliation', () => {
|
|
26
|
+
assert.doesNotMatch(source, /import\s*\{[^}]*updateSliceStatus[^}]*\}\s*from\s*["']\.\/gsd-db/,
|
|
27
|
+
'auto-post-unit must not import updateSliceStatus for disk-to-DB reconciliation');
|
|
29
28
|
});
|
|
30
29
|
|
|
31
|
-
test('
|
|
32
|
-
assert.
|
|
33
|
-
'
|
|
30
|
+
test('complete-slice rogue branch does not mark DB complete from disk', () => {
|
|
31
|
+
assert.doesNotMatch(source, /updateSliceStatus\(mid,\s*sid,\s*["']complete["']/,
|
|
32
|
+
'SUMMARY.md on disk must not mark slice complete in DB');
|
|
34
33
|
});
|
|
35
34
|
|
|
36
|
-
test('
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
assert.ok(updateIdx > 0, 'updateSliceStatus call should exist');
|
|
41
|
-
|
|
42
|
-
// Find surrounding try-catch
|
|
43
|
-
const before = source.slice(Math.max(0, updateIdx - 200), updateIdx);
|
|
44
|
-
assert.match(before, /try\s*\{/,
|
|
45
|
-
'updateSliceStatus should be inside a try block');
|
|
46
|
-
|
|
47
|
-
// Bound the region to stop before the rogue fallback so /catch/ only
|
|
48
|
-
// matches this try block's catch, not an unrelated later one.
|
|
49
|
-
const after = extractSourceRegion(source, 'updateSliceStatus(mid, sid', 'rogues.push({');
|
|
50
|
-
assert.match(after, /catch/,
|
|
51
|
-
'try block should have a catch for fallback');
|
|
35
|
+
test('explicit rogue diagnostic reports stale slice summary projection', () => {
|
|
36
|
+
const branch = extractSourceRegion(source, 'unitType === "complete-slice"', 'unitType === "plan-milestone"');
|
|
37
|
+
assert.match(branch, /rogues\.push\(\{\s*path:\s*summaryPath,\s*unitType,\s*unitId\s*\}\)/,
|
|
38
|
+
'complete-slice branch should report stale SUMMARY.md as rogue');
|
|
52
39
|
});
|
|
53
40
|
|
|
54
|
-
test('
|
|
55
|
-
|
|
56
|
-
assert.
|
|
57
|
-
'
|
|
41
|
+
test('post-unit runtime does not call rogue diagnostics automatically', () => {
|
|
42
|
+
const postUnit = extractSourceRegion(source, 'export async function postUnitPostVerification');
|
|
43
|
+
assert.doesNotMatch(postUnit, /detectRogueFileWrites\(/,
|
|
44
|
+
'runtime post-unit path must not scan disk projections for rogue files');
|
|
58
45
|
});
|
|
59
46
|
});
|
|
@@ -41,7 +41,7 @@ const VALID_PARAMS = {
|
|
|
41
41
|
],
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
describe("complete-task
|
|
44
|
+
describe("complete-task projection failures keep DB completion committed", () => {
|
|
45
45
|
let base: string;
|
|
46
46
|
|
|
47
47
|
afterEach(() => {
|
|
@@ -75,7 +75,7 @@ describe("complete-task rollback cleans up verification_evidence (#2724)", () =>
|
|
|
75
75
|
assert.equal(rows.length, 2, "should have 2 evidence rows after success");
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
it("
|
|
78
|
+
it("keeps task completion and verification_evidence when disk projection write fails", async () => {
|
|
79
79
|
base = makeTmpBase();
|
|
80
80
|
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
81
81
|
insertMilestone({ id: "M001" });
|
|
@@ -87,20 +87,19 @@ describe("complete-task rollback cleans up verification_evidence (#2724)", () =>
|
|
|
87
87
|
writeFileSync(tasksDir, "not-a-directory");
|
|
88
88
|
|
|
89
89
|
const result = await handleCompleteTask(VALID_PARAMS, base);
|
|
90
|
-
assert.ok("error" in result,
|
|
90
|
+
assert.ok(!("error" in result), `unexpected error: ${"error" in result ? result.error : ""}`);
|
|
91
|
+
assert.equal(result.stale, true, "result should report stale projection");
|
|
91
92
|
|
|
92
|
-
// Task should be rolled back to pending
|
|
93
93
|
const adapter = _getAdapter()!;
|
|
94
94
|
const task = adapter.prepare(
|
|
95
95
|
`SELECT status FROM tasks WHERE milestone_id = 'M001' AND slice_id = 'S01' AND id = 'T01'`,
|
|
96
96
|
).get() as { status: string } | undefined;
|
|
97
97
|
assert.ok(task, "task row should still exist");
|
|
98
|
-
assert.equal(task!.status, "
|
|
98
|
+
assert.equal(task!.status, "complete", "task status should remain complete");
|
|
99
99
|
|
|
100
|
-
// Verification evidence should be cleaned up — no orphaned rows
|
|
101
100
|
const evidenceRows = adapter.prepare(
|
|
102
101
|
`SELECT * FROM verification_evidence WHERE task_id = 'T01' AND slice_id = 'S01' AND milestone_id = 'M001'`,
|
|
103
102
|
).all();
|
|
104
|
-
assert.equal(evidenceRows.length,
|
|
103
|
+
assert.equal(evidenceRows.length, 2, "verification_evidence should remain committed");
|
|
105
104
|
});
|
|
106
105
|
});
|
|
@@ -403,12 +403,11 @@ console.log('\n=== complete-task: handler idempotency ===');
|
|
|
403
403
|
const r1 = await handleCompleteTask(params, basePath);
|
|
404
404
|
assertTrue(!('error' in r1), 'first call should succeed');
|
|
405
405
|
|
|
406
|
-
// Verify complete-task did not duplicate T01.
|
|
407
|
-
// the remaining plan task
|
|
406
|
+
// Verify complete-task did not duplicate T01. S01-PLAN.md is a projection,
|
|
407
|
+
// so the remaining plan task is not imported implicitly.
|
|
408
408
|
const tasks = getSliceTasks('M001', 'S01');
|
|
409
|
-
assertEq(tasks.length,
|
|
409
|
+
assertEq(tasks.length, 1, 'should only have the completed DB task after first call');
|
|
410
410
|
assertEq(tasks.filter(t => t.id === 'T01').length, 1, 'should have exactly one T01 row after first call');
|
|
411
|
-
assertEq(tasks.find(t => t.id === 'T02')?.status, 'pending', 'T02 should be reconciled as pending');
|
|
412
411
|
|
|
413
412
|
// Second call with same params — state machine guard rejects (task is already complete)
|
|
414
413
|
const r2 = await handleCompleteTask(params, basePath);
|
|
@@ -419,7 +418,7 @@ console.log('\n=== complete-task: handler idempotency ===');
|
|
|
419
418
|
|
|
420
419
|
// Still no duplicate rows from the rejected second call.
|
|
421
420
|
const tasksAfter = getSliceTasks('M001', 'S01');
|
|
422
|
-
assertEq(tasksAfter.length,
|
|
421
|
+
assertEq(tasksAfter.length, 1, 'should still only have T01 after rejected second call');
|
|
423
422
|
assertEq(tasksAfter.filter(t => t.id === 'T01').length, 1, 'should still have exactly one T01 row');
|
|
424
423
|
|
|
425
424
|
cleanupDir(basePath);
|
|
@@ -447,10 +446,13 @@ console.log('\n=== complete-task: handler with missing plan file ===');
|
|
|
447
446
|
const params = makeValidParams();
|
|
448
447
|
const result = await handleCompleteTask(params, basePath);
|
|
449
448
|
|
|
450
|
-
// Should succeed
|
|
449
|
+
// Should succeed and regenerate the missing plan projection from DB.
|
|
451
450
|
assertTrue(!('error' in result), 'handler should succeed without plan file');
|
|
452
451
|
if (!('error' in result)) {
|
|
453
452
|
assertTrue(fs.existsSync(result.summaryPath), 'summary should be written even without plan file');
|
|
453
|
+
const planPath = path.join(basePath, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-PLAN.md');
|
|
454
|
+
assertTrue(fs.existsSync(planPath), 'missing plan projection should be regenerated from DB');
|
|
455
|
+
assertTrue(fs.readFileSync(planPath, 'utf-8').includes('[x] **T01:'), 'regenerated plan should reflect DB task completion');
|
|
454
456
|
}
|
|
455
457
|
|
|
456
458
|
cleanupDir(basePath);
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Behavioural regression test for
|
|
2
|
+
* Behavioural regression test for DB-authoritative task completion.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* timestamp, leaving completed_at NULL forever.
|
|
8
|
-
*
|
|
9
|
-
* The fix passes new Date().toISOString() as the 5th argument; this test
|
|
10
|
-
* exercises that path end-to-end and asserts the column is populated.
|
|
11
|
-
*
|
|
12
|
-
* Refs #4829 (rewrite from positional source-grep).
|
|
4
|
+
* A task SUMMARY.md on disk is a projection, not a completion command.
|
|
5
|
+
* deriveStateFromDb must not flip a pending DB task to complete or invent a
|
|
6
|
+
* completed_at timestamp from disk evidence.
|
|
13
7
|
*/
|
|
14
8
|
|
|
15
9
|
import { describe, test, beforeEach, afterEach } from 'node:test';
|
|
@@ -46,27 +40,26 @@ function setupProject(): void {
|
|
|
46
40
|
`# M001\n\n## Slices\n\n- [ ] **S01: Slice** \`risk:low\` \`depends:[]\`\n - After this: works\n`,
|
|
47
41
|
);
|
|
48
42
|
|
|
49
|
-
// Plan file for the slice
|
|
43
|
+
// Plan file for the slice. It is a projection and must not drive DB state.
|
|
50
44
|
writeFileSync(
|
|
51
45
|
join(basePath, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-PLAN.md'),
|
|
52
46
|
`# S01: Slice\n\n## Tasks\n\n- [ ] **T01: Test task** \`est:30m\`\n - Do: x\n - Verify: y\n`,
|
|
53
47
|
);
|
|
54
48
|
|
|
55
|
-
// The summary file
|
|
56
|
-
// status to "complete" inside reconcileSliceTasks.
|
|
49
|
+
// The summary file is a projection and must not complete the task.
|
|
57
50
|
writeFileSync(
|
|
58
51
|
join(basePath, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'tasks', 'T01-SUMMARY.md'),
|
|
59
52
|
'---\nid: T01\nparent: S01\nmilestone: M001\nblocker_discovered: false\n---\n# T01\n',
|
|
60
53
|
);
|
|
61
54
|
}
|
|
62
55
|
|
|
63
|
-
describe('completed_at
|
|
56
|
+
describe('completed_at DB-authoritative derivation', () => {
|
|
64
57
|
beforeEach(() => {
|
|
65
58
|
setupProject();
|
|
66
59
|
openDatabase(join(basePath, '.gsd', 'gsd.db'));
|
|
67
60
|
insertMilestone({ id: 'M001', title: 'M001', status: 'active' });
|
|
68
61
|
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'active' });
|
|
69
|
-
// Task is "pending" in DB,
|
|
62
|
+
// Task is "pending" in DB, even though SUMMARY.md exists on disk.
|
|
70
63
|
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Test task', status: 'pending' });
|
|
71
64
|
invalidateStateCache();
|
|
72
65
|
});
|
|
@@ -76,24 +69,16 @@ describe('completed_at reconcile (#4129)', () => {
|
|
|
76
69
|
try { rmSync(basePath, { recursive: true, force: true }); } catch { /* */ }
|
|
77
70
|
});
|
|
78
71
|
|
|
79
|
-
test('
|
|
72
|
+
test('deriveStateFromDb does not set completed_at from a disk SUMMARY projection', async () => {
|
|
80
73
|
const before = getTask('M001', 'S01', 'T01');
|
|
81
74
|
assert.strictEqual(before?.status, 'pending', 'task starts pending');
|
|
82
75
|
assert.strictEqual(before?.completed_at, null, 'task starts with completed_at NULL');
|
|
83
76
|
|
|
84
|
-
//
|
|
77
|
+
// Derive runtime state. Disk SUMMARY.md must not mutate the DB row.
|
|
85
78
|
await deriveStateFromDb(basePath);
|
|
86
79
|
|
|
87
80
|
const after = getTask('M001', 'S01', 'T01');
|
|
88
|
-
assert.strictEqual(after?.status, '
|
|
89
|
-
assert.
|
|
90
|
-
typeof after?.completed_at === 'string' && after.completed_at.length > 0,
|
|
91
|
-
`completed_at must be populated by reconcileSliceTasks (#4129); got ${JSON.stringify(after?.completed_at)}`,
|
|
92
|
-
);
|
|
93
|
-
// Sanity: timestamp parses as a valid ISO date.
|
|
94
|
-
assert.ok(
|
|
95
|
-
!Number.isNaN(Date.parse(after!.completed_at!)),
|
|
96
|
-
`completed_at should be a valid ISO timestamp, got ${after!.completed_at}`,
|
|
97
|
-
);
|
|
81
|
+
assert.strictEqual(after?.status, 'pending', 'task remains pending');
|
|
82
|
+
assert.strictEqual(after?.completed_at, null, 'completed_at remains NULL');
|
|
98
83
|
});
|
|
99
84
|
});
|
|
@@ -58,13 +58,26 @@ test("#2313: syncStateToProjectRoot should sync metrics.json", () => {
|
|
|
58
58
|
);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
test("
|
|
61
|
+
test("syncStateToProjectRoot should back-sync completed-units.json", () => {
|
|
62
|
+
const syncSrcPath = join(import.meta.dirname, "..", "auto-worktree.ts");
|
|
63
|
+
const syncSrc = readFileSync(syncSrcPath, "utf-8");
|
|
64
|
+
const fnIdx = syncSrc.indexOf("export function syncStateToProjectRoot(");
|
|
65
|
+
assert.ok(fnIdx !== -1, "syncStateToProjectRoot exists");
|
|
66
|
+
const fnBlock = syncSrc.slice(fnIdx, syncSrc.indexOf("// ─── Resource Staleness", fnIdx));
|
|
67
|
+
|
|
68
|
+
assert.ok(
|
|
69
|
+
fnBlock.includes('"completed-units.json"'),
|
|
70
|
+
"syncStateToProjectRoot should copy completed-units.json back to the project root",
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("#2313: syncWorktreeStateBack should include metrics.json in ROOT_DIAGNOSTIC_FILES", () => {
|
|
62
75
|
const autoWorktreeSrcPath = join(import.meta.dirname, "..", "auto-worktree.ts");
|
|
63
76
|
const autoWorktreeSrc = readFileSync(autoWorktreeSrcPath, "utf-8");
|
|
64
77
|
|
|
65
|
-
// Find the
|
|
66
|
-
const constIdx = autoWorktreeSrc.indexOf("
|
|
67
|
-
assert.ok(constIdx !== -1, "
|
|
78
|
+
// Find the ROOT_DIAGNOSTIC_FILES constant used for worktree copy-back.
|
|
79
|
+
const constIdx = autoWorktreeSrc.indexOf("ROOT_DIAGNOSTIC_FILES");
|
|
80
|
+
assert.ok(constIdx !== -1, "ROOT_DIAGNOSTIC_FILES constant exists");
|
|
68
81
|
|
|
69
82
|
// Get the array content
|
|
70
83
|
const arrayStart = autoWorktreeSrc.indexOf("[", constIdx);
|
|
@@ -73,7 +86,7 @@ test("#2313: syncWorktreeStateBack should include metrics.json in ROOT_STATE_FIL
|
|
|
73
86
|
|
|
74
87
|
assert.ok(
|
|
75
88
|
rootFilesBlock.includes("metrics.json"),
|
|
76
|
-
"metrics.json should be in
|
|
89
|
+
"metrics.json should be in ROOT_DIAGNOSTIC_FILES list",
|
|
77
90
|
);
|
|
78
91
|
});
|
|
79
92
|
|
|
@@ -47,7 +47,7 @@ const symlinkResult = resolveProjectRootDbPath(symlinkPath);
|
|
|
47
47
|
assertEq(
|
|
48
48
|
symlinkResult,
|
|
49
49
|
join("/home/user/myproject/.gsd/projects/abc123def", "gsd.db"),
|
|
50
|
-
"/.gsd/projects/<hash>/worktrees/ resolves to
|
|
50
|
+
"/.gsd/projects/<hash>/worktrees/ resolves to external project state DB",
|
|
51
51
|
);
|
|
52
52
|
|
|
53
53
|
// Windows-style separators for symlink layout
|
|
@@ -57,7 +57,7 @@ if (sep === "\\") {
|
|
|
57
57
|
assertEq(
|
|
58
58
|
winResult,
|
|
59
59
|
join("C:\\Users\\dev\\project\\.gsd\\projects\\abc123def", "gsd.db"),
|
|
60
|
-
"Windows /.gsd/projects/<hash>/worktrees/ resolves to
|
|
60
|
+
"Windows /.gsd/projects/<hash>/worktrees/ resolves to external project state DB",
|
|
61
61
|
);
|
|
62
62
|
} else {
|
|
63
63
|
// On non-Windows, test forward-slash variant explicitly
|
|
@@ -66,7 +66,7 @@ if (sep === "\\") {
|
|
|
66
66
|
assertEq(
|
|
67
67
|
fwdResult,
|
|
68
68
|
join("/home/user/myproject/.gsd/projects/abc123def", "gsd.db"),
|
|
69
|
-
"Forward-slash /.gsd/projects/<hash>/worktrees/ resolves to
|
|
69
|
+
"Forward-slash /.gsd/projects/<hash>/worktrees/ resolves to external project state DB on POSIX",
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -76,7 +76,7 @@ const deepResult = resolveProjectRootDbPath(deepSymlinkPath);
|
|
|
76
76
|
assertEq(
|
|
77
77
|
deepResult,
|
|
78
78
|
join("/home/user/myproject/.gsd/projects/deadbeef42", "gsd.db"),
|
|
79
|
-
"Deep /.gsd/projects/<hash>/worktrees/ path resolves to
|
|
79
|
+
"Deep /.gsd/projects/<hash>/worktrees/ path resolves to external project state DB",
|
|
80
80
|
);
|
|
81
81
|
|
|
82
82
|
// Non-worktree path should be unchanged
|
|
@@ -481,7 +481,7 @@ describe('db-writer', () => {
|
|
|
481
481
|
}
|
|
482
482
|
});
|
|
483
483
|
|
|
484
|
-
test('updateRequirementInDb —
|
|
484
|
+
test('updateRequirementInDb — ignores REQUIREMENTS.md projection when DB empty', async () => {
|
|
485
485
|
const tmpDir = makeTmpDir();
|
|
486
486
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
487
487
|
openDatabase(dbPath);
|
|
@@ -515,31 +515,28 @@ describe('db-writer', () => {
|
|
|
515
515
|
].join('\n');
|
|
516
516
|
fs.writeFileSync(path.join(tmpDir, '.gsd', 'REQUIREMENTS.md'), reqContent);
|
|
517
517
|
|
|
518
|
-
// DB is empty
|
|
519
|
-
//
|
|
520
|
-
// After fix: this seeds all 3 requirements from REQUIREMENTS.md first.
|
|
518
|
+
// DB is empty. REQUIREMENTS.md is a projection and must not be imported
|
|
519
|
+
// implicitly by a runtime DB write.
|
|
521
520
|
await updateRequirementInDb('R005', {
|
|
522
521
|
status: 'validated',
|
|
523
522
|
validation: 'S02 — auth flow verified',
|
|
524
523
|
}, tmpDir);
|
|
525
524
|
|
|
526
|
-
// R005 should have the update
|
|
525
|
+
// R005 should have the requested update only; disk projection content is ignored.
|
|
527
526
|
const r005 = getRequirementById('R005');
|
|
528
527
|
assert.ok(r005, 'R005 should exist');
|
|
529
528
|
assert.equal(r005!.status, 'validated', 'status should be updated');
|
|
530
529
|
assert.equal(r005!.validation, 'S02 — auth flow verified', 'validation should be updated');
|
|
531
|
-
assert.equal(r005!.class, '
|
|
532
|
-
assert.ok(r005!.description?.includes('authentication')
|
|
533
|
-
|
|
530
|
+
assert.equal(r005!.class, '', 'class should not be imported from REQUIREMENTS.md');
|
|
531
|
+
assert.ok(!r005!.description?.includes('authentication'), 'description should not be imported');
|
|
532
|
+
assert.ok(!r005!.full_content?.includes('authentication'), 'full content should not be imported');
|
|
534
533
|
|
|
535
|
-
//
|
|
534
|
+
// Other requirements in the projection are not seeded.
|
|
536
535
|
const r007 = getRequirementById('R007');
|
|
537
|
-
assert.
|
|
538
|
-
assert.equal(r007!.status, 'active', 'R007 status should be active');
|
|
536
|
+
assert.equal(r007, null, 'R007 should not be imported from REQUIREMENTS.md');
|
|
539
537
|
|
|
540
538
|
const r001 = getRequirementById('R001');
|
|
541
|
-
assert.
|
|
542
|
-
assert.equal(r001!.status, 'validated', 'R001 status should be validated (from section heading)');
|
|
539
|
+
assert.equal(r001, null, 'R001 should not be imported from REQUIREMENTS.md');
|
|
543
540
|
} finally {
|
|
544
541
|
closeDatabase();
|
|
545
542
|
cleanupDir(tmpDir);
|
|
@@ -663,15 +660,16 @@ describe('db-writer', () => {
|
|
|
663
660
|
'disk file preserved — shrinkage guard prevented overwrite',
|
|
664
661
|
);
|
|
665
662
|
|
|
666
|
-
// DB should
|
|
663
|
+
// DB should keep the caller-provided content. The larger disk file is a
|
|
664
|
+
// stale projection, not runtime authority.
|
|
667
665
|
const adapter = _getAdapter();
|
|
668
666
|
const row = adapter!
|
|
669
667
|
.prepare('SELECT full_content FROM artifacts WHERE path = ?')
|
|
670
668
|
.get(relPath);
|
|
671
669
|
assert.deepStrictEqual(
|
|
672
670
|
row!['full_content'],
|
|
673
|
-
|
|
674
|
-
'DB stores
|
|
671
|
+
abbreviatedContent,
|
|
672
|
+
'DB stores caller-provided content instead of importing disk projection content',
|
|
675
673
|
);
|
|
676
674
|
} finally {
|
|
677
675
|
closeDatabase();
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
insertSlice,
|
|
21
21
|
insertTask,
|
|
22
22
|
} from '../gsd-db.ts';
|
|
23
|
-
import { migrateHierarchyToDb } from '../md-importer.ts';
|
|
23
|
+
import { migrateFromMarkdown, migrateHierarchyToDb } from '../md-importer.ts';
|
|
24
24
|
import type { GSDState } from '../types.ts';
|
|
25
25
|
|
|
26
26
|
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
@@ -479,12 +479,13 @@ skills_used: []
|
|
|
479
479
|
|
|
480
480
|
// Step 2: Migrate markdown to DB
|
|
481
481
|
openDatabase(':memory:');
|
|
482
|
-
const counts =
|
|
482
|
+
const counts = migrateFromMarkdown(base);
|
|
483
483
|
|
|
484
484
|
// Verify migration populated correctly
|
|
485
|
-
assert.ok(counts.milestones >= 1, 'G-roundtrip: migrated milestones');
|
|
486
|
-
assert.ok(counts.slices >= 2, 'G-roundtrip: migrated slices');
|
|
487
|
-
assert.ok(counts.tasks >= 3, 'G-roundtrip: migrated tasks');
|
|
485
|
+
assert.ok(counts.hierarchy.milestones >= 1, 'G-roundtrip: migrated milestones');
|
|
486
|
+
assert.ok(counts.hierarchy.slices >= 2, 'G-roundtrip: migrated slices');
|
|
487
|
+
assert.ok(counts.hierarchy.tasks >= 3, 'G-roundtrip: migrated tasks');
|
|
488
|
+
assert.equal(counts.requirements, 3, 'G-roundtrip: migrated requirements');
|
|
488
489
|
|
|
489
490
|
// Step 3: Get DB-backed state
|
|
490
491
|
invalidateStateCache();
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* derive-state-db-disk-reconcile.test.ts — #2416
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* deriveStateFromDb reconciles disk milestones with DB milestones.
|
|
4
|
+
* DB-authoritative state: milestones that exist only as markdown projections
|
|
5
|
+
* are not imported by deriveStateFromDb(). Explicit migration/import is the
|
|
6
|
+
* only markdown-to-DB path.
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -17,7 +16,6 @@ import {
|
|
|
17
16
|
closeDatabase,
|
|
18
17
|
insertMilestone,
|
|
19
18
|
insertSlice,
|
|
20
|
-
insertTask,
|
|
21
19
|
} from "../gsd-db.ts";
|
|
22
20
|
import { createTestContext } from "./test-helpers.ts";
|
|
23
21
|
|
|
@@ -58,7 +56,7 @@ const ROADMAP_CONTENT = `# M002: Disk-Only Milestone
|
|
|
58
56
|
`;
|
|
59
57
|
|
|
60
58
|
async function main(): Promise<void> {
|
|
61
|
-
console.log("\n===
|
|
59
|
+
console.log("\n=== deriveStateFromDb does not reconcile disk milestones ===");
|
|
62
60
|
|
|
63
61
|
// Set up: M001 in DB, M002 on disk only
|
|
64
62
|
const base = createFixtureBase();
|
|
@@ -81,12 +79,9 @@ async function main(): Promise<void> {
|
|
|
81
79
|
invalidateStateCache();
|
|
82
80
|
const state = await deriveStateFromDb(base);
|
|
83
81
|
|
|
84
|
-
// M002 should be visible in
|
|
82
|
+
// M002 is disk-only and should not be visible in DB-backed state.
|
|
85
83
|
const m002Entry = state.registry.find((m) => m.id === "M002");
|
|
86
|
-
|
|
87
|
-
m002Entry !== undefined,
|
|
88
|
-
"M002 (disk-only milestone) should appear in state.registry (#2416)",
|
|
89
|
-
);
|
|
84
|
+
assertEq(m002Entry, undefined, "M002 disk-only milestone should not appear in DB-backed state");
|
|
90
85
|
|
|
91
86
|
// M001 should still be in the registry
|
|
92
87
|
const m001Entry = state.registry.find((m) => m.id === "M001");
|
|
@@ -95,24 +90,14 @@ async function main(): Promise<void> {
|
|
|
95
90
|
"M001 (DB milestone) should still appear in state.registry",
|
|
96
91
|
);
|
|
97
92
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
state.activeMilestone !== null,
|
|
101
|
-
"There should be an active milestone",
|
|
102
|
-
);
|
|
103
|
-
if (state.activeMilestone) {
|
|
104
|
-
assertEq(
|
|
105
|
-
state.activeMilestone.id,
|
|
106
|
-
"M002",
|
|
107
|
-
"Active milestone should be M002 (disk-only, not complete) (#2416)",
|
|
108
|
-
);
|
|
109
|
-
}
|
|
93
|
+
assertEq(state.activeMilestone, null, "No active milestone should be inferred from disk-only markdown");
|
|
94
|
+
assertEq(state.phase, "complete", "DB-only complete milestone drives complete state");
|
|
110
95
|
} finally {
|
|
111
96
|
closeDatabase();
|
|
112
97
|
cleanup(base);
|
|
113
98
|
}
|
|
114
99
|
|
|
115
|
-
|
|
100
|
+
console.log("\n=== summary-only disk milestones are not imported ===");
|
|
116
101
|
|
|
117
102
|
{
|
|
118
103
|
const summaryOnlyBase = createFixtureBase();
|
|
@@ -132,20 +117,7 @@ async function main(): Promise<void> {
|
|
|
132
117
|
const state = await deriveStateFromDb(summaryOnlyBase);
|
|
133
118
|
const m002Entry = state.registry.find((m) => m.id === "M002");
|
|
134
119
|
|
|
135
|
-
|
|
136
|
-
m002Entry !== undefined,
|
|
137
|
-
"M002 summary-only disk milestone should appear in state.registry (#4974)",
|
|
138
|
-
);
|
|
139
|
-
assertEq(
|
|
140
|
-
m002Entry?.title,
|
|
141
|
-
"Summary-Only Milestone",
|
|
142
|
-
"M002 summary-only disk milestone should use parsed SUMMARY title (#4974)",
|
|
143
|
-
);
|
|
144
|
-
assertEq(
|
|
145
|
-
m002Entry?.status,
|
|
146
|
-
"complete",
|
|
147
|
-
"M002 summary-only disk milestone should reconcile as complete (#4974)",
|
|
148
|
-
);
|
|
120
|
+
assertEq(m002Entry, undefined, "M002 summary-only disk milestone should not appear without explicit import");
|
|
149
121
|
} finally {
|
|
150
122
|
closeDatabase();
|
|
151
123
|
cleanup(summaryOnlyBase);
|