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
|
@@ -63,35 +63,33 @@ steps:
|
|
|
63
63
|
|
|
64
64
|
// ─── loadDefinition: valid YAML ──────────────────────────────────────────
|
|
65
65
|
|
|
66
|
-
test("loadDefinition: valid 3-step YAML returns correct structure", () => {
|
|
66
|
+
test("loadDefinition: valid 3-step YAML returns correct structure", (t) => {
|
|
67
67
|
const dir = writeDefYaml(VALID_3STEP_YAML);
|
|
68
|
-
try {
|
|
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
|
-
try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ }
|
|
94
|
-
}
|
|
68
|
+
t.after(() => { try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ } });
|
|
69
|
+
|
|
70
|
+
const def = loadDefinition(dir, "test-workflow");
|
|
71
|
+
|
|
72
|
+
assert.equal(def.version, 1);
|
|
73
|
+
assert.equal(def.name, "test-workflow");
|
|
74
|
+
assert.equal(def.description, "A test workflow");
|
|
75
|
+
assert.deepEqual(def.params, { topic: "AI" });
|
|
76
|
+
assert.equal(def.steps.length, 3);
|
|
77
|
+
|
|
78
|
+
// Step 1: research
|
|
79
|
+
assert.equal(def.steps[0].id, "research");
|
|
80
|
+
assert.equal(def.steps[0].name, "Research the topic");
|
|
81
|
+
assert.equal(def.steps[0].prompt, "Research {{topic}} and write findings to research.md");
|
|
82
|
+
assert.deepEqual(def.steps[0].requires, []);
|
|
83
|
+
assert.deepEqual(def.steps[0].produces, ["research.md"]);
|
|
84
|
+
|
|
85
|
+
// Step 2: outline — depends on research
|
|
86
|
+
assert.equal(def.steps[1].id, "outline");
|
|
87
|
+
assert.deepEqual(def.steps[1].requires, ["research"]);
|
|
88
|
+
|
|
89
|
+
// Step 3: draft — depends on outline
|
|
90
|
+
assert.equal(def.steps[2].id, "draft");
|
|
91
|
+
assert.deepEqual(def.steps[2].requires, ["outline"]);
|
|
92
|
+
assert.deepEqual(def.steps[2].produces, ["draft.md"]);
|
|
95
93
|
});
|
|
96
94
|
|
|
97
95
|
// ─── validateDefinition: rejection cases ─────────────────────────────────
|
|
@@ -223,23 +221,21 @@ test("validateDefinition: missing step name → error", () => {
|
|
|
223
221
|
|
|
224
222
|
// ─── loadDefinition: error cases ─────────────────────────────────────────
|
|
225
223
|
|
|
226
|
-
test("loadDefinition: missing file → descriptive error", () => {
|
|
224
|
+
test("loadDefinition: missing file → descriptive error", (t) => {
|
|
227
225
|
const dir = makeTmpDir();
|
|
228
|
-
try {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
test("loadDefinition: invalid YAML schema → descriptive error", () => {
|
|
226
|
+
t.after(() => { try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ } });
|
|
227
|
+
|
|
228
|
+
assert.throws(
|
|
229
|
+
() => loadDefinition(dir, "nonexistent"),
|
|
230
|
+
(err: Error) => {
|
|
231
|
+
assert.ok(err.message.includes("not found"));
|
|
232
|
+
assert.ok(err.message.includes("nonexistent.yaml"));
|
|
233
|
+
return true;
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("loadDefinition: invalid YAML schema → descriptive error", (t) => {
|
|
243
239
|
const dir = writeDefYaml(`
|
|
244
240
|
version: 2
|
|
245
241
|
name: "bad"
|
|
@@ -248,23 +244,21 @@ steps:
|
|
|
248
244
|
name: "A"
|
|
249
245
|
prompt: "do A"
|
|
250
246
|
`);
|
|
251
|
-
try {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ }
|
|
262
|
-
}
|
|
247
|
+
t.after(() => { try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ } });
|
|
248
|
+
|
|
249
|
+
assert.throws(
|
|
250
|
+
() => loadDefinition(dir, "test-workflow"),
|
|
251
|
+
(err: Error) => {
|
|
252
|
+
assert.ok(err.message.includes("Invalid workflow definition"));
|
|
253
|
+
assert.ok(err.message.includes("Unsupported version"));
|
|
254
|
+
return true;
|
|
255
|
+
},
|
|
256
|
+
);
|
|
263
257
|
});
|
|
264
258
|
|
|
265
259
|
// ─── loadDefinition: snake_case → camelCase conversion ───────────────────
|
|
266
260
|
|
|
267
|
-
test("loadDefinition: depends_on in YAML maps to requires in TypeScript", () => {
|
|
261
|
+
test("loadDefinition: depends_on in YAML maps to requires in TypeScript", (t) => {
|
|
268
262
|
const dir = writeDefYaml(`
|
|
269
263
|
version: 1
|
|
270
264
|
name: "dep-test"
|
|
@@ -277,15 +271,13 @@ steps:
|
|
|
277
271
|
prompt: "do second"
|
|
278
272
|
depends_on: [first]
|
|
279
273
|
`);
|
|
280
|
-
try {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ }
|
|
285
|
-
}
|
|
274
|
+
t.after(() => { try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ } });
|
|
275
|
+
|
|
276
|
+
const def = loadDefinition(dir, "test-workflow");
|
|
277
|
+
assert.deepEqual(def.steps[1].requires, ["first"]);
|
|
286
278
|
});
|
|
287
279
|
|
|
288
|
-
test("loadDefinition: context_from in YAML maps to contextFrom in TypeScript", () => {
|
|
280
|
+
test("loadDefinition: context_from in YAML maps to contextFrom in TypeScript", (t) => {
|
|
289
281
|
const dir = writeDefYaml(`
|
|
290
282
|
version: 1
|
|
291
283
|
name: "ctx-test"
|
|
@@ -298,12 +290,10 @@ steps:
|
|
|
298
290
|
prompt: "do second"
|
|
299
291
|
context_from: [first]
|
|
300
292
|
`);
|
|
301
|
-
try {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ }
|
|
306
|
-
}
|
|
293
|
+
t.after(() => { try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ } });
|
|
294
|
+
|
|
295
|
+
const def = loadDefinition(dir, "test-workflow");
|
|
296
|
+
assert.deepEqual(def.steps[1].contextFrom, ["first"]);
|
|
307
297
|
});
|
|
308
298
|
|
|
309
299
|
// ─── validateDefinition: iterate field validation ────────────────────────
|
|
@@ -725,7 +715,7 @@ test("validateDefinition: valid minimal step (no requires/produces) → accepted
|
|
|
725
715
|
assert.equal(result.errors.length, 0);
|
|
726
716
|
});
|
|
727
717
|
|
|
728
|
-
test("loadDefinition: loads without params field → params is undefined", () => {
|
|
718
|
+
test("loadDefinition: loads without params field → params is undefined", (t) => {
|
|
729
719
|
const dir = writeDefYaml(`
|
|
730
720
|
version: 1
|
|
731
721
|
name: "no-params"
|
|
@@ -734,15 +724,13 @@ steps:
|
|
|
734
724
|
name: "A"
|
|
735
725
|
prompt: "do A"
|
|
736
726
|
`);
|
|
737
|
-
try {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ }
|
|
742
|
-
}
|
|
727
|
+
t.after(() => { try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ } });
|
|
728
|
+
|
|
729
|
+
const def = loadDefinition(dir, "test-workflow");
|
|
730
|
+
assert.equal(def.params, undefined);
|
|
743
731
|
});
|
|
744
732
|
|
|
745
|
-
test("loadDefinition: loads without description → description is undefined", () => {
|
|
733
|
+
test("loadDefinition: loads without description → description is undefined", (t) => {
|
|
746
734
|
const dir = writeDefYaml(`
|
|
747
735
|
version: 1
|
|
748
736
|
name: "no-desc"
|
|
@@ -751,15 +739,13 @@ steps:
|
|
|
751
739
|
name: "A"
|
|
752
740
|
prompt: "do A"
|
|
753
741
|
`);
|
|
754
|
-
try {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ }
|
|
759
|
-
}
|
|
742
|
+
t.after(() => { try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ } });
|
|
743
|
+
|
|
744
|
+
const def = loadDefinition(dir, "test-workflow");
|
|
745
|
+
assert.equal(def.description, undefined);
|
|
760
746
|
});
|
|
761
747
|
|
|
762
|
-
test("loadDefinition: step with no requires/produces defaults to empty arrays", () => {
|
|
748
|
+
test("loadDefinition: step with no requires/produces defaults to empty arrays", (t) => {
|
|
763
749
|
const dir = writeDefYaml(`
|
|
764
750
|
version: 1
|
|
765
751
|
name: "defaults"
|
|
@@ -768,11 +754,9 @@ steps:
|
|
|
768
754
|
name: "A"
|
|
769
755
|
prompt: "do A"
|
|
770
756
|
`);
|
|
771
|
-
try {
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ }
|
|
777
|
-
}
|
|
757
|
+
t.after(() => { try { rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ } });
|
|
758
|
+
|
|
759
|
+
const def = loadDefinition(dir, "test-workflow");
|
|
760
|
+
assert.deepEqual(def.steps[0].requires, []);
|
|
761
|
+
assert.deepEqual(def.steps[0].produces, []);
|
|
778
762
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
1
3
|
// derive-state-crossval.test.ts — Cross-validation: deriveStateFromDb() vs _deriveStateImpl()
|
|
2
4
|
// Proves both paths produce field-identical GSDState across 7 fixture scenarios,
|
|
3
5
|
// plus an auto-migration round-trip test.
|
|
@@ -19,11 +21,8 @@ import {
|
|
|
19
21
|
insertTask,
|
|
20
22
|
} from '../gsd-db.ts';
|
|
21
23
|
import { migrateHierarchyToDb } from '../md-importer.ts';
|
|
22
|
-
import { createTestContext } from './test-helpers.ts';
|
|
23
24
|
import type { GSDState } from '../types.ts';
|
|
24
25
|
|
|
25
|
-
const { assertEq, assertTrue, report } = createTestContext();
|
|
26
|
-
|
|
27
26
|
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
28
27
|
|
|
29
28
|
function createFixtureBase(): string {
|
|
@@ -48,29 +47,29 @@ function cleanup(base: string): void {
|
|
|
48
47
|
*/
|
|
49
48
|
function assertStatesEqual(dbState: GSDState, fileState: GSDState, prefix: string): void {
|
|
50
49
|
// Phase
|
|
51
|
-
|
|
50
|
+
assert.deepStrictEqual(dbState.phase, fileState.phase, `${prefix}: phase`);
|
|
52
51
|
|
|
53
52
|
// Active refs
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
assert.deepStrictEqual(dbState.activeMilestone?.id ?? null, fileState.activeMilestone?.id ?? null, `${prefix}: activeMilestone.id`);
|
|
54
|
+
assert.deepStrictEqual(dbState.activeMilestone?.title ?? null, fileState.activeMilestone?.title ?? null, `${prefix}: activeMilestone.title`);
|
|
55
|
+
assert.deepStrictEqual(dbState.activeSlice?.id ?? null, fileState.activeSlice?.id ?? null, `${prefix}: activeSlice.id`);
|
|
56
|
+
assert.deepStrictEqual(dbState.activeSlice?.title ?? null, fileState.activeSlice?.title ?? null, `${prefix}: activeSlice.title`);
|
|
57
|
+
assert.deepStrictEqual(dbState.activeTask?.id ?? null, fileState.activeTask?.id ?? null, `${prefix}: activeTask.id`);
|
|
58
|
+
assert.deepStrictEqual(dbState.activeTask?.title ?? null, fileState.activeTask?.title ?? null, `${prefix}: activeTask.title`);
|
|
60
59
|
|
|
61
60
|
// Blockers
|
|
62
|
-
|
|
61
|
+
assert.deepStrictEqual(dbState.blockers.length, fileState.blockers.length, `${prefix}: blockers.length`);
|
|
63
62
|
|
|
64
63
|
// Next action (may differ in wording between paths — compare presence)
|
|
65
|
-
|
|
64
|
+
assert.ok(typeof dbState.nextAction === 'string', `${prefix}: nextAction is string`);
|
|
66
65
|
|
|
67
66
|
// Registry — length and each entry
|
|
68
|
-
|
|
67
|
+
assert.deepStrictEqual(dbState.registry.length, fileState.registry.length, `${prefix}: registry.length`);
|
|
69
68
|
for (let i = 0; i < fileState.registry.length; i++) {
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
assert.deepStrictEqual(dbState.registry[i]?.id, fileState.registry[i]?.id, `${prefix}: registry[${i}].id`);
|
|
70
|
+
assert.deepStrictEqual(dbState.registry[i]?.status, fileState.registry[i]?.status, `${prefix}: registry[${i}].status`);
|
|
72
71
|
// dependsOn may or may not be present
|
|
73
|
-
|
|
72
|
+
assert.deepStrictEqual(
|
|
74
73
|
JSON.stringify(dbState.registry[i]?.dependsOn ?? []),
|
|
75
74
|
JSON.stringify(fileState.registry[i]?.dependsOn ?? []),
|
|
76
75
|
`${prefix}: registry[${i}].dependsOn`,
|
|
@@ -78,28 +77,27 @@ function assertStatesEqual(dbState: GSDState, fileState: GSDState, prefix: strin
|
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
// Requirements
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
assert.deepStrictEqual(dbState.requirements?.active ?? 0, fileState.requirements?.active ?? 0, `${prefix}: requirements.active`);
|
|
81
|
+
assert.deepStrictEqual(dbState.requirements?.validated ?? 0, fileState.requirements?.validated ?? 0, `${prefix}: requirements.validated`);
|
|
82
|
+
assert.deepStrictEqual(dbState.requirements?.total ?? 0, fileState.requirements?.total ?? 0, `${prefix}: requirements.total`);
|
|
84
83
|
|
|
85
84
|
// Progress
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
assert.deepStrictEqual(dbState.progress?.milestones?.done, fileState.progress?.milestones?.done, `${prefix}: progress.milestones.done`);
|
|
86
|
+
assert.deepStrictEqual(dbState.progress?.milestones?.total, fileState.progress?.milestones?.total, `${prefix}: progress.milestones.total`);
|
|
87
|
+
assert.deepStrictEqual(dbState.progress?.slices?.done ?? 0, fileState.progress?.slices?.done ?? 0, `${prefix}: progress.slices.done`);
|
|
88
|
+
assert.deepStrictEqual(dbState.progress?.slices?.total ?? 0, fileState.progress?.slices?.total ?? 0, `${prefix}: progress.slices.total`);
|
|
89
|
+
assert.deepStrictEqual(dbState.progress?.tasks?.done ?? 0, fileState.progress?.tasks?.done ?? 0, `${prefix}: progress.tasks.done`);
|
|
90
|
+
assert.deepStrictEqual(dbState.progress?.tasks?.total ?? 0, fileState.progress?.tasks?.total ?? 0, `${prefix}: progress.tasks.total`);
|
|
92
91
|
}
|
|
93
92
|
|
|
94
93
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
95
94
|
// Scenario fixtures
|
|
96
95
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
97
96
|
|
|
98
|
-
async
|
|
97
|
+
describe('derive-state-crossval', async () => {
|
|
99
98
|
|
|
100
99
|
// ─── Scenario A: Pre-planning — milestone with CONTEXT but no roadmap ──
|
|
101
|
-
|
|
102
|
-
{
|
|
100
|
+
test('crossval A: pre-planning', async () => {
|
|
103
101
|
const base = createFixtureBase();
|
|
104
102
|
try {
|
|
105
103
|
writeFile(base, 'milestones/M001/M001-CONTEXT.md', '# M001: New Project\n\nWe are exploring scope.');
|
|
@@ -116,18 +114,17 @@ async function main(): Promise<void> {
|
|
|
116
114
|
const dbState = await deriveStateFromDb(base);
|
|
117
115
|
|
|
118
116
|
assertStatesEqual(dbState, fileState, 'A-preplan');
|
|
119
|
-
|
|
117
|
+
assert.deepStrictEqual(dbState.phase, 'pre-planning', 'A-preplan: phase is pre-planning');
|
|
120
118
|
|
|
121
119
|
closeDatabase();
|
|
122
120
|
} finally {
|
|
123
121
|
closeDatabase();
|
|
124
122
|
cleanup(base);
|
|
125
123
|
}
|
|
126
|
-
}
|
|
124
|
+
});
|
|
127
125
|
|
|
128
126
|
// ─── Scenario B: Executing — 2 slices, first complete, second active ──
|
|
129
|
-
|
|
130
|
-
{
|
|
127
|
+
test('crossval B: executing', async () => {
|
|
131
128
|
const base = createFixtureBase();
|
|
132
129
|
try {
|
|
133
130
|
const roadmap = `# M001: Test Project
|
|
@@ -182,20 +179,19 @@ skills_used: []
|
|
|
182
179
|
const dbState = await deriveStateFromDb(base);
|
|
183
180
|
|
|
184
181
|
assertStatesEqual(dbState, fileState, 'B-executing');
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
182
|
+
assert.deepStrictEqual(dbState.phase, 'executing', 'B-executing: phase is executing');
|
|
183
|
+
assert.deepStrictEqual(dbState.activeSlice?.id, 'S02', 'B-executing: activeSlice is S02');
|
|
184
|
+
assert.deepStrictEqual(dbState.activeTask?.id, 'T02', 'B-executing: activeTask is T02');
|
|
188
185
|
|
|
189
186
|
closeDatabase();
|
|
190
187
|
} finally {
|
|
191
188
|
closeDatabase();
|
|
192
189
|
cleanup(base);
|
|
193
190
|
}
|
|
194
|
-
}
|
|
191
|
+
});
|
|
195
192
|
|
|
196
193
|
// ─── Scenario C: Summarizing — all tasks done, no slice summary ────────
|
|
197
|
-
|
|
198
|
-
{
|
|
194
|
+
test('crossval C: summarizing', async () => {
|
|
199
195
|
const base = createFixtureBase();
|
|
200
196
|
try {
|
|
201
197
|
const roadmap = `# M001: Summarize Test
|
|
@@ -245,20 +241,19 @@ skills_used: []
|
|
|
245
241
|
const dbState = await deriveStateFromDb(base);
|
|
246
242
|
|
|
247
243
|
assertStatesEqual(dbState, fileState, 'C-summarizing');
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
244
|
+
assert.deepStrictEqual(dbState.phase, 'summarizing', 'C-summarizing: phase is summarizing');
|
|
245
|
+
assert.deepStrictEqual(dbState.activeSlice?.id, 'S01', 'C-summarizing: activeSlice is S01');
|
|
246
|
+
assert.deepStrictEqual(dbState.activeTask, null, 'C-summarizing: no activeTask');
|
|
251
247
|
|
|
252
248
|
closeDatabase();
|
|
253
249
|
} finally {
|
|
254
250
|
closeDatabase();
|
|
255
251
|
cleanup(base);
|
|
256
252
|
}
|
|
257
|
-
}
|
|
253
|
+
});
|
|
258
254
|
|
|
259
255
|
// ─── Scenario D: Multi-milestone — M001 complete, M002 active ─────────
|
|
260
|
-
|
|
261
|
-
{
|
|
256
|
+
test('crossval D: multi-milestone', async () => {
|
|
262
257
|
const base = createFixtureBase();
|
|
263
258
|
try {
|
|
264
259
|
const m1Roadmap = `# M001: First Milestone
|
|
@@ -313,24 +308,23 @@ skills_used: []
|
|
|
313
308
|
const dbState = await deriveStateFromDb(base);
|
|
314
309
|
|
|
315
310
|
assertStatesEqual(dbState, fileState, 'D-multims');
|
|
316
|
-
|
|
317
|
-
|
|
311
|
+
assert.deepStrictEqual(dbState.activeMilestone?.id, 'M002', 'D-multims: activeMilestone is M002');
|
|
312
|
+
assert.deepStrictEqual(dbState.registry.length, 2, 'D-multims: 2 milestones in registry');
|
|
318
313
|
|
|
319
314
|
const m1 = dbState.registry.find(e => e.id === 'M001');
|
|
320
315
|
const m2 = dbState.registry.find(e => e.id === 'M002');
|
|
321
|
-
|
|
322
|
-
|
|
316
|
+
assert.deepStrictEqual(m1?.status, 'complete', 'D-multims: M001 complete');
|
|
317
|
+
assert.deepStrictEqual(m2?.status, 'active', 'D-multims: M002 active');
|
|
323
318
|
|
|
324
319
|
closeDatabase();
|
|
325
320
|
} finally {
|
|
326
321
|
closeDatabase();
|
|
327
322
|
cleanup(base);
|
|
328
323
|
}
|
|
329
|
-
}
|
|
324
|
+
});
|
|
330
325
|
|
|
331
326
|
// ─── Scenario E: Blocked — circular slice deps ────────────────────────
|
|
332
|
-
|
|
333
|
-
{
|
|
327
|
+
test('crossval E: blocked', async () => {
|
|
334
328
|
const base = createFixtureBase();
|
|
335
329
|
try {
|
|
336
330
|
const roadmap = `# M001: Blocked Test
|
|
@@ -357,19 +351,18 @@ skills_used: []
|
|
|
357
351
|
const dbState = await deriveStateFromDb(base);
|
|
358
352
|
|
|
359
353
|
assertStatesEqual(dbState, fileState, 'E-blocked');
|
|
360
|
-
|
|
361
|
-
|
|
354
|
+
assert.deepStrictEqual(dbState.phase, 'blocked', 'E-blocked: phase is blocked');
|
|
355
|
+
assert.ok(dbState.blockers.length > 0, 'E-blocked: has blockers');
|
|
362
356
|
|
|
363
357
|
closeDatabase();
|
|
364
358
|
} finally {
|
|
365
359
|
closeDatabase();
|
|
366
360
|
cleanup(base);
|
|
367
361
|
}
|
|
368
|
-
}
|
|
362
|
+
});
|
|
369
363
|
|
|
370
364
|
// ─── Scenario F: Parked — PARKED file on milestone ────────────────────
|
|
371
|
-
|
|
372
|
-
{
|
|
365
|
+
test('crossval F: parked', async () => {
|
|
373
366
|
const base = createFixtureBase();
|
|
374
367
|
try {
|
|
375
368
|
const roadmap = `# M001: Parked Milestone
|
|
@@ -396,20 +389,19 @@ skills_used: []
|
|
|
396
389
|
const dbState = await deriveStateFromDb(base);
|
|
397
390
|
|
|
398
391
|
assertStatesEqual(dbState, fileState, 'F-parked');
|
|
399
|
-
|
|
400
|
-
|
|
392
|
+
assert.deepStrictEqual(dbState.activeMilestone?.id, 'M002', 'F-parked: activeMilestone is M002');
|
|
393
|
+
assert.ok(dbState.registry.some(e => e.id === 'M001' && e.status === 'parked'), 'F-parked: M001 parked');
|
|
401
394
|
|
|
402
395
|
closeDatabase();
|
|
403
396
|
} finally {
|
|
404
397
|
closeDatabase();
|
|
405
398
|
cleanup(base);
|
|
406
399
|
}
|
|
407
|
-
}
|
|
400
|
+
});
|
|
408
401
|
|
|
409
402
|
// ─── Scenario G: Auto-migration round-trip ────────────────────────────
|
|
410
403
|
// Create a markdown-only fixture (no DB). Migrate to DB. Both paths identical.
|
|
411
|
-
|
|
412
|
-
{
|
|
404
|
+
test('crossval G: auto-migration round-trip', async () => {
|
|
413
405
|
const base = createFixtureBase();
|
|
414
406
|
try {
|
|
415
407
|
const roadmap = `# M001: Migration Test
|
|
@@ -489,9 +481,9 @@ skills_used: []
|
|
|
489
481
|
const counts = migrateHierarchyToDb(base);
|
|
490
482
|
|
|
491
483
|
// Verify migration populated correctly
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
484
|
+
assert.ok(counts.milestones >= 1, 'G-roundtrip: migrated milestones');
|
|
485
|
+
assert.ok(counts.slices >= 2, 'G-roundtrip: migrated slices');
|
|
486
|
+
assert.ok(counts.tasks >= 3, 'G-roundtrip: migrated tasks');
|
|
495
487
|
|
|
496
488
|
// Step 3: Get DB-backed state
|
|
497
489
|
invalidateStateCache();
|
|
@@ -499,29 +491,22 @@ skills_used: []
|
|
|
499
491
|
|
|
500
492
|
// Step 4: Deep cross-validation
|
|
501
493
|
assertStatesEqual(dbState, fileState, 'G-roundtrip');
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
494
|
+
assert.deepStrictEqual(dbState.phase, 'executing', 'G-roundtrip: phase is executing');
|
|
495
|
+
assert.deepStrictEqual(dbState.activeSlice?.id, 'S02', 'G-roundtrip: activeSlice is S02');
|
|
496
|
+
assert.deepStrictEqual(dbState.activeTask?.id, 'T02', 'G-roundtrip: activeTask is T02');
|
|
497
|
+
assert.deepStrictEqual(dbState.requirements?.active, 1, 'G-roundtrip: requirements.active = 1');
|
|
498
|
+
assert.deepStrictEqual(dbState.requirements?.validated, 1, 'G-roundtrip: requirements.validated = 1');
|
|
499
|
+
assert.deepStrictEqual(dbState.requirements?.deferred, 1, 'G-roundtrip: requirements.deferred = 1');
|
|
500
|
+
assert.deepStrictEqual(dbState.requirements?.total, 3, 'G-roundtrip: requirements.total = 3');
|
|
501
|
+
assert.deepStrictEqual(dbState.progress?.slices?.done, 1, 'G-roundtrip: slices.done = 1');
|
|
502
|
+
assert.deepStrictEqual(dbState.progress?.slices?.total, 3, 'G-roundtrip: slices.total = 3');
|
|
503
|
+
assert.deepStrictEqual(dbState.progress?.tasks?.done, 1, 'G-roundtrip: tasks.done = 1');
|
|
504
|
+
assert.deepStrictEqual(dbState.progress?.tasks?.total, 3, 'G-roundtrip: tasks.total = 3');
|
|
513
505
|
|
|
514
506
|
closeDatabase();
|
|
515
507
|
} finally {
|
|
516
508
|
closeDatabase();
|
|
517
509
|
cleanup(base);
|
|
518
510
|
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
report();
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
main().catch((error) => {
|
|
525
|
-
console.error(error);
|
|
526
|
-
process.exit(1);
|
|
511
|
+
});
|
|
527
512
|
});
|