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
|
@@ -73,7 +73,7 @@ describe("DevWorkflowEngine", () => {
|
|
|
73
73
|
assert.equal(engine.engineId, "dev");
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
test("deriveState returns EngineState with expected fields", async () => {
|
|
76
|
+
test("deriveState returns EngineState with expected fields", async (t) => {
|
|
77
77
|
const { DevWorkflowEngine } = await import("../dev-workflow-engine.ts");
|
|
78
78
|
const engine = new DevWorkflowEngine();
|
|
79
79
|
|
|
@@ -81,31 +81,29 @@ describe("DevWorkflowEngine", () => {
|
|
|
81
81
|
const tempDir = mkdtempSync(join(tmpdir(), "gsd-engine-test-"));
|
|
82
82
|
mkdirSync(join(tempDir, ".gsd", "milestones"), { recursive: true });
|
|
83
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
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
108
|
-
}
|
|
84
|
+
t.after(() => rmSync(tempDir, { recursive: true, force: true }));
|
|
85
|
+
|
|
86
|
+
const state = await engine.deriveState(tempDir);
|
|
87
|
+
|
|
88
|
+
assert.equal(typeof state.phase, "string", "phase should be a string");
|
|
89
|
+
assert.ok(
|
|
90
|
+
"currentMilestoneId" in state,
|
|
91
|
+
"state should have currentMilestoneId",
|
|
92
|
+
);
|
|
93
|
+
assert.ok(
|
|
94
|
+
"activeSliceId" in state,
|
|
95
|
+
"state should have activeSliceId",
|
|
96
|
+
);
|
|
97
|
+
assert.ok(
|
|
98
|
+
"activeTaskId" in state,
|
|
99
|
+
"state should have activeTaskId",
|
|
100
|
+
);
|
|
101
|
+
assert.equal(
|
|
102
|
+
typeof state.isComplete,
|
|
103
|
+
"boolean",
|
|
104
|
+
"isComplete should be boolean",
|
|
105
|
+
);
|
|
106
|
+
assert.ok("raw" in state, "state should have raw field");
|
|
109
107
|
});
|
|
110
108
|
|
|
111
109
|
test("reconcile returns continue for non-complete state", async () => {
|
|
@@ -280,16 +278,14 @@ describe("Kill switch (GSD_ENGINE_BYPASS)", () => {
|
|
|
280
278
|
}
|
|
281
279
|
});
|
|
282
280
|
|
|
283
|
-
test("GSD_ENGINE_BYPASS=1 does not affect resolveEngine (bypass checked in autoLoop)", async () => {
|
|
281
|
+
test("GSD_ENGINE_BYPASS=1 does not affect resolveEngine (bypass checked in autoLoop)", async (t) => {
|
|
284
282
|
const { resolveEngine } = await import("../engine-resolver.ts");
|
|
285
283
|
process.env.GSD_ENGINE_BYPASS = "1";
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
delete process.env.GSD_ENGINE_BYPASS;
|
|
292
|
-
}
|
|
284
|
+
t.after(() => delete process.env.GSD_ENGINE_BYPASS);
|
|
285
|
+
|
|
286
|
+
// resolveEngine should still resolve normally — bypass is checked in autoLoop
|
|
287
|
+
const { engine } = resolveEngine({ activeEngineId: null });
|
|
288
|
+
assert.ok(engine, "should return an engine even with bypass set");
|
|
293
289
|
});
|
|
294
290
|
});
|
|
295
291
|
|
|
@@ -20,215 +20,199 @@ function teardownRepo(repo: string): void {
|
|
|
20
20
|
rmSync(repo, { recursive: true, force: true });
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
test("dispatch guard blocks when prior milestone has incomplete slices", () => {
|
|
23
|
+
test("dispatch guard blocks when prior milestone has incomplete slices", (t) => {
|
|
24
24
|
const repo = setupRepo();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
teardownRepo(repo);
|
|
49
|
-
}
|
|
25
|
+
t.after(() => teardownRepo(repo));
|
|
26
|
+
|
|
27
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M002"), { recursive: true });
|
|
28
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M003"), { recursive: true });
|
|
29
|
+
|
|
30
|
+
// Seed DB: M002 with S01 complete, S02 pending
|
|
31
|
+
insertMilestone({ id: "M002", title: "Previous" });
|
|
32
|
+
insertSlice({ id: "S01", milestoneId: "M002", title: "Done", status: "complete", depends: [], sequence: 1 });
|
|
33
|
+
insertSlice({ id: "S02", milestoneId: "M002", title: "Pending", status: "pending", depends: ["S01"], sequence: 2 });
|
|
34
|
+
|
|
35
|
+
// M003 with two pending slices
|
|
36
|
+
insertMilestone({ id: "M003", title: "Current" });
|
|
37
|
+
insertSlice({ id: "S01", milestoneId: "M003", title: "First", status: "pending", depends: [], sequence: 1 });
|
|
38
|
+
insertSlice({ id: "S02", milestoneId: "M003", title: "Second", status: "pending", depends: ["S01"], sequence: 2 });
|
|
39
|
+
|
|
40
|
+
// Need ROADMAP files for milestone discovery (findMilestoneIds reads disk)
|
|
41
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M002", "M002-ROADMAP.md"), "# M002\n");
|
|
42
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M003", "M003-ROADMAP.md"), "# M003\n");
|
|
43
|
+
|
|
44
|
+
assert.equal(
|
|
45
|
+
getPriorSliceCompletionBlocker(repo, "main", "plan-slice", "M003/S01"),
|
|
46
|
+
"Cannot dispatch plan-slice M003/S01: earlier slice M002/S02 is not complete.",
|
|
47
|
+
);
|
|
50
48
|
});
|
|
51
49
|
|
|
52
|
-
test("dispatch guard blocks later slice in same milestone when earlier incomplete", () => {
|
|
50
|
+
test("dispatch guard blocks later slice in same milestone when earlier incomplete", (t) => {
|
|
53
51
|
const repo = setupRepo();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
teardownRepo(repo);
|
|
75
|
-
}
|
|
52
|
+
t.after(() => teardownRepo(repo));
|
|
53
|
+
|
|
54
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M002"), { recursive: true });
|
|
55
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M003"), { recursive: true });
|
|
56
|
+
|
|
57
|
+
insertMilestone({ id: "M002", title: "Previous" });
|
|
58
|
+
insertSlice({ id: "S01", milestoneId: "M002", title: "Done", status: "complete", depends: [], sequence: 1 });
|
|
59
|
+
insertSlice({ id: "S02", milestoneId: "M002", title: "Done", status: "complete", depends: ["S01"], sequence: 2 });
|
|
60
|
+
|
|
61
|
+
insertMilestone({ id: "M003", title: "Current" });
|
|
62
|
+
insertSlice({ id: "S01", milestoneId: "M003", title: "First", status: "pending", depends: [], sequence: 1 });
|
|
63
|
+
insertSlice({ id: "S02", milestoneId: "M003", title: "Second", status: "pending", depends: ["S01"], sequence: 2 });
|
|
64
|
+
|
|
65
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M002", "M002-ROADMAP.md"), "# M002\n");
|
|
66
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M003", "M003-ROADMAP.md"), "# M003\n");
|
|
67
|
+
|
|
68
|
+
assert.equal(
|
|
69
|
+
getPriorSliceCompletionBlocker(repo, "main", "execute-task", "M003/S02/T01"),
|
|
70
|
+
"Cannot dispatch execute-task M003/S02/T01: dependency slice M003/S01 is not complete.",
|
|
71
|
+
);
|
|
76
72
|
});
|
|
77
73
|
|
|
78
|
-
test("dispatch guard allows dispatch when all earlier slices complete", () => {
|
|
74
|
+
test("dispatch guard allows dispatch when all earlier slices complete", (t) => {
|
|
79
75
|
const repo = setupRepo();
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
t.after(() => teardownRepo(repo));
|
|
77
|
+
|
|
78
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M003"), { recursive: true });
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
insertMilestone({ id: "M003", title: "Current" });
|
|
81
|
+
insertSlice({ id: "S01", milestoneId: "M003", title: "First", status: "complete", depends: [], sequence: 1 });
|
|
82
|
+
insertSlice({ id: "S02", milestoneId: "M003", title: "Second", status: "pending", depends: ["S01"], sequence: 2 });
|
|
86
83
|
|
|
87
|
-
|
|
84
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M003", "M003-ROADMAP.md"), "# M003\n");
|
|
88
85
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
} finally {
|
|
92
|
-
teardownRepo(repo);
|
|
93
|
-
}
|
|
86
|
+
assert.equal(getPriorSliceCompletionBlocker(repo, "main", "execute-task", "M003/S02/T01"), null);
|
|
87
|
+
assert.equal(getPriorSliceCompletionBlocker(repo, "main", "plan-milestone", "M003"), null);
|
|
94
88
|
});
|
|
95
89
|
|
|
96
|
-
test("dispatch guard unblocks slice when positionally-earlier slice depends on it (#1638)", () => {
|
|
90
|
+
test("dispatch guard unblocks slice when positionally-earlier slice depends on it (#1638)", (t) => {
|
|
97
91
|
// S05 depends on S06, but S05 appears first positionally.
|
|
98
92
|
// Old behavior: S06 blocked because S05 (positionally earlier) is incomplete.
|
|
99
93
|
// Fixed behavior: S06 has no unmet dependencies, so it can dispatch.
|
|
100
94
|
const repo = setupRepo();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
teardownRepo(repo);
|
|
127
|
-
}
|
|
95
|
+
t.after(() => teardownRepo(repo));
|
|
96
|
+
|
|
97
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M001"), { recursive: true });
|
|
98
|
+
|
|
99
|
+
insertMilestone({ id: "M001", title: "Test" });
|
|
100
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Setup", status: "complete", depends: [], sequence: 1 });
|
|
101
|
+
insertSlice({ id: "S02", milestoneId: "M001", title: "Core", status: "complete", depends: ["S01"], sequence: 2 });
|
|
102
|
+
insertSlice({ id: "S03", milestoneId: "M001", title: "API", status: "complete", depends: ["S02"], sequence: 3 });
|
|
103
|
+
insertSlice({ id: "S04", milestoneId: "M001", title: "Auth", status: "complete", depends: ["S03"], sequence: 4 });
|
|
104
|
+
insertSlice({ id: "S05", milestoneId: "M001", title: "Integration", status: "pending", depends: ["S04", "S06"], sequence: 5 });
|
|
105
|
+
insertSlice({ id: "S06", milestoneId: "M001", title: "Data Layer", status: "pending", depends: ["S04"], sequence: 6 });
|
|
106
|
+
|
|
107
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# M001\n");
|
|
108
|
+
|
|
109
|
+
// S06 depends only on S04 (complete) — should be unblocked
|
|
110
|
+
assert.equal(
|
|
111
|
+
getPriorSliceCompletionBlocker(repo, "main", "plan-slice", "M001/S06"),
|
|
112
|
+
null,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// S05 depends on S04 (complete) and S06 (incomplete) — should be blocked
|
|
116
|
+
assert.equal(
|
|
117
|
+
getPriorSliceCompletionBlocker(repo, "main", "plan-slice", "M001/S05"),
|
|
118
|
+
"Cannot dispatch plan-slice M001/S05: dependency slice M001/S06 is not complete.",
|
|
119
|
+
);
|
|
128
120
|
});
|
|
129
121
|
|
|
130
|
-
test("dispatch guard falls back to positional ordering when no dependencies declared", () => {
|
|
122
|
+
test("dispatch guard falls back to positional ordering when no dependencies declared", (t) => {
|
|
131
123
|
const repo = setupRepo();
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
teardownRepo(repo);
|
|
155
|
-
}
|
|
124
|
+
t.after(() => teardownRepo(repo));
|
|
125
|
+
|
|
126
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M001"), { recursive: true });
|
|
127
|
+
|
|
128
|
+
insertMilestone({ id: "M001", title: "Test" });
|
|
129
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "First", status: "complete", depends: [], sequence: 1 });
|
|
130
|
+
insertSlice({ id: "S02", milestoneId: "M001", title: "Second", status: "pending", depends: [], sequence: 2 });
|
|
131
|
+
insertSlice({ id: "S03", milestoneId: "M001", title: "Third", status: "pending", depends: [], sequence: 3 });
|
|
132
|
+
|
|
133
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# M001\n");
|
|
134
|
+
|
|
135
|
+
// S03 has no dependencies — positional fallback blocks on S02
|
|
136
|
+
assert.equal(
|
|
137
|
+
getPriorSliceCompletionBlocker(repo, "main", "plan-slice", "M001/S03"),
|
|
138
|
+
"Cannot dispatch plan-slice M001/S03: earlier slice M001/S02 is not complete.",
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// S02 has no dependencies — positional fallback: S01 is done, so unblocked
|
|
142
|
+
assert.equal(
|
|
143
|
+
getPriorSliceCompletionBlocker(repo, "main", "plan-slice", "M001/S02"),
|
|
144
|
+
null,
|
|
145
|
+
);
|
|
156
146
|
});
|
|
157
147
|
|
|
158
|
-
test("dispatch guard allows slice with all declared dependencies complete", () => {
|
|
148
|
+
test("dispatch guard allows slice with all declared dependencies complete", (t) => {
|
|
159
149
|
const repo = setupRepo();
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
teardownRepo(repo);
|
|
184
|
-
}
|
|
150
|
+
t.after(() => teardownRepo(repo));
|
|
151
|
+
|
|
152
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M001"), { recursive: true });
|
|
153
|
+
|
|
154
|
+
insertMilestone({ id: "M001", title: "Test" });
|
|
155
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Setup", status: "complete", depends: [], sequence: 1 });
|
|
156
|
+
insertSlice({ id: "S02", milestoneId: "M001", title: "Core", status: "complete", depends: ["S01"], sequence: 2 });
|
|
157
|
+
insertSlice({ id: "S03", milestoneId: "M001", title: "Feature A", status: "pending", depends: ["S01", "S02"], sequence: 3 });
|
|
158
|
+
insertSlice({ id: "S04", milestoneId: "M001", title: "Feature B", status: "pending", depends: ["S01"], sequence: 4 });
|
|
159
|
+
|
|
160
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# M001\n");
|
|
161
|
+
|
|
162
|
+
// S03 depends on S01 (done) and S02 (done) — unblocked
|
|
163
|
+
assert.equal(
|
|
164
|
+
getPriorSliceCompletionBlocker(repo, "main", "plan-slice", "M001/S03"),
|
|
165
|
+
null,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// S04 depends only on S01 (done) — unblocked even though S03 is incomplete
|
|
169
|
+
assert.equal(
|
|
170
|
+
getPriorSliceCompletionBlocker(repo, "main", "plan-slice", "M001/S04"),
|
|
171
|
+
null,
|
|
172
|
+
);
|
|
185
173
|
});
|
|
186
174
|
|
|
187
|
-
test("dispatch guard skips completed milestone with SUMMARY even if it has unchecked remediation slices (#1716)", () => {
|
|
175
|
+
test("dispatch guard skips completed milestone with SUMMARY even if it has unchecked remediation slices (#1716)", (t) => {
|
|
188
176
|
const repo = setupRepo();
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
teardownRepo(repo);
|
|
216
|
-
}
|
|
177
|
+
t.after(() => teardownRepo(repo));
|
|
178
|
+
|
|
179
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M001"), { recursive: true });
|
|
180
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M002"), { recursive: true });
|
|
181
|
+
|
|
182
|
+
// M001 is complete (has SUMMARY) but has unchecked remediation slices in DB
|
|
183
|
+
insertMilestone({ id: "M001", title: "Previous" });
|
|
184
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Core", status: "complete", depends: [], sequence: 1 });
|
|
185
|
+
insertSlice({ id: "S02", milestoneId: "M001", title: "Tests", status: "complete", depends: ["S01"], sequence: 2 });
|
|
186
|
+
insertSlice({ id: "S03-R", milestoneId: "M001", title: "Remediation", status: "pending", depends: ["S02"], sequence: 3 });
|
|
187
|
+
insertSlice({ id: "S04-R", milestoneId: "M001", title: "Remediation 2", status: "pending", depends: ["S02"], sequence: 4 });
|
|
188
|
+
|
|
189
|
+
insertMilestone({ id: "M002", title: "Current" });
|
|
190
|
+
insertSlice({ id: "S01", milestoneId: "M002", title: "Start", status: "pending", depends: [], sequence: 1 });
|
|
191
|
+
|
|
192
|
+
// M001 SUMMARY on disk triggers skip
|
|
193
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# M001\n");
|
|
194
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M001", "M001-SUMMARY.md"),
|
|
195
|
+
"---\nstatus: complete\n---\n# M001 Summary\nDone.\n");
|
|
196
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M002", "M002-ROADMAP.md"), "# M002\n");
|
|
197
|
+
|
|
198
|
+
// M001 has SUMMARY — should be skipped, not block M002/S01
|
|
199
|
+
assert.equal(
|
|
200
|
+
getPriorSliceCompletionBlocker(repo, "main", "plan-slice", "M002/S01"),
|
|
201
|
+
null,
|
|
202
|
+
);
|
|
217
203
|
});
|
|
218
204
|
|
|
219
|
-
test("dispatch guard works without git repo", () => {
|
|
205
|
+
test("dispatch guard works without git repo", (t) => {
|
|
220
206
|
const repo = setupRepo();
|
|
221
|
-
|
|
222
|
-
|
|
207
|
+
t.after(() => teardownRepo(repo));
|
|
208
|
+
|
|
209
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M001"), { recursive: true });
|
|
223
210
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
211
|
+
insertMilestone({ id: "M001", title: "Test" });
|
|
212
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Done", status: "complete", depends: [], sequence: 1 });
|
|
213
|
+
insertSlice({ id: "S02", milestoneId: "M001", title: "Pending", status: "pending", depends: ["S01"], sequence: 2 });
|
|
227
214
|
|
|
228
|
-
|
|
215
|
+
writeFileSync(join(repo, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "# M001\n");
|
|
229
216
|
|
|
230
|
-
|
|
231
|
-
} finally {
|
|
232
|
-
teardownRepo(repo);
|
|
233
|
-
}
|
|
217
|
+
assert.equal(getPriorSliceCompletionBlocker(repo, "main", "plan-slice", "M001/S02"), null);
|
|
234
218
|
});
|
|
@@ -71,62 +71,56 @@ function scaffoldTaskPlan(basePath: string, mid: string, sid: string, tid: strin
|
|
|
71
71
|
|
|
72
72
|
// ─── Tests ─────────────────────────────────────────────────────────────────
|
|
73
73
|
|
|
74
|
-
test("dispatch: missing task plan triggers plan-slice (not stop) — issue #909", async () => {
|
|
74
|
+
test("dispatch: missing task plan triggers plan-slice (not stop) — issue #909", async (t) => {
|
|
75
75
|
const tmp = mkdtempSync(join(tmpdir(), "gsd-909-"));
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
90
|
-
}
|
|
76
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
77
|
+
|
|
78
|
+
// Slice plan exists with tasks, but tasks/ directory is empty
|
|
79
|
+
scaffoldSlicePlan(tmp, "M002", "S03");
|
|
80
|
+
|
|
81
|
+
const ctx = makeContext(tmp);
|
|
82
|
+
const result = await resolveDispatch(ctx);
|
|
83
|
+
|
|
84
|
+
assert.equal(result.action, "dispatch", "should dispatch, not stop");
|
|
85
|
+
assert.ok(result.action === "dispatch" && result.unitType === "plan-slice",
|
|
86
|
+
`unitType should be plan-slice, got: ${result.action === "dispatch" ? result.unitType : "(stop)"}`);
|
|
87
|
+
assert.ok(result.action === "dispatch" && result.unitId === "M002/S03",
|
|
88
|
+
`unitId should be M002/S03, got: ${result.action === "dispatch" ? result.unitId : "(stop)"}`);
|
|
91
89
|
});
|
|
92
90
|
|
|
93
|
-
test("dispatch: present task plan proceeds to execute-task normally", async () => {
|
|
91
|
+
test("dispatch: present task plan proceeds to execute-task normally", async (t) => {
|
|
94
92
|
const tmp = mkdtempSync(join(tmpdir(), "gsd-909-ok-"));
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
109
|
-
}
|
|
93
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
94
|
+
|
|
95
|
+
scaffoldSlicePlan(tmp, "M002", "S03");
|
|
96
|
+
scaffoldTaskPlan(tmp, "M002", "S03", "T01");
|
|
97
|
+
|
|
98
|
+
const ctx = makeContext(tmp);
|
|
99
|
+
const result = await resolveDispatch(ctx);
|
|
100
|
+
|
|
101
|
+
assert.equal(result.action, "dispatch");
|
|
102
|
+
assert.ok(result.action === "dispatch" && result.unitType === "execute-task",
|
|
103
|
+
`unitType should be execute-task, got: ${result.action === "dispatch" ? result.unitType : "(stop)"}`);
|
|
104
|
+
assert.ok(result.action === "dispatch" && result.unitId === "M002/S03/T01",
|
|
105
|
+
`unitId should be M002/S03/T01, got: ${result.action === "dispatch" ? result.unitId : "(stop)"}`);
|
|
110
106
|
});
|
|
111
107
|
|
|
112
|
-
test("dispatch: plan-slice recovery loop — second call after plan-slice still recovers cleanly", async () => {
|
|
108
|
+
test("dispatch: plan-slice recovery loop — second call after plan-slice still recovers cleanly", async (t) => {
|
|
113
109
|
// Simulate: plan-slice ran but T01-PLAN.md is still missing (e.g. agent crashed mid-write).
|
|
114
110
|
// Dispatch should still re-dispatch plan-slice, not hard-stop.
|
|
115
111
|
const tmp = mkdtempSync(join(tmpdir(), "gsd-909-loop-"));
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
131
|
-
}
|
|
112
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
113
|
+
|
|
114
|
+
scaffoldSlicePlan(tmp, "M002", "S03");
|
|
115
|
+
|
|
116
|
+
const ctx = makeContext(tmp);
|
|
117
|
+
const r1 = await resolveDispatch(ctx);
|
|
118
|
+
assert.equal(r1.action, "dispatch");
|
|
119
|
+
assert.ok(r1.action === "dispatch" && r1.unitType === "plan-slice");
|
|
120
|
+
|
|
121
|
+
// Still no task plan written — dispatch again
|
|
122
|
+
const r2 = await resolveDispatch(ctx);
|
|
123
|
+
assert.equal(r2.action, "dispatch");
|
|
124
|
+
assert.ok(r2.action === "dispatch" && r2.unitType === "plan-slice",
|
|
125
|
+
"should keep dispatching plan-slice until task plans appear");
|
|
132
126
|
});
|