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,12 +1,9 @@
|
|
|
1
|
-
// GSD Extension — Tests for
|
|
1
|
+
// GSD Extension — Tests for DB-authoritative deriveStateFromDb behavior
|
|
2
2
|
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// detectBlockers, checkReplanTrigger, checkInterruptedWork
|
|
8
|
-
//
|
|
9
|
-
// Helpers are private — exercised through deriveStateFromDb integration.
|
|
4
|
+
// Private helper behavior is exercised through deriveStateFromDb integration.
|
|
5
|
+
// Markdown files in these tests are projections unless the DB row explicitly
|
|
6
|
+
// makes them authoritative.
|
|
10
7
|
|
|
11
8
|
import { describe, test, beforeEach, afterEach } from 'node:test';
|
|
12
9
|
import assert from 'node:assert/strict';
|
|
@@ -14,13 +11,16 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
|
14
11
|
import { join } from 'node:path';
|
|
15
12
|
import { tmpdir } from 'node:os';
|
|
16
13
|
|
|
17
|
-
import { invalidateStateCache, deriveStateFromDb } from '../state.ts';
|
|
14
|
+
import { invalidateStateCache, deriveStateFromDb, getActiveMilestoneId } from '../state.ts';
|
|
18
15
|
import {
|
|
19
16
|
openDatabase,
|
|
20
17
|
closeDatabase,
|
|
18
|
+
insertAssessment,
|
|
21
19
|
insertMilestone,
|
|
20
|
+
insertRequirement,
|
|
22
21
|
insertSlice,
|
|
23
22
|
insertTask,
|
|
23
|
+
setMilestoneQueueOrder,
|
|
24
24
|
updateTaskStatus,
|
|
25
25
|
} from '../gsd-db.ts';
|
|
26
26
|
|
|
@@ -112,6 +112,20 @@ describe('derive-state-helpers', () => {
|
|
|
112
112
|
|
|
113
113
|
openDatabase(':memory:');
|
|
114
114
|
insertMilestone({ id: 'M001', title: 'First', status: 'complete' });
|
|
115
|
+
insertRequirement({
|
|
116
|
+
id: 'R001',
|
|
117
|
+
class: 'functional',
|
|
118
|
+
status: 'active',
|
|
119
|
+
description: 'Unmapped',
|
|
120
|
+
why: 'test',
|
|
121
|
+
source: 'test',
|
|
122
|
+
primary_owner: '',
|
|
123
|
+
supporting_slices: '',
|
|
124
|
+
validation: '',
|
|
125
|
+
notes: '',
|
|
126
|
+
full_content: '',
|
|
127
|
+
superseded_by: null,
|
|
128
|
+
});
|
|
115
129
|
|
|
116
130
|
invalidateStateCache();
|
|
117
131
|
const state = await deriveStateFromDb(base);
|
|
@@ -126,10 +140,11 @@ describe('derive-state-helpers', () => {
|
|
|
126
140
|
});
|
|
127
141
|
|
|
128
142
|
// ─── resolveSliceDependencies: GSD_SLICE_LOCK with missing slice ────
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
143
|
+
test('resolveSliceDependencies: GSD_SLICE_LOCK pointing to non-existent slice returns blocked', async () => {
|
|
144
|
+
const base = createFixtureBase();
|
|
145
|
+
const origLock = process.env.GSD_SLICE_LOCK;
|
|
146
|
+
const origWorker = process.env.GSD_PARALLEL_WORKER;
|
|
147
|
+
try {
|
|
133
148
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
134
149
|
writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
|
|
135
150
|
writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
|
|
@@ -140,26 +155,30 @@ describe('derive-state-helpers', () => {
|
|
|
140
155
|
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First', status: 'active', risk: 'low', depends: [] });
|
|
141
156
|
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First Task', status: 'pending' });
|
|
142
157
|
|
|
143
|
-
|
|
158
|
+
process.env.GSD_SLICE_LOCK = 'S99';
|
|
159
|
+
process.env.GSD_PARALLEL_WORKER = '1';
|
|
144
160
|
|
|
145
161
|
invalidateStateCache();
|
|
146
162
|
const state = await deriveStateFromDb(base);
|
|
147
163
|
|
|
148
164
|
assert.equal(state.phase, 'blocked', 'slice-lock-miss: phase is blocked');
|
|
149
165
|
assert.ok(state.blockers.some(b => b.includes('GSD_SLICE_LOCK=S99')), 'slice-lock-miss: blocker mentions lock');
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
166
|
+
} finally {
|
|
167
|
+
if (origLock !== undefined) process.env.GSD_SLICE_LOCK = origLock;
|
|
168
|
+
else delete process.env.GSD_SLICE_LOCK;
|
|
169
|
+
if (origWorker !== undefined) process.env.GSD_PARALLEL_WORKER = origWorker;
|
|
170
|
+
else delete process.env.GSD_PARALLEL_WORKER;
|
|
171
|
+
closeDatabase();
|
|
154
172
|
cleanup(base);
|
|
155
173
|
}
|
|
156
174
|
});
|
|
157
175
|
|
|
158
176
|
// ─── resolveSliceDependencies: GSD_SLICE_LOCK with valid slice ──────
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
177
|
+
test('resolveSliceDependencies: GSD_SLICE_LOCK targeting valid slice bypasses deps', async () => {
|
|
178
|
+
const base = createFixtureBase();
|
|
179
|
+
const origLock = process.env.GSD_SLICE_LOCK;
|
|
180
|
+
const origWorker = process.env.GSD_PARALLEL_WORKER;
|
|
181
|
+
try {
|
|
163
182
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
164
183
|
// S02 depends on S01 but we lock to S02 directly
|
|
165
184
|
writeFile(base, 'milestones/M001/slices/S02/S02-PLAN.md', `# S02\n\n**Goal:** Test.\n**Demo:** Pass.\n\n## Tasks\n\n- [ ] **T01: Task** \`est:5m\`\n Do thing.\n`);
|
|
@@ -172,23 +191,26 @@ describe('derive-state-helpers', () => {
|
|
|
172
191
|
insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Second', status: 'pending', risk: 'low', depends: ['S01'] });
|
|
173
192
|
insertTask({ id: 'T01', sliceId: 'S02', milestoneId: 'M001', title: 'Task', status: 'pending' });
|
|
174
193
|
|
|
175
|
-
|
|
194
|
+
process.env.GSD_SLICE_LOCK = 'S02';
|
|
195
|
+
process.env.GSD_PARALLEL_WORKER = '1';
|
|
176
196
|
|
|
177
197
|
invalidateStateCache();
|
|
178
198
|
const state = await deriveStateFromDb(base);
|
|
179
199
|
|
|
180
200
|
assert.equal(state.activeSlice?.id, 'S02', 'slice-lock-valid: activeSlice is S02 (locked)');
|
|
181
201
|
assert.equal(state.phase, 'executing', 'slice-lock-valid: phase is executing');
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
202
|
+
} finally {
|
|
203
|
+
if (origLock !== undefined) process.env.GSD_SLICE_LOCK = origLock;
|
|
204
|
+
else delete process.env.GSD_SLICE_LOCK;
|
|
205
|
+
if (origWorker !== undefined) process.env.GSD_PARALLEL_WORKER = origWorker;
|
|
206
|
+
else delete process.env.GSD_PARALLEL_WORKER;
|
|
207
|
+
closeDatabase();
|
|
186
208
|
cleanup(base);
|
|
187
209
|
}
|
|
188
210
|
});
|
|
189
211
|
|
|
190
|
-
// ───
|
|
191
|
-
test('
|
|
212
|
+
// ─── DB-authoritative tasks: plan projection does not import tasks ──────
|
|
213
|
+
test('deriveStateFromDb: DB-empty task list does not import PLAN tasks', async () => {
|
|
192
214
|
const base = createFixtureBase();
|
|
193
215
|
try {
|
|
194
216
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -200,24 +222,23 @@ describe('derive-state-helpers', () => {
|
|
|
200
222
|
insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
|
|
201
223
|
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First', status: 'active', risk: 'low', depends: [] });
|
|
202
224
|
insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Second', status: 'pending', risk: 'low', depends: ['S01'] });
|
|
203
|
-
// No tasks inserted —
|
|
225
|
+
// No tasks inserted — PLAN.md is a projection and must not be imported.
|
|
204
226
|
|
|
205
227
|
invalidateStateCache();
|
|
206
228
|
const state = await deriveStateFromDb(base);
|
|
207
229
|
|
|
208
|
-
|
|
209
|
-
assert.equal(state.
|
|
210
|
-
assert.equal(state.
|
|
211
|
-
assert.equal(state.progress?.tasks?.
|
|
212
|
-
assert.equal(state.progress?.tasks?.done, 1, 'task-reconcile: done tasks = 1 (T02 was [x])');
|
|
230
|
+
assert.equal(state.phase, 'planning', 'db-empty-tasks: phase is planning');
|
|
231
|
+
assert.equal(state.activeTask, null, 'db-empty-tasks: no active task');
|
|
232
|
+
assert.equal(state.progress?.tasks?.total, 0, 'db-empty-tasks: no tasks imported');
|
|
233
|
+
assert.equal(state.progress?.tasks?.done, 0, 'db-empty-tasks: no completed tasks imported');
|
|
213
234
|
} finally {
|
|
214
235
|
closeDatabase();
|
|
215
236
|
cleanup(base);
|
|
216
237
|
}
|
|
217
238
|
});
|
|
218
239
|
|
|
219
|
-
// ───
|
|
220
|
-
test('
|
|
240
|
+
// ─── DB-authoritative tasks: SUMMARY projection does not complete task ────
|
|
241
|
+
test('deriveStateFromDb: disk SUMMARY does not reconcile pending task', async () => {
|
|
221
242
|
const base = createFixtureBase();
|
|
222
243
|
try {
|
|
223
244
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -237,11 +258,9 @@ describe('derive-state-helpers', () => {
|
|
|
237
258
|
invalidateStateCache();
|
|
238
259
|
const state = await deriveStateFromDb(base);
|
|
239
260
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
assert.equal(state.
|
|
243
|
-
assert.equal(state.activeTask, null, 'stale-task: no active task (all done)');
|
|
244
|
-
assert.equal(state.progress?.tasks?.done, 2, 'stale-task: tasks.done = 2');
|
|
261
|
+
assert.equal(state.phase, 'executing', 'disk-summary-ignored: phase is executing');
|
|
262
|
+
assert.equal(state.activeTask?.id, 'T01', 'disk-summary-ignored: T01 remains active');
|
|
263
|
+
assert.equal(state.progress?.tasks?.done, 1, 'disk-summary-ignored: only DB-complete task is done');
|
|
245
264
|
} finally {
|
|
246
265
|
closeDatabase();
|
|
247
266
|
cleanup(base);
|
|
@@ -256,7 +275,8 @@ describe('derive-state-helpers', () => {
|
|
|
256
275
|
writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
|
|
257
276
|
writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
|
|
258
277
|
writeFile(base, 'milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01 Plan');
|
|
259
|
-
// T02 completed with blocker discovered
|
|
278
|
+
// T02 completed with blocker discovered. The disk summary is a projection;
|
|
279
|
+
// only the DB blocker flag is authoritative for deriveStateFromDb().
|
|
260
280
|
writeFile(base, 'milestones/M001/slices/S01/tasks/T02-SUMMARY.md',
|
|
261
281
|
'---\nblocker_discovered: true\n---\n\n# T02 Summary\n\nFound a blocker.');
|
|
262
282
|
|
|
@@ -265,7 +285,7 @@ describe('derive-state-helpers', () => {
|
|
|
265
285
|
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First', status: 'active', risk: 'low', depends: [] });
|
|
266
286
|
insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Second', status: 'pending', risk: 'low', depends: ['S01'] });
|
|
267
287
|
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First Task', status: 'pending' });
|
|
268
|
-
insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Done Task', status: 'complete' });
|
|
288
|
+
insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Done Task', status: 'complete', blockerDiscovered: true });
|
|
269
289
|
|
|
270
290
|
invalidateStateCache();
|
|
271
291
|
const state = await deriveStateFromDb(base);
|
|
@@ -278,8 +298,8 @@ describe('derive-state-helpers', () => {
|
|
|
278
298
|
}
|
|
279
299
|
});
|
|
280
300
|
|
|
281
|
-
// ───
|
|
282
|
-
test('
|
|
301
|
+
// ─── CONTINUE.md projection is ignored by DB derive ─────────────────
|
|
302
|
+
test('deriveStateFromDb: continue.md projection does not trigger resume nextAction', async () => {
|
|
283
303
|
const base = createFixtureBase();
|
|
284
304
|
try {
|
|
285
305
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -299,8 +319,8 @@ describe('derive-state-helpers', () => {
|
|
|
299
319
|
const state = await deriveStateFromDb(base);
|
|
300
320
|
|
|
301
321
|
assert.equal(state.phase, 'executing', 'continue: phase is still executing');
|
|
302
|
-
assert.ok(state.nextAction.includes('Resume interrupted work'), 'continue: nextAction
|
|
303
|
-
assert.ok(state.nextAction.includes('continue.md'), 'continue: nextAction
|
|
322
|
+
assert.ok(!state.nextAction.includes('Resume interrupted work'), 'continue: nextAction does not mention resume');
|
|
323
|
+
assert.ok(!state.nextAction.includes('continue.md'), 'continue: nextAction does not mention continue.md');
|
|
304
324
|
} finally {
|
|
305
325
|
closeDatabase();
|
|
306
326
|
cleanup(base);
|
|
@@ -380,6 +400,13 @@ describe('derive-state-helpers', () => {
|
|
|
380
400
|
insertMilestone({ id: 'M001', title: 'First', status: 'active' });
|
|
381
401
|
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First', status: 'complete', risk: 'low', depends: [] });
|
|
382
402
|
insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Second', status: 'complete', risk: 'low', depends: ['S01'] });
|
|
403
|
+
insertAssessment({
|
|
404
|
+
path: 'milestones/M001/M001-VALIDATION.md',
|
|
405
|
+
milestoneId: 'M001',
|
|
406
|
+
status: 'pass',
|
|
407
|
+
scope: 'milestone-validation',
|
|
408
|
+
fullContent: 'verdict: passed',
|
|
409
|
+
});
|
|
383
410
|
|
|
384
411
|
invalidateStateCache();
|
|
385
412
|
const state = await deriveStateFromDb(base);
|
|
@@ -394,34 +421,34 @@ describe('derive-state-helpers', () => {
|
|
|
394
421
|
}
|
|
395
422
|
});
|
|
396
423
|
|
|
397
|
-
// ───
|
|
398
|
-
test('
|
|
424
|
+
// ─── DB-authoritative slices: roadmap projection does not insert slices ───
|
|
425
|
+
test('deriveStateFromDb: ROADMAP slices missing from DB are not auto-inserted', async () => {
|
|
399
426
|
const base = createFixtureBase();
|
|
400
427
|
try {
|
|
401
428
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
402
429
|
|
|
403
430
|
openDatabase(':memory:');
|
|
404
431
|
insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
|
|
405
|
-
// No slices inserted —
|
|
432
|
+
// No slices inserted — ROADMAP.md is a projection and must not be imported.
|
|
406
433
|
|
|
407
434
|
invalidateStateCache();
|
|
408
435
|
const state = await deriveStateFromDb(base);
|
|
409
436
|
|
|
410
|
-
|
|
411
|
-
assert.equal(state.
|
|
412
|
-
assert.equal(state.
|
|
413
|
-
assert.
|
|
437
|
+
assert.equal(state.activeMilestone?.id, 'M001', 'roadmap-projection: M001 is active');
|
|
438
|
+
assert.equal(state.activeSlice, null, 'roadmap-projection: no active slice imported');
|
|
439
|
+
assert.equal(state.phase, 'pre-planning', 'roadmap-projection: no DB slices routes to pre-planning');
|
|
440
|
+
assert.equal(state.progress?.slices, undefined, 'roadmap-projection: no slice progress from projection');
|
|
414
441
|
} finally {
|
|
415
442
|
closeDatabase();
|
|
416
443
|
cleanup(base);
|
|
417
444
|
}
|
|
418
445
|
});
|
|
419
446
|
|
|
420
|
-
// ─── Queue order:
|
|
421
|
-
test('deriveStateFromDb
|
|
447
|
+
// ─── Queue order: DB sequence is authoritative ─────────────────────
|
|
448
|
+
test('deriveStateFromDb ignores QUEUE-ORDER.json and uses DB sequence', async () => {
|
|
422
449
|
const base = createFixtureBase();
|
|
423
450
|
try {
|
|
424
|
-
//
|
|
451
|
+
// QUEUE-ORDER.json is a projection and should not drive DB derivation.
|
|
425
452
|
const queueOrder = JSON.stringify({ order: ['M003', 'M001', 'M002'], updatedAt: new Date().toISOString() });
|
|
426
453
|
writeFileSync(join(base, '.gsd', 'QUEUE-ORDER.json'), queueOrder);
|
|
427
454
|
writeFile(base, 'milestones/M001/M001-CONTEXT.md', '# M001\n\nContext.');
|
|
@@ -429,23 +456,47 @@ describe('derive-state-helpers', () => {
|
|
|
429
456
|
writeFile(base, 'milestones/M003/M003-CONTEXT.md', '# M003\n\nContext.');
|
|
430
457
|
|
|
431
458
|
openDatabase(':memory:');
|
|
432
|
-
// Insert in natural order
|
|
459
|
+
// Insert in natural order, then store the authoritative DB sequence.
|
|
433
460
|
insertMilestone({ id: 'M001', title: 'First', status: 'active' });
|
|
434
461
|
insertMilestone({ id: 'M002', title: 'Second', status: 'active' });
|
|
435
462
|
insertMilestone({ id: 'M003', title: 'Third', status: 'active' });
|
|
463
|
+
setMilestoneQueueOrder(['M002', 'M001', 'M003']);
|
|
436
464
|
|
|
437
465
|
invalidateStateCache();
|
|
438
466
|
const state = await deriveStateFromDb(base);
|
|
439
467
|
|
|
440
|
-
|
|
441
|
-
assert.equal(state.
|
|
442
|
-
assert.equal(state.registry[0]?.id, 'M003', 'queue-order: registry[0] is M003');
|
|
468
|
+
assert.equal(state.activeMilestone?.id, 'M002', 'queue-order: DB sequence chooses M002');
|
|
469
|
+
assert.equal(state.registry[0]?.id, 'M002', 'queue-order: registry[0] follows DB sequence');
|
|
443
470
|
} finally {
|
|
444
471
|
closeDatabase();
|
|
445
472
|
cleanup(base);
|
|
446
473
|
}
|
|
447
474
|
});
|
|
448
475
|
|
|
476
|
+
test('getActiveMilestoneId: DB lock path ignores PARKED flag projection', async () => {
|
|
477
|
+
const base = createFixtureBase();
|
|
478
|
+
const previousLock = process.env.GSD_MILESTONE_LOCK;
|
|
479
|
+
const previousWorker = process.env.GSD_PARALLEL_WORKER;
|
|
480
|
+
try {
|
|
481
|
+
process.env.GSD_MILESTONE_LOCK = 'M001';
|
|
482
|
+
process.env.GSD_PARALLEL_WORKER = '1';
|
|
483
|
+
writeFile(base, 'milestones/M001/M001-PARKED.md', '# Parked on disk');
|
|
484
|
+
|
|
485
|
+
openDatabase(':memory:');
|
|
486
|
+
insertMilestone({ id: 'M001', title: 'Active in DB', status: 'active' });
|
|
487
|
+
|
|
488
|
+
const id = await getActiveMilestoneId(base);
|
|
489
|
+
assert.equal(id, 'M001', 'DB status remains authoritative despite PARKED projection');
|
|
490
|
+
} finally {
|
|
491
|
+
if (previousLock === undefined) delete process.env.GSD_MILESTONE_LOCK;
|
|
492
|
+
else process.env.GSD_MILESTONE_LOCK = previousLock;
|
|
493
|
+
if (previousWorker === undefined) delete process.env.GSD_PARALLEL_WORKER;
|
|
494
|
+
else process.env.GSD_PARALLEL_WORKER = previousWorker;
|
|
495
|
+
closeDatabase();
|
|
496
|
+
cleanup(base);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
|
|
449
500
|
// ─── handleAllSlicesDone: needs-remediation + all slices done → blocked (#4506) ──
|
|
450
501
|
test('handleAllSlicesDone: needs-remediation with all slices done returns blocked', async () => {
|
|
451
502
|
const base = createFixtureBase();
|
|
@@ -458,6 +509,13 @@ describe('derive-state-helpers', () => {
|
|
|
458
509
|
openDatabase(':memory:');
|
|
459
510
|
insertMilestone({ id: 'M001', title: 'Remediation Test', status: 'active' });
|
|
460
511
|
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Done', status: 'complete', risk: 'low', depends: [] });
|
|
512
|
+
insertAssessment({
|
|
513
|
+
path: 'milestones/M001/M001-VALIDATION.md',
|
|
514
|
+
milestoneId: 'M001',
|
|
515
|
+
status: 'needs-remediation',
|
|
516
|
+
scope: 'milestone-validation',
|
|
517
|
+
fullContent: 'verdict: needs-remediation',
|
|
518
|
+
});
|
|
461
519
|
|
|
462
520
|
invalidateStateCache();
|
|
463
521
|
const state = await deriveStateFromDb(base);
|
|
@@ -5,6 +5,10 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
|
|
7
7
|
import { deriveState, isSliceComplete, isMilestoneComplete, isGhostMilestone } from '../state.ts';
|
|
8
|
+
|
|
9
|
+
// This suite exercises the explicit legacy markdown derivation path.
|
|
10
|
+
process.env.GSD_ALLOW_MARKDOWN_DERIVE_FALLBACK = '1';
|
|
11
|
+
|
|
8
12
|
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
9
13
|
|
|
10
14
|
function createFixtureBase(): string {
|
|
@@ -47,26 +47,12 @@ describe("completing-milestone dispatch guard (#4324)", () => {
|
|
|
47
47
|
assert.ok(dispatchIdx > -1, "complete-milestone dispatch should exist after the skip guard");
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
test("
|
|
50
|
+
test("does not reconcile DB from SUMMARY.md projection (#4658 superseded)", () => {
|
|
51
51
|
const phaseCheck = source.indexOf('phase !== "completing-milestone"');
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
assert.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
assert.ok(classifyCall > -1, "SUMMARY mismatch handling should classify summary content");
|
|
58
|
-
const dbGateBeforeClassify = source.indexOf("existingSummary && isDbAvailable()", summaryGuard);
|
|
59
|
-
assert.ok(
|
|
60
|
-
dbGateBeforeClassify === -1 || dbGateBeforeClassify > classifyCall,
|
|
61
|
-
"SUMMARY classification must not be gated on DB availability",
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const reconcileCall = source.indexOf('updateMilestoneStatus(mid, "complete"', summaryGuard);
|
|
65
|
-
assert.ok(reconcileCall > -1, "successful SUMMARY should reconcile DB to complete");
|
|
66
|
-
|
|
67
|
-
const stopAction = source.indexOf('action: "stop"', summaryGuard);
|
|
68
|
-
assert.ok(stopAction > -1, "SUMMARY mismatch should return stop action");
|
|
69
|
-
const warningLevel = source.indexOf('level: "warning"', summaryGuard);
|
|
70
|
-
assert.ok(warningLevel > -1, "SUMMARY mismatch should be warning-level stop (pauses auto-mode)");
|
|
52
|
+
assert.ok(phaseCheck > -1, "completing-milestone phase check should exist");
|
|
53
|
+
const ruleTail = source.slice(phaseCheck, source.indexOf('name:', phaseCheck + 1));
|
|
54
|
+
assert.doesNotMatch(ruleTail, /resolveMilestoneFile\(basePath,\s*mid,\s*"SUMMARY"\)/);
|
|
55
|
+
assert.doesNotMatch(ruleTail, /classifyMilestoneSummaryContent/);
|
|
56
|
+
assert.doesNotMatch(ruleTail, /updateMilestoneStatus\(mid,\s*"complete"/);
|
|
71
57
|
});
|
|
72
58
|
});
|
|
@@ -225,14 +225,14 @@ test("dispatch guard allows slice with all declared dependencies complete", (t)
|
|
|
225
225
|
);
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
-
test("dispatch guard
|
|
228
|
+
test("dispatch guard does not skip prior milestone from SUMMARY projection when DB is not closed", (t) => {
|
|
229
229
|
const repo = setupRepo();
|
|
230
230
|
t.after(() => teardownRepo(repo));
|
|
231
231
|
|
|
232
232
|
mkdirSync(join(repo, ".gsd", "milestones", "M001"), { recursive: true });
|
|
233
233
|
mkdirSync(join(repo, ".gsd", "milestones", "M002"), { recursive: true });
|
|
234
234
|
|
|
235
|
-
// M001
|
|
235
|
+
// M001 has a successful SUMMARY projection but is not closed in the DB.
|
|
236
236
|
insertMilestone({ id: "M001", title: "Previous" });
|
|
237
237
|
insertSlice({ id: "S01", milestoneId: "M001", title: "Core", status: "complete", depends: [], sequence: 1 });
|
|
238
238
|
insertSlice({ id: "S02", milestoneId: "M001", title: "Tests", status: "complete", depends: ["S01"], sequence: 2 });
|
|
@@ -242,16 +242,15 @@ test("dispatch guard skips completed milestone with SUMMARY even if it has unche
|
|
|
242
242
|
insertMilestone({ id: "M002", title: "Current" });
|
|
243
243
|
insertSlice({ id: "S01", milestoneId: "M002", title: "Start", status: "pending", depends: [], sequence: 1 });
|
|
244
244
|
|
|
245
|
-
// M001 SUMMARY on disk
|
|
245
|
+
// M001 SUMMARY on disk must not trigger skip while DB remains open/active.
|
|
246
246
|
writeFileSync(join(repo, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# M001\n");
|
|
247
247
|
writeFileSync(join(repo, ".gsd", "milestones", "M001", "M001-SUMMARY.md"),
|
|
248
248
|
"---\nstatus: complete\n---\n# M001 Summary\nDone.\n");
|
|
249
249
|
writeFileSync(join(repo, ".gsd", "milestones", "M002", "M002-ROADMAP.md"), "# M002\n");
|
|
250
250
|
|
|
251
|
-
// M001 has SUMMARY — should be skipped, not block M002/S01
|
|
252
251
|
assert.equal(
|
|
253
252
|
getPriorSliceCompletionBlocker(repo, "main", "plan-slice", "M002/S01"),
|
|
254
|
-
|
|
253
|
+
"Cannot dispatch plan-slice M002/S01: earlier slice M001/S03-R is not complete.",
|
|
255
254
|
);
|
|
256
255
|
});
|
|
257
256
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dispatcher-stuck-planning.test.ts
|
|
2
|
+
* dispatcher-stuck-planning.test.ts
|
|
3
3
|
*
|
|
4
|
-
* Verify that state.ts
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* tool, leaving the DB with zero or partial task rows.
|
|
4
|
+
* Verify that state.ts no longer imports disk PLAN.md tasks into the runtime
|
|
5
|
+
* DB. PLAN.md is a projection; task rows must be created through DB-backed
|
|
6
|
+
* planning/import APIs.
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
import { describe, test } from "node:test";
|
|
@@ -16,23 +15,23 @@ import { fileURLToPath } from "node:url";
|
|
|
16
15
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
16
|
const sourceFile = join(__dirname, "..", "state.ts");
|
|
18
17
|
|
|
19
|
-
describe("dispatcher
|
|
18
|
+
describe("dispatcher DB-authoritative planning boundary", () => {
|
|
20
19
|
const source = readFileSync(sourceFile, "utf-8");
|
|
21
20
|
|
|
22
|
-
test("
|
|
23
|
-
assert.
|
|
21
|
+
test("does not import insertTask into state derivation", () => {
|
|
22
|
+
assert.doesNotMatch(source, /import\s*\{[^}]*insertTask[^}]*\}\s*from/);
|
|
24
23
|
});
|
|
25
24
|
|
|
26
|
-
test("
|
|
27
|
-
assert.
|
|
28
|
-
assert.match(source, /
|
|
25
|
+
test("does not contain plan-file task reconciliation block", () => {
|
|
26
|
+
assert.doesNotMatch(source, /dbTaskIds\.has\(t\.id\)/);
|
|
27
|
+
assert.match(source, /Slice \$\{activeSlice\.id\} has no DB tasks/);
|
|
29
28
|
});
|
|
30
29
|
|
|
31
|
-
test("
|
|
32
|
-
assert.
|
|
30
|
+
test("does not call insertTask from state derivation", () => {
|
|
31
|
+
assert.doesNotMatch(source, /insertTask\(\{/);
|
|
33
32
|
});
|
|
34
33
|
|
|
35
|
-
test("
|
|
36
|
-
assert.match(source,
|
|
34
|
+
test("documents markdown projections as non-authoritative", () => {
|
|
35
|
+
assert.match(source, /Markdown files are projections only/);
|
|
37
36
|
});
|
|
38
37
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, test } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
// ensureDbOpen — Tests that the lazy DB opener creates
|
|
4
|
-
//
|
|
3
|
+
// ensureDbOpen — Tests that the lazy DB opener creates/opens the authoritative
|
|
4
|
+
// database without implicitly importing markdown projections.
|
|
5
5
|
//
|
|
6
6
|
// This covers the bug where interactive (non-auto) sessions got
|
|
7
7
|
// "GSD database is not available" because ensureDbOpen only opened
|
|
@@ -11,7 +11,7 @@ import * as path from 'node:path';
|
|
|
11
11
|
import * as os from 'node:os';
|
|
12
12
|
import * as fs from 'node:fs';
|
|
13
13
|
import { createRequire } from 'node:module';
|
|
14
|
-
import { closeDatabase, isDbAvailable, getDecisionById, _getAdapter } from '../gsd-db.ts';
|
|
14
|
+
import { closeDatabase, isDbAvailable, getDecisionById, SCHEMA_VERSION, _getAdapter } from '../gsd-db.ts';
|
|
15
15
|
|
|
16
16
|
const _require = createRequire(import.meta.url);
|
|
17
17
|
|
|
@@ -284,11 +284,11 @@ function createLegacyV15Db(dbPath: string): void {
|
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
287
|
-
// ensureDbOpen creates DB
|
|
287
|
+
// ensureDbOpen creates DB without implicit Markdown migration
|
|
288
288
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
289
289
|
|
|
290
290
|
describe('ensure-db-open', () => {
|
|
291
|
-
test('ensureDbOpen: creates DB
|
|
291
|
+
test('ensureDbOpen: creates empty DB without importing Markdown', async () => {
|
|
292
292
|
const tmpDir = makeTmpDir();
|
|
293
293
|
const gsdDir = path.join(tmpDir, '.gsd');
|
|
294
294
|
fs.mkdirSync(gsdDir, { recursive: true });
|
|
@@ -319,17 +319,12 @@ describe('ensure-db-open', () => {
|
|
|
319
319
|
|
|
320
320
|
const result = await ensureDbOpen();
|
|
321
321
|
|
|
322
|
-
assert.ok(result === true, 'ensureDbOpen should return true when .gsd/
|
|
322
|
+
assert.ok(result === true, 'ensureDbOpen should return true when .gsd/ exists');
|
|
323
323
|
assert.ok(fs.existsSync(dbPath), 'DB file should be created after ensureDbOpen');
|
|
324
324
|
assert.ok(isDbAvailable(), 'DB should be available after ensureDbOpen');
|
|
325
325
|
|
|
326
|
-
// Verify that Markdown migration actually ran
|
|
327
326
|
const decision = getDecisionById('D001');
|
|
328
|
-
assert.
|
|
329
|
-
if (decision) {
|
|
330
|
-
assert.deepStrictEqual(decision.scope, 'architecture', 'Migrated decision scope should match');
|
|
331
|
-
assert.deepStrictEqual(decision.choice, 'SQLite', 'Migrated decision choice should match');
|
|
332
|
-
}
|
|
327
|
+
assert.equal(decision, null, 'D001 should not be imported from DECISIONS.md without explicit migration');
|
|
333
328
|
} finally {
|
|
334
329
|
process.cwd = origCwd;
|
|
335
330
|
closeDatabase();
|
|
@@ -360,7 +355,7 @@ describe('ensure-db-open', () => {
|
|
|
360
355
|
assert.ok(result === true, 'ensureDbOpen should honor explicit basePath');
|
|
361
356
|
assert.equal(process.cwd(), originalCwd, 'ensureDbOpen should not mutate process.cwd');
|
|
362
357
|
assert.ok(isDbAvailable(), 'DB should be available after explicit open');
|
|
363
|
-
assert.
|
|
358
|
+
assert.equal(getDecisionById('D777'), null, 'explicit basePath should not import DECISIONS.md');
|
|
364
359
|
} finally {
|
|
365
360
|
closeDatabase();
|
|
366
361
|
cleanupDir(tmpDir);
|
|
@@ -389,7 +384,7 @@ describe('ensure-db-open', () => {
|
|
|
389
384
|
assert.ok(db, 'adapter should be available after ensureDbOpen');
|
|
390
385
|
assert.equal(
|
|
391
386
|
db.prepare('SELECT MAX(version) as version FROM schema_version').get()?.version,
|
|
392
|
-
|
|
387
|
+
SCHEMA_VERSION,
|
|
393
388
|
'legacy DB should migrate to current schema version',
|
|
394
389
|
);
|
|
395
390
|
|
|
@@ -519,9 +514,9 @@ describe('ensure-db-open', () => {
|
|
|
519
514
|
try {
|
|
520
515
|
const { ensureDbOpen } = await import('../bootstrap/dynamic-tools.ts');
|
|
521
516
|
assert.equal(await ensureDbOpen(firstDir), true);
|
|
522
|
-
assert.
|
|
517
|
+
assert.equal(getDecisionById('D101'), null, 'first DB should not import DECISIONS.md');
|
|
523
518
|
assert.equal(await ensureDbOpen(secondDir), true);
|
|
524
|
-
assert.
|
|
519
|
+
assert.equal(getDecisionById('D202'), null, 'second DB should not import DECISIONS.md');
|
|
525
520
|
assert.equal(getDecisionById('D101'), null, 'first DB should no longer be active after switch');
|
|
526
521
|
} finally {
|
|
527
522
|
closeDatabase();
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
claimEscalationOverride,
|
|
20
20
|
findUnappliedEscalationOverride,
|
|
21
21
|
listEscalationArtifacts,
|
|
22
|
+
SCHEMA_VERSION,
|
|
22
23
|
_getAdapter,
|
|
23
24
|
} from "../gsd-db.ts";
|
|
24
25
|
import {
|
|
@@ -348,7 +349,7 @@ test("ADR-011 P2: schema v20 fresh DB has all escalation columns on tasks + sour
|
|
|
348
349
|
assert.ok(decCols.includes("source"), "decisions table must have source column");
|
|
349
350
|
|
|
350
351
|
const version = adapter.prepare("SELECT MAX(version) as v FROM schema_version").get();
|
|
351
|
-
assert.equal(version?.["v"],
|
|
352
|
+
assert.equal(version?.["v"], SCHEMA_VERSION);
|
|
352
353
|
});
|
|
353
354
|
|
|
354
355
|
test("ADR-011 P2: findUnappliedEscalationOverride returns null when escalation_pending=1 (still pending)", (t) => {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
wasDbOpenAttempted,
|
|
12
12
|
getDbProvider,
|
|
13
13
|
getDbStatus,
|
|
14
|
+
SCHEMA_VERSION,
|
|
14
15
|
insertDecision,
|
|
15
16
|
getDecisionById,
|
|
16
17
|
insertRequirement,
|
|
@@ -101,7 +102,7 @@ describe('gsd-db', () => {
|
|
|
101
102
|
// Check schema_version table
|
|
102
103
|
const adapter = _getAdapter()!;
|
|
103
104
|
const version = adapter.prepare('SELECT MAX(version) as version FROM schema_version').get();
|
|
104
|
-
assert.deepStrictEqual(version?.['version'],
|
|
105
|
+
assert.deepStrictEqual(version?.['version'], SCHEMA_VERSION, `schema version should be ${SCHEMA_VERSION}`);
|
|
105
106
|
|
|
106
107
|
// Check tables exist by querying them
|
|
107
108
|
const dRows = adapter.prepare('SELECT count(*) as cnt FROM decisions').get();
|