gsd-pi 2.77.0-dev.58d3d4d6c → 2.77.0-dev.cfd69e714
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 +1 -1
- package/dist/claude-cli-check.js +5 -1
- package/dist/headless.js +49 -4
- package/dist/resource-loader.d.ts +40 -0
- package/dist/resource-loader.js +32 -13
- package/dist/resources/extensions/browser-tools/capture.js +9 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
- package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
- package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
- package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
- package/dist/resources/extensions/claude-code-cli/readiness.js +5 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +481 -17
- package/dist/resources/extensions/gsd/auto/loop.js +43 -0
- package/dist/resources/extensions/gsd/auto/phases.js +15 -21
- package/dist/resources/extensions/gsd/auto/session.js +0 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +102 -24
- package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +71 -64
- package/dist/resources/extensions/gsd/auto-prompts.js +329 -102
- package/dist/resources/extensions/gsd/auto-recovery.js +195 -23
- package/dist/resources/extensions/gsd/auto-start.js +34 -24
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
- package/dist/resources/extensions/gsd/auto-worktree.js +122 -26
- package/dist/resources/extensions/gsd/auto.js +31 -20
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
- package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +3 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
- package/dist/resources/extensions/gsd/component-loader.js +447 -0
- package/dist/resources/extensions/gsd/component-types.js +69 -0
- package/dist/resources/extensions/gsd/detection.js +49 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/dist/resources/extensions/gsd/gate-registry.js +2 -2
- package/dist/resources/extensions/gsd/git-constants.js +28 -1
- package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
- package/dist/resources/extensions/gsd/git-service.js +126 -2
- package/dist/resources/extensions/gsd/gsd-db.js +6 -3
- package/dist/resources/extensions/gsd/guided-flow.js +17 -5
- package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
- package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
- package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
- package/dist/resources/extensions/gsd/model-router.js +6 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
- package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
- package/dist/resources/extensions/gsd/service-tier.js +5 -2
- package/dist/resources/extensions/gsd/session-lock.js +19 -10
- package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
- package/dist/resources/extensions/gsd/state.js +44 -33
- package/dist/resources/extensions/gsd/sync-lock.js +98 -42
- package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
- package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
- package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +34 -8
- package/dist/resources/extensions/mcp-client/index.js +3 -1
- package/dist/resources/extensions/ollama/index.js +5 -1
- package/dist/resources/extensions/remote-questions/manager.js +11 -5
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
- 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/git/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 +5 -5
- package/dist/web/standalone/.next/server/chunks/1926.js +1 -1
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- 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 +2 -3
- package/packages/daemon/src/logger.ts +4 -3
- package/packages/mcp-server/dist/server.d.ts +24 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +88 -87
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +25 -3
- package/packages/mcp-server/src/readers/graph.test.ts +87 -15
- package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
- package/packages/mcp-server/src/server.ts +131 -105
- package/packages/mcp-server/src/workflow-tools.test.ts +80 -39
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +1 -1
- package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
- package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
- package/packages/native/src/__tests__/ps.test.mjs +14 -8
- package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
- package/packages/native/src/__tests__/truncate.test.mjs +17 -2
- package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
- package/packages/pi-agent-core/src/agent.test.ts +96 -102
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/capability-patches.js +9 -2
- package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
- package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
- package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
- package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai.js +17 -0
- package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.test.js +43 -70
- package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
- package/packages/pi-ai/dist/models.test.js +36 -11
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/scripts/generate-models.ts +44 -0
- package/packages/pi-ai/src/models/capability-patches.ts +10 -2
- package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
- package/packages/pi-ai/src/models/generated/openai.ts +17 -0
- package/packages/pi-ai/src/models.generated.test.ts +46 -73
- package/packages/pi-ai/src/models.test.ts +48 -11
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
- package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -0
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +125 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
- 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 +105 -13
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
- package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
- package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
- package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
- package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
- package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
- package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
- package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +36 -12
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
- package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
- package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
- package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
- package/packages/pi-tui/dist/components/editor.d.ts +14 -0
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +19 -0
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/dist/components/image.test.js +6 -5
- package/packages/pi-tui/dist/components/image.test.js.map +1 -1
- package/packages/pi-tui/dist/editor-component.d.ts +2 -0
- package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
- package/packages/pi-tui/dist/editor-component.js.map +1 -1
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
- package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
- package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +41 -12
- package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
- package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
- package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
- package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
- package/packages/pi-tui/src/components/editor.ts +22 -0
- package/packages/pi-tui/src/components/image.test.ts +10 -5
- package/packages/pi-tui/src/editor-component.ts +3 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/dist/rpc-client.test.js +101 -51
- package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
- package/packages/rpc-client/src/rpc-client.test.ts +109 -52
- package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
- package/scripts/install.js +15 -1
- package/src/resources/extensions/browser-tools/capture.ts +12 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
- package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
- package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
- package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
- package/src/resources/extensions/claude-code-cli/readiness.ts +5 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +518 -19
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +919 -75
- package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
- package/src/resources/extensions/github-sync/tests/templates.test.ts +33 -1
- package/src/resources/extensions/gsd/auto/loop.ts +47 -0
- package/src/resources/extensions/gsd/auto/phases.ts +16 -20
- package/src/resources/extensions/gsd/auto/session.ts +0 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +113 -24
- package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +82 -73
- package/src/resources/extensions/gsd/auto-prompts.ts +330 -90
- package/src/resources/extensions/gsd/auto-recovery.ts +225 -24
- package/src/resources/extensions/gsd/auto-start.ts +54 -6
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
- package/src/resources/extensions/gsd/auto-worktree.ts +130 -26
- package/src/resources/extensions/gsd/auto.ts +43 -22
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
- package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +3 -7
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
- package/src/resources/extensions/gsd/component-loader.ts +598 -0
- package/src/resources/extensions/gsd/component-types.ts +362 -0
- package/src/resources/extensions/gsd/detection.ts +58 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/src/resources/extensions/gsd/gate-registry.ts +2 -2
- package/src/resources/extensions/gsd/git-constants.ts +30 -1
- package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
- package/src/resources/extensions/gsd/git-service.ts +133 -2
- package/src/resources/extensions/gsd/gsd-db.ts +6 -3
- package/src/resources/extensions/gsd/guided-flow.ts +20 -5
- package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
- package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
- package/src/resources/extensions/gsd/model-router.ts +6 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
- package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
- package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
- package/src/resources/extensions/gsd/service-tier.ts +5 -2
- package/src/resources/extensions/gsd/session-lock.ts +20 -10
- package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
- package/src/resources/extensions/gsd/state.ts +49 -44
- package/src/resources/extensions/gsd/sync-lock.ts +97 -39
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
- package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +94 -289
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -197
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
- package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
- package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +9 -105
- package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
- package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -57
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
- package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
- package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -62
- package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -49
- package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -133
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
- package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +23 -24
- package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
- package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
- package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
- package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
- package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
- package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
- package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
- package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/test-helpers.test.ts +12 -61
- package/src/resources/extensions/gsd/tests/test-helpers.ts +21 -8
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
- package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -81
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
- package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
- package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
- package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
- package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
- package/src/resources/extensions/gsd/types.ts +3 -3
- package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
- package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
- package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +55 -7
- package/src/resources/extensions/mcp-client/index.ts +3 -1
- package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
- package/src/resources/extensions/ollama/index.ts +5 -1
- package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
- package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
- package/src/resources/extensions/remote-questions/manager.ts +36 -4
- package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
- package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
- package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
- package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -144
- package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
- package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
- package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
- package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
- package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
- package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
- package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -75
- package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
- package/src/resources/extensions/gsd/tests/forensics-worktree-telemetry.test.ts +0 -145
- package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
- package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
- package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -130
- package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -43
- /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{Cev5xrAYA3ZGTRLyjR2fX → SvCJDZPQW104bR1KnBQg1}/_ssgManifest.js +0 -0
|
@@ -353,7 +353,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
353
353
|
if (isAutoActive() && typeof event.toolCallId === "string") {
|
|
354
354
|
markToolEnd(event.toolCallId);
|
|
355
355
|
}
|
|
356
|
-
if (isAutoActive() && event.isError
|
|
356
|
+
if (isAutoActive() && event.isError) {
|
|
357
357
|
const resultPayload = ("result" in event ? event.result : undefined);
|
|
358
358
|
const errorText = typeof resultPayload === "string"
|
|
359
359
|
? resultPayload
|
|
@@ -362,6 +362,8 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
362
362
|
: (typeof event.content === "string"
|
|
363
363
|
? event.content
|
|
364
364
|
: String(resultPayload ?? "")));
|
|
365
|
+
// Let recordToolInvocationError classify the failure so non-gsd_ harness
|
|
366
|
+
// errors and deterministic policy rejections are handled consistently.
|
|
365
367
|
recordToolInvocationError(event.toolName, errorText);
|
|
366
368
|
}
|
|
367
369
|
if (event.toolName !== "ask_user_questions")
|
|
@@ -447,12 +449,14 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
447
449
|
});
|
|
448
450
|
pi.on("tool_execution_end", async (event) => {
|
|
449
451
|
markToolEnd(event.toolCallId);
|
|
450
|
-
// #2883: Capture
|
|
452
|
+
// #2883/#4974: Capture deterministic invocation/policy errors
|
|
451
453
|
// so postUnitPreVerification can break the retry loop instead of re-dispatching.
|
|
452
|
-
if (event.isError
|
|
454
|
+
if (event.isError) {
|
|
453
455
|
const errorText = typeof event.result === "string"
|
|
454
456
|
? event.result
|
|
455
457
|
: (typeof event.result?.content?.[0]?.text === "string" ? event.result.content[0].text : String(event.result));
|
|
458
|
+
// Let recordToolInvocationError classify the failure so non-gsd_ harness
|
|
459
|
+
// errors and deterministic policy rejections are handled consistently.
|
|
456
460
|
recordToolInvocationError(event.toolName, errorText);
|
|
457
461
|
}
|
|
458
462
|
// Safety harness: record tool execution results for evidence cross-referencing
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { minimatch } from "minimatch";
|
|
4
|
+
/**
|
|
5
|
+
* Regex matching milestone CONTEXT.md file names in both legacy M001
|
|
6
|
+
* and unique M001-abc123 formats. Exported so regex-hardening tests
|
|
7
|
+
* can exercise the real pattern rather than a drift-prone inline
|
|
8
|
+
* re-implementation (see #4835).
|
|
9
|
+
*/
|
|
10
|
+
export const MILESTONE_CONTEXT_RE = /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/;
|
|
4
11
|
const CONTEXT_MILESTONE_RE = /(?:^|[/\\])(M\d+(?:-[a-z0-9]{6})?)-CONTEXT\.md$/i;
|
|
5
12
|
const DEPTH_VERIFICATION_MILESTONE_RE = /depth_verification[_-](M\d+(?:-[a-z0-9]{6})?)/i;
|
|
6
13
|
/**
|
|
@@ -76,8 +83,15 @@ const GATE_SAFE_TOOLS = new Set([
|
|
|
76
83
|
"search-the-web", "resolve_library", "get_library_docs", "fetch_page",
|
|
77
84
|
"search_and_read",
|
|
78
85
|
]);
|
|
86
|
+
/**
|
|
87
|
+
* Persistence is ON by default (opt-out).
|
|
88
|
+
* Set GSD_PERSIST_WRITE_GATE_STATE="0" or GSD_PERSIST_WRITE_GATE_STATE="false"
|
|
89
|
+
* to disable. All other values — including unset — persist the snapshot.
|
|
90
|
+
* (Inverted from the original opt-in guard; see #4950.)
|
|
91
|
+
*/
|
|
79
92
|
function shouldPersistWriteGateSnapshot(env = process.env) {
|
|
80
|
-
|
|
93
|
+
const v = env.GSD_PERSIST_WRITE_GATE_STATE;
|
|
94
|
+
return v !== "0" && v !== "false";
|
|
81
95
|
}
|
|
82
96
|
function writeGateSnapshotPath(basePath = process.cwd()) {
|
|
83
97
|
return join(basePath, ".gsd", "runtime", "write-gate-state.json");
|
|
@@ -94,9 +108,22 @@ function persistWriteGateSnapshot(basePath = process.cwd()) {
|
|
|
94
108
|
return;
|
|
95
109
|
const path = writeGateSnapshotPath(basePath);
|
|
96
110
|
mkdirSync(join(basePath, ".gsd", "runtime"), { recursive: true });
|
|
97
|
-
const tempPath = `${path}.tmp`;
|
|
111
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
98
112
|
writeFileSync(tempPath, JSON.stringify(currentWriteGateSnapshot(), null, 2), "utf-8");
|
|
99
|
-
|
|
113
|
+
try {
|
|
114
|
+
renameSync(tempPath, path);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
// EXDEV: cross-device rename (temp and dest on different mounts). Fall back
|
|
118
|
+
// to copy-then-delete so the snapshot is still written atomically enough.
|
|
119
|
+
if (err instanceof Error && err.code === "EXDEV") {
|
|
120
|
+
copyFileSync(tempPath, path);
|
|
121
|
+
unlinkSync(tempPath);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
100
127
|
}
|
|
101
128
|
function clearPersistedWriteGateSnapshot(basePath = process.cwd()) {
|
|
102
129
|
if (!shouldPersistWriteGateSnapshot())
|
|
@@ -295,9 +322,9 @@ export function isDepthConfirmationAnswer(selected, options) {
|
|
|
295
322
|
const confirmLabel = options[0]?.label;
|
|
296
323
|
return typeof confirmLabel === "string" && value === confirmLabel;
|
|
297
324
|
}
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
return
|
|
325
|
+
// Fail-closed: no options means we cannot structurally validate the answer.
|
|
326
|
+
// Returning false prevents any free-form string from unlocking the gate.
|
|
327
|
+
return false;
|
|
301
328
|
}
|
|
302
329
|
export function shouldBlockContextWrite(toolName, inputPath, milestoneId, _queuePhaseActive) {
|
|
303
330
|
if (toolName !== "write")
|
|
@@ -413,3 +440,94 @@ export function shouldBlockQueueExecutionInSnapshot(snapshot, toolName, input, q
|
|
|
413
440
|
reason: `Blocked: /gsd queue is a planning tool — it creates milestones, not executes work. Unknown tools are not permitted during queue mode.`,
|
|
414
441
|
};
|
|
415
442
|
}
|
|
443
|
+
// ─── Planning-unit tools-policy enforcement (#4934) ───────────────────────
|
|
444
|
+
const PLANNING_WRITE_TOOLS = new Set(["write", "edit", "multi_edit", "notebook_edit"]);
|
|
445
|
+
const PLANNING_SUBAGENT_TOOLS = new Set(["subagent", "task"]);
|
|
446
|
+
const PLANNING_SAFE_TOOLS = new Set([
|
|
447
|
+
"read", "grep", "find", "ls", "glob",
|
|
448
|
+
"ask_user_questions",
|
|
449
|
+
"search-the-web", "resolve_library", "get_library_docs", "fetch_page",
|
|
450
|
+
"search_and_read",
|
|
451
|
+
]);
|
|
452
|
+
function isPathUnderGsd(absPath, basePath) {
|
|
453
|
+
const gsdRoot = resolve(basePath, ".gsd");
|
|
454
|
+
const rel = relative(gsdRoot, absPath);
|
|
455
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
456
|
+
}
|
|
457
|
+
function matchesAllowedGlob(absPath, basePath, globs) {
|
|
458
|
+
const rel = relative(basePath, absPath);
|
|
459
|
+
if (rel.startsWith("..") || isAbsolute(rel))
|
|
460
|
+
return false;
|
|
461
|
+
const posix = rel.split(sep).join("/");
|
|
462
|
+
return globs.some(g => minimatch(posix, g, { dot: false, nocase: false }));
|
|
463
|
+
}
|
|
464
|
+
function blockReason(unitType, mode, what) {
|
|
465
|
+
return [
|
|
466
|
+
`HARD BLOCK: unit "${unitType}" runs under tools-policy "${mode}" — ${what}.`,
|
|
467
|
+
`This is a mechanical gate enforced by manifest.tools (#4934). You MUST NOT proceed,`,
|
|
468
|
+
`retry the same call, or rationalize past this block. If you need to write user source,`,
|
|
469
|
+
`the work belongs in execute-task, not in a planning unit.`,
|
|
470
|
+
].join(" ");
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Planning-unit tool-policy enforcement. Returns { block } per the policy
|
|
474
|
+
* resolved from the active unit's manifest:
|
|
475
|
+
*
|
|
476
|
+
* - "all" → never blocks.
|
|
477
|
+
* - "read-only" → blocks all writes, bash, and subagent dispatch.
|
|
478
|
+
* - "planning" → blocks writes to paths outside <basePath>/.gsd/,
|
|
479
|
+
* bash that isn't read-only, and subagent dispatch.
|
|
480
|
+
* - "docs" → like "planning" but also allows writes to paths
|
|
481
|
+
* matching `allowedPathGlobs` relative to basePath.
|
|
482
|
+
*
|
|
483
|
+
* `policy` of null means "no manifest resolved" — pass-through.
|
|
484
|
+
*/
|
|
485
|
+
export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitType, policy) {
|
|
486
|
+
if (!policy)
|
|
487
|
+
return { block: false };
|
|
488
|
+
if (policy.mode === "all")
|
|
489
|
+
return { block: false };
|
|
490
|
+
const tool = toolName;
|
|
491
|
+
if (policy.mode === "read-only") {
|
|
492
|
+
if (PLANNING_SAFE_TOOLS.has(tool))
|
|
493
|
+
return { block: false };
|
|
494
|
+
if (tool.startsWith("gsd_"))
|
|
495
|
+
return { block: false };
|
|
496
|
+
if (PLANNING_WRITE_TOOLS.has(tool) || tool === "bash" || PLANNING_SUBAGENT_TOOLS.has(tool)) {
|
|
497
|
+
return { block: true, reason: blockReason(unitType, policy.mode, `${tool} is not permitted (read-only)`) };
|
|
498
|
+
}
|
|
499
|
+
return { block: true, reason: blockReason(unitType, policy.mode, `tool "${tool}" is not on the read-only allowlist`) };
|
|
500
|
+
}
|
|
501
|
+
// planning / docs modes
|
|
502
|
+
if (PLANNING_SAFE_TOOLS.has(tool))
|
|
503
|
+
return { block: false };
|
|
504
|
+
if (tool.startsWith("gsd_"))
|
|
505
|
+
return { block: false };
|
|
506
|
+
if (PLANNING_SUBAGENT_TOOLS.has(tool)) {
|
|
507
|
+
return { block: true, reason: blockReason(unitType, policy.mode, `subagent dispatch is not permitted in planning units`) };
|
|
508
|
+
}
|
|
509
|
+
if (tool === "bash") {
|
|
510
|
+
if (BASH_READ_ONLY_RE.test(pathOrCommand))
|
|
511
|
+
return { block: false };
|
|
512
|
+
return {
|
|
513
|
+
block: true,
|
|
514
|
+
reason: blockReason(unitType, policy.mode, `bash is restricted to read-only commands (cat/grep/git log/etc); cannot run "${pathOrCommand.slice(0, 80)}${pathOrCommand.length > 80 ? "…" : ""}"`),
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
if (PLANNING_WRITE_TOOLS.has(tool)) {
|
|
518
|
+
if (!pathOrCommand) {
|
|
519
|
+
return { block: true, reason: blockReason(unitType, policy.mode, `${tool} called with empty path`) };
|
|
520
|
+
}
|
|
521
|
+
const absPath = isAbsolute(pathOrCommand) ? pathOrCommand : resolve(basePath, pathOrCommand);
|
|
522
|
+
if (isPathUnderGsd(absPath, basePath))
|
|
523
|
+
return { block: false };
|
|
524
|
+
if (policy.mode === "docs" && matchesAllowedGlob(absPath, basePath, policy.allowedPathGlobs)) {
|
|
525
|
+
return { block: false };
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
block: true,
|
|
529
|
+
reason: blockReason(unitType, policy.mode, `cannot ${tool} "${pathOrCommand}" — writes are restricted to .gsd/${policy.mode === "docs" ? " and " + policy.allowedPathGlobs.join(", ") : ""}`),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
return { block: false };
|
|
533
|
+
}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Loader
|
|
3
|
+
*
|
|
4
|
+
* Multi-format loader that handles:
|
|
5
|
+
* 1. New format: component.yaml + SKILL.md/AGENT.md
|
|
6
|
+
* 2. Legacy skill format: SKILL.md with YAML frontmatter
|
|
7
|
+
* 3. Legacy agent format: .md with YAML frontmatter (name, description, tools, model)
|
|
8
|
+
*
|
|
9
|
+
* Auto-detects format by checking for component.yaml first, then falling back
|
|
10
|
+
* to legacy formats based on file naming conventions.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
13
|
+
import { basename, dirname, join } from 'node:path';
|
|
14
|
+
import { parse as parseYaml } from 'yaml';
|
|
15
|
+
import { parseFrontmatter } from '@gsd/pi-coding-agent';
|
|
16
|
+
import { validateComponentName, validateComponentDescription, computeComponentId, } from './component-types.js';
|
|
17
|
+
const SUPPORTED_COMPONENT_KINDS = ['skill', 'agent'];
|
|
18
|
+
const SUPPORTED_API_VERSIONS = ['gsd/v1'];
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Single Component Loading
|
|
21
|
+
// ============================================================================
|
|
22
|
+
/**
|
|
23
|
+
* Load a component from a directory.
|
|
24
|
+
* Checks for component.yaml first, then legacy formats.
|
|
25
|
+
*/
|
|
26
|
+
export function loadComponentFromDir(dir, source) {
|
|
27
|
+
const diagnostics = [];
|
|
28
|
+
// Try new format first: component.yaml
|
|
29
|
+
const componentYamlPath = join(dir, 'component.yaml');
|
|
30
|
+
if (existsSync(componentYamlPath)) {
|
|
31
|
+
return loadFromComponentYaml(componentYamlPath, dir, source);
|
|
32
|
+
}
|
|
33
|
+
// Try legacy skill format: SKILL.md
|
|
34
|
+
const skillMdPath = join(dir, 'SKILL.md');
|
|
35
|
+
if (existsSync(skillMdPath)) {
|
|
36
|
+
return loadFromLegacySkill(skillMdPath, dir, source);
|
|
37
|
+
}
|
|
38
|
+
// No recognized component format found
|
|
39
|
+
return { component: null, diagnostics };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Load a component from a legacy agent .md file (flat file, not directory).
|
|
43
|
+
*/
|
|
44
|
+
export function loadComponentFromAgentFile(filePath, source) {
|
|
45
|
+
return loadFromLegacyAgent(filePath, source);
|
|
46
|
+
}
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// New Format: component.yaml
|
|
49
|
+
// ============================================================================
|
|
50
|
+
function loadFromComponentYaml(yamlPath, dir, source) {
|
|
51
|
+
const diagnostics = [];
|
|
52
|
+
let raw;
|
|
53
|
+
try {
|
|
54
|
+
raw = readFileSync(yamlPath, 'utf-8');
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const msg = error instanceof Error ? error.message : 'failed to read component.yaml';
|
|
58
|
+
diagnostics.push({ type: 'error', message: msg, path: yamlPath });
|
|
59
|
+
return { component: null, diagnostics };
|
|
60
|
+
}
|
|
61
|
+
let definition;
|
|
62
|
+
try {
|
|
63
|
+
definition = parseYaml(raw);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const msg = error instanceof Error ? error.message : 'failed to parse component.yaml';
|
|
67
|
+
diagnostics.push({ type: 'error', message: `invalid YAML: ${msg}`, path: yamlPath });
|
|
68
|
+
return { component: null, diagnostics };
|
|
69
|
+
}
|
|
70
|
+
// Validate required fields
|
|
71
|
+
if (!definition?.apiVersion) {
|
|
72
|
+
diagnostics.push({ type: 'error', message: 'missing apiVersion', path: yamlPath });
|
|
73
|
+
return { component: null, diagnostics };
|
|
74
|
+
}
|
|
75
|
+
if (!SUPPORTED_API_VERSIONS.includes(definition.apiVersion)) {
|
|
76
|
+
diagnostics.push({
|
|
77
|
+
type: 'error',
|
|
78
|
+
message: `unsupported apiVersion "${String(definition.apiVersion)}"`,
|
|
79
|
+
path: yamlPath,
|
|
80
|
+
});
|
|
81
|
+
return { component: null, diagnostics };
|
|
82
|
+
}
|
|
83
|
+
if (!definition.kind) {
|
|
84
|
+
diagnostics.push({ type: 'error', message: 'missing kind', path: yamlPath });
|
|
85
|
+
return { component: null, diagnostics };
|
|
86
|
+
}
|
|
87
|
+
if (!SUPPORTED_COMPONENT_KINDS.includes(definition.kind)) {
|
|
88
|
+
diagnostics.push({
|
|
89
|
+
type: 'error',
|
|
90
|
+
message: `unsupported kind "${definition.kind}"`,
|
|
91
|
+
path: yamlPath,
|
|
92
|
+
});
|
|
93
|
+
return { component: null, diagnostics };
|
|
94
|
+
}
|
|
95
|
+
if (!definition.metadata?.name) {
|
|
96
|
+
diagnostics.push({ type: 'error', message: 'missing metadata.name', path: yamlPath });
|
|
97
|
+
return { component: null, diagnostics };
|
|
98
|
+
}
|
|
99
|
+
if (!definition.metadata?.description) {
|
|
100
|
+
diagnostics.push({ type: 'error', message: 'missing metadata.description', path: yamlPath });
|
|
101
|
+
return { component: null, diagnostics };
|
|
102
|
+
}
|
|
103
|
+
const nameErrors = validateComponentName(definition.metadata.name);
|
|
104
|
+
for (const err of nameErrors) {
|
|
105
|
+
diagnostics.push({ type: 'error', message: err, path: yamlPath });
|
|
106
|
+
}
|
|
107
|
+
const descErrors = validateComponentDescription(definition.metadata.description);
|
|
108
|
+
for (const err of descErrors) {
|
|
109
|
+
diagnostics.push({ type: 'error', message: err, path: yamlPath });
|
|
110
|
+
}
|
|
111
|
+
if (nameErrors.length > 0 || descErrors.length > 0) {
|
|
112
|
+
return { component: null, diagnostics };
|
|
113
|
+
}
|
|
114
|
+
// Validate kind-specific spec
|
|
115
|
+
if (!definition.spec) {
|
|
116
|
+
diagnostics.push({ type: 'error', message: 'missing spec', path: yamlPath });
|
|
117
|
+
return { component: null, diagnostics };
|
|
118
|
+
}
|
|
119
|
+
const entryFileDiagnostic = validateEntryFile(definition.kind, definition.spec, dir, yamlPath);
|
|
120
|
+
if (entryFileDiagnostic) {
|
|
121
|
+
diagnostics.push(entryFileDiagnostic);
|
|
122
|
+
return { component: null, diagnostics };
|
|
123
|
+
}
|
|
124
|
+
const id = computeComponentId(definition.metadata.name, definition.metadata.namespace);
|
|
125
|
+
const component = {
|
|
126
|
+
id,
|
|
127
|
+
kind: definition.kind,
|
|
128
|
+
metadata: definition.metadata,
|
|
129
|
+
spec: definition.spec,
|
|
130
|
+
requires: definition.requires,
|
|
131
|
+
compatibility: definition.compatibility,
|
|
132
|
+
routing: definition.routing,
|
|
133
|
+
dirPath: dir,
|
|
134
|
+
filePath: yamlPath,
|
|
135
|
+
source,
|
|
136
|
+
format: 'component-yaml',
|
|
137
|
+
enabled: true,
|
|
138
|
+
};
|
|
139
|
+
return { component, diagnostics };
|
|
140
|
+
}
|
|
141
|
+
function loadFromLegacySkill(filePath, dir, source) {
|
|
142
|
+
const diagnostics = [];
|
|
143
|
+
let raw;
|
|
144
|
+
try {
|
|
145
|
+
raw = readFileSync(filePath, 'utf-8');
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
const msg = error instanceof Error ? error.message : 'failed to read SKILL.md';
|
|
149
|
+
diagnostics.push({ type: 'warning', message: msg, path: filePath });
|
|
150
|
+
return { component: null, diagnostics };
|
|
151
|
+
}
|
|
152
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
153
|
+
const parentDirName = basename(dir);
|
|
154
|
+
const name = frontmatter.name || parentDirName;
|
|
155
|
+
// Validate
|
|
156
|
+
const nameErrors = validateComponentName(name);
|
|
157
|
+
for (const err of nameErrors) {
|
|
158
|
+
diagnostics.push({ type: 'warning', message: err, path: filePath });
|
|
159
|
+
}
|
|
160
|
+
const descErrors = validateComponentDescription(frontmatter.description);
|
|
161
|
+
for (const err of descErrors) {
|
|
162
|
+
diagnostics.push({ type: 'warning', message: err, path: filePath });
|
|
163
|
+
}
|
|
164
|
+
if (!frontmatter.description || frontmatter.description.trim() === '') {
|
|
165
|
+
return { component: null, diagnostics };
|
|
166
|
+
}
|
|
167
|
+
const spec = {
|
|
168
|
+
prompt: 'SKILL.md',
|
|
169
|
+
disableModelInvocation: frontmatter['disable-model-invocation'] === true,
|
|
170
|
+
};
|
|
171
|
+
const id = computeComponentId(name);
|
|
172
|
+
const component = {
|
|
173
|
+
id,
|
|
174
|
+
kind: 'skill',
|
|
175
|
+
metadata: {
|
|
176
|
+
name,
|
|
177
|
+
description: frontmatter.description,
|
|
178
|
+
},
|
|
179
|
+
spec,
|
|
180
|
+
dirPath: dir,
|
|
181
|
+
filePath,
|
|
182
|
+
source,
|
|
183
|
+
format: 'skill-md',
|
|
184
|
+
enabled: true,
|
|
185
|
+
};
|
|
186
|
+
return { component, diagnostics };
|
|
187
|
+
}
|
|
188
|
+
function loadFromLegacyAgent(filePath, source) {
|
|
189
|
+
const diagnostics = [];
|
|
190
|
+
let raw;
|
|
191
|
+
try {
|
|
192
|
+
raw = readFileSync(filePath, 'utf-8');
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
const msg = error instanceof Error ? error.message : 'failed to read agent file';
|
|
196
|
+
diagnostics.push({ type: 'warning', message: msg, path: filePath });
|
|
197
|
+
return { component: null, diagnostics };
|
|
198
|
+
}
|
|
199
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
200
|
+
if (!frontmatter.name || !frontmatter.description) {
|
|
201
|
+
diagnostics.push({
|
|
202
|
+
type: 'warning',
|
|
203
|
+
message: 'agent file missing name or description in frontmatter',
|
|
204
|
+
path: filePath,
|
|
205
|
+
});
|
|
206
|
+
return { component: null, diagnostics };
|
|
207
|
+
}
|
|
208
|
+
// Parse tools from comma-separated string
|
|
209
|
+
const tools = frontmatter.tools
|
|
210
|
+
? {
|
|
211
|
+
allow: frontmatter.tools
|
|
212
|
+
.split(',')
|
|
213
|
+
.map((t) => t.trim())
|
|
214
|
+
.filter(Boolean),
|
|
215
|
+
}
|
|
216
|
+
: undefined;
|
|
217
|
+
const spec = {
|
|
218
|
+
systemPrompt: basename(filePath),
|
|
219
|
+
model: frontmatter.model,
|
|
220
|
+
tools,
|
|
221
|
+
};
|
|
222
|
+
const id = computeComponentId(frontmatter.name);
|
|
223
|
+
const dir = dirname(filePath);
|
|
224
|
+
const component = {
|
|
225
|
+
id,
|
|
226
|
+
kind: 'agent',
|
|
227
|
+
metadata: {
|
|
228
|
+
name: frontmatter.name,
|
|
229
|
+
description: frontmatter.description,
|
|
230
|
+
},
|
|
231
|
+
spec,
|
|
232
|
+
dirPath: dir,
|
|
233
|
+
filePath,
|
|
234
|
+
source,
|
|
235
|
+
format: 'agent-md',
|
|
236
|
+
enabled: true,
|
|
237
|
+
};
|
|
238
|
+
return { component, diagnostics };
|
|
239
|
+
}
|
|
240
|
+
// ============================================================================
|
|
241
|
+
// Directory Scanning
|
|
242
|
+
// ============================================================================
|
|
243
|
+
/**
|
|
244
|
+
* Scan a directory for components (skills format).
|
|
245
|
+
* Handles both new and legacy directory layouts.
|
|
246
|
+
*
|
|
247
|
+
* Expected layouts:
|
|
248
|
+
* - dir/{component-name}/component.yaml (new format)
|
|
249
|
+
* - dir/{component-name}/SKILL.md (legacy skill)
|
|
250
|
+
* - dir/{name}.md (legacy root-level skill)
|
|
251
|
+
*/
|
|
252
|
+
export function scanComponentDir(dir, source, kind) {
|
|
253
|
+
const components = [];
|
|
254
|
+
const diagnostics = [];
|
|
255
|
+
if (!existsSync(dir)) {
|
|
256
|
+
return { components, diagnostics };
|
|
257
|
+
}
|
|
258
|
+
let entries;
|
|
259
|
+
try {
|
|
260
|
+
entries = readdirSync(dir, { withFileTypes: true, encoding: 'utf-8' });
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return { components, diagnostics };
|
|
264
|
+
}
|
|
265
|
+
for (const entry of entries) {
|
|
266
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const fullPath = join(dir, entry.name);
|
|
270
|
+
let isDir = entry.isDirectory();
|
|
271
|
+
let isFile = entry.isFile();
|
|
272
|
+
if (entry.isSymbolicLink()) {
|
|
273
|
+
try {
|
|
274
|
+
const stats = statSync(fullPath);
|
|
275
|
+
isDir = stats.isDirectory();
|
|
276
|
+
isFile = stats.isFile();
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (isDir) {
|
|
283
|
+
const result = loadComponentFromDir(fullPath, source);
|
|
284
|
+
if (result.component) {
|
|
285
|
+
if (!kind || result.component.kind === kind) {
|
|
286
|
+
components.push(result.component);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
diagnostics.push(...result.diagnostics);
|
|
290
|
+
}
|
|
291
|
+
else if (isFile && entry.name.endsWith('.md')) {
|
|
292
|
+
// Root-level .md files — could be legacy skills or agents
|
|
293
|
+
// Peek at frontmatter to determine type
|
|
294
|
+
const result = loadFromFile(fullPath, source);
|
|
295
|
+
if (result.component) {
|
|
296
|
+
if (!kind || result.component.kind === kind) {
|
|
297
|
+
components.push(result.component);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
diagnostics.push(...result.diagnostics);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return { components, diagnostics };
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Scan a directory specifically for agent .md files (legacy agent format).
|
|
307
|
+
*/
|
|
308
|
+
export function scanAgentDir(dir, source) {
|
|
309
|
+
const components = [];
|
|
310
|
+
const diagnostics = [];
|
|
311
|
+
if (!existsSync(dir)) {
|
|
312
|
+
return { components, diagnostics };
|
|
313
|
+
}
|
|
314
|
+
let entries;
|
|
315
|
+
try {
|
|
316
|
+
entries = readdirSync(dir, { withFileTypes: true, encoding: 'utf-8' });
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return { components, diagnostics };
|
|
320
|
+
}
|
|
321
|
+
for (const entry of entries) {
|
|
322
|
+
const fullPath = join(dir, entry.name);
|
|
323
|
+
let isDir = entry.isDirectory();
|
|
324
|
+
let isFile = entry.isFile();
|
|
325
|
+
if (entry.isSymbolicLink()) {
|
|
326
|
+
try {
|
|
327
|
+
const stats = statSync(fullPath);
|
|
328
|
+
isDir = stats.isDirectory();
|
|
329
|
+
isFile = stats.isFile();
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (isDir) {
|
|
336
|
+
const result = loadComponentFromDir(fullPath, source);
|
|
337
|
+
if (result.component?.kind === 'agent') {
|
|
338
|
+
components.push(result.component);
|
|
339
|
+
}
|
|
340
|
+
diagnostics.push(...result.diagnostics);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (!entry.name.endsWith('.md'))
|
|
344
|
+
continue;
|
|
345
|
+
if (!isFile)
|
|
346
|
+
continue;
|
|
347
|
+
// Check if there's a component.yaml in a same-named directory
|
|
348
|
+
const nameWithoutExt = entry.name.replace(/\.md$/, '');
|
|
349
|
+
const componentDir = join(dir, nameWithoutExt);
|
|
350
|
+
if (existsSync(join(componentDir, 'component.yaml'))) {
|
|
351
|
+
// New format takes precedence and is loaded by the directory branch.
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const result = loadComponentFromAgentFile(fullPath, source);
|
|
355
|
+
if (result.component) {
|
|
356
|
+
components.push(result.component);
|
|
357
|
+
}
|
|
358
|
+
diagnostics.push(...result.diagnostics);
|
|
359
|
+
}
|
|
360
|
+
return { components, diagnostics };
|
|
361
|
+
}
|
|
362
|
+
// ============================================================================
|
|
363
|
+
// Helpers
|
|
364
|
+
// ============================================================================
|
|
365
|
+
/**
|
|
366
|
+
* Load a single file, detecting whether it's a skill or agent by frontmatter.
|
|
367
|
+
*/
|
|
368
|
+
function loadFromFile(filePath, source) {
|
|
369
|
+
const diagnostics = [];
|
|
370
|
+
let raw;
|
|
371
|
+
try {
|
|
372
|
+
raw = readFileSync(filePath, 'utf-8');
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
const msg = error instanceof Error ? error.message : 'failed to read file';
|
|
376
|
+
diagnostics.push({ type: 'warning', message: msg, path: filePath });
|
|
377
|
+
return { component: null, diagnostics };
|
|
378
|
+
}
|
|
379
|
+
const { frontmatter } = parseFrontmatter(raw);
|
|
380
|
+
// If it has 'tools' field, treat as agent
|
|
381
|
+
if (frontmatter.tools !== undefined) {
|
|
382
|
+
return loadFromLegacyAgent(filePath, source);
|
|
383
|
+
}
|
|
384
|
+
// Otherwise treat as a legacy skill (root-level .md)
|
|
385
|
+
const dir = dirname(filePath);
|
|
386
|
+
const name = frontmatter.name || basename(filePath, '.md');
|
|
387
|
+
const description = frontmatter.description;
|
|
388
|
+
if (!description || description.trim() === '') {
|
|
389
|
+
return { component: null, diagnostics };
|
|
390
|
+
}
|
|
391
|
+
const spec = {
|
|
392
|
+
prompt: basename(filePath),
|
|
393
|
+
disableModelInvocation: frontmatter['disable-model-invocation'] === true,
|
|
394
|
+
};
|
|
395
|
+
const id = computeComponentId(name);
|
|
396
|
+
const component = {
|
|
397
|
+
id,
|
|
398
|
+
kind: 'skill',
|
|
399
|
+
metadata: { name, description },
|
|
400
|
+
spec,
|
|
401
|
+
dirPath: dir,
|
|
402
|
+
filePath,
|
|
403
|
+
source,
|
|
404
|
+
format: 'skill-md',
|
|
405
|
+
enabled: true,
|
|
406
|
+
};
|
|
407
|
+
return { component, diagnostics };
|
|
408
|
+
}
|
|
409
|
+
function validateEntryFile(kind, spec, dir, yamlPath) {
|
|
410
|
+
const relativePath = kind === 'skill'
|
|
411
|
+
? spec.prompt
|
|
412
|
+
: spec.systemPrompt;
|
|
413
|
+
const field = kind === 'skill' ? 'spec.prompt' : 'spec.systemPrompt';
|
|
414
|
+
if (!relativePath || typeof relativePath !== 'string') {
|
|
415
|
+
return {
|
|
416
|
+
type: 'error',
|
|
417
|
+
message: `missing ${field}`,
|
|
418
|
+
path: yamlPath,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
const entryPath = join(dir, relativePath);
|
|
422
|
+
if (!existsSync(entryPath)) {
|
|
423
|
+
return {
|
|
424
|
+
type: 'error',
|
|
425
|
+
message: `missing referenced file for ${field}: ${relativePath}`,
|
|
426
|
+
path: entryPath,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
if (!statSync(entryPath).isFile()) {
|
|
431
|
+
return {
|
|
432
|
+
type: 'error',
|
|
433
|
+
message: `referenced ${field} is not a file: ${relativePath}`,
|
|
434
|
+
path: entryPath,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
const msg = error instanceof Error ? error.message : 'failed to inspect referenced file';
|
|
440
|
+
return {
|
|
441
|
+
type: 'error',
|
|
442
|
+
message: `${msg}: ${relativePath}`,
|
|
443
|
+
path: entryPath,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
}
|