gsd-pi 2.71.0-dev.06b86c6 → 2.71.0-dev.246c32d
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/README.md +34 -1
- package/dist/cli.js +17 -0
- package/dist/headless-events.d.ts +2 -0
- package/dist/headless-events.js +7 -0
- package/dist/headless.js +16 -3
- package/dist/mcp-server.js +37 -14
- package/dist/resource-loader.js +6 -3
- package/dist/resources/agents/debugger.md +58 -0
- package/dist/resources/agents/doc-writer.md +43 -0
- package/dist/resources/agents/git-ops.md +56 -0
- package/dist/resources/agents/javascript-pro.md +46 -271
- package/dist/resources/agents/planner.md +55 -0
- package/dist/resources/agents/refactorer.md +47 -0
- package/dist/resources/agents/reviewer.md +48 -0
- package/dist/resources/agents/security.md +59 -0
- package/dist/resources/agents/tester.md +50 -0
- package/dist/resources/agents/typescript-pro.md +41 -235
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +113 -10
- package/dist/resources/extensions/gsd/auto/infra-errors.js +34 -0
- package/dist/resources/extensions/gsd/auto/loop.js +32 -1
- package/dist/resources/extensions/gsd/auto/phases.js +5 -1
- package/dist/resources/extensions/gsd/auto/session.js +11 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +22 -16
- package/dist/resources/extensions/gsd/auto-model-selection.js +10 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
- package/dist/resources/extensions/gsd/auto-start.js +33 -6
- package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
- package/dist/resources/extensions/gsd/auto.js +56 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +63 -51
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -33
- package/dist/resources/extensions/gsd/commands/handlers/core.js +56 -11
- package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +15 -6
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +4 -10
- package/dist/resources/extensions/gsd/dashboard-overlay.js +8 -3
- package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
- package/dist/resources/extensions/gsd/error-classifier.js +4 -1
- package/dist/resources/extensions/gsd/forensics.js +19 -6
- package/dist/resources/extensions/gsd/gate-registry.js +208 -0
- package/dist/resources/extensions/gsd/gsd-db.js +41 -0
- package/dist/resources/extensions/gsd/guided-flow.js +5 -10
- package/dist/resources/extensions/gsd/metrics.js +1 -0
- package/dist/resources/extensions/gsd/milestone-actions.js +10 -4
- package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
- package/dist/resources/extensions/gsd/notification-overlay.js +42 -13
- package/dist/resources/extensions/gsd/notification-store.js +35 -4
- package/dist/resources/extensions/gsd/notification-widget.js +5 -13
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +8 -3
- package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/session-model-override.js +25 -0
- package/dist/resources/extensions/gsd/shortcut-defs.js +40 -0
- package/dist/resources/extensions/gsd/state.js +9 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
- package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +4 -1
- package/dist/resources/extensions/ollama/index.js +13 -5
- package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
- package/dist/resources/extensions/subagent/agents.js +8 -0
- package/dist/resources/extensions/subagent/index.js +17 -0
- package/dist/startup-model-validation.d.ts +0 -1
- package/dist/startup-model-validation.js +6 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- 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/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/server.d.ts +12 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +90 -42
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/server.ts +110 -38
- package/packages/mcp-server/src/workflow-tools.ts +1 -1
- package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-auth.test.js +20 -0
- package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +7 -4
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic-auth.test.ts +32 -0
- package/packages/pi-ai/src/providers/anthropic.ts +8 -4
- package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js +61 -0
- package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +2 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +27 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +85 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js +64 -0
- package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +22 -18
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +55 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +57 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +38 -5
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/sdk.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.test.js +71 -0
- package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js +13 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +24 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +43 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +7 -2
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -3
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +4 -2
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +70 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +2 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +108 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -0
- package/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts +78 -0
- package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
- package/packages/pi-coding-agent/src/core/model-resolver.ts +22 -18
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +83 -0
- package/packages/pi-coding-agent/src/core/retry-handler.ts +60 -1
- package/packages/pi-coding-agent/src/core/sdk.test.ts +89 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +45 -9
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts +24 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +30 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +47 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +7 -2
- package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -3
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +4 -2
- package/src/resources/agents/debugger.md +58 -0
- package/src/resources/agents/doc-writer.md +43 -0
- package/src/resources/agents/git-ops.md +56 -0
- package/src/resources/agents/javascript-pro.md +46 -271
- package/src/resources/agents/planner.md +55 -0
- package/src/resources/agents/refactorer.md +47 -0
- package/src/resources/agents/reviewer.md +48 -0
- package/src/resources/agents/security.md +59 -0
- package/src/resources/agents/tester.md +50 -0
- package/src/resources/agents/typescript-pro.md +41 -235
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +122 -8
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +189 -6
- package/src/resources/extensions/gsd/auto/infra-errors.ts +38 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -0
- package/src/resources/extensions/gsd/auto/loop.ts +45 -1
- package/src/resources/extensions/gsd/auto/phases.ts +6 -0
- package/src/resources/extensions/gsd/auto/session.ts +11 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +29 -18
- package/src/resources/extensions/gsd/auto-model-selection.ts +9 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
- package/src/resources/extensions/gsd/auto-start.ts +40 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +72 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +79 -60
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +10 -36
- package/src/resources/extensions/gsd/commands/handlers/core.ts +58 -11
- package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +17 -7
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +4 -10
- package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -3
- package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
- package/src/resources/extensions/gsd/error-classifier.ts +4 -1
- package/src/resources/extensions/gsd/forensics.ts +23 -7
- package/src/resources/extensions/gsd/gate-registry.ts +251 -0
- package/src/resources/extensions/gsd/gsd-db.ts +51 -0
- package/src/resources/extensions/gsd/guided-flow.ts +5 -10
- package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
- package/src/resources/extensions/gsd/metrics.ts +12 -1
- package/src/resources/extensions/gsd/milestone-actions.ts +10 -3
- package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
- package/src/resources/extensions/gsd/notification-overlay.ts +47 -14
- package/src/resources/extensions/gsd/notification-store.ts +35 -4
- package/src/resources/extensions/gsd/notification-widget.ts +5 -14
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -3
- package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/session-model-override.ts +36 -0
- package/src/resources/extensions/gsd/shortcut-defs.ts +56 -0
- package/src/resources/extensions/gsd/state.ts +13 -2
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +25 -9
- package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +66 -1
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +36 -51
- package/src/resources/extensions/gsd/tests/notification-store.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/notification-widget.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +63 -5
- package/src/resources/extensions/gsd/tests/session-model-override.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +90 -0
- package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
- package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +4 -1
- package/src/resources/extensions/gsd/types.ts +26 -0
- package/src/resources/extensions/ollama/index.ts +13 -3
- package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
- package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
- package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
- package/src/resources/extensions/subagent/agents.ts +10 -0
- package/src/resources/extensions/subagent/index.ts +18 -0
- package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
- /package/dist/web/standalone/.next/static/{dYVdRaunb2ZSEA8fjkT-V → hnGGkVMxIGGpxSJkf5jIV}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{dYVdRaunb2ZSEA8fjkT-V → hnGGkVMxIGGpxSJkf5jIV}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Gate Registry — single source of truth for quality-gate ownership.
|
|
3
|
+
*
|
|
4
|
+
* Each gate declares which workflow turn owns it, the scope at which it is
|
|
5
|
+
* persisted in the `quality_gates` table, and the question/guidance text used
|
|
6
|
+
* in the prompt that turn sends. The registry replaces the ad-hoc
|
|
7
|
+
* `GATE_QUESTIONS` table that used to live in `auto-prompts.ts`, and every
|
|
8
|
+
* layer of the prompt system (prompt builders, dispatch rules, state
|
|
9
|
+
* derivation, tool handlers) consults it so a pending gate can never be
|
|
10
|
+
* silently dropped.
|
|
11
|
+
*
|
|
12
|
+
* Design notes:
|
|
13
|
+
* - `GATE_REGISTRY` is exhaustiveness-checked against `GateId` via
|
|
14
|
+
* `satisfies Record<GateId, GateDefinition>`, so adding a new GateId
|
|
15
|
+
* without a registry entry is a compile error.
|
|
16
|
+
* - `getGatesForTurn(turn)` returns the definitions a turn owns.
|
|
17
|
+
* - `assertGateCoverage(pending, turn)` throws a GSDError if the pending
|
|
18
|
+
* list for a turn contains unknown gates, or if any gate owned by the
|
|
19
|
+
* turn is missing from the pending list.
|
|
20
|
+
*/
|
|
21
|
+
import { GSDError, GSD_PARSE_ERROR } from "./errors.js";
|
|
22
|
+
export const GATE_REGISTRY = {
|
|
23
|
+
Q3: {
|
|
24
|
+
id: "Q3",
|
|
25
|
+
scope: "slice",
|
|
26
|
+
ownerTurn: "gate-evaluate",
|
|
27
|
+
question: "How can this be exploited?",
|
|
28
|
+
guidance: [
|
|
29
|
+
"Identify abuse scenarios: parameter tampering, replay attacks, privilege escalation.",
|
|
30
|
+
"Map data exposure risks: PII, tokens, secrets accessible through this slice.",
|
|
31
|
+
"Define input trust boundaries: untrusted user input reaching DB, API, or filesystem.",
|
|
32
|
+
"If none apply, return verdict 'omitted' with rationale explaining why.",
|
|
33
|
+
].join("\n"),
|
|
34
|
+
promptSection: "Abuse Surface",
|
|
35
|
+
},
|
|
36
|
+
Q4: {
|
|
37
|
+
id: "Q4",
|
|
38
|
+
scope: "slice",
|
|
39
|
+
ownerTurn: "gate-evaluate",
|
|
40
|
+
question: "What existing promises does this break?",
|
|
41
|
+
guidance: [
|
|
42
|
+
"List which existing requirements (R001, R003, etc.) are touched by this slice.",
|
|
43
|
+
"Identify what must be re-tested after shipping.",
|
|
44
|
+
"Flag decisions that should be revisited given the new scope.",
|
|
45
|
+
"If no existing requirements are affected, return verdict 'omitted'.",
|
|
46
|
+
].join("\n"),
|
|
47
|
+
promptSection: "Broken Promises",
|
|
48
|
+
},
|
|
49
|
+
Q5: {
|
|
50
|
+
id: "Q5",
|
|
51
|
+
scope: "task",
|
|
52
|
+
ownerTurn: "execute-task",
|
|
53
|
+
question: "What breaks when dependencies fail?",
|
|
54
|
+
guidance: [
|
|
55
|
+
"Enumerate the task's external dependencies (APIs, filesystem, network, subprocesses).",
|
|
56
|
+
"Describe the failure path for each: timeout, malformed response, connection loss.",
|
|
57
|
+
"Verify the implementation handles each failure or explicitly bubbles the error.",
|
|
58
|
+
"Return verdict 'omitted' only if the task has no external dependencies.",
|
|
59
|
+
].join("\n"),
|
|
60
|
+
promptSection: "Failure Modes",
|
|
61
|
+
},
|
|
62
|
+
Q6: {
|
|
63
|
+
id: "Q6",
|
|
64
|
+
scope: "task",
|
|
65
|
+
ownerTurn: "execute-task",
|
|
66
|
+
question: "What is the 10x load breakpoint?",
|
|
67
|
+
guidance: [
|
|
68
|
+
"Identify the resource that saturates first at 10x the expected load.",
|
|
69
|
+
"Describe the protection applied (pool sizing, rate limiting, pagination, caching).",
|
|
70
|
+
"Return verdict 'omitted' if the task has no runtime load dimension.",
|
|
71
|
+
].join("\n"),
|
|
72
|
+
promptSection: "Load Profile",
|
|
73
|
+
},
|
|
74
|
+
Q7: {
|
|
75
|
+
id: "Q7",
|
|
76
|
+
scope: "task",
|
|
77
|
+
ownerTurn: "execute-task",
|
|
78
|
+
question: "What negative tests protect this task?",
|
|
79
|
+
guidance: [
|
|
80
|
+
"List malformed inputs, error paths, and boundary conditions the tests cover.",
|
|
81
|
+
"Point to the specific test files or cases that assert each negative scenario.",
|
|
82
|
+
"Return verdict 'omitted' only if the task has no meaningful negative surface.",
|
|
83
|
+
].join("\n"),
|
|
84
|
+
promptSection: "Negative Tests",
|
|
85
|
+
},
|
|
86
|
+
Q8: {
|
|
87
|
+
id: "Q8",
|
|
88
|
+
scope: "slice",
|
|
89
|
+
ownerTurn: "complete-slice",
|
|
90
|
+
question: "How will ops know this slice is healthy or broken?",
|
|
91
|
+
guidance: [
|
|
92
|
+
"Describe the health signal (metric, log line, dashboard) that proves the slice works.",
|
|
93
|
+
"Describe the failure signal that triggers an alert or paging.",
|
|
94
|
+
"Document the recovery procedure and any monitoring gaps.",
|
|
95
|
+
"Return verdict 'omitted' only for slices with no runtime behavior at all.",
|
|
96
|
+
].join("\n"),
|
|
97
|
+
promptSection: "Operational Readiness",
|
|
98
|
+
},
|
|
99
|
+
MV01: {
|
|
100
|
+
id: "MV01",
|
|
101
|
+
scope: "milestone",
|
|
102
|
+
ownerTurn: "validate-milestone",
|
|
103
|
+
question: "Is every success criterion in the milestone roadmap satisfied?",
|
|
104
|
+
guidance: [
|
|
105
|
+
"Walk the success-criteria checklist from the milestone roadmap.",
|
|
106
|
+
"For each criterion, point to the slice / assessment / verification evidence that proves it.",
|
|
107
|
+
"Return verdict 'flag' if any criterion is unmet or unverifiable.",
|
|
108
|
+
].join("\n"),
|
|
109
|
+
promptSection: "Success Criteria Checklist",
|
|
110
|
+
},
|
|
111
|
+
MV02: {
|
|
112
|
+
id: "MV02",
|
|
113
|
+
scope: "milestone",
|
|
114
|
+
ownerTurn: "validate-milestone",
|
|
115
|
+
question: "Does every slice have a SUMMARY.md and a passing assessment?",
|
|
116
|
+
guidance: [
|
|
117
|
+
"Confirm every slice listed in the roadmap has a SUMMARY.md.",
|
|
118
|
+
"Confirm each slice has an ASSESSMENT verdict of 'pass' (or justified 'omitted').",
|
|
119
|
+
"Flag missing artifacts and slices with outstanding follow-ups or known limitations.",
|
|
120
|
+
].join("\n"),
|
|
121
|
+
promptSection: "Slice Delivery Audit",
|
|
122
|
+
},
|
|
123
|
+
MV03: {
|
|
124
|
+
id: "MV03",
|
|
125
|
+
scope: "milestone",
|
|
126
|
+
ownerTurn: "validate-milestone",
|
|
127
|
+
question: "Do the slices integrate end-to-end?",
|
|
128
|
+
guidance: [
|
|
129
|
+
"Trace at least one cross-slice flow proving the pieces compose.",
|
|
130
|
+
"Flag gaps where two slices were built in isolation with no integration evidence.",
|
|
131
|
+
].join("\n"),
|
|
132
|
+
promptSection: "Cross-Slice Integration",
|
|
133
|
+
},
|
|
134
|
+
MV04: {
|
|
135
|
+
id: "MV04",
|
|
136
|
+
scope: "milestone",
|
|
137
|
+
ownerTurn: "validate-milestone",
|
|
138
|
+
question: "Are all touched requirements covered and still coherent?",
|
|
139
|
+
guidance: [
|
|
140
|
+
"For each requirement advanced, validated, surfaced, or invalidated across the milestone's slices, confirm the milestone-level evidence matches.",
|
|
141
|
+
"Flag requirements that slices claim to advance but no artifact proves.",
|
|
142
|
+
].join("\n"),
|
|
143
|
+
promptSection: "Requirement Coverage",
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
/** Stable ordered lists per owner turn — iteration order matches declaration. */
|
|
147
|
+
const ORDERED_GATES = Object.values(GATE_REGISTRY);
|
|
148
|
+
/** Return every gate owned by a turn, in stable declaration order. */
|
|
149
|
+
export function getGatesForTurn(turn) {
|
|
150
|
+
return ORDERED_GATES.filter((g) => g.ownerTurn === turn);
|
|
151
|
+
}
|
|
152
|
+
/** Return the set of gate ids a turn owns. */
|
|
153
|
+
export function getGateIdsForTurn(turn) {
|
|
154
|
+
return new Set(getGatesForTurn(turn).map((g) => g.id));
|
|
155
|
+
}
|
|
156
|
+
/** Look up a definition by gate id, or undefined if unknown. */
|
|
157
|
+
export function getGateDefinition(id) {
|
|
158
|
+
return GATE_REGISTRY[id];
|
|
159
|
+
}
|
|
160
|
+
/** Look up the owner turn for a gate id. Throws if the gate is unknown. */
|
|
161
|
+
export function getOwnerTurn(id) {
|
|
162
|
+
const def = GATE_REGISTRY[id];
|
|
163
|
+
if (!def) {
|
|
164
|
+
throw new GSDError(GSD_PARSE_ERROR, `gate-registry: unknown gate id "${id}"`);
|
|
165
|
+
}
|
|
166
|
+
return def.ownerTurn;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Assert that the pending gate rows for a turn match what the registry says
|
|
170
|
+
* the turn owns. Fails loudly rather than silently skipping.
|
|
171
|
+
*
|
|
172
|
+
* - Every row in `pending` must have a definition whose `ownerTurn` matches `turn`.
|
|
173
|
+
* (The caller is responsible for scoping the pending list — e.g. filtering
|
|
174
|
+
* by slice scope before passing it in.)
|
|
175
|
+
* - `options.requireAll` (default true): every gate the turn owns must appear
|
|
176
|
+
* in `pending`. Set to false for turns like `execute-task` that only need
|
|
177
|
+
* coverage for the subset of gates that were seeded (e.g. tasks with no
|
|
178
|
+
* external dependencies have no Q5 row).
|
|
179
|
+
*/
|
|
180
|
+
export function assertGateCoverage(pending, turn, options = {}) {
|
|
181
|
+
const requireAll = options.requireAll ?? true;
|
|
182
|
+
const expected = getGateIdsForTurn(turn);
|
|
183
|
+
const pendingIds = new Set(pending.map((g) => g.gate_id));
|
|
184
|
+
const unknown = [];
|
|
185
|
+
for (const id of pendingIds) {
|
|
186
|
+
const def = getGateDefinition(id);
|
|
187
|
+
if (!def) {
|
|
188
|
+
unknown.push(id);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (def.ownerTurn !== turn) {
|
|
192
|
+
unknown.push(`${id} (owned by ${def.ownerTurn}, not ${turn})`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (unknown.length > 0) {
|
|
196
|
+
throw new GSDError(GSD_PARSE_ERROR, `assertGateCoverage: turn "${turn}" received pending gates it does not own: ${unknown.join(", ")}`);
|
|
197
|
+
}
|
|
198
|
+
if (requireAll) {
|
|
199
|
+
const missing = [];
|
|
200
|
+
for (const id of expected) {
|
|
201
|
+
if (!pendingIds.has(id))
|
|
202
|
+
missing.push(id);
|
|
203
|
+
}
|
|
204
|
+
if (missing.length > 0) {
|
|
205
|
+
throw new GSDError(GSD_PARSE_ERROR, `assertGateCoverage: turn "${turn}" is missing required gates: ${missing.join(", ")}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -8,6 +8,7 @@ import { createRequire } from "node:module";
|
|
|
8
8
|
import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
|
|
9
9
|
import { dirname } from "node:path";
|
|
10
10
|
import { GSDError, GSD_STALE_STATE } from "./errors.js";
|
|
11
|
+
import { getGateIdsForTurn } from "./gate-registry.js";
|
|
11
12
|
import { logError, logWarning } from "./workflow-logger.js";
|
|
12
13
|
const _require = createRequire(import.meta.url);
|
|
13
14
|
let providerName = null;
|
|
@@ -1927,3 +1928,43 @@ export function getPendingSliceGateCount(milestoneId, sliceId) {
|
|
|
1927
1928
|
WHERE milestone_id = :mid AND slice_id = :sid AND scope = 'slice' AND status = 'pending'`).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
1928
1929
|
return row ? row["cnt"] : 0;
|
|
1929
1930
|
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Return pending gate rows owned by a specific workflow turn.
|
|
1933
|
+
*
|
|
1934
|
+
* Unlike `getPendingGates(..., scope)`, this filters by the registry's
|
|
1935
|
+
* `ownerTurn` metadata so callers can distinguish Q3/Q4 (owned by
|
|
1936
|
+
* gate-evaluate) from Q8 (owned by complete-slice) even though both are
|
|
1937
|
+
* scope:"slice". Pass `taskId` to narrow task-scoped results to one task.
|
|
1938
|
+
*/
|
|
1939
|
+
export function getPendingGatesForTurn(milestoneId, sliceId, turn, taskId) {
|
|
1940
|
+
if (!currentDb)
|
|
1941
|
+
return [];
|
|
1942
|
+
const ids = getGateIdsForTurn(turn);
|
|
1943
|
+
if (ids.size === 0)
|
|
1944
|
+
return [];
|
|
1945
|
+
const idList = [...ids];
|
|
1946
|
+
const placeholders = idList.map((_, i) => `:gid${i}`).join(",");
|
|
1947
|
+
const params = {
|
|
1948
|
+
":mid": milestoneId,
|
|
1949
|
+
":sid": sliceId,
|
|
1950
|
+
};
|
|
1951
|
+
idList.forEach((id, i) => {
|
|
1952
|
+
params[`:gid${i}`] = id;
|
|
1953
|
+
});
|
|
1954
|
+
let sql = `SELECT * FROM quality_gates
|
|
1955
|
+
WHERE milestone_id = :mid AND slice_id = :sid
|
|
1956
|
+
AND status = 'pending'
|
|
1957
|
+
AND gate_id IN (${placeholders})`;
|
|
1958
|
+
if (taskId !== undefined) {
|
|
1959
|
+
sql += ` AND task_id = :tid`;
|
|
1960
|
+
params[":tid"] = taskId;
|
|
1961
|
+
}
|
|
1962
|
+
return currentDb.prepare(sql).all(params).map(rowToGate);
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Count pending gates for a turn. Convenience wrapper used by state
|
|
1966
|
+
* derivation to decide whether a phase transition should pause.
|
|
1967
|
+
*/
|
|
1968
|
+
export function getPendingGateCountForTurn(milestoneId, sliceId, turn) {
|
|
1969
|
+
return getPendingGatesForTurn(milestoneId, sliceId, turn).length;
|
|
1970
|
+
}
|
|
@@ -13,7 +13,7 @@ import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
|
13
13
|
import { buildSkillActivationBlock } from "./auto-prompts.js";
|
|
14
14
|
import { deriveState } from "./state.js";
|
|
15
15
|
import { invalidateAllCaches } from "./cache.js";
|
|
16
|
-
import {
|
|
16
|
+
import { startAutoDetached } from "./auto.js";
|
|
17
17
|
import { clearLock } from "./crash-recovery.js";
|
|
18
18
|
import { assessInterruptedSession, formatInterruptedSessionRunningMessage, formatInterruptedSessionSummary, } from "./interrupted-session.js";
|
|
19
19
|
import { listUnitRuntimeRecords, clearUnitRuntimeRecord } from "./unit-runtime.js";
|
|
@@ -40,7 +40,6 @@ import { runPreparation, formatCodebaseBrief, formatPriorContextBrief, } from ".
|
|
|
40
40
|
// ─── Re-exports (preserve public API for existing importers) ────────────────
|
|
41
41
|
export { MILESTONE_ID_RE, generateMilestoneSuffix, nextMilestoneId, extractMilestoneSeq, parseMilestoneId, milestoneIdSort, maxMilestoneNum, findMilestoneIds, reserveMilestoneId, claimReservedId, getReservedMilestoneIds, clearReservedMilestoneIds, } from "./milestone-ids.js";
|
|
42
42
|
export { showQueue, handleQueueReorder, showQueueAdd, buildExistingMilestonesContext, } from "./guided-flow-queue.js";
|
|
43
|
-
import { getErrorMessage } from "./error-utils.js";
|
|
44
43
|
import { logWarning } from "./workflow-logger.js";
|
|
45
44
|
// ─── ID Generation with Reservation ─────────────────────────────────────────
|
|
46
45
|
/**
|
|
@@ -200,11 +199,7 @@ export function checkAutoStartAfterDiscuss() {
|
|
|
200
199
|
}
|
|
201
200
|
pendingAutoStartMap.delete(basePath);
|
|
202
201
|
ctx.ui.notify(`Milestone ${milestoneId} ready.`, "info");
|
|
203
|
-
|
|
204
|
-
ctx.ui.notify(`Auto-start failed: ${getErrorMessage(err)}`, "error");
|
|
205
|
-
logWarning("guided", `auto start error: ${getErrorMessage(err)}`);
|
|
206
|
-
debugLog("auto-start-failed", { error: getErrorMessage(err) });
|
|
207
|
-
});
|
|
202
|
+
startAutoDetached(ctx, pi, basePath, false, { step });
|
|
208
203
|
return true;
|
|
209
204
|
}
|
|
210
205
|
/**
|
|
@@ -1097,7 +1092,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1097
1092
|
],
|
|
1098
1093
|
});
|
|
1099
1094
|
if (resume === "resume") {
|
|
1100
|
-
|
|
1095
|
+
startAutoDetached(ctx, pi, basePath, false, {
|
|
1101
1096
|
interrupted,
|
|
1102
1097
|
step: interrupted.pausedSession?.stepMode ?? false,
|
|
1103
1098
|
});
|
|
@@ -1409,7 +1404,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1409
1404
|
notYetMessage: "Run /gsd status for details.",
|
|
1410
1405
|
});
|
|
1411
1406
|
if (choice === "auto") {
|
|
1412
|
-
|
|
1407
|
+
startAutoDetached(ctx, pi, basePath, false);
|
|
1413
1408
|
}
|
|
1414
1409
|
else if (choice === "status") {
|
|
1415
1410
|
const { fireStatusViaCommand } = await import("./commands.js");
|
|
@@ -1622,7 +1617,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1622
1617
|
notYetMessage: "Run /gsd when ready.",
|
|
1623
1618
|
});
|
|
1624
1619
|
if (choice === "auto") {
|
|
1625
|
-
|
|
1620
|
+
startAutoDetached(ctx, pi, basePath, false);
|
|
1626
1621
|
return;
|
|
1627
1622
|
}
|
|
1628
1623
|
if (choice === "execute") {
|
|
@@ -15,7 +15,7 @@ import { join } from "node:path";
|
|
|
15
15
|
import { resolveMilestonePath, resolveMilestoneFile, buildMilestoneFileName, } from "./paths.js";
|
|
16
16
|
import { invalidateAllCaches } from "./cache.js";
|
|
17
17
|
import { loadQueueOrder, saveQueueOrder } from "./queue-order.js";
|
|
18
|
-
import { isDbAvailable, updateMilestoneStatus } from "./gsd-db.js";
|
|
18
|
+
import { getMilestone, isDbAvailable, updateMilestoneStatus } from "./gsd-db.js";
|
|
19
19
|
import { logWarning } from "./workflow-logger.js";
|
|
20
20
|
// ─── Park ──────────────────────────────────────────────────────────────────
|
|
21
21
|
/**
|
|
@@ -68,9 +68,15 @@ export function unparkMilestone(basePath, milestoneId) {
|
|
|
68
68
|
if (!mDir || !existsSync(mDir))
|
|
69
69
|
return false;
|
|
70
70
|
const parkedPath = join(mDir, buildMilestoneFileName(milestoneId, "PARKED"));
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
const hadParkedFile = existsSync(parkedPath);
|
|
72
|
+
const dbThinksParked = isDbAvailable() && getMilestone(milestoneId)?.status === "parked";
|
|
73
|
+
// Recover the reverse desync too: DB can still say "parked" even when the
|
|
74
|
+
// PARKED marker was lost on disk, and /gsd unpark should repair that state.
|
|
75
|
+
if (!hadParkedFile && !dbThinksParked)
|
|
76
|
+
return false;
|
|
77
|
+
if (hadParkedFile) {
|
|
78
|
+
unlinkSync(parkedPath);
|
|
79
|
+
}
|
|
74
80
|
// Sync DB status so deriveStateFromDb picks up the unparked milestone (#2694)
|
|
75
81
|
if (isDbAvailable()) {
|
|
76
82
|
try {
|
|
@@ -6,17 +6,12 @@
|
|
|
6
6
|
* records in the DB. This module inserts milestone-level validation gates
|
|
7
7
|
* that correspond to the validation checks performed.
|
|
8
8
|
*
|
|
9
|
-
* Gate IDs for milestone validation
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* MV03 — Cross-slice integration
|
|
13
|
-
* MV04 — Requirement coverage
|
|
14
|
-
*
|
|
15
|
-
* These use the existing quality_gates table with scope "milestone".
|
|
9
|
+
* Gate IDs for milestone validation (MV01–MV04) are sourced from the
|
|
10
|
+
* gate registry so the definitions stay in lockstep with prompt builders,
|
|
11
|
+
* dispatch rules, and state derivation. See gate-registry.ts.
|
|
16
12
|
*/
|
|
17
13
|
import { _getAdapter } from "./gsd-db.js";
|
|
18
|
-
|
|
19
|
-
const MILESTONE_GATE_IDS = ["MV01", "MV02", "MV03", "MV04"];
|
|
14
|
+
import { getGatesForTurn } from "./gate-registry.js";
|
|
20
15
|
/**
|
|
21
16
|
* Insert milestone-level quality_gates records for a validation run.
|
|
22
17
|
*
|
|
@@ -24,21 +19,25 @@ const MILESTONE_GATE_IDS = ["MV01", "MV02", "MV03", "MV04"];
|
|
|
24
19
|
* from the overall milestone validation verdict. Individual gate-level
|
|
25
20
|
* verdicts are not available (the handler receives a single verdict),
|
|
26
21
|
* so all gates share the overall verdict.
|
|
22
|
+
*
|
|
23
|
+
* Gate IDs come from the registry — adding/removing an MV-scoped gate
|
|
24
|
+
* in gate-registry.ts automatically flows through here.
|
|
27
25
|
*/
|
|
28
26
|
export function insertMilestoneValidationGates(milestoneId, sliceId, verdict, evaluatedAt) {
|
|
29
27
|
const db = _getAdapter();
|
|
30
28
|
if (!db)
|
|
31
29
|
return;
|
|
32
30
|
const gateVerdict = verdict === "pass" ? "pass" : "flag";
|
|
33
|
-
|
|
31
|
+
const milestoneGates = getGatesForTurn("validate-milestone");
|
|
32
|
+
for (const def of milestoneGates) {
|
|
34
33
|
db.prepare(`INSERT OR REPLACE INTO quality_gates
|
|
35
34
|
(milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
|
|
36
35
|
VALUES (:mid, :sid, :gid, 'milestone', '', 'complete', :verdict, :rationale, '', :evaluated_at)`).run({
|
|
37
36
|
":mid": milestoneId,
|
|
38
37
|
":sid": sliceId,
|
|
39
|
-
":gid":
|
|
38
|
+
":gid": def.id,
|
|
40
39
|
":verdict": gateVerdict,
|
|
41
|
-
":rationale":
|
|
40
|
+
":rationale": `${def.promptSection} — milestone validation verdict: ${verdict}`,
|
|
42
41
|
":evaluated_at": evaluatedAt,
|
|
43
42
|
});
|
|
44
43
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// GSD Extension — Notification History Overlay
|
|
2
2
|
// Scrollable panel showing all persisted notifications with severity filtering.
|
|
3
|
-
// Toggled with Ctrl+Alt+N (⌃⌥N on macOS)
|
|
3
|
+
// Toggled with Ctrl+Alt+N (⌃⌥N on macOS), Ctrl+Shift+N fallback, or /gsd notifications.
|
|
4
4
|
import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
|
|
5
|
-
import { readNotifications, markAllRead, clearNotifications, } from "./notification-store.js";
|
|
5
|
+
import { readNotifications, markAllRead, clearNotifications, onNotificationStoreChange, } from "./notification-store.js";
|
|
6
|
+
import { formattedShortcutPair } from "./shortcut-defs.js";
|
|
6
7
|
import { padRight, joinColumns } from "../shared/mod.js";
|
|
7
8
|
const FILTER_CYCLE = ["all", "error", "warning", "info"];
|
|
8
9
|
function severityIcon(severity) {
|
|
@@ -55,6 +56,11 @@ function formatTimestamp(ts) {
|
|
|
55
56
|
return ts.slice(11, 19); // fallback: HH:MM:SS
|
|
56
57
|
}
|
|
57
58
|
}
|
|
59
|
+
function notificationSignature(entries) {
|
|
60
|
+
return entries
|
|
61
|
+
.map((entry) => `${entry.ts}|${entry.severity}|${entry.read ? 1 : 0}|${entry.message}`)
|
|
62
|
+
.join("\n");
|
|
63
|
+
}
|
|
58
64
|
export class GSDNotificationOverlay {
|
|
59
65
|
tui;
|
|
60
66
|
theme;
|
|
@@ -64,9 +70,11 @@ export class GSDNotificationOverlay {
|
|
|
64
70
|
scrollOffset = 0;
|
|
65
71
|
filterIndex = 0;
|
|
66
72
|
entries = [];
|
|
73
|
+
entriesSignature = "";
|
|
67
74
|
refreshTimer;
|
|
68
75
|
disposed = false;
|
|
69
76
|
resizeHandler = null;
|
|
77
|
+
unsubscribeStore = null;
|
|
70
78
|
constructor(tui, theme, onClose) {
|
|
71
79
|
this.tui = tui;
|
|
72
80
|
this.theme = theme;
|
|
@@ -74,6 +82,7 @@ export class GSDNotificationOverlay {
|
|
|
74
82
|
// Mark all as read on open
|
|
75
83
|
markAllRead();
|
|
76
84
|
this.entries = readNotifications();
|
|
85
|
+
this.entriesSignature = notificationSignature(this.entries);
|
|
77
86
|
// Resize handler
|
|
78
87
|
this.resizeHandler = () => {
|
|
79
88
|
if (this.disposed)
|
|
@@ -82,18 +91,18 @@ export class GSDNotificationOverlay {
|
|
|
82
91
|
this.tui.requestRender();
|
|
83
92
|
};
|
|
84
93
|
process.stdout.on("resize", this.resizeHandler);
|
|
85
|
-
//
|
|
94
|
+
// Subscribe to store mutations for immediate updates
|
|
95
|
+
this.unsubscribeStore = onNotificationStoreChange(() => {
|
|
96
|
+
if (this.disposed)
|
|
97
|
+
return;
|
|
98
|
+
this._refreshFromDisk();
|
|
99
|
+
});
|
|
100
|
+
// 30s safety-net for cross-process edits (web subprocess, parallel workers)
|
|
86
101
|
this.refreshTimer = setInterval(() => {
|
|
87
102
|
if (this.disposed)
|
|
88
103
|
return;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.entries = fresh;
|
|
92
|
-
markAllRead();
|
|
93
|
-
this.invalidate();
|
|
94
|
-
this.tui.requestRender();
|
|
95
|
-
}
|
|
96
|
-
}, 3000);
|
|
104
|
+
this._refreshFromDisk();
|
|
105
|
+
}, 30_000);
|
|
97
106
|
}
|
|
98
107
|
get filter() {
|
|
99
108
|
return FILTER_CYCLE[this.filterIndex];
|
|
@@ -104,7 +113,10 @@ export class GSDNotificationOverlay {
|
|
|
104
113
|
return this.entries.filter((e) => e.severity === this.filter);
|
|
105
114
|
}
|
|
106
115
|
handleInput(data) {
|
|
107
|
-
if (matchesKey(data, Key.escape) ||
|
|
116
|
+
if (matchesKey(data, Key.escape) ||
|
|
117
|
+
matchesKey(data, Key.ctrl("c")) ||
|
|
118
|
+
matchesKey(data, Key.ctrlAlt("n")) ||
|
|
119
|
+
matchesKey(data, Key.ctrlShift("n"))) {
|
|
108
120
|
this.dispose();
|
|
109
121
|
this.onClose();
|
|
110
122
|
return;
|
|
@@ -146,6 +158,7 @@ export class GSDNotificationOverlay {
|
|
|
146
158
|
if (data === "c") {
|
|
147
159
|
clearNotifications();
|
|
148
160
|
this.entries = [];
|
|
161
|
+
this.entriesSignature = notificationSignature(this.entries);
|
|
149
162
|
this.scrollOffset = 0;
|
|
150
163
|
this.invalidate();
|
|
151
164
|
this.tui.requestRender();
|
|
@@ -174,11 +187,26 @@ export class GSDNotificationOverlay {
|
|
|
174
187
|
dispose() {
|
|
175
188
|
this.disposed = true;
|
|
176
189
|
clearInterval(this.refreshTimer);
|
|
190
|
+
if (this.unsubscribeStore) {
|
|
191
|
+
this.unsubscribeStore();
|
|
192
|
+
this.unsubscribeStore = null;
|
|
193
|
+
}
|
|
177
194
|
if (this.resizeHandler) {
|
|
178
195
|
process.stdout.removeListener("resize", this.resizeHandler);
|
|
179
196
|
this.resizeHandler = null;
|
|
180
197
|
}
|
|
181
198
|
}
|
|
199
|
+
_refreshFromDisk() {
|
|
200
|
+
const fresh = readNotifications();
|
|
201
|
+
const signature = notificationSignature(fresh);
|
|
202
|
+
if (signature !== this.entriesSignature) {
|
|
203
|
+
markAllRead();
|
|
204
|
+
this.entries = readNotifications();
|
|
205
|
+
this.entriesSignature = notificationSignature(this.entries);
|
|
206
|
+
this.invalidate();
|
|
207
|
+
this.tui.requestRender();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
182
210
|
wrapInBox(inner, width) {
|
|
183
211
|
const th = this.theme;
|
|
184
212
|
const border = (s) => th.fg("borderAccent", s);
|
|
@@ -215,7 +243,8 @@ export class GSDNotificationOverlay {
|
|
|
215
243
|
lines.push(row(joinColumns(`${title} ${th.fg("dim", "filter:")} ${filterLabel}`, th.fg("dim", count), contentWidth)));
|
|
216
244
|
lines.push(hr());
|
|
217
245
|
// Controls
|
|
218
|
-
|
|
246
|
+
const closeShortcut = formattedShortcutPair("notifications");
|
|
247
|
+
lines.push(row(th.fg("dim", `↑/↓ scroll f filter c clear Esc close (${closeShortcut})`)));
|
|
219
248
|
lines.push(blank());
|
|
220
249
|
// Entries
|
|
221
250
|
const filtered = this.filteredEntries;
|
|
@@ -16,6 +16,7 @@ let _basePath = null;
|
|
|
16
16
|
let _lineCount = 0; // Hint for rotation — not authoritative for public API
|
|
17
17
|
let _suppressCount = 0;
|
|
18
18
|
let _recentMessageTimestamps = new Map();
|
|
19
|
+
const _changeListeners = new Set();
|
|
19
20
|
// ─── Public API ─────────────────────────────────────────────────────────
|
|
20
21
|
/**
|
|
21
22
|
* Initialize the notification store. Call once at session start with the
|
|
@@ -68,6 +69,7 @@ export function appendNotification(message, severity, source = "notify") {
|
|
|
68
69
|
if (_lineCount > MAX_ENTRIES) {
|
|
69
70
|
_rotate();
|
|
70
71
|
}
|
|
72
|
+
_emitChange();
|
|
71
73
|
}
|
|
72
74
|
catch {
|
|
73
75
|
// Non-fatal — never let persistence break the caller
|
|
@@ -96,6 +98,7 @@ export function markAllRead(basePath) {
|
|
|
96
98
|
const hasUnread = entries.some((e) => !e.read);
|
|
97
99
|
if (!hasUnread)
|
|
98
100
|
return;
|
|
101
|
+
let changed = false;
|
|
99
102
|
try {
|
|
100
103
|
_withLock(bp, () => {
|
|
101
104
|
// Re-read inside lock to get freshest state
|
|
@@ -104,11 +107,14 @@ export function markAllRead(basePath) {
|
|
|
104
107
|
return;
|
|
105
108
|
const lines = fresh.map((e) => JSON.stringify({ ...e, read: true }));
|
|
106
109
|
_atomicWrite(bp, lines.join("\n") + "\n");
|
|
110
|
+
changed = true;
|
|
107
111
|
});
|
|
108
112
|
}
|
|
109
113
|
catch {
|
|
110
114
|
// Non-fatal
|
|
111
115
|
}
|
|
116
|
+
if (changed)
|
|
117
|
+
_emitChange();
|
|
112
118
|
}
|
|
113
119
|
/**
|
|
114
120
|
* Clear all notifications. Atomic write of empty content under lock.
|
|
@@ -121,6 +127,8 @@ export function clearNotifications(basePath) {
|
|
|
121
127
|
_withLock(bp, () => {
|
|
122
128
|
_atomicWrite(bp, "");
|
|
123
129
|
});
|
|
130
|
+
_lineCount = 0;
|
|
131
|
+
_emitChange();
|
|
124
132
|
}
|
|
125
133
|
catch {
|
|
126
134
|
// Non-fatal
|
|
@@ -165,6 +173,16 @@ export function suppressPersistence() {
|
|
|
165
173
|
export function unsuppressPersistence() {
|
|
166
174
|
_suppressCount = Math.max(0, _suppressCount - 1);
|
|
167
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Subscribe to notification-store mutations (append, mark-read, clear).
|
|
178
|
+
* Returns an unsubscribe function.
|
|
179
|
+
*/
|
|
180
|
+
export function onNotificationStoreChange(listener) {
|
|
181
|
+
_changeListeners.add(listener);
|
|
182
|
+
return () => {
|
|
183
|
+
_changeListeners.delete(listener);
|
|
184
|
+
};
|
|
185
|
+
}
|
|
168
186
|
// ─── Test Helpers ───────────────────────────────────────────────────────
|
|
169
187
|
/**
|
|
170
188
|
* Reset module state. Only for use in tests.
|
|
@@ -174,6 +192,7 @@ export function _resetNotificationStore() {
|
|
|
174
192
|
_lineCount = 0;
|
|
175
193
|
_suppressCount = 0;
|
|
176
194
|
_recentMessageTimestamps = new Map();
|
|
195
|
+
_changeListeners.clear();
|
|
177
196
|
}
|
|
178
197
|
// ─── Internal ───────────────────────────────────────────────────────────
|
|
179
198
|
function _readEntriesFromDisk(basePath) {
|
|
@@ -211,12 +230,23 @@ function _rotate() {
|
|
|
211
230
|
const trimmed = entries.slice(entries.length - MAX_ENTRIES);
|
|
212
231
|
const lines = trimmed.map((e) => JSON.stringify(e));
|
|
213
232
|
_atomicWrite(_basePath, lines.join("\n") + "\n");
|
|
233
|
+
_lineCount = trimmed.length;
|
|
214
234
|
});
|
|
215
235
|
}
|
|
216
236
|
catch {
|
|
217
237
|
// Non-fatal
|
|
218
238
|
}
|
|
219
239
|
}
|
|
240
|
+
function _emitChange() {
|
|
241
|
+
for (const listener of _changeListeners) {
|
|
242
|
+
try {
|
|
243
|
+
listener();
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// Non-fatal
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
220
250
|
/**
|
|
221
251
|
* Atomic file rewrite via temp-file + rename. Prevents partial reads
|
|
222
252
|
* by other processes (web API subprocess, parallel workers).
|
|
@@ -271,10 +301,11 @@ function _withLock(basePath, fn) {
|
|
|
271
301
|
break;
|
|
272
302
|
}
|
|
273
303
|
}
|
|
274
|
-
//
|
|
275
|
-
|
|
304
|
+
// Best-effort: mutation runs regardless of lock status (idempotent overwrites).
|
|
305
|
+
// createdLock gates cleanup only — never skip fn() on lock failure.
|
|
306
|
+
const createdLock = fd !== null;
|
|
276
307
|
try {
|
|
277
|
-
if (
|
|
308
|
+
if (createdLock && fd !== null) {
|
|
278
309
|
// Write our PID timestamp into the lock for stale detection
|
|
279
310
|
writeFileSync(lockPath, String(Date.now()), "utf-8");
|
|
280
311
|
closeSync(fd);
|
|
@@ -283,7 +314,7 @@ function _withLock(basePath, fn) {
|
|
|
283
314
|
}
|
|
284
315
|
finally {
|
|
285
316
|
// Only delete the lock if we created it — never remove another process's lock
|
|
286
|
-
if (
|
|
317
|
+
if (createdLock) {
|
|
287
318
|
try {
|
|
288
319
|
unlinkSync(lockPath);
|
|
289
320
|
}
|