@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
|
@@ -723,7 +723,9 @@ test('(u) run-uat prompt promotes artifact-driven browser specs to browser-execu
|
|
|
723
723
|
|
|
724
724
|
assert.match(prompt, /\*\*Detected UAT mode:\*\*\s*`browser-executable`/);
|
|
725
725
|
assert.match(prompt, /uatType: "browser-executable"/);
|
|
726
|
-
assert.match(prompt, /use
|
|
726
|
+
assert.match(prompt, /use browser tools/i);
|
|
727
|
+
assert.match(prompt, /"browser_navigate"/);
|
|
728
|
+
assert.match(prompt, /"browser_assert"/);
|
|
727
729
|
} finally {
|
|
728
730
|
cleanup(base);
|
|
729
731
|
}
|
|
@@ -741,6 +743,7 @@ test('(v) run-uat prompt keeps deferred browser work artifact-driven', async ()
|
|
|
741
743
|
assert.match(prompt, /\*\*Detected UAT mode:\*\*\s*`artifact-driven`/);
|
|
742
744
|
assert.match(prompt, /uatType: "artifact-driven"/);
|
|
743
745
|
assert.doesNotMatch(prompt, /uatType: "browser-executable"/);
|
|
746
|
+
assert.doesNotMatch(prompt, /"browser_navigate"/);
|
|
744
747
|
} finally {
|
|
745
748
|
cleanup(base);
|
|
746
749
|
}
|
|
@@ -226,6 +226,33 @@ test("direct /gsd auto skips paused-session replay when recovered unit already c
|
|
|
226
226
|
}
|
|
227
227
|
});
|
|
228
228
|
|
|
229
|
+
test("paused-session resume skips replay when unit identity was never recorded", () => {
|
|
230
|
+
const base = makeTmpBase();
|
|
231
|
+
try {
|
|
232
|
+
// No currentUnit and no persisted unit type/id — identity is unknown. The
|
|
233
|
+
// old code fell back to the literal "unknown" unit, which can neither be
|
|
234
|
+
// verified nor correctly targeted, and synthesized a full tool-call replay
|
|
235
|
+
// (the thrash that turns one stuck unit into several). The fix skips the
|
|
236
|
+
// replay and resumes from rebuilt disk state instead.
|
|
237
|
+
const state = {
|
|
238
|
+
pausedSessionFile: join(base, ".gsd", "activity", "paused-session.jsonl"),
|
|
239
|
+
currentUnit: null,
|
|
240
|
+
pausedUnitType: null,
|
|
241
|
+
pausedUnitId: null,
|
|
242
|
+
pendingCrashRecovery: "stale-recovery-prompt",
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const result = _handlePausedSessionResumeRecoveryForTest(base, state);
|
|
246
|
+
assert.equal(result.skippedReplay, true);
|
|
247
|
+
assert.equal(state.pausedSessionFile, null);
|
|
248
|
+
assert.equal(state.pendingCrashRecovery, null, "must not synthesize a replay for an unknown unit");
|
|
249
|
+
assert.equal(state.pausedUnitType, null);
|
|
250
|
+
assert.equal(state.pausedUnitId, null);
|
|
251
|
+
} finally {
|
|
252
|
+
cleanup(base);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
229
256
|
test("interrupted-session source preserves raw lock and excludes same-pid from running classification", async () => {
|
|
230
257
|
const source = await import(`node:fs/promises`).then((fs) =>
|
|
231
258
|
fs.readFile(new URL("../interrupted-session.ts", import.meta.url), "utf-8")
|
|
@@ -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,
|
|
@@ -200,12 +202,26 @@ function makeSession() {
|
|
|
200
202
|
unitLifetimeDispatches: new Map<string, number>(),
|
|
201
203
|
unitRecoveryCount: new Map<string, number>(),
|
|
202
204
|
verificationRetryCount: new Map<string, number>(),
|
|
205
|
+
zeroToolRetryCount: new Map<string, number>(),
|
|
203
206
|
gitService: null,
|
|
204
207
|
autoStartTime: Date.now(),
|
|
205
208
|
cmdCtx: {
|
|
206
209
|
newSession: () => Promise.resolve({ cancelled: false }),
|
|
207
210
|
getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
|
|
208
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
|
+
},
|
|
209
225
|
clearTimers: () => {},
|
|
210
226
|
} as any;
|
|
211
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');
|
|
@@ -54,7 +54,13 @@ test("ensureProjectWorkflowMcpConfig creates .mcp.json with workflow and browser
|
|
|
54
54
|
"--identity-scope",
|
|
55
55
|
"project",
|
|
56
56
|
]);
|
|
57
|
-
|
|
57
|
+
// --identity-scope requires a non-empty --identity-key or gsd-browser exits
|
|
58
|
+
// immediately ("Connection closed"); the key must be stable per project.
|
|
59
|
+
assert.equal(browserArgs[mcpArgIndex + 5], "--identity-key");
|
|
60
|
+
assert.equal(typeof browserArgs[mcpArgIndex + 6], "string");
|
|
61
|
+
assert.ok((browserArgs[mcpArgIndex + 6] ?? "").length > 0, "identity-key must be non-empty");
|
|
62
|
+
assert.equal(browserArgs[mcpArgIndex + 7], "--identity-project");
|
|
63
|
+
assert.equal(browserArgs[mcpArgIndex + 8], projectRoot);
|
|
58
64
|
assert.equal((browserServer as { cwd?: string })?.cwd, projectRoot);
|
|
59
65
|
|
|
60
66
|
const settings = JSON.parse(readFileSync(join(projectRoot, ".claude", "settings.local.json"), "utf-8")) as {
|
|
@@ -342,7 +342,7 @@ describe("formatMcpInitResult", () => {
|
|
|
342
342
|
assert.match(result, /\/tmp\/project\/\.mcp\.json/);
|
|
343
343
|
assert.match(result, /mcp-capable clients/i);
|
|
344
344
|
assert.match(result, /workflow and gsd-browser MCP servers/i);
|
|
345
|
-
assert.match(result, /Pi Providers use
|
|
345
|
+
assert.match(result, /Pi Providers use built-in browser tools/i);
|
|
346
346
|
assert.doesNotMatch(result, /claude code/i);
|
|
347
347
|
});
|
|
348
348
|
|
|
@@ -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'));
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { test, describe } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
import { GSD_COMMAND_DESCRIPTION, getGsdArgumentCompletions, TOP_LEVEL_SUBCOMMANDS } from "../commands/catalog.ts";
|
|
8
|
+
import { handleCoreCommand } from "../commands/handlers/core.ts";
|
|
9
|
+
import { DISPATCH_RULES } from "../auto-dispatch.ts";
|
|
10
|
+
import {
|
|
11
|
+
buildGsdPlannerSpawnPlan,
|
|
12
|
+
formatGsdPlannerCommand,
|
|
13
|
+
hasPlannerHandoffBeenOffered,
|
|
14
|
+
markPlannerHandoffOffered,
|
|
15
|
+
PLANNER_HANDOFF_RULE_NAME,
|
|
16
|
+
} from "../planner-handoff.ts";
|
|
17
|
+
|
|
18
|
+
describe("planner handoff command catalog", () => {
|
|
19
|
+
test("/gsd planner is hidden from description and completions", () => {
|
|
20
|
+
assert.doesNotMatch(GSD_COMMAND_DESCRIPTION, /\|planner(?:\||$)/);
|
|
21
|
+
assert.equal(
|
|
22
|
+
TOP_LEVEL_SUBCOMMANDS.some((command) => command.cmd === "planner"),
|
|
23
|
+
false,
|
|
24
|
+
"planner should not appear in top-level commands",
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const completions = getGsdArgumentCompletions("pla");
|
|
28
|
+
|
|
29
|
+
assert.equal(
|
|
30
|
+
completions.some((completion) => completion.value === "planner"),
|
|
31
|
+
false,
|
|
32
|
+
"planner should not appear in top-level completions",
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
assert.deepEqual(
|
|
36
|
+
getGsdArgumentCompletions("planner --"),
|
|
37
|
+
[],
|
|
38
|
+
"planner should not expose nested completions",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("planner handoff command handler", () => {
|
|
44
|
+
test("/gsd planner falls through to the unknown-command path", async () => {
|
|
45
|
+
const notifications: Array<{ message: string; level?: string }> = [];
|
|
46
|
+
const ctx = {
|
|
47
|
+
ui: {
|
|
48
|
+
notify(message: string, level?: string) {
|
|
49
|
+
notifications.push({ message, level });
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handled = await handleCoreCommand("planner M001 --dry-run --inspect", ctx as any);
|
|
55
|
+
|
|
56
|
+
assert.equal(handled, false);
|
|
57
|
+
assert.deepEqual(notifications, []);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("planner handoff launcher", () => {
|
|
62
|
+
test("builds gsd-planner command with project and milestone context", () => {
|
|
63
|
+
const plan = buildGsdPlannerSpawnPlan({
|
|
64
|
+
basePath: "/tmp/project with spaces",
|
|
65
|
+
milestoneId: "M001",
|
|
66
|
+
extraArgs: ["--inspect"],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
assert.deepEqual(plan, {
|
|
70
|
+
command: "gsd-planner",
|
|
71
|
+
args: ["--project", "/tmp/project with spaces", "--milestone", "M001", "--inspect"],
|
|
72
|
+
cwd: "/tmp/project with spaces",
|
|
73
|
+
});
|
|
74
|
+
assert.equal(
|
|
75
|
+
formatGsdPlannerCommand(plan),
|
|
76
|
+
'gsd-planner --project "/tmp/project with spaces" --milestone M001 --inspect',
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("records one-shot handoff markers per milestone", () => {
|
|
81
|
+
const basePath = mkdtempSync(join(tmpdir(), "gsd-planner-marker-"));
|
|
82
|
+
try {
|
|
83
|
+
assert.equal(hasPlannerHandoffBeenOffered(basePath, "M001"), false);
|
|
84
|
+
markPlannerHandoffOffered(basePath, "M001");
|
|
85
|
+
assert.equal(hasPlannerHandoffBeenOffered(basePath, "M001"), true);
|
|
86
|
+
assert.equal(hasPlannerHandoffBeenOffered(basePath, "M002"), false);
|
|
87
|
+
} finally {
|
|
88
|
+
rmSync(basePath, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("planner handoff dispatch rule", () => {
|
|
94
|
+
test("rule is not registered while /gsd planner is disabled", () => {
|
|
95
|
+
assert.equal(
|
|
96
|
+
DISPATCH_RULES.some((rule) => rule.name === PLANNER_HANDOFF_RULE_NAME),
|
|
97
|
+
false,
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -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 },
|