@opengsd/gsd-pi 1.1.1-dev.a5a2de8 → 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/engine/managed-gsd-browser.js +18 -2
- package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
- package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/dist/resources/extensions/browser-tools/index.js +68 -24
- 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/browser-tools/web-app-detect.js +52 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -2
- package/dist/resources/extensions/gsd/auto/phases.js +87 -12
- package/dist/resources/extensions/gsd/auto/session.js +22 -1
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +81 -13
- package/dist/resources/extensions/gsd/auto-model-selection.js +154 -9
- package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +26 -21
- 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-timers.js +24 -10
- package/dist/resources/extensions/gsd/auto.js +40 -15
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +192 -77
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
- package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
- 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-mcp-status.js +1 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
- package/dist/resources/extensions/gsd/config-overlay.js +1 -0
- package/dist/resources/extensions/gsd/context-masker.js +129 -5
- 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/guided-flow.js +4 -1
- 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/planner-handoff.js +98 -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 +111 -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-milestone.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/prompts/run-uat.md +2 -2
- package/dist/resources/extensions/gsd/prompts/system.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/skill-manifest.js +12 -0
- 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 +15 -1
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +24 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -0
- 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 +62 -406
- 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/unit-tool-contracts.js +38 -14
- 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-mcp.js +2 -3
- package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
- package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
- package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
- package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
- 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 +6 -6
- 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 +6 -6
- 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 +174 -29
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +178 -54
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +8 -1
- package/packages/pi-ai/dist/providers/transform-messages.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/scripts/install/handoff.js +16 -3
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
- package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
- package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/src/resources/extensions/browser-tools/index.ts +75 -27
- package/src/resources/extensions/browser-tools/state.ts +13 -0
- package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
- package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -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/browser-tools/web-app-detect.ts +63 -0
- 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 +89 -15
- package/src/resources/extensions/gsd/auto/session.ts +24 -1
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +117 -12
- package/src/resources/extensions/gsd/auto-model-selection.ts +190 -12
- package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +25 -22
- 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-timers.ts +25 -9
- package/src/resources/extensions/gsd/auto.ts +41 -14
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +250 -78
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
- package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
- 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-mcp-status.ts +1 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
- package/src/resources/extensions/gsd/config-overlay.ts +1 -0
- package/src/resources/extensions/gsd/context-masker.ts +152 -5
- 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/guided-flow.ts +4 -1
- 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/planner-handoff.ts +149 -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 +113 -43
- package/src/resources/extensions/gsd/preferences-types.ts +47 -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-milestone.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/prompts/run-uat.md +2 -2
- package/src/resources/extensions/gsd/prompts/system.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/skill-manifest.ts +12 -0
- 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 +114 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
- 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/auto-supervisor.test.mjs +4 -0
- package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -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/complete-slice-verification-gate.test.ts +118 -0
- package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +17 -2
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
- 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/integration/run-uat.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
- 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/planner-handoff.test.ts +100 -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 +133 -0
- package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -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 +28 -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/skill-manifest.test.ts +4 -3
- 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-mcp.test.ts +77 -10
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +260 -5
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
- package/src/resources/extensions/gsd/tool-contract.ts +29 -1
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +41 -6
- package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -0
- 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 +71 -489
- 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/unit-tool-contracts.ts +38 -14
- 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-mcp.ts +2 -3
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
- package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
- package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
- package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
- 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/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
buildCloseoutMenuActions,
|
|
9
9
|
buildIdleMenuSummary,
|
|
10
10
|
getPrimaryCloseoutRecommendation,
|
|
11
|
+
showMilestoneMergeCloseout,
|
|
11
12
|
} from "../closeout-wizard.ts";
|
|
12
13
|
import { buildGsdHomeModel } from "../gsd-command-home.ts";
|
|
13
14
|
import type { GSDState } from "../types.ts";
|
|
@@ -119,3 +120,46 @@ test("/gsd home recommends merge milestone when closeout is pending", () => {
|
|
|
119
120
|
assert.equal(merge?.recommended, true);
|
|
120
121
|
assert.equal(merge?.enabled, true);
|
|
121
122
|
});
|
|
123
|
+
|
|
124
|
+
test("milestone merge closeout clears stale timer controls and installs the closeout outcome", () => {
|
|
125
|
+
const statuses: Array<[string, string | undefined]> = [];
|
|
126
|
+
const widgets: Array<[string, unknown]> = [];
|
|
127
|
+
|
|
128
|
+
showMilestoneMergeCloseout({
|
|
129
|
+
hasUI: true,
|
|
130
|
+
ui: {
|
|
131
|
+
setStatus: (key: string, value: string | undefined) => {
|
|
132
|
+
statuses.push([key, value]);
|
|
133
|
+
},
|
|
134
|
+
setWidget: (key: string, value: unknown) => {
|
|
135
|
+
widgets.push([key, value]);
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
} as any, {
|
|
139
|
+
milestoneId: "M004",
|
|
140
|
+
branch: "milestone/M004",
|
|
141
|
+
integrationBranch: "main",
|
|
142
|
+
files: ["src/app.ts"],
|
|
143
|
+
dirtyOverlap: [],
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
assert.deepEqual(statuses, [
|
|
147
|
+
["gsd-auto", undefined],
|
|
148
|
+
["gsd-step", undefined],
|
|
149
|
+
]);
|
|
150
|
+
assert.ok(
|
|
151
|
+
widgets.some(([key, value]) => key === "gsd-progress" && value === undefined),
|
|
152
|
+
"stale progress/timer widget should be cleared",
|
|
153
|
+
);
|
|
154
|
+
const outcome = widgets.find(([key]) => key === "gsd-outcome")?.[1];
|
|
155
|
+
assert.equal(typeof outcome, "function");
|
|
156
|
+
|
|
157
|
+
const component = (outcome as any)(
|
|
158
|
+
{ requestRender() {} },
|
|
159
|
+
{ fg: (_color: string, text: string) => text, bold: (text: string) => text },
|
|
160
|
+
);
|
|
161
|
+
const rendered = component.render(100).join("\n");
|
|
162
|
+
assert.match(rendered, /Milestone M004 merged/);
|
|
163
|
+
assert.match(rendered, /Review the closeout/);
|
|
164
|
+
assert.doesNotMatch(rendered, /\/gsd auto/);
|
|
165
|
+
});
|
|
@@ -40,6 +40,7 @@ function makeMockCtx(base: string): {
|
|
|
40
40
|
return {
|
|
41
41
|
ctx: {
|
|
42
42
|
cwd: base,
|
|
43
|
+
hasUI: true,
|
|
43
44
|
ui: {
|
|
44
45
|
notify: (message: string, kind: string) => {
|
|
45
46
|
calls.push({ message, kind });
|
|
@@ -217,7 +218,7 @@ test("dispatcher recovers a completed unmerged milestone through complete-milest
|
|
|
217
218
|
const base = makeTempRepo("gsd-dispatch-unmerged-");
|
|
218
219
|
try {
|
|
219
220
|
seedRegisteredCompletedWorktree(base);
|
|
220
|
-
const { ctx, calls } = makeMockCtx(base);
|
|
221
|
+
const { ctx, calls, widgets, statuses } = makeMockCtx(base);
|
|
221
222
|
const { pi, messages } = makeMockPi();
|
|
222
223
|
|
|
223
224
|
await handleGSDCommand("dispatch complete-milestone M008", ctx, pi);
|
|
@@ -231,6 +232,30 @@ test("dispatcher recovers a completed unmerged milestone through complete-milest
|
|
|
231
232
|
calls.some((call) => call.message.includes("Milestone M008 merged to main")),
|
|
232
233
|
"user sees merge recovery success",
|
|
233
234
|
);
|
|
235
|
+
assert.ok(
|
|
236
|
+
calls.some((call) => call.message.includes("Closeout is complete")),
|
|
237
|
+
"merge recovery ends at a closeout-complete message",
|
|
238
|
+
);
|
|
239
|
+
assert.ok(
|
|
240
|
+
calls.every((call) => !call.message.includes("Run /gsd again when ready")),
|
|
241
|
+
"merge recovery must not send the user past the closeout endpoint",
|
|
242
|
+
);
|
|
243
|
+
assert.ok(
|
|
244
|
+
statuses.some(([key, value]) => key === "gsd-auto" && value === undefined),
|
|
245
|
+
"merge recovery clears stale auto status so the run timer stops",
|
|
246
|
+
);
|
|
247
|
+
assert.ok(
|
|
248
|
+
statuses.some(([key, value]) => key === "gsd-step" && value === undefined),
|
|
249
|
+
"merge recovery clears stale step status",
|
|
250
|
+
);
|
|
251
|
+
assert.ok(
|
|
252
|
+
widgets.some(([key, value]) => key === "gsd-progress" && value === undefined),
|
|
253
|
+
"merge recovery clears stale progress/timer controls",
|
|
254
|
+
);
|
|
255
|
+
assert.ok(
|
|
256
|
+
widgets.some(([key, value]) => key === "gsd-outcome" && typeof value === "function"),
|
|
257
|
+
"merge recovery installs a durable closeout outcome",
|
|
258
|
+
);
|
|
234
259
|
assert.equal(readFileSync(join(base, "index.html"), "utf-8"), "<h1>M008</h1>\n");
|
|
235
260
|
assert.equal(git(base, "branch", "--list", "milestone/M008"), "");
|
|
236
261
|
} finally {
|
|
@@ -155,6 +155,124 @@ describe('complete-slice verification gate (#3580)', () => {
|
|
|
155
155
|
}
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
+
// ── Browser/web UAT classification gate (M001/S03 regression) ──────────
|
|
159
|
+
const BROWSER_UAT_BODY = [
|
|
160
|
+
'## UAT Type',
|
|
161
|
+
'- UAT mode: artifact-driven',
|
|
162
|
+
'',
|
|
163
|
+
'## Smoke Test',
|
|
164
|
+
'1. Open the page in a browser and perform add/edit/complete/delete once.',
|
|
165
|
+
].join('\n');
|
|
166
|
+
|
|
167
|
+
test('rejects an artifact-driven UAT that drives a browser (open the page in a browser)', async () => {
|
|
168
|
+
const result = await handleCompleteSlice(
|
|
169
|
+
makeParams({ uatContent: BROWSER_UAT_BODY }),
|
|
170
|
+
basePath,
|
|
171
|
+
);
|
|
172
|
+
assert.ok('error' in result, 'expected handler to reject a browser UAT mislabeled artifact-driven');
|
|
173
|
+
assert.match((result as { error: string }).error, /requires browser verification/i);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('allows a runtime-executable UAT that runs a browser test command (playwright)', async () => {
|
|
177
|
+
// Bugbot regression: runtime-executable legitimately drives a browser via a
|
|
178
|
+
// command captured by gsd_uat_exec — it must not be pushed to gsd-browser.
|
|
179
|
+
const body = [
|
|
180
|
+
'## UAT Type',
|
|
181
|
+
'- UAT mode: runtime-executable',
|
|
182
|
+
'',
|
|
183
|
+
'## Test Cases',
|
|
184
|
+
'1. Run `npx playwright test` and confirm a passing exit code; capture a screenshot artifact.',
|
|
185
|
+
'2. Hit http://localhost:3000/health and assert a 200 response.',
|
|
186
|
+
].join('\n');
|
|
187
|
+
const result = await handleCompleteSlice(
|
|
188
|
+
makeParams({ uatContent: body }),
|
|
189
|
+
basePath,
|
|
190
|
+
);
|
|
191
|
+
if ('error' in result) {
|
|
192
|
+
assert.doesNotMatch(
|
|
193
|
+
result.error,
|
|
194
|
+
/artifact-driven|browser-capable|browser verification/i,
|
|
195
|
+
`runtime-executable command UATs must not be gated, got: ${result.error}`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('allows an artifact-driven UAT that only disclaims browser coverage (no false positive)', async () => {
|
|
201
|
+
// S01-style: genuinely artifact-driven persistence scaffolding that merely
|
|
202
|
+
// mentions "cross-browser" / "browser-level" in a Not-Proven disclaimer.
|
|
203
|
+
const body = [
|
|
204
|
+
'## UAT Type',
|
|
205
|
+
'- UAT mode: artifact-driven',
|
|
206
|
+
'',
|
|
207
|
+
'## Not Proven By This UAT',
|
|
208
|
+
'- Interactive browser-level CRUD and real cross-browser localStorage behavior.',
|
|
209
|
+
].join('\n');
|
|
210
|
+
const result = await handleCompleteSlice(
|
|
211
|
+
makeParams({ uatContent: body }),
|
|
212
|
+
basePath,
|
|
213
|
+
);
|
|
214
|
+
if ('error' in result) {
|
|
215
|
+
assert.doesNotMatch(
|
|
216
|
+
result.error,
|
|
217
|
+
/requires browser verification/i,
|
|
218
|
+
`disclaimer-only mention must not trip the browser gate, got: ${result.error}`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('allows an artifact-driven UAT whose "navigate" step targets a file, not a browser', async () => {
|
|
224
|
+
// Bugbot regression: a bare "navigate to <file/API>" must not trip the gate
|
|
225
|
+
// just because it contains the word "navigate".
|
|
226
|
+
const body = [
|
|
227
|
+
'## UAT Type',
|
|
228
|
+
'- UAT mode: artifact-driven',
|
|
229
|
+
'',
|
|
230
|
+
'## Test Cases',
|
|
231
|
+
'1. Navigate to the generated report file and confirm the schema section exists.',
|
|
232
|
+
].join('\n');
|
|
233
|
+
const result = await handleCompleteSlice(
|
|
234
|
+
makeParams({ uatContent: body }),
|
|
235
|
+
basePath,
|
|
236
|
+
);
|
|
237
|
+
if ('error' in result) {
|
|
238
|
+
assert.doesNotMatch(
|
|
239
|
+
result.error,
|
|
240
|
+
/requires browser verification/i,
|
|
241
|
+
`non-web "navigate" must not trip the browser gate, got: ${result.error}`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('allows a browser UAT when it is declared browser-executable', async () => {
|
|
247
|
+
const body = BROWSER_UAT_BODY.replace('artifact-driven', 'browser-executable');
|
|
248
|
+
const result = await handleCompleteSlice(
|
|
249
|
+
makeParams({ uatContent: body }),
|
|
250
|
+
basePath,
|
|
251
|
+
);
|
|
252
|
+
if ('error' in result) {
|
|
253
|
+
assert.doesNotMatch(
|
|
254
|
+
result.error,
|
|
255
|
+
/requires browser verification/i,
|
|
256
|
+
`browser-executable UAT must pass the browser gate, got: ${result.error}`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('allows a browser UAT when it is declared mixed (mixed receives browser tools)', async () => {
|
|
262
|
+
const body = BROWSER_UAT_BODY.replace('artifact-driven', 'mixed (artifact-driven + browser)');
|
|
263
|
+
const result = await handleCompleteSlice(
|
|
264
|
+
makeParams({ uatContent: body }),
|
|
265
|
+
basePath,
|
|
266
|
+
);
|
|
267
|
+
if ('error' in result) {
|
|
268
|
+
assert.doesNotMatch(
|
|
269
|
+
result.error,
|
|
270
|
+
/requires browser verification/i,
|
|
271
|
+
`mixed UAT must pass the browser gate, got: ${result.error}`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
158
276
|
test('backfills prior verification narrative when verification is omitted on re-completion', async () => {
|
|
159
277
|
// Seed full_summary_md with a prior verification narrative (simulates a
|
|
160
278
|
// previous completion where the verification text was recorded).
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
import { validateContent } from "../safety/content-validator.ts";
|
|
8
|
+
|
|
9
|
+
function makeTempFile(content: string): { base: string; path: string } {
|
|
10
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-content-validator-"));
|
|
11
|
+
mkdirSync(base, { recursive: true });
|
|
12
|
+
const path = join(base, "artifact.md");
|
|
13
|
+
writeFileSync(path, content, "utf-8");
|
|
14
|
+
return { base, path };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test("validateContent marks empty milestone roadmaps as blocking", () => {
|
|
18
|
+
const { base, path } = makeTempFile([
|
|
19
|
+
"# M004: Empty roadmap",
|
|
20
|
+
"",
|
|
21
|
+
"## Slices",
|
|
22
|
+
"",
|
|
23
|
+
"_TBD_",
|
|
24
|
+
"",
|
|
25
|
+
].join("\n"));
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const violations = validateContent("plan-milestone", path);
|
|
29
|
+
assert.deepEqual(violations, [{
|
|
30
|
+
severity: "error",
|
|
31
|
+
reason: "Milestone roadmap has 0 slice(s) — expected at least 1",
|
|
32
|
+
}]);
|
|
33
|
+
} finally {
|
|
34
|
+
rmSync(base, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("validateContent accepts checkbox milestone roadmap slices", () => {
|
|
39
|
+
const { base, path } = makeTempFile([
|
|
40
|
+
"# M004: Roadmap",
|
|
41
|
+
"",
|
|
42
|
+
"## Slices",
|
|
43
|
+
"",
|
|
44
|
+
"- [ ] **S01: Browser due dates** `risk:low` `depends:[]`",
|
|
45
|
+
" > After this: due dates are visible.",
|
|
46
|
+
"",
|
|
47
|
+
].join("\n"));
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const violations = validateContent("plan-milestone", path);
|
|
51
|
+
assert.deepEqual(violations, []);
|
|
52
|
+
} finally {
|
|
53
|
+
rmSync(base, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("validateContent marks empty slice plans as blocking", () => {
|
|
58
|
+
const { base, path } = makeTempFile([
|
|
59
|
+
"# S01: Empty slice",
|
|
60
|
+
"",
|
|
61
|
+
"## Tasks",
|
|
62
|
+
"",
|
|
63
|
+
"_TBD_",
|
|
64
|
+
"",
|
|
65
|
+
].join("\n"));
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const violations = validateContent("plan-slice", path);
|
|
69
|
+
assert.equal(violations[0]?.severity, "error");
|
|
70
|
+
assert.equal(violations[0]?.reason, "Slice plan has 0 task(s) — expected at least 1");
|
|
71
|
+
} finally {
|
|
72
|
+
rmSync(base, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createObservationMask,
|
|
6
|
+
createResponsesInputObservationMask,
|
|
7
|
+
truncateContextResultMessages,
|
|
8
|
+
truncateResponsesInputResultItems,
|
|
9
|
+
} from "../context-masker.js";
|
|
5
10
|
|
|
6
11
|
// These helpers produce messages in the pi-ai LLM payload format
|
|
7
12
|
// (post-convertToLlm, pre-provider), which is what before_provider_request sees.
|
|
@@ -120,3 +125,53 @@ test("masks toolResult by role, not by type field", () => {
|
|
|
120
125
|
const result = mask(messages as any);
|
|
121
126
|
assert.equal((result[1].content as any)[0].text, MASK_TEXT);
|
|
122
127
|
});
|
|
128
|
+
|
|
129
|
+
test("truncates recent bash result user messages", () => {
|
|
130
|
+
const messages = [
|
|
131
|
+
userMsg("turn 1"),
|
|
132
|
+
bashResult("a".repeat(50)),
|
|
133
|
+
assistantMsg("response 1"),
|
|
134
|
+
];
|
|
135
|
+
const result = truncateContextResultMessages(messages as any, 10);
|
|
136
|
+
const text = (result[1].content as any)[0].text;
|
|
137
|
+
assert.ok(text.length < (messages[1].content as any)[0].text.length);
|
|
138
|
+
assert.match(text, /…\[truncated\]/);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("masks Responses API function outputs older than keepRecentTurns", () => {
|
|
142
|
+
const mask = createResponsesInputObservationMask(1);
|
|
143
|
+
const items = [
|
|
144
|
+
{ role: "user", content: [{ type: "input_text", text: "turn 1" }] },
|
|
145
|
+
{ type: "function_call_output", call_id: "call_1", output: "old output" },
|
|
146
|
+
{ type: "message", role: "assistant", content: [{ type: "output_text", text: "response 1" }] },
|
|
147
|
+
{ role: "user", content: [{ type: "input_text", text: "turn 2" }] },
|
|
148
|
+
];
|
|
149
|
+
const result = mask(items as any);
|
|
150
|
+
assert.equal(result[1].output, MASK_TEXT);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("masks Responses API bash result user items older than keepRecentTurns", () => {
|
|
154
|
+
const mask = createResponsesInputObservationMask(1);
|
|
155
|
+
const items = [
|
|
156
|
+
{ role: "user", content: [{ type: "input_text", text: "turn 1" }] },
|
|
157
|
+
{ role: "user", content: [{ type: "input_text", text: "Ran `npm test`\n```\nold output\n```" }] },
|
|
158
|
+
{ type: "message", role: "assistant", content: [{ type: "output_text", text: "response 1" }] },
|
|
159
|
+
{ role: "user", content: [{ type: "input_text", text: "turn 2" }] },
|
|
160
|
+
];
|
|
161
|
+
const result = mask(items as any);
|
|
162
|
+
assert.equal((result[1].content as any)[0].text, MASK_TEXT);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("truncates Responses API function outputs and recent bash result items", () => {
|
|
166
|
+
const items = [
|
|
167
|
+
{ role: "user", content: [{ type: "input_text", text: "turn 1" }] },
|
|
168
|
+
{ type: "function_call_output", call_id: "call_1", output: "b".repeat(50) },
|
|
169
|
+
{ role: "user", content: [{ type: "input_text", text: "Ran `npm test`\n```\n" + "c".repeat(50) + "\n```" }] },
|
|
170
|
+
];
|
|
171
|
+
const result = truncateResponsesInputResultItems(items as any, 12);
|
|
172
|
+
|
|
173
|
+
assert.match(result[1].output as string, /…\[truncated\]/);
|
|
174
|
+
assert.match((result[2].content as any)[0].text, /…\[truncated\]/);
|
|
175
|
+
assert.ok((result[1].output as string).length < (items[1].output as string).length);
|
|
176
|
+
assert.ok((result[2].content as any)[0].text.length < (items[2].content as any)[0].text.length);
|
|
177
|
+
});
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { describe, it, afterEach } from "node:test";
|
|
10
10
|
import assert from "node:assert/strict";
|
|
11
|
-
import { mkdtempSync, rmSync,
|
|
11
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { tmpdir } from "node:os";
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ import type { LoopDeps } from "../auto/loop-deps.js";
|
|
|
18
18
|
import { WorktreeStateProjection } from "../worktree-state-projection.js";
|
|
19
19
|
import type { SessionLockStatus } from "../session-lock.js";
|
|
20
20
|
import { writeGraph, readGraph, type WorkflowGraph, type GraphStep } from "../graph.ts";
|
|
21
|
-
import {
|
|
21
|
+
import { SourceObservationStore } from "../source-observations.js";
|
|
22
22
|
import { stringify } from "yaml";
|
|
23
23
|
|
|
24
24
|
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
@@ -115,6 +115,7 @@ function makeLoopSession(overrides?: Record<string, unknown>) {
|
|
|
115
115
|
currentMilestoneId: null,
|
|
116
116
|
currentUnit: null,
|
|
117
117
|
currentUnitRouting: null,
|
|
118
|
+
sourceObservations: new SourceObservationStore(),
|
|
118
119
|
completedUnits: [],
|
|
119
120
|
resourceVersionOnStart: null,
|
|
120
121
|
lastPromptCharCount: undefined,
|
|
@@ -129,6 +130,7 @@ function makeLoopSession(overrides?: Record<string, unknown>) {
|
|
|
129
130
|
unitLifetimeDispatches: new Map<string, number>(),
|
|
130
131
|
unitRecoveryCount: new Map<string, number>(),
|
|
131
132
|
verificationRetryCount: new Map<string, number>(),
|
|
133
|
+
zeroToolRetryCount: new Map<string, number>(),
|
|
132
134
|
gitService: null,
|
|
133
135
|
autoStartTime: Date.now(),
|
|
134
136
|
activeEngineId: null,
|
|
@@ -138,6 +140,19 @@ function makeLoopSession(overrides?: Record<string, unknown>) {
|
|
|
138
140
|
newSession: () => Promise.resolve({ cancelled: false }),
|
|
139
141
|
getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
|
|
140
142
|
},
|
|
143
|
+
setCurrentUnit(this: any, unit: any) {
|
|
144
|
+
this.currentUnit = unit;
|
|
145
|
+
this.sourceObservations.beginUnit({
|
|
146
|
+
unitType: unit.type,
|
|
147
|
+
unitId: unit.id,
|
|
148
|
+
startedAt: unit.startedAt,
|
|
149
|
+
basePath: unit.workspaceRoot ?? this.basePath,
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
clearCurrentUnit(this: any) {
|
|
153
|
+
this.currentUnit = null;
|
|
154
|
+
this.sourceObservations.clear();
|
|
155
|
+
},
|
|
141
156
|
clearTimers: () => {},
|
|
142
157
|
lockBasePath: "/tmp/project",
|
|
143
158
|
...overrides,
|
|
@@ -216,6 +216,30 @@ test("dispatch-rule-coverage: planning with active slice and skip_research → p
|
|
|
216
216
|
);
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
+
test("dispatch-rule-coverage: planning boundary without planner handoff → research-slice", async (t) => {
|
|
220
|
+
const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-planning-"));
|
|
221
|
+
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
222
|
+
|
|
223
|
+
writeMilestoneFile(tmp, "M001", "CONTEXT", "# Context\n");
|
|
224
|
+
writeMilestoneFile(tmp, "M001", "ROADMAP", "# Roadmap\n");
|
|
225
|
+
|
|
226
|
+
const state = makeState({
|
|
227
|
+
phase: "planning",
|
|
228
|
+
activeSlice: { id: "S01", title: "First Slice" },
|
|
229
|
+
nextAction: "Plan slice S01 (First Slice).",
|
|
230
|
+
});
|
|
231
|
+
const match = await findFirstMatch(makeCtx(tmp, state));
|
|
232
|
+
assertMatch(
|
|
233
|
+
match,
|
|
234
|
+
{
|
|
235
|
+
ruleName: "planning (no research, not S01) → research-slice",
|
|
236
|
+
action: "dispatch",
|
|
237
|
+
unitType: "research-slice",
|
|
238
|
+
},
|
|
239
|
+
"planning boundary without planner handoff",
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
219
243
|
test("dispatch-rule-coverage: executing with task plan present → execute-task", async (t) => {
|
|
220
244
|
const tmp = mkdtempSync(join(tmpdir(), "gsd-disp-cov-exec-"));
|
|
221
245
|
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
|
@@ -109,22 +109,12 @@ test("checkEngineHealth treats explicit reopen as authoritative when dispatch ti
|
|
|
109
109
|
worker_id, host, pid, started_at, version, last_heartbeat_at, status, project_root_realpath
|
|
110
110
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
111
111
|
).run("worker-1", "localhost", 1, "2026-01-01T00:00:00.000Z", "test", "2026-01-01T00:00:00.000Z", "stopped", base);
|
|
112
|
-
db.exec("PRAGMA writable_schema = ON");
|
|
113
112
|
db.prepare(
|
|
114
|
-
`UPDATE sqlite_schema
|
|
115
|
-
SET sql = replace(sql, 'started_at TEXT NOT NULL', 'started_at TEXT')
|
|
116
|
-
WHERE type = 'table' AND name = 'unit_dispatches'`,
|
|
117
|
-
).run();
|
|
118
|
-
db.exec("PRAGMA writable_schema = OFF");
|
|
119
|
-
closeDatabase();
|
|
120
|
-
openDatabase(join(gsdDir, "gsd.db"));
|
|
121
|
-
const reopenedDb = _getAdapter()!;
|
|
122
|
-
reopenedDb.prepare(
|
|
123
113
|
`INSERT INTO unit_dispatches (
|
|
124
114
|
trace_id, worker_id, milestone_lease_token, milestone_id,
|
|
125
115
|
unit_type, unit_id, status, attempt_n, started_at, ended_at
|
|
126
116
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
127
|
-
).run("trace-1", "worker-1", 1, "M001", "complete-milestone", "M001", "completed", 1,
|
|
117
|
+
).run("trace-1", "worker-1", 1, "M001", "complete-milestone", "M001", "completed", 1, "", "");
|
|
128
118
|
appendEvent(base, {
|
|
129
119
|
cmd: "reopen-milestone",
|
|
130
120
|
params: { milestoneId: "M001" },
|
|
@@ -19,10 +19,12 @@ import {
|
|
|
19
19
|
saveGateResult,
|
|
20
20
|
markAllGatesOmitted,
|
|
21
21
|
getPendingSliceGateCount,
|
|
22
|
+
getGateResults,
|
|
22
23
|
} from "../gsd-db.ts";
|
|
23
24
|
import { deriveState, invalidateStateCache } from "../state.ts";
|
|
24
25
|
import { renderPlanFromDb } from "../markdown-renderer.ts";
|
|
25
26
|
import { invalidateAllCaches } from "../cache.ts";
|
|
27
|
+
import { DISPATCH_RULES } from "../auto-dispatch.ts";
|
|
26
28
|
|
|
27
29
|
function setupTestProject(): { tmpDir: string; dbPath: string } {
|
|
28
30
|
const tmpDir = mkdtempSync(join(tmpdir(), "gate-dispatch-"));
|
|
@@ -213,4 +215,66 @@ describe("evaluating-gates phase", () => {
|
|
|
213
215
|
`pending Q8 must not stall evaluating-gates — got phase=${state.phase}`,
|
|
214
216
|
);
|
|
215
217
|
});
|
|
218
|
+
|
|
219
|
+
test("gate-evaluate dispatch id includes only gate-evaluate-owned gates", async () => {
|
|
220
|
+
planSlice(tmpDir);
|
|
221
|
+
await renderPlanFromDb(tmpDir, "M001", "S01");
|
|
222
|
+
|
|
223
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
|
|
224
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", scope: "slice" });
|
|
225
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q8", scope: "slice" });
|
|
226
|
+
|
|
227
|
+
invalidateStateCache();
|
|
228
|
+
const state = await deriveState(tmpDir);
|
|
229
|
+
assert.equal(state.phase, "evaluating-gates");
|
|
230
|
+
|
|
231
|
+
const rule = DISPATCH_RULES.find((candidate) => candidate.name === "evaluating-gates → gate-evaluate");
|
|
232
|
+
assert.ok(rule, "gate-evaluate dispatch rule must exist");
|
|
233
|
+
|
|
234
|
+
const result = await rule.match({
|
|
235
|
+
basePath: tmpDir,
|
|
236
|
+
mid: "M001",
|
|
237
|
+
midTitle: "Test Milestone",
|
|
238
|
+
state,
|
|
239
|
+
prefs: { gate_evaluation: { enabled: true } },
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
assert.ok(result);
|
|
243
|
+
assert.equal(result.action, "dispatch");
|
|
244
|
+
if (result.action !== "dispatch") throw new Error("expected gate-evaluate dispatch");
|
|
245
|
+
assert.equal(result.unitId, "M001/S01/gates+Q3,Q4");
|
|
246
|
+
assert.doesNotMatch(result.prompt, /\*\*Q8\*\*/);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("disabled gate evaluation only omits gate-evaluate-owned gates", async () => {
|
|
250
|
+
planSlice(tmpDir);
|
|
251
|
+
await renderPlanFromDb(tmpDir, "M001", "S01");
|
|
252
|
+
|
|
253
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
|
|
254
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q4", scope: "slice" });
|
|
255
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q8", scope: "slice" });
|
|
256
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q5", scope: "task", taskId: "T01" });
|
|
257
|
+
|
|
258
|
+
invalidateStateCache();
|
|
259
|
+
const state = await deriveState(tmpDir);
|
|
260
|
+
assert.equal(state.phase, "evaluating-gates");
|
|
261
|
+
|
|
262
|
+
const rule = DISPATCH_RULES.find((candidate) => candidate.name === "evaluating-gates → gate-evaluate");
|
|
263
|
+
assert.ok(rule, "gate-evaluate dispatch rule must exist");
|
|
264
|
+
|
|
265
|
+
const result = await rule.match({
|
|
266
|
+
basePath: tmpDir,
|
|
267
|
+
mid: "M001",
|
|
268
|
+
midTitle: "Test Milestone",
|
|
269
|
+
state,
|
|
270
|
+
prefs: { gate_evaluation: { enabled: false } },
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
assert.equal(result?.action, "skip");
|
|
274
|
+
const byId = new Map(getGateResults("M001", "S01").map((gate) => [gate.gate_id, gate]));
|
|
275
|
+
assert.equal(byId.get("Q3")?.verdict, "omitted");
|
|
276
|
+
assert.equal(byId.get("Q4")?.verdict, "omitted");
|
|
277
|
+
assert.equal(byId.get("Q8")?.status, "pending");
|
|
278
|
+
assert.equal(byId.get("Q5")?.status, "pending");
|
|
279
|
+
});
|
|
216
280
|
});
|
|
@@ -83,6 +83,21 @@ describe("quality_gates CRUD", () => {
|
|
|
83
83
|
assert.ok(results[0].evaluated_at);
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
+
test("saveGateResult throws when the gate row does not exist", () => {
|
|
87
|
+
assert.throws(
|
|
88
|
+
() => saveGateResult({
|
|
89
|
+
milestoneId: "M001",
|
|
90
|
+
sliceId: "S01",
|
|
91
|
+
gateId: "Q3",
|
|
92
|
+
verdict: "pass",
|
|
93
|
+
rationale: "No row exists.",
|
|
94
|
+
findings: "",
|
|
95
|
+
}),
|
|
96
|
+
/quality gate row not found/,
|
|
97
|
+
);
|
|
98
|
+
assert.equal(getGateResults("M001", "S01").length, 0);
|
|
99
|
+
});
|
|
100
|
+
|
|
86
101
|
test("getPendingGates filters by scope", () => {
|
|
87
102
|
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
|
|
88
103
|
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q5", scope: "task", taskId: "T01" });
|
|
@@ -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
|
});
|