gsd-pi 2.41.0-dev.9446b20 → 2.41.0-dev.97349b1
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 +69 -29
- package/dist/cli-web-branch.d.ts +6 -0
- package/dist/cli-web-branch.js +17 -0
- package/dist/onboarding.js +2 -1
- package/dist/resources/extensions/gsd/auto/loop.js +9 -1
- package/dist/resources/extensions/gsd/auto/phases.js +26 -8
- package/dist/resources/extensions/gsd/auto-dashboard.js +6 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +19 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +7 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +12 -4
- package/dist/resources/extensions/gsd/auto-start.js +8 -3
- package/dist/resources/extensions/gsd/auto-worktree.js +147 -13
- package/dist/resources/extensions/gsd/auto.js +36 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +199 -164
- package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +62 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +16 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +8 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/context-store.js +4 -3
- package/dist/resources/extensions/gsd/db-writer.js +5 -2
- package/dist/resources/extensions/gsd/detection.js +1 -1
- package/dist/resources/extensions/gsd/doctor.js +11 -1
- package/dist/resources/extensions/gsd/exit-command.js +12 -2
- package/dist/resources/extensions/gsd/export.js +9 -13
- package/dist/resources/extensions/gsd/extension-manifest.json +2 -2
- package/dist/resources/extensions/gsd/files.js +28 -11
- package/dist/resources/extensions/gsd/forensics.js +10 -3
- package/dist/resources/extensions/gsd/git-service.js +5 -1
- package/dist/resources/extensions/gsd/gsd-db.js +25 -8
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +7 -3
- package/dist/resources/extensions/gsd/journal.js +85 -0
- package/dist/resources/extensions/gsd/md-importer.js +5 -0
- package/dist/resources/extensions/gsd/milestone-ids.js +1 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +2 -2
- package/dist/resources/extensions/gsd/post-unit-hooks.js +24 -412
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences.js +1 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +34 -4
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
- package/dist/resources/extensions/gsd/prompts/discuss.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
- package/dist/resources/extensions/gsd/repo-identity.js +46 -2
- package/dist/resources/extensions/gsd/rule-registry.js +489 -0
- package/dist/resources/extensions/gsd/rule-types.js +6 -0
- package/dist/resources/extensions/gsd/service-tier.js +138 -0
- package/dist/resources/extensions/gsd/structured-data-formatter.js +2 -1
- package/dist/resources/extensions/gsd/templates/decisions.md +2 -2
- package/dist/resources/extensions/gsd/workflow-templates.js +13 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +20 -6
- package/dist/resources/extensions/gsd/worktree-resolver.js +19 -2
- package/dist/resources/extensions/subagent/index.js +7 -3
- package/dist/resources/extensions/voice/index.js +4 -4
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- 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/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +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 +16 -16
- package/dist/web/standalone/.next/server/chunks/229.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- 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/dist/web/standalone/.next/static/chunks/4024.c195dc1fdd2adbea.js +9 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-9afaaebf6042a1d7.js → webpack-fa307370fcf9fb2c.js} +1 -1
- package/dist/web-mode.d.ts +2 -0
- package/dist/web-mode.js +29 -7
- package/package.json +1 -1
- package/packages/native/src/__tests__/text.test.mjs +33 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +3 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +10 -7
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +4 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +11 -7
- package/src/resources/extensions/gsd/auto/loop-deps.ts +5 -1
- package/src/resources/extensions/gsd/auto/loop.ts +10 -1
- package/src/resources/extensions/gsd/auto/phases.ts +28 -8
- package/src/resources/extensions/gsd/auto/types.ts +4 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +7 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +25 -5
- package/src/resources/extensions/gsd/auto-post-unit.ts +8 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +12 -4
- package/src/resources/extensions/gsd/auto-start.ts +8 -3
- package/src/resources/extensions/gsd/auto-worktree.ts +162 -18
- package/src/resources/extensions/gsd/auto.ts +40 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +209 -162
- package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +62 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +8 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/context-store.ts +4 -3
- package/src/resources/extensions/gsd/db-writer.ts +6 -2
- package/src/resources/extensions/gsd/detection.ts +1 -1
- package/src/resources/extensions/gsd/doctor.ts +12 -1
- package/src/resources/extensions/gsd/exit-command.ts +14 -2
- package/src/resources/extensions/gsd/export.ts +8 -15
- package/src/resources/extensions/gsd/extension-manifest.json +2 -2
- package/src/resources/extensions/gsd/files.ts +29 -12
- package/src/resources/extensions/gsd/forensics.ts +9 -3
- package/src/resources/extensions/gsd/git-service.ts +5 -4
- package/src/resources/extensions/gsd/gsd-db.ts +37 -8
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +7 -3
- package/src/resources/extensions/gsd/journal.ts +134 -0
- package/src/resources/extensions/gsd/md-importer.ts +6 -0
- package/src/resources/extensions/gsd/milestone-ids.ts +1 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +2 -2
- package/src/resources/extensions/gsd/post-unit-hooks.ts +24 -462
- package/src/resources/extensions/gsd/preferences-types.ts +3 -0
- package/src/resources/extensions/gsd/preferences.ts +1 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +35 -4
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
- package/src/resources/extensions/gsd/prompts/discuss.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +1 -1
- package/src/resources/extensions/gsd/repo-identity.ts +47 -2
- package/src/resources/extensions/gsd/rule-registry.ts +599 -0
- package/src/resources/extensions/gsd/rule-types.ts +68 -0
- package/src/resources/extensions/gsd/service-tier.ts +171 -0
- package/src/resources/extensions/gsd/structured-data-formatter.ts +3 -1
- package/src/resources/extensions/gsd/templates/decisions.md +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +103 -120
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +202 -0
- package/src/resources/extensions/gsd/tests/captures.test.ts +12 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +10 -5
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +20 -20
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +15 -10
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +5 -4
- package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/doctor-task-done-missing-summary-slice-loop.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +513 -0
- package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +147 -0
- package/src/resources/extensions/gsd/tests/journal.test.ts +386 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/milestone-id-reservation.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parsers.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +47 -25
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +61 -1
- package/src/resources/extensions/gsd/tests/routing-history.test.ts +11 -22
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +413 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +178 -0
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +195 -105
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +78 -3
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +74 -0
- package/src/resources/extensions/gsd/types.ts +3 -0
- package/src/resources/extensions/gsd/workflow-templates.ts +12 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +21 -6
- package/src/resources/extensions/gsd/worktree-resolver.ts +30 -9
- package/src/resources/extensions/subagent/index.ts +7 -3
- package/src/resources/extensions/voice/index.ts +4 -4
- package/dist/web/standalone/.next/static/chunks/4024.279c423e4661ece1.js +0 -9
- /package/dist/web/standalone/.next/static/{02cti5IXH7FycOqkbAkWL → ZrI3HOoXD7Fh84fAHZVxb}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{02cti5IXH7FycOqkbAkWL → ZrI3HOoXD7Fh84fAHZVxb}/_ssgManifest.js +0 -0
|
@@ -386,6 +386,91 @@ test("verifyExpectedArtifact plan-slice fails for plan with no tasks (#699)", ()
|
|
|
386
386
|
}
|
|
387
387
|
});
|
|
388
388
|
|
|
389
|
+
// ─── verifyExpectedArtifact: heading-style plan tasks (#1691) ─────────────
|
|
390
|
+
|
|
391
|
+
test("verifyExpectedArtifact accepts plan-slice with heading-style tasks (### T01 --)", () => {
|
|
392
|
+
const base = makeTmpBase();
|
|
393
|
+
try {
|
|
394
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
395
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
396
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
397
|
+
writeFileSync(join(sliceDir, "S01-PLAN.md"), [
|
|
398
|
+
"# S01: Test Slice",
|
|
399
|
+
"",
|
|
400
|
+
"## Tasks",
|
|
401
|
+
"",
|
|
402
|
+
"### T01 -- Implement feature",
|
|
403
|
+
"",
|
|
404
|
+
"Feature description.",
|
|
405
|
+
"",
|
|
406
|
+
"### T02 -- Write tests",
|
|
407
|
+
"",
|
|
408
|
+
"Test description.",
|
|
409
|
+
].join("\n"));
|
|
410
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
|
|
411
|
+
writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan");
|
|
412
|
+
assert.strictEqual(
|
|
413
|
+
verifyExpectedArtifact("plan-slice", "M001/S01", base),
|
|
414
|
+
true,
|
|
415
|
+
"Heading-style plan with task entries should be treated as completed artifact",
|
|
416
|
+
);
|
|
417
|
+
} finally {
|
|
418
|
+
cleanup(base);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test("verifyExpectedArtifact accepts plan-slice with colon-style heading tasks (### T01:)", () => {
|
|
423
|
+
const base = makeTmpBase();
|
|
424
|
+
try {
|
|
425
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
426
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
427
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
428
|
+
writeFileSync(join(sliceDir, "S01-PLAN.md"), [
|
|
429
|
+
"# S01: Test Slice",
|
|
430
|
+
"",
|
|
431
|
+
"## Tasks",
|
|
432
|
+
"",
|
|
433
|
+
"### T01: Implement feature",
|
|
434
|
+
"",
|
|
435
|
+
"Feature description.",
|
|
436
|
+
].join("\n"));
|
|
437
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
|
|
438
|
+
assert.strictEqual(
|
|
439
|
+
verifyExpectedArtifact("plan-slice", "M001/S01", base),
|
|
440
|
+
true,
|
|
441
|
+
"Colon heading-style plan should be treated as completed artifact",
|
|
442
|
+
);
|
|
443
|
+
} finally {
|
|
444
|
+
cleanup(base);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
test("verifyExpectedArtifact execute-task passes for heading-style plan entry (#1691)", () => {
|
|
449
|
+
const base = makeTmpBase();
|
|
450
|
+
try {
|
|
451
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
452
|
+
const tasksDir = join(sliceDir, "tasks");
|
|
453
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
454
|
+
writeFileSync(join(sliceDir, "S01-PLAN.md"), [
|
|
455
|
+
"# S01: Test Slice",
|
|
456
|
+
"",
|
|
457
|
+
"## Tasks",
|
|
458
|
+
"",
|
|
459
|
+
"### T01 -- Implement feature",
|
|
460
|
+
"",
|
|
461
|
+
"Feature description.",
|
|
462
|
+
].join("\n"));
|
|
463
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "# T01 Summary\n\nDone.");
|
|
464
|
+
assert.strictEqual(
|
|
465
|
+
verifyExpectedArtifact("execute-task", "M001/S01/T01", base),
|
|
466
|
+
true,
|
|
467
|
+
"execute-task should pass for heading-style plan entry when summary exists",
|
|
468
|
+
);
|
|
469
|
+
} finally {
|
|
470
|
+
cleanup(base);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
389
474
|
// ─── selfHealRuntimeRecords — worktree base path (#769) ──────────────────
|
|
390
475
|
|
|
391
476
|
test("selfHealRuntimeRecords clears stale dispatched records (#769)", async () => {
|
|
@@ -101,8 +101,8 @@ test('secrets gate: pending keys exist — gate triggers collection, manifest up
|
|
|
101
101
|
const status = await getManifestStatus(tmp, 'M001');
|
|
102
102
|
assert.notStrictEqual(status, null, 'manifest should exist');
|
|
103
103
|
assert.ok(status!.pending.length > 0, 'should have pending keys');
|
|
104
|
-
assert.deepStrictEqual(status!.pending, ['GSD_GATE_TEST_PEND_A', 'GSD_GATE_TEST_PEND_B']);
|
|
105
|
-
assert.deepStrictEqual(status!.existing, ['GSD_GATE_TEST_EXISTING']);
|
|
104
|
+
assert.deepStrictEqual(status!.pending, ['GSD_GATE_TEST_PEND_A', 'GSD_GATE_TEST_PEND_B'], 'pending keys');
|
|
105
|
+
assert.deepStrictEqual(status!.existing, ['GSD_GATE_TEST_EXISTING'], 'existing keys');
|
|
106
106
|
|
|
107
107
|
// (b) Call collectSecretsFromManifest with no-UI context
|
|
108
108
|
// With hasUI: false, collectOneSecret returns null → pending keys become "skipped"
|
|
@@ -569,6 +569,208 @@ async function main(): Promise<void> {
|
|
|
569
569
|
assertTrue(existsSync(join(repo, "landed.ts")), "landed.ts present on main");
|
|
570
570
|
}
|
|
571
571
|
|
|
572
|
+
// ─── Test 14: Stale branch ref — worktree HEAD ahead of branch (#1846) ─
|
|
573
|
+
console.log("\n=== stale branch ref — fast-forward before squash merge (#1846) ===");
|
|
574
|
+
{
|
|
575
|
+
const repo = freshRepo();
|
|
576
|
+
const wtPath = createAutoWorktree(repo, "M140");
|
|
577
|
+
|
|
578
|
+
// Add a first slice normally — this advances both the branch ref and HEAD
|
|
579
|
+
addSliceToMilestone(repo, wtPath, "M140", "S01", "Initial work", [
|
|
580
|
+
{ file: "initial.ts", content: "export const initial = true;\n", message: "add initial" },
|
|
581
|
+
]);
|
|
582
|
+
|
|
583
|
+
// Now simulate the bug: detach HEAD in the worktree, then make commits
|
|
584
|
+
// that advance HEAD but leave the milestone/M140 branch ref behind.
|
|
585
|
+
const branchRefBefore = run("git rev-parse milestone/M140", wtPath);
|
|
586
|
+
run("git checkout --detach HEAD", wtPath);
|
|
587
|
+
|
|
588
|
+
// Add multiple commits on the detached HEAD (simulates agent work)
|
|
589
|
+
writeFileSync(join(wtPath, "feature-a.ts"), "export const featureA = true;\n");
|
|
590
|
+
run("git add .", wtPath);
|
|
591
|
+
run('git commit -m "add feature-a"', wtPath);
|
|
592
|
+
|
|
593
|
+
writeFileSync(join(wtPath, "feature-b.ts"), "export const featureB = true;\n");
|
|
594
|
+
run("git add .", wtPath);
|
|
595
|
+
run('git commit -m "add feature-b"', wtPath);
|
|
596
|
+
|
|
597
|
+
writeFileSync(join(wtPath, "feature-c.ts"), "export const featureC = true;\n");
|
|
598
|
+
run("git add .", wtPath);
|
|
599
|
+
run('git commit -m "add feature-c"', wtPath);
|
|
600
|
+
|
|
601
|
+
// Verify: branch ref is stale, HEAD is ahead
|
|
602
|
+
const branchRefAfter = run("git rev-parse milestone/M140", wtPath);
|
|
603
|
+
const worktreeHead = run("git rev-parse HEAD", wtPath);
|
|
604
|
+
assertEq(branchRefBefore, branchRefAfter, "branch ref unchanged (stale)");
|
|
605
|
+
assertTrue(worktreeHead !== branchRefAfter, "worktree HEAD ahead of branch ref");
|
|
606
|
+
|
|
607
|
+
const roadmap = makeRoadmap("M140", "Stale ref milestone", [
|
|
608
|
+
{ id: "S01", title: "Initial work" },
|
|
609
|
+
]);
|
|
610
|
+
|
|
611
|
+
// The fix should fast-forward the branch ref to worktree HEAD before
|
|
612
|
+
// squash-merging, so ALL commits are captured.
|
|
613
|
+
let threw = false;
|
|
614
|
+
let errMsg = "";
|
|
615
|
+
try {
|
|
616
|
+
const result = mergeMilestoneToMain(repo, "M140", roadmap);
|
|
617
|
+
assertTrue(result.commitMessage.includes("feat(M140)"), "merge commit created");
|
|
618
|
+
} catch (err) {
|
|
619
|
+
threw = true;
|
|
620
|
+
errMsg = err instanceof Error ? err.message : String(err);
|
|
621
|
+
}
|
|
622
|
+
assertTrue(!threw, `should not throw with stale branch ref (got: ${errMsg})`);
|
|
623
|
+
|
|
624
|
+
// ALL files from detached HEAD commits must be on main — not just
|
|
625
|
+
// the ones from the stale branch ref
|
|
626
|
+
assertTrue(existsSync(join(repo, "initial.ts")), "initial.ts on main");
|
|
627
|
+
assertTrue(existsSync(join(repo, "feature-a.ts")), "feature-a.ts on main (#1846)");
|
|
628
|
+
assertTrue(existsSync(join(repo, "feature-b.ts")), "feature-b.ts on main (#1846)");
|
|
629
|
+
assertTrue(existsSync(join(repo, "feature-c.ts")), "feature-c.ts on main (#1846)");
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// ─── Test 15: Diverged worktree HEAD — throws instead of losing data (#1846) ─
|
|
633
|
+
console.log("\n=== diverged worktree HEAD — throws on divergence (#1846) ===");
|
|
634
|
+
{
|
|
635
|
+
const repo = freshRepo();
|
|
636
|
+
const wtPath = createAutoWorktree(repo, "M150");
|
|
637
|
+
|
|
638
|
+
addSliceToMilestone(repo, wtPath, "M150", "S01", "Base work", [
|
|
639
|
+
{ file: "base.ts", content: "export const base = true;\n", message: "add base" },
|
|
640
|
+
]);
|
|
641
|
+
|
|
642
|
+
run("git checkout --detach HEAD", wtPath);
|
|
643
|
+
writeFileSync(join(wtPath, "detached-work.ts"), "export const detached = true;\n");
|
|
644
|
+
run("git add .", wtPath);
|
|
645
|
+
run('git commit -m "detached work"', wtPath);
|
|
646
|
+
|
|
647
|
+
run("git checkout milestone/M150", repo);
|
|
648
|
+
writeFileSync(join(repo, "diverged-work.ts"), "export const diverged = true;\n");
|
|
649
|
+
run("git add .", repo);
|
|
650
|
+
run('git commit -m "diverged work on branch"', repo);
|
|
651
|
+
run("git checkout main", repo);
|
|
652
|
+
|
|
653
|
+
process.chdir(wtPath);
|
|
654
|
+
|
|
655
|
+
const roadmap = makeRoadmap("M150", "Diverged milestone", [
|
|
656
|
+
{ id: "S01", title: "Base work" },
|
|
657
|
+
]);
|
|
658
|
+
|
|
659
|
+
let threw = false;
|
|
660
|
+
let errMsg = "";
|
|
661
|
+
try {
|
|
662
|
+
mergeMilestoneToMain(repo, "M150", roadmap);
|
|
663
|
+
} catch (err) {
|
|
664
|
+
threw = true;
|
|
665
|
+
errMsg = err instanceof Error ? err.message : String(err);
|
|
666
|
+
}
|
|
667
|
+
assertTrue(threw, "throws when worktree HEAD diverged from branch ref (#1846)");
|
|
668
|
+
assertTrue(errMsg.includes("diverged"), "error message mentions divergence (#1846)");
|
|
669
|
+
|
|
670
|
+
const branches = run("git branch", repo);
|
|
671
|
+
assertTrue(branches.includes("milestone/M150"), "milestone branch preserved on divergence (#1846)");
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// ─── Test 16: #1853 Bug 1 — SQUASH_MSG cleaned up after squash-merge ──
|
|
675
|
+
console.log("\n=== #1853 bug 1: SQUASH_MSG cleaned up after successful squash-merge ===");
|
|
676
|
+
{
|
|
677
|
+
const repo = freshRepo();
|
|
678
|
+
const wtPath = createAutoWorktree(repo, "M160");
|
|
679
|
+
|
|
680
|
+
addSliceToMilestone(repo, wtPath, "M160", "S01", "SQUASH_MSG cleanup test", [
|
|
681
|
+
{ file: "squash-cleanup.ts", content: "export const cleanup = true;\n", message: "add squash-cleanup" },
|
|
682
|
+
]);
|
|
683
|
+
|
|
684
|
+
const roadmap = makeRoadmap("M160", "SQUASH_MSG cleanup", [
|
|
685
|
+
{ id: "S01", title: "SQUASH_MSG cleanup test" },
|
|
686
|
+
]);
|
|
687
|
+
|
|
688
|
+
const squashMsgPath = join(repo, ".git", "SQUASH_MSG");
|
|
689
|
+
writeFileSync(squashMsgPath, "leftover squash message\n");
|
|
690
|
+
assertTrue(existsSync(squashMsgPath), "SQUASH_MSG planted before merge");
|
|
691
|
+
|
|
692
|
+
const result = mergeMilestoneToMain(repo, "M160", roadmap);
|
|
693
|
+
assertTrue(result.commitMessage.includes("feat(M160)"), "merge commit created");
|
|
694
|
+
|
|
695
|
+
assertTrue(
|
|
696
|
+
!existsSync(squashMsgPath),
|
|
697
|
+
"#1853: SQUASH_MSG must not persist after successful squash-merge",
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// ─── Test 17: #1853 Bug 2 — uncommitted worktree code survives teardown ──
|
|
702
|
+
console.log("\n=== #1853 bug 2: uncommitted worktree changes committed before teardown ===");
|
|
703
|
+
{
|
|
704
|
+
const repo = freshRepo();
|
|
705
|
+
const wtPath = createAutoWorktree(repo, "M170");
|
|
706
|
+
|
|
707
|
+
addSliceToMilestone(repo, wtPath, "M170", "S01", "Teardown safety test", [
|
|
708
|
+
{ file: "safe-file.ts", content: "export const safe = true;\n", message: "add safe file" },
|
|
709
|
+
]);
|
|
710
|
+
|
|
711
|
+
writeFileSync(join(wtPath, "uncommitted-agent-code.ts"), "export const lost = true;\n");
|
|
712
|
+
|
|
713
|
+
const roadmap = makeRoadmap("M170", "Teardown safety", [
|
|
714
|
+
{ id: "S01", title: "Teardown safety test" },
|
|
715
|
+
]);
|
|
716
|
+
|
|
717
|
+
const result = mergeMilestoneToMain(repo, "M170", roadmap);
|
|
718
|
+
assertTrue(result.commitMessage.includes("feat(M170)"), "merge commit created");
|
|
719
|
+
|
|
720
|
+
assertTrue(
|
|
721
|
+
existsSync(join(repo, "uncommitted-agent-code.ts")),
|
|
722
|
+
"#1853: uncommitted worktree code must survive teardown",
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// ─── Test 18: #1906 — codeFilesChanged false when only .gsd/ metadata merged ──
|
|
727
|
+
console.log("\n=== #1906: codeFilesChanged=false when only .gsd/ metadata merged ===");
|
|
728
|
+
{
|
|
729
|
+
const repo = freshRepo();
|
|
730
|
+
const wtPath = createAutoWorktree(repo, "M180");
|
|
731
|
+
|
|
732
|
+
// Only add .gsd/ metadata files — no actual code
|
|
733
|
+
mkdirSync(join(wtPath, ".gsd", "milestones", "M180"), { recursive: true });
|
|
734
|
+
writeFileSync(
|
|
735
|
+
join(wtPath, ".gsd", "milestones", "M180", "SUMMARY.md"),
|
|
736
|
+
"# M180 Summary\n\nThis milestone was planned but not implemented.\n",
|
|
737
|
+
);
|
|
738
|
+
run("git add .", wtPath);
|
|
739
|
+
run('git commit -m "chore: add milestone summary"', wtPath);
|
|
740
|
+
|
|
741
|
+
const roadmap = makeRoadmap("M180", "Metadata-only milestone", []);
|
|
742
|
+
|
|
743
|
+
const result = mergeMilestoneToMain(repo, "M180", roadmap);
|
|
744
|
+
assertEq(
|
|
745
|
+
result.codeFilesChanged,
|
|
746
|
+
false,
|
|
747
|
+
"#1906: codeFilesChanged must be false when only .gsd/ files were merged",
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// ─── Test 19: #1906 — codeFilesChanged true when real code is merged ──
|
|
752
|
+
console.log("\n=== #1906: codeFilesChanged=true when real code is merged ===");
|
|
753
|
+
{
|
|
754
|
+
const repo = freshRepo();
|
|
755
|
+
const wtPath = createAutoWorktree(repo, "M190");
|
|
756
|
+
|
|
757
|
+
addSliceToMilestone(repo, wtPath, "M190", "S01", "Real code", [
|
|
758
|
+
{ file: "real-code.ts", content: "export const real = true;\n", message: "add real code" },
|
|
759
|
+
]);
|
|
760
|
+
|
|
761
|
+
const roadmap = makeRoadmap("M190", "Code milestone", [
|
|
762
|
+
{ id: "S01", title: "Real code" },
|
|
763
|
+
]);
|
|
764
|
+
|
|
765
|
+
const result = mergeMilestoneToMain(repo, "M190", roadmap);
|
|
766
|
+
assertEq(
|
|
767
|
+
result.codeFilesChanged,
|
|
768
|
+
true,
|
|
769
|
+
"#1906: codeFilesChanged must be true when real code files were merged",
|
|
770
|
+
);
|
|
771
|
+
assertTrue(existsSync(join(repo, "real-code.ts")), "real-code.ts merged to main");
|
|
772
|
+
}
|
|
773
|
+
|
|
572
774
|
} finally {
|
|
573
775
|
process.chdir(savedCwd);
|
|
574
776
|
for (const d of tempDirs) {
|
|
@@ -119,12 +119,23 @@ test("captures: loadPendingCaptures filters resolved entries", () => {
|
|
|
119
119
|
const id1 = appendCapture(tmp, "pending one");
|
|
120
120
|
appendCapture(tmp, "pending two");
|
|
121
121
|
|
|
122
|
-
// Resolve the first one
|
|
123
122
|
markCaptureResolved(tmp, id1, "note", "acknowledged", "just a note");
|
|
124
123
|
|
|
125
124
|
const pending = loadPendingCaptures(tmp);
|
|
126
125
|
assert.strictEqual(pending.length, 1, "should have 1 pending");
|
|
127
126
|
assert.strictEqual(pending[0].text, "pending two");
|
|
127
|
+
} finally {
|
|
128
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("captures: loadAllCaptures preserves resolved entries", () => {
|
|
133
|
+
const tmp = makeTempDir("cap-all-resolved");
|
|
134
|
+
try {
|
|
135
|
+
const id1 = appendCapture(tmp, "pending one");
|
|
136
|
+
appendCapture(tmp, "pending two");
|
|
137
|
+
|
|
138
|
+
markCaptureResolved(tmp, id1, "note", "acknowledged", "just a note");
|
|
128
139
|
|
|
129
140
|
const all = loadAllCaptures(tmp);
|
|
130
141
|
assert.strictEqual(all.length, 2, "all should still have 2");
|
|
@@ -51,17 +51,17 @@ console.log('\n=== context-store: query all active decisions ===');
|
|
|
51
51
|
insertDecision({
|
|
52
52
|
id: 'D001', when_context: 'M001/S01', scope: 'architecture',
|
|
53
53
|
decision: 'use SQLite', choice: 'node:sqlite', rationale: 'built-in',
|
|
54
|
-
revisable: 'yes', superseded_by: 'D003', // superseded!
|
|
54
|
+
revisable: 'yes', made_by: 'agent', superseded_by: 'D003', // superseded!
|
|
55
55
|
});
|
|
56
56
|
insertDecision({
|
|
57
57
|
id: 'D002', when_context: 'M001/S01', scope: 'architecture',
|
|
58
58
|
decision: 'use WAL mode', choice: 'WAL', rationale: 'concurrent reads',
|
|
59
|
-
revisable: 'no', superseded_by: null,
|
|
59
|
+
revisable: 'no', made_by: 'agent', superseded_by: null,
|
|
60
60
|
});
|
|
61
61
|
insertDecision({
|
|
62
62
|
id: 'D003', when_context: 'M002/S01', scope: 'performance',
|
|
63
63
|
decision: 'use better-sqlite3', choice: 'better-sqlite3', rationale: 'faster',
|
|
64
|
-
revisable: 'yes', superseded_by: null,
|
|
64
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
const all = queryDecisions();
|
|
@@ -81,11 +81,13 @@ console.log('\n=== context-store: query decisions by milestone ===');
|
|
|
81
81
|
insertDecision({
|
|
82
82
|
id: 'D001', when_context: 'M001/S01', scope: 'architecture',
|
|
83
83
|
decision: 'decision A', choice: 'A', rationale: 'r', revisable: 'yes',
|
|
84
|
+
made_by: 'agent',
|
|
84
85
|
superseded_by: null,
|
|
85
86
|
});
|
|
86
87
|
insertDecision({
|
|
87
88
|
id: 'D002', when_context: 'M002/S02', scope: 'architecture',
|
|
88
89
|
decision: 'decision B', choice: 'B', rationale: 'r', revisable: 'yes',
|
|
90
|
+
made_by: 'agent',
|
|
89
91
|
superseded_by: null,
|
|
90
92
|
});
|
|
91
93
|
|
|
@@ -107,11 +109,13 @@ console.log('\n=== context-store: query decisions by scope ===');
|
|
|
107
109
|
insertDecision({
|
|
108
110
|
id: 'D001', when_context: 'M001/S01', scope: 'architecture',
|
|
109
111
|
decision: 'decision A', choice: 'A', rationale: 'r', revisable: 'yes',
|
|
112
|
+
made_by: 'agent',
|
|
110
113
|
superseded_by: null,
|
|
111
114
|
});
|
|
112
115
|
insertDecision({
|
|
113
116
|
id: 'D002', when_context: 'M001/S01', scope: 'performance',
|
|
114
117
|
decision: 'decision B', choice: 'B', rationale: 'r', revisable: 'yes',
|
|
118
|
+
made_by: 'agent',
|
|
115
119
|
superseded_by: null,
|
|
116
120
|
});
|
|
117
121
|
|
|
@@ -248,12 +252,12 @@ console.log('\n=== context-store: formatDecisionsForPrompt ===');
|
|
|
248
252
|
{
|
|
249
253
|
seq: 1, id: 'D001', when_context: 'M001/S01', scope: 'architecture',
|
|
250
254
|
decision: 'use SQLite', choice: 'node:sqlite', rationale: 'built-in',
|
|
251
|
-
revisable: 'yes', superseded_by: null,
|
|
255
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
252
256
|
},
|
|
253
257
|
{
|
|
254
258
|
seq: 2, id: 'D002', when_context: 'M001/S02', scope: 'performance',
|
|
255
259
|
decision: 'use WAL', choice: 'WAL', rationale: 'concurrent',
|
|
256
|
-
revisable: 'no', superseded_by: null,
|
|
260
|
+
revisable: 'no', made_by: 'human', superseded_by: null,
|
|
257
261
|
},
|
|
258
262
|
]);
|
|
259
263
|
|
|
@@ -323,6 +327,7 @@ console.log('\n=== context-store: sub-5ms query timing ===');
|
|
|
323
327
|
choice: `choice ${i}`,
|
|
324
328
|
rationale: `rationale ${i}`,
|
|
325
329
|
revisable: i % 3 === 0 ? 'no' : 'yes',
|
|
330
|
+
made_by: 'agent',
|
|
326
331
|
superseded_by: null,
|
|
327
332
|
});
|
|
328
333
|
}
|
|
@@ -72,18 +72,17 @@ describe("continue-here", () => {
|
|
|
72
72
|
const budget = computeBudgets(128_000);
|
|
73
73
|
const threshold = budget.continueThresholdPercent;
|
|
74
74
|
|
|
75
|
-
// Simulate repeated polls with percent above threshold
|
|
76
|
-
|
|
77
|
-
let fireCount = 0;
|
|
75
|
+
// Simulate repeated polls with percent above threshold using a reducer
|
|
76
|
+
// so there is no control flow inside the test body.
|
|
78
77
|
const usagePercents = [75, 80, 85, 90, 95];
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
78
|
+
const { fired, fireCount } = usagePercents.reduce(
|
|
79
|
+
(acc, percent) => {
|
|
80
|
+
if (acc.fired) return acc; // one-shot guard
|
|
81
|
+
if (percent >= threshold) return { fired: true, fireCount: acc.fireCount + 1 };
|
|
82
|
+
return acc;
|
|
83
|
+
},
|
|
84
|
+
{ fired: false, fireCount: 0 },
|
|
85
|
+
);
|
|
87
86
|
|
|
88
87
|
assert.equal(fireCount, 1, "must fire exactly once");
|
|
89
88
|
assert.equal(fired, true);
|
|
@@ -97,16 +96,17 @@ describe("continue-here", () => {
|
|
|
97
96
|
{ name: "1M", contextWindow: 1_000_000 },
|
|
98
97
|
];
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
const thresholdCases: Array<[string, number]> = [
|
|
100
|
+
["128K", 128_000],
|
|
101
|
+
["200K", 200_000],
|
|
102
|
+
["1M", 1_000_000],
|
|
103
|
+
];
|
|
104
|
+
for (const [name, contextWindow] of thresholdCases) {
|
|
105
|
+
it(`${name} model produces continueThresholdPercent of 70`, () => {
|
|
102
106
|
const budget = computeBudgets(contextWindow);
|
|
103
|
-
assert.equal(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
`${name} model should have 70% threshold`,
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
107
|
+
assert.equal(budget.continueThresholdPercent, 70, `${name} model should have 70% threshold`);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
110
|
|
|
111
111
|
it("larger models produce larger verificationBudgetChars", () => {
|
|
112
112
|
const budgets = modelSizes.map(({ contextWindow }) => computeBudgets(contextWindow));
|
|
@@ -59,6 +59,7 @@ const SAMPLE_DECISIONS: Decision[] = [
|
|
|
59
59
|
choice: 'better-sqlite3',
|
|
60
60
|
rationale: 'Sync API',
|
|
61
61
|
revisable: 'No',
|
|
62
|
+
made_by: 'collaborative',
|
|
62
63
|
superseded_by: null,
|
|
63
64
|
},
|
|
64
65
|
{
|
|
@@ -70,6 +71,7 @@ const SAMPLE_DECISIONS: Decision[] = [
|
|
|
70
71
|
choice: '.gsd/gsd.db',
|
|
71
72
|
rationale: 'Derived state',
|
|
72
73
|
revisable: 'No',
|
|
74
|
+
made_by: 'agent',
|
|
73
75
|
superseded_by: null,
|
|
74
76
|
},
|
|
75
77
|
{
|
|
@@ -81,6 +83,7 @@ const SAMPLE_DECISIONS: Decision[] = [
|
|
|
81
83
|
choice: 'node:sqlite fallback',
|
|
82
84
|
rationale: 'Zero deps',
|
|
83
85
|
revisable: 'Yes',
|
|
86
|
+
made_by: 'human',
|
|
84
87
|
superseded_by: null,
|
|
85
88
|
},
|
|
86
89
|
];
|
|
@@ -166,6 +169,7 @@ console.log('\n── generateDecisionsMd round-trip ──');
|
|
|
166
169
|
assertEq(rt.choice, orig.choice, `decision ${orig.id} choice round-trips`);
|
|
167
170
|
assertEq(rt.rationale, orig.rationale, `decision ${orig.id} rationale round-trips`);
|
|
168
171
|
assertEq(rt.revisable, orig.revisable, `decision ${orig.id} revisable round-trips`);
|
|
172
|
+
assertEq(rt.made_by, orig.made_by, `decision ${orig.id} made_by round-trips`);
|
|
169
173
|
}
|
|
170
174
|
}
|
|
171
175
|
|
|
@@ -177,6 +181,7 @@ console.log('\n── generateDecisionsMd format ──');
|
|
|
177
181
|
assertTrue(md.includes('<!-- Append-only'), 'contains HTML comment block');
|
|
178
182
|
assertTrue(md.includes('| # | When | Scope'), 'contains table header');
|
|
179
183
|
assertTrue(md.includes('|---|------|-------'), 'contains separator row');
|
|
184
|
+
assertTrue(md.includes('| Made By |'), 'contains Made By column header');
|
|
180
185
|
}
|
|
181
186
|
|
|
182
187
|
console.log('\n── generateDecisionsMd empty input ──');
|
|
@@ -200,6 +205,7 @@ console.log('\n── generateDecisionsMd pipe escaping ──');
|
|
|
200
205
|
choice: 'A',
|
|
201
206
|
rationale: 'Better',
|
|
202
207
|
revisable: 'No',
|
|
208
|
+
made_by: 'agent',
|
|
203
209
|
superseded_by: null,
|
|
204
210
|
};
|
|
205
211
|
const md = generateDecisionsMd([withPipe]);
|
|
@@ -291,6 +297,7 @@ console.log('\n── nextDecisionId ──');
|
|
|
291
297
|
choice: 'test choice',
|
|
292
298
|
rationale: 'test',
|
|
293
299
|
revisable: 'No',
|
|
300
|
+
made_by: 'agent',
|
|
294
301
|
superseded_by: null,
|
|
295
302
|
});
|
|
296
303
|
upsertDecision({
|
|
@@ -301,6 +308,7 @@ console.log('\n── nextDecisionId ──');
|
|
|
301
308
|
choice: 'test choice',
|
|
302
309
|
rationale: 'test',
|
|
303
310
|
revisable: 'No',
|
|
311
|
+
made_by: 'agent',
|
|
304
312
|
superseded_by: null,
|
|
305
313
|
});
|
|
306
314
|
|
|
@@ -520,6 +528,7 @@ console.log('\n── Full DB round-trip: decisions ──');
|
|
|
520
528
|
choice: d.choice,
|
|
521
529
|
rationale: d.rationale,
|
|
522
530
|
revisable: d.revisable,
|
|
531
|
+
made_by: d.made_by,
|
|
523
532
|
superseded_by: d.superseded_by,
|
|
524
533
|
});
|
|
525
534
|
}
|
|
@@ -536,6 +545,7 @@ console.log('\n── Full DB round-trip: decisions ──');
|
|
|
536
545
|
choice: row['choice'] as string,
|
|
537
546
|
rationale: row['rationale'] as string,
|
|
538
547
|
revisable: row['revisable'] as string,
|
|
548
|
+
made_by: (row['made_by'] as string as import('../types.js').DecisionMadeBy) ?? 'agent',
|
|
539
549
|
superseded_by: (row['superseded_by'] as string) ?? null,
|
|
540
550
|
}));
|
|
541
551
|
|
|
@@ -80,7 +80,7 @@ test("COMPLETION_TRANSITION_CODES only contains slice summary code", () => {
|
|
|
80
80
|
);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
test("fixLevel:task — fixes
|
|
83
|
+
test("fixLevel:task — fixes UAT stub immediately, defers summary and roadmap checkbox (#1808, #1910)", async () => {
|
|
84
84
|
const tmp = makeTmp("partial-deferral");
|
|
85
85
|
try {
|
|
86
86
|
buildScaffold(tmp);
|
|
@@ -101,15 +101,16 @@ test("fixLevel:task — fixes roadmap checkbox and UAT stub immediately, defers
|
|
|
101
101
|
const sliceUatPath = join(tmp, ".gsd", "milestones", "M001", "slices", "S01", "S01-UAT.md");
|
|
102
102
|
assert.ok(existsSync(sliceUatPath), "should have created UAT stub immediately");
|
|
103
103
|
|
|
104
|
-
// Roadmap checkbox
|
|
104
|
+
// Roadmap checkbox must NOT be checked without summary on disk (#1910).
|
|
105
|
+
// Checking it without the summary causes deriveState() to skip complete-slice.
|
|
105
106
|
const roadmapContent = readFileSync(join(tmp, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "utf8");
|
|
106
|
-
assert.ok(roadmapContent.includes("- [
|
|
107
|
+
assert.ok(roadmapContent.includes("- [ ] **S01"), "roadmap must NOT be checked without summary on disk (#1910)");
|
|
107
108
|
} finally {
|
|
108
109
|
rmSync(tmp, { recursive: true, force: true });
|
|
109
110
|
}
|
|
110
111
|
});
|
|
111
112
|
|
|
112
|
-
test("fixLevel:task — session crash after last task leaves roadmap
|
|
113
|
+
test("fixLevel:task — session crash after last task leaves UAT consistent, roadmap deferred with summary (#1808, #1910)", async () => {
|
|
113
114
|
const tmp = makeTmp("crash-consistency");
|
|
114
115
|
try {
|
|
115
116
|
buildScaffold(tmp);
|
|
@@ -121,13 +122,7 @@ test("fixLevel:task — session crash after last task leaves roadmap and UAT con
|
|
|
121
122
|
// A new session starts and runs doctor again at task level.
|
|
122
123
|
const report2 = await runGSDDoctor(tmp, { fix: true, fixLevel: "task" });
|
|
123
124
|
|
|
124
|
-
// The only remaining issue should be the deferred summary.
|
|
125
|
-
// Roadmap and UAT should already be fixed from the first run.
|
|
126
125
|
const remainingCodes = report2.issues.map(i => i.code);
|
|
127
|
-
assert.ok(
|
|
128
|
-
!remainingCodes.includes("all_tasks_done_roadmap_not_checked"),
|
|
129
|
-
"roadmap should already be fixed from first doctor run"
|
|
130
|
-
);
|
|
131
126
|
assert.ok(
|
|
132
127
|
!remainingCodes.includes("all_tasks_done_missing_slice_uat"),
|
|
133
128
|
"UAT should already be fixed from first doctor run"
|
|
@@ -137,6 +132,16 @@ test("fixLevel:task — session crash after last task leaves roadmap and UAT con
|
|
|
137
132
|
remainingCodes.includes("all_tasks_done_missing_slice_summary"),
|
|
138
133
|
"summary should still be detected as missing (deferred)"
|
|
139
134
|
);
|
|
135
|
+
// Roadmap should still be unchecked because summary doesn't exist (#1910)
|
|
136
|
+
assert.ok(
|
|
137
|
+
remainingCodes.includes("all_tasks_done_roadmap_not_checked"),
|
|
138
|
+
"roadmap should still be unchecked — summary does not exist on disk (#1910)"
|
|
139
|
+
);
|
|
140
|
+
// Must NOT produce the cascade error from checking roadmap without summary
|
|
141
|
+
assert.ok(
|
|
142
|
+
!remainingCodes.includes("slice_checked_missing_summary"),
|
|
143
|
+
"must not produce slice_checked_missing_summary (#1910)"
|
|
144
|
+
);
|
|
140
145
|
} finally {
|
|
141
146
|
rmSync(tmp, { recursive: true, force: true });
|
|
142
147
|
}
|
|
@@ -63,7 +63,7 @@ Done.
|
|
|
63
63
|
`);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
test("fixLevel:task — defers
|
|
66
|
+
test("fixLevel:task — defers summary stub and roadmap checkbox, fixes UAT immediately (#1808, #1910)", async () => {
|
|
67
67
|
const tmp = makeTmp("task-level");
|
|
68
68
|
try {
|
|
69
69
|
buildScaffold(tmp);
|
|
@@ -79,13 +79,14 @@ test("fixLevel:task — defers only summary stub, fixes roadmap and UAT immediat
|
|
|
79
79
|
const sliceSummaryPath = join(tmp, ".gsd", "milestones", "M001", "slices", "S01", "S01-SUMMARY.md");
|
|
80
80
|
assert.ok(!existsSync(sliceSummaryPath), "should NOT have created summary stub");
|
|
81
81
|
|
|
82
|
-
// Roadmap
|
|
82
|
+
// Roadmap must NOT be checked without summary on disk (#1910)
|
|
83
83
|
const roadmapContent = readFileSync(join(tmp, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "utf8");
|
|
84
|
-
assert.ok(roadmapContent.includes("- [
|
|
84
|
+
assert.ok(roadmapContent.includes("- [ ] **S01"), "roadmap must NOT be checked without summary (#1910)");
|
|
85
85
|
|
|
86
|
-
// Fixes applied should NOT include summary
|
|
86
|
+
// Fixes applied should NOT include summary or roadmap
|
|
87
87
|
for (const f of report.fixesApplied) {
|
|
88
88
|
assert.ok(!f.includes("SUMMARY"), `should not have fixed summary: ${f}`);
|
|
89
|
+
assert.ok(!f.includes("ROADMAP") && !f.includes("roadmap"), `should not have fixed roadmap: ${f}`);
|
|
89
90
|
}
|
|
90
91
|
} finally {
|
|
91
92
|
rmSync(tmp, { recursive: true, force: true });
|