@opengsd/gsd-pi 1.1.1-dev.9f86580 → 1.1.1-dev.b2556262
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/dist/headless-recover.js +56 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/index.js +39 -22
- package/dist/resources/extensions/browser-tools/state.js +12 -0
- package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
- package/dist/resources/extensions/browser-tools/utils.js +3 -3
- package/dist/resources/extensions/gsd/auto/loop.js +4 -2
- package/dist/resources/extensions/gsd/auto/phases.js +43 -10
- package/dist/resources/extensions/gsd/auto/session.js +20 -1
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +72 -12
- package/dist/resources/extensions/gsd/auto-model-selection.js +128 -9
- package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +24 -19
- package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
- package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
- package/dist/resources/extensions/gsd/auto-start.js +1 -1
- package/dist/resources/extensions/gsd/auto.js +14 -11
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +172 -65
- package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
- package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
- package/dist/resources/extensions/gsd/db-writer.js +35 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
- package/dist/resources/extensions/gsd/gsd-db.js +480 -172
- package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
- package/dist/resources/extensions/gsd/md-importer.js +38 -3
- package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
- package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
- package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
- package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
- package/dist/resources/extensions/gsd/preferences-models.js +110 -43
- package/dist/resources/extensions/gsd/preferences-types.js +13 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
- package/dist/resources/extensions/gsd/preferences.js +4 -1
- package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
- package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
- package/dist/resources/extensions/gsd/source-observations.js +306 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
- package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
- package/dist/resources/extensions/gsd/state.js +7 -3
- package/dist/resources/extensions/gsd/tool-contract.js +14 -0
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +1 -9
- package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -6
- package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
- package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +57 -429
- package/dist/resources/extensions/gsd/uat-policy.js +130 -0
- package/dist/resources/extensions/gsd/uat-run.js +414 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
- package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
- package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
- package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
- package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
- package/dist/resources/extensions/subagent/agents.js +1 -0
- package/dist/resources/extensions/subagent/index.js +27 -12
- package/dist/resources/extensions/subagent/launch.js +7 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/dist/native.js +22 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/package.json +4 -4
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/dist/native.js +22 -0
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +30 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +23 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +25 -24
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +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/theme/themes.js +1 -1
- package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/utils.d.ts +11 -0
- package/packages/pi-tui/dist/utils.d.ts.map +1 -1
- package/packages/pi-tui/dist/utils.js +119 -6
- package/packages/pi-tui/dist/utils.js.map +1 -1
- package/packages/pi-tui/package.json +2 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/themes.js +1 -1
- package/pkg/dist/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/index.ts +39 -22
- package/src/resources/extensions/browser-tools/state.ts +13 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
- package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
- package/src/resources/extensions/browser-tools/utils.ts +3 -3
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/loop.ts +4 -2
- package/src/resources/extensions/gsd/auto/phases.ts +42 -10
- package/src/resources/extensions/gsd/auto/session.ts +22 -1
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +85 -12
- package/src/resources/extensions/gsd/auto-model-selection.ts +164 -12
- package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +23 -20
- package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
- package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
- package/src/resources/extensions/gsd/auto-start.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +13 -10
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +225 -72
- package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
- package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
- package/src/resources/extensions/gsd/db-writer.ts +38 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
- package/src/resources/extensions/gsd/gsd-db.ts +564 -186
- package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
- package/src/resources/extensions/gsd/md-importer.ts +49 -2
- package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
- package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
- package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
- package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
- package/src/resources/extensions/gsd/preferences-models.ts +112 -43
- package/src/resources/extensions/gsd/preferences-types.ts +39 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
- package/src/resources/extensions/gsd/preferences.ts +5 -0
- package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
- package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
- package/src/resources/extensions/gsd/source-observations.ts +402 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
- package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
- package/src/resources/extensions/gsd/state.ts +7 -4
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +16 -2
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
- package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
- package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
- package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +73 -6
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
- package/src/resources/extensions/gsd/tool-contract.ts +28 -0
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +1 -11
- package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -6
- package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
- package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +66 -526
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/uat-policy.ts +191 -0
- package/src/resources/extensions/gsd/uat-run.ts +550 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
- package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
- package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
- package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
- package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
- package/src/resources/extensions/subagent/agents.ts +4 -0
- package/src/resources/extensions/subagent/index.ts +28 -3
- package/src/resources/extensions/subagent/launch.ts +8 -0
- package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
- /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
|
@@ -502,7 +502,9 @@ describe('gsd-recover', async () => {
|
|
|
502
502
|
insertMilestone({ id: 'M999', title: 'Existing DB State', status: 'active' });
|
|
503
503
|
|
|
504
504
|
const { ctx, notes } = makeCtx();
|
|
505
|
-
|
|
505
|
+
// M999 is in the DB but not in the markdown, so recover would delete it:
|
|
506
|
+
// a data-loss recover now requires explicit --allow-data-loss.
|
|
507
|
+
await handleRecover(ctx, base, '--confirm --allow-data-loss');
|
|
506
508
|
|
|
507
509
|
assert.equal(getMilestone('M999'), null, 'confirmed recover clears old hierarchy rows');
|
|
508
510
|
assert.ok(getMilestone('M001'), 'confirmed recover imports markdown hierarchy');
|
|
@@ -512,4 +514,63 @@ describe('gsd-recover', async () => {
|
|
|
512
514
|
cleanup(base);
|
|
513
515
|
}
|
|
514
516
|
});
|
|
517
|
+
|
|
518
|
+
test('handleRecover refuses to delete DB rows markdown lacks without --allow-data-loss', async () => {
|
|
519
|
+
const base = createFixtureBase();
|
|
520
|
+
try {
|
|
521
|
+
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_M001);
|
|
522
|
+
openDatabase(':memory:');
|
|
523
|
+
insertMilestone({ id: 'M999', title: 'Existing DB State', status: 'active' });
|
|
524
|
+
|
|
525
|
+
const { ctx, notes } = makeCtx();
|
|
526
|
+
await handleRecover(ctx, base, '--confirm');
|
|
527
|
+
|
|
528
|
+
// --confirm alone must NOT clear authoritative DB rows the markdown lacks.
|
|
529
|
+
assert.ok(getMilestone('M999'), 'data-loss recover is refused, DB row preserved');
|
|
530
|
+
assert.equal(getMilestone('M001'), null, 'markdown not imported on refusal');
|
|
531
|
+
assert.equal(notes.at(-1)?.kind, 'error');
|
|
532
|
+
} finally {
|
|
533
|
+
closeDatabase();
|
|
534
|
+
cleanup(base);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test('handleRecover interactive data-loss requires a second explicit acknowledgement', async () => {
|
|
539
|
+
const base = createFixtureBase();
|
|
540
|
+
try {
|
|
541
|
+
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_M001);
|
|
542
|
+
openDatabase(':memory:');
|
|
543
|
+
insertMilestone({ id: 'M999', title: 'Existing DB State', status: 'active' });
|
|
544
|
+
|
|
545
|
+
// First confirm (proceed?) = yes; second confirm (delete rows?) = no.
|
|
546
|
+
let call = 0;
|
|
547
|
+
const { ctx, notes } = makeCtx(async () => { call += 1; return call === 1; });
|
|
548
|
+
await handleRecover(ctx, base, '');
|
|
549
|
+
|
|
550
|
+
assert.ok(getMilestone('M999'), 'declining the data-loss ack preserves DB rows');
|
|
551
|
+
assert.equal(getMilestone('M001'), null, 'markdown not imported when data-loss ack declined');
|
|
552
|
+
assert.match(notes.at(-1)?.message ?? '', /cancelled/);
|
|
553
|
+
} finally {
|
|
554
|
+
closeDatabase();
|
|
555
|
+
cleanup(base);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test('handleRecover interactive proceeds when the data-loss deletion is acknowledged', async () => {
|
|
560
|
+
const base = createFixtureBase();
|
|
561
|
+
try {
|
|
562
|
+
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_M001);
|
|
563
|
+
openDatabase(':memory:');
|
|
564
|
+
insertMilestone({ id: 'M999', title: 'Existing DB State', status: 'active' });
|
|
565
|
+
|
|
566
|
+
const { ctx } = makeCtx(async () => true); // both confirms accepted
|
|
567
|
+
await handleRecover(ctx, base, '');
|
|
568
|
+
|
|
569
|
+
assert.equal(getMilestone('M999'), null, 'acknowledged data-loss recover clears old rows');
|
|
570
|
+
assert.ok(getMilestone('M001'), 'acknowledged data-loss recover imports markdown');
|
|
571
|
+
} finally {
|
|
572
|
+
closeDatabase();
|
|
573
|
+
cleanup(base);
|
|
574
|
+
}
|
|
575
|
+
});
|
|
515
576
|
});
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
insertTask,
|
|
31
31
|
openDatabase,
|
|
32
32
|
} from "../gsd-db.js";
|
|
33
|
+
import { SourceObservationStore } from "../source-observations.js";
|
|
33
34
|
|
|
34
35
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
35
36
|
|
|
@@ -186,6 +187,7 @@ function makeSession() {
|
|
|
186
187
|
currentMilestoneId: "M001",
|
|
187
188
|
currentUnit: null,
|
|
188
189
|
currentUnitRouting: null,
|
|
190
|
+
sourceObservations: new SourceObservationStore(),
|
|
189
191
|
completedUnits: [],
|
|
190
192
|
resourceVersionOnStart: null,
|
|
191
193
|
lastPromptCharCount: undefined,
|
|
@@ -207,6 +209,19 @@ function makeSession() {
|
|
|
207
209
|
newSession: () => Promise.resolve({ cancelled: false }),
|
|
208
210
|
getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
|
|
209
211
|
},
|
|
212
|
+
setCurrentUnit(this: any, unit: any) {
|
|
213
|
+
this.currentUnit = unit;
|
|
214
|
+
this.sourceObservations.beginUnit({
|
|
215
|
+
unitType: unit.type,
|
|
216
|
+
unitId: unit.id,
|
|
217
|
+
startedAt: unit.startedAt,
|
|
218
|
+
basePath: unit.workspaceRoot ?? this.basePath,
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
clearCurrentUnit(this: any) {
|
|
222
|
+
this.currentUnit = null;
|
|
223
|
+
this.sourceObservations.clear();
|
|
224
|
+
},
|
|
210
225
|
clearTimers: () => {},
|
|
211
226
|
} as any;
|
|
212
227
|
}
|
|
@@ -393,6 +393,48 @@ test('── markdown-renderer: renderPlanCheckboxes round-trip ──', async (
|
|
|
393
393
|
}
|
|
394
394
|
});
|
|
395
395
|
|
|
396
|
+
test('── markdown-renderer: renderPlanCheckboxes re-renders DB tasks added after the plan artifact ──', async () => {
|
|
397
|
+
// Regression for the lossy-projection root cause: renderPlanCheckboxes used to
|
|
398
|
+
// patch the cached PLAN artifact in place, silently dropping tasks added to
|
|
399
|
+
// the DB after the artifact was first written (the 4S/0T-vs-5S/13T drift).
|
|
400
|
+
const tmpDir = makeTmpDir();
|
|
401
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
402
|
+
openDatabase(dbPath);
|
|
403
|
+
clearAllCaches();
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
scaffoldDirs(tmpDir, 'M001', ['S01']);
|
|
407
|
+
insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
|
|
408
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
|
|
409
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First task', status: 'pending' });
|
|
410
|
+
|
|
411
|
+
// PLAN.md on disk reflects an earlier state with only T01.
|
|
412
|
+
const planPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-PLAN.md');
|
|
413
|
+
fs.writeFileSync(planPath, makePlanContent('S01', [{ id: 'T01', title: 'First task', done: false }]));
|
|
414
|
+
clearAllCaches();
|
|
415
|
+
|
|
416
|
+
// Two more tasks are written to the DB after the artifact already exists.
|
|
417
|
+
insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Second task', status: 'done' });
|
|
418
|
+
insertTask({ id: 'T03', sliceId: 'S01', milestoneId: 'M001', title: 'Third task', status: 'pending' });
|
|
419
|
+
|
|
420
|
+
const ok = await renderPlanCheckboxes(tmpDir, 'M001', 'S01');
|
|
421
|
+
assert.ok(ok, 'renderPlanCheckboxes returns true');
|
|
422
|
+
|
|
423
|
+
const parsed = parsePlan(fs.readFileSync(planPath, 'utf-8'));
|
|
424
|
+
clearAllCaches();
|
|
425
|
+
assert.deepStrictEqual(
|
|
426
|
+
parsed.tasks.map(t => t.id).sort(),
|
|
427
|
+
['T01', 'T02', 'T03'],
|
|
428
|
+
'full re-render must include tasks added after the artifact was written',
|
|
429
|
+
);
|
|
430
|
+
assert.ok(parsed.tasks.find(t => t.id === 'T02')!.done, 'T02 reflects done status from DB');
|
|
431
|
+
assert.ok(!parsed.tasks.find(t => t.id === 'T03')!.done, 'T03 reflects pending status from DB');
|
|
432
|
+
} finally {
|
|
433
|
+
closeDatabase();
|
|
434
|
+
cleanupDir(tmpDir);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
396
438
|
test('── markdown-renderer: renderPlanCheckboxes bidirectional ──', async () => {
|
|
397
439
|
const tmpDir = makeTmpDir();
|
|
398
440
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
@@ -151,6 +151,105 @@ test("migration auto-check leaves matching DB hierarchy alone", async () => {
|
|
|
151
151
|
}
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
+
test("migration auto-check flags a populated DB with missing markdown and points at rebuild (not recover)", async () => {
|
|
155
|
+
const base = makeBase();
|
|
156
|
+
try {
|
|
157
|
+
// A project with no milestone markdown: simulate lost/empty projections
|
|
158
|
+
// over a populated DB. The previous early return treated all-zero markdown
|
|
159
|
+
// as 'no project' and never even opened the DB, silently hiding the rows.
|
|
160
|
+
await writeGSDDirectory({ projectContent: "# P\n", decisionsContent: "", requirements: [], milestones: [] }, base);
|
|
161
|
+
assert.equal(await ensureDbOpen(base), true);
|
|
162
|
+
assert.deepEqual(countMarkdownHierarchy(base), { milestones: 0, slices: 0, tasks: 0 });
|
|
163
|
+
insertMilestone({ id: "M001", title: "Legacy Milestone", status: "active" });
|
|
164
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Legacy Slice", status: "pending", risk: "medium", depends: [], demo: "Legacy slice demo", sequence: 1 });
|
|
165
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Legacy Task", status: "pending" });
|
|
166
|
+
|
|
167
|
+
const result = await checkMarkdownHierarchyAgainstDb(base);
|
|
168
|
+
assert.equal(result.action, "recovery-required");
|
|
169
|
+
assert.equal(result.reason, "markdown-missing");
|
|
170
|
+
// The DB is the richer side, so recover (md → DB) would DELETE rows. The
|
|
171
|
+
// safe repair is to re-project from the DB.
|
|
172
|
+
assert.equal(result.recoveryCommand, "/gsd rebuild markdown");
|
|
173
|
+
assert.match(result.message ?? "", /rebuild markdown/);
|
|
174
|
+
assert.match(result.message ?? "", /Do NOT run/);
|
|
175
|
+
// The check must not mutate the DB.
|
|
176
|
+
assert.equal(getAllMilestones().length, 1);
|
|
177
|
+
assert.equal(getSliceTasks("M001", "S01").length, 1);
|
|
178
|
+
} finally {
|
|
179
|
+
cleanup(base);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("migration auto-check detects identity drift even when counts match", async () => {
|
|
184
|
+
const base = makeBase();
|
|
185
|
+
try {
|
|
186
|
+
await writeGSDDirectory(projectFixture(), base); // markdown: M001 / S01 / T01
|
|
187
|
+
assert.equal(await ensureDbOpen(base), true);
|
|
188
|
+
// Same cardinalities (1M/1S/1T) but a DIFFERENT slice identity (S99 vs S01).
|
|
189
|
+
insertMilestone({ id: "M001", title: "Legacy Milestone", status: "active" });
|
|
190
|
+
insertSlice({ id: "S99", milestoneId: "M001", title: "Other Slice", status: "pending", risk: "medium", depends: [], demo: "d", sequence: 1 });
|
|
191
|
+
insertTask({ id: "T01", sliceId: "S99", milestoneId: "M001", title: "Legacy Task", status: "pending" });
|
|
192
|
+
|
|
193
|
+
const result = await checkMarkdownHierarchyAgainstDb(base);
|
|
194
|
+
// Counts are equal on both sides, so the old count-only comparison reported
|
|
195
|
+
// 'in-sync'. Identity comparison must catch the divergence instead.
|
|
196
|
+
assert.equal(result.action, "recovery-required");
|
|
197
|
+
assert.notEqual(result.reason, "in-sync");
|
|
198
|
+
assert.deepEqual(result.markdown, { milestones: 1, slices: 1, tasks: 1 });
|
|
199
|
+
assert.deepEqual(result.beforeDb, { milestones: 1, slices: 1, tasks: 1 });
|
|
200
|
+
// The DB holds S99 (which markdown lacks), so recover would DELETE it. Even
|
|
201
|
+
// at equal counts the safe recommendation must be rebuild, not recover.
|
|
202
|
+
assert.equal(result.recoveryCommand, "/gsd rebuild markdown");
|
|
203
|
+
assert.match(result.message ?? "", /Do NOT run/);
|
|
204
|
+
} finally {
|
|
205
|
+
cleanup(base);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("recoverWouldDeleteDbRows flags identity drift the markdown lacks (even at equal counts)", async () => {
|
|
210
|
+
const base = makeBase();
|
|
211
|
+
try {
|
|
212
|
+
await writeGSDDirectory(projectFixture(), base); // markdown: M001 / S01 / T01
|
|
213
|
+
assert.equal(await ensureDbOpen(base), true);
|
|
214
|
+
// DB row identity (S99) differs from markdown (S01) at the same count.
|
|
215
|
+
insertMilestone({ id: "M001", title: "Legacy Milestone", status: "active" });
|
|
216
|
+
insertSlice({ id: "S99", milestoneId: "M001", title: "Other Slice", status: "pending", risk: "medium", depends: [], demo: "d", sequence: 1 });
|
|
217
|
+
insertTask({ id: "T01", sliceId: "S99", milestoneId: "M001", title: "Legacy Task", status: "pending" });
|
|
218
|
+
|
|
219
|
+
const { recoverWouldDeleteDbRows } = await import("../migration-auto-check.ts");
|
|
220
|
+
assert.equal(recoverWouldDeleteDbRows(base), true, "DB S99 is absent from markdown — recover would delete it");
|
|
221
|
+
} finally {
|
|
222
|
+
cleanup(base);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("migration auto-check canonicalizes a legacy descriptor milestone dir (no false drift)", async () => {
|
|
227
|
+
const base = makeBase();
|
|
228
|
+
try {
|
|
229
|
+
await writeGSDDirectory(projectFixture(), base); // creates .gsd/milestones/M001
|
|
230
|
+
// Rename the dir to a legacy descriptor form while the DB id stays "M001".
|
|
231
|
+
// scanMarkdownHierarchy must canonicalize "M001-old" → "M001" so the
|
|
232
|
+
// identity sets line up with scanDbHierarchy (which uses milestone.id).
|
|
233
|
+
const milestonesRoot = join(base, ".gsd", "milestones");
|
|
234
|
+
renameSync(join(milestonesRoot, "M001"), join(milestonesRoot, "M001-old"));
|
|
235
|
+
|
|
236
|
+
assert.equal(await ensureDbOpen(base), true);
|
|
237
|
+
insertMilestone({ id: "M001", title: "Legacy Milestone", status: "active" });
|
|
238
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Legacy Slice", status: "pending", risk: "medium", depends: [], demo: "Legacy slice demo", sequence: 1 });
|
|
239
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Legacy Task", status: "pending" });
|
|
240
|
+
|
|
241
|
+
const result = await checkMarkdownHierarchyAgainstDb(base);
|
|
242
|
+
// Must be in-sync: the raw dir name "M001-old" would otherwise mismatch the
|
|
243
|
+
// DB id "M001" and be flagged as false drift.
|
|
244
|
+
assert.equal(result.action, "none");
|
|
245
|
+
assert.equal(result.reason, "in-sync");
|
|
246
|
+
assert.deepEqual(result.markdown, { milestones: 1, slices: 1, tasks: 1 });
|
|
247
|
+
assert.deepEqual(result.beforeDb, { milestones: 1, slices: 1, tasks: 1 });
|
|
248
|
+
} finally {
|
|
249
|
+
cleanup(base);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
154
253
|
test("migration auto-check refreshes a stale open DB handle before comparing", async () => {
|
|
155
254
|
const base = makeBase();
|
|
156
255
|
try {
|
|
@@ -133,6 +133,36 @@ test('handlePlanSlice persists explicit slice/task target repositories', async (
|
|
|
133
133
|
}
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
+
test('handlePlanSlice honors configured gate-evaluation gate sets', async () => {
|
|
137
|
+
const base = makeTmpBase();
|
|
138
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
seedParentSlice();
|
|
142
|
+
writeFileSync(
|
|
143
|
+
join(base, '.gsd', 'PREFERENCES.md'),
|
|
144
|
+
[
|
|
145
|
+
'---',
|
|
146
|
+
'gate_evaluation:',
|
|
147
|
+
' enabled: true',
|
|
148
|
+
' slice_gates:',
|
|
149
|
+
' - Q3',
|
|
150
|
+
' task_gates: false',
|
|
151
|
+
'---',
|
|
152
|
+
].join('\n'),
|
|
153
|
+
'utf-8',
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const result = await handlePlanSlice(validParams(), base);
|
|
157
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
158
|
+
|
|
159
|
+
const gateIds = getGateResults('M001', 'S02').map((gate) => gate.gate_id).sort();
|
|
160
|
+
assert.deepEqual(gateIds, ['Q3', 'Q8']);
|
|
161
|
+
} finally {
|
|
162
|
+
cleanup(base);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
136
166
|
test('handlePlanSlice rejects unknown target repositories', async () => {
|
|
137
167
|
const base = makeTmpBase();
|
|
138
168
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -196,6 +226,50 @@ test('handlePlanSlice enforces absolute path scope to declared target repositori
|
|
|
196
226
|
}
|
|
197
227
|
});
|
|
198
228
|
|
|
229
|
+
test('handlePlanSlice resolves relative task IO paths against declared target repository roots', async () => {
|
|
230
|
+
const base = makeTmpBase();
|
|
231
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
seedParentSlice();
|
|
235
|
+
mkdirSync(join(base, 'frontend'), { recursive: true });
|
|
236
|
+
writeFileSync(join(base, 'frontend', 'app.js'), 'export {};\n', 'utf-8');
|
|
237
|
+
writeFileSync(
|
|
238
|
+
join(base, '.gsd', 'PREFERENCES.md'),
|
|
239
|
+
[
|
|
240
|
+
'---',
|
|
241
|
+
'workspace:',
|
|
242
|
+
' mode: parent',
|
|
243
|
+
' repositories:',
|
|
244
|
+
' frontend:',
|
|
245
|
+
' path: frontend',
|
|
246
|
+
'---',
|
|
247
|
+
].join('\n'),
|
|
248
|
+
'utf-8',
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const params = validParams();
|
|
252
|
+
const result = await handlePlanSlice({
|
|
253
|
+
...params,
|
|
254
|
+
targetRepositories: ['frontend'],
|
|
255
|
+
tasks: [
|
|
256
|
+
{
|
|
257
|
+
...params.tasks[0],
|
|
258
|
+
files: ['app.js'],
|
|
259
|
+
inputs: ['app.js'],
|
|
260
|
+
expectedOutput: ['app.js'],
|
|
261
|
+
targetRepositories: ['frontend'],
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
}, base);
|
|
265
|
+
|
|
266
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
267
|
+
assert.deepEqual(getSliceTasks('M001', 'S02').map((task) => task.id), ['T01']);
|
|
268
|
+
} finally {
|
|
269
|
+
cleanup(base);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
199
273
|
test('handlePlanSlice rejects relative traversal outside declared target repositories', async () => {
|
|
200
274
|
const base = makeTmpBase();
|
|
201
275
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -749,7 +823,7 @@ test('regression: validateTasks surfaces clean per-field errors for non-array IO
|
|
|
749
823
|
}
|
|
750
824
|
});
|
|
751
825
|
|
|
752
|
-
test('handlePlanSlice skips prose and sentinel values in planning path scope', async () => {
|
|
826
|
+
test('handlePlanSlice skips prose and sentinel input values in planning path scope', async () => {
|
|
753
827
|
const base = makeTmpBase();
|
|
754
828
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
755
829
|
|
|
@@ -760,7 +834,7 @@ test('handlePlanSlice skips prose and sentinel values in planning path scope', a
|
|
|
760
834
|
tasks: [{
|
|
761
835
|
...validParams().tasks[0],
|
|
762
836
|
inputs: ['Current enum shape in codebase', 'None'],
|
|
763
|
-
expectedOutput: ['
|
|
837
|
+
expectedOutput: ['src/resources/extensions/gsd/planning-path-scope.ts'],
|
|
764
838
|
}],
|
|
765
839
|
}, base);
|
|
766
840
|
|
|
@@ -771,6 +845,29 @@ test('handlePlanSlice skips prose and sentinel values in planning path scope', a
|
|
|
771
845
|
}
|
|
772
846
|
});
|
|
773
847
|
|
|
848
|
+
test('handlePlanSlice rejects prose expectedOutput entries before path-scope validation', async () => {
|
|
849
|
+
const base = makeTmpBase();
|
|
850
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
851
|
+
|
|
852
|
+
try {
|
|
853
|
+
seedParentSlice();
|
|
854
|
+
const result = await handlePlanSlice({
|
|
855
|
+
...validParams(),
|
|
856
|
+
tasks: [{
|
|
857
|
+
...validParams().tasks[0],
|
|
858
|
+
expectedOutput: ['Browser UI supports due-date add/edit flows and mixed-list urgency rendering.'],
|
|
859
|
+
}],
|
|
860
|
+
}, base);
|
|
861
|
+
|
|
862
|
+
assert.ok('error' in result);
|
|
863
|
+
assert.match(result.error, /expectedOutput must contain only file paths/);
|
|
864
|
+
assert.doesNotMatch(result.error, /outside allowed repository roots/);
|
|
865
|
+
assert.equal(getSliceTasks('M001', 'S02').length, 0, 'invalid output contract must not persist tasks');
|
|
866
|
+
} finally {
|
|
867
|
+
cleanup(base);
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
|
|
774
871
|
test('handlePlanSlice resolves relative task IO paths against worktree roots', async () => {
|
|
775
872
|
const base = makeTmpBase();
|
|
776
873
|
const worktree = join(base, '.gsd', 'worktrees', 'M001');
|
|
@@ -96,6 +96,25 @@ test('handlePlanTask explains string IO fields must be arrays', async () => {
|
|
|
96
96
|
}
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
test('handlePlanTask rejects prose expectedOutput entries', async () => {
|
|
100
|
+
const base = makeTmpBase();
|
|
101
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
seedParent();
|
|
105
|
+
const result = await handlePlanTask({
|
|
106
|
+
...validParams(),
|
|
107
|
+
expectedOutput: ['Browser UI supports due-date add/edit flows and mixed-list urgency rendering.'],
|
|
108
|
+
}, base);
|
|
109
|
+
assert.ok('error' in result);
|
|
110
|
+
assert.match(result.error, /validation failed: expectedOutput must contain only file paths/);
|
|
111
|
+
assert.doesNotMatch(result.error, /outside allowed repository roots/);
|
|
112
|
+
assert.equal(getTask('M001', 'S02', 'T02'), null, 'invalid output contract must not persist the task');
|
|
113
|
+
} finally {
|
|
114
|
+
cleanup(base);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
99
118
|
test('handlePlanTask rejects absolute task IO paths outside the active worktree', async () => {
|
|
100
119
|
const base = makeTmpBase();
|
|
101
120
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -467,6 +467,20 @@ test("notification fields validate correctly", () => {
|
|
|
467
467
|
assert.equal(preferences.notifications?.on_complete, false);
|
|
468
468
|
});
|
|
469
469
|
|
|
470
|
+
test("gate_evaluation slice_gates only accepts gate-evaluate-owned gates", () => {
|
|
471
|
+
const valid = validatePreferences({
|
|
472
|
+
gate_evaluation: { enabled: true, slice_gates: ["Q3", "Q4"], task_gates: false },
|
|
473
|
+
});
|
|
474
|
+
assert.equal(valid.errors.length, 0);
|
|
475
|
+
assert.deepEqual(valid.preferences.gate_evaluation?.slice_gates, ["Q3", "Q4"]);
|
|
476
|
+
assert.equal(valid.preferences.gate_evaluation?.task_gates, false);
|
|
477
|
+
|
|
478
|
+
const invalid = validatePreferences({
|
|
479
|
+
gate_evaluation: { enabled: true, slice_gates: ["Q3", "Q8"] },
|
|
480
|
+
});
|
|
481
|
+
assert.ok(invalid.errors.some((error) => error.includes("gate_evaluation.slice_gates")));
|
|
482
|
+
});
|
|
483
|
+
|
|
470
484
|
test("cmux fields validate correctly", () => {
|
|
471
485
|
const { preferences, errors } = validatePreferences({
|
|
472
486
|
cmux: {
|
|
@@ -18,6 +18,7 @@ const PREF_SAMPLE_VALUES: Record<string, unknown> = {
|
|
|
18
18
|
skill_rules: [{ when: "unit:execute-task", use: ["test-writer-fixer"] }],
|
|
19
19
|
custom_instructions: ["Keep changes focused."],
|
|
20
20
|
models: { execution: "openai/gpt-5" },
|
|
21
|
+
thinking: { planning: "xhigh", execution: "low" },
|
|
21
22
|
skill_discovery: "auto",
|
|
22
23
|
skill_staleness_days: 7,
|
|
23
24
|
auto_supervisor: { soft_timeout_minutes: 20, idle_timeout_minutes: 10, hard_timeout_minutes: 30 },
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from "../bootstrap/register-hooks.ts";
|
|
18
18
|
import { shouldBlockAutoUnitToolCall } from "../auto-unit-tool-scope.ts";
|
|
19
19
|
import { UNIT_TOOL_CONTRACTS } from "../unit-tool-contracts.ts";
|
|
20
|
+
import { uatTypeIncludesBrowser } from "../uat-policy.ts";
|
|
20
21
|
|
|
21
22
|
const promptsDir = join(process.cwd(), "src/resources/extensions/gsd/prompts");
|
|
22
23
|
const templatesDir = join(process.cwd(), "src/resources/extensions/gsd/templates");
|
|
@@ -183,6 +184,7 @@ test("live-runtime and mixed UAT presentations also surface browser tools", () =
|
|
|
183
184
|
// drive a browser, so the runner must actually receive the browser tools and
|
|
184
185
|
// a hybrid surface — otherwise live checks silently downgrade to NEEDS-HUMAN.
|
|
185
186
|
for (const uatType of ["live-runtime", "mixed", "human-experience"] as const) {
|
|
187
|
+
assert.equal(uatTypeIncludesBrowser(uatType), true, `${uatType} policy should include browser tools`);
|
|
186
188
|
const presentation = buildRunUatPresentationForType(uatType);
|
|
187
189
|
assert.equal(presentation.surface, "hybrid", `${uatType} should use the hybrid surface`);
|
|
188
190
|
for (const toolName of RUN_UAT_BROWSER_TOOL_NAMES) {
|
|
@@ -196,6 +198,7 @@ test("live-runtime and mixed UAT presentations also surface browser tools", () =
|
|
|
196
198
|
|
|
197
199
|
test("artifact-driven and runtime-executable UAT presentations stay browser-free", () => {
|
|
198
200
|
for (const uatType of ["artifact-driven", "runtime-executable"] as const) {
|
|
201
|
+
assert.equal(uatTypeIncludesBrowser(uatType), false, `${uatType} policy should stay browser-free`);
|
|
199
202
|
const presentation = buildRunUatPresentationForType(uatType);
|
|
200
203
|
assert.equal(presentation.surface, "mcp", `${uatType} should use the mcp surface`);
|
|
201
204
|
assert.ok(
|
|
@@ -659,6 +662,12 @@ test("parallel subagent prompts forbid serialized tasks arrays", () => {
|
|
|
659
662
|
}
|
|
660
663
|
});
|
|
661
664
|
|
|
665
|
+
test("gate-evaluate prompt requires gate result findings field", () => {
|
|
666
|
+
const prompt = readPrompt("gate-evaluate");
|
|
667
|
+
assert.match(prompt, /`findings`/);
|
|
668
|
+
assert.match(prompt, /empty string if none/i);
|
|
669
|
+
});
|
|
670
|
+
|
|
662
671
|
// ─── Project-shape classifier + 3-or-4-options-with-Other-hatch contract ──
|
|
663
672
|
|
|
664
673
|
test("guided-discuss-project classifies project shape and persists the verdict to PROJECT.md", () => {
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { mkdirSync, rmSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
|
|
7
7
|
import { registerHooks } from "../bootstrap/register-hooks.ts";
|
|
8
|
+
import {
|
|
9
|
+
clearPendingAutoStart,
|
|
10
|
+
setPendingAutoStart,
|
|
11
|
+
} from "../guided-flow.ts";
|
|
12
|
+
import { closeDatabase, getMilestone } from "../gsd-db.ts";
|
|
13
|
+
import { deriveState, invalidateStateCache } from "../state.ts";
|
|
8
14
|
import {
|
|
9
15
|
getPendingGate,
|
|
10
16
|
resetWriteGateState,
|
|
@@ -186,6 +192,100 @@ test("register-hooks unlocks milestone depth verification from question id witho
|
|
|
186
192
|
);
|
|
187
193
|
});
|
|
188
194
|
|
|
195
|
+
test("register-hooks persists first structured question round for new milestone re-entry", async (t) => {
|
|
196
|
+
const dir = makeTempDir("question-draft");
|
|
197
|
+
mkdirSync(join(dir, ".gsd", "milestones"), { recursive: true });
|
|
198
|
+
const originalCwd = process.cwd();
|
|
199
|
+
process.chdir(dir);
|
|
200
|
+
resetWriteGateState(dir);
|
|
201
|
+
clearPendingAutoStart(dir);
|
|
202
|
+
|
|
203
|
+
t.after(() => {
|
|
204
|
+
try {
|
|
205
|
+
resetWriteGateState(dir);
|
|
206
|
+
clearPendingAutoStart(dir);
|
|
207
|
+
closeDatabase();
|
|
208
|
+
} finally {
|
|
209
|
+
process.chdir(originalCwd);
|
|
210
|
+
rmSync(dir, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const handlers = new Map<string, Array<(event: any, ctx?: any) => Promise<void> | void>>();
|
|
215
|
+
const pi = {
|
|
216
|
+
on(event: string, handler: (event: any, ctx?: any) => Promise<void> | void) {
|
|
217
|
+
const existing = handlers.get(event) ?? [];
|
|
218
|
+
existing.push(handler);
|
|
219
|
+
handlers.set(event, existing);
|
|
220
|
+
},
|
|
221
|
+
} as any;
|
|
222
|
+
const ctx = { cwd: dir, ui: { notify: () => undefined } } as any;
|
|
223
|
+
|
|
224
|
+
registerHooks(pi, []);
|
|
225
|
+
setPendingAutoStart(dir, {
|
|
226
|
+
basePath: dir,
|
|
227
|
+
milestoneId: "M004",
|
|
228
|
+
ctx,
|
|
229
|
+
pi: { sendMessage: () => undefined } as any,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const questions = [
|
|
233
|
+
{
|
|
234
|
+
id: "m004_shape",
|
|
235
|
+
header: "M004 Shape",
|
|
236
|
+
question: "What are you picturing for M004?",
|
|
237
|
+
options: [
|
|
238
|
+
{ label: "Planning metadata (Recommended)", description: "Plan the next metadata layer." },
|
|
239
|
+
{ label: "Find and organize", description: "Improve searching and organizing." },
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
id: "boundary",
|
|
244
|
+
header: "Boundary",
|
|
245
|
+
question: "Which boundary should I plan around?",
|
|
246
|
+
options: [
|
|
247
|
+
{ label: "No new dependencies (Recommended)", description: "Keep implementation vanilla." },
|
|
248
|
+
{ label: "Browser APIs OK", description: "Use browser-native capabilities." },
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
for (const handler of handlers.get("tool_result") ?? []) {
|
|
254
|
+
await handler({
|
|
255
|
+
toolName: "ask_user_questions",
|
|
256
|
+
input: { questions },
|
|
257
|
+
details: {
|
|
258
|
+
response: {
|
|
259
|
+
answers: {
|
|
260
|
+
m004_shape: { selected: "Planning metadata (Recommended)" },
|
|
261
|
+
boundary: { selected: "No new dependencies (Recommended)" },
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
}, ctx);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const milestoneDir = join(dir, ".gsd", "milestones", "M004");
|
|
269
|
+
const draftPath = join(milestoneDir, "M004-CONTEXT-DRAFT.md");
|
|
270
|
+
const discussionPath = join(milestoneDir, "M004-DISCUSSION.md");
|
|
271
|
+
|
|
272
|
+
assert.equal(existsSync(draftPath), true, "first answer round should create a resumable context draft");
|
|
273
|
+
assert.equal(existsSync(discussionPath), true, "first answer round should create a discussion log");
|
|
274
|
+
|
|
275
|
+
const draft = readFileSync(draftPath, "utf-8");
|
|
276
|
+
assert.match(draft, /What are you picturing for M004\?/);
|
|
277
|
+
assert.match(draft, /Planning metadata \(Recommended\)/);
|
|
278
|
+
assert.match(draft, /No new dependencies \(Recommended\)/);
|
|
279
|
+
|
|
280
|
+
const row = getMilestone("M004");
|
|
281
|
+
assert.equal(row?.status, "queued", "new milestone shell should be registered in the DB");
|
|
282
|
+
|
|
283
|
+
invalidateStateCache();
|
|
284
|
+
const state = await deriveState(dir);
|
|
285
|
+
assert.equal(state.activeMilestone?.id, "M004");
|
|
286
|
+
assert.equal(state.phase, "needs-discussion");
|
|
287
|
+
});
|
|
288
|
+
|
|
189
289
|
test("register-hooks clears depth gate when remote (Telegram/Slack/Discord) answer is normalized (#4406)", async (t) => {
|
|
190
290
|
const dir = makeTempDir("remote");
|
|
191
291
|
const originalCwd = process.cwd();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import test from "node:test";
|
|
4
4
|
import assert from "node:assert/strict";
|
|
5
|
-
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { execFileSync } from "node:child_process";
|
|
@@ -117,7 +117,7 @@ test("repository registry keeps project root anchored to .gsd project in monorep
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
test("repository registry uses external-state worktree checkout as project root", (t) => {
|
|
120
|
-
const base = mkdtempSync(join(tmpdir(), "gsd-repo-registry-external-"));
|
|
120
|
+
const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-repo-registry-external-")));
|
|
121
121
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
122
122
|
const worktree = join(base, ".gsd", "projects", "abc123", "worktrees", "M001");
|
|
123
123
|
mkdirSync(worktree, { recursive: true });
|
|
@@ -67,6 +67,14 @@ test("Tool Contract compiles known Unit prompt and tool policy", () => {
|
|
|
67
67
|
assert.deepEqual(result.ok && result.contract.forbiddenWorkflowTools, []);
|
|
68
68
|
assert.equal(result.ok && result.contract.toolsPolicy.mode, "all");
|
|
69
69
|
assert.ok(result.ok && result.contract.validationRules.includes("closeout-tool-present"));
|
|
70
|
+
assert.ok(result.ok && result.contract.validationRules.includes("source-observation-contract-present"));
|
|
71
|
+
assert.deepEqual(result.ok && result.contract.sourceObservations, {
|
|
72
|
+
mode: "whole-file-active-unit",
|
|
73
|
+
seedFields: ["task.files", "task.inputs"],
|
|
74
|
+
excludedFields: ["expectedOutput"],
|
|
75
|
+
maxBytes: 50 * 1024,
|
|
76
|
+
maxLines: 2000,
|
|
77
|
+
});
|
|
70
78
|
});
|
|
71
79
|
|
|
72
80
|
test("Tool Contract records high-risk cross-phase tool boundaries without single-owning every tool", () => {
|