gsd-pi 2.23.0 → 2.25.0
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 +2 -1
- package/dist/cli.js +12 -3
- package/dist/headless.d.ts +4 -0
- package/dist/headless.js +118 -10
- package/dist/help-text.js +22 -7
- package/dist/models-resolver.d.ts +0 -11
- package/dist/models-resolver.js +0 -15
- package/dist/resource-loader.d.ts +0 -1
- package/dist/resource-loader.js +64 -18
- package/dist/resources/GSD-WORKFLOW.md +12 -9
- package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
- package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
- package/dist/resources/extensions/gsd/activity-log.ts +5 -3
- package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +87 -0
- package/dist/resources/extensions/gsd/auto-recovery.ts +41 -2
- package/dist/resources/extensions/gsd/auto-worktree.ts +134 -4
- package/dist/resources/extensions/gsd/auto.ts +307 -77
- package/dist/resources/extensions/gsd/cache.ts +3 -1
- package/dist/resources/extensions/gsd/commands.ts +176 -10
- package/dist/resources/extensions/gsd/complexity.ts +1 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +38 -0
- package/dist/resources/extensions/gsd/doctor.ts +58 -11
- package/dist/resources/extensions/gsd/exit-command.ts +2 -2
- package/dist/resources/extensions/gsd/git-service.ts +74 -14
- package/dist/resources/extensions/gsd/gitignore.ts +1 -0
- package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +109 -12
- package/dist/resources/extensions/gsd/index.ts +48 -2
- package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/dist/resources/extensions/gsd/memory-store.ts +441 -0
- package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
- package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/dist/resources/extensions/gsd/preferences.ts +65 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
- package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
- package/dist/resources/extensions/gsd/state.ts +72 -30
- package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +256 -2
- package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
- package/dist/resources/extensions/gsd/types.ts +15 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/dist/resources/extensions/gsd/worktree.ts +9 -2
- package/dist/resources/extensions/search-the-web/native-search.ts +15 -5
- package/dist/resources/extensions/subagent/index.ts +5 -0
- package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
- package/dist/update-check.d.ts +9 -0
- package/dist/update-check.js +97 -0
- package/package.json +6 -1
- package/packages/pi-agent-core/dist/agent-loop.js +2 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +2 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +55 -7
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
- package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/packages/pi-ai/dist/providers/mistral.js +3 -0
- package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +23 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +59 -9
- package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
- package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
- package/packages/pi-ai/src/providers/mistral.ts +3 -0
- package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
- package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
- package/packages/pi-ai/src/types.ts +19 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +72 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +84 -0
- package/scripts/postinstall.js +7 -109
- package/src/resources/GSD-WORKFLOW.md +12 -9
- package/src/resources/extensions/bg-shell/overlay.ts +18 -17
- package/src/resources/extensions/get-secrets-from-user.ts +5 -23
- package/src/resources/extensions/gsd/activity-log.ts +5 -3
- package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +87 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +41 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +134 -4
- package/src/resources/extensions/gsd/auto.ts +307 -77
- package/src/resources/extensions/gsd/cache.ts +3 -1
- package/src/resources/extensions/gsd/commands.ts +176 -10
- package/src/resources/extensions/gsd/complexity.ts +1 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +38 -0
- package/src/resources/extensions/gsd/doctor.ts +58 -11
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/git-service.ts +74 -14
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/gsd-db.ts +78 -1
- package/src/resources/extensions/gsd/guided-flow.ts +109 -12
- package/src/resources/extensions/gsd/index.ts +48 -2
- package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/src/resources/extensions/gsd/memory-store.ts +441 -0
- package/src/resources/extensions/gsd/migrate/command.ts +2 -2
- package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/src/resources/extensions/gsd/preferences.ts +65 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
- package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/src/resources/extensions/gsd/session-status-io.ts +197 -0
- package/src/resources/extensions/gsd/state.ts +72 -30
- package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +256 -2
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/src/resources/extensions/gsd/triage-ui.ts +1 -1
- package/src/resources/extensions/gsd/types.ts +15 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/src/resources/extensions/gsd/worktree.ts +9 -2
- package/src/resources/extensions/search-the-web/native-search.ts +15 -5
- package/src/resources/extensions/subagent/index.ts +5 -0
- package/src/resources/extensions/subagent/worker-registry.ts +99 -0
|
@@ -10,12 +10,15 @@ import {
|
|
|
10
10
|
verifyExpectedArtifact,
|
|
11
11
|
diagnoseExpectedArtifact,
|
|
12
12
|
buildLoopRemediationSteps,
|
|
13
|
+
selfHealRuntimeRecords,
|
|
13
14
|
completedKeysPath,
|
|
14
15
|
persistCompletedKey,
|
|
15
16
|
removePersistedKey,
|
|
16
17
|
loadPersistedKeys,
|
|
17
18
|
} from "../auto-recovery.ts";
|
|
18
19
|
import { parseRoadmap, clearParseCache } from "../files.ts";
|
|
20
|
+
import { invalidateAllCaches } from "../cache.ts";
|
|
21
|
+
import { deriveState, invalidateStateCache } from "../state.ts";
|
|
19
22
|
|
|
20
23
|
function makeTmpBase(): string {
|
|
21
24
|
const base = join(tmpdir(), `gsd-test-${randomUUID()}`);
|
|
@@ -273,6 +276,68 @@ test("removePersistedKey is safe when file doesn't exist", () => {
|
|
|
273
276
|
}
|
|
274
277
|
});
|
|
275
278
|
|
|
279
|
+
// ─── Dual-load across worktree boundary (#769) ───────────────────────────
|
|
280
|
+
|
|
281
|
+
test("loadPersistedKeys unions keys from project root and worktree", () => {
|
|
282
|
+
// Simulate two separate .gsd directories (project root + worktree)
|
|
283
|
+
// each with a different set of completed keys. Loading from both
|
|
284
|
+
// into the same Set should produce the union.
|
|
285
|
+
const projectRoot = makeTmpBase();
|
|
286
|
+
const worktree = makeTmpBase();
|
|
287
|
+
try {
|
|
288
|
+
// Persist different keys in each location
|
|
289
|
+
persistCompletedKey(projectRoot, "execute-task/M001/S01/T01");
|
|
290
|
+
persistCompletedKey(projectRoot, "plan-slice/M001/S02");
|
|
291
|
+
|
|
292
|
+
persistCompletedKey(worktree, "execute-task/M001/S01/T02");
|
|
293
|
+
persistCompletedKey(worktree, "plan-slice/M001/S02"); // overlap
|
|
294
|
+
|
|
295
|
+
// Load from both into the same set (mimicking startup dual-load)
|
|
296
|
+
const keys = new Set<string>();
|
|
297
|
+
loadPersistedKeys(projectRoot, keys);
|
|
298
|
+
loadPersistedKeys(worktree, keys);
|
|
299
|
+
|
|
300
|
+
assert.ok(keys.has("execute-task/M001/S01/T01"), "key from project root");
|
|
301
|
+
assert.ok(keys.has("plan-slice/M001/S02"), "shared key");
|
|
302
|
+
assert.ok(keys.has("execute-task/M001/S01/T02"), "key from worktree");
|
|
303
|
+
assert.equal(keys.size, 3, "union should deduplicate overlapping keys");
|
|
304
|
+
} finally {
|
|
305
|
+
cleanup(projectRoot);
|
|
306
|
+
cleanup(worktree);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("completed-units.json set-union merge produces correct result", () => {
|
|
311
|
+
// Verify that a manual set-union merge (as done in syncStateToProjectRoot)
|
|
312
|
+
// correctly merges two JSON arrays of keys.
|
|
313
|
+
const projectRoot = makeTmpBase();
|
|
314
|
+
const worktree = makeTmpBase();
|
|
315
|
+
try {
|
|
316
|
+
// Write keys to both locations
|
|
317
|
+
const prKeysFile = join(projectRoot, ".gsd", "completed-units.json");
|
|
318
|
+
const wtKeysFile = join(worktree, ".gsd", "completed-units.json");
|
|
319
|
+
|
|
320
|
+
writeFileSync(prKeysFile, JSON.stringify(["a", "b"]));
|
|
321
|
+
writeFileSync(wtKeysFile, JSON.stringify(["b", "c", "d"]));
|
|
322
|
+
|
|
323
|
+
// Perform the same merge logic used in syncStateToProjectRoot
|
|
324
|
+
const srcKeys: string[] = JSON.parse(readFileSync(wtKeysFile, "utf8"));
|
|
325
|
+
let dstKeys: string[] = [];
|
|
326
|
+
if (existsSync(prKeysFile)) {
|
|
327
|
+
dstKeys = JSON.parse(readFileSync(prKeysFile, "utf8"));
|
|
328
|
+
}
|
|
329
|
+
const merged = [...new Set([...dstKeys, ...srcKeys])];
|
|
330
|
+
writeFileSync(prKeysFile, JSON.stringify(merged, null, 2));
|
|
331
|
+
|
|
332
|
+
// Verify the merged result
|
|
333
|
+
const result: string[] = JSON.parse(readFileSync(prKeysFile, "utf8"));
|
|
334
|
+
assert.deepStrictEqual(result.sort(), ["a", "b", "c", "d"]);
|
|
335
|
+
} finally {
|
|
336
|
+
cleanup(projectRoot);
|
|
337
|
+
cleanup(worktree);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
276
341
|
// ─── verifyExpectedArtifact: parse cache collision regression ─────────────
|
|
277
342
|
|
|
278
343
|
test("verifyExpectedArtifact detects roadmap [x] change despite parse cache", () => {
|
|
@@ -343,7 +408,8 @@ test("verifyExpectedArtifact accepts plan-slice with actual tasks", () => {
|
|
|
343
408
|
const base = makeTmpBase();
|
|
344
409
|
try {
|
|
345
410
|
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
346
|
-
|
|
411
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
412
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
347
413
|
writeFileSync(join(sliceDir, "S01-PLAN.md"), [
|
|
348
414
|
"# S01: Test Slice",
|
|
349
415
|
"",
|
|
@@ -352,6 +418,8 @@ test("verifyExpectedArtifact accepts plan-slice with actual tasks", () => {
|
|
|
352
418
|
"- [ ] **T01: Implement feature** `est:2h`",
|
|
353
419
|
"- [ ] **T02: Write tests** `est:1h`",
|
|
354
420
|
].join("\n"));
|
|
421
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
|
|
422
|
+
writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan");
|
|
355
423
|
assert.strictEqual(
|
|
356
424
|
verifyExpectedArtifact("plan-slice", "M001/S01", base),
|
|
357
425
|
true,
|
|
@@ -366,7 +434,8 @@ test("verifyExpectedArtifact accepts plan-slice with completed tasks", () => {
|
|
|
366
434
|
const base = makeTmpBase();
|
|
367
435
|
try {
|
|
368
436
|
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
369
|
-
|
|
437
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
438
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
370
439
|
writeFileSync(join(sliceDir, "S01-PLAN.md"), [
|
|
371
440
|
"# S01: Test Slice",
|
|
372
441
|
"",
|
|
@@ -375,6 +444,8 @@ test("verifyExpectedArtifact accepts plan-slice with completed tasks", () => {
|
|
|
375
444
|
"- [x] **T01: Implement feature** `est:2h`",
|
|
376
445
|
"- [ ] **T02: Write tests** `est:1h`",
|
|
377
446
|
].join("\n"));
|
|
447
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
|
|
448
|
+
writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan");
|
|
378
449
|
assert.strictEqual(
|
|
379
450
|
verifyExpectedArtifact("plan-slice", "M001/S01", base),
|
|
380
451
|
true,
|
|
@@ -384,3 +455,186 @@ test("verifyExpectedArtifact accepts plan-slice with completed tasks", () => {
|
|
|
384
455
|
cleanup(base);
|
|
385
456
|
}
|
|
386
457
|
});
|
|
458
|
+
|
|
459
|
+
// ─── verifyExpectedArtifact: plan-slice task plan check (#739) ────────────
|
|
460
|
+
|
|
461
|
+
test("verifyExpectedArtifact plan-slice passes when all task plan files exist", () => {
|
|
462
|
+
const base = makeTmpBase();
|
|
463
|
+
try {
|
|
464
|
+
const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
465
|
+
const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
|
|
466
|
+
const planContent = [
|
|
467
|
+
"# S01: Test Slice",
|
|
468
|
+
"",
|
|
469
|
+
"## Tasks",
|
|
470
|
+
"",
|
|
471
|
+
"- [ ] **T01: First task** `est:1h`",
|
|
472
|
+
"- [ ] **T02: Second task** `est:2h`",
|
|
473
|
+
].join("\n");
|
|
474
|
+
writeFileSync(planPath, planContent);
|
|
475
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n\nDo the thing.");
|
|
476
|
+
writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan\n\nDo the other thing.");
|
|
477
|
+
|
|
478
|
+
const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
|
|
479
|
+
assert.equal(result, true, "should pass when all task plan files exist");
|
|
480
|
+
} finally {
|
|
481
|
+
cleanup(base);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
test("verifyExpectedArtifact plan-slice fails when a task plan file is missing (#739)", () => {
|
|
486
|
+
const base = makeTmpBase();
|
|
487
|
+
try {
|
|
488
|
+
const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
489
|
+
const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
|
|
490
|
+
const planContent = [
|
|
491
|
+
"# S01: Test Slice",
|
|
492
|
+
"",
|
|
493
|
+
"## Tasks",
|
|
494
|
+
"",
|
|
495
|
+
"- [ ] **T01: First task** `est:1h`",
|
|
496
|
+
"- [ ] **T02: Second task** `est:2h`",
|
|
497
|
+
].join("\n");
|
|
498
|
+
writeFileSync(planPath, planContent);
|
|
499
|
+
// Only write T01-PLAN.md — T02 is missing
|
|
500
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n\nDo the thing.");
|
|
501
|
+
|
|
502
|
+
const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
|
|
503
|
+
assert.equal(result, false, "should fail when T02-PLAN.md is missing");
|
|
504
|
+
} finally {
|
|
505
|
+
cleanup(base);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test("verifyExpectedArtifact plan-slice fails for plan with no tasks (#699)", () => {
|
|
510
|
+
const base = makeTmpBase();
|
|
511
|
+
try {
|
|
512
|
+
const planPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md");
|
|
513
|
+
const planContent = [
|
|
514
|
+
"# S01: Test Slice",
|
|
515
|
+
"",
|
|
516
|
+
"## Goal",
|
|
517
|
+
"",
|
|
518
|
+
"Just some documentation updates, no tasks.",
|
|
519
|
+
].join("\n");
|
|
520
|
+
writeFileSync(planPath, planContent);
|
|
521
|
+
|
|
522
|
+
const result = verifyExpectedArtifact("plan-slice", "M001/S01", base);
|
|
523
|
+
assert.equal(result, false, "should fail when plan has no task entries (empty scaffold, #699)");
|
|
524
|
+
} finally {
|
|
525
|
+
cleanup(base);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// ─── selfHealRuntimeRecords — worktree base path (#769) ──────────────────
|
|
530
|
+
|
|
531
|
+
test("selfHealRuntimeRecords clears stale record when artifact exists at worktree base (#769)", async () => {
|
|
532
|
+
// Simulate worktree layout: the runtime record AND the artifact both live
|
|
533
|
+
// under the worktree's .gsd/, not the main project root.
|
|
534
|
+
const worktreeBase = makeTmpBase();
|
|
535
|
+
const mainBase = makeTmpBase();
|
|
536
|
+
try {
|
|
537
|
+
const { writeUnitRuntimeRecord, readUnitRuntimeRecord } = await import("../unit-runtime.ts");
|
|
538
|
+
|
|
539
|
+
// Write a stale runtime record in the worktree .gsd/runtime/units/
|
|
540
|
+
writeUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01", Date.now() - 7200_000, {
|
|
541
|
+
phase: "dispatched",
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// Write the UAT result artifact in the worktree .gsd/milestones/
|
|
545
|
+
const uatPath = join(worktreeBase, ".gsd", "milestones", "M001", "slices", "S01", "S01-UAT-RESULT.md");
|
|
546
|
+
writeFileSync(uatPath, "---\nresult: pass\n---\n# UAT Result\nAll tests passed.\n");
|
|
547
|
+
|
|
548
|
+
// Verify the runtime record exists before heal
|
|
549
|
+
const before = readUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01");
|
|
550
|
+
assert.ok(before, "runtime record should exist before heal");
|
|
551
|
+
|
|
552
|
+
// Mock ExtensionContext with minimal notify
|
|
553
|
+
const notifications: string[] = [];
|
|
554
|
+
const mockCtx = {
|
|
555
|
+
ui: { notify: (msg: string) => { notifications.push(msg); } },
|
|
556
|
+
} as any;
|
|
557
|
+
|
|
558
|
+
// Call selfHeal with worktreeBase — this is the fix: using the worktree path
|
|
559
|
+
// so both the runtime record and artifact are found
|
|
560
|
+
const completedKeys = new Set<string>();
|
|
561
|
+
await selfHealRuntimeRecords(worktreeBase, mockCtx, completedKeys);
|
|
562
|
+
|
|
563
|
+
// The stale record should be cleared
|
|
564
|
+
const after = readUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01");
|
|
565
|
+
assert.equal(after, null, "runtime record should be cleared after heal");
|
|
566
|
+
|
|
567
|
+
// The completion key should be persisted
|
|
568
|
+
assert.ok(completedKeys.has("run-uat/M001/S01"), "completion key should be added");
|
|
569
|
+
assert.ok(notifications.some(n => n.includes("Self-heal")), "should emit self-heal notification");
|
|
570
|
+
|
|
571
|
+
// Now verify that calling with mainBase does NOT find/clear anything (the old bug)
|
|
572
|
+
// Write a stale record at mainBase but NO artifact there
|
|
573
|
+
writeUnitRuntimeRecord(mainBase, "run-uat", "M001/S01", Date.now() - 7200_000, {
|
|
574
|
+
phase: "dispatched",
|
|
575
|
+
});
|
|
576
|
+
const mainKeys = new Set<string>();
|
|
577
|
+
await selfHealRuntimeRecords(mainBase, mockCtx, mainKeys);
|
|
578
|
+
|
|
579
|
+
// The record at mainBase should be cleared by the stale timeout (>1h),
|
|
580
|
+
// but the completion key should NOT be set (artifact doesn't exist at mainBase)
|
|
581
|
+
const afterMain = readUnitRuntimeRecord(mainBase, "run-uat", "M001/S01");
|
|
582
|
+
assert.equal(afterMain, null, "stale record at main base should be cleared by timeout");
|
|
583
|
+
assert.ok(!mainKeys.has("run-uat/M001/S01"), "completion key should NOT be set when artifact is missing");
|
|
584
|
+
} finally {
|
|
585
|
+
cleanup(worktreeBase);
|
|
586
|
+
cleanup(mainBase);
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
// ─── #793: invalidateAllCaches unblocks skip-loop ─────────────────────────
|
|
591
|
+
// When the skip-loop breaker fires, it must call invalidateAllCaches() (not
|
|
592
|
+
// just invalidateStateCache()) to clear path/parse caches that deriveState
|
|
593
|
+
// depends on. Without this, even after cache invalidation, deriveState reads
|
|
594
|
+
// stale directory listings and returns the same unit, looping forever.
|
|
595
|
+
test("#793: invalidateAllCaches clears all caches so deriveState sees fresh disk state", async () => {
|
|
596
|
+
const base = makeTmpBase();
|
|
597
|
+
try {
|
|
598
|
+
const mid = "M001";
|
|
599
|
+
const sid = "S01";
|
|
600
|
+
const planDir = join(base, ".gsd", "milestones", mid, "slices", sid);
|
|
601
|
+
const tasksDir = join(planDir, "tasks");
|
|
602
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
603
|
+
mkdirSync(join(base, ".gsd", "milestones", mid), { recursive: true });
|
|
604
|
+
|
|
605
|
+
writeFileSync(
|
|
606
|
+
join(base, ".gsd", "milestones", mid, `${mid}-ROADMAP.md`),
|
|
607
|
+
`# M001: Test Milestone\n\n**Vision:** test.\n\n## Slices\n\n- [ ] **${sid}: Slice One** \`risk:low\` \`depends:[]\`\n > After this: done.\n`,
|
|
608
|
+
);
|
|
609
|
+
const planUnchecked = `# ${sid}: Slice One\n\n**Goal:** test.\n\n## Tasks\n\n- [ ] **T01: Task One** \`est:10m\`\n- [ ] **T02: Task Two** \`est:10m\`\n`;
|
|
610
|
+
writeFileSync(join(planDir, `${sid}-PLAN.md`), planUnchecked);
|
|
611
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01: Task One\n\n**Goal:** t\n\n## Steps\n- step\n\n## Verification\n- v\n");
|
|
612
|
+
writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02: Task Two\n\n**Goal:** t\n\n## Steps\n- step\n\n## Verification\n- v\n");
|
|
613
|
+
|
|
614
|
+
// Warm all caches
|
|
615
|
+
const state1 = await deriveState(base);
|
|
616
|
+
assert.equal(state1.activeTask?.id, "T01", "initial: T01 is active");
|
|
617
|
+
|
|
618
|
+
// Simulate task completion on disk (what the LLM does)
|
|
619
|
+
const planChecked = `# ${sid}: Slice One\n\n**Goal:** test.\n\n## Tasks\n\n- [x] **T01: Task One** \`est:10m\`\n- [ ] **T02: Task Two** \`est:10m\`\n`;
|
|
620
|
+
writeFileSync(join(planDir, `${sid}-PLAN.md`), planChecked);
|
|
621
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "---\nid: T01\n---\n# Summary\n");
|
|
622
|
+
|
|
623
|
+
// invalidateStateCache alone: _stateCache cleared but path/parse caches warm
|
|
624
|
+
invalidateStateCache();
|
|
625
|
+
|
|
626
|
+
// invalidateAllCaches: all caches cleared — deriveState must re-read disk
|
|
627
|
+
invalidateAllCaches();
|
|
628
|
+
const state2 = await deriveState(base);
|
|
629
|
+
|
|
630
|
+
// After full invalidation, T01 should be complete and T02 should be next
|
|
631
|
+
assert.notEqual(state2.activeTask?.id, "T01", "#793: T01 not re-dispatched after full invalidation");
|
|
632
|
+
|
|
633
|
+
// Verify the caches are truly cleared by calling clearParseCache and clearPathCache
|
|
634
|
+
// do not throw (they should be no-ops after invalidateAllCaches already cleared them)
|
|
635
|
+
clearParseCache(); // no-op, but should not throw
|
|
636
|
+
assert.ok(true, "clearParseCache after invalidateAllCaches is safe");
|
|
637
|
+
} finally {
|
|
638
|
+
cleanup(base);
|
|
639
|
+
}
|
|
640
|
+
});
|
|
@@ -290,6 +290,40 @@ async function main(): Promise<void> {
|
|
|
290
290
|
assertTrue(existsSync(join(repo, "feature.ts")), "feature.ts merged to main");
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
// ─── Test 6: Skip checkout when main already current (#757) ───────
|
|
294
|
+
console.log("\n=== skip checkout when main already current (#757) ===");
|
|
295
|
+
{
|
|
296
|
+
const repo = freshRepo();
|
|
297
|
+
const wtPath = createAutoWorktree(repo, "M060");
|
|
298
|
+
|
|
299
|
+
addSliceToMilestone(repo, wtPath, "M060", "S01", "Skip checkout test", [
|
|
300
|
+
{ file: "skip-checkout.ts", content: "export const skip = true;\n", message: "add skip-checkout" },
|
|
301
|
+
]);
|
|
302
|
+
|
|
303
|
+
const roadmap = makeRoadmap("M060", "Skip checkout verification", [
|
|
304
|
+
{ id: "S01", title: "Skip checkout test" },
|
|
305
|
+
]);
|
|
306
|
+
|
|
307
|
+
// Verify main is already checked out at repo root (worktree default)
|
|
308
|
+
const branchAtRoot = run("git rev-parse --abbrev-ref HEAD", repo);
|
|
309
|
+
assertEq(branchAtRoot, "main", "main is already checked out at project root");
|
|
310
|
+
|
|
311
|
+
// mergeMilestoneToMain should succeed without attempting to checkout main
|
|
312
|
+
// (which would fail with "already used by worktree" error)
|
|
313
|
+
let threw = false;
|
|
314
|
+
try {
|
|
315
|
+
const result = mergeMilestoneToMain(repo, "M060", roadmap);
|
|
316
|
+
assertTrue(result.commitMessage.includes("feat(M060)"), "merge commit created");
|
|
317
|
+
} catch (err) {
|
|
318
|
+
threw = true;
|
|
319
|
+
console.error("Unexpected error:", err);
|
|
320
|
+
}
|
|
321
|
+
assertTrue(!threw, "does not fail when main is already checked out at project root");
|
|
322
|
+
|
|
323
|
+
// Verify the merge actually happened
|
|
324
|
+
assertTrue(existsSync(join(repo, "skip-checkout.ts")), "skip-checkout.ts merged to main");
|
|
325
|
+
}
|
|
326
|
+
|
|
293
327
|
} finally {
|
|
294
328
|
process.chdir(savedCwd);
|
|
295
329
|
for (const d of tempDirs) {
|
|
@@ -153,6 +153,64 @@ async function main(): Promise<void> {
|
|
|
153
153
|
// After teardown, originalBase should be null
|
|
154
154
|
assertEq(getAutoWorktreeOriginalBase(), null, "no split-brain: originalBase cleared");
|
|
155
155
|
|
|
156
|
+
// ─── #778: reconcile plan checkboxes on re-attach ─────────────────
|
|
157
|
+
console.log("\n=== #778: reconcile plan checkboxes on re-attach ===");
|
|
158
|
+
{
|
|
159
|
+
// Simulate: T01 [x] was committed to milestone branch, T02 [x] was
|
|
160
|
+
// written to project root by syncStateToProjectRoot() but the
|
|
161
|
+
// auto-commit crashed before it fired. On restart the worktree is
|
|
162
|
+
// re-created from the milestone branch HEAD (T02 still [ ]).
|
|
163
|
+
// reconcilePlanCheckboxes should forward-apply T02 [x] from the root.
|
|
164
|
+
|
|
165
|
+
const planRelPath = join(".gsd", "milestones", "M004", "slices", "S01", "S01-PLAN.md");
|
|
166
|
+
const planDir = join(tempDir, ".gsd", "milestones", "M004", "slices", "S01");
|
|
167
|
+
const { mkdirSync: mkdir, writeFileSync: write, readFileSync: read } = await import("node:fs");
|
|
168
|
+
|
|
169
|
+
// Plan on integration branch (project root): T01 [x], T02 [x]
|
|
170
|
+
mkdir(planDir, { recursive: true });
|
|
171
|
+
write(
|
|
172
|
+
join(tempDir, planRelPath),
|
|
173
|
+
"# S01 Plan\n- [x] **T01:** task one\n- [x] **T02:** task two\n- [ ] **T03:** task three\n",
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Write integration-branch plan to git so milestone branch starts from it
|
|
177
|
+
run(`git add .`, tempDir);
|
|
178
|
+
run(`git commit -m "add plan with T01 and T02 checked" --allow-empty`, tempDir);
|
|
179
|
+
|
|
180
|
+
// Create milestone branch with only T01 [x] (simulating crash before T02 commit)
|
|
181
|
+
const milestoneBranch = "milestone/M004";
|
|
182
|
+
run(`git checkout -b ${milestoneBranch}`, tempDir);
|
|
183
|
+
mkdir(planDir, { recursive: true });
|
|
184
|
+
write(
|
|
185
|
+
join(tempDir, planRelPath),
|
|
186
|
+
"# S01 Plan\n- [x] **T01:** task one\n- [ ] **T02:** task two\n- [ ] **T03:** task three\n",
|
|
187
|
+
);
|
|
188
|
+
run(`git add .`, tempDir);
|
|
189
|
+
run(`git commit -m "milestone: only T01 checked"`, tempDir);
|
|
190
|
+
run(`git checkout main`, tempDir);
|
|
191
|
+
|
|
192
|
+
// Restore project root plan (T01+T02 [x]) — simulates syncStateToProjectRoot
|
|
193
|
+
write(
|
|
194
|
+
join(tempDir, planRelPath),
|
|
195
|
+
"# S01 Plan\n- [x] **T01:** task one\n- [x] **T02:** task two\n- [ ] **T03:** task three\n",
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Create worktree re-attached to existing milestone branch (T02 still [ ] in branch)
|
|
199
|
+
const wtPath = createAutoWorktree(tempDir, "M004");
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const wtPlanPath = join(wtPath, planRelPath);
|
|
203
|
+
assertTrue(existsSync(wtPlanPath), "plan file exists in worktree after re-attach");
|
|
204
|
+
|
|
205
|
+
const wtPlan = read(wtPlanPath, "utf-8");
|
|
206
|
+
assertTrue(wtPlan.includes("- [x] **T02:"), "T02 should be [x] after reconciliation (was [ ] on branch)");
|
|
207
|
+
assertTrue(wtPlan.includes("- [x] **T01:"), "T01 stays [x]");
|
|
208
|
+
assertTrue(wtPlan.includes("- [ ] **T03:"), "T03 stays [ ] (not in root either)");
|
|
209
|
+
} finally {
|
|
210
|
+
teardownAutoWorktree(tempDir, "M004");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
156
214
|
} finally {
|
|
157
215
|
// Always restore cwd and clean up
|
|
158
216
|
process.chdir(savedCwd);
|
|
@@ -45,6 +45,12 @@ function writeMilestoneSummary(base: string, mid: string, content: string): void
|
|
|
45
45
|
writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function writeMilestoneValidation(base: string, mid: string, verdict: string = "pass"): void {
|
|
49
|
+
const dir = join(base, ".gsd", "milestones", mid);
|
|
50
|
+
mkdirSync(dir, { recursive: true });
|
|
51
|
+
writeFileSync(join(dir, `${mid}-VALIDATION.md`), `---\nverdict: ${verdict}\nremediation_round: 0\n---\n\n# Validation\nValidated.`);
|
|
52
|
+
}
|
|
53
|
+
|
|
48
54
|
function cleanup(base: string): void {
|
|
49
55
|
rmSync(base, { recursive: true, force: true });
|
|
50
56
|
}
|
|
@@ -176,7 +182,8 @@ async function main(): Promise<void> {
|
|
|
176
182
|
const roadmap = parseRoadmap(roadmapContent!);
|
|
177
183
|
assertTrue(isMilestoneComplete(roadmap), "isMilestoneComplete returns true when all slices are [x]");
|
|
178
184
|
|
|
179
|
-
// Verify deriveState returns completing-milestone phase
|
|
185
|
+
// Verify deriveState returns completing-milestone phase (with validation already done)
|
|
186
|
+
writeMilestoneValidation(base, "M001");
|
|
180
187
|
const state = await deriveState(base);
|
|
181
188
|
assertEq(state.phase, "completing-milestone", "deriveState returns completing-milestone when all slices done, no summary");
|
|
182
189
|
assertEq(state.activeMilestone?.id, "M001", "active milestone is M001");
|
|
@@ -248,31 +248,24 @@ async function main(): Promise<void> {
|
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
// ─── Test 5: Requirements counting from DB content
|
|
252
|
-
console.log('\n=== derive-state-db: requirements from
|
|
251
|
+
// ─── Test 5: Requirements counting from disk (DB no longer used for content) ─
|
|
252
|
+
console.log('\n=== derive-state-db: requirements from disk content ===');
|
|
253
253
|
{
|
|
254
254
|
const base = createFixtureBase();
|
|
255
255
|
try {
|
|
256
256
|
// Write minimal milestone dir (needed for milestone discovery)
|
|
257
257
|
mkdirSync(join(base, '.gsd', 'milestones', 'M001'), { recursive: true });
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
openDatabase(':memory:');
|
|
261
|
-
insertArtifactRow('REQUIREMENTS.md', REQUIREMENTS_CONTENT, {
|
|
262
|
-
artifact_type: 'requirements',
|
|
263
|
-
});
|
|
258
|
+
// Write REQUIREMENTS.md to disk (DB content is no longer used by deriveState)
|
|
259
|
+
writeFile(base, 'REQUIREMENTS.md', REQUIREMENTS_CONTENT);
|
|
264
260
|
|
|
265
261
|
invalidateStateCache();
|
|
266
262
|
const state = await deriveState(base);
|
|
267
263
|
|
|
268
|
-
// Requirements should come from
|
|
269
|
-
assertEq(state.requirements?.active, 2, 'req-from-
|
|
270
|
-
assertEq(state.requirements?.validated, 1, 'req-from-
|
|
271
|
-
assertEq(state.requirements?.total, 3, 'req-from-
|
|
272
|
-
|
|
273
|
-
closeDatabase();
|
|
264
|
+
// Requirements should come from disk
|
|
265
|
+
assertEq(state.requirements?.active, 2, 'req-from-disk: requirements.active = 2');
|
|
266
|
+
assertEq(state.requirements?.validated, 1, 'req-from-disk: requirements.validated = 1');
|
|
267
|
+
assertEq(state.requirements?.total, 3, 'req-from-disk: requirements.total = 3');
|
|
274
268
|
} finally {
|
|
275
|
-
closeDatabase();
|
|
276
269
|
cleanup(base);
|
|
277
270
|
}
|
|
278
271
|
}
|
|
@@ -310,6 +303,7 @@ async function main(): Promise<void> {
|
|
|
310
303
|
mkdirSync(join(base, '.gsd', 'milestones', 'M001'), { recursive: true });
|
|
311
304
|
mkdirSync(join(base, '.gsd', 'milestones', 'M002'), { recursive: true });
|
|
312
305
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', completedRoadmap);
|
|
306
|
+
writeFile(base, 'milestones/M001/M001-VALIDATION.md', `---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\nPassed.`);
|
|
313
307
|
writeFile(base, 'milestones/M001/M001-SUMMARY.md', summaryContent);
|
|
314
308
|
writeFile(base, 'milestones/M002/M002-ROADMAP.md', activeRoadmap);
|
|
315
309
|
|
|
@@ -26,6 +26,12 @@ function writeMilestoneSummary(base: string, mid: string, content: string): void
|
|
|
26
26
|
writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function writeMilestoneValidation(base: string, mid: string): void {
|
|
30
|
+
const dir = join(base, '.gsd', 'milestones', mid);
|
|
31
|
+
mkdirSync(dir, { recursive: true });
|
|
32
|
+
writeFileSync(join(dir, `${mid}-VALIDATION.md`), `---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\nPassed.`);
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
/**
|
|
30
36
|
* Creates M00x-CONTEXT.md with a valid YAML frontmatter block.
|
|
31
37
|
* frontmatter is the raw YAML lines between the --- delimiters.
|
|
@@ -120,6 +126,7 @@ async function main(): Promise<void> {
|
|
|
120
126
|
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
121
127
|
> After this: Done.
|
|
122
128
|
`);
|
|
129
|
+
writeMilestoneValidation(base, 'M001');
|
|
123
130
|
writeMilestoneSummary(base, 'M001', '# M001 Summary\n\nFirst milestone is complete.');
|
|
124
131
|
|
|
125
132
|
// M002: depends on M001, now unblocked
|
|
@@ -252,6 +259,7 @@ async function main(): Promise<void> {
|
|
|
252
259
|
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
253
260
|
> After this: Done.
|
|
254
261
|
`);
|
|
262
|
+
writeMilestoneValidation(base, 'M002');
|
|
255
263
|
writeMilestoneSummary(base, 'M002', '# M002 Summary\n\nSecond milestone is complete.');
|
|
256
264
|
|
|
257
265
|
const state = await deriveState(base);
|
|
@@ -321,6 +329,7 @@ async function main(): Promise<void> {
|
|
|
321
329
|
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
322
330
|
> After this: Done.
|
|
323
331
|
`);
|
|
332
|
+
writeMilestoneValidation(base, 'M004-0zjrg0');
|
|
324
333
|
writeMilestoneSummary(base, 'M004-0zjrg0', '# M004-0zjrg0 Summary\n\nComplete.');
|
|
325
334
|
|
|
326
335
|
// M005-b0m2hl: depends on M004-0zjrg0 (lowercase hex suffix)
|
|
@@ -54,6 +54,12 @@ function writeMilestoneSummary(base: string, mid: string, content: string): void
|
|
|
54
54
|
writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function writeMilestoneValidation(base: string, mid: string): void {
|
|
58
|
+
const dir = join(base, '.gsd', 'milestones', mid);
|
|
59
|
+
mkdirSync(dir, { recursive: true });
|
|
60
|
+
writeFileSync(join(dir, `${mid}-VALIDATION.md`), `---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\nPassed.`);
|
|
61
|
+
}
|
|
62
|
+
|
|
57
63
|
function cleanup(base: string): void {
|
|
58
64
|
rmSync(base, { recursive: true, force: true });
|
|
59
65
|
}
|
|
@@ -143,6 +149,7 @@ async function main(): Promise<void> {
|
|
|
143
149
|
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
144
150
|
> After this: Done.
|
|
145
151
|
`);
|
|
152
|
+
writeMilestoneValidation(base, 'M001');
|
|
146
153
|
writeMilestoneSummary(base, 'M001', '# M001 Summary\n\nFirst milestone complete.');
|
|
147
154
|
|
|
148
155
|
// M002: only CONTEXT-DRAFT.md
|
|
@@ -178,6 +185,7 @@ async function main(): Promise<void> {
|
|
|
178
185
|
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
179
186
|
> After this: Done.
|
|
180
187
|
`);
|
|
188
|
+
writeMilestoneValidation(base, 'M001');
|
|
181
189
|
writeMilestoneSummary(base, 'M001', '# M001 Summary\n\nComplete.');
|
|
182
190
|
|
|
183
191
|
// M002: draft only — should become active with needs-discussion
|
|
@@ -38,6 +38,12 @@ function writeMilestoneSummary(base: string, mid: string, content: string): void
|
|
|
38
38
|
writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
function writeMilestoneValidation(base: string, mid: string, verdict: string = 'pass'): void {
|
|
42
|
+
const dir = join(base, '.gsd', 'milestones', mid);
|
|
43
|
+
mkdirSync(dir, { recursive: true });
|
|
44
|
+
writeFileSync(join(dir, `${mid}-VALIDATION.md`), `---\nverdict: ${verdict}\nremediation_round: 0\n---\n\n# Validation\nValidated.`);
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
function writeRequirements(base: string, content: string): void {
|
|
42
48
|
writeFileSync(join(base, '.gsd', 'REQUIREMENTS.md'), content);
|
|
43
49
|
}
|
|
@@ -285,6 +291,7 @@ Continue from step 2.
|
|
|
285
291
|
> After this: Done.
|
|
286
292
|
`);
|
|
287
293
|
|
|
294
|
+
writeMilestoneValidation(base, 'M001');
|
|
288
295
|
writeMilestoneSummary(base, 'M001', `# M001 Summary\n\nMilestone complete.`);
|
|
289
296
|
|
|
290
297
|
const state = await deriveState(base);
|
|
@@ -381,6 +388,7 @@ Continue from step 2.
|
|
|
381
388
|
> After this: Done.
|
|
382
389
|
`);
|
|
383
390
|
|
|
391
|
+
writeMilestoneValidation(base, 'M001');
|
|
384
392
|
writeMilestoneSummary(base, 'M001', `# M001 Summary\n\nFirst milestone complete.`);
|
|
385
393
|
|
|
386
394
|
// M002: active (has incomplete slices)
|
|
@@ -486,6 +494,8 @@ Continue from step 2.
|
|
|
486
494
|
> After this: S02 complete.
|
|
487
495
|
`);
|
|
488
496
|
|
|
497
|
+
writeMilestoneValidation(base, 'M001');
|
|
498
|
+
|
|
489
499
|
const state = await deriveState(base);
|
|
490
500
|
|
|
491
501
|
assertEq(state.phase, 'completing-milestone', 'completing-ms: phase is completing-milestone');
|
|
@@ -521,6 +531,7 @@ Continue from step 2.
|
|
|
521
531
|
> After this: Done.
|
|
522
532
|
`);
|
|
523
533
|
|
|
534
|
+
writeMilestoneValidation(base, 'M001');
|
|
524
535
|
writeMilestoneSummary(base, 'M001', `# M001 Summary\n\nMilestone is complete.`);
|
|
525
536
|
|
|
526
537
|
const state = await deriveState(base);
|
|
@@ -550,6 +561,7 @@ Continue from step 2.
|
|
|
550
561
|
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
551
562
|
> After this: Done.
|
|
552
563
|
`);
|
|
564
|
+
writeMilestoneValidation(base, 'M001');
|
|
553
565
|
writeMilestoneSummary(base, 'M001', `# M001 Summary\n\nFirst milestone complete.`);
|
|
554
566
|
|
|
555
567
|
// M002: all slices done, no summary → completing-milestone
|
|
@@ -566,6 +578,8 @@ Continue from step 2.
|
|
|
566
578
|
> After this: Done.
|
|
567
579
|
`);
|
|
568
580
|
|
|
581
|
+
writeMilestoneValidation(base, 'M002');
|
|
582
|
+
|
|
569
583
|
// M003: has incomplete slices → pending (M002 is active)
|
|
570
584
|
writeRoadmap(base, 'M003', `# M003: Third Milestone
|
|
571
585
|
|