gsd-pi 2.44.0-dev.62b5d6c → 2.44.0-dev.848dd4c
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 +30 -12
- package/dist/resources/extensions/gsd/auto-start.js +10 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +5 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
- 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 +2 -2
- 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/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 +18 -18
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +24 -26
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js +29 -48
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +30 -34
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
- package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
- package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
- package/src/resources/extensions/gsd/auto-start.ts +14 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +14 -16
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +465 -523
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +533 -656
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
- package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +38 -59
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +228 -263
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +250 -302
- package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
- package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +390 -420
- package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +68 -83
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +152 -183
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
- package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
- package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +164 -180
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
- package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
- package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +54 -75
- package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +21 -32
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +38 -44
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
- package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +54 -60
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
- package/src/resources/extensions/gsd/tests/doctor.test.ts +104 -134
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +30 -42
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +198 -206
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
- package/src/resources/extensions/gsd/tests/git-service.test.ts +285 -388
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
- package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +81 -103
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -102
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +150 -194
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +80 -93
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
- package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +83 -93
- package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +87 -96
- package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +125 -164
- package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
- package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
- package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
- package/src/resources/extensions/gsd/tests/parsers.test.ts +546 -611
- package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
- package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
- package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
- package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
- package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
- package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +19 -26
- package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
- package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +9 -11
- package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
- package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
- package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
- package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
- package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
- /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → -zps1Q9mQmioAKLcQiCr8}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → -zps1Q9mQmioAKLcQiCr8}/_ssgManifest.js +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
1
3
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
4
|
import { join } from 'node:path';
|
|
3
5
|
import { tmpdir } from 'node:os';
|
|
@@ -12,10 +14,6 @@ import {
|
|
|
12
14
|
insertSlice,
|
|
13
15
|
insertTask,
|
|
14
16
|
} from '../gsd-db.ts';
|
|
15
|
-
import { createTestContext } from './test-helpers.ts';
|
|
16
|
-
|
|
17
|
-
const { assertEq, assertTrue, report } = createTestContext();
|
|
18
|
-
|
|
19
17
|
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
20
18
|
|
|
21
19
|
function createFixtureBase(): string {
|
|
@@ -100,11 +98,10 @@ const REQUIREMENTS_CONTENT = `# Requirements
|
|
|
100
98
|
- Description: Already validated.
|
|
101
99
|
`;
|
|
102
100
|
|
|
103
|
-
async
|
|
101
|
+
describe('derive-state-db', async () => {
|
|
104
102
|
|
|
105
103
|
// ─── Test 1: DB-backed deriveState produces identical GSDState ─────────
|
|
106
|
-
|
|
107
|
-
{
|
|
104
|
+
test('derive-state-db: DB path matches file path', async () => {
|
|
108
105
|
const base = createFixtureBase();
|
|
109
106
|
try {
|
|
110
107
|
// Write files to disk (for file-only path)
|
|
@@ -120,7 +117,7 @@ async function main(): Promise<void> {
|
|
|
120
117
|
|
|
121
118
|
// Now open DB, insert matching artifacts
|
|
122
119
|
openDatabase(':memory:');
|
|
123
|
-
|
|
120
|
+
assert.ok(isDbAvailable(), 'db-match: DB is available after open');
|
|
124
121
|
|
|
125
122
|
insertArtifactRow('milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT, {
|
|
126
123
|
artifact_type: 'roadmap',
|
|
@@ -140,36 +137,35 @@ async function main(): Promise<void> {
|
|
|
140
137
|
const dbState = await deriveState(base);
|
|
141
138
|
|
|
142
139
|
// Field-by-field equality
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
140
|
+
assert.deepStrictEqual(dbState.phase, fileState.phase, 'db-match: phase matches');
|
|
141
|
+
assert.deepStrictEqual(dbState.activeMilestone?.id, fileState.activeMilestone?.id, 'db-match: activeMilestone.id matches');
|
|
142
|
+
assert.deepStrictEqual(dbState.activeMilestone?.title, fileState.activeMilestone?.title, 'db-match: activeMilestone.title matches');
|
|
143
|
+
assert.deepStrictEqual(dbState.activeSlice?.id, fileState.activeSlice?.id, 'db-match: activeSlice.id matches');
|
|
144
|
+
assert.deepStrictEqual(dbState.activeSlice?.title, fileState.activeSlice?.title, 'db-match: activeSlice.title matches');
|
|
145
|
+
assert.deepStrictEqual(dbState.activeTask?.id, fileState.activeTask?.id, 'db-match: activeTask.id matches');
|
|
146
|
+
assert.deepStrictEqual(dbState.activeTask?.title, fileState.activeTask?.title, 'db-match: activeTask.title matches');
|
|
147
|
+
assert.deepStrictEqual(dbState.blockers, fileState.blockers, 'db-match: blockers match');
|
|
148
|
+
assert.deepStrictEqual(dbState.registry.length, fileState.registry.length, 'db-match: registry length matches');
|
|
149
|
+
assert.deepStrictEqual(dbState.registry[0]?.status, fileState.registry[0]?.status, 'db-match: registry[0] status matches');
|
|
150
|
+
assert.deepStrictEqual(dbState.requirements?.active, fileState.requirements?.active, 'db-match: requirements.active matches');
|
|
151
|
+
assert.deepStrictEqual(dbState.requirements?.validated, fileState.requirements?.validated, 'db-match: requirements.validated matches');
|
|
152
|
+
assert.deepStrictEqual(dbState.requirements?.total, fileState.requirements?.total, 'db-match: requirements.total matches');
|
|
153
|
+
assert.deepStrictEqual(dbState.progress?.milestones?.done, fileState.progress?.milestones?.done, 'db-match: milestones.done matches');
|
|
154
|
+
assert.deepStrictEqual(dbState.progress?.milestones?.total, fileState.progress?.milestones?.total, 'db-match: milestones.total matches');
|
|
155
|
+
assert.deepStrictEqual(dbState.progress?.slices?.done, fileState.progress?.slices?.done, 'db-match: slices.done matches');
|
|
156
|
+
assert.deepStrictEqual(dbState.progress?.slices?.total, fileState.progress?.slices?.total, 'db-match: slices.total matches');
|
|
157
|
+
assert.deepStrictEqual(dbState.progress?.tasks?.done, fileState.progress?.tasks?.done, 'db-match: tasks.done matches');
|
|
158
|
+
assert.deepStrictEqual(dbState.progress?.tasks?.total, fileState.progress?.tasks?.total, 'db-match: tasks.total matches');
|
|
162
159
|
|
|
163
160
|
closeDatabase();
|
|
164
161
|
} finally {
|
|
165
162
|
closeDatabase();
|
|
166
163
|
cleanup(base);
|
|
167
164
|
}
|
|
168
|
-
}
|
|
165
|
+
});
|
|
169
166
|
|
|
170
167
|
// ─── Test 2: Fallback when DB unavailable ─────────────────────────────
|
|
171
|
-
|
|
172
|
-
{
|
|
168
|
+
test('derive-state-db: fallback when DB unavailable', async () => {
|
|
173
169
|
const base = createFixtureBase();
|
|
174
170
|
try {
|
|
175
171
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -178,22 +174,21 @@ async function main(): Promise<void> {
|
|
|
178
174
|
writeFile(base, 'milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01 Plan');
|
|
179
175
|
|
|
180
176
|
// No DB open — isDbAvailable() is false
|
|
181
|
-
|
|
177
|
+
assert.ok(!isDbAvailable(), 'fallback: DB is not available');
|
|
182
178
|
invalidateStateCache();
|
|
183
179
|
const state = await deriveState(base);
|
|
184
180
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
181
|
+
assert.deepStrictEqual(state.phase, 'executing', 'fallback: phase is executing');
|
|
182
|
+
assert.deepStrictEqual(state.activeMilestone?.id, 'M001', 'fallback: activeMilestone is M001');
|
|
183
|
+
assert.deepStrictEqual(state.activeSlice?.id, 'S01', 'fallback: activeSlice is S01');
|
|
184
|
+
assert.deepStrictEqual(state.activeTask?.id, 'T01', 'fallback: activeTask is T01');
|
|
189
185
|
} finally {
|
|
190
186
|
cleanup(base);
|
|
191
187
|
}
|
|
192
|
-
}
|
|
188
|
+
});
|
|
193
189
|
|
|
194
190
|
// ─── Test 3: Empty DB falls back to file reads ────────────────────────
|
|
195
|
-
|
|
196
|
-
{
|
|
191
|
+
test('derive-state-db: empty DB falls back to files', async () => {
|
|
197
192
|
const base = createFixtureBase();
|
|
198
193
|
try {
|
|
199
194
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -203,27 +198,26 @@ async function main(): Promise<void> {
|
|
|
203
198
|
|
|
204
199
|
// Open DB but insert nothing — empty artifacts table
|
|
205
200
|
openDatabase(':memory:');
|
|
206
|
-
|
|
201
|
+
assert.ok(isDbAvailable(), 'empty-db: DB is available');
|
|
207
202
|
|
|
208
203
|
invalidateStateCache();
|
|
209
204
|
const state = await deriveState(base);
|
|
210
205
|
|
|
211
206
|
// Should still work via cachedLoadFile → loadFile disk fallback
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
207
|
+
assert.deepStrictEqual(state.phase, 'executing', 'empty-db: phase is executing');
|
|
208
|
+
assert.deepStrictEqual(state.activeMilestone?.id, 'M001', 'empty-db: activeMilestone is M001');
|
|
209
|
+
assert.deepStrictEqual(state.activeSlice?.id, 'S01', 'empty-db: activeSlice is S01');
|
|
210
|
+
assert.deepStrictEqual(state.activeTask?.id, 'T01', 'empty-db: activeTask is T01');
|
|
216
211
|
|
|
217
212
|
closeDatabase();
|
|
218
213
|
} finally {
|
|
219
214
|
closeDatabase();
|
|
220
215
|
cleanup(base);
|
|
221
216
|
}
|
|
222
|
-
}
|
|
217
|
+
});
|
|
223
218
|
|
|
224
219
|
// ─── Test 4: Partial DB content fills gaps from disk ──────────────────
|
|
225
|
-
|
|
226
|
-
{
|
|
220
|
+
test('derive-state-db: partial DB fills gaps from disk', async () => {
|
|
227
221
|
const base = createFixtureBase();
|
|
228
222
|
try {
|
|
229
223
|
// Write all files to disk
|
|
@@ -244,25 +238,24 @@ async function main(): Promise<void> {
|
|
|
244
238
|
const state = await deriveState(base);
|
|
245
239
|
|
|
246
240
|
// Should work: roadmap from DB, plan from disk fallback
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
241
|
+
assert.deepStrictEqual(state.phase, 'executing', 'partial-db: phase is executing');
|
|
242
|
+
assert.deepStrictEqual(state.activeMilestone?.id, 'M001', 'partial-db: activeMilestone is M001');
|
|
243
|
+
assert.deepStrictEqual(state.activeSlice?.id, 'S01', 'partial-db: activeSlice is S01');
|
|
244
|
+
assert.deepStrictEqual(state.activeTask?.id, 'T01', 'partial-db: activeTask is T01');
|
|
251
245
|
// Requirements loaded from disk fallback
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
246
|
+
assert.deepStrictEqual(state.requirements?.active, 2, 'partial-db: requirements.active from disk');
|
|
247
|
+
assert.deepStrictEqual(state.requirements?.validated, 1, 'partial-db: requirements.validated from disk');
|
|
248
|
+
assert.deepStrictEqual(state.requirements?.total, 3, 'partial-db: requirements.total from disk');
|
|
255
249
|
|
|
256
250
|
closeDatabase();
|
|
257
251
|
} finally {
|
|
258
252
|
closeDatabase();
|
|
259
253
|
cleanup(base);
|
|
260
254
|
}
|
|
261
|
-
}
|
|
255
|
+
});
|
|
262
256
|
|
|
263
257
|
// ─── Test 5: Requirements counting from disk (DB no longer used for content) ─
|
|
264
|
-
|
|
265
|
-
{
|
|
258
|
+
test('derive-state-db: requirements from disk content', async () => {
|
|
266
259
|
const base = createFixtureBase();
|
|
267
260
|
try {
|
|
268
261
|
// Write minimal milestone dir (needed for milestone discovery)
|
|
@@ -274,17 +267,16 @@ async function main(): Promise<void> {
|
|
|
274
267
|
const state = await deriveState(base);
|
|
275
268
|
|
|
276
269
|
// Requirements should come from disk
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
270
|
+
assert.deepStrictEqual(state.requirements?.active, 2, 'req-from-disk: requirements.active = 2');
|
|
271
|
+
assert.deepStrictEqual(state.requirements?.validated, 1, 'req-from-disk: requirements.validated = 1');
|
|
272
|
+
assert.deepStrictEqual(state.requirements?.total, 3, 'req-from-disk: requirements.total = 3');
|
|
280
273
|
} finally {
|
|
281
274
|
cleanup(base);
|
|
282
275
|
}
|
|
283
|
-
}
|
|
276
|
+
});
|
|
284
277
|
|
|
285
278
|
// ─── Test 6: DB content with multi-milestone registry ─────────────────
|
|
286
|
-
|
|
287
|
-
{
|
|
279
|
+
test('derive-state-db: multi-milestone from DB', async () => {
|
|
288
280
|
const base = createFixtureBase();
|
|
289
281
|
|
|
290
282
|
const completedRoadmap = `# M001: First Milestone
|
|
@@ -337,24 +329,23 @@ async function main(): Promise<void> {
|
|
|
337
329
|
invalidateStateCache();
|
|
338
330
|
const state = await deriveState(base);
|
|
339
331
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
332
|
+
assert.deepStrictEqual(state.registry.length, 2, 'multi-ms-db: registry has 2 entries');
|
|
333
|
+
assert.deepStrictEqual(state.registry[0]?.id, 'M001', 'multi-ms-db: registry[0] is M001');
|
|
334
|
+
assert.deepStrictEqual(state.registry[0]?.status, 'complete', 'multi-ms-db: M001 is complete');
|
|
335
|
+
assert.deepStrictEqual(state.registry[1]?.id, 'M002', 'multi-ms-db: registry[1] is M002');
|
|
336
|
+
assert.deepStrictEqual(state.registry[1]?.status, 'active', 'multi-ms-db: M002 is active');
|
|
337
|
+
assert.deepStrictEqual(state.activeMilestone?.id, 'M002', 'multi-ms-db: activeMilestone is M002');
|
|
338
|
+
assert.deepStrictEqual(state.phase, 'planning', 'multi-ms-db: phase is planning (no plan for S01)');
|
|
347
339
|
|
|
348
340
|
closeDatabase();
|
|
349
341
|
} finally {
|
|
350
342
|
closeDatabase();
|
|
351
343
|
cleanup(base);
|
|
352
344
|
}
|
|
353
|
-
}
|
|
345
|
+
});
|
|
354
346
|
|
|
355
347
|
// ─── Test 7: Cache invalidation works for DB path ─────────────────────
|
|
356
|
-
|
|
357
|
-
{
|
|
348
|
+
test('derive-state-db: cache invalidation', async () => {
|
|
358
349
|
const base = createFixtureBase();
|
|
359
350
|
try {
|
|
360
351
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -375,7 +366,7 @@ async function main(): Promise<void> {
|
|
|
375
366
|
|
|
376
367
|
invalidateStateCache();
|
|
377
368
|
const state1 = await deriveState(base);
|
|
378
|
-
|
|
369
|
+
assert.deepStrictEqual(state1.activeTask?.id, 'T01', 'cache-inv: first call gets T01');
|
|
379
370
|
|
|
380
371
|
// Simulate task completion by updating the plan in DB
|
|
381
372
|
const updatedPlan = PLAN_CONTENT.replace('- [ ] **T01:', '- [x] **T01:');
|
|
@@ -389,28 +380,27 @@ async function main(): Promise<void> {
|
|
|
389
380
|
|
|
390
381
|
// Without invalidation, should return cached result (T01 still active)
|
|
391
382
|
const state2 = await deriveState(base);
|
|
392
|
-
|
|
383
|
+
assert.deepStrictEqual(state2.activeTask?.id, 'T01', 'cache-inv: cached result still has T01');
|
|
393
384
|
|
|
394
385
|
// After invalidation, should pick up updated content
|
|
395
386
|
invalidateStateCache();
|
|
396
387
|
const state3 = await deriveState(base);
|
|
397
|
-
|
|
398
|
-
|
|
388
|
+
assert.deepStrictEqual(state3.phase, 'summarizing', 'cache-inv: after invalidation, phase is summarizing (all tasks done)');
|
|
389
|
+
assert.deepStrictEqual(state3.activeTask, null, 'cache-inv: activeTask is null after all done');
|
|
399
390
|
|
|
400
391
|
closeDatabase();
|
|
401
392
|
} finally {
|
|
402
393
|
closeDatabase();
|
|
403
394
|
cleanup(base);
|
|
404
395
|
}
|
|
405
|
-
}
|
|
396
|
+
});
|
|
406
397
|
|
|
407
398
|
// ═════════════════════════════════════════════════════════════════════════
|
|
408
399
|
// New: deriveStateFromDb() cross-validation tests
|
|
409
400
|
// ═════════════════════════════════════════════════════════════════════════
|
|
410
401
|
|
|
411
402
|
// ─── Test 8: Pre-planning — milestone exists, no roadmap, no slices ───
|
|
412
|
-
|
|
413
|
-
{
|
|
403
|
+
test('derive-state-db: pre-planning via DB', async () => {
|
|
414
404
|
const base = createFixtureBase();
|
|
415
405
|
try {
|
|
416
406
|
// Create milestone dir on disk with a CONTEXT file (not a ghost)
|
|
@@ -427,23 +417,22 @@ async function main(): Promise<void> {
|
|
|
427
417
|
invalidateStateCache();
|
|
428
418
|
const dbState = await deriveStateFromDb(base);
|
|
429
419
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
420
|
+
assert.deepStrictEqual(dbState.phase, fileState.phase, 'pre-plan-db: phase matches');
|
|
421
|
+
assert.deepStrictEqual(dbState.activeMilestone?.id, fileState.activeMilestone?.id, 'pre-plan-db: activeMilestone.id matches');
|
|
422
|
+
assert.deepStrictEqual(dbState.activeSlice, fileState.activeSlice, 'pre-plan-db: activeSlice matches');
|
|
423
|
+
assert.deepStrictEqual(dbState.activeTask, fileState.activeTask, 'pre-plan-db: activeTask matches');
|
|
424
|
+
assert.deepStrictEqual(dbState.registry.length, fileState.registry.length, 'pre-plan-db: registry length matches');
|
|
425
|
+
assert.deepStrictEqual(dbState.registry[0]?.status, fileState.registry[0]?.status, 'pre-plan-db: registry[0] status matches');
|
|
436
426
|
|
|
437
427
|
closeDatabase();
|
|
438
428
|
} finally {
|
|
439
429
|
closeDatabase();
|
|
440
430
|
cleanup(base);
|
|
441
431
|
}
|
|
442
|
-
}
|
|
432
|
+
});
|
|
443
433
|
|
|
444
434
|
// ─── Test 9: Executing — active task with partial completion ──────────
|
|
445
|
-
|
|
446
|
-
{
|
|
435
|
+
test('derive-state-db: executing via DB', async () => {
|
|
447
436
|
const base = createFixtureBase();
|
|
448
437
|
try {
|
|
449
438
|
// Build filesystem fixture
|
|
@@ -466,24 +455,23 @@ async function main(): Promise<void> {
|
|
|
466
455
|
invalidateStateCache();
|
|
467
456
|
const dbState = await deriveStateFromDb(base);
|
|
468
457
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
458
|
+
assert.deepStrictEqual(dbState.phase, 'executing', 'exec-db: phase is executing');
|
|
459
|
+
assert.deepStrictEqual(dbState.activeMilestone?.id, 'M001', 'exec-db: activeMilestone is M001');
|
|
460
|
+
assert.deepStrictEqual(dbState.activeSlice?.id, 'S01', 'exec-db: activeSlice is S01');
|
|
461
|
+
assert.deepStrictEqual(dbState.activeTask?.id, 'T01', 'exec-db: activeTask is T01');
|
|
462
|
+
assert.deepStrictEqual(dbState.progress?.tasks?.done, 1, 'exec-db: tasks.done = 1');
|
|
463
|
+
assert.deepStrictEqual(dbState.progress?.tasks?.total, 2, 'exec-db: tasks.total = 2');
|
|
464
|
+
assert.deepStrictEqual(dbState.phase, fileState.phase, 'exec-db: phase matches filesystem');
|
|
476
465
|
|
|
477
466
|
closeDatabase();
|
|
478
467
|
} finally {
|
|
479
468
|
closeDatabase();
|
|
480
469
|
cleanup(base);
|
|
481
470
|
}
|
|
482
|
-
}
|
|
471
|
+
});
|
|
483
472
|
|
|
484
473
|
// ─── Test 10: Summarizing — all tasks complete, no slice summary ──────
|
|
485
|
-
|
|
486
|
-
{
|
|
474
|
+
test('derive-state-db: summarizing via DB', async () => {
|
|
487
475
|
const base = createFixtureBase();
|
|
488
476
|
try {
|
|
489
477
|
const allDonePlan = `# S01: First Slice
|
|
@@ -517,21 +505,20 @@ async function main(): Promise<void> {
|
|
|
517
505
|
invalidateStateCache();
|
|
518
506
|
const dbState = await deriveStateFromDb(base);
|
|
519
507
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
508
|
+
assert.deepStrictEqual(dbState.phase, 'summarizing', 'summarize-db: phase is summarizing');
|
|
509
|
+
assert.deepStrictEqual(dbState.phase, fileState.phase, 'summarize-db: phase matches filesystem');
|
|
510
|
+
assert.deepStrictEqual(dbState.activeSlice?.id, 'S01', 'summarize-db: activeSlice is S01');
|
|
511
|
+
assert.deepStrictEqual(dbState.activeTask, null, 'summarize-db: activeTask is null');
|
|
524
512
|
|
|
525
513
|
closeDatabase();
|
|
526
514
|
} finally {
|
|
527
515
|
closeDatabase();
|
|
528
516
|
cleanup(base);
|
|
529
517
|
}
|
|
530
|
-
}
|
|
518
|
+
});
|
|
531
519
|
|
|
532
520
|
// ─── Test 11: Complete — all milestones complete ──────────────────────
|
|
533
|
-
|
|
534
|
-
{
|
|
521
|
+
test('derive-state-db: all complete via DB', async () => {
|
|
535
522
|
const base = createFixtureBase();
|
|
536
523
|
try {
|
|
537
524
|
const completedRoadmap = `# M001: Done Milestone
|
|
@@ -557,21 +544,20 @@ async function main(): Promise<void> {
|
|
|
557
544
|
invalidateStateCache();
|
|
558
545
|
const dbState = await deriveStateFromDb(base);
|
|
559
546
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
547
|
+
assert.deepStrictEqual(dbState.phase, 'complete', 'complete-db: phase is complete');
|
|
548
|
+
assert.deepStrictEqual(dbState.phase, fileState.phase, 'complete-db: phase matches filesystem');
|
|
549
|
+
assert.deepStrictEqual(dbState.registry.length, 1, 'complete-db: registry has 1 entry');
|
|
550
|
+
assert.deepStrictEqual(dbState.registry[0]?.status, 'complete', 'complete-db: M001 is complete');
|
|
564
551
|
|
|
565
552
|
closeDatabase();
|
|
566
553
|
} finally {
|
|
567
554
|
closeDatabase();
|
|
568
555
|
cleanup(base);
|
|
569
556
|
}
|
|
570
|
-
}
|
|
557
|
+
});
|
|
571
558
|
|
|
572
559
|
// ─── Test 12: Blocked — slice deps unmet ──────────────────────────────
|
|
573
|
-
|
|
574
|
-
{
|
|
560
|
+
test('derive-state-db: blocked slice via DB', async () => {
|
|
575
561
|
const base = createFixtureBase();
|
|
576
562
|
try {
|
|
577
563
|
// Roadmap with S02 depending on S01, but S01 not done
|
|
@@ -601,20 +587,19 @@ async function main(): Promise<void> {
|
|
|
601
587
|
invalidateStateCache();
|
|
602
588
|
const dbState = await deriveStateFromDb(base);
|
|
603
589
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
590
|
+
assert.deepStrictEqual(dbState.phase, 'blocked', 'blocked-db: phase is blocked');
|
|
591
|
+
assert.deepStrictEqual(dbState.phase, fileState.phase, 'blocked-db: phase matches filesystem');
|
|
592
|
+
assert.ok(dbState.blockers.length > 0, 'blocked-db: has blockers');
|
|
607
593
|
|
|
608
594
|
closeDatabase();
|
|
609
595
|
} finally {
|
|
610
596
|
closeDatabase();
|
|
611
597
|
cleanup(base);
|
|
612
598
|
}
|
|
613
|
-
}
|
|
599
|
+
});
|
|
614
600
|
|
|
615
601
|
// ─── Test 13: Parked milestone ────────────────────────────────────────
|
|
616
|
-
|
|
617
|
-
{
|
|
602
|
+
test('derive-state-db: parked milestone via DB', async () => {
|
|
618
603
|
const base = createFixtureBase();
|
|
619
604
|
try {
|
|
620
605
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -631,20 +616,19 @@ async function main(): Promise<void> {
|
|
|
631
616
|
invalidateStateCache();
|
|
632
617
|
const dbState = await deriveStateFromDb(base);
|
|
633
618
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
619
|
+
assert.deepStrictEqual(dbState.phase, fileState.phase, 'parked-db: phase matches filesystem');
|
|
620
|
+
assert.deepStrictEqual(dbState.activeMilestone?.id, 'M002', 'parked-db: activeMilestone is M002');
|
|
621
|
+
assert.ok(dbState.registry.some(e => e.id === 'M001' && e.status === 'parked'), 'parked-db: M001 is parked in registry');
|
|
637
622
|
|
|
638
623
|
closeDatabase();
|
|
639
624
|
} finally {
|
|
640
625
|
closeDatabase();
|
|
641
626
|
cleanup(base);
|
|
642
627
|
}
|
|
643
|
-
}
|
|
628
|
+
});
|
|
644
629
|
|
|
645
630
|
// ─── Test 14: Validating-milestone — all slices done, no terminal validation ─
|
|
646
|
-
|
|
647
|
-
{
|
|
631
|
+
test('derive-state-db: validating-milestone via DB', async () => {
|
|
648
632
|
const base = createFixtureBase();
|
|
649
633
|
try {
|
|
650
634
|
const doneRoadmap = `# M001: Validate Test
|
|
@@ -669,20 +653,19 @@ async function main(): Promise<void> {
|
|
|
669
653
|
invalidateStateCache();
|
|
670
654
|
const dbState = await deriveStateFromDb(base);
|
|
671
655
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
656
|
+
assert.deepStrictEqual(dbState.phase, 'validating-milestone', 'validate-db: phase is validating-milestone');
|
|
657
|
+
assert.deepStrictEqual(dbState.phase, fileState.phase, 'validate-db: phase matches filesystem');
|
|
658
|
+
assert.deepStrictEqual(dbState.activeMilestone?.id, 'M001', 'validate-db: activeMilestone is M001');
|
|
675
659
|
|
|
676
660
|
closeDatabase();
|
|
677
661
|
} finally {
|
|
678
662
|
closeDatabase();
|
|
679
663
|
cleanup(base);
|
|
680
664
|
}
|
|
681
|
-
}
|
|
665
|
+
});
|
|
682
666
|
|
|
683
667
|
// ─── Test 15: Completing-milestone — terminal validation, no summary ──
|
|
684
|
-
|
|
685
|
-
{
|
|
668
|
+
test('derive-state-db: completing-milestone via DB', async () => {
|
|
686
669
|
const base = createFixtureBase();
|
|
687
670
|
try {
|
|
688
671
|
const doneRoadmap = `# M001: Complete Test
|
|
@@ -707,19 +690,18 @@ async function main(): Promise<void> {
|
|
|
707
690
|
invalidateStateCache();
|
|
708
691
|
const dbState = await deriveStateFromDb(base);
|
|
709
692
|
|
|
710
|
-
|
|
711
|
-
|
|
693
|
+
assert.deepStrictEqual(dbState.phase, 'completing-milestone', 'completing-db: phase is completing-milestone');
|
|
694
|
+
assert.deepStrictEqual(dbState.phase, fileState.phase, 'completing-db: phase matches filesystem');
|
|
712
695
|
|
|
713
696
|
closeDatabase();
|
|
714
697
|
} finally {
|
|
715
698
|
closeDatabase();
|
|
716
699
|
cleanup(base);
|
|
717
700
|
}
|
|
718
|
-
}
|
|
701
|
+
});
|
|
719
702
|
|
|
720
703
|
// ─── Test 16: Replanning-slice — REPLAN-TRIGGER file exists ───────────
|
|
721
|
-
|
|
722
|
-
{
|
|
704
|
+
test('derive-state-db: replanning-slice via DB', async () => {
|
|
723
705
|
const base = createFixtureBase();
|
|
724
706
|
try {
|
|
725
707
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -749,19 +731,18 @@ async function main(): Promise<void> {
|
|
|
749
731
|
invalidateStateCache();
|
|
750
732
|
const dbState = await deriveStateFromDb(base);
|
|
751
733
|
|
|
752
|
-
|
|
753
|
-
|
|
734
|
+
assert.deepStrictEqual(dbState.phase, 'replanning-slice', 'replan-db: phase is replanning-slice');
|
|
735
|
+
assert.deepStrictEqual(dbState.phase, fileState.phase, 'replan-db: phase matches filesystem');
|
|
754
736
|
|
|
755
737
|
closeDatabase();
|
|
756
738
|
} finally {
|
|
757
739
|
closeDatabase();
|
|
758
740
|
cleanup(base);
|
|
759
741
|
}
|
|
760
|
-
}
|
|
742
|
+
});
|
|
761
743
|
|
|
762
744
|
// ─── Test 17: Performance — deriveStateFromDb < 1ms on populated DB ───
|
|
763
|
-
|
|
764
|
-
{
|
|
745
|
+
test('derive-state-db: performance assertion', async () => {
|
|
765
746
|
const base = createFixtureBase();
|
|
766
747
|
try {
|
|
767
748
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -789,18 +770,17 @@ async function main(): Promise<void> {
|
|
|
789
770
|
console.log(` deriveStateFromDb() took ${elapsed.toFixed(3)}ms`);
|
|
790
771
|
// Use 10ms threshold — catches real regressions without flaking on
|
|
791
772
|
// CI runners under load (1ms threshold failed at 1.050ms on GitHub Actions)
|
|
792
|
-
|
|
773
|
+
assert.ok(elapsed < 10, `perf-db: deriveStateFromDb() <10ms (got ${elapsed.toFixed(3)}ms)`);
|
|
793
774
|
|
|
794
775
|
closeDatabase();
|
|
795
776
|
} finally {
|
|
796
777
|
closeDatabase();
|
|
797
778
|
cleanup(base);
|
|
798
779
|
}
|
|
799
|
-
}
|
|
780
|
+
});
|
|
800
781
|
|
|
801
782
|
// ─── Test 18: Multi-milestone with deps — M001 complete, M002 depends on M001, M003 depends on M002 ─
|
|
802
|
-
|
|
803
|
-
{
|
|
783
|
+
test('derive-state-db: multi-milestone deps via DB', async () => {
|
|
804
784
|
const base = createFixtureBase();
|
|
805
785
|
try {
|
|
806
786
|
const m1Roadmap = `# M001: First
|
|
@@ -841,29 +821,28 @@ async function main(): Promise<void> {
|
|
|
841
821
|
invalidateStateCache();
|
|
842
822
|
const dbState = await deriveStateFromDb(base);
|
|
843
823
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
824
|
+
assert.deepStrictEqual(dbState.registry.length, fileState.registry.length, 'multi-deps-db: registry length matches');
|
|
825
|
+
assert.deepStrictEqual(dbState.activeMilestone?.id, 'M002', 'multi-deps-db: activeMilestone is M002 (M001 complete, M003 dep unmet)');
|
|
826
|
+
assert.deepStrictEqual(dbState.activeMilestone?.id, fileState.activeMilestone?.id, 'multi-deps-db: activeMilestone matches filesystem');
|
|
827
|
+
assert.deepStrictEqual(dbState.phase, fileState.phase, 'multi-deps-db: phase matches filesystem');
|
|
848
828
|
|
|
849
829
|
// Check registry statuses
|
|
850
830
|
const m1reg = dbState.registry.find(e => e.id === 'M001');
|
|
851
831
|
const m2reg = dbState.registry.find(e => e.id === 'M002');
|
|
852
832
|
const m3reg = dbState.registry.find(e => e.id === 'M003');
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
833
|
+
assert.deepStrictEqual(m1reg?.status, 'complete', 'multi-deps-db: M001 is complete');
|
|
834
|
+
assert.deepStrictEqual(m2reg?.status, 'active', 'multi-deps-db: M002 is active');
|
|
835
|
+
assert.deepStrictEqual(m3reg?.status, 'pending', 'multi-deps-db: M003 is pending (dep M002 unmet)');
|
|
856
836
|
|
|
857
837
|
closeDatabase();
|
|
858
838
|
} finally {
|
|
859
839
|
closeDatabase();
|
|
860
840
|
cleanup(base);
|
|
861
841
|
}
|
|
862
|
-
}
|
|
842
|
+
});
|
|
863
843
|
|
|
864
844
|
// ─── Test 19: K002 — both 'complete' and 'done' treated as done ───────
|
|
865
|
-
|
|
866
|
-
{
|
|
845
|
+
test('derive-state-db: K002 status handling', async () => {
|
|
867
846
|
const base = createFixtureBase();
|
|
868
847
|
try {
|
|
869
848
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -882,20 +861,19 @@ async function main(): Promise<void> {
|
|
|
882
861
|
invalidateStateCache();
|
|
883
862
|
const dbState = await deriveStateFromDb(base);
|
|
884
863
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
864
|
+
assert.deepStrictEqual(dbState.phase, 'executing', 'k002-db: phase is executing');
|
|
865
|
+
assert.deepStrictEqual(dbState.activeTask?.id, 'T01', 'k002-db: activeTask is T01 (T02 done)');
|
|
866
|
+
assert.deepStrictEqual(dbState.progress?.tasks?.done, 1, 'k002-db: tasks.done counts done status');
|
|
888
867
|
|
|
889
868
|
closeDatabase();
|
|
890
869
|
} finally {
|
|
891
870
|
closeDatabase();
|
|
892
871
|
cleanup(base);
|
|
893
872
|
}
|
|
894
|
-
}
|
|
873
|
+
});
|
|
895
874
|
|
|
896
875
|
// ─── Test 20: Dual-path wiring — deriveState() uses DB when populated ─
|
|
897
|
-
|
|
898
|
-
{
|
|
876
|
+
test('derive-state-db: dual-path wiring', async () => {
|
|
899
877
|
const base = createFixtureBase();
|
|
900
878
|
try {
|
|
901
879
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -914,21 +892,20 @@ async function main(): Promise<void> {
|
|
|
914
892
|
invalidateStateCache();
|
|
915
893
|
const state = await deriveState(base);
|
|
916
894
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
895
|
+
assert.deepStrictEqual(state.phase, 'executing', 'dual-path: phase is executing');
|
|
896
|
+
assert.deepStrictEqual(state.activeMilestone?.id, 'M001', 'dual-path: activeMilestone is M001');
|
|
897
|
+
assert.deepStrictEqual(state.activeSlice?.id, 'S01', 'dual-path: activeSlice is S01');
|
|
898
|
+
assert.deepStrictEqual(state.activeTask?.id, 'T01', 'dual-path: activeTask is T01');
|
|
921
899
|
|
|
922
900
|
closeDatabase();
|
|
923
901
|
} finally {
|
|
924
902
|
closeDatabase();
|
|
925
903
|
cleanup(base);
|
|
926
904
|
}
|
|
927
|
-
}
|
|
905
|
+
});
|
|
928
906
|
|
|
929
907
|
// ─── Test 21: Ghost milestone skipped ─────────────────────────────────
|
|
930
|
-
|
|
931
|
-
{
|
|
908
|
+
test('derive-state-db: ghost milestone skipped', async () => {
|
|
932
909
|
const base = createFixtureBase();
|
|
933
910
|
try {
|
|
934
911
|
// Ghost: milestone dir exists with only META.json, no context/roadmap/summary
|
|
@@ -949,21 +926,20 @@ async function main(): Promise<void> {
|
|
|
949
926
|
const dbState = await deriveStateFromDb(base);
|
|
950
927
|
|
|
951
928
|
// Ghost should be skipped — M002 should be active
|
|
952
|
-
|
|
953
|
-
|
|
929
|
+
assert.deepStrictEqual(dbState.activeMilestone?.id, 'M002', 'ghost-db: activeMilestone is M002 (ghost skipped)');
|
|
930
|
+
assert.deepStrictEqual(dbState.activeMilestone?.id, fileState.activeMilestone?.id, 'ghost-db: matches filesystem');
|
|
954
931
|
// Ghost should not appear in registry
|
|
955
|
-
|
|
932
|
+
assert.ok(!dbState.registry.some(e => e.id === 'M001'), 'ghost-db: M001 not in registry');
|
|
956
933
|
|
|
957
934
|
closeDatabase();
|
|
958
935
|
} finally {
|
|
959
936
|
closeDatabase();
|
|
960
937
|
cleanup(base);
|
|
961
938
|
}
|
|
962
|
-
}
|
|
939
|
+
});
|
|
963
940
|
|
|
964
941
|
// ─── Test 22: Needs-discussion — CONTEXT-DRAFT exists ─────────────────
|
|
965
|
-
|
|
966
|
-
{
|
|
942
|
+
test('derive-state-db: needs-discussion via DB', async () => {
|
|
967
943
|
const base = createFixtureBase();
|
|
968
944
|
try {
|
|
969
945
|
writeFile(base, 'milestones/M001/M001-CONTEXT-DRAFT.md', '# M001: Draft\n\nDraft content.');
|
|
@@ -977,20 +953,13 @@ async function main(): Promise<void> {
|
|
|
977
953
|
invalidateStateCache();
|
|
978
954
|
const dbState = await deriveStateFromDb(base);
|
|
979
955
|
|
|
980
|
-
|
|
981
|
-
|
|
956
|
+
assert.deepStrictEqual(dbState.phase, 'needs-discussion', 'discuss-db: phase is needs-discussion');
|
|
957
|
+
assert.deepStrictEqual(dbState.phase, fileState.phase, 'discuss-db: phase matches filesystem');
|
|
982
958
|
|
|
983
959
|
closeDatabase();
|
|
984
960
|
} finally {
|
|
985
961
|
closeDatabase();
|
|
986
962
|
cleanup(base);
|
|
987
963
|
}
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
report();
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
main().catch((error) => {
|
|
994
|
-
console.error(error);
|
|
995
|
-
process.exit(1);
|
|
964
|
+
});
|
|
996
965
|
});
|