@opengsd/gsd-pi 1.1.1-dev.74e8dd1 → 1.1.1-dev.75048e7
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/cli.js +3 -2
- package/dist/help-text.js +10 -6
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +495 -0
- package/dist/resources/extensions/browser-tools/engine/selection.js +16 -0
- package/dist/resources/extensions/browser-tools/extension-manifest.json +2 -2
- package/dist/resources/extensions/browser-tools/index.js +57 -9
- package/dist/resources/extensions/browser-tools/package.json +5 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +0 -1
- package/dist/resources/extensions/gsd/auto-dashboard.js +77 -13
- package/dist/resources/extensions/gsd/auto-dispatch.js +16 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +21 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +63 -22
- package/dist/resources/extensions/gsd/auto-recovery.js +3 -4
- package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +18 -66
- package/dist/resources/extensions/gsd/auto-worktree.js +18 -5
- package/dist/resources/extensions/gsd/auto.js +9 -2
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +20 -14
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +28 -13
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +18 -29
- package/dist/resources/extensions/gsd/browser-evidence.js +29 -2
- package/dist/resources/extensions/gsd/closeout-consistency-gate.js +61 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +76 -11
- package/dist/resources/extensions/gsd/commands-mcp-status.js +2 -1
- package/dist/resources/extensions/gsd/dashboard-overlay.js +21 -7
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +2 -2
- package/dist/resources/extensions/gsd/escalation.js +4 -4
- package/dist/resources/extensions/gsd/forensics.js +74 -2
- package/dist/resources/extensions/gsd/gsd-db.js +5 -2
- package/dist/resources/extensions/gsd/guided-flow.js +118 -175
- package/dist/resources/extensions/gsd/mcp-project-config.js +9 -76
- package/dist/resources/extensions/gsd/memory-store.js +4 -1
- package/dist/resources/extensions/gsd/milestone-closeout.js +3 -1
- package/dist/resources/extensions/gsd/pending-auto-start.js +0 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.js +9 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
- package/dist/resources/extensions/gsd/prompts/forensics.md +61 -1
- package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -21
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
- package/dist/resources/extensions/gsd/recovery-classification.js +20 -0
- package/dist/resources/extensions/gsd/rule-registry.js +428 -52
- package/dist/resources/extensions/gsd/state.js +2 -2
- package/dist/resources/extensions/gsd/templates/plan.md +3 -1
- package/dist/resources/extensions/gsd/tool-contract.js +5 -0
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +30 -7
- package/dist/resources/extensions/gsd/tools/complete-slice.js +15 -1
- package/dist/resources/extensions/gsd/tools/complete-task.js +11 -1
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +46 -16
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +132 -18
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +169 -0
- package/dist/resources/extensions/gsd/verdict-parser.js +59 -15
- package/dist/resources/extensions/gsd/verification-gate.js +72 -1
- package/dist/resources/extensions/gsd/workflow-mcp.js +3 -75
- package/dist/resources/extensions/shared/gsd-browser-cli.js +145 -0
- package/dist/rtk.d.ts +7 -1
- package/dist/rtk.js +27 -11
- package/dist/update-check.d.ts +15 -1
- package/dist/update-check.js +87 -12
- package/dist/update-cmd.d.ts +1 -0
- package/dist/update-cmd.js +53 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +4 -2
- 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/dist/agent-session.d.ts +9 -0
- package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/agent-session.js +32 -0
- package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
- package/packages/gsd-agent-core/dist/index.d.ts +1 -0
- package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/index.js +1 -0
- package/packages/gsd-agent-core/dist/index.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts +2 -0
- package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-compaction.js +8 -2
- package/packages/gsd-agent-core/dist/session/agent-session-compaction.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +7 -0
- package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +69 -1
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
- package/packages/gsd-agent-core/dist/turn-latency.d.ts +47 -0
- package/packages/gsd-agent-core/dist/turn-latency.d.ts.map +1 -0
- package/packages/gsd-agent-core/dist/turn-latency.js +123 -0
- package/packages/gsd-agent-core/dist/turn-latency.js.map +1 -0
- package/packages/gsd-agent-core/package.json +6 -6
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +21 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +213 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +1 -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 +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.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 +20 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +7 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js +6 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-command-handlers.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +23 -9
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +2 -2
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +42 -3
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +5 -1
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.js +3 -1
- package/packages/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
- package/packages/pi-agent-core/dist/harness/types.d.ts +1 -0
- package/packages/pi-agent-core/dist/harness/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/types.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +6 -1
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/api-registry.d.ts +2 -0
- package/packages/pi-ai/dist/api-registry.d.ts.map +1 -1
- package/packages/pi-ai/dist/api-registry.js +23 -0
- package/packages/pi-ai/dist/api-registry.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +74 -23
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +82 -31
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/stream.js +6 -6
- package/packages/pi-ai/dist/stream.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +2 -2
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +2 -2
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +3 -2
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/tools/render-utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/render-utils.js +6 -0
- package/packages/pi-coding-agent/dist/core/tools/render-utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +3 -2
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +579 -0
- package/src/resources/extensions/browser-tools/engine/selection.ts +19 -0
- package/src/resources/extensions/browser-tools/extension-manifest.json +2 -2
- package/src/resources/extensions/browser-tools/index.ts +60 -9
- package/src/resources/extensions/browser-tools/package.json +5 -1
- package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +35 -0
- package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +33 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +0 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +82 -14
- package/src/resources/extensions/gsd/auto-dispatch.ts +19 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +28 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +97 -15
- package/src/resources/extensions/gsd/auto-recovery.ts +3 -3
- package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -0
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +43 -74
- package/src/resources/extensions/gsd/auto-worktree.ts +23 -5
- package/src/resources/extensions/gsd/auto.ts +12 -2
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +20 -14
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +32 -13
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +50 -54
- package/src/resources/extensions/gsd/browser-evidence.ts +26 -2
- package/src/resources/extensions/gsd/closeout-consistency-gate.ts +137 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +76 -11
- package/src/resources/extensions/gsd/commands-mcp-status.ts +2 -1
- package/src/resources/extensions/gsd/dashboard-overlay.ts +28 -7
- package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +2 -2
- package/src/resources/extensions/gsd/escalation.ts +4 -4
- package/src/resources/extensions/gsd/forensics.ts +99 -5
- package/src/resources/extensions/gsd/gsd-db.ts +5 -2
- package/src/resources/extensions/gsd/guided-flow.ts +214 -216
- package/src/resources/extensions/gsd/mcp-project-config.ts +13 -78
- package/src/resources/extensions/gsd/memory-store.ts +4 -1
- package/src/resources/extensions/gsd/milestone-closeout.ts +3 -1
- package/src/resources/extensions/gsd/pending-auto-start.ts +0 -2
- package/src/resources/extensions/gsd/post-unit-hooks.ts +14 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +36 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
- package/src/resources/extensions/gsd/prompts/forensics.md +61 -1
- package/src/resources/extensions/gsd/prompts/gate-evaluate.md +3 -1
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +3 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +3 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +25 -21
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
- package/src/resources/extensions/gsd/recovery-classification.ts +20 -0
- package/src/resources/extensions/gsd/rule-registry.ts +558 -58
- package/src/resources/extensions/gsd/rule-types.ts +2 -0
- package/src/resources/extensions/gsd/state.ts +2 -2
- package/src/resources/extensions/gsd/templates/plan.md +3 -1
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +105 -4
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +10 -2
- package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/browser-evidence.test.ts +142 -0
- package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +26 -16
- package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/deep-planning-mode-dispatch.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/discuss-milestone-structured-questions.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +40 -1
- package/src/resources/extensions/gsd/tests/doctor-runtime-checks.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/escalation.test.ts +16 -27
- package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/forensics-prompt-rendering.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/forensics-tool-scope.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +31 -79
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +40 -1
- package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +40 -4
- package/src/resources/extensions/gsd/tests/guided-flow.test.ts +12 -9
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +72 -10
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +39 -8
- package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/new-milestone-discuss-routing.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +179 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +73 -1
- package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +410 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -0
- package/src/resources/extensions/gsd/tool-contract.ts +6 -0
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +63 -7
- package/src/resources/extensions/gsd/tools/complete-slice.ts +14 -1
- package/src/resources/extensions/gsd/tools/complete-task.ts +20 -2
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +46 -15
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +163 -20
- package/src/resources/extensions/gsd/types.ts +69 -5
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +186 -0
- package/src/resources/extensions/gsd/verdict-parser.ts +54 -13
- package/src/resources/extensions/gsd/verification-gate.ts +87 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +3 -75
- package/src/resources/extensions/shared/gsd-browser-cli.ts +172 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +0 -246
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +0 -218
- /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → h4TGni4xJzlZjGkxaT6uU}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → h4TGni4xJzlZjGkxaT6uU}/_ssgManifest.js +0 -0
|
@@ -10,6 +10,9 @@ import { resolvePostUnitHooks, resolvePreDispatchHooks } from "./preferences.js"
|
|
|
10
10
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { parseUnitId } from "./unit-id.js";
|
|
13
|
+
import { queryJournal } from "./journal.js";
|
|
14
|
+
import { readUnitRuntimeRecord } from "./unit-runtime.js";
|
|
15
|
+
import { extractFrontmatterVerdict } from "./verdict-parser.js";
|
|
13
16
|
// ─── Artifact Path Resolution ──────────────────────────────────────────────
|
|
14
17
|
export function resolveHookArtifactPath(basePath, unitId, artifactName) {
|
|
15
18
|
const { milestone, slice, task } = parseUnitId(unitId);
|
|
@@ -38,6 +41,28 @@ export function convertDispatchRules(rules) {
|
|
|
38
41
|
}
|
|
39
42
|
// ─── RuleRegistry ─────────────────────────────────────────────────────────
|
|
40
43
|
const HOOK_STATE_FILE = "hook-state.json";
|
|
44
|
+
const FAILED_HOOK_RUNTIME_PHASES = new Set([
|
|
45
|
+
"timeout",
|
|
46
|
+
"finalize-timeout",
|
|
47
|
+
"crashed",
|
|
48
|
+
"paused",
|
|
49
|
+
]);
|
|
50
|
+
const HOOK_OUTCOME_VERDICTS = new Set([
|
|
51
|
+
"pass",
|
|
52
|
+
"advisory",
|
|
53
|
+
"needs-rework",
|
|
54
|
+
"needs-remediation",
|
|
55
|
+
"needs-attention",
|
|
56
|
+
]);
|
|
57
|
+
function isBlockingHook(config) {
|
|
58
|
+
return config?.criticality === "blocking";
|
|
59
|
+
}
|
|
60
|
+
function hookMaxCycles(config) {
|
|
61
|
+
return config.max_cycles ?? 1;
|
|
62
|
+
}
|
|
63
|
+
function hookCycleKey(config, trigger) {
|
|
64
|
+
return `${config.name}/${trigger.triggerUnitType}/${trigger.triggerUnitId}`;
|
|
65
|
+
}
|
|
41
66
|
export class RuleRegistry {
|
|
42
67
|
/** Static dispatch rules provided at construction time. */
|
|
43
68
|
dispatchRules;
|
|
@@ -47,6 +72,8 @@ export class RuleRegistry {
|
|
|
47
72
|
cycleCounts = new Map();
|
|
48
73
|
retryPending = false;
|
|
49
74
|
retryTrigger = null;
|
|
75
|
+
hookFailure = null;
|
|
76
|
+
gateBlockPending = null;
|
|
50
77
|
constructor(dispatchRules) {
|
|
51
78
|
this.dispatchRules = dispatchRules;
|
|
52
79
|
}
|
|
@@ -71,6 +98,7 @@ export class RuleRegistry {
|
|
|
71
98
|
artifact: hook.artifact,
|
|
72
99
|
retry_on: hook.retry_on,
|
|
73
100
|
max_cycles: hook.max_cycles,
|
|
101
|
+
criticality: hook.criticality,
|
|
74
102
|
},
|
|
75
103
|
});
|
|
76
104
|
}
|
|
@@ -117,7 +145,9 @@ export class RuleRegistry {
|
|
|
117
145
|
evaluatePostUnit(completedUnitType, completedUnitId, basePath) {
|
|
118
146
|
// If we just completed a hook unit, handle its result
|
|
119
147
|
if (this.activeHook) {
|
|
120
|
-
|
|
148
|
+
const observedCleanExecution = completedUnitType === `hook/${this.activeHook.hookName}` &&
|
|
149
|
+
completedUnitId === this.activeHook.triggerUnitId;
|
|
150
|
+
return this._handleHookCompletion(basePath, observedCleanExecution);
|
|
121
151
|
}
|
|
122
152
|
// Don't trigger hooks for other hook units (prevent hook-on-hook chains)
|
|
123
153
|
// Don't trigger hooks for triage units or quick-task units
|
|
@@ -141,75 +171,371 @@ export class RuleRegistry {
|
|
|
141
171
|
_dequeueNextHook(basePath) {
|
|
142
172
|
while (this.hookQueue.length > 0) {
|
|
143
173
|
const entry = this.hookQueue.shift();
|
|
144
|
-
const { config, triggerUnitType, triggerUnitId } = entry;
|
|
145
|
-
//
|
|
146
|
-
|
|
174
|
+
const { config, triggerUnitType, triggerUnitId, forceRun } = entry;
|
|
175
|
+
// Advisory hooks preserve existing idempotency: any configured artifact
|
|
176
|
+
// means the hook already ran. Blocking gates must verify outcome first.
|
|
177
|
+
if (config.artifact && !forceRun) {
|
|
147
178
|
const artifactPath = resolveHookArtifactPath(basePath, triggerUnitId, config.artifact);
|
|
148
|
-
if (existsSync(artifactPath))
|
|
149
|
-
|
|
179
|
+
if (existsSync(artifactPath)) {
|
|
180
|
+
const completion = this._assessConfiguredHookCompletion(basePath, config.name, triggerUnitId);
|
|
181
|
+
if (completion.outcome === "failed") {
|
|
182
|
+
return this._handleFailedHookCompletion(basePath, {
|
|
183
|
+
hookName: config.name,
|
|
184
|
+
triggerUnitType,
|
|
185
|
+
triggerUnitId,
|
|
186
|
+
cycle: this.cycleCounts.get(hookCycleKey(config, { triggerUnitType, triggerUnitId })) ?? 0,
|
|
187
|
+
pendingRetry: false,
|
|
188
|
+
}, config, completion.reason);
|
|
189
|
+
}
|
|
190
|
+
if (!isBlockingHook(config))
|
|
191
|
+
continue;
|
|
192
|
+
const decision = this._handleExistingBlockingArtifact(config, { triggerUnitType, triggerUnitId }, basePath);
|
|
193
|
+
if (decision === "skip")
|
|
194
|
+
continue;
|
|
195
|
+
return decision;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const dispatch = this._startHook(config, triggerUnitType, triggerUnitId);
|
|
199
|
+
if (dispatch)
|
|
200
|
+
return dispatch;
|
|
201
|
+
if (isBlockingHook(config)) {
|
|
202
|
+
const cycleKey = hookCycleKey(config, { triggerUnitType, triggerUnitId });
|
|
203
|
+
const maxCycles = hookMaxCycles(config);
|
|
204
|
+
const currentCycle = this.cycleCounts.get(cycleKey) ?? 0;
|
|
205
|
+
if (currentCycle >= maxCycles) {
|
|
206
|
+
this._setGateBlock(config, { triggerUnitType, triggerUnitId }, {
|
|
207
|
+
action: "pause",
|
|
208
|
+
reason: `gate cycle budget exhausted before ${config.name} produced a passing outcome`,
|
|
209
|
+
cycle: currentCycle,
|
|
210
|
+
maxCycles,
|
|
211
|
+
});
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
150
214
|
}
|
|
151
|
-
// Check cycle limit
|
|
152
|
-
const cycleKey = `${config.name}/${triggerUnitType}/${triggerUnitId}`;
|
|
153
|
-
const currentCycle = (this.cycleCounts.get(cycleKey) ?? 0) + 1;
|
|
154
|
-
const maxCycles = config.max_cycles ?? 1;
|
|
155
|
-
if (currentCycle > maxCycles)
|
|
156
|
-
continue;
|
|
157
|
-
this.cycleCounts.set(cycleKey, currentCycle);
|
|
158
|
-
this.activeHook = {
|
|
159
|
-
hookName: config.name,
|
|
160
|
-
triggerUnitType,
|
|
161
|
-
triggerUnitId,
|
|
162
|
-
cycle: currentCycle,
|
|
163
|
-
pendingRetry: false,
|
|
164
|
-
};
|
|
165
|
-
// Build prompt with variable substitution
|
|
166
|
-
const { milestone: mid, slice: sid, task: tid } = parseUnitId(triggerUnitId);
|
|
167
|
-
let prompt = config.prompt
|
|
168
|
-
.replace(/\{milestoneId\}/g, mid ?? "")
|
|
169
|
-
.replace(/\{sliceId\}/g, sid ?? "")
|
|
170
|
-
.replace(/\{taskId\}/g, tid ?? "");
|
|
171
|
-
// Inject browser safety instruction
|
|
172
|
-
prompt += "\n\n**Browser tool safety:** Do NOT use `browser_wait_for` with `condition: \"network_idle\"` — it hangs indefinitely when dev servers keep persistent connections (Vite HMR, WebSocket). Use `selector_visible`, `text_visible`, or `delay` instead.";
|
|
173
|
-
return {
|
|
174
|
-
hookName: config.name,
|
|
175
|
-
prompt,
|
|
176
|
-
model: config.model,
|
|
177
|
-
unitType: `hook/${config.name}`,
|
|
178
|
-
unitId: triggerUnitId,
|
|
179
|
-
};
|
|
180
215
|
}
|
|
181
216
|
// No more hooks — clear active state
|
|
182
217
|
this.activeHook = null;
|
|
183
218
|
return null;
|
|
184
219
|
}
|
|
185
|
-
_handleHookCompletion(basePath) {
|
|
220
|
+
_handleHookCompletion(basePath, observedCleanExecution) {
|
|
186
221
|
const hook = this.activeHook;
|
|
187
222
|
const hooks = resolvePostUnitHooks(basePath);
|
|
188
223
|
const config = hooks.find(h => h.name === hook.hookName);
|
|
224
|
+
if (!config) {
|
|
225
|
+
this.activeHook = null;
|
|
226
|
+
return this._dequeueNextHook(basePath);
|
|
227
|
+
}
|
|
228
|
+
const completion = this._assessHookCompletion(basePath, hook);
|
|
229
|
+
if (completion.outcome === "failed") {
|
|
230
|
+
return this._handleFailedHookCompletion(basePath, hook, config, completion.reason);
|
|
231
|
+
}
|
|
189
232
|
// Check if retry was requested via retry_on artifact
|
|
190
|
-
if (config
|
|
233
|
+
if (config.retry_on) {
|
|
191
234
|
const retryArtifactPath = resolveHookArtifactPath(basePath, hook.triggerUnitId, config.retry_on);
|
|
192
235
|
if (existsSync(retryArtifactPath)) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (
|
|
236
|
+
if (this._requestTriggerRetry(config, hook, config.retry_on)) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
if (isBlockingHook(config)) {
|
|
240
|
+
this._setGateBlock(config, hook, {
|
|
241
|
+
action: "pause",
|
|
242
|
+
reason: `gate cycle budget exhausted after ${config.retry_on} requested rework`,
|
|
243
|
+
retryArtifact: config.retry_on,
|
|
244
|
+
});
|
|
197
245
|
this.activeHook = null;
|
|
198
246
|
this.hookQueue = [];
|
|
199
|
-
this.retryPending = true;
|
|
200
|
-
this.retryTrigger = {
|
|
201
|
-
unitType: hook.triggerUnitType,
|
|
202
|
-
unitId: hook.triggerUnitId,
|
|
203
|
-
retryArtifact: config.retry_on,
|
|
204
|
-
};
|
|
205
247
|
return null;
|
|
206
248
|
}
|
|
207
249
|
}
|
|
208
250
|
}
|
|
251
|
+
if (isBlockingHook(config)) {
|
|
252
|
+
return this._handleBlockingGateCompletion(config, hook, basePath, observedCleanExecution);
|
|
253
|
+
}
|
|
209
254
|
// Hook completed normally — try next hook in queue
|
|
210
255
|
this.activeHook = null;
|
|
211
256
|
return this._dequeueNextHook(basePath);
|
|
212
257
|
}
|
|
258
|
+
_startHook(config, triggerUnitType, triggerUnitId) {
|
|
259
|
+
const cycleKey = `${config.name}/${triggerUnitType}/${triggerUnitId}`;
|
|
260
|
+
const currentCycle = (this.cycleCounts.get(cycleKey) ?? 0) + 1;
|
|
261
|
+
const maxCycles = config.max_cycles ?? 1;
|
|
262
|
+
if (currentCycle > maxCycles)
|
|
263
|
+
return null;
|
|
264
|
+
this.cycleCounts.set(cycleKey, currentCycle);
|
|
265
|
+
this.activeHook = {
|
|
266
|
+
hookName: config.name,
|
|
267
|
+
triggerUnitType,
|
|
268
|
+
triggerUnitId,
|
|
269
|
+
cycle: currentCycle,
|
|
270
|
+
pendingRetry: false,
|
|
271
|
+
};
|
|
272
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(triggerUnitId);
|
|
273
|
+
let prompt = config.prompt
|
|
274
|
+
.replace(/\{milestoneId\}/g, mid ?? "")
|
|
275
|
+
.replace(/\{sliceId\}/g, sid ?? "")
|
|
276
|
+
.replace(/\{taskId\}/g, tid ?? "");
|
|
277
|
+
prompt += "\n\n**Browser tool safety:** Do NOT use `browser_wait_for` with `condition: \"network_idle\"` — it hangs indefinitely when dev servers keep persistent connections (Vite HMR, WebSocket). Use `selector_visible`, `text_visible`, or `delay` instead.";
|
|
278
|
+
return {
|
|
279
|
+
hookName: config.name,
|
|
280
|
+
prompt,
|
|
281
|
+
model: config.model,
|
|
282
|
+
unitType: `hook/${config.name}`,
|
|
283
|
+
unitId: triggerUnitId,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
_assessHookCompletion(basePath, hook) {
|
|
287
|
+
return this._assessConfiguredHookCompletion(basePath, hook.hookName, hook.triggerUnitId);
|
|
288
|
+
}
|
|
289
|
+
_assessConfiguredHookCompletion(basePath, hookName, unitId) {
|
|
290
|
+
const unitType = `hook/${hookName}`;
|
|
291
|
+
const latestUnitEnd = this._latestHookUnitEnd(basePath, unitType, unitId);
|
|
292
|
+
if (latestUnitEnd) {
|
|
293
|
+
const data = latestUnitEnd.data ?? {};
|
|
294
|
+
const status = data.status;
|
|
295
|
+
const artifactVerified = data.artifactVerified;
|
|
296
|
+
if (status === "completed" && artifactVerified !== false) {
|
|
297
|
+
return { outcome: "success" };
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
outcome: "failed",
|
|
301
|
+
reason: this._formatHookFailureReason(status, artifactVerified, data.errorContext),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
const runtime = readUnitRuntimeRecord(basePath, unitType, unitId);
|
|
305
|
+
if (runtime && FAILED_HOOK_RUNTIME_PHASES.has(runtime.phase)) {
|
|
306
|
+
return { outcome: "failed", reason: `runtime phase ${runtime.phase}` };
|
|
307
|
+
}
|
|
308
|
+
return { outcome: "unknown" };
|
|
309
|
+
}
|
|
310
|
+
_latestHookUnitEnd(basePath, unitType, unitId) {
|
|
311
|
+
const unitEnds = queryJournal(basePath, { eventType: "unit-end", unitId })
|
|
312
|
+
.filter(entry => entry.data?.unitType === unitType);
|
|
313
|
+
return unitEnds[unitEnds.length - 1] ?? null;
|
|
314
|
+
}
|
|
315
|
+
_formatHookFailureReason(status, artifactVerified, errorContext) {
|
|
316
|
+
const parts = [`status ${typeof status === "string" ? status : "unknown"}`];
|
|
317
|
+
if (artifactVerified === false) {
|
|
318
|
+
parts.push("artifact not verified");
|
|
319
|
+
}
|
|
320
|
+
if (typeof errorContext === "object" && errorContext !== null && "message" in errorContext) {
|
|
321
|
+
const message = errorContext.message;
|
|
322
|
+
if (typeof message === "string" && message.length > 0) {
|
|
323
|
+
parts.push(message);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return parts.join("; ");
|
|
327
|
+
}
|
|
328
|
+
_handleFailedHookCompletion(basePath, hook, config, reason) {
|
|
329
|
+
if (config) {
|
|
330
|
+
const retry = this._startHook(config, hook.triggerUnitType, hook.triggerUnitId);
|
|
331
|
+
if (retry)
|
|
332
|
+
return retry;
|
|
333
|
+
}
|
|
334
|
+
this.hookFailure = {
|
|
335
|
+
hookName: hook.hookName,
|
|
336
|
+
unitType: `hook/${hook.hookName}`,
|
|
337
|
+
unitId: hook.triggerUnitId,
|
|
338
|
+
reason,
|
|
339
|
+
};
|
|
340
|
+
this.activeHook = null;
|
|
341
|
+
this.hookQueue = [];
|
|
342
|
+
this.persistState(basePath);
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
_handleExistingBlockingArtifact(config, trigger, basePath) {
|
|
346
|
+
const outcome = this._readGateOutcome(config, trigger, basePath);
|
|
347
|
+
switch (outcome.verdict) {
|
|
348
|
+
case "pass":
|
|
349
|
+
case "advisory":
|
|
350
|
+
return "skip";
|
|
351
|
+
case "needs-rework":
|
|
352
|
+
return this._routeNeedsRework(config, trigger, outcome);
|
|
353
|
+
case "needs-remediation":
|
|
354
|
+
case "needs-attention":
|
|
355
|
+
this._pauseForGate(config, trigger, outcome, `gate reported ${outcome.verdict}`);
|
|
356
|
+
return null;
|
|
357
|
+
case "failed":
|
|
358
|
+
case undefined:
|
|
359
|
+
return this._rerunGateOrBlock(config, trigger, basePath, {
|
|
360
|
+
reason: outcome.reason ?? `gate artifact reported verdict=${outcome.verdict}`,
|
|
361
|
+
outcome,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
return this._rerunGateOrBlock(config, trigger, basePath, {
|
|
365
|
+
reason: `gate artifact reported unsupported verdict=${String(outcome.verdict)}`,
|
|
366
|
+
outcome,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
_handleBlockingGateCompletion(config, hook, basePath, observedCleanExecution) {
|
|
370
|
+
if (!observedCleanExecution) {
|
|
371
|
+
return this._rerunGateOrBlock(config, hook, basePath, {
|
|
372
|
+
reason: `hook/${config.name} did not complete cleanly before the trigger unit resumed`,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
const outcome = this._readGateOutcome(config, hook, basePath);
|
|
376
|
+
switch (outcome.verdict) {
|
|
377
|
+
case "pass":
|
|
378
|
+
case "advisory":
|
|
379
|
+
this.activeHook = null;
|
|
380
|
+
return this._dequeueNextHook(basePath);
|
|
381
|
+
case "needs-rework":
|
|
382
|
+
return this._routeNeedsRework(config, hook, outcome);
|
|
383
|
+
case "needs-remediation":
|
|
384
|
+
case "needs-attention":
|
|
385
|
+
return this._pauseForGate(config, hook, outcome, `gate reported ${outcome.verdict}`);
|
|
386
|
+
case "failed":
|
|
387
|
+
case undefined:
|
|
388
|
+
return this._rerunGateOrBlock(config, hook, basePath, {
|
|
389
|
+
reason: outcome.reason ?? `gate artifact reported verdict=${outcome.verdict}`,
|
|
390
|
+
outcome,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
return this._rerunGateOrBlock(config, hook, basePath, {
|
|
394
|
+
reason: `gate artifact reported unsupported verdict=${String(outcome.verdict)}`,
|
|
395
|
+
outcome,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
_routeNeedsRework(config, trigger, outcome) {
|
|
399
|
+
const action = config.on_block?.action ?? "retry-unit";
|
|
400
|
+
if (action === "retry-task" || action === "retry-unit") {
|
|
401
|
+
if (this._requestTriggerRetry(config, trigger, config.on_block?.artifact)) {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
this._setGateBlock(config, trigger, {
|
|
405
|
+
action: "pause",
|
|
406
|
+
reason: "gate cycle budget exhausted after needs-rework",
|
|
407
|
+
outcome,
|
|
408
|
+
retryArtifact: config.on_block?.artifact,
|
|
409
|
+
});
|
|
410
|
+
this.activeHook = null;
|
|
411
|
+
this.hookQueue = [];
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
return this._pauseForGate(config, trigger, outcome, `gate reported needs-rework; configured on_block action is ${action}`);
|
|
415
|
+
}
|
|
416
|
+
_pauseForGate(config, trigger, outcome, reason) {
|
|
417
|
+
this._setGateBlock(config, trigger, {
|
|
418
|
+
action: config.on_block?.action ?? "pause",
|
|
419
|
+
reason,
|
|
420
|
+
outcome,
|
|
421
|
+
retryArtifact: config.on_block?.artifact,
|
|
422
|
+
});
|
|
423
|
+
this.activeHook = null;
|
|
424
|
+
this.hookQueue = [];
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
_requestTriggerRetry(config, hook, retryArtifact) {
|
|
428
|
+
const cycleKey = hookCycleKey(config, hook);
|
|
429
|
+
const currentCycle = this.cycleCounts.get(cycleKey) ?? 1;
|
|
430
|
+
const maxCycles = hookMaxCycles(config);
|
|
431
|
+
if (currentCycle >= maxCycles)
|
|
432
|
+
return false;
|
|
433
|
+
this.activeHook = null;
|
|
434
|
+
this.hookQueue = [];
|
|
435
|
+
this.retryPending = true;
|
|
436
|
+
this.retryTrigger = {
|
|
437
|
+
unitType: hook.triggerUnitType,
|
|
438
|
+
unitId: hook.triggerUnitId,
|
|
439
|
+
};
|
|
440
|
+
if (retryArtifact !== undefined) {
|
|
441
|
+
this.retryTrigger.retryArtifact = retryArtifact;
|
|
442
|
+
}
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
_rerunGateOrBlock(config, trigger, basePath, opts) {
|
|
446
|
+
const cycleKey = hookCycleKey(config, trigger);
|
|
447
|
+
const currentCycle = this.cycleCounts.get(cycleKey) ?? 0;
|
|
448
|
+
const maxCycles = hookMaxCycles(config);
|
|
449
|
+
if (currentCycle < maxCycles) {
|
|
450
|
+
this.activeHook = null;
|
|
451
|
+
this.hookQueue.unshift({
|
|
452
|
+
config,
|
|
453
|
+
triggerUnitType: trigger.triggerUnitType,
|
|
454
|
+
triggerUnitId: trigger.triggerUnitId,
|
|
455
|
+
forceRun: true,
|
|
456
|
+
});
|
|
457
|
+
return this._dequeueNextHook(basePath);
|
|
458
|
+
}
|
|
459
|
+
this._setGateBlock(config, trigger, {
|
|
460
|
+
action: "pause",
|
|
461
|
+
reason: `${opts.reason}; gate cycle budget exhausted`,
|
|
462
|
+
outcome: opts.outcome,
|
|
463
|
+
cycle: currentCycle,
|
|
464
|
+
maxCycles,
|
|
465
|
+
});
|
|
466
|
+
this.activeHook = null;
|
|
467
|
+
this.hookQueue = [];
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
_readGateOutcome(config, trigger, basePath) {
|
|
471
|
+
if (!config.artifact) {
|
|
472
|
+
return { reason: "blocking gate has no configured artifact" };
|
|
473
|
+
}
|
|
474
|
+
const artifactPath = resolveHookArtifactPath(basePath, trigger.triggerUnitId, config.artifact);
|
|
475
|
+
if (!existsSync(artifactPath)) {
|
|
476
|
+
return {
|
|
477
|
+
artifact: config.artifact,
|
|
478
|
+
artifactPath,
|
|
479
|
+
reason: `missing required gate artifact ${config.artifact}`,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
let content = "";
|
|
483
|
+
try {
|
|
484
|
+
content = readFileSync(artifactPath, "utf-8");
|
|
485
|
+
}
|
|
486
|
+
catch (e) {
|
|
487
|
+
return {
|
|
488
|
+
artifact: config.artifact,
|
|
489
|
+
artifactPath,
|
|
490
|
+
reason: `could not read gate artifact ${config.artifact}: ${e.message}`,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
const rawVerdict = extractFrontmatterVerdict(content);
|
|
494
|
+
if (!rawVerdict) {
|
|
495
|
+
return {
|
|
496
|
+
artifact: config.artifact,
|
|
497
|
+
artifactPath,
|
|
498
|
+
reason: `gate artifact ${config.artifact} is missing frontmatter verdict`,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
if (rawVerdict === "failed") {
|
|
502
|
+
return {
|
|
503
|
+
artifact: config.artifact,
|
|
504
|
+
artifactPath,
|
|
505
|
+
verdict: "failed",
|
|
506
|
+
reason: `gate artifact ${config.artifact} reported verdict=failed`,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
if (!HOOK_OUTCOME_VERDICTS.has(rawVerdict)) {
|
|
510
|
+
return {
|
|
511
|
+
artifact: config.artifact,
|
|
512
|
+
artifactPath,
|
|
513
|
+
reason: `gate artifact ${config.artifact} has unsupported verdict=${rawVerdict}`,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
artifact: config.artifact,
|
|
518
|
+
artifactPath,
|
|
519
|
+
verdict: rawVerdict,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
_setGateBlock(config, trigger, opts) {
|
|
523
|
+
const cycleKey = hookCycleKey(config, trigger);
|
|
524
|
+
const cycle = opts.cycle ?? this.cycleCounts.get(cycleKey) ?? 0;
|
|
525
|
+
this.gateBlockPending = {
|
|
526
|
+
hookName: config.name,
|
|
527
|
+
triggerUnitType: trigger.triggerUnitType,
|
|
528
|
+
triggerUnitId: trigger.triggerUnitId,
|
|
529
|
+
artifact: opts.outcome?.artifact ?? config.artifact,
|
|
530
|
+
artifactPath: opts.outcome?.artifactPath,
|
|
531
|
+
verdict: opts.outcome?.verdict,
|
|
532
|
+
action: opts.action,
|
|
533
|
+
reason: opts.reason,
|
|
534
|
+
cycle,
|
|
535
|
+
maxCycles: opts.maxCycles ?? hookMaxCycles(config),
|
|
536
|
+
retryArtifact: opts.retryArtifact,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
213
539
|
// ── Pre-dispatch hook evaluation (sync, all-matching with compose) ──
|
|
214
540
|
/**
|
|
215
541
|
* Replicate exact semantics of runPreDispatchHooks from post-unit-hooks.ts:
|
|
@@ -275,6 +601,16 @@ export class RuleRegistry {
|
|
|
275
601
|
isRetryPending() {
|
|
276
602
|
return this.retryPending;
|
|
277
603
|
}
|
|
604
|
+
consumeHookFailure() {
|
|
605
|
+
if (!this.hookFailure)
|
|
606
|
+
return null;
|
|
607
|
+
const failure = { ...this.hookFailure };
|
|
608
|
+
this.hookFailure = null;
|
|
609
|
+
return failure;
|
|
610
|
+
}
|
|
611
|
+
isGateBlockPending() {
|
|
612
|
+
return this.gateBlockPending !== null;
|
|
613
|
+
}
|
|
278
614
|
/**
|
|
279
615
|
* Returns the trigger unit info for a pending retry, or null.
|
|
280
616
|
* Clears the retry state after reading.
|
|
@@ -287,22 +623,42 @@ export class RuleRegistry {
|
|
|
287
623
|
this.retryTrigger = null;
|
|
288
624
|
return trigger;
|
|
289
625
|
}
|
|
290
|
-
/**
|
|
626
|
+
/**
|
|
627
|
+
* Returns a pending post-unit gate block, or null.
|
|
628
|
+
* Clears the block state after reading.
|
|
629
|
+
*/
|
|
630
|
+
consumeGateBlock() {
|
|
631
|
+
if (!this.gateBlockPending)
|
|
632
|
+
return null;
|
|
633
|
+
const block = { ...this.gateBlockPending };
|
|
634
|
+
this.gateBlockPending = null;
|
|
635
|
+
return block;
|
|
636
|
+
}
|
|
637
|
+
/** Clear all mutable hook lifecycle state. */
|
|
291
638
|
resetState() {
|
|
292
639
|
this.activeHook = null;
|
|
293
640
|
this.hookQueue = [];
|
|
294
641
|
this.cycleCounts.clear();
|
|
295
642
|
this.retryPending = false;
|
|
296
643
|
this.retryTrigger = null;
|
|
644
|
+
this.hookFailure = null;
|
|
645
|
+
this.gateBlockPending = null;
|
|
297
646
|
}
|
|
298
647
|
// ── Persistence ─────────────────────────────────────────────────────
|
|
299
648
|
_hookStatePath(basePath) {
|
|
300
649
|
return join(basePath, ".gsd", HOOK_STATE_FILE);
|
|
301
650
|
}
|
|
302
|
-
/** Persist current hook
|
|
651
|
+
/** Persist current hook state to disk. */
|
|
303
652
|
persistState(basePath) {
|
|
304
653
|
const state = {
|
|
305
654
|
cycleCounts: Object.fromEntries(this.cycleCounts),
|
|
655
|
+
activeHook: this.activeHook ? { ...this.activeHook } : null,
|
|
656
|
+
hookQueue: this.hookQueue.map(entry => ({
|
|
657
|
+
hookName: entry.config.name,
|
|
658
|
+
triggerUnitType: entry.triggerUnitType,
|
|
659
|
+
triggerUnitId: entry.triggerUnitId,
|
|
660
|
+
forceRun: entry.forceRun,
|
|
661
|
+
})),
|
|
306
662
|
savedAt: new Date().toISOString(),
|
|
307
663
|
};
|
|
308
664
|
try {
|
|
@@ -315,7 +671,7 @@ export class RuleRegistry {
|
|
|
315
671
|
logWarning("registry", `failed to persist hook state: ${e.message}`);
|
|
316
672
|
}
|
|
317
673
|
}
|
|
318
|
-
/** Restore hook
|
|
674
|
+
/** Restore hook state from disk after a crash/restart. */
|
|
319
675
|
restoreState(basePath) {
|
|
320
676
|
try {
|
|
321
677
|
const filePath = this._hookStatePath(basePath);
|
|
@@ -331,6 +687,24 @@ export class RuleRegistry {
|
|
|
331
687
|
}
|
|
332
688
|
}
|
|
333
689
|
}
|
|
690
|
+
this.activeHook = state.activeHook && typeof state.activeHook === "object"
|
|
691
|
+
? { ...state.activeHook }
|
|
692
|
+
: null;
|
|
693
|
+
this.hookQueue = [];
|
|
694
|
+
if (Array.isArray(state.hookQueue)) {
|
|
695
|
+
const hooks = resolvePostUnitHooks(basePath);
|
|
696
|
+
for (const entry of state.hookQueue) {
|
|
697
|
+
const config = hooks.find(h => h.name === entry.hookName);
|
|
698
|
+
if (config) {
|
|
699
|
+
this.hookQueue.push({
|
|
700
|
+
config,
|
|
701
|
+
triggerUnitType: entry.triggerUnitType,
|
|
702
|
+
triggerUnitId: entry.triggerUnitId,
|
|
703
|
+
forceRun: entry.forceRun,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
334
708
|
}
|
|
335
709
|
catch (e) {
|
|
336
710
|
logWarning("registry", `failed to restore hook state: ${e.message}`);
|
|
@@ -341,7 +715,7 @@ export class RuleRegistry {
|
|
|
341
715
|
try {
|
|
342
716
|
const filePath = this._hookStatePath(basePath);
|
|
343
717
|
if (existsSync(filePath)) {
|
|
344
|
-
writeFileSync(filePath, JSON.stringify({ cycleCounts: {}, savedAt: new Date().toISOString() }, null, 2), "utf-8");
|
|
718
|
+
writeFileSync(filePath, JSON.stringify({ cycleCounts: {}, activeHook: null, hookQueue: [], savedAt: new Date().toISOString() }, null, 2), "utf-8");
|
|
345
719
|
}
|
|
346
720
|
}
|
|
347
721
|
catch (e) {
|
|
@@ -365,6 +739,7 @@ export class RuleRegistry {
|
|
|
365
739
|
type: "post",
|
|
366
740
|
enabled: hook.enabled !== false,
|
|
367
741
|
targets: hook.after,
|
|
742
|
+
criticality: hook.criticality ?? "advisory",
|
|
368
743
|
activeCycles,
|
|
369
744
|
});
|
|
370
745
|
}
|
|
@@ -436,9 +811,10 @@ export class RuleRegistry {
|
|
|
436
811
|
lines.push("Post-Unit Hooks (run after unit completes):");
|
|
437
812
|
for (const hook of postHooks) {
|
|
438
813
|
const status = hook.enabled ? "enabled" : "disabled";
|
|
814
|
+
const criticality = hook.criticality ?? "advisory";
|
|
439
815
|
const cycles = Object.keys(hook.activeCycles).length;
|
|
440
816
|
const cycleInfo = cycles > 0 ? ` (${cycles} active cycle${cycles === 1 ? "" : "s"})` : "";
|
|
441
|
-
lines.push(` ${hook.name} [${status}] → after: ${hook.targets.join(", ")}${cycleInfo}`);
|
|
817
|
+
lines.push(` ${hook.name} [${status}, ${criticality}] → after: ${hook.targets.join(", ")}${cycleInfo}`);
|
|
442
818
|
}
|
|
443
819
|
lines.push("");
|
|
444
820
|
}
|
|
@@ -724,8 +724,8 @@ export async function deriveStateFromDb(basePath, artifactReadRoot = basePath) {
|
|
|
724
724
|
}
|
|
725
725
|
}
|
|
726
726
|
// ADR-011 Phase 2: pause-on-escalation takes precedence over dispatching the
|
|
727
|
-
// next task. `awaiting_review` tasks (continueWithDefault=true)
|
|
728
|
-
//
|
|
727
|
+
// next task. `awaiting_review` tasks (continueWithDefault=true) still pause
|
|
728
|
+
// here so silence is never treated as consent.
|
|
729
729
|
//
|
|
730
730
|
// We do NOT gate this on `phases.mid_execution_escalation` — creation of
|
|
731
731
|
// new escalations is gated at the write site (tools/complete-task.ts:315),
|
|
@@ -132,14 +132,16 @@
|
|
|
132
132
|
Verify field rules:
|
|
133
133
|
- MUST be a mechanically executable command: `npm test`, `grep -q "pattern" file`, `test -f path`
|
|
134
134
|
- MUST NOT use shell pipes, redirects, semicolons, backticks, command substitution, output trimming, or grep regex alternation with `|`
|
|
135
|
+
- For absence checks, use `! grep -q "pattern" file` or `! rg -q "pattern" file`; do not use `grep -c` or `rg -c` to assert zero matches because count commands exit 1 when they find zero matches
|
|
135
136
|
- MUST NOT use inline `node -e` assertions for verification; put assertions in a real test file and run it with `node --test` or a package test script
|
|
136
137
|
- For content/document tasks: verify file existence, section count, YAML validity, or word count
|
|
137
138
|
NOT exact phrasing, specific formulas, or "zero TBD" aspirational criteria
|
|
138
139
|
- If no command can verify the output, write: "Manual review — file exists and is non-empty"
|
|
139
140
|
- BAD: `python3 -m pytest tests/ -q --tb=short 2>&1 | tail -5`
|
|
141
|
+
- BAD: `grep -c "old_api" src/index.ts`
|
|
140
142
|
- BAD: "Sections 3.1 and 3.2 exist with exact formulas. Zero TBD/TODO."
|
|
141
143
|
- GOOD: `python3 -m pytest tests/ -q --tb=short`
|
|
142
|
-
- GOOD: `node --test tests/verify-doc.test.js`, `grep -q "Required heading" doc.md`, `test -s doc.md`
|
|
144
|
+
- GOOD: `node --test tests/verify-doc.test.js`, `grep -q "Required heading" doc.md`, `! grep -q "old_api" src/index.ts`, `test -s doc.md`
|
|
143
145
|
|
|
144
146
|
Integration closure rule:
|
|
145
147
|
- At least one slice in any multi-boundary milestone should perform real composition/wiring, not just contract hardening
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// File Purpose: ADR-015 Tool Contract module for Unit prompt, policy, and tool parity.
|
|
3
3
|
import { resolveManifest, } from "./unit-context-manifest.js";
|
|
4
4
|
import { getRequiredWorkflowToolsForAutoUnit } from "./workflow-mcp.js";
|
|
5
|
+
import { getUnitToolSurfaceContract } from "./unit-tool-contracts.js";
|
|
5
6
|
export function compileUnitToolContract(unitType) {
|
|
6
7
|
const manifest = resolveManifest(unitType);
|
|
8
|
+
const surfaceContract = getUnitToolSurfaceContract(unitType);
|
|
7
9
|
if (!manifest) {
|
|
8
10
|
return {
|
|
9
11
|
ok: false,
|
|
@@ -12,6 +14,8 @@ export function compileUnitToolContract(unitType) {
|
|
|
12
14
|
};
|
|
13
15
|
}
|
|
14
16
|
const requiredWorkflowTools = getRequiredWorkflowToolsForAutoUnit(unitType);
|
|
17
|
+
const forbiddenWorkflowTools = Object.entries(surfaceContract?.forbiddenGsdTools ?? {})
|
|
18
|
+
.map(([name, reason]) => ({ name, reason }));
|
|
15
19
|
const closeoutTools = requiredWorkflowTools.filter((tool) => /^gsd_(?:task|slice|milestone|complete|validate|save|summary)/.test(tool));
|
|
16
20
|
if (requiresCloseoutTool(unitType) && closeoutTools.length === 0) {
|
|
17
21
|
return {
|
|
@@ -27,6 +31,7 @@ export function compileUnitToolContract(unitType) {
|
|
|
27
31
|
contextMode: manifest.contextMode,
|
|
28
32
|
toolsPolicy: manifest.tools,
|
|
29
33
|
requiredWorkflowTools,
|
|
34
|
+
forbiddenWorkflowTools,
|
|
30
35
|
promptObligations: [
|
|
31
36
|
`context-mode:${manifest.contextMode}`,
|
|
32
37
|
`tools-policy:${manifest.tools.mode}`,
|