@qodo/sdk 0.13.3 → 2.0.0-next.0
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/LICENSE +31 -118
- package/README.md +133 -121
- package/bin/qodo-skills.mjs +13 -0
- package/bundled-skills/code-review/SKILL.md +41 -0
- package/bundled-skills/pr-summary/SKILL.md +59 -0
- package/bundled-skills/test-gen/SKILL.md +47 -0
- package/dist/auth/index.browser.d.ts +38 -0
- package/dist/auth/index.browser.d.ts.map +1 -0
- package/dist/auth/index.browser.js +62 -0
- package/dist/auth/index.browser.js.map +1 -0
- package/dist/auth/index.d.ts +44 -30
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +57 -110
- package/dist/auth/index.js.map +1 -1
- package/dist/client/AgentsClient.d.ts +33 -0
- package/dist/client/AgentsClient.d.ts.map +1 -0
- package/dist/client/AgentsClient.js +40 -0
- package/dist/client/AgentsClient.js.map +1 -0
- package/dist/client/ArtifactsClient.d.ts +43 -0
- package/dist/client/ArtifactsClient.d.ts.map +1 -0
- package/dist/client/ArtifactsClient.js +54 -0
- package/dist/client/ArtifactsClient.js.map +1 -0
- package/dist/client/BulletinClient.d.ts +45 -0
- package/dist/client/BulletinClient.d.ts.map +1 -0
- package/dist/client/BulletinClient.js +51 -0
- package/dist/client/BulletinClient.js.map +1 -0
- package/dist/client/InfoClient.d.ts +58 -0
- package/dist/client/InfoClient.d.ts.map +1 -0
- package/dist/client/InfoClient.js +135 -0
- package/dist/client/InfoClient.js.map +1 -0
- package/dist/client/PipelineClient.d.ts +162 -0
- package/dist/client/PipelineClient.d.ts.map +1 -0
- package/dist/client/PipelineClient.js +340 -0
- package/dist/client/PipelineClient.js.map +1 -0
- package/dist/client/QarRegistryClient.d.ts +396 -0
- package/dist/client/QarRegistryClient.d.ts.map +1 -0
- package/dist/client/QarRegistryClient.js +536 -0
- package/dist/client/QarRegistryClient.js.map +1 -0
- package/dist/client/QodoClient.d.ts +296 -0
- package/dist/client/QodoClient.d.ts.map +1 -0
- package/dist/client/QodoClient.js +803 -0
- package/dist/client/QodoClient.js.map +1 -0
- package/dist/client/SpecsClient.d.ts +121 -0
- package/dist/client/SpecsClient.d.ts.map +1 -0
- package/dist/client/SpecsClient.js +252 -0
- package/dist/client/SpecsClient.js.map +1 -0
- package/dist/client/StateClient.d.ts +35 -0
- package/dist/client/StateClient.d.ts.map +1 -0
- package/dist/client/StateClient.js +36 -0
- package/dist/client/StateClient.js.map +1 -0
- package/dist/client/TaskClient.d.ts +706 -0
- package/dist/client/TaskClient.d.ts.map +1 -0
- package/dist/client/TaskClient.js +2522 -0
- package/dist/client/TaskClient.js.map +1 -0
- package/dist/client/ToolClient.d.ts +278 -0
- package/dist/client/ToolClient.d.ts.map +1 -0
- package/dist/client/ToolClient.js +1115 -0
- package/dist/client/ToolClient.js.map +1 -0
- package/dist/client/a2a/index.d.ts +10 -0
- package/dist/client/a2a/index.d.ts.map +1 -0
- package/dist/client/a2a/index.js +9 -0
- package/dist/client/a2a/index.js.map +1 -0
- package/dist/client/a2a/registerA2A.d.ts +170 -0
- package/dist/client/a2a/registerA2A.d.ts.map +1 -0
- package/dist/client/a2a/registerA2A.js +85 -0
- package/dist/client/a2a/registerA2A.js.map +1 -0
- package/dist/client/connection.d.ts +800 -0
- package/dist/client/connection.d.ts.map +1 -0
- package/dist/client/connection.js +2020 -0
- package/dist/client/connection.js.map +1 -0
- package/dist/client/errors.d.ts +735 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +921 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/index.d.ts +26 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +20 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/inlineGraph.d.ts +66 -0
- package/dist/client/inlineGraph.d.ts.map +1 -0
- package/dist/client/inlineGraph.js +500 -0
- package/dist/client/inlineGraph.js.map +1 -0
- package/dist/client/internal/thenable.d.ts +27 -0
- package/dist/client/internal/thenable.d.ts.map +1 -0
- package/dist/client/internal/thenable.js +31 -0
- package/dist/client/internal/thenable.js.map +1 -0
- package/dist/client/iterator.d.ts +32 -0
- package/dist/client/iterator.d.ts.map +1 -0
- package/dist/client/iterator.js +73 -0
- package/dist/client/iterator.js.map +1 -0
- package/dist/client/mcp/McpClientPool.browser.d.ts +76 -0
- package/dist/client/mcp/McpClientPool.browser.d.ts.map +1 -0
- package/dist/client/mcp/McpClientPool.browser.js +78 -0
- package/dist/client/mcp/McpClientPool.browser.js.map +1 -0
- package/dist/client/mcp/McpClientPool.d.ts +236 -0
- package/dist/client/mcp/McpClientPool.d.ts.map +1 -0
- package/dist/client/mcp/McpClientPool.js +585 -0
- package/dist/client/mcp/McpClientPool.js.map +1 -0
- package/dist/client/mcp/projection.d.ts +109 -0
- package/dist/client/mcp/projection.d.ts.map +1 -0
- package/dist/client/mcp/projection.js +446 -0
- package/dist/client/mcp/projection.js.map +1 -0
- package/dist/client/mcp/substituteEnv.browser.d.ts +18 -0
- package/dist/client/mcp/substituteEnv.browser.d.ts.map +1 -0
- package/dist/client/mcp/substituteEnv.browser.js +20 -0
- package/dist/client/mcp/substituteEnv.browser.js.map +1 -0
- package/dist/client/mcp/substituteEnv.d.ts +45 -0
- package/dist/client/mcp/substituteEnv.d.ts.map +1 -0
- package/dist/client/mcp/substituteEnv.js +63 -0
- package/dist/client/mcp/substituteEnv.js.map +1 -0
- package/dist/client/observers.d.ts +57 -0
- package/dist/client/observers.d.ts.map +1 -0
- package/dist/client/observers.js +203 -0
- package/dist/client/observers.js.map +1 -0
- package/dist/client/options.d.ts +269 -0
- package/dist/client/options.d.ts.map +1 -0
- package/dist/client/options.js +9 -0
- package/dist/client/options.js.map +1 -0
- package/dist/client/tools/_readlineApprovalPrompt.browser.d.ts +17 -0
- package/dist/client/tools/_readlineApprovalPrompt.browser.d.ts.map +1 -0
- package/dist/client/tools/_readlineApprovalPrompt.browser.js +24 -0
- package/dist/client/tools/_readlineApprovalPrompt.browser.js.map +1 -0
- package/dist/client/tools/_readlineApprovalPrompt.d.ts +33 -0
- package/dist/client/tools/_readlineApprovalPrompt.d.ts.map +1 -0
- package/dist/client/tools/_readlineApprovalPrompt.js +90 -0
- package/dist/client/tools/_readlineApprovalPrompt.js.map +1 -0
- package/dist/client/tools/approval.d.ts +280 -0
- package/dist/client/tools/approval.d.ts.map +1 -0
- package/dist/client/tools/approval.js +229 -0
- package/dist/client/tools/approval.js.map +1 -0
- package/dist/client/tools/bindFunctionToolDefs.d.ts +156 -0
- package/dist/client/tools/bindFunctionToolDefs.d.ts.map +1 -0
- package/dist/client/tools/bindFunctionToolDefs.js +360 -0
- package/dist/client/tools/bindFunctionToolDefs.js.map +1 -0
- package/dist/client/tools/defineFunctionTool.d.ts +277 -0
- package/dist/client/tools/defineFunctionTool.d.ts.map +1 -0
- package/dist/client/tools/defineFunctionTool.js +190 -0
- package/dist/client/tools/defineFunctionTool.js.map +1 -0
- package/dist/client/transport.browser.d.ts +20 -0
- package/dist/client/transport.browser.d.ts.map +1 -0
- package/dist/client/transport.browser.js +29 -0
- package/dist/client/transport.browser.js.map +1 -0
- package/dist/client/transport.d.ts +47 -0
- package/dist/client/transport.d.ts.map +1 -0
- package/dist/client/transport.js +102 -0
- package/dist/client/transport.js.map +1 -0
- package/dist/client/transport.shared.d.ts +30 -0
- package/dist/client/transport.shared.d.ts.map +1 -0
- package/dist/client/transport.shared.js +40 -0
- package/dist/client/transport.shared.js.map +1 -0
- package/dist/client/uuid.d.ts +32 -0
- package/dist/client/uuid.d.ts.map +1 -0
- package/dist/client/uuid.js +65 -0
- package/dist/client/uuid.js.map +1 -0
- package/dist/index.d.ts +88 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +166 -43
- package/dist/index.js.map +1 -1
- package/dist/observability/attributes.d.ts +136 -0
- package/dist/observability/attributes.d.ts.map +1 -0
- package/dist/observability/attributes.js +184 -0
- package/dist/observability/attributes.js.map +1 -0
- package/dist/observability/index.d.ts +14 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +11 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/resolveOTel.browser.d.ts +13 -0
- package/dist/observability/resolveOTel.browser.d.ts.map +1 -0
- package/dist/observability/resolveOTel.browser.js +14 -0
- package/dist/observability/resolveOTel.browser.js.map +1 -0
- package/dist/observability/resolveOTel.d.ts +28 -0
- package/dist/observability/resolveOTel.d.ts.map +1 -0
- package/dist/observability/resolveOTel.js +74 -0
- package/dist/observability/resolveOTel.js.map +1 -0
- package/dist/observability/spans.d.ts +198 -0
- package/dist/observability/spans.d.ts.map +1 -0
- package/dist/observability/spans.js +300 -0
- package/dist/observability/spans.js.map +1 -0
- package/dist/observability/traceContext.d.ts +51 -0
- package/dist/observability/traceContext.d.ts.map +1 -0
- package/dist/observability/traceContext.js +151 -0
- package/dist/observability/traceContext.js.map +1 -0
- package/dist/observability/transportMetrics.d.ts +58 -0
- package/dist/observability/transportMetrics.d.ts.map +1 -0
- package/dist/observability/transportMetrics.js +55 -0
- package/dist/observability/transportMetrics.js.map +1 -0
- package/dist/qar/agentSpec.d.ts +93 -0
- package/dist/qar/agentSpec.d.ts.map +1 -0
- package/dist/qar/agentSpec.js +184 -0
- package/dist/qar/agentSpec.js.map +1 -0
- package/dist/qar/clientEvents.d.ts +86 -0
- package/dist/qar/clientEvents.d.ts.map +1 -0
- package/dist/qar/clientEvents.js +36 -0
- package/dist/qar/clientEvents.js.map +1 -0
- package/dist/qar/envelopes.d.ts +227 -0
- package/dist/qar/envelopes.d.ts.map +1 -0
- package/dist/qar/envelopes.js +67 -0
- package/dist/qar/envelopes.js.map +1 -0
- package/dist/qar/generated/envelope.d.ts +332 -0
- package/dist/qar/generated/envelope.d.ts.map +1 -0
- package/dist/qar/generated/envelope.js +15 -0
- package/dist/qar/generated/envelope.js.map +1 -0
- package/dist/qar/generated/qar-info.d.ts +76 -0
- package/dist/qar/generated/qar-info.d.ts.map +1 -0
- package/dist/qar/generated/qar-info.js +15 -0
- package/dist/qar/generated/qar-info.js.map +1 -0
- package/dist/qar/generated/qodo-task-start-payload.d.ts +54 -0
- package/dist/qar/generated/qodo-task-start-payload.d.ts.map +1 -0
- package/dist/qar/generated/qodo-task-start-payload.js +15 -0
- package/dist/qar/generated/qodo-task-start-payload.js.map +1 -0
- package/dist/qar/ids.d.ts +19 -0
- package/dist/qar/ids.d.ts.map +1 -0
- package/dist/qar/ids.js +11 -0
- package/dist/qar/ids.js.map +1 -0
- package/dist/qar/index.d.ts +24 -0
- package/dist/qar/index.d.ts.map +1 -0
- package/dist/qar/index.js +16 -0
- package/dist/qar/index.js.map +1 -0
- package/dist/qar/info.d.ts +37 -0
- package/dist/qar/info.d.ts.map +1 -0
- package/dist/qar/info.js +17 -0
- package/dist/qar/info.js.map +1 -0
- package/dist/qar/json.d.ts +14 -0
- package/dist/qar/json.d.ts.map +1 -0
- package/dist/qar/json.js +9 -0
- package/dist/qar/json.js.map +1 -0
- package/dist/qar/payloads.d.ts +480 -0
- package/dist/qar/payloads.d.ts.map +1 -0
- package/dist/qar/payloads.js +37 -0
- package/dist/qar/payloads.js.map +1 -0
- package/dist/qar/specs.d.ts +604 -0
- package/dist/qar/specs.d.ts.map +1 -0
- package/dist/qar/specs.js +29 -0
- package/dist/qar/specs.js.map +1 -0
- package/dist/qar/taskEvents.d.ts +25 -0
- package/dist/qar/taskEvents.d.ts.map +1 -0
- package/dist/qar/taskEvents.js +22 -0
- package/dist/qar/taskEvents.js.map +1 -0
- package/dist/qar/trace.d.ts +12 -0
- package/dist/qar/trace.d.ts.map +1 -0
- package/dist/qar/trace.js +12 -0
- package/dist/qar/trace.js.map +1 -0
- package/dist/skills/activation.d.ts +177 -0
- package/dist/skills/activation.d.ts.map +1 -0
- package/dist/skills/activation.js +428 -0
- package/dist/skills/activation.js.map +1 -0
- package/dist/skills/cli/index.browser.d.ts +18 -0
- package/dist/skills/cli/index.browser.d.ts.map +1 -0
- package/dist/skills/cli/index.browser.js +27 -0
- package/dist/skills/cli/index.browser.js.map +1 -0
- package/dist/skills/cli/index.d.ts +37 -0
- package/dist/skills/cli/index.d.ts.map +1 -0
- package/dist/skills/cli/index.js +494 -0
- package/dist/skills/cli/index.js.map +1 -0
- package/dist/skills/events.d.ts +255 -0
- package/dist/skills/events.d.ts.map +1 -0
- package/dist/skills/events.js +224 -0
- package/dist/skills/events.js.map +1 -0
- package/dist/skills/index.d.ts +45 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +34 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/inject.d.ts +57 -0
- package/dist/skills/inject.d.ts.map +1 -0
- package/dist/skills/inject.js +162 -0
- package/dist/skills/inject.js.map +1 -0
- package/dist/skills/lockfile.browser.d.ts +56 -0
- package/dist/skills/lockfile.browser.d.ts.map +1 -0
- package/dist/skills/lockfile.browser.js +55 -0
- package/dist/skills/lockfile.browser.js.map +1 -0
- package/dist/skills/lockfile.d.ts +137 -0
- package/dist/skills/lockfile.d.ts.map +1 -0
- package/dist/skills/lockfile.js +423 -0
- package/dist/skills/lockfile.js.map +1 -0
- package/dist/skills/manager.browser.d.ts +94 -0
- package/dist/skills/manager.browser.d.ts.map +1 -0
- package/dist/skills/manager.browser.js +159 -0
- package/dist/skills/manager.browser.js.map +1 -0
- package/dist/skills/manager.d.ts +362 -0
- package/dist/skills/manager.d.ts.map +1 -0
- package/dist/skills/manager.js +1386 -0
- package/dist/skills/manager.js.map +1 -0
- package/dist/skills/mcp/index.d.ts +15 -0
- package/dist/skills/mcp/index.d.ts.map +1 -0
- package/dist/skills/mcp/index.js +12 -0
- package/dist/skills/mcp/index.js.map +1 -0
- package/dist/skills/mcp/path.browser.d.ts +27 -0
- package/dist/skills/mcp/path.browser.d.ts.map +1 -0
- package/dist/skills/mcp/path.browser.js +33 -0
- package/dist/skills/mcp/path.browser.js.map +1 -0
- package/dist/skills/mcp/path.d.ts +57 -0
- package/dist/skills/mcp/path.d.ts.map +1 -0
- package/dist/skills/mcp/path.js +150 -0
- package/dist/skills/mcp/path.js.map +1 -0
- package/dist/skills/mcp/server.browser.d.ts +32 -0
- package/dist/skills/mcp/server.browser.d.ts.map +1 -0
- package/dist/skills/mcp/server.browser.js +53 -0
- package/dist/skills/mcp/server.browser.js.map +1 -0
- package/dist/skills/mcp/server.d.ts +144 -0
- package/dist/skills/mcp/server.d.ts.map +1 -0
- package/dist/skills/mcp/server.js +841 -0
- package/dist/skills/mcp/server.js.map +1 -0
- package/dist/skills/mcp/types.d.ts +72 -0
- package/dist/skills/mcp/types.d.ts.map +1 -0
- package/dist/skills/mcp/types.js +20 -0
- package/dist/skills/mcp/types.js.map +1 -0
- package/dist/skills/mcp/wireDefs.d.ts +58 -0
- package/dist/skills/mcp/wireDefs.d.ts.map +1 -0
- package/dist/skills/mcp/wireDefs.js +141 -0
- package/dist/skills/mcp/wireDefs.js.map +1 -0
- package/dist/skills/parser.d.ts +63 -0
- package/dist/skills/parser.d.ts.map +1 -0
- package/dist/skills/parser.js +755 -0
- package/dist/skills/parser.js.map +1 -0
- package/dist/skills/prefilter.d.ts +104 -0
- package/dist/skills/prefilter.d.ts.map +1 -0
- package/dist/skills/prefilter.js +398 -0
- package/dist/skills/prefilter.js.map +1 -0
- package/dist/skills/preprocess.d.ts +169 -0
- package/dist/skills/preprocess.d.ts.map +1 -0
- package/dist/skills/preprocess.js +535 -0
- package/dist/skills/preprocess.js.map +1 -0
- package/dist/skills/render.d.ts +83 -0
- package/dist/skills/render.d.ts.map +1 -0
- package/dist/skills/render.js +397 -0
- package/dist/skills/render.js.map +1 -0
- package/dist/skills/sources/index.browser.d.ts +29 -0
- package/dist/skills/sources/index.browser.d.ts.map +1 -0
- package/dist/skills/sources/index.browser.js +16 -0
- package/dist/skills/sources/index.browser.js.map +1 -0
- package/dist/skills/sources/index.d.ts +59 -0
- package/dist/skills/sources/index.d.ts.map +1 -0
- package/dist/skills/sources/index.js +471 -0
- package/dist/skills/sources/index.js.map +1 -0
- package/dist/skills/sources/walk.browser.d.ts +17 -0
- package/dist/skills/sources/walk.browser.d.ts.map +1 -0
- package/dist/skills/sources/walk.browser.js +19 -0
- package/dist/skills/sources/walk.browser.js.map +1 -0
- package/dist/skills/sources/walk.d.ts +68 -0
- package/dist/skills/sources/walk.d.ts.map +1 -0
- package/dist/skills/sources/walk.js +264 -0
- package/dist/skills/sources/walk.js.map +1 -0
- package/dist/skills/substitute.d.ts +87 -0
- package/dist/skills/substitute.d.ts.map +1 -0
- package/dist/skills/substitute.js +322 -0
- package/dist/skills/substitute.js.map +1 -0
- package/dist/skills/testing/SkillKit.browser.d.ts +62 -0
- package/dist/skills/testing/SkillKit.browser.d.ts.map +1 -0
- package/dist/skills/testing/SkillKit.browser.js +41 -0
- package/dist/skills/testing/SkillKit.browser.js.map +1 -0
- package/dist/skills/testing/SkillKit.d.ts +130 -0
- package/dist/skills/testing/SkillKit.d.ts.map +1 -0
- package/dist/skills/testing/SkillKit.js +316 -0
- package/dist/skills/testing/SkillKit.js.map +1 -0
- package/dist/skills/testing/index.d.ts +9 -0
- package/dist/skills/testing/index.d.ts.map +1 -0
- package/dist/skills/testing/index.js +8 -0
- package/dist/skills/testing/index.js.map +1 -0
- package/dist/skills/trust.d.ts +72 -0
- package/dist/skills/trust.d.ts.map +1 -0
- package/dist/skills/trust.js +183 -0
- package/dist/skills/trust.js.map +1 -0
- package/dist/skills/types.d.ts +627 -0
- package/dist/skills/types.d.ts.map +1 -0
- package/dist/skills/types.js +85 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/skills/validator.d.ts +95 -0
- package/dist/skills/validator.d.ts.map +1 -0
- package/dist/skills/validator.js +486 -0
- package/dist/skills/validator.js.map +1 -0
- package/dist/tracing/PipelineTracer.d.ts +35 -22
- package/dist/tracing/PipelineTracer.d.ts.map +1 -1
- package/dist/tracing/PipelineTracer.js +106 -61
- package/dist/tracing/PipelineTracer.js.map +1 -1
- package/dist/tracing/SdkTracer.d.ts +63 -61
- package/dist/tracing/SdkTracer.d.ts.map +1 -1
- package/dist/tracing/SdkTracer.js +185 -177
- package/dist/tracing/SdkTracer.js.map +1 -1
- package/dist/tracing/index.d.ts +10 -1
- package/dist/tracing/index.d.ts.map +1 -1
- package/dist/tracing/index.js +9 -0
- package/dist/tracing/index.js.map +1 -1
- package/dist/tracing/types.d.ts +89 -16
- package/dist/tracing/types.d.ts.map +1 -1
- package/dist/tracing/types.js +17 -4
- package/dist/tracing/types.js.map +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +10 -20
- package/dist/version.js.map +1 -1
- package/package.json +53 -39
- package/.claude/skills/qodo-agent/SKILL.md +0 -974
- package/.claude/skills/qodo-agent/assets/programmatic-agent.ts +0 -407
- package/.claude/skills/qodo-agent/references/builtin-tools.md +0 -342
- package/.claude/skills/qodo-agent/references/common-issues.md +0 -537
- package/bin/rg +0 -0
- package/dist/api/agent.d.ts +0 -104
- package/dist/api/agent.d.ts.map +0 -1
- package/dist/api/agent.js +0 -939
- package/dist/api/agent.js.map +0 -1
- package/dist/api/analytics.d.ts +0 -43
- package/dist/api/analytics.d.ts.map +0 -1
- package/dist/api/analytics.js +0 -163
- package/dist/api/analytics.js.map +0 -1
- package/dist/api/http.d.ts +0 -5
- package/dist/api/http.d.ts.map +0 -1
- package/dist/api/http.js +0 -62
- package/dist/api/http.js.map +0 -1
- package/dist/api/index.d.ts +0 -12
- package/dist/api/index.d.ts.map +0 -1
- package/dist/api/index.js +0 -17
- package/dist/api/index.js.map +0 -1
- package/dist/api/taskTracking.d.ts +0 -54
- package/dist/api/taskTracking.d.ts.map +0 -1
- package/dist/api/taskTracking.js +0 -208
- package/dist/api/taskTracking.js.map +0 -1
- package/dist/api/types.d.ts +0 -93
- package/dist/api/types.d.ts.map +0 -1
- package/dist/api/types.js +0 -2
- package/dist/api/types.js.map +0 -1
- package/dist/api/utils.d.ts +0 -8
- package/dist/api/utils.d.ts.map +0 -1
- package/dist/api/utils.js +0 -63
- package/dist/api/utils.js.map +0 -1
- package/dist/api/websocket.d.ts +0 -203
- package/dist/api/websocket.d.ts.map +0 -1
- package/dist/api/websocket.js +0 -1166
- package/dist/api/websocket.js.map +0 -1
- package/dist/bin/install-skill.d.ts +0 -14
- package/dist/bin/install-skill.d.ts.map +0 -1
- package/dist/bin/install-skill.js +0 -125
- package/dist/bin/install-skill.js.map +0 -1
- package/dist/bin/run-helpers.d.ts +0 -34
- package/dist/bin/run-helpers.d.ts.map +0 -1
- package/dist/bin/run-helpers.js +0 -186
- package/dist/bin/run-helpers.js.map +0 -1
- package/dist/bin/run.d.ts +0 -13
- package/dist/bin/run.d.ts.map +0 -1
- package/dist/bin/run.js +0 -57
- package/dist/bin/run.js.map +0 -1
- package/dist/clients/index.d.ts +0 -10
- package/dist/clients/index.d.ts.map +0 -1
- package/dist/clients/index.js +0 -8
- package/dist/clients/index.js.map +0 -1
- package/dist/clients/info/InfoClient.d.ts +0 -37
- package/dist/clients/info/InfoClient.d.ts.map +0 -1
- package/dist/clients/info/InfoClient.js +0 -69
- package/dist/clients/info/InfoClient.js.map +0 -1
- package/dist/clients/info/index.d.ts +0 -4
- package/dist/clients/info/index.d.ts.map +0 -1
- package/dist/clients/info/index.js +0 -2
- package/dist/clients/info/index.js.map +0 -1
- package/dist/clients/info/types.d.ts +0 -21
- package/dist/clients/info/types.d.ts.map +0 -1
- package/dist/clients/info/types.js +0 -2
- package/dist/clients/info/types.js.map +0 -1
- package/dist/clients/sessions/SessionsClient.d.ts +0 -34
- package/dist/clients/sessions/SessionsClient.d.ts.map +0 -1
- package/dist/clients/sessions/SessionsClient.js +0 -71
- package/dist/clients/sessions/SessionsClient.js.map +0 -1
- package/dist/clients/sessions/index.d.ts +0 -4
- package/dist/clients/sessions/index.d.ts.map +0 -1
- package/dist/clients/sessions/index.js +0 -2
- package/dist/clients/sessions/index.js.map +0 -1
- package/dist/clients/sessions/types.d.ts +0 -20
- package/dist/clients/sessions/types.d.ts.map +0 -1
- package/dist/clients/sessions/types.js +0 -2
- package/dist/clients/sessions/types.js.map +0 -1
- package/dist/clients/tools/ToolsClient.d.ts +0 -39
- package/dist/clients/tools/ToolsClient.d.ts.map +0 -1
- package/dist/clients/tools/ToolsClient.js +0 -95
- package/dist/clients/tools/ToolsClient.js.map +0 -1
- package/dist/clients/tools/index.d.ts +0 -4
- package/dist/clients/tools/index.d.ts.map +0 -1
- package/dist/clients/tools/index.js +0 -2
- package/dist/clients/tools/index.js.map +0 -1
- package/dist/clients/tools/types.d.ts +0 -14
- package/dist/clients/tools/types.d.ts.map +0 -1
- package/dist/clients/tools/types.js +0 -2
- package/dist/clients/tools/types.js.map +0 -1
- package/dist/config/ConfigManager.d.ts +0 -43
- package/dist/config/ConfigManager.d.ts.map +0 -1
- package/dist/config/ConfigManager.js +0 -472
- package/dist/config/ConfigManager.js.map +0 -1
- package/dist/config/index.d.ts +0 -6
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js +0 -7
- package/dist/config/index.js.map +0 -1
- package/dist/config/urlConfig.d.ts +0 -15
- package/dist/config/urlConfig.d.ts.map +0 -1
- package/dist/config/urlConfig.js +0 -75
- package/dist/config/urlConfig.js.map +0 -1
- package/dist/constants/errors.d.ts +0 -2
- package/dist/constants/errors.d.ts.map +0 -1
- package/dist/constants/errors.js +0 -2
- package/dist/constants/errors.js.map +0 -1
- package/dist/constants/index.d.ts +0 -7
- package/dist/constants/index.d.ts.map +0 -1
- package/dist/constants/index.js +0 -11
- package/dist/constants/index.js.map +0 -1
- package/dist/constants/tools.d.ts +0 -4
- package/dist/constants/tools.d.ts.map +0 -1
- package/dist/constants/tools.js +0 -4
- package/dist/constants/tools.js.map +0 -1
- package/dist/constants/versions.d.ts +0 -2
- package/dist/constants/versions.d.ts.map +0 -1
- package/dist/constants/versions.js +0 -2
- package/dist/constants/versions.js.map +0 -1
- package/dist/context/buildUserContext.d.ts +0 -18
- package/dist/context/buildUserContext.d.ts.map +0 -1
- package/dist/context/buildUserContext.js +0 -34
- package/dist/context/buildUserContext.js.map +0 -1
- package/dist/context/index.d.ts +0 -9
- package/dist/context/index.d.ts.map +0 -1
- package/dist/context/index.js +0 -9
- package/dist/context/index.js.map +0 -1
- package/dist/context/messageManager.d.ts +0 -42
- package/dist/context/messageManager.d.ts.map +0 -1
- package/dist/context/messageManager.js +0 -322
- package/dist/context/messageManager.js.map +0 -1
- package/dist/context/taskFocus.d.ts +0 -2
- package/dist/context/taskFocus.d.ts.map +0 -1
- package/dist/context/taskFocus.js +0 -26
- package/dist/context/taskFocus.js.map +0 -1
- package/dist/context/userInput.d.ts +0 -3
- package/dist/context/userInput.d.ts.map +0 -1
- package/dist/context/userInput.js +0 -20
- package/dist/context/userInput.js.map +0 -1
- package/dist/mcp/MCPManager.d.ts +0 -109
- package/dist/mcp/MCPManager.d.ts.map +0 -1
- package/dist/mcp/MCPManager.js +0 -592
- package/dist/mcp/MCPManager.js.map +0 -1
- package/dist/mcp/approvedTools.d.ts +0 -4
- package/dist/mcp/approvedTools.d.ts.map +0 -1
- package/dist/mcp/approvedTools.js +0 -19
- package/dist/mcp/approvedTools.js.map +0 -1
- package/dist/mcp/baseServer.d.ts +0 -75
- package/dist/mcp/baseServer.d.ts.map +0 -1
- package/dist/mcp/baseServer.js +0 -107
- package/dist/mcp/baseServer.js.map +0 -1
- package/dist/mcp/builtinServers.d.ts +0 -15
- package/dist/mcp/builtinServers.d.ts.map +0 -1
- package/dist/mcp/builtinServers.js +0 -141
- package/dist/mcp/builtinServers.js.map +0 -1
- package/dist/mcp/dynamicBEServer.d.ts +0 -20
- package/dist/mcp/dynamicBEServer.d.ts.map +0 -1
- package/dist/mcp/dynamicBEServer.js +0 -52
- package/dist/mcp/dynamicBEServer.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -18
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -23
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/mcpInitialization.d.ts +0 -2
- package/dist/mcp/mcpInitialization.d.ts.map +0 -1
- package/dist/mcp/mcpInitialization.js +0 -56
- package/dist/mcp/mcpInitialization.js.map +0 -1
- package/dist/mcp/servers/filesystem.d.ts +0 -44
- package/dist/mcp/servers/filesystem.d.ts.map +0 -1
- package/dist/mcp/servers/filesystem.js +0 -776
- package/dist/mcp/servers/filesystem.js.map +0 -1
- package/dist/mcp/servers/git.d.ts +0 -18
- package/dist/mcp/servers/git.d.ts.map +0 -1
- package/dist/mcp/servers/git.js +0 -441
- package/dist/mcp/servers/git.js.map +0 -1
- package/dist/mcp/servers/ripgrep.d.ts +0 -39
- package/dist/mcp/servers/ripgrep.d.ts.map +0 -1
- package/dist/mcp/servers/ripgrep.js +0 -550
- package/dist/mcp/servers/ripgrep.js.map +0 -1
- package/dist/mcp/servers/shell.d.ts +0 -20
- package/dist/mcp/servers/shell.d.ts.map +0 -1
- package/dist/mcp/servers/shell.js +0 -519
- package/dist/mcp/servers/shell.js.map +0 -1
- package/dist/mcp/serversRegistry.d.ts +0 -55
- package/dist/mcp/serversRegistry.d.ts.map +0 -1
- package/dist/mcp/serversRegistry.js +0 -416
- package/dist/mcp/serversRegistry.js.map +0 -1
- package/dist/mcp/toolProcessor.d.ts +0 -82
- package/dist/mcp/toolProcessor.d.ts.map +0 -1
- package/dist/mcp/toolProcessor.js +0 -392
- package/dist/mcp/toolProcessor.js.map +0 -1
- package/dist/mcp/types.d.ts +0 -29
- package/dist/mcp/types.d.ts.map +0 -1
- package/dist/mcp/types.js +0 -2
- package/dist/mcp/types.js.map +0 -1
- package/dist/messages/index.d.ts +0 -8
- package/dist/messages/index.d.ts.map +0 -1
- package/dist/messages/index.js +0 -7
- package/dist/messages/index.js.map +0 -1
- package/dist/messages/openai.d.ts +0 -26
- package/dist/messages/openai.d.ts.map +0 -1
- package/dist/messages/openai.js +0 -55
- package/dist/messages/openai.js.map +0 -1
- package/dist/messages/types.d.ts +0 -73
- package/dist/messages/types.d.ts.map +0 -1
- package/dist/messages/types.js +0 -78
- package/dist/messages/types.js.map +0 -1
- package/dist/parser/index.d.ts +0 -72
- package/dist/parser/index.d.ts.map +0 -1
- package/dist/parser/index.js +0 -967
- package/dist/parser/index.js.map +0 -1
- package/dist/parser/types.d.ts +0 -153
- package/dist/parser/types.d.ts.map +0 -1
- package/dist/parser/types.js +0 -6
- package/dist/parser/types.js.map +0 -1
- package/dist/parser/utils.d.ts +0 -18
- package/dist/parser/utils.d.ts.map +0 -1
- package/dist/parser/utils.js +0 -64
- package/dist/parser/utils.js.map +0 -1
- package/dist/sdk/QodoSDK.d.ts +0 -218
- package/dist/sdk/QodoSDK.d.ts.map +0 -1
- package/dist/sdk/QodoSDK.js +0 -1115
- package/dist/sdk/QodoSDK.js.map +0 -1
- package/dist/sdk/artifacts.d.ts +0 -156
- package/dist/sdk/artifacts.d.ts.map +0 -1
- package/dist/sdk/artifacts.js +0 -166
- package/dist/sdk/artifacts.js.map +0 -1
- package/dist/sdk/bootstrap.d.ts +0 -16
- package/dist/sdk/bootstrap.d.ts.map +0 -1
- package/dist/sdk/bootstrap.js +0 -28
- package/dist/sdk/bootstrap.js.map +0 -1
- package/dist/sdk/builders.d.ts +0 -54
- package/dist/sdk/builders.d.ts.map +0 -1
- package/dist/sdk/builders.js +0 -117
- package/dist/sdk/builders.js.map +0 -1
- package/dist/sdk/defaults.d.ts +0 -11
- package/dist/sdk/defaults.d.ts.map +0 -1
- package/dist/sdk/defaults.js +0 -39
- package/dist/sdk/defaults.js.map +0 -1
- package/dist/sdk/discovery.d.ts +0 -2
- package/dist/sdk/discovery.d.ts.map +0 -1
- package/dist/sdk/discovery.js +0 -25
- package/dist/sdk/discovery.js.map +0 -1
- package/dist/sdk/events.d.ts +0 -269
- package/dist/sdk/events.d.ts.map +0 -1
- package/dist/sdk/events.js +0 -69
- package/dist/sdk/events.js.map +0 -1
- package/dist/sdk/exit-expression.d.ts +0 -13
- package/dist/sdk/exit-expression.d.ts.map +0 -1
- package/dist/sdk/exit-expression.js +0 -35
- package/dist/sdk/exit-expression.js.map +0 -1
- package/dist/sdk/index.d.ts +0 -17
- package/dist/sdk/index.d.ts.map +0 -1
- package/dist/sdk/index.js +0 -17
- package/dist/sdk/index.js.map +0 -1
- package/dist/sdk/middleware.d.ts +0 -59
- package/dist/sdk/middleware.d.ts.map +0 -1
- package/dist/sdk/middleware.js +0 -69
- package/dist/sdk/middleware.js.map +0 -1
- package/dist/sdk/pipeline/PipelineBuilder.d.ts +0 -79
- package/dist/sdk/pipeline/PipelineBuilder.d.ts.map +0 -1
- package/dist/sdk/pipeline/PipelineBuilder.js +0 -129
- package/dist/sdk/pipeline/PipelineBuilder.js.map +0 -1
- package/dist/sdk/pipeline/PipelineRunner.d.ts +0 -28
- package/dist/sdk/pipeline/PipelineRunner.d.ts.map +0 -1
- package/dist/sdk/pipeline/PipelineRunner.js +0 -326
- package/dist/sdk/pipeline/PipelineRunner.js.map +0 -1
- package/dist/sdk/pipeline/compiler.d.ts +0 -24
- package/dist/sdk/pipeline/compiler.d.ts.map +0 -1
- package/dist/sdk/pipeline/compiler.js +0 -199
- package/dist/sdk/pipeline/compiler.js.map +0 -1
- package/dist/sdk/pipeline/declarative.d.ts +0 -34
- package/dist/sdk/pipeline/declarative.d.ts.map +0 -1
- package/dist/sdk/pipeline/declarative.js +0 -9
- package/dist/sdk/pipeline/declarative.js.map +0 -1
- package/dist/sdk/pipeline/index.d.ts +0 -20
- package/dist/sdk/pipeline/index.d.ts.map +0 -1
- package/dist/sdk/pipeline/index.js +0 -19
- package/dist/sdk/pipeline/index.js.map +0 -1
- package/dist/sdk/pipeline/types.d.ts +0 -93
- package/dist/sdk/pipeline/types.d.ts.map +0 -1
- package/dist/sdk/pipeline/types.js +0 -10
- package/dist/sdk/pipeline/types.js.map +0 -1
- package/dist/sdk/policies.d.ts +0 -163
- package/dist/sdk/policies.d.ts.map +0 -1
- package/dist/sdk/policies.js +0 -243
- package/dist/sdk/policies.js.map +0 -1
- package/dist/sdk/runner/AgentRunner.d.ts +0 -22
- package/dist/sdk/runner/AgentRunner.d.ts.map +0 -1
- package/dist/sdk/runner/AgentRunner.js +0 -222
- package/dist/sdk/runner/AgentRunner.js.map +0 -1
- package/dist/sdk/runner/finalize.d.ts +0 -56
- package/dist/sdk/runner/finalize.d.ts.map +0 -1
- package/dist/sdk/runner/finalize.js +0 -155
- package/dist/sdk/runner/finalize.js.map +0 -1
- package/dist/sdk/runner/formats.d.ts +0 -7
- package/dist/sdk/runner/formats.d.ts.map +0 -1
- package/dist/sdk/runner/formats.js +0 -76
- package/dist/sdk/runner/formats.js.map +0 -1
- package/dist/sdk/runner/index.d.ts +0 -9
- package/dist/sdk/runner/index.d.ts.map +0 -1
- package/dist/sdk/runner/index.js +0 -9
- package/dist/sdk/runner/index.js.map +0 -1
- package/dist/sdk/runner/progress.d.ts +0 -3
- package/dist/sdk/runner/progress.d.ts.map +0 -1
- package/dist/sdk/runner/progress.js +0 -16
- package/dist/sdk/runner/progress.js.map +0 -1
- package/dist/sdk/schemas.d.ts +0 -72
- package/dist/sdk/schemas.d.ts.map +0 -1
- package/dist/sdk/schemas.js +0 -282
- package/dist/sdk/schemas.js.map +0 -1
- package/dist/sdk/trigger-context.d.ts +0 -24
- package/dist/sdk/trigger-context.d.ts.map +0 -1
- package/dist/sdk/trigger-context.js +0 -136
- package/dist/sdk/trigger-context.js.map +0 -1
- package/dist/session/SessionContext.d.ts +0 -89
- package/dist/session/SessionContext.d.ts.map +0 -1
- package/dist/session/SessionContext.js +0 -410
- package/dist/session/SessionContext.js.map +0 -1
- package/dist/session/environment.d.ts +0 -52
- package/dist/session/environment.d.ts.map +0 -1
- package/dist/session/environment.js +0 -27
- package/dist/session/environment.js.map +0 -1
- package/dist/session/history.d.ts +0 -18
- package/dist/session/history.d.ts.map +0 -1
- package/dist/session/history.js +0 -68
- package/dist/session/history.js.map +0 -1
- package/dist/session/index.d.ts +0 -10
- package/dist/session/index.d.ts.map +0 -1
- package/dist/session/index.js +0 -9
- package/dist/session/index.js.map +0 -1
- package/dist/session/serverData.d.ts +0 -38
- package/dist/session/serverData.d.ts.map +0 -1
- package/dist/session/serverData.js +0 -261
- package/dist/session/serverData.js.map +0 -1
- package/dist/tracing/pipelineHelpers.d.ts +0 -29
- package/dist/tracing/pipelineHelpers.d.ts.map +0 -1
- package/dist/tracing/pipelineHelpers.js +0 -224
- package/dist/tracing/pipelineHelpers.js.map +0 -1
- package/dist/tracking/Tracker.d.ts +0 -55
- package/dist/tracking/Tracker.d.ts.map +0 -1
- package/dist/tracking/Tracker.js +0 -217
- package/dist/tracking/Tracker.js.map +0 -1
- package/dist/tracking/index.d.ts +0 -8
- package/dist/tracking/index.d.ts.map +0 -1
- package/dist/tracking/index.js +0 -8
- package/dist/tracking/index.js.map +0 -1
- package/dist/tracking/schemas.d.ts +0 -292
- package/dist/tracking/schemas.d.ts.map +0 -1
- package/dist/tracking/schemas.js +0 -91
- package/dist/tracking/schemas.js.map +0 -1
- package/dist/utils/extractSetFlags.d.ts +0 -6
- package/dist/utils/extractSetFlags.d.ts.map +0 -1
- package/dist/utils/extractSetFlags.js +0 -16
- package/dist/utils/extractSetFlags.js.map +0 -1
- package/dist/utils/formatTimeAgo.d.ts +0 -2
- package/dist/utils/formatTimeAgo.d.ts.map +0 -1
- package/dist/utils/formatTimeAgo.js +0 -20
- package/dist/utils/formatTimeAgo.js.map +0 -1
- package/dist/utils/index.d.ts +0 -12
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -12
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/machineId.d.ts +0 -14
- package/dist/utils/machineId.d.ts.map +0 -1
- package/dist/utils/machineId.js +0 -66
- package/dist/utils/machineId.js.map +0 -1
- package/dist/utils/pathUtils.d.ts +0 -22
- package/dist/utils/pathUtils.d.ts.map +0 -1
- package/dist/utils/pathUtils.js +0 -54
- package/dist/utils/pathUtils.js.map +0 -1
- package/scripts/download-ripgrep.js +0 -269
|
@@ -0,0 +1,1386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SkillsManager`.
|
|
3
|
+
*
|
|
4
|
+
* Walks every configured source, parses + validates each `SKILL.md`, then
|
|
5
|
+
* builds an in-memory catalog keyed by fully-qualified
|
|
6
|
+
* `<vendor>/<name>@<version>` with shadowing.
|
|
7
|
+
*
|
|
8
|
+
* Construction is synchronous and cheap (validate config, store). The
|
|
9
|
+
* `discover()` method does the disk IO; it's idempotent — subsequent
|
|
10
|
+
* calls return the same already-built catalog. Public lookups via
|
|
11
|
+
* `list()` / `get()` await discovery completion implicitly.
|
|
12
|
+
*
|
|
13
|
+
* Invariants:
|
|
14
|
+
* - Catalog is built once per discovery; hot reload reruns discovery
|
|
15
|
+
* to produce a fresh snapshot.
|
|
16
|
+
* - Trust tier is derived from source type only; consumer-supplied
|
|
17
|
+
* `trustedSources` is used to elevate third-party sources.
|
|
18
|
+
* - Per-skill `min_sdk_version` exclusion lands here.
|
|
19
|
+
*/
|
|
20
|
+
import { promises as fs, watch as fsWatch } from 'node:fs';
|
|
21
|
+
import * as path from 'node:path';
|
|
22
|
+
import { parseSkillDocument, SkillParseError } from './parser.js';
|
|
23
|
+
import { defaultWalkerEnv, walkSource } from './sources/index.js';
|
|
24
|
+
import { safeRealPath } from './sources/walk.js';
|
|
25
|
+
import { isSkillTrusted, validateTrustedSources } from './trust.js';
|
|
26
|
+
import { findLockfile, LockfileValidationError, loadLockfile, validateLockfileEntry, } from './lockfile.js';
|
|
27
|
+
import { DEFAULT_SOURCE_ORDER, REQUIRES_DEFAULT_MAX_DEPTH, REQUIRES_DEFAULT_MAX_TRANSITIVE, REQUIRES_DEFAULT_MAX_TRANSITIVE_TOKENS, SKILL_FILE_HARD_BYTES, SOURCE_DEFAULT_VENDORS, SOURCE_TRUST_TIERS, } from './types.js';
|
|
28
|
+
import { extractBodyReferences, semverLessThan, validateBody, validateFrontmatter, } from './validator.js';
|
|
29
|
+
/** Default `version` when frontmatter omits it. */
|
|
30
|
+
const DEFAULT_VERSION = '0.0.0';
|
|
31
|
+
export class SkillsManager {
|
|
32
|
+
config;
|
|
33
|
+
env;
|
|
34
|
+
emit;
|
|
35
|
+
trustedSourceIdentities;
|
|
36
|
+
requiresCaps;
|
|
37
|
+
/**
|
|
38
|
+
* Per-client active-skill set. Populated by caller-pin activation
|
|
39
|
+
* (`TaskClient`) and by LLM-driven `get_skill` calls (the MCP server,
|
|
40
|
+
* via `markLoaded`). Cleared by `resetActive()`.
|
|
41
|
+
*
|
|
42
|
+
* SDK 2.0 has no per-task AsyncLocalStorage env-scope (the design that
|
|
43
|
+
* shipped on SDK 1.x's `EnvironmentServices`); active-state lives on
|
|
44
|
+
* the manager, scoped to the client lifetime. Concurrent dispatches
|
|
45
|
+
* against the same client observe a merged set — documented in the
|
|
46
|
+
* architectural-translation note. `disconnect()` calls
|
|
47
|
+
* `skillsMcpServer.dispose()` which calls `resetActive()` (transitively
|
|
48
|
+
* through the existing `activeFqns` clear), so a fresh `connect()`
|
|
49
|
+
* starts cold.
|
|
50
|
+
*/
|
|
51
|
+
activeFqns = new Set();
|
|
52
|
+
discoverPromise = null;
|
|
53
|
+
snapshot = null;
|
|
54
|
+
/**
|
|
55
|
+
* Active `fs.watch` handles keyed by the absolute directory path
|
|
56
|
+
* being watched. Populated by `startWatching()`, cleared by
|
|
57
|
+
* `stopWatching()` / `dispose()`.
|
|
58
|
+
*/
|
|
59
|
+
watchers = new Map();
|
|
60
|
+
/** Pending watcher-triggered rebuild timer, debounced. */
|
|
61
|
+
rebuildTimer = null;
|
|
62
|
+
/** Promise that resolves with the next rebuilt snapshot, if a rebuild is in-flight. */
|
|
63
|
+
rebuildPromise = null;
|
|
64
|
+
/** Optional listener fired after every successful rebuild — used by tests / consumers. */
|
|
65
|
+
rebuildListeners = new Set();
|
|
66
|
+
epoch = 0;
|
|
67
|
+
/**
|
|
68
|
+
* In-flight `startWatching()` promise. Concurrent calls await the
|
|
69
|
+
* same promise so the install pass is serialised — defends against
|
|
70
|
+
* the watcher-duplication race when auto-start + manual start fire
|
|
71
|
+
* close together.
|
|
72
|
+
*/
|
|
73
|
+
startWatchingPromise = null;
|
|
74
|
+
/**
|
|
75
|
+
* Marks that a watcher event arrived while a rebuild was already
|
|
76
|
+
* in flight. The current rebuild's `.then` re-schedules so the late
|
|
77
|
+
* change isn't dropped.
|
|
78
|
+
*/
|
|
79
|
+
pendingRebuild = false;
|
|
80
|
+
constructor(config, env = defaultWalkerEnv()) {
|
|
81
|
+
this.config = config;
|
|
82
|
+
this.env = env;
|
|
83
|
+
this.emit = config.emit;
|
|
84
|
+
// Validate `trustedSources` against the configured `sources` list at
|
|
85
|
+
// construction time so a misconfigured allowlist fails loud at `new
|
|
86
|
+
// QodoClient({ skills: {...} })` instead of at the first activation
|
|
87
|
+
// attempt.
|
|
88
|
+
const sources = this.resolveSourcesList();
|
|
89
|
+
this.trustedSourceIdentities = validateTrustedSources(config.trustedSources, sources);
|
|
90
|
+
this.requiresCaps = resolveRequiresCaps(config.requires);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Forward a renderer-produced event batch to the configured sink.
|
|
94
|
+
* Used by `TaskClient`'s skills injection helpers — the renderer is
|
|
95
|
+
* pure and returns events as data; the manager owns the sink wiring
|
|
96
|
+
* so consumers see one consistent emission surface.
|
|
97
|
+
*/
|
|
98
|
+
forwardEvents(events) {
|
|
99
|
+
for (const event of events)
|
|
100
|
+
this.emitEvent(event);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Walk every configured source, parse + validate, build the FQN-keyed
|
|
104
|
+
* catalog. Idempotent — concurrent calls share one in-flight promise.
|
|
105
|
+
*/
|
|
106
|
+
discover() {
|
|
107
|
+
if (this.snapshot !== null)
|
|
108
|
+
return Promise.resolve(this.snapshot);
|
|
109
|
+
if (this.discoverPromise !== null)
|
|
110
|
+
return this.discoverPromise;
|
|
111
|
+
this.discoverPromise = this.doRebuild('initial_discover').then((snapshot) => {
|
|
112
|
+
if (this.config.watch === true) {
|
|
113
|
+
// Fire-and-forget — watcher installation reads the now-set
|
|
114
|
+
// `snapshot.bySource`; failures emit `sdk.skill.error`.
|
|
115
|
+
this.startWatching().catch(() => {
|
|
116
|
+
// ignored — startWatching surfaces errors through emitEvent
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return snapshot;
|
|
120
|
+
});
|
|
121
|
+
return this.discoverPromise;
|
|
122
|
+
}
|
|
123
|
+
/** Resolved `true` once `discover()` has completed at least once. */
|
|
124
|
+
get isReady() {
|
|
125
|
+
return this.snapshot !== null;
|
|
126
|
+
}
|
|
127
|
+
/** Synchronous snapshot accessor. Returns `null` until `discover()` resolves. */
|
|
128
|
+
get currentSnapshot() {
|
|
129
|
+
return this.snapshot;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Constructor-supplied default `activate` set. `TaskClient` reads this
|
|
133
|
+
* when the per-call `opts.skills` is `undefined`. Returned read-only;
|
|
134
|
+
* `[]` and `undefined` are distinct (`[]` = explicit empty, `undefined`
|
|
135
|
+
* = "no default set").
|
|
136
|
+
*/
|
|
137
|
+
get defaultActivate() {
|
|
138
|
+
return this.config.activate;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Constructor-supplied `indexCharBudget`. `TaskClient` forwards this
|
|
142
|
+
* to `renderSlimIndex()` so consumers can tighten the model-visible
|
|
143
|
+
* block below the 8000-char default. `undefined` means "renderer
|
|
144
|
+
* picks the default".
|
|
145
|
+
*/
|
|
146
|
+
get indexCharBudget() {
|
|
147
|
+
return this.config.indexCharBudget;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Constructor-supplied `!command` preprocessing config.
|
|
151
|
+
* `undefined` means "default disabled" — the MCP server's `get_skill`
|
|
152
|
+
* handler short-circuits when this is `undefined` or `mode: 'disabled'`.
|
|
153
|
+
*/
|
|
154
|
+
get preprocessingConfig() {
|
|
155
|
+
return this.config.preprocessing;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Constructor-supplied embedder, if any. `undefined` means
|
|
159
|
+
* "no pre-filter; rely on render-side truncation".
|
|
160
|
+
*/
|
|
161
|
+
get embedder() {
|
|
162
|
+
return this.config.embedder;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Constructor-supplied embedding-pre-filter tuning. `undefined` means
|
|
166
|
+
* "use documented defaults" (`topK: 8`, `minScore: 0.35`).
|
|
167
|
+
*/
|
|
168
|
+
get activationConfig() {
|
|
169
|
+
return this.config.activation;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* List every catalog entry. Awaits discovery on first call. Returns
|
|
173
|
+
* latest-version-only by default; pass `{ includeAllVersions: true }`
|
|
174
|
+
* to surface every version recorded during discovery.
|
|
175
|
+
*/
|
|
176
|
+
async list(opts = {}) {
|
|
177
|
+
const snapshot = await this.discover();
|
|
178
|
+
if (opts.includeAllVersions === true)
|
|
179
|
+
return snapshot.skills;
|
|
180
|
+
return collectLatestVersions(snapshot.skills);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Resolve a skill specifier (`name`, `vendor/name`, or `vendor/name@version`)
|
|
184
|
+
* to a `ParsedSkill` or `null` if no match.
|
|
185
|
+
*
|
|
186
|
+
* - Bare name: returns the latest-version match of any vendor; if multiple
|
|
187
|
+
* vendors ship a skill of that name, the highest-precedence source wins.
|
|
188
|
+
* - `vendor/name`: latest version of that vendor's skill.
|
|
189
|
+
* - `vendor/name@version`: exact pin.
|
|
190
|
+
*/
|
|
191
|
+
async get(specifier) {
|
|
192
|
+
const snapshot = await this.discover();
|
|
193
|
+
return resolveSpecifier(snapshot.skills, specifier);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Verify every name in `specifiers` resolves. Throws on the first
|
|
197
|
+
* unresolved entry — the SDK uses this from the wire boundary to fail
|
|
198
|
+
* loud on caller-pinned activation per `skills-activation.md` §2.
|
|
199
|
+
*/
|
|
200
|
+
async resolveOrThrow(specifiers) {
|
|
201
|
+
const snapshot = await this.discover();
|
|
202
|
+
const out = [];
|
|
203
|
+
for (const spec of specifiers) {
|
|
204
|
+
const resolved = resolveSpecifier(snapshot.skills, spec);
|
|
205
|
+
if (resolved === null) {
|
|
206
|
+
throw new SkillNotFoundError(spec);
|
|
207
|
+
}
|
|
208
|
+
out.push(resolved);
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
// ---------------------------------------------------------------------
|
|
213
|
+
// Trust + requires caps + active-skill state
|
|
214
|
+
// ---------------------------------------------------------------------
|
|
215
|
+
/**
|
|
216
|
+
* Whether `skill` is trusted enough to honor its declarative affordances
|
|
217
|
+
* (`allowed-tools`, `disable-model-invocation: false`, `hooks`). True for
|
|
218
|
+
* the `'trusted'` / `'operator'` tiers; for `'third-party'` true iff the
|
|
219
|
+
* skill's source identity is in `trustedSources`.
|
|
220
|
+
*/
|
|
221
|
+
isTrusted(skill) {
|
|
222
|
+
return isSkillTrusted(skill, this.trustedSourceIdentities);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Resolved `requires:` caps with defaults filled in. Read by the
|
|
226
|
+
* activation pipeline (`TaskClient` caller-pin + `QodoSkillsMcpServer.get_skill`).
|
|
227
|
+
*/
|
|
228
|
+
get requiresCapsResolved() {
|
|
229
|
+
return this.requiresCaps;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Snapshot of currently active skill FQNs. The active set is union of
|
|
233
|
+
* (a) caller-pinned skills the SDK injected for the latest dispatch and
|
|
234
|
+
* (b) skills the model loaded via `qodo-skills.get_skill`. Returned
|
|
235
|
+
* sorted for stable test output.
|
|
236
|
+
*/
|
|
237
|
+
getActiveSkills() {
|
|
238
|
+
return [...this.activeFqns].sort();
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Mark a skill FQN as active. Idempotent — re-marking emits no extra
|
|
242
|
+
* event. Returns `true` iff the FQN was newly added (i.e., this is a
|
|
243
|
+
* fresh activation, not a cache hit).
|
|
244
|
+
*
|
|
245
|
+
* The caller is responsible for emitting `sdk.skill.activated` — the
|
|
246
|
+
* event carries `source: 'caller' | 'llm'` which only the caller knows.
|
|
247
|
+
* `markActive` just owns the set membership.
|
|
248
|
+
*/
|
|
249
|
+
markActive(fqn) {
|
|
250
|
+
if (this.activeFqns.has(fqn))
|
|
251
|
+
return false;
|
|
252
|
+
this.activeFqns.add(fqn);
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Remove a skill FQN from the active set. Returns `true` iff it was
|
|
257
|
+
* present. Used by `QodoSkillsMcpServer.dispose` and (rarely) by
|
|
258
|
+
* consumers programmatically rolling back activation.
|
|
259
|
+
*/
|
|
260
|
+
deactivate(fqn) {
|
|
261
|
+
return this.activeFqns.delete(fqn);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Clear the active set entirely. Called by `QodoSkillsMcpServer.dispose`
|
|
265
|
+
* on `disconnect()` so the next `connect()` starts cold.
|
|
266
|
+
*/
|
|
267
|
+
resetActive() {
|
|
268
|
+
this.activeFqns.clear();
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* @internal — used by `QodoSkillsMcpServer` to share the active set with
|
|
272
|
+
* the body cache. Exposes the same `Set` the manager uses; mutations
|
|
273
|
+
* propagate to both surfaces.
|
|
274
|
+
*/
|
|
275
|
+
get internalActiveFqns() {
|
|
276
|
+
return this.activeFqns;
|
|
277
|
+
}
|
|
278
|
+
// ---------------------------------------------------------------------
|
|
279
|
+
// Hot reload watcher (dev mode)
|
|
280
|
+
// ---------------------------------------------------------------------
|
|
281
|
+
/**
|
|
282
|
+
* Begin watching every configured local source directory for SKILL.md
|
|
283
|
+
* changes. Idempotent — calling twice has no extra effect.
|
|
284
|
+
*
|
|
285
|
+
* Requires `discover()` to have completed; the watcher targets only
|
|
286
|
+
* directories already known to be on disk. New top-level source dirs
|
|
287
|
+
* appearing post-startup are NOT auto-watched (operators would
|
|
288
|
+
* typically `stopWatching()` + reconfigure + start a new client).
|
|
289
|
+
*
|
|
290
|
+
* `package` and `bundled` sources are intentionally NOT watched —
|
|
291
|
+
* those ship with the application code and shouldn't churn at
|
|
292
|
+
* runtime. Watching them would expose tampering surface without
|
|
293
|
+
* meaningful upside.
|
|
294
|
+
*
|
|
295
|
+
* Default behavior is determined by `SkillsConfig.watch`:
|
|
296
|
+
* `undefined`/`false` ⇒ no-op; `true` ⇒ start watching after the first
|
|
297
|
+
* `discover()` resolves.
|
|
298
|
+
*/
|
|
299
|
+
async startWatching() {
|
|
300
|
+
// Overlapping `startWatching()` calls (e.g. auto-start + an
|
|
301
|
+
// explicit manual start) could race past the
|
|
302
|
+
// `watchers.size > 0` early-out and install duplicate `FSWatcher`s
|
|
303
|
+
// for the same directory. Serialise through an in-flight promise so
|
|
304
|
+
// every concurrent caller waits on the same install pass.
|
|
305
|
+
if (this.startWatchingPromise !== null) {
|
|
306
|
+
await this.startWatchingPromise;
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
this.startWatchingPromise = this.doStartWatching().finally(() => {
|
|
310
|
+
this.startWatchingPromise = null;
|
|
311
|
+
});
|
|
312
|
+
await this.startWatchingPromise;
|
|
313
|
+
}
|
|
314
|
+
async doStartWatching() {
|
|
315
|
+
await this.discover();
|
|
316
|
+
if (this.watchers.size > 0)
|
|
317
|
+
return;
|
|
318
|
+
const seen = new Set();
|
|
319
|
+
for (const result of this.snapshot.bySource) {
|
|
320
|
+
// Skip source types that ship with code and shouldn't churn.
|
|
321
|
+
if (!isWatchableSourceType(result.source.type))
|
|
322
|
+
continue;
|
|
323
|
+
// Watch each discovered skill's parent (the `<source>/<skill-dir>`'s
|
|
324
|
+
// parent — typically the source root). That covers add/change/delete
|
|
325
|
+
// of the skill's own SKILL.md and sibling adds (a new skill dir
|
|
326
|
+
// appearing alongside an existing one).
|
|
327
|
+
for (const walked of result.skills) {
|
|
328
|
+
const parent = path.dirname(walked.dirPath);
|
|
329
|
+
if (seen.has(parent))
|
|
330
|
+
continue;
|
|
331
|
+
const real = await safeRealPath(parent);
|
|
332
|
+
if (real === null || seen.has(real))
|
|
333
|
+
continue;
|
|
334
|
+
seen.add(real);
|
|
335
|
+
this.installWatcher(real);
|
|
336
|
+
}
|
|
337
|
+
// Also watch the source's declared root if it has one (covers an
|
|
338
|
+
// empty source — no skills yet, but operators expect adds to be
|
|
339
|
+
// picked up). When `source.path` is omitted (the common default
|
|
340
|
+
// for `project` / `user`), the watcher needs to know the
|
|
341
|
+
// resolved default location too; `resolveDefaultWatchRoot`
|
|
342
|
+
// bridges the env-based defaulting the walker itself does.
|
|
343
|
+
const root = watchableRoot(result.source) ?? resolveDefaultWatchRoot(result.source, this.env);
|
|
344
|
+
if (root !== null) {
|
|
345
|
+
const real = await safeRealPath(root);
|
|
346
|
+
if (real !== null && !seen.has(real)) {
|
|
347
|
+
seen.add(real);
|
|
348
|
+
this.installWatcher(real);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Tear down every active watcher and cancel any pending debounce
|
|
355
|
+
* timer. Safe to call multiple times. Called automatically by
|
|
356
|
+
* `dispose()`.
|
|
357
|
+
*/
|
|
358
|
+
stopWatching() {
|
|
359
|
+
for (const watcher of this.watchers.values()) {
|
|
360
|
+
try {
|
|
361
|
+
watcher.close();
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// Closing a watcher can throw on some platforms when the
|
|
365
|
+
// underlying directory is already gone; ignored — there's
|
|
366
|
+
// nothing to recover.
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
this.watchers.clear();
|
|
370
|
+
if (this.rebuildTimer !== null) {
|
|
371
|
+
clearTimeout(this.rebuildTimer);
|
|
372
|
+
this.rebuildTimer = null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Tear down the manager — equivalent to `stopWatching()`. Provided as
|
|
377
|
+
* a parallel to `QodoSkillsMcpServer.dispose()` so consumer code can
|
|
378
|
+
* call both in a uniform shutdown sequence.
|
|
379
|
+
*/
|
|
380
|
+
dispose() {
|
|
381
|
+
this.stopWatching();
|
|
382
|
+
this.rebuildListeners.clear();
|
|
383
|
+
}
|
|
384
|
+
/** Current catalog epoch counter. `0` before the first `discover()`. */
|
|
385
|
+
get catalogEpoch() {
|
|
386
|
+
return this.epoch;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Subscribe to rebuild events. The listener fires after every
|
|
390
|
+
* successful watcher-triggered rebuild (NOT on the initial discover).
|
|
391
|
+
* Returns an unsubscribe function. Used primarily by tests.
|
|
392
|
+
*/
|
|
393
|
+
onRebuild(listener) {
|
|
394
|
+
this.rebuildListeners.add(listener);
|
|
395
|
+
return () => {
|
|
396
|
+
this.rebuildListeners.delete(listener);
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Trigger an out-of-band rebuild. Test seam — production code goes
|
|
401
|
+
* through the debounced watcher path. The rebuild is in-flight
|
|
402
|
+
* already if another rebuild is queued; returns the same in-flight
|
|
403
|
+
* promise to avoid double-rebuild races.
|
|
404
|
+
*/
|
|
405
|
+
rebuildNow() {
|
|
406
|
+
if (this.rebuildPromise !== null) {
|
|
407
|
+
// A change that arrives mid-rebuild must not be silently dropped.
|
|
408
|
+
// Record the trailing request; the current rebuild's finally-hook
|
|
409
|
+
// will fire another pass.
|
|
410
|
+
this.pendingRebuild = true;
|
|
411
|
+
return this.rebuildPromise;
|
|
412
|
+
}
|
|
413
|
+
this.rebuildPromise = this.doRebuild('watcher_event').finally(() => {
|
|
414
|
+
this.rebuildPromise = null;
|
|
415
|
+
if (this.pendingRebuild) {
|
|
416
|
+
this.pendingRebuild = false;
|
|
417
|
+
// Schedule the follow-up through the debouncer so multiple
|
|
418
|
+
// late changes coalesce into one extra rebuild.
|
|
419
|
+
this.scheduleRebuild();
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
return this.rebuildPromise;
|
|
423
|
+
}
|
|
424
|
+
installWatcher(dir) {
|
|
425
|
+
if (this.watchers.has(dir))
|
|
426
|
+
return;
|
|
427
|
+
let watcher;
|
|
428
|
+
try {
|
|
429
|
+
watcher = fsWatch(dir, { recursive: true });
|
|
430
|
+
}
|
|
431
|
+
catch (err) {
|
|
432
|
+
this.emitEvent({
|
|
433
|
+
kind: 'sdk.skill.error',
|
|
434
|
+
path: dir,
|
|
435
|
+
severity: 'warning',
|
|
436
|
+
message: `watch failed for ${dir}: ${errorMessage(err)}`,
|
|
437
|
+
});
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
watcher.on('change', () => this.scheduleRebuild());
|
|
441
|
+
watcher.on('error', (err) => {
|
|
442
|
+
this.emitEvent({
|
|
443
|
+
kind: 'sdk.skill.error',
|
|
444
|
+
path: dir,
|
|
445
|
+
severity: 'warning',
|
|
446
|
+
message: `watcher error for ${dir}: ${err.message}`,
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
this.watchers.set(dir, watcher);
|
|
450
|
+
}
|
|
451
|
+
scheduleRebuild() {
|
|
452
|
+
if (this.rebuildTimer !== null)
|
|
453
|
+
return;
|
|
454
|
+
// If a rebuild is already in flight, record the trailing request
|
|
455
|
+
// and let the in-flight rebuild's finally-hook re-fire — no need
|
|
456
|
+
// to start a separate debounce timer that would race the hook.
|
|
457
|
+
if (this.rebuildPromise !== null) {
|
|
458
|
+
this.pendingRebuild = true;
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
this.rebuildTimer = setTimeout(() => {
|
|
462
|
+
this.rebuildTimer = null;
|
|
463
|
+
this.rebuildNow().catch(() => {
|
|
464
|
+
// Errors are emitted as `sdk.skill.error` inside `doRebuild`;
|
|
465
|
+
// there's no caller to propagate to from the watcher path.
|
|
466
|
+
});
|
|
467
|
+
}, 100);
|
|
468
|
+
}
|
|
469
|
+
async doRebuild(reason) {
|
|
470
|
+
const previous = this.snapshot;
|
|
471
|
+
const next = await this.buildCatalog();
|
|
472
|
+
this.epoch += 1;
|
|
473
|
+
const stamped = { ...next, epoch: this.epoch };
|
|
474
|
+
this.snapshot = stamped;
|
|
475
|
+
this.emitEvent({
|
|
476
|
+
kind: 'sdk.skill.catalog_epoch_bumped',
|
|
477
|
+
from: this.epoch - 1,
|
|
478
|
+
to: this.epoch,
|
|
479
|
+
reason,
|
|
480
|
+
});
|
|
481
|
+
if (previous !== null) {
|
|
482
|
+
this.diffAndEmit(previous, stamped);
|
|
483
|
+
// `onRebuild()` is documented as firing only on watcher-triggered
|
|
484
|
+
// rebuilds, not on the initial discover.
|
|
485
|
+
// Listeners registered before `discover()` would otherwise see
|
|
486
|
+
// an unexpected callback at startup. Gate on `previous !== null`
|
|
487
|
+
// so the initial-discover path stays silent.
|
|
488
|
+
for (const listener of this.rebuildListeners) {
|
|
489
|
+
try {
|
|
490
|
+
listener(stamped);
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
// Listener sinks must not break the rebuild loop.
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return stamped;
|
|
498
|
+
}
|
|
499
|
+
diffAndEmit(prev, next) {
|
|
500
|
+
const prevByFqn = new Map();
|
|
501
|
+
for (const s of prev.skills)
|
|
502
|
+
prevByFqn.set(s.fqn, s);
|
|
503
|
+
const nextByFqn = new Map();
|
|
504
|
+
for (const s of next.skills)
|
|
505
|
+
nextByFqn.set(s.fqn, s);
|
|
506
|
+
for (const [fqn, before] of prevByFqn) {
|
|
507
|
+
if (!nextByFqn.has(fqn)) {
|
|
508
|
+
this.emitEvent({
|
|
509
|
+
kind: 'sdk.skill.removed',
|
|
510
|
+
fqn,
|
|
511
|
+
source: before.source,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// `sdk.skill.discovered` events for fresh adds are emitted from
|
|
516
|
+
// inside `doDiscover()` already; nothing more to do here for the
|
|
517
|
+
// add path.
|
|
518
|
+
}
|
|
519
|
+
// ---------------------------------------------------------------------
|
|
520
|
+
// Internals
|
|
521
|
+
// ---------------------------------------------------------------------
|
|
522
|
+
/**
|
|
523
|
+
* Walk every configured source + parse + validate + assemble the
|
|
524
|
+
* catalog. Pure with respect to `this.snapshot` — the caller decides
|
|
525
|
+
* how to publish the returned snapshot (initial discover stamps the
|
|
526
|
+
* epoch; rebuild diffs against the prior snapshot first).
|
|
527
|
+
*/
|
|
528
|
+
async buildCatalog() {
|
|
529
|
+
const sources = this.resolveSourcesList();
|
|
530
|
+
const bySource = [];
|
|
531
|
+
const allCandidates = [];
|
|
532
|
+
for (const source of sources) {
|
|
533
|
+
const start = Date.now();
|
|
534
|
+
let result;
|
|
535
|
+
try {
|
|
536
|
+
result = await walkSource(source, this.env);
|
|
537
|
+
}
|
|
538
|
+
catch (err) {
|
|
539
|
+
result = {
|
|
540
|
+
source,
|
|
541
|
+
skills: [],
|
|
542
|
+
errors: [{ message: `walker threw: ${errorMessage(err)}` }],
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
const durationMs = Date.now() - start;
|
|
546
|
+
this.emitEvent({
|
|
547
|
+
kind: 'sdk.skill.source_scanned',
|
|
548
|
+
source: result.source,
|
|
549
|
+
durationMs,
|
|
550
|
+
count: result.skills.length,
|
|
551
|
+
errors: result.errors.length,
|
|
552
|
+
});
|
|
553
|
+
for (const errEntry of result.errors) {
|
|
554
|
+
this.emitEvent({
|
|
555
|
+
kind: 'sdk.skill.error',
|
|
556
|
+
source: result.source,
|
|
557
|
+
...(errEntry.path !== undefined ? { path: errEntry.path } : {}),
|
|
558
|
+
severity: 'error',
|
|
559
|
+
message: errEntry.message,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
bySource.push(result);
|
|
563
|
+
for (const walked of result.skills) {
|
|
564
|
+
allCandidates.push({
|
|
565
|
+
walked,
|
|
566
|
+
source: result.source,
|
|
567
|
+
...(result.resolvedVendor !== undefined ? { resolvedVendor: result.resolvedVendor } : {}),
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// Parse + validate each candidate. Keyed by FQN; later candidates of
|
|
572
|
+
// the same FQN are shadow-resolved (highest-precedence source wins).
|
|
573
|
+
const byFqn = new Map();
|
|
574
|
+
const excluded = [];
|
|
575
|
+
const seenWithinSource = new Set();
|
|
576
|
+
let currentSourceKey = '';
|
|
577
|
+
for (const candidate of allCandidates) {
|
|
578
|
+
const sourceKey = sourceIdentity(candidate.source);
|
|
579
|
+
if (sourceKey !== currentSourceKey) {
|
|
580
|
+
seenWithinSource.clear();
|
|
581
|
+
currentSourceKey = sourceKey;
|
|
582
|
+
}
|
|
583
|
+
const parsed = await this.parseAndValidate(candidate.walked, candidate.source, candidate.resolvedVendor);
|
|
584
|
+
if (parsed === null)
|
|
585
|
+
continue;
|
|
586
|
+
const fqn = parsed.fqn;
|
|
587
|
+
const intraSourceKey = `${sourceKey}|${fqn}`;
|
|
588
|
+
if (seenWithinSource.has(intraSourceKey)) {
|
|
589
|
+
this.emitEvent({
|
|
590
|
+
kind: 'sdk.skill.error',
|
|
591
|
+
source: candidate.source,
|
|
592
|
+
path: candidate.walked.skillFilePath,
|
|
593
|
+
severity: 'error',
|
|
594
|
+
message: `duplicate FQN within source: ${fqn}`,
|
|
595
|
+
});
|
|
596
|
+
excluded.push({
|
|
597
|
+
source: candidate.source,
|
|
598
|
+
path: candidate.walked.skillFilePath,
|
|
599
|
+
reason: `duplicate FQN within source: ${fqn}`,
|
|
600
|
+
});
|
|
601
|
+
// Also drop any prior winner from the catalog if it came from
|
|
602
|
+
// the same source — both copies excluded.
|
|
603
|
+
const prior = byFqn.get(fqn);
|
|
604
|
+
if (prior !== undefined && sourceIdentity(prior.source) === sourceKey) {
|
|
605
|
+
byFqn.delete(fqn);
|
|
606
|
+
}
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
seenWithinSource.add(intraSourceKey);
|
|
610
|
+
const existing = byFqn.get(fqn);
|
|
611
|
+
if (existing === undefined) {
|
|
612
|
+
byFqn.set(fqn, parsed);
|
|
613
|
+
this.emitEvent({
|
|
614
|
+
kind: 'sdk.skill.discovered',
|
|
615
|
+
fqn,
|
|
616
|
+
source: parsed.source,
|
|
617
|
+
version: parsed.version,
|
|
618
|
+
trustTier: parsed.trustTier,
|
|
619
|
+
});
|
|
620
|
+
// Emit the dedicated tier-assigned event for audit-log replay.
|
|
621
|
+
// The information is already encoded in
|
|
622
|
+
// `sdk.skill.discovered.trustTier`; the explicit event surface
|
|
623
|
+
// lets operators wire a security-only sink without inheriting
|
|
624
|
+
// the rest of the discovery telemetry volume.
|
|
625
|
+
this.emitEvent({
|
|
626
|
+
kind: 'sdk.skill.tier_assigned',
|
|
627
|
+
skill: parsed.fqn,
|
|
628
|
+
tier: parsed.trustTier,
|
|
629
|
+
});
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
// Same FQN seen earlier; the earlier source has higher precedence
|
|
633
|
+
// (we iterate `sources` in highest-first order). The new one is
|
|
634
|
+
// shadowed.
|
|
635
|
+
this.emitEvent({
|
|
636
|
+
kind: 'sdk.skill.shadowed',
|
|
637
|
+
fqn,
|
|
638
|
+
winner: { source: existing.source, version: existing.version },
|
|
639
|
+
loser: { source: parsed.source, version: parsed.version },
|
|
640
|
+
});
|
|
641
|
+
excluded.push({
|
|
642
|
+
source: parsed.source,
|
|
643
|
+
path: parsed.skillFile,
|
|
644
|
+
reason: `shadowed by ${sourceIdentity(existing.source)}/${existing.version}`,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
const skills = [...byFqn.values()].sort((a, b) => a.fqn.localeCompare(b.fqn));
|
|
648
|
+
// Validate any configured lockfile *after* discovery. Skills not in
|
|
649
|
+
// the lockfile are still included; pinned entries that drift or are
|
|
650
|
+
// unreachable fail boot loudly.
|
|
651
|
+
await this.applyLockfile(skills);
|
|
652
|
+
// `epoch` is stamped by the caller (`doRebuild`); placeholder `0`
|
|
653
|
+
// here keeps the shape consistent without redundant copying.
|
|
654
|
+
const snapshot = {
|
|
655
|
+
skills,
|
|
656
|
+
bySource,
|
|
657
|
+
excluded,
|
|
658
|
+
epoch: 0,
|
|
659
|
+
};
|
|
660
|
+
return snapshot;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Locate + apply the lockfile per `SkillsConfig.lockfile`. When
|
|
664
|
+
* `'off'`, returns immediately. When `'discover'`, searches the
|
|
665
|
+
* configured CWD; when an explicit `{ path }`, loads it directly.
|
|
666
|
+
*
|
|
667
|
+
* Validation outcomes:
|
|
668
|
+
*
|
|
669
|
+
* - All entries match → return silently.
|
|
670
|
+
* - At least one drifts → emit `sdk.skill.lock_drift` per offender + throw.
|
|
671
|
+
* - At least one unreachable → emit `sdk.skill.lock_unreachable` + throw.
|
|
672
|
+
*
|
|
673
|
+
* Throws `LockfileValidationError` so the wire boundary fails loud
|
|
674
|
+
* rather than serving a divergent catalog.
|
|
675
|
+
*/
|
|
676
|
+
async applyLockfile(skills) {
|
|
677
|
+
const policy = this.config.lockfile ?? 'discover';
|
|
678
|
+
if (policy === 'off')
|
|
679
|
+
return;
|
|
680
|
+
let lockfilePath = null;
|
|
681
|
+
if (typeof policy === 'object' && policy !== null) {
|
|
682
|
+
lockfilePath = policy.path;
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
// Discover the lockfile from the configured CWD AND from the
|
|
686
|
+
// nearest ancestor directory containing `agent.toml` — matches the
|
|
687
|
+
// documented "beside agent.toml" behavior.
|
|
688
|
+
// Walk up at most 8 levels so the search is bounded even when the
|
|
689
|
+
// CWD is buried in a deep monorepo.
|
|
690
|
+
const searchPaths = await collectLockfileSearchPaths(this.env.cwd);
|
|
691
|
+
lockfilePath = await findLockfile(searchPaths);
|
|
692
|
+
}
|
|
693
|
+
if (lockfilePath === null)
|
|
694
|
+
return;
|
|
695
|
+
let lockfile;
|
|
696
|
+
try {
|
|
697
|
+
lockfile = await loadLockfile(lockfilePath);
|
|
698
|
+
}
|
|
699
|
+
catch (err) {
|
|
700
|
+
// Malformed lockfile is a configuration error, surfaced through the
|
|
701
|
+
// emit sink and rethrown so the consumer fails fast.
|
|
702
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
703
|
+
this.emitEvent({
|
|
704
|
+
kind: 'sdk.skill.error',
|
|
705
|
+
severity: 'error',
|
|
706
|
+
message: `failed to parse lockfile at ${lockfilePath}: ${message}`,
|
|
707
|
+
});
|
|
708
|
+
throw err;
|
|
709
|
+
}
|
|
710
|
+
const byFqn = new Map();
|
|
711
|
+
for (const skill of skills)
|
|
712
|
+
byFqn.set(skill.fqn, skill);
|
|
713
|
+
const failures = [];
|
|
714
|
+
for (const entry of lockfile.skills) {
|
|
715
|
+
const discovered = byFqn.get(entry.fqn);
|
|
716
|
+
const outcome = await validateLockfileEntry(entry, discovered);
|
|
717
|
+
switch (outcome.outcome) {
|
|
718
|
+
case 'ok':
|
|
719
|
+
continue;
|
|
720
|
+
case 'drift': {
|
|
721
|
+
this.emitEvent({
|
|
722
|
+
kind: 'sdk.skill.lock_drift',
|
|
723
|
+
fqn: entry.fqn,
|
|
724
|
+
expectedSha256: outcome.expectedSha256,
|
|
725
|
+
actualSha256: outcome.actualSha256,
|
|
726
|
+
});
|
|
727
|
+
failures.push({
|
|
728
|
+
fqn: entry.fqn,
|
|
729
|
+
kind: 'drift',
|
|
730
|
+
message: `expected sha256 ${outcome.expectedSha256}, got ${outcome.actualSha256}`,
|
|
731
|
+
});
|
|
732
|
+
break;
|
|
733
|
+
}
|
|
734
|
+
case 'unreachable': {
|
|
735
|
+
this.emitEvent({
|
|
736
|
+
kind: 'sdk.skill.lock_unreachable',
|
|
737
|
+
fqn: entry.fqn,
|
|
738
|
+
source: entry.source,
|
|
739
|
+
});
|
|
740
|
+
failures.push({
|
|
741
|
+
fqn: entry.fqn,
|
|
742
|
+
kind: 'unreachable',
|
|
743
|
+
message: outcome.reason,
|
|
744
|
+
});
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (failures.length > 0) {
|
|
750
|
+
const summary = failures
|
|
751
|
+
.map((f) => ` - ${f.fqn}: ${f.kind} (${f.message})`)
|
|
752
|
+
.join('\n');
|
|
753
|
+
throw new LockfileValidationError(failures[0].fqn, failures[0].kind, `lockfile validation failed for ${failures.length} skill(s):\n${summary}\n` +
|
|
754
|
+
'Resolve drift via `qodo skills lock --update <fqn>`.');
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
resolveSourcesList() {
|
|
758
|
+
if (this.config.sources !== undefined)
|
|
759
|
+
return this.config.sources;
|
|
760
|
+
// Default: project + user + bundled. We deliberately leave package
|
|
761
|
+
// / config / cli-fetched off the default because they require
|
|
762
|
+
// explicit opt-in (config has no default path; package needs a
|
|
763
|
+
// packageName; cli-fetched needs the CLI command to have run).
|
|
764
|
+
return DEFAULT_SOURCE_ORDER.flatMap((type) => {
|
|
765
|
+
switch (type) {
|
|
766
|
+
case 'project':
|
|
767
|
+
return [{ type: 'project' }];
|
|
768
|
+
case 'user':
|
|
769
|
+
return [{ type: 'user' }];
|
|
770
|
+
case 'bundled':
|
|
771
|
+
return [{ type: 'bundled' }];
|
|
772
|
+
default:
|
|
773
|
+
return [];
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
async parseAndValidate(walked, source, resolvedVendor) {
|
|
778
|
+
const dirName = path.basename(walked.dirPath);
|
|
779
|
+
// F2: enforce the file-size hard cap *before* `fs.readFile` so an
|
|
780
|
+
// attacker-controlled multi-megabyte SKILL.md cannot exhaust memory
|
|
781
|
+
// through the walker / parser path.
|
|
782
|
+
//
|
|
783
|
+
// We re-stat here at the read boundary rather than trusting
|
|
784
|
+
// `walked.size` (captured during discovery) — between scan and
|
|
785
|
+
// read the file can grow, which would otherwise bypass the cap.
|
|
786
|
+
// The walker's `size` is kept for early-exit on already-huge
|
|
787
|
+
// files; this re-stat closes the TOCTTOU window.
|
|
788
|
+
let liveStat;
|
|
789
|
+
try {
|
|
790
|
+
liveStat = await fs.stat(walked.skillFilePath);
|
|
791
|
+
}
|
|
792
|
+
catch (err) {
|
|
793
|
+
this.emitEvent({
|
|
794
|
+
kind: 'sdk.skill.error',
|
|
795
|
+
source,
|
|
796
|
+
path: walked.skillFilePath,
|
|
797
|
+
severity: 'error',
|
|
798
|
+
message: `stat failed: ${errorMessage(err)}`,
|
|
799
|
+
});
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
if (liveStat.size > SKILL_FILE_HARD_BYTES) {
|
|
803
|
+
this.emitEvent({
|
|
804
|
+
kind: 'sdk.skill.error',
|
|
805
|
+
source,
|
|
806
|
+
path: walked.skillFilePath,
|
|
807
|
+
severity: 'error',
|
|
808
|
+
message: `SKILL.md exceeds the ${SKILL_FILE_HARD_BYTES}-byte file cap ` +
|
|
809
|
+
`(${liveStat.size} bytes); skipping without reading`,
|
|
810
|
+
});
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
let text;
|
|
814
|
+
try {
|
|
815
|
+
text = await fs.readFile(walked.skillFilePath, 'utf-8');
|
|
816
|
+
}
|
|
817
|
+
catch (err) {
|
|
818
|
+
this.emitEvent({
|
|
819
|
+
kind: 'sdk.skill.error',
|
|
820
|
+
source,
|
|
821
|
+
path: walked.skillFilePath,
|
|
822
|
+
severity: 'error',
|
|
823
|
+
message: `readFile failed: ${errorMessage(err)}`,
|
|
824
|
+
});
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
// Defensive last-resort: even with the pre-read stat, a race between
|
|
828
|
+
// `stat` and `readFile` can grow the file by an unbounded amount.
|
|
829
|
+
// Verify the actual loaded byte length and reject if it exceeded
|
|
830
|
+
// the cap during the read window.
|
|
831
|
+
const actualBytes = Buffer.byteLength(text, 'utf-8');
|
|
832
|
+
if (actualBytes > SKILL_FILE_HARD_BYTES) {
|
|
833
|
+
this.emitEvent({
|
|
834
|
+
kind: 'sdk.skill.error',
|
|
835
|
+
source,
|
|
836
|
+
path: walked.skillFilePath,
|
|
837
|
+
severity: 'error',
|
|
838
|
+
message: `SKILL.md grew past the ${SKILL_FILE_HARD_BYTES}-byte file cap ` +
|
|
839
|
+
`during read (${actualBytes} bytes); skipping`,
|
|
840
|
+
});
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
let parsed;
|
|
844
|
+
try {
|
|
845
|
+
parsed = parseSkillDocument(text);
|
|
846
|
+
}
|
|
847
|
+
catch (err) {
|
|
848
|
+
const msg = err instanceof SkillParseError ? err.message : errorMessage(err);
|
|
849
|
+
this.emitEvent({
|
|
850
|
+
kind: 'sdk.skill.error',
|
|
851
|
+
source,
|
|
852
|
+
path: walked.skillFilePath,
|
|
853
|
+
severity: 'error',
|
|
854
|
+
message: msg,
|
|
855
|
+
});
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
const validated = validateFrontmatter(parsed.frontmatter, dirName);
|
|
859
|
+
for (const issue of validated.issues) {
|
|
860
|
+
this.emitEvent({
|
|
861
|
+
kind: 'sdk.skill.error',
|
|
862
|
+
source,
|
|
863
|
+
path: walked.skillFilePath,
|
|
864
|
+
severity: issue.severity,
|
|
865
|
+
message: issue.field !== undefined ? `${issue.field}: ${issue.message}` : issue.message,
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
for (const unknownField of validated.unknownFields) {
|
|
869
|
+
this.emitEvent({
|
|
870
|
+
kind: 'sdk.skill.unknown_field',
|
|
871
|
+
path: walked.skillFilePath,
|
|
872
|
+
field: unknownField,
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
// Fatal frontmatter issues exclude the skill.
|
|
876
|
+
if (validated.issues.some((i) => i.severity === 'error')) {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
const bodyValidation = validateBody(parsed.body);
|
|
880
|
+
for (const issue of bodyValidation.issues) {
|
|
881
|
+
this.emitEvent({
|
|
882
|
+
kind: 'sdk.skill.error',
|
|
883
|
+
source,
|
|
884
|
+
path: walked.skillFilePath,
|
|
885
|
+
severity: issue.severity,
|
|
886
|
+
message: issue.message,
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
if (bodyValidation.issues.some((i) => i.severity === 'error')) {
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
const vendor = resolveVendor(validated.frontmatter.vendor, source, resolvedVendor);
|
|
893
|
+
const version = validated.frontmatter.version ?? DEFAULT_VERSION;
|
|
894
|
+
const name = validated.frontmatter.name; // validator guarantees presence
|
|
895
|
+
const fqn = `${vendor}/${name}@${version}`;
|
|
896
|
+
const fqnNoVersion = `${vendor}/${name}`;
|
|
897
|
+
// `min_sdk_version` enforcement: exclude with a warning if the running
|
|
898
|
+
// SDK is older than the declared minimum.
|
|
899
|
+
if (validated.frontmatter.min_sdk_version !== undefined && this.config.sdkVersion !== undefined) {
|
|
900
|
+
try {
|
|
901
|
+
if (semverLessThan(this.config.sdkVersion, validated.frontmatter.min_sdk_version)) {
|
|
902
|
+
this.emitEvent({
|
|
903
|
+
kind: 'sdk.skill.error',
|
|
904
|
+
source,
|
|
905
|
+
path: walked.skillFilePath,
|
|
906
|
+
severity: 'warning',
|
|
907
|
+
message: `min_sdk_version ${validated.frontmatter.min_sdk_version} not met by SDK ${this.config.sdkVersion}`,
|
|
908
|
+
});
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
catch {
|
|
913
|
+
// already reported as a validator error
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
const trustTier = SOURCE_TRUST_TIERS[source.type];
|
|
917
|
+
// Walk supporting-file references in the body. Emits a
|
|
918
|
+
// warning for each reference deeper than one segment (authors are
|
|
919
|
+
// encouraged to keep referenced files at the skill root for
|
|
920
|
+
// discoverability) and a warning for each reference whose target file
|
|
921
|
+
// doesn't exist. The skill itself still loads — these are advisory
|
|
922
|
+
// findings the operator can ignore or wire to CI.
|
|
923
|
+
await this.auditBodyReferences(walked, source, parsed.body);
|
|
924
|
+
return {
|
|
925
|
+
fqn,
|
|
926
|
+
fqnNoVersion,
|
|
927
|
+
name,
|
|
928
|
+
vendor,
|
|
929
|
+
version,
|
|
930
|
+
path: walked.dirPath,
|
|
931
|
+
skillFile: walked.skillFilePath,
|
|
932
|
+
mtimeMs: walked.mtimeMs,
|
|
933
|
+
frontmatter: validated.frontmatter,
|
|
934
|
+
body: parsed.body,
|
|
935
|
+
source,
|
|
936
|
+
trustTier,
|
|
937
|
+
raw: parsed.frontmatter,
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Inspect the skill body for `[label](path)` and `get_skill_file("path")`
|
|
942
|
+
* references; emit:
|
|
943
|
+
*
|
|
944
|
+
* - `sdk.skill.error severity: 'warning'` when the referenced file
|
|
945
|
+
* doesn't exist under the skill directory.
|
|
946
|
+
* - `sdk.skill.error severity: 'warning'` when a reference has depth
|
|
947
|
+
* greater than 1 (e.g. `scripts/helpers/foo.py`). Authors who
|
|
948
|
+
* intentionally nest files can ignore this warning.
|
|
949
|
+
*
|
|
950
|
+
* The check is best-effort: any `fs.stat` failure other than ENOENT is
|
|
951
|
+
* silently ignored so a transient filesystem error doesn't poison
|
|
952
|
+
* discovery.
|
|
953
|
+
*/
|
|
954
|
+
async auditBodyReferences(walked, source, body) {
|
|
955
|
+
const refs = extractBodyReferences(body);
|
|
956
|
+
for (const ref of refs) {
|
|
957
|
+
if (ref.depth > 1) {
|
|
958
|
+
this.emitEvent({
|
|
959
|
+
kind: 'sdk.skill.error',
|
|
960
|
+
source,
|
|
961
|
+
path: walked.skillFilePath,
|
|
962
|
+
severity: 'warning',
|
|
963
|
+
message: `reference '${ref.path}' is ${ref.depth} segments deep — ` +
|
|
964
|
+
'authors are encouraged to keep referenced files at the skill root for discoverability',
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
// Reject obvious traversal in the reference itself before we touch
|
|
968
|
+
// the filesystem; the body might be hostile.
|
|
969
|
+
if (ref.path.split('/').some((seg) => seg === '..')) {
|
|
970
|
+
this.emitEvent({
|
|
971
|
+
kind: 'sdk.skill.error',
|
|
972
|
+
source,
|
|
973
|
+
path: walked.skillFilePath,
|
|
974
|
+
severity: 'warning',
|
|
975
|
+
message: `reference '${ref.path}' contains '..' — skipping existence check`,
|
|
976
|
+
});
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
const absolute = path.join(walked.dirPath, ref.path);
|
|
980
|
+
try {
|
|
981
|
+
await fs.stat(absolute);
|
|
982
|
+
}
|
|
983
|
+
catch (err) {
|
|
984
|
+
const code = err?.code;
|
|
985
|
+
if (code === 'ENOENT') {
|
|
986
|
+
this.emitEvent({
|
|
987
|
+
kind: 'sdk.skill.error',
|
|
988
|
+
source,
|
|
989
|
+
path: walked.skillFilePath,
|
|
990
|
+
severity: 'warning',
|
|
991
|
+
message: `broken reference '${ref.path}' — file not found in skill directory`,
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
// For non-ENOENT errors (permission denied, IO failure), stay
|
|
995
|
+
// silent — discovery shouldn't gate on transient FS issues.
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
emitEvent(event) {
|
|
1000
|
+
if (this.emit === undefined)
|
|
1001
|
+
return;
|
|
1002
|
+
try {
|
|
1003
|
+
this.emit(scrubEvent(event));
|
|
1004
|
+
}
|
|
1005
|
+
catch {
|
|
1006
|
+
// Telemetry sinks must not break catalog construction; swallow.
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Thrown by `SkillsManager.resolveOrThrow` when a caller-pinned specifier
|
|
1012
|
+
* doesn't match anything in the catalog. The wire boundary (TaskClient
|
|
1013
|
+
* startWithAgent / startWithGraph) re-throws these so consumers see a
|
|
1014
|
+
* loud failure per `skills-activation.md` §2.
|
|
1015
|
+
*/
|
|
1016
|
+
export class SkillNotFoundError extends Error {
|
|
1017
|
+
specifier;
|
|
1018
|
+
constructor(specifier) {
|
|
1019
|
+
super(`Skill not found: ${specifier}`);
|
|
1020
|
+
this.name = 'SkillNotFoundError';
|
|
1021
|
+
this.specifier = specifier;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Thrown when a bare-name caller pin matches multiple skills under
|
|
1026
|
+
* different vendors (`acme/code-review` and `local/code-review` both
|
|
1027
|
+
* shipping `name: code-review`). The first-match-wins behavior silently
|
|
1028
|
+
* picked one, which could change at runtime depending on source order.
|
|
1029
|
+
* Force the caller to disambiguate with a `vendor/name` pin.
|
|
1030
|
+
*/
|
|
1031
|
+
export class SkillAmbiguousPinError extends Error {
|
|
1032
|
+
specifier;
|
|
1033
|
+
candidates;
|
|
1034
|
+
constructor(specifier, candidates) {
|
|
1035
|
+
super(`Skill pin "${specifier}" is ambiguous — multiple vendors ship a skill ` +
|
|
1036
|
+
`of that name: ${candidates.join(', ')}. Pin with a full ` +
|
|
1037
|
+
'`vendor/name` specifier to disambiguate.');
|
|
1038
|
+
this.name = 'SkillAmbiguousPinError';
|
|
1039
|
+
this.specifier = specifier;
|
|
1040
|
+
this.candidates = candidates;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Thrown when caller-pinned skills cannot fit inside the slim-index
|
|
1045
|
+
* hard cap (`2 × charBudget`, floored at `charBudget + 4096`). Pinning
|
|
1046
|
+
* is a "must include" contract — silently dropping pins would make the
|
|
1047
|
+
* model behave as if the caller never pinned them; throwing forces the
|
|
1048
|
+
* caller to fix the pin list or raise the budget.
|
|
1049
|
+
*/
|
|
1050
|
+
export class SkillsBudgetExceededError extends Error {
|
|
1051
|
+
omittedSkills;
|
|
1052
|
+
hardCap;
|
|
1053
|
+
constructor(omittedSkills, hardCap) {
|
|
1054
|
+
super(`caller-pinned skills exceeded the slim-index hard cap (${hardCap} chars). ` +
|
|
1055
|
+
`Omitted: ${omittedSkills.join(', ')}. Reduce the pin list or raise ` +
|
|
1056
|
+
'`SkillsConfig.indexCharBudget`.');
|
|
1057
|
+
this.name = 'SkillsBudgetExceededError';
|
|
1058
|
+
this.omittedSkills = omittedSkills;
|
|
1059
|
+
this.hardCap = hardCap;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Thrown when caller-pinned activation can't proceed because of a
|
|
1064
|
+
* `requires:` chain failure — trust violation, depth/breadth/token cap
|
|
1065
|
+
* breach, cycle, or unresolved dependency. The `kind` discriminator
|
|
1066
|
+
* carries the specific reason. Caller-pin
|
|
1067
|
+
* activation is an authoritative contract; failure must be loud rather
|
|
1068
|
+
* than silently dropping the affected skill.
|
|
1069
|
+
*/
|
|
1070
|
+
export class QodoSkillError extends Error {
|
|
1071
|
+
skill;
|
|
1072
|
+
reason;
|
|
1073
|
+
constructor(skill, reason, message) {
|
|
1074
|
+
super(message);
|
|
1075
|
+
this.name = 'QodoSkillError';
|
|
1076
|
+
this.skill = skill;
|
|
1077
|
+
this.reason = reason;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
// ---------------------------------------------------------------------------
|
|
1081
|
+
// Helpers
|
|
1082
|
+
// ---------------------------------------------------------------------------
|
|
1083
|
+
/**
|
|
1084
|
+
* Resolve `vendor`: explicit frontmatter `vendor:` wins; otherwise the
|
|
1085
|
+
* source-derived default.
|
|
1086
|
+
*/
|
|
1087
|
+
function resolveVendor(explicit, source, resolvedVendor) {
|
|
1088
|
+
if (explicit !== undefined && explicit.length > 0)
|
|
1089
|
+
return explicit;
|
|
1090
|
+
if (resolvedVendor !== undefined && resolvedVendor.length > 0)
|
|
1091
|
+
return resolvedVendor;
|
|
1092
|
+
const def = SOURCE_DEFAULT_VENDORS[source.type];
|
|
1093
|
+
if (def === '__from_package_name__' && source.type === 'package') {
|
|
1094
|
+
return source.packageName;
|
|
1095
|
+
}
|
|
1096
|
+
if (def === '__from_ref__' && source.type === 'cli-fetched') {
|
|
1097
|
+
const ref = source.ref;
|
|
1098
|
+
if (typeof ref === 'string' && ref.length > 0) {
|
|
1099
|
+
const first = ref.split('/')[0];
|
|
1100
|
+
if (first !== undefined && first.length > 0)
|
|
1101
|
+
return first;
|
|
1102
|
+
}
|
|
1103
|
+
return 'cli-fetched';
|
|
1104
|
+
}
|
|
1105
|
+
return def;
|
|
1106
|
+
}
|
|
1107
|
+
function sourceIdentity(source) {
|
|
1108
|
+
switch (source.type) {
|
|
1109
|
+
case 'project':
|
|
1110
|
+
return source.path !== undefined ? `project:${source.path}` : 'project';
|
|
1111
|
+
case 'config':
|
|
1112
|
+
return `config:${source.path}`;
|
|
1113
|
+
case 'user':
|
|
1114
|
+
return source.path !== undefined ? `user:${source.path}` : 'user';
|
|
1115
|
+
case 'package':
|
|
1116
|
+
return `package:${source.packageName}`;
|
|
1117
|
+
case 'cli-fetched':
|
|
1118
|
+
return source.ref !== undefined ? `cli-fetched:${source.ref}` : 'cli-fetched';
|
|
1119
|
+
case 'bundled':
|
|
1120
|
+
return source.path !== undefined ? `bundled:${source.path}` : 'bundled';
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
function collectLatestVersions(skills) {
|
|
1124
|
+
const byFqnNoVersion = new Map();
|
|
1125
|
+
for (const skill of skills) {
|
|
1126
|
+
const existing = byFqnNoVersion.get(skill.fqnNoVersion);
|
|
1127
|
+
if (existing === undefined) {
|
|
1128
|
+
byFqnNoVersion.set(skill.fqnNoVersion, skill);
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
try {
|
|
1132
|
+
if (semverLessThan(existing.version, skill.version)) {
|
|
1133
|
+
byFqnNoVersion.set(skill.fqnNoVersion, skill);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
catch {
|
|
1137
|
+
// both versions semver-invalid (shouldn't happen — validator
|
|
1138
|
+
// catches non-semver) — fall back to FQN string compare
|
|
1139
|
+
if (existing.fqn < skill.fqn)
|
|
1140
|
+
byFqnNoVersion.set(skill.fqnNoVersion, skill);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return [...byFqnNoVersion.values()].sort((a, b) => a.fqn.localeCompare(b.fqn));
|
|
1144
|
+
}
|
|
1145
|
+
function resolveSpecifier(skills, specifier) {
|
|
1146
|
+
const versionMatch = /^(.+?)@([^@]+)$/.exec(specifier);
|
|
1147
|
+
if (versionMatch !== null) {
|
|
1148
|
+
const fqnNoVersion = versionMatch[1];
|
|
1149
|
+
const version = versionMatch[2];
|
|
1150
|
+
return skills.find((s) => s.fqnNoVersion === fqnNoVersion && s.version === version) ?? null;
|
|
1151
|
+
}
|
|
1152
|
+
// Bare name or vendor/name.
|
|
1153
|
+
if (specifier.includes('/')) {
|
|
1154
|
+
return collectLatestVersions(skills.filter((s) => s.fqnNoVersion === specifier))[0] ?? null;
|
|
1155
|
+
}
|
|
1156
|
+
// Bare name — find latest of each vendor matching `name === specifier`.
|
|
1157
|
+
const matches = skills.filter((s) => s.name === specifier);
|
|
1158
|
+
if (matches.length === 0)
|
|
1159
|
+
return null;
|
|
1160
|
+
const latest = collectLatestVersions(matches);
|
|
1161
|
+
// If multiple vendors match, take the FIRST in the catalog's source-order.
|
|
1162
|
+
// The catalog itself is sorted by FQN; for bare-name resolution we want
|
|
1163
|
+
// the highest-precedence source's pick, which the catalog construction
|
|
1164
|
+
// already chose (first-wins via the shadowing loop). Pick by smallest
|
|
1165
|
+
// source-precedence index across matches.
|
|
1166
|
+
return latest[0] ?? null;
|
|
1167
|
+
}
|
|
1168
|
+
function errorMessage(err) {
|
|
1169
|
+
if (err instanceof Error)
|
|
1170
|
+
return err.message;
|
|
1171
|
+
return String(err);
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* F9: scrub absolute filesystem paths out of emitted `SkillEvent`s
|
|
1175
|
+
* before forwarding to the consumer's telemetry sink. Two fields
|
|
1176
|
+
* carry filesystem info:
|
|
1177
|
+
*
|
|
1178
|
+
* - `event.path` (absolute SKILL.md path) → reduced to the last two
|
|
1179
|
+
* path segments (`<skill-dir>/<file>`), enough for a developer to
|
|
1180
|
+
* identify the skill from logs without revealing home directories
|
|
1181
|
+
* or repo layout.
|
|
1182
|
+
* - `event.source.path` (for variants that carry it: project / user /
|
|
1183
|
+
* config / bundled) → stripped. The variant tag alone identifies
|
|
1184
|
+
* where the skill came from; the absolute resolved path is local
|
|
1185
|
+
* debug info the manager already retains internally.
|
|
1186
|
+
*
|
|
1187
|
+
* Manager-side state still references absolute paths for symlink
|
|
1188
|
+
* resolution, shadowing, and local debugging. This helper only
|
|
1189
|
+
* sanitises the *emitted* event payload.
|
|
1190
|
+
*/
|
|
1191
|
+
function scrubEvent(event) {
|
|
1192
|
+
switch (event.kind) {
|
|
1193
|
+
case 'sdk.skill.error':
|
|
1194
|
+
return {
|
|
1195
|
+
...event,
|
|
1196
|
+
...(event.source !== undefined ? { source: stripSourcePath(event.source) } : {}),
|
|
1197
|
+
...(event.path !== undefined ? { path: relativizeSkillPath(event.path) } : {}),
|
|
1198
|
+
};
|
|
1199
|
+
case 'sdk.skill.unknown_field':
|
|
1200
|
+
return { ...event, path: relativizeSkillPath(event.path) };
|
|
1201
|
+
case 'sdk.skill.source_scanned':
|
|
1202
|
+
return { ...event, source: stripSourcePath(event.source) };
|
|
1203
|
+
case 'sdk.skill.discovered':
|
|
1204
|
+
return { ...event, source: stripSourcePath(event.source) };
|
|
1205
|
+
case 'sdk.skill.shadowed':
|
|
1206
|
+
return {
|
|
1207
|
+
...event,
|
|
1208
|
+
winner: { ...event.winner, source: stripSourcePath(event.winner.source) },
|
|
1209
|
+
loser: { ...event.loser, source: stripSourcePath(event.loser.source) },
|
|
1210
|
+
};
|
|
1211
|
+
// Runtime / activation / trust / substitution / lockfile /
|
|
1212
|
+
// preprocess / fork / removed events. The `skill` / `parent` /
|
|
1213
|
+
// `child` / `fqn` fields are FQNs (no
|
|
1214
|
+
// filesystem leakage); other payload fields carry no path data. The
|
|
1215
|
+
// `lock_unreachable` variant carries a `source`, which gets the same
|
|
1216
|
+
// path stripping the other source-carrying events get.
|
|
1217
|
+
case 'sdk.skill.loaded':
|
|
1218
|
+
case 'sdk.skill.file_loaded':
|
|
1219
|
+
case 'sdk.skill.file_rejected':
|
|
1220
|
+
case 'sdk.skill.invocation_denied':
|
|
1221
|
+
case 'sdk.skill.activated':
|
|
1222
|
+
case 'sdk.skill.disable_model_invocation_overridden':
|
|
1223
|
+
case 'sdk.skill.allowed_tools_applied':
|
|
1224
|
+
case 'sdk.skill.allowed_tools_advisory':
|
|
1225
|
+
case 'sdk.skill.allowed_tools_unmatched':
|
|
1226
|
+
case 'sdk.skill.hooks_ignored':
|
|
1227
|
+
case 'sdk.skill.tier_assigned':
|
|
1228
|
+
case 'sdk.skill.requires_trust_denied':
|
|
1229
|
+
case 'sdk.skill.requires_cap_exceeded':
|
|
1230
|
+
case 'sdk.skill.substitution_warn':
|
|
1231
|
+
case 'sdk.skill.lock_drift':
|
|
1232
|
+
case 'sdk.skill.preprocess_executed':
|
|
1233
|
+
case 'sdk.skill.preprocess_blocked':
|
|
1234
|
+
case 'sdk.skill.fork_declined':
|
|
1235
|
+
case 'sdk.skill.catalog_epoch_bumped':
|
|
1236
|
+
case 'sdk.skill.index_filtered':
|
|
1237
|
+
return event;
|
|
1238
|
+
case 'sdk.skill.lock_unreachable':
|
|
1239
|
+
case 'sdk.skill.removed':
|
|
1240
|
+
return { ...event, source: stripSourcePath(event.source) };
|
|
1241
|
+
default:
|
|
1242
|
+
return event;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Identify the top-level directory to watch for hot reload on a given
|
|
1247
|
+
* source variant. Returns `null` for variants whose root we can't
|
|
1248
|
+
* point to without re-walking the env.
|
|
1249
|
+
*/
|
|
1250
|
+
function watchableRoot(source) {
|
|
1251
|
+
switch (source.type) {
|
|
1252
|
+
case 'project':
|
|
1253
|
+
return source.path ?? null;
|
|
1254
|
+
case 'config':
|
|
1255
|
+
return source.path;
|
|
1256
|
+
case 'user':
|
|
1257
|
+
return source.path ?? null;
|
|
1258
|
+
case 'cli-fetched':
|
|
1259
|
+
case 'package':
|
|
1260
|
+
case 'bundled':
|
|
1261
|
+
return null;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Whether a source-type is eligible for hot-reload watching. `package`
|
|
1266
|
+
* + `bundled` ship with code and shouldn't churn at runtime; `cli-fetched`
|
|
1267
|
+
* is intentionally static (the CLI command stamps a frozen ref).
|
|
1268
|
+
*/
|
|
1269
|
+
function isWatchableSourceType(type) {
|
|
1270
|
+
return type === 'project' || type === 'config' || type === 'user';
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Resolve the env-derived default directory for sources whose `path`
|
|
1274
|
+
* field is optional (`project` / `user`). Mirrors the walker's
|
|
1275
|
+
* defaulting logic so the watcher can target the same directory the
|
|
1276
|
+
* discoverer scanned, even when no skills exist there yet. Returns
|
|
1277
|
+
* `null` for source variants without an env-derived default.
|
|
1278
|
+
*
|
|
1279
|
+
* Defaults `project` to the env CWD and `user` to
|
|
1280
|
+
* `$XDG_CONFIG_HOME/qodo/skills` or `~/.qodo/skills`.
|
|
1281
|
+
*/
|
|
1282
|
+
function resolveDefaultWatchRoot(source, env) {
|
|
1283
|
+
if (source.type === 'project') {
|
|
1284
|
+
return path.join(env.cwd, '.qodo', 'skills');
|
|
1285
|
+
}
|
|
1286
|
+
if (source.type === 'user') {
|
|
1287
|
+
const xdg = env.env['XDG_CONFIG_HOME'];
|
|
1288
|
+
if (xdg !== undefined && xdg.length > 0)
|
|
1289
|
+
return path.join(xdg, 'qodo', 'skills');
|
|
1290
|
+
return path.join(env.homedir, '.qodo', 'skills');
|
|
1291
|
+
}
|
|
1292
|
+
return null;
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Fill `SkillsConfig.requires` defaults. Invalid (non-finite, non-positive)
|
|
1296
|
+
* inputs fall back to the documented defaults rather than throwing — the
|
|
1297
|
+
* caps are defense-in-depth; a misconfigured cap should not break the
|
|
1298
|
+
* activation path.
|
|
1299
|
+
*/
|
|
1300
|
+
function resolveRequiresCaps(config) {
|
|
1301
|
+
const maxDepth = pickPositiveInt(config?.maxDepth, REQUIRES_DEFAULT_MAX_DEPTH);
|
|
1302
|
+
const maxTransitive = pickPositiveInt(config?.maxTransitive, REQUIRES_DEFAULT_MAX_TRANSITIVE);
|
|
1303
|
+
const maxTransitiveTokens = pickPositiveInt(config?.maxTransitiveTokens, REQUIRES_DEFAULT_MAX_TRANSITIVE_TOKENS);
|
|
1304
|
+
return { maxDepth, maxTransitive, maxTransitiveTokens };
|
|
1305
|
+
}
|
|
1306
|
+
function pickPositiveInt(value, fallback) {
|
|
1307
|
+
if (value === undefined)
|
|
1308
|
+
return fallback;
|
|
1309
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
1310
|
+
return fallback;
|
|
1311
|
+
}
|
|
1312
|
+
return Math.floor(value);
|
|
1313
|
+
}
|
|
1314
|
+
function stripSourcePath(source) {
|
|
1315
|
+
switch (source.type) {
|
|
1316
|
+
case 'project':
|
|
1317
|
+
case 'user':
|
|
1318
|
+
case 'bundled':
|
|
1319
|
+
return { type: source.type };
|
|
1320
|
+
case 'config':
|
|
1321
|
+
// `config` requires `path` in the type — replace with the basename
|
|
1322
|
+
// so the operator can still tell `config:foo` apart from
|
|
1323
|
+
// `config:bar` without exposing the absolute resolved location.
|
|
1324
|
+
return { type: 'config', path: path.basename(source.path) };
|
|
1325
|
+
case 'package':
|
|
1326
|
+
return source.path !== undefined
|
|
1327
|
+
? { type: 'package', packageName: source.packageName, path: path.basename(source.path) }
|
|
1328
|
+
: { type: 'package', packageName: source.packageName };
|
|
1329
|
+
case 'cli-fetched':
|
|
1330
|
+
return source;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Reduce an absolute SKILL.md path to its last two segments, e.g.
|
|
1335
|
+
* `/path/to/.qodo/skills/code-review/SKILL.md` → `code-review/SKILL.md`.
|
|
1336
|
+
* Non-absolute paths and very short paths pass through unchanged.
|
|
1337
|
+
*/
|
|
1338
|
+
function relativizeSkillPath(absPath) {
|
|
1339
|
+
if (!path.isAbsolute(absPath))
|
|
1340
|
+
return absPath;
|
|
1341
|
+
const segments = absPath.split(/[/\\]/).filter((s) => s.length > 0);
|
|
1342
|
+
if (segments.length <= 2)
|
|
1343
|
+
return segments.join('/');
|
|
1344
|
+
return segments.slice(-2).join('/');
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Build the search-path list for lockfile discovery. Returns:
|
|
1348
|
+
*
|
|
1349
|
+
* 1. `cwd` (highest priority).
|
|
1350
|
+
* 2. The nearest ancestor of `cwd` that contains `agent.toml`. Walks
|
|
1351
|
+
* up at most {@link AGENT_TOML_WALK_DEPTH} levels so the search
|
|
1352
|
+
* is bounded even in deeply-nested directory trees.
|
|
1353
|
+
*
|
|
1354
|
+
* Both directories are searched in order; the first `qodo.lock.yaml`
|
|
1355
|
+
* hit wins. Used by `applyLockfile()` to satisfy the docs' "alongside
|
|
1356
|
+
* agent.toml" semantics.
|
|
1357
|
+
*/
|
|
1358
|
+
async function collectLockfileSearchPaths(cwd) {
|
|
1359
|
+
const out = [cwd];
|
|
1360
|
+
let current = cwd;
|
|
1361
|
+
for (let depth = 0; depth < AGENT_TOML_WALK_DEPTH; depth += 1) {
|
|
1362
|
+
const candidate = path.join(current, 'agent.toml');
|
|
1363
|
+
try {
|
|
1364
|
+
const stat = await fs.stat(candidate);
|
|
1365
|
+
if (stat.isFile()) {
|
|
1366
|
+
// agent.toml lives at `current` — the lockfile would sit next
|
|
1367
|
+
// to it. Add `current` to the search list unless it's already
|
|
1368
|
+
// included as the CWD.
|
|
1369
|
+
if (current !== cwd)
|
|
1370
|
+
out.push(current);
|
|
1371
|
+
return out;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
catch {
|
|
1375
|
+
// Missing agent.toml at this level — keep walking up.
|
|
1376
|
+
}
|
|
1377
|
+
const parent = path.dirname(current);
|
|
1378
|
+
if (parent === current)
|
|
1379
|
+
break; // hit the filesystem root
|
|
1380
|
+
current = parent;
|
|
1381
|
+
}
|
|
1382
|
+
return out;
|
|
1383
|
+
}
|
|
1384
|
+
/** Maximum levels of parent directories the lockfile search walks. */
|
|
1385
|
+
const AGENT_TOML_WALK_DEPTH = 8;
|
|
1386
|
+
//# sourceMappingURL=manager.js.map
|