@opengsd/gsd-pi 1.1.1-dev.74e8dd1 → 1.1.1-dev.a5a2de8
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 +17 -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 +7 -7
- 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 +7 -7
- 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 -6
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +78 -10
- 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 +69 -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 +61 -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 +351 -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 +38 -8
- 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 → 9y3LeeR2uGr2yRj9RjY3D}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{eRWf-RI9bzbrwEurm_3uI → 9y3LeeR2uGr2yRj9RjY3D}/_ssgManifest.js +0 -0
|
@@ -17,11 +17,16 @@ import type {
|
|
|
17
17
|
HookExecutionState,
|
|
18
18
|
PersistedHookState,
|
|
19
19
|
HookStatusEntry,
|
|
20
|
+
PostUnitGateBlock,
|
|
21
|
+
PostUnitHookOutcomeVerdict,
|
|
20
22
|
} from "./types.js";
|
|
21
23
|
import { resolvePostUnitHooks, resolvePreDispatchHooks } from "./preferences.js";
|
|
22
24
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
23
25
|
import { join } from "node:path";
|
|
24
26
|
import { parseUnitId } from "./unit-id.js";
|
|
27
|
+
import { queryJournal, type JournalEntry } from "./journal.js";
|
|
28
|
+
import { readUnitRuntimeRecord, type UnitRuntimePhase } from "./unit-runtime.js";
|
|
29
|
+
import { extractFrontmatterVerdict } from "./verdict-parser.js";
|
|
25
30
|
|
|
26
31
|
// ─── Artifact Path Resolution ──────────────────────────────────────────────
|
|
27
32
|
|
|
@@ -56,6 +61,56 @@ export function convertDispatchRules(rules: DispatchRule[]): UnifiedRule[] {
|
|
|
56
61
|
// ─── RuleRegistry ─────────────────────────────────────────────────────────
|
|
57
62
|
|
|
58
63
|
const HOOK_STATE_FILE = "hook-state.json";
|
|
64
|
+
const FAILED_HOOK_RUNTIME_PHASES: ReadonlySet<UnitRuntimePhase> = new Set([
|
|
65
|
+
"timeout",
|
|
66
|
+
"finalize-timeout",
|
|
67
|
+
"crashed",
|
|
68
|
+
"paused",
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
interface HookFailureState {
|
|
72
|
+
hookName: string;
|
|
73
|
+
unitType: string;
|
|
74
|
+
unitId: string;
|
|
75
|
+
reason: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
type HookCompletionAssessment =
|
|
79
|
+
| { outcome: "success" }
|
|
80
|
+
| { outcome: "failed"; reason: string }
|
|
81
|
+
| { outcome: "unknown" };
|
|
82
|
+
|
|
83
|
+
const HOOK_OUTCOME_VERDICTS = new Set<PostUnitHookOutcomeVerdict>([
|
|
84
|
+
"pass",
|
|
85
|
+
"advisory",
|
|
86
|
+
"needs-rework",
|
|
87
|
+
"needs-remediation",
|
|
88
|
+
"needs-attention",
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
interface HookTriggerRef {
|
|
92
|
+
triggerUnitType: string;
|
|
93
|
+
triggerUnitId: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface GateOutcome {
|
|
97
|
+
verdict?: PostUnitHookOutcomeVerdict | "failed";
|
|
98
|
+
artifact?: string;
|
|
99
|
+
artifactPath?: string;
|
|
100
|
+
reason?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isBlockingHook(config: PostUnitHookConfig | undefined): boolean {
|
|
104
|
+
return config?.criticality === "blocking";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function hookMaxCycles(config: PostUnitHookConfig): number {
|
|
108
|
+
return config.max_cycles ?? 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function hookCycleKey(config: PostUnitHookConfig, trigger: HookTriggerRef): string {
|
|
112
|
+
return `${config.name}/${trigger.triggerUnitType}/${trigger.triggerUnitId}`;
|
|
113
|
+
}
|
|
59
114
|
|
|
60
115
|
export class RuleRegistry {
|
|
61
116
|
/** Static dispatch rules provided at construction time. */
|
|
@@ -68,10 +123,13 @@ export class RuleRegistry {
|
|
|
68
123
|
config: PostUnitHookConfig;
|
|
69
124
|
triggerUnitType: string;
|
|
70
125
|
triggerUnitId: string;
|
|
126
|
+
forceRun?: boolean;
|
|
71
127
|
}> = [];
|
|
72
128
|
cycleCounts: Map<string, number> = new Map();
|
|
73
129
|
retryPending: boolean = false;
|
|
74
|
-
retryTrigger: { unitType: string; unitId: string; retryArtifact
|
|
130
|
+
retryTrigger: { unitType: string; unitId: string; retryArtifact?: string } | null = null;
|
|
131
|
+
hookFailure: HookFailureState | null = null;
|
|
132
|
+
gateBlockPending: PostUnitGateBlock | null = null;
|
|
75
133
|
|
|
76
134
|
constructor(dispatchRules: UnifiedRule[]) {
|
|
77
135
|
this.dispatchRules = dispatchRules;
|
|
@@ -100,6 +158,7 @@ export class RuleRegistry {
|
|
|
100
158
|
artifact: hook.artifact,
|
|
101
159
|
retry_on: hook.retry_on,
|
|
102
160
|
max_cycles: hook.max_cycles,
|
|
161
|
+
criticality: hook.criticality,
|
|
103
162
|
},
|
|
104
163
|
});
|
|
105
164
|
}
|
|
@@ -155,7 +214,10 @@ export class RuleRegistry {
|
|
|
155
214
|
): HookDispatchResult | null {
|
|
156
215
|
// If we just completed a hook unit, handle its result
|
|
157
216
|
if (this.activeHook) {
|
|
158
|
-
|
|
217
|
+
const observedCleanExecution =
|
|
218
|
+
completedUnitType === `hook/${this.activeHook.hookName}` &&
|
|
219
|
+
completedUnitId === this.activeHook.triggerUnitId;
|
|
220
|
+
return this._handleHookCompletion(basePath, observedCleanExecution);
|
|
159
221
|
}
|
|
160
222
|
|
|
161
223
|
// Don't trigger hooks for other hook units (prevent hook-on-hook chains)
|
|
@@ -187,47 +249,51 @@ export class RuleRegistry {
|
|
|
187
249
|
private _dequeueNextHook(basePath: string): HookDispatchResult | null {
|
|
188
250
|
while (this.hookQueue.length > 0) {
|
|
189
251
|
const entry = this.hookQueue.shift()!;
|
|
190
|
-
const { config, triggerUnitType, triggerUnitId } = entry;
|
|
252
|
+
const { config, triggerUnitType, triggerUnitId, forceRun } = entry;
|
|
191
253
|
|
|
192
|
-
//
|
|
193
|
-
|
|
254
|
+
// Advisory hooks preserve existing idempotency: any configured artifact
|
|
255
|
+
// means the hook already ran. Blocking gates must verify outcome first.
|
|
256
|
+
if (config.artifact && !forceRun) {
|
|
194
257
|
const artifactPath = resolveHookArtifactPath(basePath, triggerUnitId, config.artifact);
|
|
195
|
-
if (existsSync(artifactPath))
|
|
258
|
+
if (existsSync(artifactPath)) {
|
|
259
|
+
const completion = this._assessConfiguredHookCompletion(basePath, config.name, triggerUnitId);
|
|
260
|
+
if (completion.outcome === "failed") {
|
|
261
|
+
return this._handleFailedHookCompletion(
|
|
262
|
+
basePath,
|
|
263
|
+
{
|
|
264
|
+
hookName: config.name,
|
|
265
|
+
triggerUnitType,
|
|
266
|
+
triggerUnitId,
|
|
267
|
+
cycle: this.cycleCounts.get(hookCycleKey(config, { triggerUnitType, triggerUnitId })) ?? 0,
|
|
268
|
+
pendingRetry: false,
|
|
269
|
+
},
|
|
270
|
+
config,
|
|
271
|
+
completion.reason,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
if (!isBlockingHook(config)) continue;
|
|
275
|
+
const decision = this._handleExistingBlockingArtifact(config, { triggerUnitType, triggerUnitId }, basePath);
|
|
276
|
+
if (decision === "skip") continue;
|
|
277
|
+
return decision;
|
|
278
|
+
}
|
|
196
279
|
}
|
|
197
280
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// Build prompt with variable substitution
|
|
215
|
-
const { milestone: mid, slice: sid, task: tid } = parseUnitId(triggerUnitId);
|
|
216
|
-
let prompt = config.prompt
|
|
217
|
-
.replace(/\{milestoneId\}/g, mid ?? "")
|
|
218
|
-
.replace(/\{sliceId\}/g, sid ?? "")
|
|
219
|
-
.replace(/\{taskId\}/g, tid ?? "");
|
|
220
|
-
|
|
221
|
-
// Inject browser safety instruction
|
|
222
|
-
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.";
|
|
223
|
-
|
|
224
|
-
return {
|
|
225
|
-
hookName: config.name,
|
|
226
|
-
prompt,
|
|
227
|
-
model: config.model,
|
|
228
|
-
unitType: `hook/${config.name}`,
|
|
229
|
-
unitId: triggerUnitId,
|
|
230
|
-
};
|
|
281
|
+
const dispatch = this._startHook(config, triggerUnitType, triggerUnitId);
|
|
282
|
+
if (dispatch) return dispatch;
|
|
283
|
+
if (isBlockingHook(config)) {
|
|
284
|
+
const cycleKey = hookCycleKey(config, { triggerUnitType, triggerUnitId });
|
|
285
|
+
const maxCycles = hookMaxCycles(config);
|
|
286
|
+
const currentCycle = this.cycleCounts.get(cycleKey) ?? 0;
|
|
287
|
+
if (currentCycle >= maxCycles) {
|
|
288
|
+
this._setGateBlock(config, { triggerUnitType, triggerUnitId }, {
|
|
289
|
+
action: "pause",
|
|
290
|
+
reason: `gate cycle budget exhausted before ${config.name} produced a passing outcome`,
|
|
291
|
+
cycle: currentCycle,
|
|
292
|
+
maxCycles,
|
|
293
|
+
});
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
231
297
|
}
|
|
232
298
|
|
|
233
299
|
// No more hooks — clear active state
|
|
@@ -235,38 +301,421 @@ export class RuleRegistry {
|
|
|
235
301
|
return null;
|
|
236
302
|
}
|
|
237
303
|
|
|
238
|
-
private _handleHookCompletion(basePath: string): HookDispatchResult | null {
|
|
304
|
+
private _handleHookCompletion(basePath: string, observedCleanExecution: boolean): HookDispatchResult | null {
|
|
239
305
|
const hook = this.activeHook!;
|
|
240
306
|
const hooks = resolvePostUnitHooks(basePath);
|
|
241
307
|
const config = hooks.find(h => h.name === hook.hookName);
|
|
308
|
+
if (!config) {
|
|
309
|
+
this.activeHook = null;
|
|
310
|
+
return this._dequeueNextHook(basePath);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const completion = this._assessHookCompletion(basePath, hook);
|
|
314
|
+
if (completion.outcome === "failed") {
|
|
315
|
+
return this._handleFailedHookCompletion(basePath, hook, config, completion.reason);
|
|
316
|
+
}
|
|
242
317
|
|
|
243
318
|
// Check if retry was requested via retry_on artifact
|
|
244
|
-
if (config
|
|
319
|
+
if (config.retry_on) {
|
|
245
320
|
const retryArtifactPath = resolveHookArtifactPath(basePath, hook.triggerUnitId, config.retry_on);
|
|
246
321
|
if (existsSync(retryArtifactPath)) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
322
|
+
if (this._requestTriggerRetry(config, hook, config.retry_on)) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
if (isBlockingHook(config)) {
|
|
326
|
+
this._setGateBlock(config, hook, {
|
|
327
|
+
action: "pause",
|
|
328
|
+
reason: `gate cycle budget exhausted after ${config.retry_on} requested rework`,
|
|
329
|
+
retryArtifact: config.retry_on,
|
|
330
|
+
});
|
|
252
331
|
this.activeHook = null;
|
|
253
332
|
this.hookQueue = [];
|
|
254
|
-
this.retryPending = true;
|
|
255
|
-
this.retryTrigger = {
|
|
256
|
-
unitType: hook.triggerUnitType,
|
|
257
|
-
unitId: hook.triggerUnitId,
|
|
258
|
-
retryArtifact: config.retry_on,
|
|
259
|
-
};
|
|
260
333
|
return null;
|
|
261
334
|
}
|
|
262
335
|
}
|
|
263
336
|
}
|
|
264
337
|
|
|
338
|
+
if (isBlockingHook(config)) {
|
|
339
|
+
return this._handleBlockingGateCompletion(config, hook, basePath, observedCleanExecution);
|
|
340
|
+
}
|
|
341
|
+
|
|
265
342
|
// Hook completed normally — try next hook in queue
|
|
266
343
|
this.activeHook = null;
|
|
267
344
|
return this._dequeueNextHook(basePath);
|
|
268
345
|
}
|
|
269
346
|
|
|
347
|
+
private _startHook(
|
|
348
|
+
config: PostUnitHookConfig,
|
|
349
|
+
triggerUnitType: string,
|
|
350
|
+
triggerUnitId: string,
|
|
351
|
+
): HookDispatchResult | null {
|
|
352
|
+
const cycleKey = `${config.name}/${triggerUnitType}/${triggerUnitId}`;
|
|
353
|
+
const currentCycle = (this.cycleCounts.get(cycleKey) ?? 0) + 1;
|
|
354
|
+
const maxCycles = config.max_cycles ?? 1;
|
|
355
|
+
if (currentCycle > maxCycles) return null;
|
|
356
|
+
|
|
357
|
+
this.cycleCounts.set(cycleKey, currentCycle);
|
|
358
|
+
|
|
359
|
+
this.activeHook = {
|
|
360
|
+
hookName: config.name,
|
|
361
|
+
triggerUnitType,
|
|
362
|
+
triggerUnitId,
|
|
363
|
+
cycle: currentCycle,
|
|
364
|
+
pendingRetry: false,
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(triggerUnitId);
|
|
368
|
+
let prompt = config.prompt
|
|
369
|
+
.replace(/\{milestoneId\}/g, mid ?? "")
|
|
370
|
+
.replace(/\{sliceId\}/g, sid ?? "")
|
|
371
|
+
.replace(/\{taskId\}/g, tid ?? "");
|
|
372
|
+
|
|
373
|
+
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.";
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
hookName: config.name,
|
|
377
|
+
prompt,
|
|
378
|
+
model: config.model,
|
|
379
|
+
unitType: `hook/${config.name}`,
|
|
380
|
+
unitId: triggerUnitId,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private _assessHookCompletion(
|
|
385
|
+
basePath: string,
|
|
386
|
+
hook: HookExecutionState,
|
|
387
|
+
): HookCompletionAssessment {
|
|
388
|
+
return this._assessConfiguredHookCompletion(basePath, hook.hookName, hook.triggerUnitId);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private _assessConfiguredHookCompletion(
|
|
392
|
+
basePath: string,
|
|
393
|
+
hookName: string,
|
|
394
|
+
unitId: string,
|
|
395
|
+
): HookCompletionAssessment {
|
|
396
|
+
const unitType = `hook/${hookName}`;
|
|
397
|
+
const latestUnitEnd = this._latestHookUnitEnd(basePath, unitType, unitId);
|
|
398
|
+
if (latestUnitEnd) {
|
|
399
|
+
const data = latestUnitEnd.data ?? {};
|
|
400
|
+
const status = data.status;
|
|
401
|
+
const artifactVerified = data.artifactVerified;
|
|
402
|
+
if (status === "completed" && artifactVerified !== false) {
|
|
403
|
+
return { outcome: "success" };
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
outcome: "failed",
|
|
407
|
+
reason: this._formatHookFailureReason(status, artifactVerified, data.errorContext),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const runtime = readUnitRuntimeRecord(basePath, unitType, unitId);
|
|
412
|
+
if (runtime && FAILED_HOOK_RUNTIME_PHASES.has(runtime.phase)) {
|
|
413
|
+
return { outcome: "failed", reason: `runtime phase ${runtime.phase}` };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return { outcome: "unknown" };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private _latestHookUnitEnd(
|
|
420
|
+
basePath: string,
|
|
421
|
+
unitType: string,
|
|
422
|
+
unitId: string,
|
|
423
|
+
): JournalEntry | null {
|
|
424
|
+
const unitEnds = queryJournal(basePath, { eventType: "unit-end", unitId })
|
|
425
|
+
.filter(entry => entry.data?.unitType === unitType);
|
|
426
|
+
return unitEnds[unitEnds.length - 1] ?? null;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
private _formatHookFailureReason(
|
|
430
|
+
status: unknown,
|
|
431
|
+
artifactVerified: unknown,
|
|
432
|
+
errorContext: unknown,
|
|
433
|
+
): string {
|
|
434
|
+
const parts = [`status ${typeof status === "string" ? status : "unknown"}`];
|
|
435
|
+
if (artifactVerified === false) {
|
|
436
|
+
parts.push("artifact not verified");
|
|
437
|
+
}
|
|
438
|
+
if (typeof errorContext === "object" && errorContext !== null && "message" in errorContext) {
|
|
439
|
+
const message = (errorContext as { message?: unknown }).message;
|
|
440
|
+
if (typeof message === "string" && message.length > 0) {
|
|
441
|
+
parts.push(message);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return parts.join("; ");
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private _handleFailedHookCompletion(
|
|
448
|
+
basePath: string,
|
|
449
|
+
hook: HookExecutionState,
|
|
450
|
+
config: PostUnitHookConfig | undefined,
|
|
451
|
+
reason: string,
|
|
452
|
+
): HookDispatchResult | null {
|
|
453
|
+
if (config) {
|
|
454
|
+
const retry = this._startHook(config, hook.triggerUnitType, hook.triggerUnitId);
|
|
455
|
+
if (retry) return retry;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
this.hookFailure = {
|
|
459
|
+
hookName: hook.hookName,
|
|
460
|
+
unitType: `hook/${hook.hookName}`,
|
|
461
|
+
unitId: hook.triggerUnitId,
|
|
462
|
+
reason,
|
|
463
|
+
};
|
|
464
|
+
this.activeHook = null;
|
|
465
|
+
this.hookQueue = [];
|
|
466
|
+
this.persistState(basePath);
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
private _handleExistingBlockingArtifact(
|
|
471
|
+
config: PostUnitHookConfig,
|
|
472
|
+
trigger: HookTriggerRef,
|
|
473
|
+
basePath: string,
|
|
474
|
+
): "skip" | HookDispatchResult | null {
|
|
475
|
+
const outcome = this._readGateOutcome(config, trigger, basePath);
|
|
476
|
+
switch (outcome.verdict) {
|
|
477
|
+
case "pass":
|
|
478
|
+
case "advisory":
|
|
479
|
+
return "skip";
|
|
480
|
+
case "needs-rework":
|
|
481
|
+
return this._routeNeedsRework(config, trigger, outcome);
|
|
482
|
+
case "needs-remediation":
|
|
483
|
+
case "needs-attention":
|
|
484
|
+
this._pauseForGate(config, trigger, outcome, `gate reported ${outcome.verdict}`);
|
|
485
|
+
return null;
|
|
486
|
+
case "failed":
|
|
487
|
+
case undefined:
|
|
488
|
+
return this._rerunGateOrBlock(config, trigger, basePath, {
|
|
489
|
+
reason: outcome.reason ?? `gate artifact reported verdict=${outcome.verdict}`,
|
|
490
|
+
outcome,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
return this._rerunGateOrBlock(config, trigger, basePath, {
|
|
494
|
+
reason: `gate artifact reported unsupported verdict=${String(outcome.verdict)}`,
|
|
495
|
+
outcome,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private _handleBlockingGateCompletion(
|
|
500
|
+
config: PostUnitHookConfig,
|
|
501
|
+
hook: HookExecutionState,
|
|
502
|
+
basePath: string,
|
|
503
|
+
observedCleanExecution: boolean,
|
|
504
|
+
): HookDispatchResult | null {
|
|
505
|
+
if (!observedCleanExecution) {
|
|
506
|
+
return this._rerunGateOrBlock(config, hook, basePath, {
|
|
507
|
+
reason: `hook/${config.name} did not complete cleanly before the trigger unit resumed`,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const outcome = this._readGateOutcome(config, hook, basePath);
|
|
512
|
+
switch (outcome.verdict) {
|
|
513
|
+
case "pass":
|
|
514
|
+
case "advisory":
|
|
515
|
+
this.activeHook = null;
|
|
516
|
+
return this._dequeueNextHook(basePath);
|
|
517
|
+
case "needs-rework":
|
|
518
|
+
return this._routeNeedsRework(config, hook, outcome);
|
|
519
|
+
case "needs-remediation":
|
|
520
|
+
case "needs-attention":
|
|
521
|
+
return this._pauseForGate(config, hook, outcome, `gate reported ${outcome.verdict}`);
|
|
522
|
+
case "failed":
|
|
523
|
+
case undefined:
|
|
524
|
+
return this._rerunGateOrBlock(config, hook, basePath, {
|
|
525
|
+
reason: outcome.reason ?? `gate artifact reported verdict=${outcome.verdict}`,
|
|
526
|
+
outcome,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
return this._rerunGateOrBlock(config, hook, basePath, {
|
|
530
|
+
reason: `gate artifact reported unsupported verdict=${String(outcome.verdict)}`,
|
|
531
|
+
outcome,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private _routeNeedsRework(
|
|
536
|
+
config: PostUnitHookConfig,
|
|
537
|
+
trigger: HookTriggerRef,
|
|
538
|
+
outcome: GateOutcome,
|
|
539
|
+
): null {
|
|
540
|
+
const action = config.on_block?.action ?? "retry-unit";
|
|
541
|
+
if (action === "retry-task" || action === "retry-unit") {
|
|
542
|
+
if (this._requestTriggerRetry(config, trigger, config.on_block?.artifact)) {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
this._setGateBlock(config, trigger, {
|
|
546
|
+
action: "pause",
|
|
547
|
+
reason: "gate cycle budget exhausted after needs-rework",
|
|
548
|
+
outcome,
|
|
549
|
+
retryArtifact: config.on_block?.artifact,
|
|
550
|
+
});
|
|
551
|
+
this.activeHook = null;
|
|
552
|
+
this.hookQueue = [];
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
return this._pauseForGate(config, trigger, outcome, `gate reported needs-rework; configured on_block action is ${action}`);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private _pauseForGate(
|
|
559
|
+
config: PostUnitHookConfig,
|
|
560
|
+
trigger: HookTriggerRef,
|
|
561
|
+
outcome: GateOutcome,
|
|
562
|
+
reason: string,
|
|
563
|
+
): null {
|
|
564
|
+
this._setGateBlock(config, trigger, {
|
|
565
|
+
action: config.on_block?.action ?? "pause",
|
|
566
|
+
reason,
|
|
567
|
+
outcome,
|
|
568
|
+
retryArtifact: config.on_block?.artifact,
|
|
569
|
+
});
|
|
570
|
+
this.activeHook = null;
|
|
571
|
+
this.hookQueue = [];
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private _requestTriggerRetry(
|
|
576
|
+
config: PostUnitHookConfig,
|
|
577
|
+
hook: HookTriggerRef,
|
|
578
|
+
retryArtifact?: string,
|
|
579
|
+
): boolean {
|
|
580
|
+
const cycleKey = hookCycleKey(config, hook);
|
|
581
|
+
const currentCycle = this.cycleCounts.get(cycleKey) ?? 1;
|
|
582
|
+
const maxCycles = hookMaxCycles(config);
|
|
583
|
+
if (currentCycle >= maxCycles) return false;
|
|
584
|
+
|
|
585
|
+
this.activeHook = null;
|
|
586
|
+
this.hookQueue = [];
|
|
587
|
+
this.retryPending = true;
|
|
588
|
+
this.retryTrigger = {
|
|
589
|
+
unitType: hook.triggerUnitType,
|
|
590
|
+
unitId: hook.triggerUnitId,
|
|
591
|
+
};
|
|
592
|
+
if (retryArtifact !== undefined) {
|
|
593
|
+
this.retryTrigger.retryArtifact = retryArtifact;
|
|
594
|
+
}
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
private _rerunGateOrBlock(
|
|
599
|
+
config: PostUnitHookConfig,
|
|
600
|
+
trigger: HookTriggerRef,
|
|
601
|
+
basePath: string,
|
|
602
|
+
opts: {
|
|
603
|
+
reason: string;
|
|
604
|
+
outcome?: GateOutcome;
|
|
605
|
+
},
|
|
606
|
+
): HookDispatchResult | null {
|
|
607
|
+
const cycleKey = hookCycleKey(config, trigger);
|
|
608
|
+
const currentCycle = this.cycleCounts.get(cycleKey) ?? 0;
|
|
609
|
+
const maxCycles = hookMaxCycles(config);
|
|
610
|
+
if (currentCycle < maxCycles) {
|
|
611
|
+
this.activeHook = null;
|
|
612
|
+
this.hookQueue.unshift({
|
|
613
|
+
config,
|
|
614
|
+
triggerUnitType: trigger.triggerUnitType,
|
|
615
|
+
triggerUnitId: trigger.triggerUnitId,
|
|
616
|
+
forceRun: true,
|
|
617
|
+
});
|
|
618
|
+
return this._dequeueNextHook(basePath);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
this._setGateBlock(config, trigger, {
|
|
622
|
+
action: "pause",
|
|
623
|
+
reason: `${opts.reason}; gate cycle budget exhausted`,
|
|
624
|
+
outcome: opts.outcome,
|
|
625
|
+
cycle: currentCycle,
|
|
626
|
+
maxCycles,
|
|
627
|
+
});
|
|
628
|
+
this.activeHook = null;
|
|
629
|
+
this.hookQueue = [];
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
private _readGateOutcome(
|
|
634
|
+
config: PostUnitHookConfig,
|
|
635
|
+
trigger: HookTriggerRef,
|
|
636
|
+
basePath: string,
|
|
637
|
+
): GateOutcome {
|
|
638
|
+
if (!config.artifact) {
|
|
639
|
+
return { reason: "blocking gate has no configured artifact" };
|
|
640
|
+
}
|
|
641
|
+
const artifactPath = resolveHookArtifactPath(basePath, trigger.triggerUnitId, config.artifact);
|
|
642
|
+
if (!existsSync(artifactPath)) {
|
|
643
|
+
return {
|
|
644
|
+
artifact: config.artifact,
|
|
645
|
+
artifactPath,
|
|
646
|
+
reason: `missing required gate artifact ${config.artifact}`,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
let content = "";
|
|
650
|
+
try {
|
|
651
|
+
content = readFileSync(artifactPath, "utf-8");
|
|
652
|
+
} catch (e) {
|
|
653
|
+
return {
|
|
654
|
+
artifact: config.artifact,
|
|
655
|
+
artifactPath,
|
|
656
|
+
reason: `could not read gate artifact ${config.artifact}: ${(e as Error).message}`,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const rawVerdict = extractFrontmatterVerdict(content);
|
|
661
|
+
if (!rawVerdict) {
|
|
662
|
+
return {
|
|
663
|
+
artifact: config.artifact,
|
|
664
|
+
artifactPath,
|
|
665
|
+
reason: `gate artifact ${config.artifact} is missing frontmatter verdict`,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
if (rawVerdict === "failed") {
|
|
669
|
+
return {
|
|
670
|
+
artifact: config.artifact,
|
|
671
|
+
artifactPath,
|
|
672
|
+
verdict: "failed",
|
|
673
|
+
reason: `gate artifact ${config.artifact} reported verdict=failed`,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
if (!HOOK_OUTCOME_VERDICTS.has(rawVerdict as PostUnitHookOutcomeVerdict)) {
|
|
677
|
+
return {
|
|
678
|
+
artifact: config.artifact,
|
|
679
|
+
artifactPath,
|
|
680
|
+
reason: `gate artifact ${config.artifact} has unsupported verdict=${rawVerdict}`,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
return {
|
|
684
|
+
artifact: config.artifact,
|
|
685
|
+
artifactPath,
|
|
686
|
+
verdict: rawVerdict as PostUnitHookOutcomeVerdict,
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
private _setGateBlock(
|
|
691
|
+
config: PostUnitHookConfig,
|
|
692
|
+
trigger: HookTriggerRef,
|
|
693
|
+
opts: {
|
|
694
|
+
action: PostUnitGateBlock["action"];
|
|
695
|
+
reason: string;
|
|
696
|
+
outcome?: GateOutcome;
|
|
697
|
+
cycle?: number;
|
|
698
|
+
maxCycles?: number;
|
|
699
|
+
retryArtifact?: string;
|
|
700
|
+
},
|
|
701
|
+
): void {
|
|
702
|
+
const cycleKey = hookCycleKey(config, trigger);
|
|
703
|
+
const cycle = opts.cycle ?? this.cycleCounts.get(cycleKey) ?? 0;
|
|
704
|
+
this.gateBlockPending = {
|
|
705
|
+
hookName: config.name,
|
|
706
|
+
triggerUnitType: trigger.triggerUnitType,
|
|
707
|
+
triggerUnitId: trigger.triggerUnitId,
|
|
708
|
+
artifact: opts.outcome?.artifact ?? config.artifact,
|
|
709
|
+
artifactPath: opts.outcome?.artifactPath,
|
|
710
|
+
verdict: opts.outcome?.verdict,
|
|
711
|
+
action: opts.action,
|
|
712
|
+
reason: opts.reason,
|
|
713
|
+
cycle,
|
|
714
|
+
maxCycles: opts.maxCycles ?? hookMaxCycles(config),
|
|
715
|
+
retryArtifact: opts.retryArtifact,
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
|
|
270
719
|
// ── Pre-dispatch hook evaluation (sync, all-matching with compose) ──
|
|
271
720
|
|
|
272
721
|
/**
|
|
@@ -351,11 +800,22 @@ export class RuleRegistry {
|
|
|
351
800
|
return this.retryPending;
|
|
352
801
|
}
|
|
353
802
|
|
|
803
|
+
consumeHookFailure(): HookFailureState | null {
|
|
804
|
+
if (!this.hookFailure) return null;
|
|
805
|
+
const failure = { ...this.hookFailure };
|
|
806
|
+
this.hookFailure = null;
|
|
807
|
+
return failure;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
isGateBlockPending(): boolean {
|
|
811
|
+
return this.gateBlockPending !== null;
|
|
812
|
+
}
|
|
813
|
+
|
|
354
814
|
/**
|
|
355
815
|
* Returns the trigger unit info for a pending retry, or null.
|
|
356
816
|
* Clears the retry state after reading.
|
|
357
817
|
*/
|
|
358
|
-
consumeRetryTrigger(): { unitType: string; unitId: string; retryArtifact
|
|
818
|
+
consumeRetryTrigger(): { unitType: string; unitId: string; retryArtifact?: string } | null {
|
|
359
819
|
if (!this.retryPending || !this.retryTrigger) return null;
|
|
360
820
|
const trigger = { ...this.retryTrigger };
|
|
361
821
|
this.retryPending = false;
|
|
@@ -363,13 +823,26 @@ export class RuleRegistry {
|
|
|
363
823
|
return trigger;
|
|
364
824
|
}
|
|
365
825
|
|
|
366
|
-
/**
|
|
826
|
+
/**
|
|
827
|
+
* Returns a pending post-unit gate block, or null.
|
|
828
|
+
* Clears the block state after reading.
|
|
829
|
+
*/
|
|
830
|
+
consumeGateBlock(): PostUnitGateBlock | null {
|
|
831
|
+
if (!this.gateBlockPending) return null;
|
|
832
|
+
const block = { ...this.gateBlockPending };
|
|
833
|
+
this.gateBlockPending = null;
|
|
834
|
+
return block;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/** Clear all mutable hook lifecycle state. */
|
|
367
838
|
resetState(): void {
|
|
368
839
|
this.activeHook = null;
|
|
369
840
|
this.hookQueue = [];
|
|
370
841
|
this.cycleCounts.clear();
|
|
371
842
|
this.retryPending = false;
|
|
372
843
|
this.retryTrigger = null;
|
|
844
|
+
this.hookFailure = null;
|
|
845
|
+
this.gateBlockPending = null;
|
|
373
846
|
}
|
|
374
847
|
|
|
375
848
|
// ── Persistence ─────────────────────────────────────────────────────
|
|
@@ -378,10 +851,17 @@ export class RuleRegistry {
|
|
|
378
851
|
return join(basePath, ".gsd", HOOK_STATE_FILE);
|
|
379
852
|
}
|
|
380
853
|
|
|
381
|
-
/** Persist current hook
|
|
854
|
+
/** Persist current hook state to disk. */
|
|
382
855
|
persistState(basePath: string): void {
|
|
383
856
|
const state: PersistedHookState = {
|
|
384
857
|
cycleCounts: Object.fromEntries(this.cycleCounts),
|
|
858
|
+
activeHook: this.activeHook ? { ...this.activeHook } : null,
|
|
859
|
+
hookQueue: this.hookQueue.map(entry => ({
|
|
860
|
+
hookName: entry.config.name,
|
|
861
|
+
triggerUnitType: entry.triggerUnitType,
|
|
862
|
+
triggerUnitId: entry.triggerUnitId,
|
|
863
|
+
forceRun: entry.forceRun,
|
|
864
|
+
})),
|
|
385
865
|
savedAt: new Date().toISOString(),
|
|
386
866
|
};
|
|
387
867
|
try {
|
|
@@ -393,7 +873,7 @@ export class RuleRegistry {
|
|
|
393
873
|
}
|
|
394
874
|
}
|
|
395
875
|
|
|
396
|
-
/** Restore hook
|
|
876
|
+
/** Restore hook state from disk after a crash/restart. */
|
|
397
877
|
restoreState(basePath: string): void {
|
|
398
878
|
try {
|
|
399
879
|
const filePath = this._hookStatePath(basePath);
|
|
@@ -408,6 +888,24 @@ export class RuleRegistry {
|
|
|
408
888
|
}
|
|
409
889
|
}
|
|
410
890
|
}
|
|
891
|
+
this.activeHook = state.activeHook && typeof state.activeHook === "object"
|
|
892
|
+
? { ...state.activeHook }
|
|
893
|
+
: null;
|
|
894
|
+
this.hookQueue = [];
|
|
895
|
+
if (Array.isArray(state.hookQueue)) {
|
|
896
|
+
const hooks = resolvePostUnitHooks(basePath);
|
|
897
|
+
for (const entry of state.hookQueue) {
|
|
898
|
+
const config = hooks.find(h => h.name === entry.hookName);
|
|
899
|
+
if (config) {
|
|
900
|
+
this.hookQueue.push({
|
|
901
|
+
config,
|
|
902
|
+
triggerUnitType: entry.triggerUnitType,
|
|
903
|
+
triggerUnitId: entry.triggerUnitId,
|
|
904
|
+
forceRun: entry.forceRun,
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
411
909
|
} catch (e) {
|
|
412
910
|
logWarning("registry", `failed to restore hook state: ${(e as Error).message}`);
|
|
413
911
|
}
|
|
@@ -420,7 +918,7 @@ export class RuleRegistry {
|
|
|
420
918
|
if (existsSync(filePath)) {
|
|
421
919
|
writeFileSync(
|
|
422
920
|
filePath,
|
|
423
|
-
JSON.stringify({ cycleCounts: {}, savedAt: new Date().toISOString() }, null, 2),
|
|
921
|
+
JSON.stringify({ cycleCounts: {}, activeHook: null, hookQueue: [], savedAt: new Date().toISOString() }, null, 2),
|
|
424
922
|
"utf-8",
|
|
425
923
|
);
|
|
426
924
|
}
|
|
@@ -448,6 +946,7 @@ export class RuleRegistry {
|
|
|
448
946
|
type: "post",
|
|
449
947
|
enabled: hook.enabled !== false,
|
|
450
948
|
targets: hook.after,
|
|
949
|
+
criticality: hook.criticality ?? "advisory",
|
|
451
950
|
activeCycles,
|
|
452
951
|
});
|
|
453
952
|
}
|
|
@@ -537,9 +1036,10 @@ export class RuleRegistry {
|
|
|
537
1036
|
lines.push("Post-Unit Hooks (run after unit completes):");
|
|
538
1037
|
for (const hook of postHooks) {
|
|
539
1038
|
const status = hook.enabled ? "enabled" : "disabled";
|
|
1039
|
+
const criticality = hook.criticality ?? "advisory";
|
|
540
1040
|
const cycles = Object.keys(hook.activeCycles).length;
|
|
541
1041
|
const cycleInfo = cycles > 0 ? ` (${cycles} active cycle${cycles === 1 ? "" : "s"})` : "";
|
|
542
|
-
lines.push(` ${hook.name} [${status}] → after: ${hook.targets.join(", ")}${cycleInfo}`);
|
|
1042
|
+
lines.push(` ${hook.name} [${status}, ${criticality}] → after: ${hook.targets.join(", ")}${cycleInfo}`);
|
|
543
1043
|
}
|
|
544
1044
|
lines.push("");
|
|
545
1045
|
}
|