gsd-pi 2.77.0-dev.1d17f366c → 2.77.0-dev.2daa994b6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/headless.js +25 -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/gsd/auto/phases.js +5 -18
- package/dist/resources/extensions/gsd/auto/session.js +6 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +37 -8
- package/dist/resources/extensions/gsd/auto-post-unit.js +79 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +372 -104
- package/dist/resources/extensions/gsd/auto-start.js +75 -24
- package/dist/resources/extensions/gsd/auto.js +34 -0
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +7 -1
- 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/context-store.js +23 -7
- 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/forensics.js +106 -0
- package/dist/resources/extensions/gsd/gsd-db.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +2 -4
- 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/preferences-validation.js +23 -0
- package/dist/resources/extensions/gsd/prompt-cache-optimizer.js +4 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +5 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
- package/dist/resources/extensions/gsd/service-tier.js +5 -2
- package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
- package/dist/resources/extensions/gsd/slice-cadence.js +238 -0
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -2
- package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +334 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +51 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +86 -7
- package/dist/resources/extensions/gsd/worktree-telemetry.js +198 -0
- 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 +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/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/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 +1 -3
- 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/workflow-tools.test.ts +80 -39
- 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/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 +29 -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/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 +39 -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/__tests__/tool-execution.test.js +36 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.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/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +30 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.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/__tests__/tool-execution.test.ts +49 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +48 -9
- 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/image.test.js +6 -5
- package/packages/pi-tui/dist/components/image.test.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/image.test.ts +10 -5
- 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/tests/stream-adapter.test.ts +80 -72
- 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/phases.ts +6 -17
- package/src/resources/extensions/gsd/auto/session.ts +7 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +40 -8
- package/src/resources/extensions/gsd/auto-post-unit.ts +81 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +385 -93
- package/src/resources/extensions/gsd/auto-start.ts +97 -4
- package/src/resources/extensions/gsd/auto.ts +37 -0
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +7 -1
- 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/context-store.ts +25 -8
- 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/forensics.ts +118 -1
- package/src/resources/extensions/gsd/git-service.ts +16 -0
- package/src/resources/extensions/gsd/gsd-db.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +2 -4
- package/src/resources/extensions/gsd/journal.ts +11 -1
- 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/preferences-validation.ts +21 -0
- package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +4 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +5 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
- package/src/resources/extensions/gsd/service-tier.ts +5 -2
- package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
- package/src/resources/extensions/gsd/slice-cadence.ts +299 -0
- package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +25 -292
- package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -194
- package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
- package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
- package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
- package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +8 -4
- 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/context-store.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +8 -104
- 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 -56
- 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 +1 -1
- package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +9 -3
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +93 -1
- 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 -55
- 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 -48
- package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +59 -2
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -130
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +15 -4
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +22 -16
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +4 -5
- 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/restore-tools-after-discuss.test.ts +6 -3
- 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/sidecar-queue.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
- 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-cadence.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
- package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +7 -6
- package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/test-helpers.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/test-helpers.ts +153 -0
- package/src/resources/extensions/gsd/tests/token-profile.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 +203 -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 -80
- 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/worktree-telemetry.test.ts +210 -0
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +8 -2
- package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +492 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +53 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +96 -9
- package/src/resources/extensions/gsd/worktree-telemetry.ts +322 -0
- 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 -143
- 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 -74
- package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
- 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 -125
- package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -42
- /package/dist/web/standalone/.next/static/{vidAVJkURvTJ0_V2-64ro → gYYky7yfxW8txb9vU2TrJ}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vidAVJkURvTJ0_V2-64ro → gYYky7yfxW8txb9vU2TrJ}/_ssgManifest.js +0 -0
|
@@ -19,12 +19,13 @@ import { invalidateAllCaches } from "./cache.js";
|
|
|
19
19
|
import { writeLock, clearLock } from "./crash-recovery.js";
|
|
20
20
|
import { acquireSessionLock, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
|
|
21
21
|
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
22
|
-
import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit, nativeGetCurrentBranch, nativeDetectMainBranch, nativeCheckoutBranch, nativeBranchList, nativeBranchListMerged, nativeBranchDelete, nativeWorktreeRemove, } from "./native-git-bridge.js";
|
|
22
|
+
import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit, nativeGetCurrentBranch, nativeDetectMainBranch, nativeCheckoutBranch, nativeBranchList, nativeBranchListMerged, nativeBranchDelete, nativeWorktreeRemove, nativeCommitCountBetween, } from "./native-git-bridge.js";
|
|
23
23
|
import { GitServiceImpl } from "./git-service.js";
|
|
24
24
|
import { captureIntegrationBranch, detectWorktreeName, setActiveMilestoneId, } from "./worktree.js";
|
|
25
25
|
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
26
26
|
import { readResourceVersion, cleanStaleRuntimeUnits } from "./auto-worktree.js";
|
|
27
27
|
import { worktreePath as getWorktreeDir, isInsideWorktreesDir } from "./worktree-manager.js";
|
|
28
|
+
import { emitWorktreeOrphaned } from "./worktree-telemetry.js";
|
|
28
29
|
import { initMetrics } from "./metrics.js";
|
|
29
30
|
import { initRoutingHistory } from "./routing-history.js";
|
|
30
31
|
import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
|
|
@@ -63,25 +64,15 @@ export async function openProjectDbIfPresent(basePath) {
|
|
|
63
64
|
logWarning("engine", `gsd-db: failed to open existing database: ${err instanceof Error ? err.message : String(err)}`);
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
* This audit runs on every fresh bootstrap to catch that gap:
|
|
76
|
-
* 1. Lists all local `milestone/*` branches.
|
|
77
|
-
* 2. For each, checks if the milestone's DB status is "complete".
|
|
78
|
-
* 3. If the branch is already merged into main → deletes the branch
|
|
79
|
-
* and cleans up any orphaned worktree directory (safe, no data loss).
|
|
80
|
-
* 4. If the branch is NOT merged → preserves it and warns the user
|
|
81
|
-
* so they can merge manually (data safety first).
|
|
82
|
-
*
|
|
83
|
-
* Returns a summary of actions taken for the caller to surface via notify.
|
|
84
|
-
*/
|
|
67
|
+
export function decideSurvivorAction(hasSurvivorBranch, phase) {
|
|
68
|
+
if (!hasSurvivorBranch)
|
|
69
|
+
return "none";
|
|
70
|
+
if (phase === "needs-discussion")
|
|
71
|
+
return "discuss";
|
|
72
|
+
if (phase === "complete")
|
|
73
|
+
return "finalize";
|
|
74
|
+
return "none";
|
|
75
|
+
}
|
|
85
76
|
export function auditOrphanedMilestoneBranches(basePath, isolationMode) {
|
|
86
77
|
const recovered = [];
|
|
87
78
|
const warnings = [];
|
|
@@ -120,10 +111,58 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) {
|
|
|
120
111
|
for (const branch of milestoneBranches) {
|
|
121
112
|
const milestoneId = branch.replace(/^milestone\//, "");
|
|
122
113
|
const milestone = getMilestone(milestoneId);
|
|
123
|
-
|
|
124
|
-
if (!milestone || milestone.status !== "complete")
|
|
114
|
+
if (!milestone)
|
|
125
115
|
continue;
|
|
126
116
|
const isMerged = mergedBranches.has(branch);
|
|
117
|
+
// #4762 — in-progress milestone branch with unmerged commits ahead of
|
|
118
|
+
// main. This is the pre-completion orphan case: auto-mode exited without
|
|
119
|
+
// completing the milestone (pause, stop, crash, merge error, blocker) and
|
|
120
|
+
// work is stranded on the branch or in the worktree. Data safety first:
|
|
121
|
+
// we never delete or touch; we just surface a warning so the user knows
|
|
122
|
+
// where to look.
|
|
123
|
+
//
|
|
124
|
+
// Gate on isClosedStatus so we only warn about genuinely open milestones.
|
|
125
|
+
// Parked/other closed statuses go through the legacy complete/unmerged
|
|
126
|
+
// path below where appropriate.
|
|
127
|
+
if (!isClosedStatus(milestone.status)) {
|
|
128
|
+
if (isMerged)
|
|
129
|
+
continue; // nothing to recover
|
|
130
|
+
let commitsAhead = 0;
|
|
131
|
+
try {
|
|
132
|
+
commitsAhead = nativeCommitCountBetween(basePath, mainBranch, branch);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Rev-walk failure — skip rather than noise
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (commitsAhead === 0)
|
|
139
|
+
continue;
|
|
140
|
+
const wtDir = getWorktreeDir(basePath, milestoneId);
|
|
141
|
+
const wtDirExists = existsSync(wtDir);
|
|
142
|
+
const wtSuffix = wtDirExists
|
|
143
|
+
? ` Worktree directory at .gsd/worktrees/${milestoneId}/ holds the live work.`
|
|
144
|
+
: "";
|
|
145
|
+
warnings.push(`Branch ${branch} has ${commitsAhead} commit(s) ahead of ${mainBranch} for in-progress milestone ${milestoneId}.` +
|
|
146
|
+
wtSuffix +
|
|
147
|
+
` Run \`/gsd auto\` to resume, or merge manually if abandoning.`);
|
|
148
|
+
// #4764 telemetry
|
|
149
|
+
try {
|
|
150
|
+
emitWorktreeOrphaned(basePath, milestoneId, {
|
|
151
|
+
reason: "in-progress-unmerged",
|
|
152
|
+
commitsAhead,
|
|
153
|
+
worktreeDirExists: wtDirExists,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
logWarning("engine", `worktree-orphaned telemetry failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
158
|
+
}
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
// Only the "complete" status participates in the merged/unmerged cleanup
|
|
162
|
+
// paths below — other closed statuses (parked, etc.) are intentionally
|
|
163
|
+
// left alone.
|
|
164
|
+
if (milestone.status !== "complete")
|
|
165
|
+
continue;
|
|
127
166
|
if (isMerged) {
|
|
128
167
|
// Branch is merged — safe to delete branch and clean up worktree dir
|
|
129
168
|
try {
|
|
@@ -170,6 +209,16 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) {
|
|
|
170
209
|
// Branch is NOT merged — preserve for safety, warn the user
|
|
171
210
|
warnings.push(`Branch ${branch} exists for completed milestone ${milestoneId} but is NOT merged into ${mainBranch}. ` +
|
|
172
211
|
`This may contain unmerged work. Merge manually or run \`/gsd health --fix\` to resolve.`);
|
|
212
|
+
// #4764 telemetry
|
|
213
|
+
try {
|
|
214
|
+
emitWorktreeOrphaned(basePath, milestoneId, {
|
|
215
|
+
reason: "complete-unmerged",
|
|
216
|
+
worktreeDirExists: existsSync(getWorktreeDir(basePath, milestoneId)),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
logWarning("engine", `worktree-orphaned telemetry failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
221
|
+
}
|
|
173
222
|
}
|
|
174
223
|
}
|
|
175
224
|
return { recovered, warnings };
|
|
@@ -391,7 +440,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
391
440
|
// The worktree/branch was created but the milestone only has CONTEXT-DRAFT.md.
|
|
392
441
|
// Route to the interactive discussion handler instead of falling through to
|
|
393
442
|
// auto-mode, which would immediately stop with "needs discussion".
|
|
394
|
-
if (hasSurvivorBranch
|
|
443
|
+
if (decideSurvivorAction(hasSurvivorBranch, state.phase) === "discuss") {
|
|
395
444
|
const { showSmartEntry } = await import("./guided-flow.js");
|
|
396
445
|
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
397
446
|
invalidateAllCaches();
|
|
@@ -411,7 +460,9 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
411
460
|
// The milestone artifacts were written but finalization (merge, worktree
|
|
412
461
|
// cleanup) never ran. Run mergeAndExit to finalize, then re-derive state
|
|
413
462
|
// so the normal "all milestones complete" or "next milestone" path runs.
|
|
414
|
-
|
|
463
|
+
// Re-evaluate via the helper — the discuss branch above may have cleared
|
|
464
|
+
// hasSurvivorBranch after a successful promotion.
|
|
465
|
+
if (decideSurvivorAction(hasSurvivorBranch, state.phase) === "finalize") {
|
|
415
466
|
const mid = state.activeMilestone.id;
|
|
416
467
|
ctx.ui.notify(`Milestone ${mid} is complete but branch/worktree was not finalized. Running merge now.`, "info");
|
|
417
468
|
const resolver = buildResolver();
|
|
@@ -528,6 +528,40 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
528
528
|
return;
|
|
529
529
|
const loadedPreferences = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
|
|
530
530
|
const reasonSuffix = reason ? ` — ${reason}` : "";
|
|
531
|
+
// #4764 — telemetry: record the exit reason and whether the current milestone
|
|
532
|
+
// was merged before we entered stopAuto. This is the producer-side signal for
|
|
533
|
+
// the #4761 orphan class: milestoneMerged=false + currentMilestoneId present
|
|
534
|
+
// is exactly the pattern that strands work.
|
|
535
|
+
try {
|
|
536
|
+
const { emitAutoExit } = await import("./worktree-telemetry.js");
|
|
537
|
+
// Normalize the free-form reason to a closed set so the telemetry
|
|
538
|
+
// aggregator buckets stably. Raw detail is preserved in the phases.ts
|
|
539
|
+
// notification and the notify'd error string.
|
|
540
|
+
const rawReason = reason ?? "stop";
|
|
541
|
+
const normalizedReason = rawReason.startsWith("Blocked:")
|
|
542
|
+
? "blocked"
|
|
543
|
+
: rawReason.startsWith("Merge conflict")
|
|
544
|
+
? "merge-conflict"
|
|
545
|
+
: rawReason.startsWith("Merge error") || rawReason.startsWith("Merge failed")
|
|
546
|
+
? "merge-failed"
|
|
547
|
+
: rawReason.startsWith("slice-merge-conflict")
|
|
548
|
+
? "slice-merge-conflict"
|
|
549
|
+
: rawReason === "All milestones complete"
|
|
550
|
+
? "all-complete"
|
|
551
|
+
: rawReason === "No active milestone"
|
|
552
|
+
? "no-active-milestone"
|
|
553
|
+
: rawReason === "stop" || rawReason === "pause"
|
|
554
|
+
? rawReason
|
|
555
|
+
: "other";
|
|
556
|
+
emitAutoExit(s.originalBasePath || s.basePath, {
|
|
557
|
+
reason: normalizedReason,
|
|
558
|
+
milestoneId: s.currentMilestoneId ?? undefined,
|
|
559
|
+
milestoneMerged: s.milestoneMergedInPhases === true,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
catch (err) {
|
|
563
|
+
logWarning("engine", `auto-exit telemetry failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
564
|
+
}
|
|
531
565
|
try {
|
|
532
566
|
// ── Step 1: Timers and locks ──
|
|
533
567
|
try {
|
|
@@ -12,7 +12,15 @@ import { classifyError, createRetryState, resetRetryState, isTransient, } from "
|
|
|
12
12
|
import { blockModel, isModelBlocked } from "../blocked-models.js";
|
|
13
13
|
const retryState = createRetryState();
|
|
14
14
|
const MAX_NETWORK_RETRIES = 2;
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Cap on auto-resume attempts for sustained transient-provider errors.
|
|
17
|
+
*
|
|
18
|
+
* Exported so tests assert against the shared constant instead of
|
|
19
|
+
* regex-scraping the source literal (see #4837). Raising this value to
|
|
20
|
+
* handle longer provider overloads should update the single constant; the
|
|
21
|
+
* test in provider-errors.test.ts consumes it directly.
|
|
22
|
+
*/
|
|
23
|
+
export const MAX_TRANSIENT_AUTO_RESUMES = 8;
|
|
16
24
|
/**
|
|
17
25
|
* Reset the module-level retry state so a resumed auto-session starts fresh.
|
|
18
26
|
* Called by provider-error-resume.ts before startAuto() — without this, the
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Regex matching milestone CONTEXT.md file names in both legacy M001
|
|
5
|
+
* and unique M001-abc123 formats. Exported so regex-hardening tests
|
|
6
|
+
* can exercise the real pattern rather than a drift-prone inline
|
|
7
|
+
* re-implementation (see #4835).
|
|
8
|
+
*/
|
|
9
|
+
export const MILESTONE_CONTEXT_RE = /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/;
|
|
4
10
|
const CONTEXT_MILESTONE_RE = /(?:^|[/\\])(M\d+(?:-[a-z0-9]{6})?)-CONTEXT\.md$/i;
|
|
5
11
|
const DEPTH_VERIFICATION_MILESTONE_RE = /depth_verification[_-](M\d+(?:-[a-z0-9]{6})?)/i;
|
|
6
12
|
/**
|
|
@@ -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
|
+
}
|