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,125 +1,114 @@
|
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
1
3
|
// gsd-inspect — Tests for /gsd inspect output formatting
|
|
2
4
|
//
|
|
3
5
|
// Tests the pure formatInspectOutput function with known data.
|
|
4
6
|
|
|
5
|
-
import { createTestContext } from './test-helpers.ts';
|
|
6
7
|
import { formatInspectOutput, type InspectData } from '../commands-inspect.ts';
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
{
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
counts: { decisions: 1, requirements: 1, artifacts: 0 },
|
|
115
|
-
recentDecisions: [{ id: "D001", decision: "Test", choice: "Yes" }],
|
|
116
|
-
recentRequirements: [{ id: "R001", status: "active", description: "Test req" }],
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const output = formatInspectOutput(data);
|
|
120
|
-
const lines = output.split("\n");
|
|
121
|
-
assertTrue(lines.length > 5, "output has multiple lines");
|
|
122
|
-
assertTrue(!output.startsWith("{"), "output is not JSON");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
report();
|
|
9
|
+
describe('gsd-inspect', () => {
|
|
10
|
+
test('full output formatting', () => {
|
|
11
|
+
const data: InspectData = {
|
|
12
|
+
schemaVersion: 2,
|
|
13
|
+
counts: { decisions: 12, requirements: 8, artifacts: 3 },
|
|
14
|
+
recentDecisions: [
|
|
15
|
+
{ id: "D012", decision: "Use SQLite for persistence", choice: "node:sqlite with fallback" },
|
|
16
|
+
{ id: "D011", decision: "Markdown dual-write", choice: "DB-first then regenerate" },
|
|
17
|
+
],
|
|
18
|
+
recentRequirements: [
|
|
19
|
+
{ id: "R015", status: "active", description: "Commands register via pi.registerCommand" },
|
|
20
|
+
{ id: "R014", status: "active", description: "DB writes use upsert pattern" },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const output = formatInspectOutput(data);
|
|
25
|
+
|
|
26
|
+
assert.match(output, /=== GSD Database Inspect ===/, "contains header");
|
|
27
|
+
assert.match(output, /Schema version: 2/, "contains schema version");
|
|
28
|
+
assert.match(output, /Decisions:\s+12/, "contains decisions count");
|
|
29
|
+
assert.match(output, /Requirements:\s+8/, "contains requirements count");
|
|
30
|
+
assert.match(output, /Artifacts:\s+3/, "contains artifacts count");
|
|
31
|
+
assert.match(output, /Recent decisions:/, "contains recent decisions header");
|
|
32
|
+
assert.match(output, /D012: Use SQLite for persistence → node:sqlite with fallback/, "contains D012 entry");
|
|
33
|
+
assert.match(output, /D011: Markdown dual-write → DB-first then regenerate/, "contains D011 entry");
|
|
34
|
+
assert.match(output, /Recent requirements:/, "contains recent requirements header");
|
|
35
|
+
assert.match(output, /R015 \[active\]: Commands register via pi\.registerCommand/, "contains R015 entry");
|
|
36
|
+
assert.match(output, /R014 \[active\]: DB writes use upsert pattern/, "contains R014 entry");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('empty data', () => {
|
|
40
|
+
const data: InspectData = {
|
|
41
|
+
schemaVersion: 1,
|
|
42
|
+
counts: { decisions: 0, requirements: 0, artifacts: 0 },
|
|
43
|
+
recentDecisions: [],
|
|
44
|
+
recentRequirements: [],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const output = formatInspectOutput(data);
|
|
48
|
+
|
|
49
|
+
assert.match(output, /Schema version: 1/, "contains schema version 1");
|
|
50
|
+
assert.match(output, /Decisions:\s+0/, "zero decisions");
|
|
51
|
+
assert.match(output, /Requirements:\s+0/, "zero requirements");
|
|
52
|
+
assert.match(output, /Artifacts:\s+0/, "zero artifacts");
|
|
53
|
+
assert.ok(!output.includes("Recent decisions:"), "no recent decisions section when empty");
|
|
54
|
+
assert.ok(!output.includes("Recent requirements:"), "no recent requirements section when empty");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('null schema version', () => {
|
|
58
|
+
const data: InspectData = {
|
|
59
|
+
schemaVersion: null,
|
|
60
|
+
counts: { decisions: 0, requirements: 0, artifacts: 0 },
|
|
61
|
+
recentDecisions: [],
|
|
62
|
+
recentRequirements: [],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const output = formatInspectOutput(data);
|
|
66
|
+
assert.match(output, /Schema version: unknown/, "null version shows as unknown");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('five recent entries', () => {
|
|
70
|
+
const data: InspectData = {
|
|
71
|
+
schemaVersion: 2,
|
|
72
|
+
counts: { decisions: 5, requirements: 5, artifacts: 0 },
|
|
73
|
+
recentDecisions: [
|
|
74
|
+
{ id: "D005", decision: "Dec 5", choice: "C5" },
|
|
75
|
+
{ id: "D004", decision: "Dec 4", choice: "C4" },
|
|
76
|
+
{ id: "D003", decision: "Dec 3", choice: "C3" },
|
|
77
|
+
{ id: "D002", decision: "Dec 2", choice: "C2" },
|
|
78
|
+
{ id: "D001", decision: "Dec 1", choice: "C1" },
|
|
79
|
+
],
|
|
80
|
+
recentRequirements: [
|
|
81
|
+
{ id: "R005", status: "active", description: "Req 5" },
|
|
82
|
+
{ id: "R004", status: "done", description: "Req 4" },
|
|
83
|
+
{ id: "R003", status: "active", description: "Req 3" },
|
|
84
|
+
{ id: "R002", status: "active", description: "Req 2" },
|
|
85
|
+
{ id: "R001", status: "done", description: "Req 1" },
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const output = formatInspectOutput(data);
|
|
90
|
+
|
|
91
|
+
for (let i = 1; i <= 5; i++) {
|
|
92
|
+
assert.match(output, new RegExp(`D00${i}: Dec ${i} → C${i}`), `contains D00${i}`);
|
|
93
|
+
}
|
|
94
|
+
for (let i = 1; i <= 5; i++) {
|
|
95
|
+
assert.match(output, new RegExp(`R00${i}`), `contains R00${i}`);
|
|
96
|
+
}
|
|
97
|
+
assert.match(output, /\[active\]/, "contains active status");
|
|
98
|
+
assert.match(output, /\[done\]/, "contains done status");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('output format', () => {
|
|
102
|
+
const data: InspectData = {
|
|
103
|
+
schemaVersion: 2,
|
|
104
|
+
counts: { decisions: 1, requirements: 1, artifacts: 0 },
|
|
105
|
+
recentDecisions: [{ id: "D001", decision: "Test", choice: "Yes" }],
|
|
106
|
+
recentRequirements: [{ id: "R001", status: "active", description: "Test req" }],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const output = formatInspectOutput(data);
|
|
110
|
+
const lines = output.split("\n");
|
|
111
|
+
assert.ok(lines.length > 5, "output has multiple lines");
|
|
112
|
+
assert.ok(!output.startsWith("{"), "output is not JSON");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
1
3
|
// gsd-recover.test.ts — Tests for the `gsd recover` recovery logic.
|
|
2
4
|
// Verifies: populate DB → clear hierarchy → recover from markdown → state matches.
|
|
3
5
|
|
|
@@ -22,10 +24,6 @@ import {
|
|
|
22
24
|
} from '../gsd-db.ts';
|
|
23
25
|
import { migrateHierarchyToDb } from '../md-importer.ts';
|
|
24
26
|
import { deriveStateFromDb, invalidateStateCache } from '../state.ts';
|
|
25
|
-
import { createTestContext } from './test-helpers.ts';
|
|
26
|
-
|
|
27
|
-
const { assertEq, assertTrue, report } = createTestContext();
|
|
28
|
-
|
|
29
27
|
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
30
28
|
|
|
31
29
|
function createFixtureBase(): string {
|
|
@@ -148,10 +146,8 @@ function clearHierarchyTables(): void {
|
|
|
148
146
|
|
|
149
147
|
// ─── Tests ────────────────────────────────────────────────────────────────
|
|
150
148
|
|
|
151
|
-
async
|
|
152
|
-
|
|
153
|
-
console.log('\n=== recover: full round-trip (populate → clear → recover → verify) ===');
|
|
154
|
-
{
|
|
149
|
+
describe('gsd-recover', async () => {
|
|
150
|
+
test('full round-trip (populate, clear, recover, verify)', async () => {
|
|
155
151
|
const base = createFixtureBase();
|
|
156
152
|
try {
|
|
157
153
|
// Set up markdown fixtures
|
|
@@ -163,14 +159,14 @@ async function main() {
|
|
|
163
159
|
// Step 1: Open DB and populate from markdown
|
|
164
160
|
openDatabase(':memory:');
|
|
165
161
|
const counts1 = migrateHierarchyToDb(base);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
162
|
+
assert.deepStrictEqual(counts1.milestones, 1, 'round-trip: initial migration - 1 milestone');
|
|
163
|
+
assert.deepStrictEqual(counts1.slices, 2, 'round-trip: initial migration - 2 slices');
|
|
164
|
+
assert.ok(counts1.tasks >= 5, 'round-trip: initial migration - at least 5 tasks');
|
|
169
165
|
|
|
170
166
|
// Step 2: Capture state from DB before clearing
|
|
171
167
|
invalidateStateCache();
|
|
172
168
|
const stateBefore = await deriveStateFromDb(base);
|
|
173
|
-
|
|
169
|
+
assert.ok(stateBefore.activeMilestone !== null, 'round-trip: state before has active milestone');
|
|
174
170
|
const milestonesBefore = getAllMilestones();
|
|
175
171
|
const slicesBefore = getMilestoneSlices('M001');
|
|
176
172
|
const s01TasksBefore = getSliceTasks('M001', 'S01');
|
|
@@ -179,30 +175,30 @@ async function main() {
|
|
|
179
175
|
// Step 3: Clear hierarchy tables
|
|
180
176
|
clearHierarchyTables();
|
|
181
177
|
const milestonesAfterClear = getAllMilestones();
|
|
182
|
-
|
|
178
|
+
assert.deepStrictEqual(milestonesAfterClear.length, 0, 'round-trip: milestones cleared');
|
|
183
179
|
|
|
184
180
|
// Step 4: Recover from markdown
|
|
185
181
|
const counts2 = migrateHierarchyToDb(base);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
182
|
+
assert.deepStrictEqual(counts2.milestones, counts1.milestones, 'round-trip: recovery milestone count matches');
|
|
183
|
+
assert.deepStrictEqual(counts2.slices, counts1.slices, 'round-trip: recovery slice count matches');
|
|
184
|
+
assert.deepStrictEqual(counts2.tasks, counts1.tasks, 'round-trip: recovery task count matches');
|
|
189
185
|
|
|
190
186
|
// Step 5: Verify state matches
|
|
191
187
|
invalidateStateCache();
|
|
192
188
|
const stateAfter = await deriveStateFromDb(base);
|
|
193
189
|
|
|
194
|
-
|
|
195
|
-
|
|
190
|
+
assert.deepStrictEqual(stateAfter.phase, stateBefore.phase, 'round-trip: phase matches');
|
|
191
|
+
assert.deepStrictEqual(
|
|
196
192
|
stateAfter.activeMilestone?.id,
|
|
197
193
|
stateBefore.activeMilestone?.id,
|
|
198
194
|
'round-trip: active milestone ID matches',
|
|
199
195
|
);
|
|
200
|
-
|
|
196
|
+
assert.deepStrictEqual(
|
|
201
197
|
stateAfter.activeSlice?.id,
|
|
202
198
|
stateBefore.activeSlice?.id,
|
|
203
199
|
'round-trip: active slice ID matches',
|
|
204
200
|
);
|
|
205
|
-
|
|
201
|
+
assert.deepStrictEqual(
|
|
206
202
|
stateAfter.activeTask?.id,
|
|
207
203
|
stateBefore.activeTask?.id,
|
|
208
204
|
'round-trip: active task ID matches',
|
|
@@ -210,32 +206,30 @@ async function main() {
|
|
|
210
206
|
|
|
211
207
|
// Verify row-level data matches
|
|
212
208
|
const milestonesAfter = getAllMilestones();
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
209
|
+
assert.deepStrictEqual(milestonesAfter.length, milestonesBefore.length, 'round-trip: milestone row count');
|
|
210
|
+
assert.deepStrictEqual(milestonesAfter[0]?.id, milestonesBefore[0]?.id, 'round-trip: milestone ID');
|
|
211
|
+
assert.deepStrictEqual(milestonesAfter[0]?.title, milestonesBefore[0]?.title, 'round-trip: milestone title');
|
|
216
212
|
|
|
217
213
|
const slicesAfter = getMilestoneSlices('M001');
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
214
|
+
assert.deepStrictEqual(slicesAfter.length, slicesBefore.length, 'round-trip: slice row count');
|
|
215
|
+
assert.deepStrictEqual(slicesAfter[0]?.id, slicesBefore[0]?.id, 'round-trip: S01 ID');
|
|
216
|
+
assert.deepStrictEqual(slicesAfter[0]?.status, slicesBefore[0]?.status, 'round-trip: S01 status');
|
|
217
|
+
assert.deepStrictEqual(slicesAfter[1]?.id, slicesBefore[1]?.id, 'round-trip: S02 ID');
|
|
222
218
|
|
|
223
219
|
const s01TasksAfter = getSliceTasks('M001', 'S01');
|
|
224
|
-
|
|
220
|
+
assert.deepStrictEqual(s01TasksAfter.length, s01TasksBefore.length, 'round-trip: S01 task count');
|
|
225
221
|
|
|
226
222
|
const s02TasksAfter = getSliceTasks('M001', 'S02');
|
|
227
|
-
|
|
223
|
+
assert.deepStrictEqual(s02TasksAfter.length, s02TasksBefore.length, 'round-trip: S02 task count');
|
|
228
224
|
|
|
229
225
|
closeDatabase();
|
|
230
226
|
} finally {
|
|
231
227
|
closeDatabase();
|
|
232
228
|
cleanup(base);
|
|
233
229
|
}
|
|
234
|
-
}
|
|
230
|
+
});
|
|
235
231
|
|
|
236
|
-
|
|
237
|
-
console.log('\n=== recover: v8 planning columns populated ===');
|
|
238
|
-
{
|
|
232
|
+
test('v8 planning columns populated', async () => {
|
|
239
233
|
const base = createFixtureBase();
|
|
240
234
|
try {
|
|
241
235
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_M001);
|
|
@@ -248,75 +242,70 @@ async function main() {
|
|
|
248
242
|
|
|
249
243
|
// Milestone planning columns
|
|
250
244
|
const milestone = getMilestone('M001');
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
245
|
+
assert.ok(milestone !== null, 'v8: milestone exists');
|
|
246
|
+
assert.deepStrictEqual(milestone!.vision, 'Test recovery round-trip.', 'v8: milestone vision populated');
|
|
247
|
+
assert.ok(milestone!.success_criteria.length >= 2, 'v8: milestone success_criteria has entries');
|
|
248
|
+
assert.deepStrictEqual(milestone!.success_criteria[0], 'All recovery tests pass', 'v8: first success criterion');
|
|
249
|
+
assert.ok(milestone!.boundary_map_markdown.includes('Boundary Map'), 'v8: boundary_map_markdown populated');
|
|
250
|
+
assert.ok(milestone!.boundary_map_markdown.includes('S01'), 'v8: boundary_map_markdown has S01');
|
|
257
251
|
|
|
258
252
|
// Tool-only fields left empty per D004
|
|
259
|
-
|
|
260
|
-
|
|
253
|
+
assert.deepStrictEqual(milestone!.key_risks.length, 0, 'v8: key_risks left empty (tool-only per D004)');
|
|
254
|
+
assert.deepStrictEqual(milestone!.requirement_coverage, '', 'v8: requirement_coverage left empty (tool-only per D004)');
|
|
261
255
|
|
|
262
256
|
// Slice planning columns
|
|
263
257
|
const sliceS01 = getSlice('M001', 'S01');
|
|
264
|
-
|
|
265
|
-
|
|
258
|
+
assert.ok(sliceS01 !== null, 'v8: slice S01 exists');
|
|
259
|
+
assert.deepStrictEqual(sliceS01!.goal, 'Setup fixtures.', 'v8: S01 goal populated');
|
|
266
260
|
|
|
267
261
|
const sliceS02 = getSlice('M001', 'S02');
|
|
268
|
-
|
|
269
|
-
|
|
262
|
+
assert.ok(sliceS02 !== null, 'v8: slice S02 exists');
|
|
263
|
+
assert.deepStrictEqual(sliceS02!.goal, 'Build core.', 'v8: S02 goal populated');
|
|
270
264
|
|
|
271
265
|
// Slice tool-only fields left empty per D004
|
|
272
|
-
|
|
266
|
+
assert.deepStrictEqual(sliceS01!.proof_level, '', 'v8: S01 proof_level left empty (tool-only per D004)');
|
|
273
267
|
|
|
274
|
-
// Task planning columns
|
|
268
|
+
// Task planning columns - S01/T01
|
|
275
269
|
const taskS01T01 = getTask('M001', 'S01', 'T01');
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
270
|
+
assert.ok(taskS01T01 !== null, 'v8: task S01/T01 exists');
|
|
271
|
+
assert.ok(taskS01T01!.files.length >= 2, 'v8: S01/T01 files populated');
|
|
272
|
+
assert.ok(taskS01T01!.files.includes('init.ts'), 'v8: S01/T01 files includes init.ts');
|
|
273
|
+
assert.ok(taskS01T01!.files.includes('config.ts'), 'v8: S01/T01 files includes config.ts');
|
|
274
|
+
assert.deepStrictEqual(taskS01T01!.verify, '`node test-init.ts`', 'v8: S01/T01 verify populated');
|
|
281
275
|
|
|
282
|
-
// Task planning columns
|
|
276
|
+
// Task planning columns - S02/T02
|
|
283
277
|
const taskS02T02 = getTask('M001', 'S02', 'T02');
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
278
|
+
assert.ok(taskS02T02 !== null, 'v8: task S02/T02 exists');
|
|
279
|
+
assert.ok(taskS02T02!.files.length >= 2, 'v8: S02/T02 files populated');
|
|
280
|
+
assert.ok(taskS02T02!.files.includes('test-core.ts'), 'v8: S02/T02 files includes test-core.ts');
|
|
281
|
+
assert.deepStrictEqual(taskS02T02!.verify, '`npm test`', 'v8: S02/T02 verify populated');
|
|
288
282
|
|
|
289
|
-
// Task with no Files/Verify — not applicable since all fixtures now have them,
|
|
290
|
-
// but confirm a task from S02 has correct data
|
|
291
283
|
const taskS02T03 = getTask('M001', 'S02', 'T03');
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
284
|
+
assert.ok(taskS02T03 !== null, 'v8: task S02/T03 exists');
|
|
285
|
+
assert.ok(taskS02T03!.files.includes('polish.ts'), 'v8: S02/T03 files includes polish.ts');
|
|
286
|
+
assert.deepStrictEqual(taskS02T03!.verify, '`node test-polish.ts`', 'v8: S02/T03 verify populated');
|
|
295
287
|
|
|
296
288
|
// Diagnostic: v8 planning columns queryable via SQL
|
|
297
289
|
const db = _getAdapter()!;
|
|
298
290
|
const milestoneRow = db.prepare("SELECT vision, success_criteria, boundary_map_markdown FROM milestones WHERE id = 'M001'").get() as any;
|
|
299
|
-
|
|
300
|
-
|
|
291
|
+
assert.ok(milestoneRow.vision.length > 0, 'v8-diag: vision column queryable');
|
|
292
|
+
assert.ok(milestoneRow.boundary_map_markdown.length > 0, 'v8-diag: boundary_map_markdown column queryable');
|
|
301
293
|
|
|
302
294
|
const sliceRow = db.prepare("SELECT goal FROM slices WHERE milestone_id = 'M001' AND id = 'S01'").get() as any;
|
|
303
|
-
|
|
295
|
+
assert.ok(sliceRow.goal.length > 0, 'v8-diag: goal column queryable');
|
|
304
296
|
|
|
305
297
|
const taskRow = db.prepare("SELECT files, verify FROM tasks WHERE milestone_id = 'M001' AND slice_id = 'S01' AND id = 'T01'").get() as any;
|
|
306
|
-
|
|
307
|
-
|
|
298
|
+
assert.ok(taskRow.files.length > 2, 'v8-diag: files column queryable (JSON array)');
|
|
299
|
+
assert.ok(taskRow.verify.length > 0, 'v8-diag: verify column queryable');
|
|
308
300
|
|
|
309
301
|
closeDatabase();
|
|
310
302
|
} finally {
|
|
311
303
|
closeDatabase();
|
|
312
304
|
cleanup(base);
|
|
313
305
|
}
|
|
314
|
-
}
|
|
315
|
-
|
|
306
|
+
});
|
|
316
307
|
|
|
317
|
-
|
|
318
|
-
console.log('\n=== recover: idempotent — double recovery produces same state ===');
|
|
319
|
-
{
|
|
308
|
+
test('idempotent - double recovery produces same state', async () => {
|
|
320
309
|
const base = createFixtureBase();
|
|
321
310
|
try {
|
|
322
311
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_M001);
|
|
@@ -337,18 +326,18 @@ async function main() {
|
|
|
337
326
|
invalidateStateCache();
|
|
338
327
|
const state2 = await deriveStateFromDb(base);
|
|
339
328
|
|
|
340
|
-
|
|
341
|
-
|
|
329
|
+
assert.deepStrictEqual(state2.phase, state1.phase, 'idempotent: phase matches');
|
|
330
|
+
assert.deepStrictEqual(
|
|
342
331
|
state2.activeMilestone?.id,
|
|
343
332
|
state1.activeMilestone?.id,
|
|
344
333
|
'idempotent: active milestone matches',
|
|
345
334
|
);
|
|
346
|
-
|
|
335
|
+
assert.deepStrictEqual(
|
|
347
336
|
state2.activeSlice?.id,
|
|
348
337
|
state1.activeSlice?.id,
|
|
349
338
|
'idempotent: active slice matches',
|
|
350
339
|
);
|
|
351
|
-
|
|
340
|
+
assert.deepStrictEqual(
|
|
352
341
|
state2.activeTask?.id,
|
|
353
342
|
state1.activeTask?.id,
|
|
354
343
|
'idempotent: active task matches',
|
|
@@ -359,11 +348,9 @@ async function main() {
|
|
|
359
348
|
closeDatabase();
|
|
360
349
|
cleanup(base);
|
|
361
350
|
}
|
|
362
|
-
}
|
|
351
|
+
});
|
|
363
352
|
|
|
364
|
-
|
|
365
|
-
console.log('\n=== recover: preserves decisions/requirements ===');
|
|
366
|
-
{
|
|
353
|
+
test('preserves decisions/requirements', async () => {
|
|
367
354
|
const base = createFixtureBase();
|
|
368
355
|
try {
|
|
369
356
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_M001);
|
|
@@ -402,35 +389,33 @@ async function main() {
|
|
|
402
389
|
|
|
403
390
|
// Verify decisions and requirements survived
|
|
404
391
|
const decisions = db.prepare('SELECT * FROM decisions').all();
|
|
405
|
-
|
|
406
|
-
|
|
392
|
+
assert.deepStrictEqual(decisions.length, 1, 'preserve: decision survives clear');
|
|
393
|
+
assert.deepStrictEqual((decisions[0] as any).id, 'D001', 'preserve: decision ID intact');
|
|
407
394
|
|
|
408
395
|
const requirements = db.prepare('SELECT * FROM requirements').all();
|
|
409
|
-
|
|
410
|
-
|
|
396
|
+
assert.deepStrictEqual(requirements.length, 1, 'preserve: requirement survives clear');
|
|
397
|
+
assert.deepStrictEqual((requirements[0] as any).id, 'R001', 'preserve: requirement ID intact');
|
|
411
398
|
|
|
412
399
|
// Recover hierarchy
|
|
413
400
|
migrateHierarchyToDb(base);
|
|
414
401
|
const milestones = getAllMilestones();
|
|
415
|
-
|
|
402
|
+
assert.ok(milestones.length > 0, 'preserve: milestones recovered after clear');
|
|
416
403
|
|
|
417
404
|
// Verify non-hierarchy data still intact after recovery
|
|
418
405
|
const decisionsAfter = db.prepare('SELECT * FROM decisions').all();
|
|
419
|
-
|
|
406
|
+
assert.deepStrictEqual(decisionsAfter.length, 1, 'preserve: decision still present after recovery');
|
|
420
407
|
|
|
421
408
|
closeDatabase();
|
|
422
409
|
} finally {
|
|
423
410
|
closeDatabase();
|
|
424
411
|
cleanup(base);
|
|
425
412
|
}
|
|
426
|
-
}
|
|
413
|
+
});
|
|
427
414
|
|
|
428
|
-
|
|
429
|
-
console.log('\n=== recover: empty milestones dir ===');
|
|
430
|
-
{
|
|
415
|
+
test('empty milestones dir', async () => {
|
|
431
416
|
const base = createFixtureBase();
|
|
432
417
|
try {
|
|
433
|
-
// No milestones written
|
|
418
|
+
// No milestones written - just the empty dir
|
|
434
419
|
openDatabase(':memory:');
|
|
435
420
|
|
|
436
421
|
// Pre-populate to simulate existing state
|
|
@@ -439,24 +424,17 @@ async function main() {
|
|
|
439
424
|
// Clear and recover from empty
|
|
440
425
|
clearHierarchyTables();
|
|
441
426
|
const counts = migrateHierarchyToDb(base);
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
427
|
+
assert.deepStrictEqual(counts.milestones, 0, 'empty: zero milestones recovered');
|
|
428
|
+
assert.deepStrictEqual(counts.slices, 0, 'empty: zero slices recovered');
|
|
429
|
+
assert.deepStrictEqual(counts.tasks, 0, 'empty: zero tasks recovered');
|
|
445
430
|
|
|
446
431
|
const all = getAllMilestones();
|
|
447
|
-
|
|
432
|
+
assert.deepStrictEqual(all.length, 0, 'empty: no milestones in DB after recovery');
|
|
448
433
|
|
|
449
434
|
closeDatabase();
|
|
450
435
|
} finally {
|
|
451
436
|
closeDatabase();
|
|
452
437
|
cleanup(base);
|
|
453
438
|
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
report();
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
main().catch((error) => {
|
|
460
|
-
console.error(error);
|
|
461
|
-
process.exit(1);
|
|
439
|
+
});
|
|
462
440
|
});
|