oc-pilot 0.5.1 → 0.88.1
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/CHANGELOG.md +683 -0
- package/CONTRIBUTING.md +4 -2
- package/README.md +588 -20
- package/README.ru.md +572 -10
- package/dist/cli.js +185 -1
- package/dist/cli.js.map +1 -1
- package/dist/cli.test.js +1211 -1
- package/dist/cli.test.js.map +1 -1
- package/dist/commands/anti-regression.js +97 -0
- package/dist/commands/anti-regression.js.map +1 -0
- package/dist/commands/arch-diff.js +80 -0
- package/dist/commands/arch-diff.js.map +1 -0
- package/dist/commands/authz.js +167 -0
- package/dist/commands/authz.js.map +1 -0
- package/dist/commands/autopsy.js +64 -0
- package/dist/commands/autopsy.js.map +1 -0
- package/dist/commands/bankruptcy.js +60 -0
- package/dist/commands/bankruptcy.js.map +1 -0
- package/dist/commands/battle.js +119 -0
- package/dist/commands/battle.js.map +1 -0
- package/dist/commands/boundaries.js +114 -0
- package/dist/commands/boundaries.js.map +1 -0
- package/dist/commands/bounty.js +56 -0
- package/dist/commands/bounty.js.map +1 -0
- package/dist/commands/brag.js +62 -0
- package/dist/commands/brag.js.map +1 -0
- package/dist/commands/cache.js +62 -0
- package/dist/commands/cache.js.map +1 -0
- package/dist/commands/cinema.js +61 -0
- package/dist/commands/cinema.js.map +1 -0
- package/dist/commands/cleanup.js +110 -0
- package/dist/commands/cleanup.js.map +1 -0
- package/dist/commands/commit.js +177 -0
- package/dist/commands/commit.js.map +1 -0
- package/dist/commands/commit.test.js +160 -0
- package/dist/commands/commit.test.js.map +1 -0
- package/dist/commands/compliance.js +121 -0
- package/dist/commands/compliance.js.map +1 -0
- package/dist/commands/contract-drift.js +97 -0
- package/dist/commands/contract-drift.js.map +1 -0
- package/dist/commands/coverage.js +137 -0
- package/dist/commands/coverage.js.map +1 -0
- package/dist/commands/day.js +116 -0
- package/dist/commands/day.js.map +1 -0
- package/dist/commands/day.test.js +45 -0
- package/dist/commands/day.test.js.map +1 -0
- package/dist/commands/deps.js +245 -0
- package/dist/commands/deps.js.map +1 -0
- package/dist/commands/docgen.js +4 -2
- package/dist/commands/docgen.js.map +1 -1
- package/dist/commands/doctor.js +146 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +69 -0
- package/dist/commands/doctor.test.js.map +1 -0
- package/dist/commands/echo.js +78 -0
- package/dist/commands/echo.js.map +1 -0
- package/dist/commands/ecosystem-verify.js +113 -0
- package/dist/commands/ecosystem-verify.js.map +1 -0
- package/dist/commands/env-sync.js +102 -0
- package/dist/commands/env-sync.js.map +1 -0
- package/dist/commands/explain.js +115 -0
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/fix.js +145 -3
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/flaky.js +111 -0
- package/dist/commands/flaky.js.map +1 -0
- package/dist/commands/fuzz.js +119 -0
- package/dist/commands/fuzz.js.map +1 -0
- package/dist/commands/hivemind.js +124 -0
- package/dist/commands/hivemind.js.map +1 -0
- package/dist/commands/impact.js +73 -0
- package/dist/commands/impact.js.map +1 -0
- package/dist/commands/init.js +73 -11
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +138 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/install-hook.js +161 -0
- package/dist/commands/install-hook.js.map +1 -0
- package/dist/commands/install-hook.test.js +100 -0
- package/dist/commands/install-hook.test.js.map +1 -0
- package/dist/commands/invariants.js +103 -0
- package/dist/commands/invariants.js.map +1 -0
- package/dist/commands/mock-leak.js +97 -0
- package/dist/commands/mock-leak.js.map +1 -0
- package/dist/commands/mutate.js +99 -0
- package/dist/commands/mutate.js.map +1 -0
- package/dist/commands/pack.js +144 -0
- package/dist/commands/pack.js.map +1 -0
- package/dist/commands/preflight.js +137 -0
- package/dist/commands/preflight.js.map +1 -0
- package/dist/commands/query.js +57 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/ready.js +152 -0
- package/dist/commands/ready.js.map +1 -0
- package/dist/commands/receipt.js +289 -0
- package/dist/commands/receipt.js.map +1 -0
- package/dist/commands/replay.js +112 -0
- package/dist/commands/replay.js.map +1 -0
- package/dist/commands/replay.test.js +125 -0
- package/dist/commands/replay.test.js.map +1 -0
- package/dist/commands/restore.js +54 -0
- package/dist/commands/restore.js.map +1 -0
- package/dist/commands/review.js +263 -4
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/review.test.js +160 -0
- package/dist/commands/review.test.js.map +1 -0
- package/dist/commands/reviewers.js +86 -0
- package/dist/commands/reviewers.js.map +1 -0
- package/dist/commands/roast.js +102 -0
- package/dist/commands/roast.js.map +1 -0
- package/dist/commands/sandbox.js +82 -0
- package/dist/commands/sandbox.js.map +1 -0
- package/dist/commands/sast.js +162 -0
- package/dist/commands/sast.js.map +1 -0
- package/dist/commands/scan.js +47 -12
- package/dist/commands/scan.js.map +1 -1
- package/dist/commands/slop.js +145 -0
- package/dist/commands/slop.js.map +1 -0
- package/dist/commands/slop.test.js +135 -0
- package/dist/commands/slop.test.js.map +1 -0
- package/dist/commands/stats.js +61 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/test-gen.js +372 -0
- package/dist/commands/test-gen.js.map +1 -0
- package/dist/commands/test-guard.js +89 -0
- package/dist/commands/test-guard.js.map +1 -0
- package/dist/commands/test-plan.js +133 -0
- package/dist/commands/test-plan.js.map +1 -0
- package/dist/commands/zoom-symbol.js +52 -0
- package/dist/commands/zoom-symbol.js.map +1 -0
- package/dist/core/agent-rules.js +35 -0
- package/dist/core/agent-rules.js.map +1 -0
- package/dist/core/anthropic.test.js +116 -0
- package/dist/core/anthropic.test.js.map +1 -0
- package/dist/core/anti-regression.js +161 -0
- package/dist/core/anti-regression.js.map +1 -0
- package/dist/core/anti-regression.test.js +154 -0
- package/dist/core/anti-regression.test.js.map +1 -0
- package/dist/core/aql.js +220 -0
- package/dist/core/aql.js.map +1 -0
- package/dist/core/aql.test.js +64 -0
- package/dist/core/aql.test.js.map +1 -0
- package/dist/core/arch-diff.js +122 -0
- package/dist/core/arch-diff.js.map +1 -0
- package/dist/core/arch-diff.test.js +93 -0
- package/dist/core/arch-diff.test.js.map +1 -0
- package/dist/core/authz.js +292 -0
- package/dist/core/authz.js.map +1 -0
- package/dist/core/authz.test.js +115 -0
- package/dist/core/authz.test.js.map +1 -0
- package/dist/core/autopsy.js +230 -0
- package/dist/core/autopsy.js.map +1 -0
- package/dist/core/autopsy.test.js +106 -0
- package/dist/core/autopsy.test.js.map +1 -0
- package/dist/core/bankruptcy.js +232 -0
- package/dist/core/bankruptcy.js.map +1 -0
- package/dist/core/bankruptcy.test.js +91 -0
- package/dist/core/bankruptcy.test.js.map +1 -0
- package/dist/core/baseline.js +131 -0
- package/dist/core/baseline.js.map +1 -0
- package/dist/core/baseline.test.js +91 -0
- package/dist/core/baseline.test.js.map +1 -0
- package/dist/core/battle.js +213 -0
- package/dist/core/battle.js.map +1 -0
- package/dist/core/battle.test.js +116 -0
- package/dist/core/battle.test.js.map +1 -0
- package/dist/core/boundaries.js +200 -0
- package/dist/core/boundaries.js.map +1 -0
- package/dist/core/boundaries.test.js +62 -0
- package/dist/core/boundaries.test.js.map +1 -0
- package/dist/core/bounty.js +128 -0
- package/dist/core/bounty.js.map +1 -0
- package/dist/core/bounty.test.js +82 -0
- package/dist/core/bounty.test.js.map +1 -0
- package/dist/core/brag.js +174 -0
- package/dist/core/brag.js.map +1 -0
- package/dist/core/brag.test.js +43 -0
- package/dist/core/brag.test.js.map +1 -0
- package/dist/core/cache.js +189 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/cache.test.js +159 -0
- package/dist/core/cache.test.js.map +1 -0
- package/dist/core/cinema.js +242 -0
- package/dist/core/cinema.js.map +1 -0
- package/dist/core/cinema.test.js +73 -0
- package/dist/core/cinema.test.js.map +1 -0
- package/dist/core/cleanup.js +107 -0
- package/dist/core/cleanup.js.map +1 -0
- package/dist/core/compliance.js +193 -0
- package/dist/core/compliance.js.map +1 -0
- package/dist/core/compliance.test.js +123 -0
- package/dist/core/compliance.test.js.map +1 -0
- package/dist/core/config.js +78 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/config.test.js +66 -0
- package/dist/core/config.test.js.map +1 -1
- package/dist/core/contract-drift.js +296 -0
- package/dist/core/contract-drift.js.map +1 -0
- package/dist/core/contract-drift.test.js +166 -0
- package/dist/core/contract-drift.test.js.map +1 -0
- package/dist/core/cost.test.js +103 -0
- package/dist/core/cost.test.js.map +1 -0
- package/dist/core/coverage.js +335 -0
- package/dist/core/coverage.js.map +1 -0
- package/dist/core/coverage.test.js +190 -0
- package/dist/core/coverage.test.js.map +1 -0
- package/dist/core/deps-fix.js +169 -0
- package/dist/core/deps-fix.js.map +1 -0
- package/dist/core/deps-fix.test.js +176 -0
- package/dist/core/deps-fix.test.js.map +1 -0
- package/dist/core/deps-health.js +353 -0
- package/dist/core/deps-health.js.map +1 -0
- package/dist/core/deps-health.test.js +230 -0
- package/dist/core/deps-health.test.js.map +1 -0
- package/dist/core/deps-why.js +260 -0
- package/dist/core/deps-why.js.map +1 -0
- package/dist/core/deps-why.test.js +175 -0
- package/dist/core/deps-why.test.js.map +1 -0
- package/dist/core/deps.js +179 -0
- package/dist/core/deps.js.map +1 -0
- package/dist/core/deps.test.js +88 -0
- package/dist/core/deps.test.js.map +1 -0
- package/dist/core/diff-source.js +4 -2
- package/dist/core/diff-source.js.map +1 -1
- package/dist/core/echo.js +151 -0
- package/dist/core/echo.js.map +1 -0
- package/dist/core/echo.test.js +90 -0
- package/dist/core/echo.test.js.map +1 -0
- package/dist/core/ecosystem-verify.js +331 -0
- package/dist/core/ecosystem-verify.js.map +1 -0
- package/dist/core/ecosystem-verify.test.js +158 -0
- package/dist/core/ecosystem-verify.test.js.map +1 -0
- package/dist/core/engine-preset.test.js +60 -0
- package/dist/core/engine-preset.test.js.map +1 -0
- package/dist/core/engine.js +278 -32
- package/dist/core/engine.js.map +1 -1
- package/dist/core/engine.test.js +63 -0
- package/dist/core/engine.test.js.map +1 -1
- package/dist/core/env-sync.js +122 -0
- package/dist/core/env-sync.js.map +1 -0
- package/dist/core/env-sync.test.js +99 -0
- package/dist/core/env-sync.test.js.map +1 -0
- package/dist/core/file-context.js +4 -2
- package/dist/core/file-context.js.map +1 -1
- package/dist/core/findings-baseline.js +60 -0
- package/dist/core/findings-baseline.js.map +1 -0
- package/dist/core/findings-baseline.test.js +77 -0
- package/dist/core/findings-baseline.test.js.map +1 -0
- package/dist/core/findings.js +68 -21
- package/dist/core/findings.js.map +1 -1
- package/dist/core/flaky.js +137 -0
- package/dist/core/flaky.js.map +1 -0
- package/dist/core/flaky.test.js +97 -0
- package/dist/core/flaky.test.js.map +1 -0
- package/dist/core/format.js +5 -3
- package/dist/core/format.js.map +1 -1
- package/dist/core/fuzz.js +306 -0
- package/dist/core/fuzz.js.map +1 -0
- package/dist/core/fuzz.test.js +121 -0
- package/dist/core/fuzz.test.js.map +1 -0
- package/dist/core/git.js +22 -0
- package/dist/core/git.js.map +1 -1
- package/dist/core/gitlab.js +176 -0
- package/dist/core/gitlab.js.map +1 -0
- package/dist/core/gitlab.test.js +199 -0
- package/dist/core/gitlab.test.js.map +1 -0
- package/dist/core/hivemind.js +250 -0
- package/dist/core/hivemind.js.map +1 -0
- package/dist/core/hivemind.test.js +79 -0
- package/dist/core/hivemind.test.js.map +1 -0
- package/dist/core/hologram.js +463 -0
- package/dist/core/hologram.js.map +1 -0
- package/dist/core/hologram.test.js +124 -0
- package/dist/core/hologram.test.js.map +1 -0
- package/dist/core/impact.js +240 -0
- package/dist/core/impact.js.map +1 -0
- package/dist/core/impact.test.js +105 -0
- package/dist/core/impact.test.js.map +1 -0
- package/dist/core/import-graph.js +170 -0
- package/dist/core/import-graph.js.map +1 -0
- package/dist/core/import-graph.test.js +52 -0
- package/dist/core/import-graph.test.js.map +1 -0
- package/dist/core/injection.js +55 -0
- package/dist/core/injection.js.map +1 -0
- package/dist/core/injection.test.js +45 -0
- package/dist/core/injection.test.js.map +1 -0
- package/dist/core/invariants.js +286 -0
- package/dist/core/invariants.js.map +1 -0
- package/dist/core/invariants.test.js +87 -0
- package/dist/core/invariants.test.js.map +1 -0
- package/dist/core/last-review.js +92 -0
- package/dist/core/last-review.js.map +1 -0
- package/dist/core/last-review.test.js +97 -0
- package/dist/core/last-review.test.js.map +1 -0
- package/dist/core/lockfile-support.test.js +112 -0
- package/dist/core/lockfile-support.test.js.map +1 -0
- package/dist/core/lockfile.js +284 -0
- package/dist/core/lockfile.js.map +1 -0
- package/dist/core/lockfile.test.js +203 -0
- package/dist/core/lockfile.test.js.map +1 -0
- package/dist/core/mock-leak.js +120 -0
- package/dist/core/mock-leak.js.map +1 -0
- package/dist/core/mock-leak.test.js +79 -0
- package/dist/core/mock-leak.test.js.map +1 -0
- package/dist/core/mutation.js +206 -0
- package/dist/core/mutation.js.map +1 -0
- package/dist/core/mutation.test.js +104 -0
- package/dist/core/mutation.test.js.map +1 -0
- package/dist/core/opencode.js +15 -2
- package/dist/core/opencode.js.map +1 -1
- package/dist/core/opencode.test.js +10 -1
- package/dist/core/opencode.test.js.map +1 -1
- package/dist/core/options.js +51 -3
- package/dist/core/options.js.map +1 -1
- package/dist/core/osv.js +202 -0
- package/dist/core/osv.js.map +1 -0
- package/dist/core/osv.test.js +173 -0
- package/dist/core/osv.test.js.map +1 -0
- package/dist/core/output-format.test.js +102 -0
- package/dist/core/output-format.test.js.map +1 -0
- package/dist/core/pack.js +212 -0
- package/dist/core/pack.js.map +1 -0
- package/dist/core/pack.test.js +129 -0
- package/dist/core/pack.test.js.map +1 -0
- package/dist/core/phantom.js +155 -0
- package/dist/core/phantom.js.map +1 -0
- package/dist/core/phantom.test.js +58 -0
- package/dist/core/phantom.test.js.map +1 -0
- package/dist/core/pr-comment.js +116 -0
- package/dist/core/pr-comment.js.map +1 -0
- package/dist/core/pr-comment.test.js +53 -0
- package/dist/core/pr-comment.test.js.map +1 -0
- package/dist/core/pr-review.js +160 -0
- package/dist/core/pr-review.js.map +1 -0
- package/dist/core/pr-review.test.js +62 -0
- package/dist/core/pr-review.test.js.map +1 -0
- package/dist/core/preflight-run.js +106 -0
- package/dist/core/preflight-run.js.map +1 -0
- package/dist/core/preflight.js +173 -0
- package/dist/core/preflight.js.map +1 -0
- package/dist/core/preflight.test.js +187 -0
- package/dist/core/preflight.test.js.map +1 -0
- package/dist/core/presets.js +408 -0
- package/dist/core/presets.js.map +1 -0
- package/dist/core/presets.test.js +79 -0
- package/dist/core/presets.test.js.map +1 -0
- package/dist/core/prompt.golden.test.js +74 -0
- package/dist/core/prompt.golden.test.js.map +1 -0
- package/dist/core/prompt.js +43 -4
- package/dist/core/prompt.js.map +1 -1
- package/dist/core/prompt.test.js +16 -0
- package/dist/core/prompt.test.js.map +1 -1
- package/dist/core/readiness-fix.js +111 -0
- package/dist/core/readiness-fix.js.map +1 -0
- package/dist/core/readiness-fix.test.js +99 -0
- package/dist/core/readiness-fix.test.js.map +1 -0
- package/dist/core/readiness.js +189 -0
- package/dist/core/readiness.js.map +1 -0
- package/dist/core/readiness.test.js +199 -0
- package/dist/core/readiness.test.js.map +1 -0
- package/dist/core/receipt.js +193 -0
- package/dist/core/receipt.js.map +1 -0
- package/dist/core/receipt.test.js +193 -0
- package/dist/core/receipt.test.js.map +1 -0
- package/dist/core/reviewers.js +179 -0
- package/dist/core/reviewers.js.map +1 -0
- package/dist/core/reviewers.test.js +105 -0
- package/dist/core/reviewers.test.js.map +1 -0
- package/dist/core/roast.js +27 -0
- package/dist/core/roast.js.map +1 -0
- package/dist/core/roast.test.js +19 -0
- package/dist/core/roast.test.js.map +1 -0
- package/dist/core/router.js +8 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/router.test.js +157 -0
- package/dist/core/router.test.js.map +1 -0
- package/dist/core/rules.js +2 -2
- package/dist/core/rules.js.map +1 -1
- package/dist/core/run.js +322 -20
- package/dist/core/run.js.map +1 -1
- package/dist/core/sarif.js +125 -0
- package/dist/core/sarif.js.map +1 -0
- package/dist/core/sarif.test.js +60 -0
- package/dist/core/sarif.test.js.map +1 -0
- package/dist/core/sast.js +324 -0
- package/dist/core/sast.js.map +1 -0
- package/dist/core/sast.test.js +105 -0
- package/dist/core/sast.test.js.map +1 -0
- package/dist/core/sbom.js +121 -0
- package/dist/core/sbom.js.map +1 -0
- package/dist/core/sbom.test.js +81 -0
- package/dist/core/sbom.test.js.map +1 -0
- package/dist/core/second-opinion.js +88 -0
- package/dist/core/second-opinion.js.map +1 -0
- package/dist/core/second-opinion.test.js +89 -0
- package/dist/core/second-opinion.test.js.map +1 -0
- package/dist/core/secrets.js +120 -5
- package/dist/core/secrets.js.map +1 -1
- package/dist/core/secrets.test.js +74 -1
- package/dist/core/secrets.test.js.map +1 -1
- package/dist/core/shadow.js +94 -0
- package/dist/core/shadow.js.map +1 -0
- package/dist/core/shadow.test.js +50 -0
- package/dist/core/shadow.test.js.map +1 -0
- package/dist/core/slicing.js +110 -0
- package/dist/core/slicing.js.map +1 -0
- package/dist/core/slicing.test.js +287 -0
- package/dist/core/slicing.test.js.map +1 -0
- package/dist/core/slop/churn.js +49 -0
- package/dist/core/slop/churn.js.map +1 -0
- package/dist/core/slop/collect.js +186 -0
- package/dist/core/slop/collect.js.map +1 -0
- package/dist/core/slop/dead-exports.js +101 -0
- package/dist/core/slop/dead-exports.js.map +1 -0
- package/dist/core/slop/duplicates.js +155 -0
- package/dist/core/slop/duplicates.js.map +1 -0
- package/dist/core/slop/index.js +72 -0
- package/dist/core/slop/index.js.map +1 -0
- package/dist/core/slop/model.js +2 -0
- package/dist/core/slop/model.js.map +1 -0
- package/dist/core/slop/score.js +185 -0
- package/dist/core/slop/score.js.map +1 -0
- package/dist/core/slop/slop.test.js +160 -0
- package/dist/core/slop/slop.test.js.map +1 -0
- package/dist/core/slop/source.js +129 -0
- package/dist/core/slop/source.js.map +1 -0
- package/dist/core/slop/test-debt.js +178 -0
- package/dist/core/slop/test-debt.js.map +1 -0
- package/dist/core/slop/test-debt.test.js +114 -0
- package/dist/core/slop/test-debt.test.js.map +1 -0
- package/dist/core/slop/todos.js +117 -0
- package/dist/core/slop/todos.js.map +1 -0
- package/dist/core/snapshot.js +214 -0
- package/dist/core/snapshot.js.map +1 -0
- package/dist/core/snapshot.test.js +101 -0
- package/dist/core/snapshot.test.js.map +1 -0
- package/dist/core/stats.js +119 -0
- package/dist/core/stats.js.map +1 -0
- package/dist/core/stats.test.js +86 -0
- package/dist/core/stats.test.js.map +1 -0
- package/dist/core/suppressions.js +96 -0
- package/dist/core/suppressions.js.map +1 -0
- package/dist/core/suppressions.test.js +90 -0
- package/dist/core/suppressions.test.js.map +1 -0
- package/dist/core/test-delta.js +53 -0
- package/dist/core/test-delta.js.map +1 -0
- package/dist/core/test-delta.test.js +41 -0
- package/dist/core/test-delta.test.js.map +1 -0
- package/dist/core/test-plan.js +212 -0
- package/dist/core/test-plan.js.map +1 -0
- package/dist/core/test-plan.test.js +94 -0
- package/dist/core/test-plan.test.js.map +1 -0
- package/dist/core/test-weakening.js +191 -0
- package/dist/core/test-weakening.js.map +1 -0
- package/dist/core/test-weakening.test.js +114 -0
- package/dist/core/test-weakening.test.js.map +1 -0
- package/dist/core/tree-scan.js +77 -0
- package/dist/core/tree-scan.js.map +1 -0
- package/dist/core/tree-scan.test.js +58 -0
- package/dist/core/tree-scan.test.js.map +1 -0
- package/dist/mcp-prompts.test.js +133 -0
- package/dist/mcp-prompts.test.js.map +1 -0
- package/dist/mcp.js +3303 -240
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.test.js +1739 -1
- package/dist/mcp.test.js.map +1 -1
- package/dist/skills.test.js +221 -0
- package/dist/skills.test.js.map +1 -0
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/integrations.md +151 -7
- package/package.json +2 -1
- package/prompts/oc-pilot.en.md +8 -1
- package/prompts/oc-pilot.ru.md +8 -1
- package/server.json +2 -2
- package/skills/agent-self-review/SKILL.md +87 -0
- package/skills/commit-discipline/SKILL.md +86 -0
- package/skills/oc-pilot/SKILL.md +86 -0
- package/skills/secure-diff-gate/SKILL.md +101 -0
- package/skills/stack-review-packs/SKILL.md +76 -0
- package/skills/test-failure-triage/SKILL.md +88 -0
package/dist/mcp.js
CHANGED
|
@@ -1,16 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
2
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
3
3
|
import { randomUUID, timingSafeEqual } from "node:crypto";
|
|
4
|
-
import { existsSync, mkdtempSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { createServer } from "node:http";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
7
|
+
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
8
8
|
import { createInterface } from "node:readline";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { loadConfig } from "./core/config.js";
|
|
11
|
+
import { runGit } from "./core/git.js";
|
|
12
|
+
import { SYSTEM_PROMPT_EN, SYSTEM_PROMPT_RU } from "./core/prompt.js";
|
|
13
|
+
import { loadRules } from "./core/rules.js";
|
|
14
|
+
import { loadSecretIgnore, scanDiff, scanText } from "./core/secrets.js";
|
|
15
|
+
import { patchTargets } from "./core/patch.js";
|
|
10
16
|
import { VERSION } from "./version.js";
|
|
11
17
|
const SERVER_NAME = "oc-pilot";
|
|
12
18
|
const PROTOCOL_VERSION = "2025-06-18";
|
|
13
19
|
const CLI_PATH = join(dirname(fileURLToPath(import.meta.url)), "cli.js");
|
|
20
|
+
/** Model-backed calls allowed per process; 0 disables the limit. */
|
|
21
|
+
let maxToolCalls = 50;
|
|
22
|
+
let toolCallsUsed = 0;
|
|
23
|
+
/** Offline tools never spend the budget and always work. */
|
|
24
|
+
const OFFLINE_TOOLS = new Set(["scan", "stats", "slop", "flaky", "impact", "deps", "coverage", "reviewers", "test_guard", "env_sync", "mock_leak", "ecosystem_verify", "pack", "contract_drift", "anti_regression", "boundaries", "test_plan", "compliance_verify", "brag", "arch_diff", "cinema", "bounty", "autopsy", "echo", "sast", "authz", "preflight", "ready", "receipt_verify"]);
|
|
25
|
+
/** Set only over stdio, where a notification channel exists. */
|
|
26
|
+
let emitNotification;
|
|
27
|
+
let logLevel;
|
|
28
|
+
/**
|
|
29
|
+
* Shared output knobs. Every tool can cap the findings array and ask for a
|
|
30
|
+
* compact summary instead of the full JSON echo.
|
|
31
|
+
*/
|
|
32
|
+
const RESULT_PROPERTIES = {
|
|
33
|
+
max_findings: {
|
|
34
|
+
type: "integer",
|
|
35
|
+
minimum: 1,
|
|
36
|
+
description: "Cap the findings array (default 50); a capped result also carries truncated and totalFindings",
|
|
37
|
+
},
|
|
38
|
+
verbosity: {
|
|
39
|
+
type: "string",
|
|
40
|
+
enum: ["full", "compact"],
|
|
41
|
+
description: "full returns the JSON payload, compact returns a one-line summary and keeps structuredContent",
|
|
42
|
+
},
|
|
43
|
+
};
|
|
14
44
|
const TOOLS = [
|
|
15
45
|
{
|
|
16
46
|
name: "review",
|
|
@@ -21,6 +51,7 @@ const TOOLS = [
|
|
|
21
51
|
additionalProperties: false,
|
|
22
52
|
properties: {
|
|
23
53
|
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs, e.g. src/ or wallet.go" },
|
|
54
|
+
...RESULT_PROPERTIES,
|
|
24
55
|
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
25
56
|
base: { type: "string", description: "Review base...HEAD, e.g. main" },
|
|
26
57
|
staged: { type: "boolean", description: "Only staged changes" },
|
|
@@ -53,6 +84,7 @@ const TOOLS = [
|
|
|
53
84
|
staged: { type: "boolean" },
|
|
54
85
|
base: { type: "string" },
|
|
55
86
|
include_untracked: { type: "boolean" },
|
|
87
|
+
...RESULT_PROPERTIES,
|
|
56
88
|
context: { type: "string" },
|
|
57
89
|
fail_on: { type: "string", enum: ["none", "critical", "warning", "style"] },
|
|
58
90
|
lang: { type: "string", enum: ["en", "ru"] },
|
|
@@ -63,6 +95,40 @@ const TOOLS = [
|
|
|
63
95
|
},
|
|
64
96
|
},
|
|
65
97
|
},
|
|
98
|
+
{
|
|
99
|
+
name: "test_gen",
|
|
100
|
+
description: "Generate tests for the changed code, or only for uncovered changed lines when coverage is requested. Without apply it returns a patch and writes nothing; with apply it writes the test files, runs the suite and reverts failing tests unless keep_failing is set. Model-backed.",
|
|
101
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
102
|
+
inputSchema: {
|
|
103
|
+
type: "object",
|
|
104
|
+
additionalProperties: false,
|
|
105
|
+
properties: {
|
|
106
|
+
apply: { type: "boolean", description: "Write the generated tests; needs --allow-write (default false)" },
|
|
107
|
+
no_run: { type: "boolean", description: "With apply, write tests without running them" },
|
|
108
|
+
keep_failing: { type: "boolean", description: "Keep generated tests even when the suite fails" },
|
|
109
|
+
force: { type: "boolean", description: "Allow editing existing test files" },
|
|
110
|
+
rounds: { type: "integer", minimum: 1, maximum: 5, description: "Generation attempts (default 2)" },
|
|
111
|
+
coverage: { type: "boolean", description: "Target only uncovered changed lines" },
|
|
112
|
+
coverage_file: { type: "string", description: "Coverage report path (lcov, Istanbul, Cobertura or Go)" },
|
|
113
|
+
from_slop: { type: "boolean", description: "Select untested files with the Slop Index instead of the diff" },
|
|
114
|
+
slop_files: { type: "integer", minimum: 1, maximum: 50, description: "How many untested files to target (default 3)" },
|
|
115
|
+
command: { type: "string", description: "Test command; auto-detected when omitted" },
|
|
116
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
117
|
+
base: { type: "string" },
|
|
118
|
+
staged: { type: "boolean" },
|
|
119
|
+
unstaged: { type: "boolean" },
|
|
120
|
+
include_untracked: { type: "boolean" },
|
|
121
|
+
paths: { type: "array", items: { type: "string" } },
|
|
122
|
+
context: { type: "string" },
|
|
123
|
+
...RESULT_PROPERTIES,
|
|
124
|
+
lang: { type: "string", enum: ["en", "ru"] },
|
|
125
|
+
model: { type: "string" },
|
|
126
|
+
timeout: { type: "integer", minimum: 1 },
|
|
127
|
+
repo_context: { type: "boolean" },
|
|
128
|
+
no_secret_scan: { type: "boolean" },
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
66
132
|
{
|
|
67
133
|
name: "docgen",
|
|
68
134
|
description: "Generate GoDoc/RustDoc/JSDoc/docstrings for files or globs. Returns proposed documentation as Markdown and JSON.",
|
|
@@ -116,13 +182,14 @@ const TOOLS = [
|
|
|
116
182
|
},
|
|
117
183
|
{
|
|
118
184
|
name: "fix",
|
|
119
|
-
description: "Ask the model for a unified diff and validate it with git apply. With apply=true, applies the patch and runs tests, reverting on failure and retrying.",
|
|
185
|
+
description: "Ask the model for a unified diff and validate it with git apply. With from_sast or from_authz it targets security findings and rescans after the patch. With apply=true, applies the patch and runs tests, reverting on failure and retrying.",
|
|
120
186
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
121
187
|
inputSchema: {
|
|
122
188
|
type: "object",
|
|
123
189
|
additionalProperties: false,
|
|
124
190
|
properties: {
|
|
125
191
|
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
192
|
+
...RESULT_PROPERTIES,
|
|
126
193
|
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
127
194
|
base: { type: "string" },
|
|
128
195
|
staged: { type: "boolean" },
|
|
@@ -131,6 +198,7 @@ const TOOLS = [
|
|
|
131
198
|
context: { type: "string" },
|
|
132
199
|
command: { type: "string", description: "Test command; auto-detected when omitted" },
|
|
133
200
|
apply: { type: "boolean", description: "Apply the patch and run tests (default false)" },
|
|
201
|
+
shadow: { type: "boolean", description: "Run apply in .oc-pilot/shadow-tree and land the diff only on success" },
|
|
134
202
|
no_tests: { type: "boolean", description: "Apply without running tests" },
|
|
135
203
|
rounds: { type: "integer", minimum: 1, maximum: 5, description: "Fix attempts (default 2)" },
|
|
136
204
|
lang: { type: "string", enum: ["en", "ru"] },
|
|
@@ -138,6 +206,14 @@ const TOOLS = [
|
|
|
138
206
|
timeout: { type: "integer", minimum: 1 },
|
|
139
207
|
repo_context: { type: "boolean" },
|
|
140
208
|
no_secret_scan: { type: "boolean" },
|
|
209
|
+
from_sast: {
|
|
210
|
+
type: "boolean",
|
|
211
|
+
description: "Fix high-severity SAST findings in the changed files and rescan",
|
|
212
|
+
},
|
|
213
|
+
from_authz: {
|
|
214
|
+
type: "boolean",
|
|
215
|
+
description: "Fix high-severity route authorization findings and rescan",
|
|
216
|
+
},
|
|
141
217
|
},
|
|
142
218
|
},
|
|
143
219
|
},
|
|
@@ -150,196 +226,2441 @@ const TOOLS = [
|
|
|
150
226
|
additionalProperties: false,
|
|
151
227
|
properties: {
|
|
152
228
|
paths: { type: "array", items: { type: "string" } },
|
|
229
|
+
...RESULT_PROPERTIES,
|
|
153
230
|
diff: { type: "string", description: "Unified diff text. Omit to use git diff." },
|
|
154
231
|
base: { type: "string" },
|
|
155
232
|
staged: { type: "boolean" },
|
|
156
233
|
unstaged: { type: "boolean" },
|
|
157
234
|
include_untracked: { type: "boolean" },
|
|
158
235
|
strict: { type: "boolean", description: "Fail on any finding, including warnings" },
|
|
236
|
+
all: { type: "boolean", description: "Scan the whole repository instead of a diff" },
|
|
159
237
|
},
|
|
160
238
|
},
|
|
161
239
|
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
// Best-effort temp cleanup.
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
function stringArg(input, name, required = false) {
|
|
185
|
-
const value = input[name];
|
|
186
|
-
if (value === undefined) {
|
|
187
|
-
if (required)
|
|
188
|
-
throw new ToolInputError(`${name} is required`);
|
|
189
|
-
return undefined;
|
|
190
|
-
}
|
|
191
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
192
|
-
throw new ToolInputError(`${name} must be a non-empty string`);
|
|
193
|
-
}
|
|
194
|
-
return value;
|
|
195
|
-
}
|
|
196
|
-
function booleanArg(input, name) {
|
|
197
|
-
const value = input[name];
|
|
198
|
-
if (value === undefined)
|
|
199
|
-
return undefined;
|
|
200
|
-
if (typeof value !== "boolean")
|
|
201
|
-
throw new ToolInputError(`${name} must be a boolean`);
|
|
202
|
-
return value;
|
|
203
|
-
}
|
|
204
|
-
function integerArg(input, name) {
|
|
205
|
-
const value = input[name];
|
|
206
|
-
if (value === undefined)
|
|
207
|
-
return undefined;
|
|
208
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
209
|
-
throw new ToolInputError(`${name} must be a positive integer`);
|
|
210
|
-
}
|
|
211
|
-
return value;
|
|
212
|
-
}
|
|
213
|
-
function stringArrayArg(input, name, required = false) {
|
|
214
|
-
const value = input[name];
|
|
215
|
-
if (value === undefined) {
|
|
216
|
-
if (required)
|
|
217
|
-
throw new ToolInputError(`${name} is required`);
|
|
218
|
-
return [];
|
|
219
|
-
}
|
|
220
|
-
if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
221
|
-
throw new ToolInputError(`${name} must be an array of non-empty strings`);
|
|
222
|
-
}
|
|
223
|
-
return value;
|
|
224
|
-
}
|
|
225
|
-
function projectPaths(input, name) {
|
|
226
|
-
const paths = stringArrayArg(input, name);
|
|
227
|
-
for (const value of paths) {
|
|
228
|
-
if (isAbsolute(value) || /(^|[\\/])\.\.([\\/]|$)/.test(value)) {
|
|
229
|
-
throw new ToolInputError(`${name} must use paths inside the project directory`);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return paths;
|
|
233
|
-
}
|
|
234
|
-
function baseArg(input) {
|
|
235
|
-
const base = stringArg(input, "base");
|
|
236
|
-
if (!base)
|
|
237
|
-
return undefined;
|
|
238
|
-
if (base.startsWith("-") || !/^[A-Za-z0-9._/@{}~^:-]+$/.test(base)) {
|
|
239
|
-
throw new ToolInputError("base must be a git ref, not an option");
|
|
240
|
-
}
|
|
241
|
-
return base;
|
|
242
|
-
}
|
|
243
|
-
function contextArg(input) {
|
|
244
|
-
const context = stringArg(input, "context");
|
|
245
|
-
if (context?.trimStart().startsWith("@")) {
|
|
246
|
-
throw new ToolInputError("context must be literal text, not an @file reference");
|
|
247
|
-
}
|
|
248
|
-
return context;
|
|
249
|
-
}
|
|
250
|
-
function commonArgs(input, options) {
|
|
251
|
-
const args = [];
|
|
252
|
-
const lang = stringArg(input, "lang");
|
|
253
|
-
const model = stringArg(input, "model");
|
|
254
|
-
const timeout = integerArg(input, "timeout");
|
|
255
|
-
const noSecretScan = options.secrets ? booleanArg(input, "no_secret_scan") : undefined;
|
|
256
|
-
const failOn = options.failOn ? stringArg(input, "fail_on") : undefined;
|
|
257
|
-
const repoContext = options.repoContext ? booleanArg(input, "repo_context") : undefined;
|
|
258
|
-
if (model)
|
|
259
|
-
args.push("--model", model);
|
|
260
|
-
if (lang)
|
|
261
|
-
args.push("--lang", lang);
|
|
262
|
-
if (timeout)
|
|
263
|
-
args.push("--timeout", String(timeout));
|
|
264
|
-
if (noSecretScan)
|
|
265
|
-
args.push("--no-secret-scan");
|
|
266
|
-
if (failOn)
|
|
267
|
-
args.push("--fail-on", failOn);
|
|
268
|
-
if (repoContext === false)
|
|
269
|
-
args.push("--no-repo-context");
|
|
270
|
-
return args;
|
|
271
|
-
}
|
|
272
|
-
function runCli(args, cwd) {
|
|
273
|
-
return new Promise((resolvePromise) => {
|
|
274
|
-
const child = spawn(process.execPath, [CLI_PATH, ...args], {
|
|
275
|
-
cwd,
|
|
276
|
-
windowsHide: true,
|
|
277
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
278
|
-
env: {
|
|
279
|
-
...process.env,
|
|
280
|
-
NO_COLOR: "1",
|
|
281
|
-
FORCE_COLOR: "0",
|
|
240
|
+
{
|
|
241
|
+
name: "commit",
|
|
242
|
+
description: "Generate a Conventional Commits message from the staged diff, matching the repository's recent commit style. Set apply to create the commit.",
|
|
243
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
244
|
+
inputSchema: {
|
|
245
|
+
type: "object",
|
|
246
|
+
additionalProperties: false,
|
|
247
|
+
properties: {
|
|
248
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the staged changes." },
|
|
249
|
+
base: { type: "string", description: "Describe base...HEAD instead of staged changes" },
|
|
250
|
+
unstaged: { type: "boolean", description: "Describe unstaged changes instead of staged" },
|
|
251
|
+
include_untracked: { type: "boolean" },
|
|
252
|
+
context: { type: "string", description: "Extra context for the model" },
|
|
253
|
+
allow_secrets: { type: "boolean", description: "Commit even when critical secrets are staged" },
|
|
254
|
+
apply: { type: "boolean", description: "Create the commit with git commit -F" },
|
|
255
|
+
model: { type: "string" },
|
|
256
|
+
timeout: { type: "integer", minimum: 1 },
|
|
257
|
+
lang: { type: "string", enum: ["en", "ru"] },
|
|
282
258
|
},
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
function
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "slop",
|
|
263
|
+
description: "Offline Slop Index 0-100: duplicated blocks, dead exports, churn without tests and the TODO graveyard. Uses git only and never calls a model, so it is free and safe in read-only mode.",
|
|
264
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
265
|
+
inputSchema: {
|
|
266
|
+
type: "object",
|
|
267
|
+
additionalProperties: false,
|
|
268
|
+
properties: {
|
|
269
|
+
since: { type: "string", description: "Compare against a git ref, e.g. origin/main" },
|
|
270
|
+
since_days: { type: "integer", minimum: 1, maximum: 3650, description: "Churn window in days (default 30)" },
|
|
271
|
+
limit: { type: "integer", minimum: 1, maximum: 50, description: "Items per category in the output (default 10)" },
|
|
272
|
+
exclude: { type: "array", items: { type: "string" }, description: "Glob or substring patterns to skip" },
|
|
273
|
+
no_blame: { type: "boolean", description: "Skip git blame and report TODO age as unknown" },
|
|
274
|
+
...RESULT_PROPERTIES,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: "fuzz",
|
|
280
|
+
description: "Safely fuzz exported functions added by the diff with null, -1, empty values, a 10MB string and __proto__ objects. Each function runs in a separate Node process with a hard timeout, a heap cap and mocked fs/net/http/child_process; files that import side-effect modules are blocked instead of executed. No model call.",
|
|
281
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
282
|
+
inputSchema: {
|
|
283
|
+
type: "object",
|
|
284
|
+
additionalProperties: false,
|
|
285
|
+
properties: {
|
|
286
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
287
|
+
base: { type: "string" },
|
|
288
|
+
staged: { type: "boolean" },
|
|
289
|
+
unstaged: { type: "boolean" },
|
|
290
|
+
include_untracked: { type: "boolean" },
|
|
291
|
+
duration_ms: { type: "integer", minimum: 100, maximum: 60000, description: "Total fuzz budget (default 2000)" },
|
|
292
|
+
memory_mb: { type: "integer", minimum: 16, maximum: 4096, description: "Heap cap per worker (default 128)" },
|
|
293
|
+
max_targets: { type: "integer", minimum: 1, maximum: 50, description: "Maximum functions to fuzz (default 10)" },
|
|
294
|
+
fail_on_crash: { type: "boolean", description: "Return exit code 1 when a crash is found" },
|
|
295
|
+
...RESULT_PROPERTIES,
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "mutate",
|
|
301
|
+
description: "Offline mutation testing for changed code: flips comparison and boolean operators, runs the test command after each mutation, restores the file and reports survivors. Mutants are capped by max_mutants; a score below min_score returns exit code 1. Temporarily rewrites source files, so it is disabled in read-only mode.",
|
|
302
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
303
|
+
inputSchema: {
|
|
304
|
+
type: "object",
|
|
305
|
+
additionalProperties: false,
|
|
306
|
+
properties: {
|
|
307
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs; defaults to all changed code files" },
|
|
308
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
309
|
+
base: { type: "string", description: "Mutate git diff <base>...HEAD, e.g. origin/main" },
|
|
310
|
+
staged: { type: "boolean", description: "Mutate staged changes" },
|
|
311
|
+
unstaged: { type: "boolean", description: "Mutate unstaged changes" },
|
|
312
|
+
include_untracked: { type: "boolean", description: "Include untracked files when diffing" },
|
|
313
|
+
command: { type: "string", description: "Test command; auto-detected when omitted" },
|
|
314
|
+
max_mutants: {
|
|
315
|
+
type: "integer",
|
|
316
|
+
minimum: 1,
|
|
317
|
+
maximum: 500,
|
|
318
|
+
description: "How many mutations to try (default 20)",
|
|
319
|
+
},
|
|
320
|
+
min_score: {
|
|
321
|
+
type: "integer",
|
|
322
|
+
minimum: 0,
|
|
323
|
+
maximum: 100,
|
|
324
|
+
description: "Return exit code 1 when the mutation score is below this value",
|
|
325
|
+
},
|
|
326
|
+
...RESULT_PROPERTIES,
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: "flaky",
|
|
332
|
+
description: "Run the test command several times and compare per-test results, flagging tests that pass in some runs and fail in others. With shuffle=true it reorders test files using a reproducible seed. It never edits source files itself, but the test command may write build artifacts. Offline and free of model calls; fail_on_flaky returns exit code 1 when a flaky test is found.",
|
|
333
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
334
|
+
inputSchema: {
|
|
335
|
+
type: "object",
|
|
336
|
+
additionalProperties: false,
|
|
337
|
+
properties: {
|
|
338
|
+
runs: { type: "integer", minimum: 1, maximum: 10, description: "How many times to run the tests (default 3)" },
|
|
339
|
+
command: { type: "string", description: "Test command; auto-detected when omitted" },
|
|
340
|
+
fail_on_flaky: { type: "boolean", description: "Return exit code 1 when any test is flaky" },
|
|
341
|
+
shuffle: { type: "boolean", description: "Shuffle test files between runs; the command must contain {files}" },
|
|
342
|
+
seed: { type: "string", description: "Shuffle seed; random when omitted" },
|
|
343
|
+
...RESULT_PROPERTIES,
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: "impact",
|
|
349
|
+
description: "Show which declarations the diff changed or removed and where else those names are used. References in untouched files are the call sites most likely to break; the search is textual, so comments and strings can match. Offline and free of model calls.",
|
|
350
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
351
|
+
inputSchema: {
|
|
352
|
+
type: "object",
|
|
353
|
+
additionalProperties: false,
|
|
354
|
+
properties: {
|
|
355
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
356
|
+
base: { type: "string", description: "Analyze git diff <base>...HEAD, e.g. origin/main" },
|
|
357
|
+
staged: { type: "boolean", description: "Analyze staged changes" },
|
|
358
|
+
unstaged: { type: "boolean", description: "Analyze unstaged changes" },
|
|
359
|
+
include_untracked: { type: "boolean", description: "Include untracked files when diffing" },
|
|
360
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
361
|
+
...RESULT_PROPERTIES,
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: "deps",
|
|
367
|
+
description: "Audit declared and imported dependencies: phantom imports, unused or missing packages, floating versions and lockfile health. With audit=true it also queries OSV for vulnerabilities and checks licenses; offline=true keeps the audit to the local advisory cache. fix=true rewrites package.json and sbom writes a file, so both need --allow-write. No model call.",
|
|
368
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
369
|
+
inputSchema: {
|
|
370
|
+
type: "object",
|
|
371
|
+
additionalProperties: false,
|
|
372
|
+
properties: {
|
|
373
|
+
why: { type: "string", description: "Explain where this package comes from instead of auditing" },
|
|
374
|
+
audit: { type: "boolean", description: "Query OSV for vulnerabilities and audit licenses" },
|
|
375
|
+
offline: { type: "boolean", description: "Keep the audit offline; requires audit" },
|
|
376
|
+
osv_url: { type: "string", description: "Override the OSV API base URL; requires audit" },
|
|
377
|
+
fail_on_issue: {
|
|
378
|
+
type: "string",
|
|
379
|
+
enum: ["high", "medium", "low"],
|
|
380
|
+
description: "Lowest dependency issue severity that fails the run",
|
|
381
|
+
},
|
|
382
|
+
fail_on_vuln: {
|
|
383
|
+
type: "string",
|
|
384
|
+
enum: ["critical", "high", "medium", "low"],
|
|
385
|
+
description: "Lowest vulnerability severity that fails the run; requires audit",
|
|
386
|
+
},
|
|
387
|
+
ignore: { type: "array", items: { type: "string" }, description: "Package names to ignore" },
|
|
388
|
+
fix: { type: "boolean", description: "Apply safe package.json fixes; needs --allow-write" },
|
|
389
|
+
sbom: { type: "string", description: "Write a CycloneDX SBOM to this path; needs --allow-write" },
|
|
390
|
+
...RESULT_PROPERTIES,
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: "coverage",
|
|
396
|
+
description: "Changed-line coverage from a local lcov, Istanbul, Cobertura or Go report: reports uncovered changed lines and files with no coverage data. Gate on the covered percentage or the number of uncovered lines. Offline and free of model calls.",
|
|
397
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
398
|
+
inputSchema: {
|
|
399
|
+
type: "object",
|
|
400
|
+
additionalProperties: false,
|
|
401
|
+
properties: {
|
|
402
|
+
coverage_file: {
|
|
403
|
+
type: "string",
|
|
404
|
+
description: "Coverage report path; auto-detected from coverage/lcov.info and other common paths",
|
|
405
|
+
},
|
|
406
|
+
fail_under: {
|
|
407
|
+
type: "integer",
|
|
408
|
+
minimum: 1,
|
|
409
|
+
maximum: 100,
|
|
410
|
+
description: "Return exit code 1 when less than this percentage of changed executable lines is covered",
|
|
411
|
+
},
|
|
412
|
+
max_uncovered: {
|
|
413
|
+
type: "integer",
|
|
414
|
+
minimum: 0,
|
|
415
|
+
description: "Return exit code 1 when more changed lines are uncovered",
|
|
416
|
+
},
|
|
417
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
418
|
+
base: { type: "string", description: "Check git diff <base>...HEAD, e.g. origin/main" },
|
|
419
|
+
staged: { type: "boolean", description: "Check staged changes" },
|
|
420
|
+
unstaged: { type: "boolean", description: "Check unstaged changes" },
|
|
421
|
+
include_untracked: { type: "boolean", description: "Include untracked files when diffing" },
|
|
422
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
423
|
+
...RESULT_PROPERTIES,
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
name: "reviewers",
|
|
429
|
+
description: "Suggest reviewers for the changed files from git history and CODEOWNERS. Ranks authors by commits touching those files, filters bots and excluded logins, and lists CODEOWNERS owners. Offline, read-only and free of model calls.",
|
|
430
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
431
|
+
inputSchema: {
|
|
432
|
+
type: "object",
|
|
433
|
+
additionalProperties: false,
|
|
434
|
+
properties: {
|
|
435
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
436
|
+
base: { type: "string", description: "Review git diff <base>...HEAD, e.g. origin/main" },
|
|
437
|
+
staged: { type: "boolean", description: "Only staged changes" },
|
|
438
|
+
unstaged: { type: "boolean", description: "Only unstaged changes" },
|
|
439
|
+
include_untracked: { type: "boolean", description: "Include untracked files" },
|
|
440
|
+
limit: { type: "integer", minimum: 1, maximum: 20, description: "How many reviewers to return (default 3)" },
|
|
441
|
+
depth: { type: "integer", minimum: 1, maximum: 5000, description: "How many commits to scan (default 50)" },
|
|
442
|
+
exclude: { type: "array", items: { type: "string" }, description: "Logins, names or emails to skip" },
|
|
443
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
444
|
+
...RESULT_PROPERTIES,
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
name: "test_guard",
|
|
450
|
+
description: "Offline guard against weakened tests in a diff: skipped or focused tests, removed assertions, commented checks, raised timeouts, strict matchers replaced by existence checks, @ts-ignore, empty catch blocks and deleted tests. Read-only and free of model calls; findings do not fail unless fail_on_weakening is set.",
|
|
451
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
452
|
+
inputSchema: {
|
|
453
|
+
type: "object",
|
|
454
|
+
additionalProperties: false,
|
|
455
|
+
properties: {
|
|
456
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
457
|
+
base: { type: "string", description: "Check git diff <base>...HEAD, e.g. origin/main" },
|
|
458
|
+
staged: { type: "boolean", description: "Only staged changes" },
|
|
459
|
+
unstaged: { type: "boolean", description: "Only unstaged changes" },
|
|
460
|
+
include_untracked: { type: "boolean", description: "Include untracked files" },
|
|
461
|
+
fail_on_weakening: { type: "boolean", description: "Return exit code 1 when any weakening is found" },
|
|
462
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
463
|
+
...RESULT_PROPERTIES,
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: "env_sync",
|
|
469
|
+
description: "Offline audit of environment variables added by the diff: every process.env, import.meta.env, Deno.env and Bun.env reference is checked against the local .env files and the .env.example/.sample/.template documentation. Reports variables that are referenced but not defined and ones defined but not documented. Read-only and free of model calls; missing variables return exit code 1 only when fail_on_missing is set.",
|
|
470
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
471
|
+
inputSchema: {
|
|
472
|
+
type: "object",
|
|
473
|
+
additionalProperties: false,
|
|
474
|
+
properties: {
|
|
475
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
476
|
+
base: { type: "string", description: "Check git diff <base>...HEAD, e.g. origin/main" },
|
|
477
|
+
staged: { type: "boolean", description: "Only staged changes" },
|
|
478
|
+
unstaged: { type: "boolean", description: "Only unstaged changes" },
|
|
479
|
+
include_untracked: { type: "boolean", description: "Include untracked files" },
|
|
480
|
+
fail_on_missing: { type: "boolean", description: "Return exit code 1 when a referenced variable is not defined" },
|
|
481
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
482
|
+
...RESULT_PROPERTIES,
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
name: "mock_leak",
|
|
488
|
+
description: "Offline scan of added production code for forgotten stubs and hardcode: local paths such as C:/Users or /home, test tokens, always-true and always-false conditions, mock/fake/stub identifiers, mock comments and localhost URLs. Test, generated and documentation files are skipped. Read-only and free of model calls; findings at or above fail_on return exit code 1.",
|
|
489
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
490
|
+
inputSchema: {
|
|
491
|
+
type: "object",
|
|
492
|
+
additionalProperties: false,
|
|
493
|
+
properties: {
|
|
494
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
495
|
+
base: { type: "string", description: "Check git diff <base>...HEAD, e.g. origin/main" },
|
|
496
|
+
staged: { type: "boolean", description: "Only staged changes" },
|
|
497
|
+
unstaged: { type: "boolean", description: "Only unstaged changes" },
|
|
498
|
+
include_untracked: { type: "boolean", description: "Include untracked files" },
|
|
499
|
+
fail_on_leak: {
|
|
500
|
+
type: "string",
|
|
501
|
+
enum: ["high", "medium", "low"],
|
|
502
|
+
description: "Lowest severity that returns exit code 1",
|
|
503
|
+
},
|
|
504
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
505
|
+
...RESULT_PROPERTIES,
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
name: "ecosystem_verify",
|
|
511
|
+
description: "Check that named imports from changed files exist in the installed package entry or type declarations, and flag packages that are neither installed nor declared. With online=true it also checks newly added npm packages for first-publish age and weekly downloads. Offline by default; findings at or above fail_on_issue return exit code 1.",
|
|
512
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
513
|
+
inputSchema: {
|
|
514
|
+
type: "object",
|
|
515
|
+
additionalProperties: false,
|
|
516
|
+
properties: {
|
|
517
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
518
|
+
base: { type: "string", description: "Check git diff <base>...HEAD, e.g. origin/main" },
|
|
519
|
+
staged: { type: "boolean", description: "Only staged changes" },
|
|
520
|
+
unstaged: { type: "boolean", description: "Only unstaged changes" },
|
|
521
|
+
include_untracked: { type: "boolean", description: "Include untracked files" },
|
|
522
|
+
online: { type: "boolean", description: "Check age and downloads of newly added npm packages" },
|
|
523
|
+
min_age_days: { type: "integer", minimum: 1, maximum: 3650, description: "Minimum package age in days (default 30)" },
|
|
524
|
+
min_downloads: {
|
|
525
|
+
type: "integer",
|
|
526
|
+
minimum: 1,
|
|
527
|
+
maximum: 1_000_000_000,
|
|
528
|
+
description: "Minimum weekly downloads (default 1000)",
|
|
529
|
+
},
|
|
530
|
+
fail_on_issue: {
|
|
531
|
+
type: "string",
|
|
532
|
+
enum: ["high", "medium", "low"],
|
|
533
|
+
description: "Lowest severity that returns exit code 1",
|
|
534
|
+
},
|
|
535
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
536
|
+
...RESULT_PROPERTIES,
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
name: "pack",
|
|
542
|
+
description: "Build a compact Markdown context bundle for a task: project rules, changed-file signatures, related references and an optional test failure excerpt, capped by budget characters. Offline and free of model calls.",
|
|
543
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
544
|
+
inputSchema: {
|
|
545
|
+
type: "object",
|
|
546
|
+
additionalProperties: false,
|
|
547
|
+
required: ["task"],
|
|
548
|
+
properties: {
|
|
549
|
+
task: { type: "string", description: "What the agent is trying to do" },
|
|
550
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
551
|
+
base: { type: "string", description: "Use git diff <base>...HEAD, e.g. origin/main" },
|
|
552
|
+
staged: { type: "boolean", description: "Use staged changes" },
|
|
553
|
+
unstaged: { type: "boolean", description: "Use unstaged changes" },
|
|
554
|
+
include_untracked: { type: "boolean", description: "Include untracked files when diffing" },
|
|
555
|
+
budget: { type: "integer", minimum: 200, description: "Maximum bundle size in characters (default 20000)" },
|
|
556
|
+
test_log: { type: "string", description: "Path to captured test output to append" },
|
|
557
|
+
rules: { type: "string", description: "Path to a rules file; defaults to .oc-pilot/rules.md" },
|
|
558
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
559
|
+
...RESULT_PROPERTIES,
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
name: "pack_hologram",
|
|
565
|
+
description: "Build a layered semantic hologram of the repository: LOD 0 keeps full text for files in the diff, LOD 1 keeps direct imports with function bodies replaced by markers and EFFECTS hints, LOD 2 lists export names only. Unbalanced braces fall back to full text with [Hologram: parse bailout]. The final Markdown is secret-scanned and redacted. Offline and free of model calls.",
|
|
566
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
567
|
+
inputSchema: {
|
|
568
|
+
type: "object",
|
|
569
|
+
additionalProperties: false,
|
|
570
|
+
properties: {
|
|
571
|
+
task: { type: "string", description: "What the agent is trying to do" },
|
|
572
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
573
|
+
base: { type: "string", description: "Use git diff <base>...HEAD, e.g. origin/main" },
|
|
574
|
+
staged: { type: "boolean", description: "Use staged changes" },
|
|
575
|
+
unstaged: { type: "boolean", description: "Use unstaged changes" },
|
|
576
|
+
include_untracked: { type: "boolean", description: "Include untracked files when diffing" },
|
|
577
|
+
max_tokens: { type: "integer", minimum: 1000, maximum: 200000, description: "Hologram token budget (default 30000)" },
|
|
578
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
579
|
+
...RESULT_PROPERTIES,
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: "zoom_symbol",
|
|
585
|
+
description: "Return the full body of one function, class or arrow function from a file. This is the escape hatch for a hologram whose LOD 1 bodies are hidden; it reads only that file and never calls a model.",
|
|
586
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
587
|
+
inputSchema: {
|
|
588
|
+
type: "object",
|
|
589
|
+
additionalProperties: false,
|
|
590
|
+
required: ["file", "symbol"],
|
|
591
|
+
properties: {
|
|
592
|
+
file: { type: "string", description: "Repository-relative file path" },
|
|
593
|
+
symbol: { type: "string", description: "Exported symbol name from the Zoom section" },
|
|
594
|
+
...RESULT_PROPERTIES,
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
name: "invariants",
|
|
600
|
+
description: "Check the diff against .oc-pilot/invariants.md: area-scoped rules, [CRITICAL] markers and a strict JSON model answer, cached by the hash of diff and invariants. Out-of-scope rules are skipped without a model call. Violations are warnings by default; a CRITICAL violation or strict=true returns exit code 3.",
|
|
601
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
602
|
+
inputSchema: {
|
|
603
|
+
type: "object",
|
|
604
|
+
additionalProperties: false,
|
|
605
|
+
properties: {
|
|
606
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
607
|
+
base: { type: "string", description: "Check git diff <base>...HEAD" },
|
|
608
|
+
staged: { type: "boolean", description: "Check staged changes" },
|
|
609
|
+
unstaged: { type: "boolean", description: "Check unstaged changes" },
|
|
610
|
+
include_untracked: { type: "boolean", description: "Include untracked files when diffing" },
|
|
611
|
+
strict: { type: "boolean", description: "Return exit code 3 for any violation, not only CRITICAL" },
|
|
612
|
+
fail_on: { type: "string", enum: ["violation"], description: "Alias for strict" },
|
|
613
|
+
no_cache: { type: "boolean", description: "Bypass the local verdict cache" },
|
|
614
|
+
...RESULT_PROPERTIES,
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
name: "bankruptcy",
|
|
620
|
+
description: "Forecast technical debt from the last N commits: weakened tests, SAST high/medium findings, added TODOs, churn and a slop proxy, smoothed with a 14-day moving average and fitted with linear regression. Returns the current score, slope, forecast date and a self-contained SVG chart. Fewer than 10 commits returns [INSUFFICIENT DATA: Need at least 10 commits to compute Slop Velocity]. No model call and no network.",
|
|
621
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
622
|
+
inputSchema: {
|
|
623
|
+
type: "object",
|
|
624
|
+
additionalProperties: false,
|
|
625
|
+
properties: {
|
|
626
|
+
history_days: { type: "integer", minimum: 1, maximum: 3650, description: "History window in days (default 90)" },
|
|
627
|
+
explain: { type: "boolean", description: "Include the formula, weights and smoothing rules" },
|
|
628
|
+
...RESULT_PROPERTIES,
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
name: "query_ast",
|
|
634
|
+
description: "Run a chainable AQL query against a lightweight code graph: find(File|Function|Class|Import|CallExpression), where(field, value), sort(field), limit(n), select(fields...), count(). No SQL parser, no model call and no network.",
|
|
635
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
636
|
+
inputSchema: {
|
|
637
|
+
type: "object",
|
|
638
|
+
additionalProperties: false,
|
|
639
|
+
required: ["query"],
|
|
640
|
+
properties: {
|
|
641
|
+
query: { type: "string", description: "AQL chain, e.g. find(CallExpression).where(name, 'stripe').select(file, line)" },
|
|
642
|
+
limit: { type: "integer", minimum: 1, maximum: 1000, description: "Maximum rows (default 200)" },
|
|
643
|
+
...RESULT_PROPERTIES,
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
name: "sandbox",
|
|
649
|
+
description: "Run one self-contained JavaScript or ESM file in node:vm with a separate context: require, static imports, fetch, process.exit and long timers are blocked, console is captured, and execution is capped by timeout and heap. TypeScript runs only from a built dist/*.js. This is not an OS sandbox for native code.",
|
|
650
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
651
|
+
inputSchema: {
|
|
652
|
+
type: "object",
|
|
653
|
+
additionalProperties: false,
|
|
654
|
+
required: ["file"],
|
|
655
|
+
properties: {
|
|
656
|
+
file: { type: "string", description: "Repository-relative JS/MJS file (or built dist JS)" },
|
|
657
|
+
timeout_ms: { type: "integer", minimum: 50, maximum: 30000, description: "Execution timeout (default 2000)" },
|
|
658
|
+
memory_mb: { type: "integer", minimum: 16, maximum: 4096, description: "Heap cap (default 128)" },
|
|
659
|
+
allow_unsafe_exec: { type: "boolean", description: "Explicit acknowledgement for native code; not used by the vm runner" },
|
|
660
|
+
...RESULT_PROPERTIES,
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
name: "hivemind_status",
|
|
666
|
+
description: "Report the local hivemind identity fingerprint and the number of explicitly trusted peers. No network call and no model call; use it before starting a mesh or sending a diff.",
|
|
667
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
668
|
+
inputSchema: {
|
|
669
|
+
type: "object",
|
|
670
|
+
additionalProperties: false,
|
|
671
|
+
properties: {
|
|
672
|
+
...RESULT_PROPERTIES,
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
name: "contract_drift",
|
|
678
|
+
description: "Detect implicit contract breakage in the diff: removed fields, optional fields that became required and changed field types in TypeScript interfaces, Zod objects, Prisma models, Pydantic models and JSON Schema. Reports where a removed field is still used in untouched files. Textual, offline and free of model calls.",
|
|
679
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
680
|
+
inputSchema: {
|
|
681
|
+
type: "object",
|
|
682
|
+
additionalProperties: false,
|
|
683
|
+
properties: {
|
|
684
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
685
|
+
base: { type: "string", description: "Check git diff <base>...HEAD, e.g. origin/main" },
|
|
686
|
+
staged: { type: "boolean", description: "Only staged changes" },
|
|
687
|
+
unstaged: { type: "boolean", description: "Only unstaged changes" },
|
|
688
|
+
include_untracked: { type: "boolean", description: "Include untracked files" },
|
|
689
|
+
fail_on_drift: {
|
|
690
|
+
type: "string",
|
|
691
|
+
enum: ["high", "medium"],
|
|
692
|
+
description: "Lowest severity that returns exit code 1",
|
|
693
|
+
},
|
|
694
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
695
|
+
...RESULT_PROPERTIES,
|
|
696
|
+
},
|
|
697
|
+
},
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
name: "anti_regression",
|
|
701
|
+
description: "Compare added lines with lines a human explicitly deleted in git history and cite the deleting commit. Skips tests and generated files, so it catches logic and imports re-added against an earlier decision. Offline and free of model calls; fail_on_regression returns exit code 1 when a match is found.",
|
|
702
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
703
|
+
inputSchema: {
|
|
704
|
+
type: "object",
|
|
705
|
+
additionalProperties: false,
|
|
706
|
+
properties: {
|
|
707
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
708
|
+
base: { type: "string", description: "Check git diff <base>...HEAD, e.g. origin/main" },
|
|
709
|
+
staged: { type: "boolean", description: "Only staged changes" },
|
|
710
|
+
unstaged: { type: "boolean", description: "Only unstaged changes" },
|
|
711
|
+
include_untracked: { type: "boolean", description: "Include untracked files" },
|
|
712
|
+
max_commits: {
|
|
713
|
+
type: "integer",
|
|
714
|
+
minimum: 1,
|
|
715
|
+
maximum: 5000,
|
|
716
|
+
description: "How far back to read git history (default 200)",
|
|
717
|
+
},
|
|
718
|
+
since: { type: "string", description: "Only inspect deletions since this date, e.g. 2025-01-01" },
|
|
719
|
+
fail_on_regression: { type: "boolean", description: "Return exit code 1 when a re-added line is found" },
|
|
720
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
721
|
+
...RESULT_PROPERTIES,
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
name: "boundaries",
|
|
727
|
+
description: "Check added imports against .oc-pilot/architecture.json layer rules: a file matched by a rule's from pattern must not import anything matched by its deny list. Reports file, line, import and rule reason. Offline and free of model calls; fail_on_violation returns exit code 1 on any violation.",
|
|
728
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
729
|
+
inputSchema: {
|
|
730
|
+
type: "object",
|
|
731
|
+
additionalProperties: false,
|
|
732
|
+
properties: {
|
|
733
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
734
|
+
base: { type: "string", description: "Check git diff <base>...HEAD, e.g. origin/main" },
|
|
735
|
+
staged: { type: "boolean", description: "Only staged changes" },
|
|
736
|
+
unstaged: { type: "boolean", description: "Only unstaged changes" },
|
|
737
|
+
include_untracked: { type: "boolean", description: "Include untracked files" },
|
|
738
|
+
config: {
|
|
739
|
+
type: "string",
|
|
740
|
+
description: "Architecture config path; defaults to .oc-pilot/architecture.json",
|
|
741
|
+
},
|
|
742
|
+
fail_on_violation: { type: "boolean", description: "Return exit code 1 when a boundary is crossed" },
|
|
743
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
744
|
+
...RESULT_PROPERTIES,
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
name: "test_plan",
|
|
750
|
+
description: "Select the test files a change can affect using the repository's import graph: changed tests, direct and transitive importers, naming conventions and symbol references. Reports changed files with no test as uncovered. Offline and free of model calls; fail_on_uncovered returns exit code 1.",
|
|
751
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
752
|
+
inputSchema: {
|
|
753
|
+
type: "object",
|
|
754
|
+
additionalProperties: false,
|
|
755
|
+
properties: {
|
|
756
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
757
|
+
base: { type: "string", description: "Plan git diff <base>...HEAD, e.g. origin/main" },
|
|
758
|
+
staged: { type: "boolean", description: "Only staged changes" },
|
|
759
|
+
unstaged: { type: "boolean", description: "Only unstaged changes" },
|
|
760
|
+
include_untracked: { type: "boolean", description: "Include untracked files" },
|
|
761
|
+
strategy: {
|
|
762
|
+
type: "string",
|
|
763
|
+
enum: ["affected", "fast", "full"],
|
|
764
|
+
description: "affected keeps direct and transitive matches, fast only high confidence, full returns every test",
|
|
765
|
+
},
|
|
766
|
+
max_tests: { type: "integer", minimum: 1, description: "Truncate the plan after this many tests" },
|
|
767
|
+
fail_on_uncovered: { type: "boolean", description: "Return exit code 1 when a changed file has no test" },
|
|
768
|
+
coverage_file: { type: "string", description: "Optional coverage report to mark changed lines without tests" },
|
|
769
|
+
paths: { type: "array", items: { type: "string" }, description: "Optional pathspecs" },
|
|
770
|
+
...RESULT_PROPERTIES,
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
name: "compliance_verify",
|
|
776
|
+
description: "Verify the signed compliance manifest for the current commit: recompute its SHA-256 hash, compare the recorded commit with HEAD, check the previous-manifest hash chain and the Ed25519 signature. Read-only and offline. A missing, edited or broken-chain manifest returns exit code 1 with ok=false, not a tool error.",
|
|
777
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
778
|
+
inputSchema: {
|
|
779
|
+
type: "object",
|
|
780
|
+
additionalProperties: false,
|
|
781
|
+
properties: {
|
|
782
|
+
manifest: { type: "string", description: "Path to a manifest; defaults to .oc-pilot/compliance/latest.json" },
|
|
783
|
+
public_key: { type: "string", description: "Public key file for the signature check; defaults to .oc-pilot/receipt.pub" },
|
|
784
|
+
...RESULT_PROPERTIES,
|
|
785
|
+
},
|
|
786
|
+
},
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
name: "brag",
|
|
790
|
+
description: "Build a self-contained HTML or SVG session report from local stats, git history, the last review and the receipt: commits, lines, runs, model calls, cache hits, findings and proof. No network, no external assets, no model call. With no_paths it hides file paths and finding locations.",
|
|
791
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
792
|
+
inputSchema: {
|
|
793
|
+
type: "object",
|
|
794
|
+
additionalProperties: false,
|
|
795
|
+
properties: {
|
|
796
|
+
title: { type: "string", description: "Report title" },
|
|
797
|
+
watermark: { type: "string", description: "Footer watermark" },
|
|
798
|
+
no_paths: { type: "boolean", description: "Hide file paths and finding locations" },
|
|
799
|
+
since_days: { type: "integer", minimum: 1, maximum: 365, description: "History window in days (default 7)" },
|
|
800
|
+
svg: { type: "boolean", description: "Return standalone SVG instead of HTML" },
|
|
801
|
+
...RESULT_PROPERTIES,
|
|
802
|
+
},
|
|
803
|
+
},
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
name: "battle",
|
|
807
|
+
description: "Run an experimental two-agent hardening loop: the coder proposes a patch (and applies it with apply=true), then the hacker reviews it and runs SAST, authorization and test-weakening checks. Repeats until no high findings remain and tests pass. Needs a configured model; apply=true needs --allow-write.",
|
|
808
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
809
|
+
inputSchema: {
|
|
810
|
+
type: "object",
|
|
811
|
+
additionalProperties: false,
|
|
812
|
+
required: ["task"],
|
|
813
|
+
properties: {
|
|
814
|
+
task: { type: "string", description: "What the coder should implement or fix" },
|
|
815
|
+
apply: { type: "boolean", description: "Apply each patch and run tests (default false)" },
|
|
816
|
+
shadow: { type: "boolean", description: "Run apply in .oc-pilot/shadow-tree and land the diff only on success" },
|
|
817
|
+
rounds: { type: "integer", minimum: 1, maximum: 5, description: "Maximum coder/hacker rounds (default 2)" },
|
|
818
|
+
command: { type: "string", description: "Test command; auto-detected when omitted" },
|
|
819
|
+
context: { type: "string", description: "Extra context for both agents" },
|
|
820
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
821
|
+
base: { type: "string" },
|
|
822
|
+
staged: { type: "boolean" },
|
|
823
|
+
unstaged: { type: "boolean" },
|
|
824
|
+
include_untracked: { type: "boolean" },
|
|
825
|
+
model: { type: "string" },
|
|
826
|
+
timeout: { type: "integer", minimum: 1 },
|
|
827
|
+
...RESULT_PROPERTIES,
|
|
828
|
+
},
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
name: "cleanup",
|
|
833
|
+
description: "Build an offline cleanup plan from dependency issues and safe package.json fixes, Slop Index, environment variables missing from .env/.env.example, and weakened tests. With apply=true it applies only the safe dependency fixes. No model call; apply needs --allow-write.",
|
|
834
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
835
|
+
inputSchema: {
|
|
836
|
+
type: "object",
|
|
837
|
+
additionalProperties: false,
|
|
838
|
+
properties: {
|
|
839
|
+
apply: { type: "boolean", description: "Apply only the safe dependency fixes (default false)" },
|
|
840
|
+
fail_on_task: {
|
|
841
|
+
type: "string",
|
|
842
|
+
enum: ["high", "medium", "low"],
|
|
843
|
+
description: "Return exit code 1 when a task at or above this severity remains",
|
|
844
|
+
},
|
|
845
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
846
|
+
base: { type: "string" },
|
|
847
|
+
staged: { type: "boolean" },
|
|
848
|
+
unstaged: { type: "boolean" },
|
|
849
|
+
include_untracked: { type: "boolean" },
|
|
850
|
+
paths: { type: "array", items: { type: "string" } },
|
|
851
|
+
...RESULT_PROPERTIES,
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
name: "arch_diff",
|
|
857
|
+
description: "Render a Mermaid diagram of architectural import changes in the diff: added internal dependencies in green and removed ones in red. Uses the static import graph, never calls a model, and is safe in read-only mode.",
|
|
858
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
859
|
+
inputSchema: {
|
|
860
|
+
type: "object",
|
|
861
|
+
additionalProperties: false,
|
|
862
|
+
properties: {
|
|
863
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
864
|
+
base: { type: "string", description: "Analyze git diff <base>...HEAD, e.g. origin/main" },
|
|
865
|
+
staged: { type: "boolean", description: "Analyze staged changes" },
|
|
866
|
+
unstaged: { type: "boolean", description: "Analyze unstaged changes" },
|
|
867
|
+
include_untracked: { type: "boolean", description: "Include untracked files when diffing" },
|
|
868
|
+
max_edges: { type: "integer", minimum: 1, maximum: 2000, description: "Cap the rendered edges (default 200)" },
|
|
869
|
+
fail_on_removed: { type: "boolean", description: "Return exit code 1 when an internal dependency was removed" },
|
|
870
|
+
paths: { type: "array", items: { type: "string" } },
|
|
871
|
+
...RESULT_PROPERTIES,
|
|
872
|
+
},
|
|
873
|
+
},
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
name: "roast",
|
|
877
|
+
description: "Roast the diff with a toxic senior persona: technically grounded jokes about complexity, naming, needless abstraction and blind trust in AI. Every joke cites file:line and the answer ends with one real fix. It attacks code, never the author. Model-backed; no file changes.",
|
|
878
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
879
|
+
inputSchema: {
|
|
880
|
+
type: "object",
|
|
881
|
+
additionalProperties: false,
|
|
882
|
+
properties: {
|
|
883
|
+
persona: { type: "string", enum: ["linus", "ramsay", "corporate", "default"], description: "Roast persona (default linus)" },
|
|
884
|
+
context: { type: "string", description: "What happened and what deserves the roast" },
|
|
885
|
+
diff: { type: "string", description: "Unified diff text. Omit to use the project's git diff." },
|
|
886
|
+
base: { type: "string" },
|
|
887
|
+
staged: { type: "boolean" },
|
|
888
|
+
unstaged: { type: "boolean" },
|
|
889
|
+
include_untracked: { type: "boolean" },
|
|
890
|
+
...RESULT_PROPERTIES,
|
|
891
|
+
lang: { type: "string", enum: ["en", "ru"] },
|
|
892
|
+
model: { type: "string" },
|
|
893
|
+
timeout: { type: "integer", minimum: 1 },
|
|
894
|
+
repo_context: { type: "boolean" },
|
|
895
|
+
no_secret_scan: { type: "boolean" },
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
name: "cinema",
|
|
901
|
+
description: "Build a terminal timelapse of local activity: commits, agent runs, API calls, the last review and the receipt, rendered as a self-contained HTML player or an asciinema v2 cast. No network, no external assets, no model call. With no_paths it hides commit subjects and finding locations.",
|
|
902
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
903
|
+
inputSchema: {
|
|
904
|
+
type: "object",
|
|
905
|
+
additionalProperties: false,
|
|
906
|
+
properties: {
|
|
907
|
+
title: { type: "string", description: "Player title" },
|
|
908
|
+
since_days: { type: "integer", minimum: 1, maximum: 365, description: "History window in days (default 7)" },
|
|
909
|
+
max_frames: { type: "integer", minimum: 1, maximum: 500, description: "Cap the number of frames (default 120)" },
|
|
910
|
+
no_paths: { type: "boolean", description: "Hide commit subjects and finding locations" },
|
|
911
|
+
cast: { type: "boolean", description: "Return an asciinema v2 cast instead of HTML" },
|
|
912
|
+
...RESULT_PROPERTIES,
|
|
913
|
+
},
|
|
914
|
+
},
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
name: "bounty",
|
|
918
|
+
description: "Build an ASCII WANTED poster for the worst file in the repository, scored offline from SAST findings, duplicated blocks, dead exports, aged TODOs and churn without tests. No model call and no network. With no_paths it hides file names and finding locations.",
|
|
919
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
920
|
+
inputSchema: {
|
|
921
|
+
type: "object",
|
|
922
|
+
additionalProperties: false,
|
|
923
|
+
properties: {
|
|
924
|
+
limit: { type: "integer", minimum: 1, maximum: 20, description: "How many suspects to rank (default 5)" },
|
|
925
|
+
since_days: { type: "integer", minimum: 1, maximum: 365, description: "Churn and TODO window in days (default 30)" },
|
|
926
|
+
...RESULT_PROPERTIES,
|
|
927
|
+
},
|
|
928
|
+
},
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
name: "autopsy",
|
|
932
|
+
description: "Forensic post-mortem for a commit: diff stats, SAST findings on added lines, weakened tests and mock leaks, ranked suspect files and a before/after commit timeline. Uses local git and files only, never calls a model, so it is free and safe in read-only mode. With fail_on_risk a dangerous verdict returns exit code 1, not a tool error.",
|
|
933
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
934
|
+
inputSchema: {
|
|
935
|
+
type: "object",
|
|
936
|
+
additionalProperties: false,
|
|
937
|
+
properties: {
|
|
938
|
+
commit: { type: "string", description: "Commit or revision to autopsy (default HEAD)" },
|
|
939
|
+
depth: { type: "integer", minimum: 1, maximum: 50, description: "Timeline commits to inspect (default 5)" },
|
|
940
|
+
fail_on_risk: { type: "boolean", description: "Return exit code 1 when the verdict is dangerous" },
|
|
941
|
+
...RESULT_PROPERTIES,
|
|
942
|
+
},
|
|
943
|
+
},
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
name: "echo",
|
|
947
|
+
description: "Build self-contained SVG badges from local stats, git history and the receipt: commits, agent runs, API calls, cache hits, findings and test status. With submit=true and an explicit endpoint it sends an anonymous aggregate payload containing only the nickname, window and stats; no paths, diffs or secrets. No network by default, no model call.",
|
|
948
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
949
|
+
inputSchema: {
|
|
950
|
+
type: "object",
|
|
951
|
+
additionalProperties: false,
|
|
952
|
+
properties: {
|
|
953
|
+
name: { type: "string", description: "Nickname for the badges and the leaderboard payload (default anonymous)" },
|
|
954
|
+
since_days: { type: "integer", minimum: 1, maximum: 365, description: "History window in days (default 30)" },
|
|
955
|
+
submit: { type: "boolean", description: "Send the anonymous payload; requires endpoint and --allow-write" },
|
|
956
|
+
endpoint: { type: "string", description: "Explicit leaderboard endpoint for submit" },
|
|
957
|
+
svg: { type: "boolean", description: "Return only the combined SVG sheet" },
|
|
958
|
+
...RESULT_PROPERTIES,
|
|
959
|
+
},
|
|
960
|
+
},
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
name: "sast",
|
|
964
|
+
description: "Deterministic offline SAST for changed files: command and SQL injection, path traversal, SSRF, code injection, XSS, open redirects, disabled TLS verification, wildcard CORS, prototype pollution, weak hashes and JWT none. Uses local files only and never calls a model, so it is free and safe in read-only mode. Findings at or above fail_on_issue return exit code 1, not a tool error.",
|
|
965
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
966
|
+
inputSchema: {
|
|
967
|
+
type: "object",
|
|
968
|
+
additionalProperties: false,
|
|
969
|
+
properties: {
|
|
970
|
+
diff: { type: "string", description: "Unified diff to scan instead of the git working tree" },
|
|
971
|
+
base: { type: "string", description: "Scan git diff <base>...HEAD, e.g. origin/main" },
|
|
972
|
+
staged: { type: "boolean", description: "Scan staged changes" },
|
|
973
|
+
unstaged: { type: "boolean", description: "Scan unstaged changes" },
|
|
974
|
+
include_untracked: { type: "boolean", description: "Include untracked files when diffing" },
|
|
975
|
+
paths: {
|
|
976
|
+
type: "array",
|
|
977
|
+
items: { type: "string" },
|
|
978
|
+
description: "Files or directories to scan instead of the git diff",
|
|
979
|
+
},
|
|
980
|
+
fail_on_issue: {
|
|
981
|
+
type: "string",
|
|
982
|
+
enum: ["high", "medium", "low"],
|
|
983
|
+
description: "Lowest severity that fails the gate (default high)",
|
|
984
|
+
},
|
|
985
|
+
ignore: { type: "array", items: { type: "string" }, description: "Rule ids to ignore" },
|
|
986
|
+
no_suppressions: { type: "boolean", description: "Ignore oc-pilot:sast-ignore comments" },
|
|
987
|
+
baseline: { type: "string", description: "Baseline file with accepted findings (default .oc-pilot/baseline.json)" },
|
|
988
|
+
update_baseline: { type: "boolean", description: "Write current findings to the baseline and exit 0" },
|
|
989
|
+
...RESULT_PROPERTIES,
|
|
990
|
+
},
|
|
991
|
+
},
|
|
992
|
+
},
|
|
993
|
+
{
|
|
994
|
+
name: "authz",
|
|
995
|
+
description: "Offline authorization analysis of HTTP routes: handlers without a session check, IDOR (an object loaded by a request id without an ownership check) and admin paths without a role guard. Understands Express, Fastify, Koa and Next.js route files. Uses local files only and never calls a model, so it is free and safe in read-only mode. Findings at or above fail_on_issue return exit code 1, not a tool error.",
|
|
996
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
997
|
+
inputSchema: {
|
|
998
|
+
type: "object",
|
|
999
|
+
additionalProperties: false,
|
|
1000
|
+
properties: {
|
|
1001
|
+
diff: { type: "string", description: "Unified diff to scan instead of the git working tree" },
|
|
1002
|
+
base: { type: "string", description: "Scan git diff <base>...HEAD, e.g. origin/main" },
|
|
1003
|
+
staged: { type: "boolean", description: "Scan staged changes" },
|
|
1004
|
+
unstaged: { type: "boolean", description: "Scan unstaged changes" },
|
|
1005
|
+
include_untracked: { type: "boolean", description: "Include untracked files when diffing" },
|
|
1006
|
+
paths: {
|
|
1007
|
+
type: "array",
|
|
1008
|
+
items: { type: "string" },
|
|
1009
|
+
description: "Files or directories to scan instead of the git diff",
|
|
1010
|
+
},
|
|
1011
|
+
fail_on_issue: {
|
|
1012
|
+
type: "string",
|
|
1013
|
+
enum: ["high", "medium"],
|
|
1014
|
+
description: "Lowest severity that fails the gate (default high)",
|
|
1015
|
+
},
|
|
1016
|
+
ignore: { type: "array", items: { type: "string" }, description: "Rule ids to ignore" },
|
|
1017
|
+
public: {
|
|
1018
|
+
type: "array",
|
|
1019
|
+
items: { type: "string" },
|
|
1020
|
+
description: "Route prefixes that are public by design, e.g. /health",
|
|
1021
|
+
},
|
|
1022
|
+
no_suppressions: { type: "boolean", description: "Ignore oc-pilot:authz-ignore comments" },
|
|
1023
|
+
baseline: { type: "string", description: "Baseline file with accepted findings (default .oc-pilot/baseline.json)" },
|
|
1024
|
+
update_baseline: { type: "boolean", description: "Write current findings to the baseline and exit 0" },
|
|
1025
|
+
...RESULT_PROPERTIES,
|
|
1026
|
+
},
|
|
1027
|
+
},
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
name: "preflight",
|
|
1031
|
+
description: "One offline verdict before a commit: secrets, SAST, route authorization, dependency health scoped to the change, test weakening and the Slop Index delta. Uses git and local files only and never calls a model, so it is free and safe in read-only mode. A failed check returns status fail with exit code 1, not a tool error.",
|
|
1032
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1033
|
+
inputSchema: {
|
|
1034
|
+
type: "object",
|
|
1035
|
+
additionalProperties: false,
|
|
1036
|
+
properties: {
|
|
1037
|
+
diff: { type: "string", description: "Unified diff to check instead of the git working tree" },
|
|
1038
|
+
base: { type: "string", description: "Check git diff <base>...HEAD, e.g. origin/main" },
|
|
1039
|
+
staged: { type: "boolean", description: "Check staged changes" },
|
|
1040
|
+
unstaged: { type: "boolean", description: "Check unstaged changes" },
|
|
1041
|
+
include_untracked: { type: "boolean", description: "Include untracked files when diffing" },
|
|
1042
|
+
since: { type: "string", description: "Slop Index baseline ref (default HEAD)" },
|
|
1043
|
+
max_delta: { type: "integer", description: "Allowed Slop Index growth in points (default 10)" },
|
|
1044
|
+
fail_on_issue: {
|
|
1045
|
+
type: "string",
|
|
1046
|
+
enum: ["high", "medium", "low"],
|
|
1047
|
+
description: "Lowest SAST, authz and dependency severity that fails the gate (default high)",
|
|
1048
|
+
},
|
|
1049
|
+
no_secret_scan: { type: "boolean", description: "Skip the secret scan" },
|
|
1050
|
+
no_sast: { type: "boolean", description: "Skip SAST" },
|
|
1051
|
+
no_authz: { type: "boolean", description: "Skip route authorization analysis" },
|
|
1052
|
+
no_deps: { type: "boolean", description: "Skip dependency health" },
|
|
1053
|
+
no_tests: { type: "boolean", description: "Skip test weakening" },
|
|
1054
|
+
no_slop: { type: "boolean", description: "Skip the Slop Index delta" },
|
|
1055
|
+
...RESULT_PROPERTIES,
|
|
1056
|
+
},
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
name: "ready",
|
|
1061
|
+
description: "Agent readiness score 0-100 for the repository: tests, lockfile, git hooks, Slop Index, a fresh receipt, agent instructions, CI, config and security. Uses git and local files only and never calls a model, so it is free and safe in read-only mode. A score below fail_under returns exit code 1 with the full report, not a tool error.",
|
|
1062
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1063
|
+
inputSchema: {
|
|
1064
|
+
type: "object",
|
|
1065
|
+
additionalProperties: false,
|
|
1066
|
+
properties: {
|
|
1067
|
+
fail_under: {
|
|
1068
|
+
type: "integer",
|
|
1069
|
+
minimum: 1,
|
|
1070
|
+
maximum: 100,
|
|
1071
|
+
description: "Return exit code 1 when the score is below this value",
|
|
1072
|
+
},
|
|
1073
|
+
...RESULT_PROPERTIES,
|
|
1074
|
+
},
|
|
1075
|
+
},
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
name: "receipt_verify",
|
|
1079
|
+
description: "Verify the oc-pilot receipt for the current HEAD: recompute its SHA-256 hash, compare the recorded commit with HEAD and report whether the recorded tests passed. Read-only and offline, so it is free and safe in read-only mode. A missing, edited or stale receipt returns exit code 1 with ok=false, not a tool error.",
|
|
1080
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1081
|
+
inputSchema: {
|
|
1082
|
+
type: "object",
|
|
1083
|
+
additionalProperties: false,
|
|
1084
|
+
properties: {
|
|
1085
|
+
receipt: {
|
|
1086
|
+
type: "string",
|
|
1087
|
+
description: "Path to a receipt file; defaults to .git/oc-pilot/receipt.json",
|
|
1088
|
+
},
|
|
1089
|
+
public_key: {
|
|
1090
|
+
type: "string",
|
|
1091
|
+
description: "Public key file for the signature check; defaults to .oc-pilot/receipt.pub",
|
|
1092
|
+
},
|
|
1093
|
+
...RESULT_PROPERTIES,
|
|
1094
|
+
},
|
|
1095
|
+
},
|
|
1096
|
+
},
|
|
1097
|
+
{
|
|
1098
|
+
name: "stats",
|
|
1099
|
+
description: "Local usage statistics: runs, API calls, cache hits and findings for today, the last week and all time. No network calls.",
|
|
1100
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1101
|
+
inputSchema: {
|
|
1102
|
+
type: "object",
|
|
1103
|
+
additionalProperties: false,
|
|
1104
|
+
properties: {
|
|
1105
|
+
clear: { type: "boolean", description: "Delete the recorded history" },
|
|
1106
|
+
},
|
|
1107
|
+
},
|
|
1108
|
+
},
|
|
1109
|
+
];
|
|
1110
|
+
const PROMPTS = [
|
|
1111
|
+
{
|
|
1112
|
+
name: "review-my-diff",
|
|
1113
|
+
title: "Review my current diff",
|
|
1114
|
+
description: "Run the review, then summarize the findings by severity and propose the smallest safe fix for each critical one.",
|
|
1115
|
+
arguments: [
|
|
1116
|
+
{ name: "context", description: "What the change is supposed to do", required: false },
|
|
1117
|
+
],
|
|
1118
|
+
},
|
|
1119
|
+
{
|
|
1120
|
+
name: "fix-failing-tests",
|
|
1121
|
+
title: "Diagnose failing tests",
|
|
1122
|
+
description: "Analyze the failing test output or run the suite, find the root cause, and propose the minimal patch.",
|
|
1123
|
+
arguments: [
|
|
1124
|
+
{ name: "log", description: "Captured test output, if tests cannot be run here", required: false },
|
|
1125
|
+
],
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
name: "pre-commit-gate",
|
|
1129
|
+
title: "Pre-commit gate",
|
|
1130
|
+
description: "Scan the staged changes for secrets, review them, and only then generate the commit message.",
|
|
1131
|
+
arguments: [],
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
name: "secure-change",
|
|
1135
|
+
title: "Security-focused review",
|
|
1136
|
+
description: "Review a change with the security mindset: injection, authorization, dependencies and prompt injection in the diff.",
|
|
1137
|
+
arguments: [
|
|
1138
|
+
{ name: "context", description: "Attack surface touched by the change", required: false },
|
|
1139
|
+
],
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
name: "slop-check",
|
|
1143
|
+
title: "Check for vibe-coding slop",
|
|
1144
|
+
description: "Run the Slop Index, then explain which category grew the most and propose the three cheapest fixes with the biggest score drop.",
|
|
1145
|
+
arguments: [
|
|
1146
|
+
{ name: "context", description: "What the recent change was supposed to add", required: false },
|
|
1147
|
+
],
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
name: "preflight-before-commit",
|
|
1151
|
+
title: "Gate the change before commit",
|
|
1152
|
+
description: "Run the offline preflight, fix the failed checks starting with the cheapest, and rerun until the verdict is pass or only intentional skips remain.",
|
|
1153
|
+
arguments: [
|
|
1154
|
+
{ name: "context", description: "What the change is supposed to do", required: false },
|
|
1155
|
+
],
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
name: "compile-hologram",
|
|
1159
|
+
title: "Compile a context hologram",
|
|
1160
|
+
description: "Build a layered context where diff files are complete, direct imports hide function bodies behind markers, and the rest exposes only export names; zoom one symbol on demand instead of dumping the repo.",
|
|
1161
|
+
arguments: [
|
|
1162
|
+
{ name: "context", description: "What the agent is trying to do", required: false },
|
|
1163
|
+
],
|
|
1164
|
+
},
|
|
1165
|
+
{
|
|
1166
|
+
name: "check-invariants",
|
|
1167
|
+
title: "Check the semantic invariants",
|
|
1168
|
+
description: "Check the change against .oc-pilot/invariants.md, explain every violation with evidence and treat CRITICAL or strict violations as a blocking gate.",
|
|
1169
|
+
arguments: [
|
|
1170
|
+
{ name: "context", description: "What the change is supposed to do", required: false },
|
|
1171
|
+
],
|
|
1172
|
+
},
|
|
1173
|
+
{
|
|
1174
|
+
name: "fuzz-the-diff",
|
|
1175
|
+
title: "Fuzz the new functions",
|
|
1176
|
+
description: "Call the new exported functions with hostile inputs in isolated processes, report crashes and timeouts, and never treat blocked side-effect files as tested.",
|
|
1177
|
+
arguments: [
|
|
1178
|
+
{ name: "context", description: "What the new functions are supposed to do", required: false },
|
|
1179
|
+
],
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
name: "forecast-tech-debt",
|
|
1183
|
+
title: "Forecast the technical debt",
|
|
1184
|
+
description: "Read the tech-debt trend from commit history, explain the score and weights, and use the forecast date to plan repayment instead of gaming TODO counts.",
|
|
1185
|
+
arguments: [
|
|
1186
|
+
{ name: "context", description: "Which horizon or area to forecast", required: false },
|
|
1187
|
+
],
|
|
1188
|
+
},
|
|
1189
|
+
{
|
|
1190
|
+
name: "query-the-ast",
|
|
1191
|
+
title: "Query the code graph",
|
|
1192
|
+
description: "Ask the code graph where a call, function, class or import lives with AQL, then use the result to plan a refactor instead of grepping blindly.",
|
|
1193
|
+
arguments: [
|
|
1194
|
+
{ name: "context", description: "What symbol or call to locate", required: false },
|
|
1195
|
+
],
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
name: "sandbox-script",
|
|
1199
|
+
title: "Sandbox a generated script",
|
|
1200
|
+
description: "Run a generated JS/ESM script in node:vm with imports, fs, net, child_process and fetch blocked, then report what it printed and whether it tried a side effect.",
|
|
1201
|
+
arguments: [
|
|
1202
|
+
{ name: "context", description: "What the script is supposed to do", required: false },
|
|
1203
|
+
],
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
name: "join-the-swarm",
|
|
1207
|
+
title: "Join the agent mesh",
|
|
1208
|
+
description: "Check the hivemind identity and trusted peers, then exchange only diffs, AST and LLM requests over the encrypted mesh; never send executable commands.",
|
|
1209
|
+
arguments: [
|
|
1210
|
+
{ name: "context", description: "What work should be shared with the mesh", required: false },
|
|
1211
|
+
],
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
name: "get-ready",
|
|
1215
|
+
title: "Make the repository agent-ready",
|
|
1216
|
+
description: "Score the repository, fix the safe gaps (hook, config, AGENTS.md), prove the result with a receipt, and rerun until the score is at least 85 or only human checks remain.",
|
|
1217
|
+
arguments: [
|
|
1218
|
+
{ name: "context", description: "What the agent is about to work on", required: false },
|
|
1219
|
+
],
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
name: "fix-security",
|
|
1223
|
+
title: "Fix security findings",
|
|
1224
|
+
description: "Ask oc-pilot to repair SAST or route-authorization findings, then rescan until no high findings remain.",
|
|
1225
|
+
arguments: [
|
|
1226
|
+
{ name: "context", description: "What the change does and which finding matters", required: false },
|
|
1227
|
+
],
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
name: "stabilize-flaky",
|
|
1231
|
+
title: "Stabilize a flaky test",
|
|
1232
|
+
description: "Reproduce a flaky test with repeated and shuffled runs, identify shared state or ordering dependence, and make the smallest deterministic fix.",
|
|
1233
|
+
arguments: [
|
|
1234
|
+
{ name: "context", description: "Which test is suspected and what changed recently", required: false },
|
|
1235
|
+
],
|
|
1236
|
+
},
|
|
1237
|
+
{
|
|
1238
|
+
name: "check-impact",
|
|
1239
|
+
title: "Check what the change touches",
|
|
1240
|
+
description: "Find external references to changed declarations, inspect every call site, update what the change broke and rerun until no stale references remain.",
|
|
1241
|
+
arguments: [
|
|
1242
|
+
{ name: "context", description: "What changed and which symbols are risky", required: false },
|
|
1243
|
+
],
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
name: "triage-deps",
|
|
1247
|
+
title: "Triage dependency problems",
|
|
1248
|
+
description: "Audit dependencies and vulnerabilities, fix the cheapest real problem first, and rerun without lowering the gate or hiding packages.",
|
|
1249
|
+
arguments: [
|
|
1250
|
+
{ name: "context", description: "What changed and which dependency is suspect", required: false },
|
|
1251
|
+
],
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
name: "cover-changed-lines",
|
|
1255
|
+
title: "Cover the changed lines",
|
|
1256
|
+
description: "Measure changed-line coverage, add tests for the uncovered lines and rerun with the same gate instead of lowering the threshold.",
|
|
1257
|
+
arguments: [
|
|
1258
|
+
{ name: "context", description: "Which change and behavior should be covered", required: false },
|
|
1259
|
+
],
|
|
1260
|
+
},
|
|
1261
|
+
{
|
|
1262
|
+
name: "generate-tests",
|
|
1263
|
+
title: "Generate tests for the change",
|
|
1264
|
+
description: "Generate tests for changed code or only for uncovered changed lines, apply them and run the suite, fixing the patch until the tests pass.",
|
|
1265
|
+
arguments: [
|
|
1266
|
+
{ name: "context", description: "What behavior the tests should protect", required: false },
|
|
1267
|
+
],
|
|
1268
|
+
},
|
|
1269
|
+
{
|
|
1270
|
+
name: "pick-reviewers",
|
|
1271
|
+
title: "Pick reviewers for the change",
|
|
1272
|
+
description: "Use git history and CODEOWNERS to choose the smallest set of reviewers who know the changed files.",
|
|
1273
|
+
arguments: [
|
|
1274
|
+
{ name: "context", description: "What changed and who usually owns it", required: false },
|
|
1275
|
+
],
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
name: "guard-tests",
|
|
1279
|
+
title: "Guard the tests against weakening",
|
|
1280
|
+
description: "Find skipped, focused or deleted tests, removed assertions, weakened matchers, suppressed type checks and swallowed assertions, then fix the cause without hiding it.",
|
|
1281
|
+
arguments: [
|
|
1282
|
+
{ name: "context", description: "Why the tests changed and what they should protect", required: false },
|
|
1283
|
+
],
|
|
1284
|
+
},
|
|
1285
|
+
{
|
|
1286
|
+
name: "sync-env",
|
|
1287
|
+
title: "Sync the environment variables",
|
|
1288
|
+
description: "Check every environment variable added by the change against .env and .env.example, then add the missing configuration and documentation without inventing secrets.",
|
|
1289
|
+
arguments: [
|
|
1290
|
+
{ name: "context", description: "What the new variable configures and where it is read", required: false },
|
|
1291
|
+
],
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
name: "remove-mocks",
|
|
1295
|
+
title: "Remove stubs and hardcode",
|
|
1296
|
+
description: "Find forgotten mocks, local paths, test tokens and always-true conditions in production code, replace them with the real implementation and rerun until the leak report is clean.",
|
|
1297
|
+
arguments: [
|
|
1298
|
+
{ name: "context", description: "What the code should do in production", required: false },
|
|
1299
|
+
],
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
name: "verify-ecosystem",
|
|
1303
|
+
title: "Verify imports and new packages",
|
|
1304
|
+
description: "Check that imported names exist in installed packages, then verify newly added npm packages against registry age and downloads before trusting them.",
|
|
1305
|
+
arguments: [
|
|
1306
|
+
{ name: "context", description: "Which dependency or import is suspicious", required: false },
|
|
1307
|
+
],
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
name: "pack-context",
|
|
1311
|
+
title: "Pack the task context",
|
|
1312
|
+
description: "Build a compact bundle of project rules, changed-file signatures, related references and the last test failure instead of dumping whole files into the prompt.",
|
|
1313
|
+
arguments: [
|
|
1314
|
+
{ name: "context", description: "What the task is supposed to change", required: false },
|
|
1315
|
+
],
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
name: "check-contracts",
|
|
1319
|
+
title: "Check contract drift",
|
|
1320
|
+
description: "Find removed or narrowed fields in changed types and schemas, inspect every remaining consumer and update the contract without silently breaking callers.",
|
|
1321
|
+
arguments: [
|
|
1322
|
+
{ name: "context", description: "Which API or schema changed", required: false },
|
|
1323
|
+
],
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
name: "guard-regressions",
|
|
1327
|
+
title: "Do not re-add deleted code",
|
|
1328
|
+
description: "Compare added lines with code a human explicitly deleted, inspect the deleting commit and reintroduce a line only with an explicit reason.",
|
|
1329
|
+
arguments: [
|
|
1330
|
+
{ name: "context", description: "Why the line is being added again", required: false },
|
|
1331
|
+
],
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
name: "respect-boundaries",
|
|
1335
|
+
title: "Keep architecture boundaries",
|
|
1336
|
+
description: "Check added imports against the layer rules, fix the direction of the dependency and rerun until no boundary is crossed.",
|
|
1337
|
+
arguments: [
|
|
1338
|
+
{ name: "context", description: "Which module or layer changed", required: false },
|
|
1339
|
+
],
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
name: "plan-tests",
|
|
1343
|
+
title: "Plan the affected tests",
|
|
1344
|
+
description: "Select only the test files the change can affect using the import graph, then run them and cover any changed file the plan marks as untested.",
|
|
1345
|
+
arguments: [
|
|
1346
|
+
{ name: "context", description: "What changed and what risk the tests should cover", required: false },
|
|
1347
|
+
],
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
name: "prove-compliance",
|
|
1351
|
+
title: "Prove the audit trail",
|
|
1352
|
+
description: "Verify the signed compliance manifest for HEAD, explain any broken hash, chain or signature, and refuse to claim completion while the proof is invalid.",
|
|
1353
|
+
arguments: [
|
|
1354
|
+
{ name: "context", description: "What was shipped and what the audit must prove", required: false },
|
|
1355
|
+
],
|
|
1356
|
+
},
|
|
1357
|
+
{
|
|
1358
|
+
name: "battle-hardening",
|
|
1359
|
+
title: "Harden the change with two agents",
|
|
1360
|
+
description: "Run the coder and hacker loop: the coder patches, the hacker attacks with review and offline security checks, and the loop repeats until no high findings remain and tests pass.",
|
|
1361
|
+
arguments: [
|
|
1362
|
+
{ name: "context", description: "What the task should achieve", required: false },
|
|
1363
|
+
],
|
|
1364
|
+
},
|
|
1365
|
+
{
|
|
1366
|
+
name: "cleanup-plan",
|
|
1367
|
+
title: "Plan the cleanup",
|
|
1368
|
+
description: "Build an offline cleanup plan from dependency issues, safe package.json fixes, Slop Index, environment variables and weakened tests, then fix the cheapest high tasks first.",
|
|
1369
|
+
arguments: [
|
|
1370
|
+
{ name: "context", description: "What area should be cleaned up first", required: false },
|
|
1371
|
+
],
|
|
1372
|
+
},
|
|
1373
|
+
{
|
|
1374
|
+
name: "show-arch-diff",
|
|
1375
|
+
title: "Show the architecture change",
|
|
1376
|
+
description: "Render a Mermaid diagram of added and removed internal dependencies, include it in the pull request and explain every removed edge instead of hiding it.",
|
|
1377
|
+
arguments: [
|
|
1378
|
+
{ name: "context", description: "What the architectural change is supposed to do", required: false },
|
|
1379
|
+
],
|
|
1380
|
+
},
|
|
1381
|
+
{
|
|
1382
|
+
name: "roast-my-code",
|
|
1383
|
+
title: "Roast my code",
|
|
1384
|
+
description: "Roast the current diff in the voice of a toxic senior, keep every joke tied to real code, then give one actionable fix.",
|
|
1385
|
+
arguments: [
|
|
1386
|
+
{ name: "context", description: "What the change was supposed to do", required: false },
|
|
1387
|
+
],
|
|
1388
|
+
},
|
|
1389
|
+
{
|
|
1390
|
+
name: "play-session",
|
|
1391
|
+
title: "Play the session back",
|
|
1392
|
+
description: "Build a terminal timelapse of recent commits, agent runs, API calls, findings and the receipt as a self-contained HTML player or asciinema cast.",
|
|
1393
|
+
arguments: [
|
|
1394
|
+
{ name: "context", description: "Which session window to replay", required: false },
|
|
1395
|
+
],
|
|
1396
|
+
},
|
|
1397
|
+
{
|
|
1398
|
+
name: "wanted-poster",
|
|
1399
|
+
title: "Post a bounty on the worst file",
|
|
1400
|
+
description: "Rank the most problematic files by SAST findings, duplication, dead exports, old TODOs and churn, then turn the worst one into an ASCII WANTED poster with concrete reasons.",
|
|
1401
|
+
arguments: [
|
|
1402
|
+
{ name: "context", description: "Which area or file to investigate", required: false },
|
|
1403
|
+
],
|
|
1404
|
+
},
|
|
1405
|
+
{
|
|
1406
|
+
name: "autopsy-commit",
|
|
1407
|
+
title: "Run a post-mortem on a commit",
|
|
1408
|
+
description: "Autopsy a commit: list the signals it introduced, rank the suspect files and read the before/after timeline to find where the protection was lost.",
|
|
1409
|
+
arguments: [
|
|
1410
|
+
{ name: "context", description: "Which commit or failure to investigate", required: false },
|
|
1411
|
+
],
|
|
1412
|
+
},
|
|
1413
|
+
{
|
|
1414
|
+
name: "share-progress",
|
|
1415
|
+
title: "Share local progress",
|
|
1416
|
+
description: "Build self-contained SVG badges from real local stats and the receipt, then submit the anonymous aggregate only after the user explicitly approves the endpoint.",
|
|
1417
|
+
arguments: [
|
|
1418
|
+
{ name: "context", description: "What to highlight and where it may be shared", required: false },
|
|
1419
|
+
],
|
|
1420
|
+
},
|
|
1421
|
+
{
|
|
1422
|
+
name: "prove-tests",
|
|
1423
|
+
title: "Prove the tests catch regressions",
|
|
1424
|
+
description: "Run mutation testing on the change, explain the surviving mutants and strengthen the tests until the mutation score is high enough.",
|
|
1425
|
+
arguments: [
|
|
1426
|
+
{ name: "context", description: "What behavior the tests are supposed to protect", required: false },
|
|
1427
|
+
],
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
name: "prove-done",
|
|
1431
|
+
title: "Prove the work is done",
|
|
1432
|
+
description: "Verify the receipt for HEAD, create a fresh one when it is missing or stale, and refuse to claim completion until the receipt is valid and its tests passed.",
|
|
1433
|
+
arguments: [
|
|
1434
|
+
{ name: "context", description: "What was changed and what should be proven", required: false },
|
|
1435
|
+
],
|
|
1436
|
+
},
|
|
1437
|
+
];
|
|
1438
|
+
class ToolInputError extends Error {
|
|
1439
|
+
}
|
|
1440
|
+
class ResourceError extends Error {
|
|
1441
|
+
}
|
|
1442
|
+
const DIFF_MODES = new Set(["staged", "unstaged", "head"]);
|
|
1443
|
+
const RESOURCES = [
|
|
1444
|
+
{
|
|
1445
|
+
uri: "oc-pilot://config",
|
|
1446
|
+
name: "oc-pilot configuration",
|
|
1447
|
+
description: "Resolved oc-pilot configuration for the current project, as JSON.",
|
|
1448
|
+
mimeType: "application/json",
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
uri: "oc-pilot://rules",
|
|
1452
|
+
name: "Project review rules",
|
|
1453
|
+
description: "Contents of .oc-pilot/rules.md with detected secrets redacted, or a notice when absent.",
|
|
1454
|
+
mimeType: "text/markdown",
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
uri: "oc-pilot://version",
|
|
1458
|
+
name: "oc-pilot version",
|
|
1459
|
+
description: "oc-pilot version and MCP protocol version, as JSON.",
|
|
1460
|
+
mimeType: "application/json",
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
uri: "oc-pilot://prompt/en",
|
|
1464
|
+
name: "Review system prompt (English)",
|
|
1465
|
+
description: "The built-in English system prompt used for model calls.",
|
|
1466
|
+
mimeType: "text/markdown",
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
uri: "oc-pilot://prompt/ru",
|
|
1470
|
+
name: "Review system prompt (Russian)",
|
|
1471
|
+
description: "The built-in Russian system prompt used for model calls.",
|
|
1472
|
+
mimeType: "text/markdown",
|
|
1473
|
+
},
|
|
1474
|
+
{
|
|
1475
|
+
uri: "oc-pilot://slop",
|
|
1476
|
+
name: "Slop Index",
|
|
1477
|
+
description: "Offline Slop Index report as JSON: duplicated blocks, dead exports, churn and TODO graveyard.",
|
|
1478
|
+
mimeType: "application/json",
|
|
1479
|
+
},
|
|
1480
|
+
];
|
|
1481
|
+
const RESOURCE_TEMPLATES = [
|
|
1482
|
+
{
|
|
1483
|
+
uriTemplate: "oc-pilot://diff/{mode}",
|
|
1484
|
+
name: "Current git diff",
|
|
1485
|
+
description: "The project's git diff by mode: staged, unstaged or head.",
|
|
1486
|
+
mimeType: "text/plain",
|
|
1487
|
+
},
|
|
1488
|
+
{
|
|
1489
|
+
uriTemplate: "oc-pilot://scan/{mode}",
|
|
1490
|
+
name: "Offline secret scan",
|
|
1491
|
+
description: "Secret scan findings (masked previews only) for the diff by mode: staged, unstaged or head.",
|
|
1492
|
+
mimeType: "application/json",
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
uriTemplate: "oc-pilot://file/{+path}",
|
|
1496
|
+
name: "Project file with secrets redacted",
|
|
1497
|
+
description: "Read-only contents of a project file (max 2 MB, text only) with detected secrets redacted.",
|
|
1498
|
+
mimeType: "text/plain",
|
|
1499
|
+
},
|
|
1500
|
+
];
|
|
1501
|
+
class TempFiles {
|
|
1502
|
+
directory;
|
|
1503
|
+
write(name, content) {
|
|
1504
|
+
this.directory ??= mkdtempSync(join(tmpdir(), "oc-pilot-mcp-"));
|
|
1505
|
+
const path = join(this.directory, name);
|
|
1506
|
+
writeFileSync(path, content, "utf8");
|
|
1507
|
+
return path;
|
|
1508
|
+
}
|
|
1509
|
+
cleanup() {
|
|
1510
|
+
if (!this.directory)
|
|
1511
|
+
return;
|
|
1512
|
+
try {
|
|
1513
|
+
rmSync(this.directory, { recursive: true, force: true });
|
|
1514
|
+
}
|
|
1515
|
+
catch {
|
|
1516
|
+
// Best-effort temp cleanup.
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
function stringArg(input, name, required = false) {
|
|
1521
|
+
const value = input[name];
|
|
1522
|
+
if (value === undefined) {
|
|
1523
|
+
if (required)
|
|
1524
|
+
throw new ToolInputError(`${name} is required`);
|
|
1525
|
+
return undefined;
|
|
1526
|
+
}
|
|
1527
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1528
|
+
throw new ToolInputError(`${name} must be a non-empty string`);
|
|
1529
|
+
}
|
|
1530
|
+
return value;
|
|
1531
|
+
}
|
|
1532
|
+
function booleanArg(input, name) {
|
|
1533
|
+
const value = input[name];
|
|
1534
|
+
if (value === undefined)
|
|
1535
|
+
return undefined;
|
|
1536
|
+
if (typeof value !== "boolean")
|
|
1537
|
+
throw new ToolInputError(`${name} must be a boolean`);
|
|
1538
|
+
return value;
|
|
1539
|
+
}
|
|
1540
|
+
function integerArg(input, name) {
|
|
1541
|
+
const value = input[name];
|
|
1542
|
+
if (value === undefined)
|
|
1543
|
+
return undefined;
|
|
1544
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
1545
|
+
throw new ToolInputError(`${name} must be a positive integer`);
|
|
1546
|
+
}
|
|
1547
|
+
return value;
|
|
1548
|
+
}
|
|
1549
|
+
/** Accepts zero and negative values, for thresholds such as a Slop Index delta. */
|
|
1550
|
+
function signedIntegerArg(input, name) {
|
|
1551
|
+
const value = input[name];
|
|
1552
|
+
if (value === undefined)
|
|
1553
|
+
return undefined;
|
|
1554
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
1555
|
+
throw new ToolInputError(`${name} must be an integer`);
|
|
1556
|
+
}
|
|
1557
|
+
return value;
|
|
1558
|
+
}
|
|
1559
|
+
function stringArrayArg(input, name, required = false) {
|
|
1560
|
+
const value = input[name];
|
|
1561
|
+
if (value === undefined) {
|
|
1562
|
+
if (required)
|
|
1563
|
+
throw new ToolInputError(`${name} is required`);
|
|
1564
|
+
return [];
|
|
1565
|
+
}
|
|
1566
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
1567
|
+
throw new ToolInputError(`${name} must be an array of non-empty strings`);
|
|
1568
|
+
}
|
|
1569
|
+
return value;
|
|
1570
|
+
}
|
|
1571
|
+
function projectPaths(input, name) {
|
|
1572
|
+
const paths = stringArrayArg(input, name);
|
|
1573
|
+
for (const value of paths) {
|
|
1574
|
+
if (isAbsolute(value) || /(^|[\\/])\.\.([\\/]|$)/.test(value)) {
|
|
1575
|
+
throw new ToolInputError(`${name} must use paths inside the project directory`);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return paths;
|
|
1579
|
+
}
|
|
1580
|
+
function baseArg(input) {
|
|
1581
|
+
const base = stringArg(input, "base");
|
|
1582
|
+
if (!base)
|
|
1583
|
+
return undefined;
|
|
1584
|
+
if (base.startsWith("-") || !/^[A-Za-z0-9._/@{}~^:-]+$/.test(base)) {
|
|
1585
|
+
throw new ToolInputError("base must be a git ref, not an option");
|
|
1586
|
+
}
|
|
1587
|
+
return base;
|
|
1588
|
+
}
|
|
1589
|
+
function contextArg(input) {
|
|
1590
|
+
const context = stringArg(input, "context");
|
|
1591
|
+
if (context?.trimStart().startsWith("@")) {
|
|
1592
|
+
throw new ToolInputError("context must be literal text, not an @file reference");
|
|
1593
|
+
}
|
|
1594
|
+
return context;
|
|
1595
|
+
}
|
|
1596
|
+
function parseResourceUri(uri) {
|
|
1597
|
+
if (!uri.startsWith("oc-pilot://")) {
|
|
1598
|
+
throw new ResourceError(`Unsupported resource URI: ${uri}`);
|
|
1599
|
+
}
|
|
1600
|
+
const tail = uri.slice("oc-pilot://".length);
|
|
1601
|
+
const slash = tail.indexOf("/");
|
|
1602
|
+
if (slash === -1)
|
|
1603
|
+
return { kind: tail, rest: "" };
|
|
1604
|
+
let rest;
|
|
1605
|
+
try {
|
|
1606
|
+
rest = decodeURIComponent(tail.slice(slash + 1));
|
|
1607
|
+
}
|
|
1608
|
+
catch {
|
|
1609
|
+
throw new ResourceError(`Malformed resource URI: ${uri}`);
|
|
1610
|
+
}
|
|
1611
|
+
return { kind: tail.slice(0, slash), rest };
|
|
1612
|
+
}
|
|
1613
|
+
function diffForMode(mode, cwd) {
|
|
1614
|
+
const args = mode === "staged" ? ["diff", "--cached"] : mode === "head" ? ["diff", "HEAD"] : ["diff"];
|
|
1615
|
+
const result = runGit(args, cwd);
|
|
1616
|
+
if (result.status !== 0) {
|
|
1617
|
+
throw new ResourceError(`git ${args.join(" ")} failed: ${result.stderr || result.stdout}`.trim());
|
|
1618
|
+
}
|
|
1619
|
+
return result.stdout;
|
|
1620
|
+
}
|
|
1621
|
+
function readResourceText(uri, cwd) {
|
|
1622
|
+
const { kind, rest } = parseResourceUri(uri);
|
|
1623
|
+
if (kind === "config") {
|
|
1624
|
+
try {
|
|
1625
|
+
const loaded = loadConfig({ cwd });
|
|
1626
|
+
return {
|
|
1627
|
+
mimeType: "application/json",
|
|
1628
|
+
text: JSON.stringify({ config: loaded.config, path: loaded.path ?? null }, null, 2),
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
catch (error) {
|
|
1632
|
+
throw new ResourceError(`config error: ${error instanceof Error ? error.message : String(error)}`);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
if (kind === "rules") {
|
|
1636
|
+
const rules = loadRules(cwd);
|
|
1637
|
+
return {
|
|
1638
|
+
mimeType: "text/markdown",
|
|
1639
|
+
text: rules ? rules.text : "(no .oc-pilot/rules.md found)",
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
if (kind === "version") {
|
|
1643
|
+
return {
|
|
1644
|
+
mimeType: "application/json",
|
|
1645
|
+
text: JSON.stringify({ version: VERSION, protocol: PROTOCOL_VERSION }, null, 2),
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
if (kind === "prompt") {
|
|
1649
|
+
if (rest === "en")
|
|
1650
|
+
return { mimeType: "text/markdown", text: SYSTEM_PROMPT_EN };
|
|
1651
|
+
if (rest === "ru")
|
|
1652
|
+
return { mimeType: "text/markdown", text: SYSTEM_PROMPT_RU };
|
|
1653
|
+
throw new ResourceError("Unknown prompt language; use oc-pilot://prompt/en or oc-pilot://prompt/ru");
|
|
1654
|
+
}
|
|
1655
|
+
if (kind === "slop") {
|
|
1656
|
+
// The report is cheap and offline, but the module is sync-only here.
|
|
1657
|
+
const result = spawnSync(process.execPath, [CLI_PATH, "slop", "--json"], {
|
|
1658
|
+
cwd,
|
|
1659
|
+
encoding: "utf8",
|
|
1660
|
+
windowsHide: true,
|
|
1661
|
+
timeout: 120_000,
|
|
1662
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
1663
|
+
env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
|
|
1664
|
+
});
|
|
1665
|
+
const text = (result.stdout ?? "").trim();
|
|
1666
|
+
if (!text.startsWith("{")) {
|
|
1667
|
+
throw new ResourceError(`slop failed: ${(result.stderr ?? "").trim() || `exit ${result.status}`}`);
|
|
1668
|
+
}
|
|
1669
|
+
return { mimeType: "application/json", text };
|
|
1670
|
+
}
|
|
1671
|
+
if (kind === "diff") {
|
|
1672
|
+
if (!DIFF_MODES.has(rest)) {
|
|
1673
|
+
throw new ResourceError("Unknown diff mode; use staged, unstaged or head");
|
|
1674
|
+
}
|
|
1675
|
+
const diff = diffForMode(rest, cwd);
|
|
1676
|
+
// The diff goes to a host model: redact detected secrets first.
|
|
1677
|
+
const scan = scanDiff(diff, { ignore: loadSecretIgnore(cwd) });
|
|
1678
|
+
return { mimeType: "text/plain", text: scan.redacted };
|
|
1679
|
+
}
|
|
1680
|
+
if (kind === "scan") {
|
|
1681
|
+
if (!DIFF_MODES.has(rest)) {
|
|
1682
|
+
throw new ResourceError("Unknown scan mode; use staged, unstaged or head");
|
|
1683
|
+
}
|
|
1684
|
+
const diff = diffForMode(rest, cwd);
|
|
1685
|
+
const scan = scanDiff(diff, { ignore: loadSecretIgnore(cwd) });
|
|
1686
|
+
return {
|
|
1687
|
+
mimeType: "application/json",
|
|
1688
|
+
text: JSON.stringify({ mode: rest, findings: scan.findings }, null, 2),
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
if (kind === "file") {
|
|
1692
|
+
if (!rest)
|
|
1693
|
+
throw new ResourceError("Missing file path; use oc-pilot://file/<path>");
|
|
1694
|
+
if (isAbsolute(rest) || /(^|[\\/])\.\.([\\/]|$)/.test(rest)) {
|
|
1695
|
+
throw new ResourceError("file path must stay inside the project directory");
|
|
1696
|
+
}
|
|
1697
|
+
const path = resolve(cwd, rest);
|
|
1698
|
+
if (path !== cwd && !path.startsWith(cwd + sep)) {
|
|
1699
|
+
throw new ResourceError("file path must stay inside the project directory");
|
|
1700
|
+
}
|
|
1701
|
+
let content;
|
|
1702
|
+
try {
|
|
1703
|
+
const stats = statSync(path);
|
|
1704
|
+
if (!stats.isFile())
|
|
1705
|
+
throw new ResourceError(`Not a file: ${rest}`);
|
|
1706
|
+
if (stats.size > 2 * 1024 * 1024)
|
|
1707
|
+
throw new ResourceError(`File too large (max 2 MB): ${rest}`);
|
|
1708
|
+
content = readFileSync(path, "utf8");
|
|
1709
|
+
}
|
|
1710
|
+
catch (error) {
|
|
1711
|
+
if (error instanceof ResourceError)
|
|
1712
|
+
throw error;
|
|
1713
|
+
throw new ResourceError(`Cannot read ${rest}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1714
|
+
}
|
|
1715
|
+
if (content.includes("\0"))
|
|
1716
|
+
throw new ResourceError("Binary files are not supported");
|
|
1717
|
+
const scan = scanText(content, rest.replace(/\\/g, "/"), { ignore: loadSecretIgnore(cwd) });
|
|
1718
|
+
return { mimeType: "text/plain", text: scan.redacted };
|
|
1719
|
+
}
|
|
1720
|
+
throw new ResourceError(`Unknown resource: ${uri}`);
|
|
1721
|
+
}
|
|
1722
|
+
function commonArgs(input, options) {
|
|
1723
|
+
const args = [];
|
|
1724
|
+
const lang = stringArg(input, "lang");
|
|
1725
|
+
const model = stringArg(input, "model");
|
|
1726
|
+
const timeout = integerArg(input, "timeout");
|
|
1727
|
+
const noSecretScan = options.secrets ? booleanArg(input, "no_secret_scan") : undefined;
|
|
1728
|
+
const failOn = options.failOn ? stringArg(input, "fail_on") : undefined;
|
|
1729
|
+
const repoContext = options.repoContext ? booleanArg(input, "repo_context") : undefined;
|
|
1730
|
+
if (model)
|
|
1731
|
+
args.push("--model", model);
|
|
1732
|
+
if (lang)
|
|
1733
|
+
args.push("--lang", lang);
|
|
1734
|
+
if (timeout)
|
|
1735
|
+
args.push("--timeout", String(timeout));
|
|
1736
|
+
if (noSecretScan)
|
|
1737
|
+
args.push("--no-secret-scan");
|
|
1738
|
+
if (failOn)
|
|
1739
|
+
args.push("--fail-on", failOn);
|
|
1740
|
+
if (repoContext === false)
|
|
1741
|
+
args.push("--no-repo-context");
|
|
1742
|
+
return args;
|
|
1743
|
+
}
|
|
1744
|
+
function runCli(args, cwd) {
|
|
1745
|
+
return new Promise((resolvePromise) => {
|
|
1746
|
+
const child = spawn(process.execPath, [CLI_PATH, ...args], {
|
|
1747
|
+
cwd,
|
|
1748
|
+
windowsHide: true,
|
|
1749
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1750
|
+
env: {
|
|
1751
|
+
...process.env,
|
|
1752
|
+
NO_COLOR: "1",
|
|
1753
|
+
FORCE_COLOR: "0",
|
|
1754
|
+
},
|
|
1755
|
+
});
|
|
1756
|
+
let stdout = "";
|
|
1757
|
+
let stderr = "";
|
|
1758
|
+
child.stdout.setEncoding("utf8");
|
|
1759
|
+
child.stderr.setEncoding("utf8");
|
|
1760
|
+
child.stdout.on("data", (chunk) => {
|
|
1761
|
+
stdout += chunk;
|
|
1762
|
+
});
|
|
1763
|
+
child.stderr.on("data", (chunk) => {
|
|
1764
|
+
stderr += chunk;
|
|
1765
|
+
if (stderr.length > 40_000)
|
|
1766
|
+
stderr = stderr.slice(-40_000);
|
|
1767
|
+
});
|
|
1768
|
+
child.on("error", (error) => {
|
|
1769
|
+
resolvePromise({ exitCode: 1, stdout, stderr: `${stderr}\n${error.message}`.trim() });
|
|
1770
|
+
});
|
|
1771
|
+
child.on("close", (code) => {
|
|
1772
|
+
resolvePromise({ exitCode: code ?? 1, stdout, stderr });
|
|
1773
|
+
});
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
function parseJsonObject(text) {
|
|
1777
|
+
const trimmed = text.trim();
|
|
1778
|
+
if (!trimmed.startsWith("{"))
|
|
1779
|
+
return undefined;
|
|
1780
|
+
try {
|
|
1781
|
+
const parsed = JSON.parse(trimmed);
|
|
1782
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
1783
|
+
return parsed;
|
|
1784
|
+
}
|
|
1785
|
+
catch {
|
|
1786
|
+
// The CLI sometimes writes a plain-text error; fall through.
|
|
1787
|
+
}
|
|
1788
|
+
return undefined;
|
|
1789
|
+
}
|
|
1790
|
+
function toToolResult(result) {
|
|
1791
|
+
const parsed = parseJsonObject(result.stdout);
|
|
1792
|
+
if (parsed) {
|
|
1793
|
+
const policyFailed = result.exitCode === 3;
|
|
1794
|
+
return {
|
|
1795
|
+
content: [{ type: "text", text: JSON.stringify(parsed, null, 2) }],
|
|
1796
|
+
structuredContent: parsed,
|
|
1797
|
+
isError: result.exitCode >= 2 && !policyFailed,
|
|
1798
|
+
_meta: { exitCode: result.exitCode, policyFailed },
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
const text = result.stdout.trim() || result.stderr.trim() || `oc-pilot exited with code ${result.exitCode}.`;
|
|
1802
|
+
return {
|
|
1803
|
+
content: [{ type: "text", text }],
|
|
1804
|
+
isError: result.exitCode !== 0,
|
|
1805
|
+
_meta: { exitCode: result.exitCode },
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
async function callReview(input, cwd) {
|
|
1809
|
+
const temp = new TempFiles();
|
|
1810
|
+
try {
|
|
1811
|
+
const args = ["review", ...commonArgs(input, { repoContext: true, secrets: true, failOn: true }), "--json"];
|
|
1812
|
+
const diff = stringArg(input, "diff");
|
|
1813
|
+
const base = baseArg(input);
|
|
1814
|
+
const context = contextArg(input);
|
|
1815
|
+
if (diff)
|
|
1816
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
1817
|
+
if (base)
|
|
1818
|
+
args.push("--base", base);
|
|
1819
|
+
if (booleanArg(input, "staged"))
|
|
1820
|
+
args.push("--staged");
|
|
1821
|
+
if (booleanArg(input, "unstaged"))
|
|
1822
|
+
args.push("--unstaged");
|
|
1823
|
+
if (booleanArg(input, "include_untracked"))
|
|
1824
|
+
args.push("--include-untracked");
|
|
1825
|
+
if (context)
|
|
1826
|
+
args.push("--context", context);
|
|
1827
|
+
args.push(...projectPaths(input, "paths"));
|
|
1828
|
+
return toToolResult(await runCli(args, cwd));
|
|
1829
|
+
}
|
|
1830
|
+
finally {
|
|
1831
|
+
temp.cleanup();
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
async function callTestFix(input, cwd) {
|
|
1835
|
+
const temp = new TempFiles();
|
|
1836
|
+
try {
|
|
1837
|
+
const args = ["test-fix", ...commonArgs(input, { repoContext: true, secrets: true, failOn: true }), "--json"];
|
|
1838
|
+
const command = stringArg(input, "command");
|
|
1839
|
+
const log = stringArg(input, "log");
|
|
1840
|
+
const diff = stringArg(input, "diff");
|
|
1841
|
+
const base = baseArg(input);
|
|
1842
|
+
const context = contextArg(input);
|
|
1843
|
+
const exitCode = integerArg(input, "exit_code");
|
|
1844
|
+
if (command)
|
|
1845
|
+
args.push("--command", command);
|
|
1846
|
+
if (log)
|
|
1847
|
+
args.push("--log", temp.write("test-output.log", log));
|
|
1848
|
+
if (exitCode !== undefined)
|
|
1849
|
+
args.push("--exit-code", String(exitCode));
|
|
1850
|
+
if (booleanArg(input, "force"))
|
|
1851
|
+
args.push("--force");
|
|
1852
|
+
if (booleanArg(input, "no_diff"))
|
|
1853
|
+
args.push("--no-diff");
|
|
1854
|
+
if (diff)
|
|
1855
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
1856
|
+
if (base)
|
|
1857
|
+
args.push("--base", base);
|
|
1858
|
+
if (booleanArg(input, "staged"))
|
|
1859
|
+
args.push("--staged");
|
|
1860
|
+
if (booleanArg(input, "include_untracked"))
|
|
1861
|
+
args.push("--include-untracked");
|
|
1862
|
+
if (context)
|
|
1863
|
+
args.push("--context", context);
|
|
1864
|
+
return toToolResult(await runCli(args, cwd));
|
|
1865
|
+
}
|
|
1866
|
+
finally {
|
|
1867
|
+
temp.cleanup();
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
async function callTestGen(input, cwd) {
|
|
1871
|
+
const temp = new TempFiles();
|
|
1872
|
+
try {
|
|
1873
|
+
const args = ["test-gen", ...commonArgs(input, { repoContext: true, secrets: true, failOn: false }), "--json"];
|
|
1874
|
+
const diff = stringArg(input, "diff");
|
|
1875
|
+
const base = baseArg(input);
|
|
1876
|
+
const command = stringArg(input, "command");
|
|
1877
|
+
const context = contextArg(input);
|
|
1878
|
+
const rounds = integerArg(input, "rounds");
|
|
1879
|
+
const slopFiles = integerArg(input, "slop_files");
|
|
1880
|
+
const coverageFile = stringArg(input, "coverage_file");
|
|
1881
|
+
if (diff)
|
|
1882
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
1883
|
+
if (base)
|
|
1884
|
+
args.push("--base", base);
|
|
1885
|
+
if (booleanArg(input, "staged"))
|
|
1886
|
+
args.push("--staged");
|
|
1887
|
+
if (booleanArg(input, "unstaged"))
|
|
1888
|
+
args.push("--unstaged");
|
|
1889
|
+
if (booleanArg(input, "include_untracked"))
|
|
1890
|
+
args.push("--include-untracked");
|
|
1891
|
+
if (booleanArg(input, "apply"))
|
|
1892
|
+
args.push("--apply");
|
|
1893
|
+
if (booleanArg(input, "no_run"))
|
|
1894
|
+
args.push("--no-run");
|
|
1895
|
+
if (booleanArg(input, "keep_failing"))
|
|
1896
|
+
args.push("--keep-failing");
|
|
1897
|
+
if (booleanArg(input, "force"))
|
|
1898
|
+
args.push("--force");
|
|
1899
|
+
if (booleanArg(input, "coverage"))
|
|
1900
|
+
args.push("--coverage");
|
|
1901
|
+
if (booleanArg(input, "from_slop"))
|
|
1902
|
+
args.push("--from-slop");
|
|
1903
|
+
if (coverageFile)
|
|
1904
|
+
args.push("--coverage-file", coverageFile);
|
|
1905
|
+
if (rounds !== undefined)
|
|
1906
|
+
args.push("--rounds", String(rounds));
|
|
1907
|
+
if (slopFiles !== undefined)
|
|
1908
|
+
args.push("--slop-files", String(slopFiles));
|
|
1909
|
+
if (command)
|
|
1910
|
+
args.push("--command", command);
|
|
1911
|
+
if (context)
|
|
1912
|
+
args.push("--context", context);
|
|
1913
|
+
args.push(...projectPaths(input, "paths"));
|
|
1914
|
+
return toToolResult(await runCli(args, cwd));
|
|
1915
|
+
}
|
|
1916
|
+
finally {
|
|
1917
|
+
temp.cleanup();
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
async function callDocgen(input, cwd) {
|
|
1921
|
+
const files = projectPaths(input, "files");
|
|
1922
|
+
if (files.length === 0)
|
|
1923
|
+
throw new ToolInputError("files is required");
|
|
1924
|
+
const args = ["docgen", ...files, ...commonArgs(input, { repoContext: true, secrets: true, failOn: true }), "--json"];
|
|
1925
|
+
const symbol = stringArg(input, "symbol");
|
|
1926
|
+
const context = contextArg(input);
|
|
1927
|
+
if (symbol)
|
|
1928
|
+
args.push("--symbol", symbol);
|
|
1929
|
+
if (context)
|
|
1930
|
+
args.push("--context", context);
|
|
1931
|
+
return toToolResult(await runCli(args, cwd));
|
|
1932
|
+
}
|
|
1933
|
+
async function callCustom(input, cwd) {
|
|
1934
|
+
const instruction = stringArg(input, "instruction", true);
|
|
1935
|
+
const temp = new TempFiles();
|
|
1936
|
+
try {
|
|
1937
|
+
const args = [
|
|
1938
|
+
"custom",
|
|
1939
|
+
instruction,
|
|
1940
|
+
...commonArgs(input, { repoContext: true, secrets: true, failOn: true }),
|
|
1941
|
+
"--json",
|
|
1942
|
+
];
|
|
1943
|
+
const diff = stringArg(input, "diff");
|
|
1944
|
+
const base = baseArg(input);
|
|
1945
|
+
const context = contextArg(input);
|
|
1946
|
+
if (diff)
|
|
1947
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
1948
|
+
if (booleanArg(input, "git_diff"))
|
|
1949
|
+
args.push("--git-diff");
|
|
1950
|
+
if (base)
|
|
1951
|
+
args.push("--base", base);
|
|
1952
|
+
if (booleanArg(input, "staged"))
|
|
1953
|
+
args.push("--staged");
|
|
1954
|
+
if (booleanArg(input, "include_untracked"))
|
|
1955
|
+
args.push("--include-untracked");
|
|
1956
|
+
if (context)
|
|
1957
|
+
args.push("--context", context);
|
|
1958
|
+
return toToolResult(await runCli(args, cwd));
|
|
1959
|
+
}
|
|
1960
|
+
finally {
|
|
1961
|
+
temp.cleanup();
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
async function callFix(input, cwd) {
|
|
1965
|
+
const temp = new TempFiles();
|
|
1966
|
+
try {
|
|
1967
|
+
const args = ["fix", ...commonArgs(input, { repoContext: true, secrets: true, failOn: false }), "--json"];
|
|
1968
|
+
const diff = stringArg(input, "diff");
|
|
1969
|
+
const base = baseArg(input);
|
|
1970
|
+
const context = contextArg(input);
|
|
1971
|
+
const command = stringArg(input, "command");
|
|
1972
|
+
const rounds = integerArg(input, "rounds");
|
|
1973
|
+
if (diff)
|
|
1974
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
1975
|
+
if (base)
|
|
1976
|
+
args.push("--base", base);
|
|
1977
|
+
if (booleanArg(input, "staged"))
|
|
1978
|
+
args.push("--staged");
|
|
1979
|
+
if (booleanArg(input, "unstaged"))
|
|
1980
|
+
args.push("--unstaged");
|
|
1981
|
+
if (booleanArg(input, "include_untracked"))
|
|
1982
|
+
args.push("--include-untracked");
|
|
1983
|
+
if (context)
|
|
1984
|
+
args.push("--context", context);
|
|
1985
|
+
if (command)
|
|
1986
|
+
args.push("--command", command);
|
|
1987
|
+
if (booleanArg(input, "apply"))
|
|
1988
|
+
args.push("--apply");
|
|
1989
|
+
if (booleanArg(input, "shadow"))
|
|
1990
|
+
args.push("--shadow");
|
|
1991
|
+
if (booleanArg(input, "no_tests"))
|
|
1992
|
+
args.push("--no-tests");
|
|
1993
|
+
if (booleanArg(input, "from_sast"))
|
|
1994
|
+
args.push("--from-sast");
|
|
1995
|
+
if (booleanArg(input, "from_authz"))
|
|
1996
|
+
args.push("--from-authz");
|
|
1997
|
+
if (rounds !== undefined)
|
|
1998
|
+
args.push("--rounds", String(rounds));
|
|
1999
|
+
args.push(...projectPaths(input, "paths"));
|
|
2000
|
+
return toToolResult(await runCli(args, cwd));
|
|
2001
|
+
}
|
|
2002
|
+
finally {
|
|
2003
|
+
temp.cleanup();
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
async function callFuzz(input, cwd) {
|
|
2007
|
+
const temp = new TempFiles();
|
|
2008
|
+
try {
|
|
2009
|
+
const args = ["fuzz", "--json"];
|
|
2010
|
+
const diff = stringArg(input, "diff");
|
|
2011
|
+
const base = baseArg(input);
|
|
2012
|
+
if (diff)
|
|
2013
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2014
|
+
if (base)
|
|
2015
|
+
args.push("--base", base);
|
|
2016
|
+
if (booleanArg(input, "staged"))
|
|
2017
|
+
args.push("--staged");
|
|
2018
|
+
if (booleanArg(input, "unstaged"))
|
|
2019
|
+
args.push("--unstaged");
|
|
2020
|
+
if (booleanArg(input, "include_untracked"))
|
|
2021
|
+
args.push("--include-untracked");
|
|
2022
|
+
const duration = integerArg(input, "duration_ms");
|
|
2023
|
+
if (duration !== undefined)
|
|
2024
|
+
args.push("--duration", `${String(duration)}ms`);
|
|
2025
|
+
const memory = integerArg(input, "memory_mb");
|
|
2026
|
+
if (memory !== undefined)
|
|
2027
|
+
args.push("--memory-limit", `${String(memory)}MB`);
|
|
2028
|
+
const maxTargets = integerArg(input, "max_targets");
|
|
2029
|
+
if (maxTargets !== undefined)
|
|
2030
|
+
args.push("--max-targets", String(maxTargets));
|
|
2031
|
+
if (booleanArg(input, "fail_on_crash"))
|
|
2032
|
+
args.push("--fail-on-crash");
|
|
2033
|
+
return toToolResult(await runCli(args, cwd));
|
|
2034
|
+
}
|
|
2035
|
+
finally {
|
|
2036
|
+
temp.cleanup();
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
async function callMutate(input, cwd) {
|
|
2040
|
+
const temp = new TempFiles();
|
|
2041
|
+
try {
|
|
2042
|
+
const args = ["mutate", "--json"];
|
|
2043
|
+
const diff = stringArg(input, "diff");
|
|
2044
|
+
const base = baseArg(input);
|
|
2045
|
+
const command = stringArg(input, "command");
|
|
2046
|
+
if (diff)
|
|
2047
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2048
|
+
if (base)
|
|
2049
|
+
args.push("--base", base);
|
|
2050
|
+
if (booleanArg(input, "staged"))
|
|
2051
|
+
args.push("--staged");
|
|
2052
|
+
if (booleanArg(input, "unstaged"))
|
|
2053
|
+
args.push("--unstaged");
|
|
2054
|
+
if (booleanArg(input, "include_untracked"))
|
|
2055
|
+
args.push("--include-untracked");
|
|
2056
|
+
if (command)
|
|
2057
|
+
args.push("--command", command);
|
|
2058
|
+
const maxMutants = integerArg(input, "max_mutants");
|
|
2059
|
+
if (maxMutants !== undefined)
|
|
2060
|
+
args.push("--max-mutants", String(maxMutants));
|
|
2061
|
+
const minScore = integerArg(input, "min_score");
|
|
2062
|
+
if (minScore !== undefined)
|
|
2063
|
+
args.push("--min-score", String(minScore));
|
|
2064
|
+
args.push(...projectPaths(input, "paths"));
|
|
2065
|
+
return toToolResult(await runCli(args, cwd));
|
|
2066
|
+
}
|
|
2067
|
+
finally {
|
|
2068
|
+
temp.cleanup();
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
async function callFlaky(input, cwd) {
|
|
2072
|
+
const args = ["flaky", "--json"];
|
|
2073
|
+
const runs = integerArg(input, "runs");
|
|
2074
|
+
const command = stringArg(input, "command");
|
|
2075
|
+
const seed = stringArg(input, "seed");
|
|
2076
|
+
if (runs !== undefined)
|
|
2077
|
+
args.push("--runs", String(runs));
|
|
2078
|
+
if (command)
|
|
2079
|
+
args.push("--command", command);
|
|
2080
|
+
if (booleanArg(input, "fail_on_flaky"))
|
|
2081
|
+
args.push("--fail-on-flaky");
|
|
2082
|
+
if (booleanArg(input, "shuffle"))
|
|
2083
|
+
args.push("--shuffle");
|
|
2084
|
+
if (seed)
|
|
2085
|
+
args.push("--seed", seed);
|
|
2086
|
+
return toToolResult(await runCli(args, cwd));
|
|
2087
|
+
}
|
|
2088
|
+
async function callImpact(input, cwd) {
|
|
2089
|
+
const temp = new TempFiles();
|
|
2090
|
+
try {
|
|
2091
|
+
const args = ["impact", "--json"];
|
|
2092
|
+
const diff = stringArg(input, "diff");
|
|
2093
|
+
const base = baseArg(input);
|
|
2094
|
+
if (diff)
|
|
2095
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2096
|
+
if (base)
|
|
2097
|
+
args.push("--base", base);
|
|
2098
|
+
if (booleanArg(input, "staged"))
|
|
2099
|
+
args.push("--staged");
|
|
2100
|
+
if (booleanArg(input, "unstaged"))
|
|
2101
|
+
args.push("--unstaged");
|
|
2102
|
+
if (booleanArg(input, "include_untracked"))
|
|
2103
|
+
args.push("--include-untracked");
|
|
2104
|
+
args.push(...projectPaths(input, "paths"));
|
|
2105
|
+
return toToolResult(await runCli(args, cwd));
|
|
2106
|
+
}
|
|
2107
|
+
finally {
|
|
2108
|
+
temp.cleanup();
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
async function callDeps(input, cwd) {
|
|
2112
|
+
const args = ["deps", "--json"];
|
|
2113
|
+
const why = stringArg(input, "why");
|
|
2114
|
+
const failOnIssue = stringArg(input, "fail_on_issue");
|
|
2115
|
+
const failOnVuln = stringArg(input, "fail_on_vuln");
|
|
2116
|
+
const osvUrl = stringArg(input, "osv_url");
|
|
2117
|
+
const sbom = stringArg(input, "sbom");
|
|
2118
|
+
if (why)
|
|
2119
|
+
args.push("--why", why);
|
|
2120
|
+
if (failOnIssue)
|
|
2121
|
+
args.push("--fail-on-issue", failOnIssue);
|
|
2122
|
+
if (failOnVuln)
|
|
2123
|
+
args.push("--fail-on-vuln", failOnVuln);
|
|
2124
|
+
if (osvUrl)
|
|
2125
|
+
args.push("--osv-url", osvUrl);
|
|
2126
|
+
if (sbom)
|
|
2127
|
+
args.push("--sbom", sbom);
|
|
2128
|
+
if (booleanArg(input, "audit"))
|
|
2129
|
+
args.push("--audit");
|
|
2130
|
+
if (booleanArg(input, "offline"))
|
|
2131
|
+
args.push("--offline");
|
|
2132
|
+
if (booleanArg(input, "fix"))
|
|
2133
|
+
args.push("--fix");
|
|
2134
|
+
const ignore = stringArrayArg(input, "ignore");
|
|
2135
|
+
if (ignore.length > 0)
|
|
2136
|
+
args.push("--ignore", ignore.join(","));
|
|
2137
|
+
return toToolResult(await runCli(args, cwd));
|
|
2138
|
+
}
|
|
2139
|
+
async function callCoverage(input, cwd) {
|
|
2140
|
+
const temp = new TempFiles();
|
|
2141
|
+
try {
|
|
2142
|
+
const args = ["coverage", "--json"];
|
|
2143
|
+
const diff = stringArg(input, "diff");
|
|
2144
|
+
const base = baseArg(input);
|
|
2145
|
+
const coverageFile = stringArg(input, "coverage_file");
|
|
2146
|
+
if (diff)
|
|
2147
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2148
|
+
if (base)
|
|
2149
|
+
args.push("--base", base);
|
|
2150
|
+
if (booleanArg(input, "staged"))
|
|
2151
|
+
args.push("--staged");
|
|
2152
|
+
if (booleanArg(input, "unstaged"))
|
|
2153
|
+
args.push("--unstaged");
|
|
2154
|
+
if (booleanArg(input, "include_untracked"))
|
|
2155
|
+
args.push("--include-untracked");
|
|
2156
|
+
if (coverageFile)
|
|
2157
|
+
args.push("--coverage-file", coverageFile);
|
|
2158
|
+
const failUnder = integerArg(input, "fail_under");
|
|
2159
|
+
if (failUnder !== undefined)
|
|
2160
|
+
args.push("--fail-under", String(failUnder));
|
|
2161
|
+
const maxUncovered = signedIntegerArg(input, "max_uncovered");
|
|
2162
|
+
if (maxUncovered !== undefined)
|
|
2163
|
+
args.push(`--max-uncovered=${maxUncovered}`);
|
|
2164
|
+
args.push(...projectPaths(input, "paths"));
|
|
2165
|
+
return toToolResult(await runCli(args, cwd));
|
|
2166
|
+
}
|
|
2167
|
+
finally {
|
|
2168
|
+
temp.cleanup();
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
async function callReviewers(input, cwd) {
|
|
2172
|
+
const temp = new TempFiles();
|
|
2173
|
+
try {
|
|
2174
|
+
const args = ["reviewers", "--json"];
|
|
2175
|
+
const diff = stringArg(input, "diff");
|
|
2176
|
+
const base = baseArg(input);
|
|
2177
|
+
if (diff)
|
|
2178
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2179
|
+
if (base)
|
|
2180
|
+
args.push("--base", base);
|
|
2181
|
+
if (booleanArg(input, "staged"))
|
|
2182
|
+
args.push("--staged");
|
|
2183
|
+
if (booleanArg(input, "unstaged"))
|
|
2184
|
+
args.push("--unstaged");
|
|
2185
|
+
if (booleanArg(input, "include_untracked"))
|
|
2186
|
+
args.push("--include-untracked");
|
|
2187
|
+
const limit = integerArg(input, "limit");
|
|
2188
|
+
if (limit !== undefined)
|
|
2189
|
+
args.push("--limit", String(limit));
|
|
2190
|
+
const depth = integerArg(input, "depth");
|
|
2191
|
+
if (depth !== undefined)
|
|
2192
|
+
args.push("--depth", String(depth));
|
|
2193
|
+
const exclude = stringArrayArg(input, "exclude");
|
|
2194
|
+
if (exclude.length > 0)
|
|
2195
|
+
args.push("--exclude", exclude.join(","));
|
|
2196
|
+
args.push(...projectPaths(input, "paths"));
|
|
2197
|
+
return toToolResult(await runCli(args, cwd));
|
|
2198
|
+
}
|
|
2199
|
+
finally {
|
|
2200
|
+
temp.cleanup();
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
async function callTestGuard(input, cwd) {
|
|
2204
|
+
const temp = new TempFiles();
|
|
2205
|
+
try {
|
|
2206
|
+
const args = ["test-guard", "--json"];
|
|
2207
|
+
const diff = stringArg(input, "diff");
|
|
2208
|
+
const base = baseArg(input);
|
|
2209
|
+
if (diff)
|
|
2210
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2211
|
+
if (base)
|
|
2212
|
+
args.push("--base", base);
|
|
2213
|
+
if (booleanArg(input, "staged"))
|
|
2214
|
+
args.push("--staged");
|
|
2215
|
+
if (booleanArg(input, "unstaged"))
|
|
2216
|
+
args.push("--unstaged");
|
|
2217
|
+
if (booleanArg(input, "include_untracked"))
|
|
2218
|
+
args.push("--include-untracked");
|
|
2219
|
+
if (booleanArg(input, "fail_on_weakening"))
|
|
2220
|
+
args.push("--fail-on-weakening");
|
|
2221
|
+
args.push(...projectPaths(input, "paths"));
|
|
2222
|
+
return toToolResult(await runCli(args, cwd));
|
|
2223
|
+
}
|
|
2224
|
+
finally {
|
|
2225
|
+
temp.cleanup();
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
async function callEnvSync(input, cwd) {
|
|
2229
|
+
const temp = new TempFiles();
|
|
2230
|
+
try {
|
|
2231
|
+
const args = ["env-sync", "--json"];
|
|
2232
|
+
const diff = stringArg(input, "diff");
|
|
2233
|
+
const base = baseArg(input);
|
|
2234
|
+
if (diff)
|
|
2235
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2236
|
+
if (base)
|
|
2237
|
+
args.push("--base", base);
|
|
2238
|
+
if (booleanArg(input, "staged"))
|
|
2239
|
+
args.push("--staged");
|
|
2240
|
+
if (booleanArg(input, "unstaged"))
|
|
2241
|
+
args.push("--unstaged");
|
|
2242
|
+
if (booleanArg(input, "include_untracked"))
|
|
2243
|
+
args.push("--include-untracked");
|
|
2244
|
+
if (booleanArg(input, "fail_on_missing"))
|
|
2245
|
+
args.push("--fail-on-missing");
|
|
2246
|
+
args.push(...projectPaths(input, "paths"));
|
|
2247
|
+
return toToolResult(await runCli(args, cwd));
|
|
2248
|
+
}
|
|
2249
|
+
finally {
|
|
2250
|
+
temp.cleanup();
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
async function callMockLeak(input, cwd) {
|
|
2254
|
+
const temp = new TempFiles();
|
|
2255
|
+
try {
|
|
2256
|
+
const args = ["mock-leak", "--json"];
|
|
2257
|
+
const diff = stringArg(input, "diff");
|
|
2258
|
+
const base = baseArg(input);
|
|
2259
|
+
if (diff)
|
|
2260
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2261
|
+
if (base)
|
|
2262
|
+
args.push("--base", base);
|
|
2263
|
+
if (booleanArg(input, "staged"))
|
|
2264
|
+
args.push("--staged");
|
|
2265
|
+
if (booleanArg(input, "unstaged"))
|
|
2266
|
+
args.push("--unstaged");
|
|
2267
|
+
if (booleanArg(input, "include_untracked"))
|
|
2268
|
+
args.push("--include-untracked");
|
|
2269
|
+
const failOn = stringArg(input, "fail_on_leak");
|
|
2270
|
+
if (failOn)
|
|
2271
|
+
args.push("--fail-on-leak", failOn);
|
|
2272
|
+
args.push(...projectPaths(input, "paths"));
|
|
2273
|
+
return toToolResult(await runCli(args, cwd));
|
|
2274
|
+
}
|
|
2275
|
+
finally {
|
|
2276
|
+
temp.cleanup();
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
async function callEcosystemVerify(input, cwd) {
|
|
2280
|
+
const temp = new TempFiles();
|
|
2281
|
+
try {
|
|
2282
|
+
const args = ["ecosystem-verify", "--json"];
|
|
2283
|
+
const diff = stringArg(input, "diff");
|
|
2284
|
+
const base = baseArg(input);
|
|
2285
|
+
if (diff)
|
|
2286
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2287
|
+
if (base)
|
|
2288
|
+
args.push("--base", base);
|
|
2289
|
+
if (booleanArg(input, "staged"))
|
|
2290
|
+
args.push("--staged");
|
|
2291
|
+
if (booleanArg(input, "unstaged"))
|
|
2292
|
+
args.push("--unstaged");
|
|
2293
|
+
if (booleanArg(input, "include_untracked"))
|
|
2294
|
+
args.push("--include-untracked");
|
|
2295
|
+
if (booleanArg(input, "online"))
|
|
2296
|
+
args.push("--online");
|
|
2297
|
+
const minAgeDays = integerArg(input, "min_age_days");
|
|
2298
|
+
if (minAgeDays !== undefined)
|
|
2299
|
+
args.push("--min-age-days", String(minAgeDays));
|
|
2300
|
+
const minDownloads = integerArg(input, "min_downloads");
|
|
2301
|
+
if (minDownloads !== undefined)
|
|
2302
|
+
args.push("--min-downloads", String(minDownloads));
|
|
2303
|
+
const failOn = stringArg(input, "fail_on_issue");
|
|
2304
|
+
if (failOn)
|
|
2305
|
+
args.push("--fail-on-issue", failOn);
|
|
2306
|
+
args.push(...projectPaths(input, "paths"));
|
|
2307
|
+
return toToolResult(await runCli(args, cwd));
|
|
2308
|
+
}
|
|
2309
|
+
finally {
|
|
2310
|
+
temp.cleanup();
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
async function callPack(input, cwd) {
|
|
2314
|
+
const temp = new TempFiles();
|
|
2315
|
+
try {
|
|
2316
|
+
const task = stringArg(input, "task", true);
|
|
2317
|
+
const args = ["pack", task, "--json"];
|
|
2318
|
+
const diff = stringArg(input, "diff");
|
|
2319
|
+
const base = baseArg(input);
|
|
2320
|
+
if (diff)
|
|
2321
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2322
|
+
if (base)
|
|
2323
|
+
args.push("--base", base);
|
|
2324
|
+
if (booleanArg(input, "staged"))
|
|
2325
|
+
args.push("--staged");
|
|
2326
|
+
if (booleanArg(input, "unstaged"))
|
|
2327
|
+
args.push("--unstaged");
|
|
2328
|
+
if (booleanArg(input, "include_untracked"))
|
|
2329
|
+
args.push("--include-untracked");
|
|
2330
|
+
const budget = integerArg(input, "budget");
|
|
2331
|
+
if (budget !== undefined)
|
|
2332
|
+
args.push("--budget", String(budget));
|
|
2333
|
+
const testLog = stringArg(input, "test_log");
|
|
2334
|
+
if (testLog)
|
|
2335
|
+
args.push("--test-log", testLog);
|
|
2336
|
+
const rules = stringArg(input, "rules");
|
|
2337
|
+
if (rules)
|
|
2338
|
+
args.push("--rules", rules);
|
|
2339
|
+
args.push(...projectPaths(input, "paths"));
|
|
2340
|
+
return toToolResult(await runCli(args, cwd));
|
|
2341
|
+
}
|
|
2342
|
+
finally {
|
|
2343
|
+
temp.cleanup();
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
async function callPackHologram(input, cwd) {
|
|
2347
|
+
const temp = new TempFiles();
|
|
2348
|
+
try {
|
|
2349
|
+
const args = ["pack", "--hologram", "--json"];
|
|
2350
|
+
const task = stringArg(input, "task");
|
|
2351
|
+
if (task)
|
|
2352
|
+
args.push(task);
|
|
2353
|
+
const diff = stringArg(input, "diff");
|
|
2354
|
+
const base = baseArg(input);
|
|
2355
|
+
if (diff)
|
|
2356
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2357
|
+
if (base)
|
|
2358
|
+
args.push("--base", base);
|
|
2359
|
+
if (booleanArg(input, "staged"))
|
|
2360
|
+
args.push("--staged");
|
|
2361
|
+
if (booleanArg(input, "unstaged"))
|
|
2362
|
+
args.push("--unstaged");
|
|
2363
|
+
if (booleanArg(input, "include_untracked"))
|
|
2364
|
+
args.push("--include-untracked");
|
|
2365
|
+
const maxTokens = integerArg(input, "max_tokens");
|
|
2366
|
+
if (maxTokens !== undefined)
|
|
2367
|
+
args.push("--max-tokens", String(maxTokens));
|
|
2368
|
+
args.push(...projectPaths(input, "paths"));
|
|
2369
|
+
return toToolResult(await runCli(args, cwd));
|
|
2370
|
+
}
|
|
2371
|
+
finally {
|
|
2372
|
+
temp.cleanup();
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
async function callZoomSymbol(input, cwd) {
|
|
2376
|
+
const file = stringArg(input, "file", true);
|
|
2377
|
+
const symbol = stringArg(input, "symbol", true);
|
|
2378
|
+
return toToolResult(await runCli(["zoom-symbol", file, symbol, "--json"], cwd));
|
|
2379
|
+
}
|
|
2380
|
+
async function callInvariants(input, cwd) {
|
|
2381
|
+
const temp = new TempFiles();
|
|
2382
|
+
try {
|
|
2383
|
+
const args = ["invariants", "--json"];
|
|
2384
|
+
const diff = stringArg(input, "diff");
|
|
2385
|
+
const base = baseArg(input);
|
|
2386
|
+
if (diff)
|
|
2387
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2388
|
+
if (base)
|
|
2389
|
+
args.push("--base", base);
|
|
2390
|
+
if (booleanArg(input, "staged"))
|
|
2391
|
+
args.push("--staged");
|
|
2392
|
+
if (booleanArg(input, "unstaged"))
|
|
2393
|
+
args.push("--unstaged");
|
|
2394
|
+
if (booleanArg(input, "include_untracked"))
|
|
2395
|
+
args.push("--include-untracked");
|
|
2396
|
+
if (booleanArg(input, "strict"))
|
|
2397
|
+
args.push("--strict");
|
|
2398
|
+
const failOn = stringArg(input, "fail_on");
|
|
2399
|
+
if (failOn)
|
|
2400
|
+
args.push("--fail-on", failOn);
|
|
2401
|
+
if (booleanArg(input, "no_cache"))
|
|
2402
|
+
args.push("--no-cache");
|
|
2403
|
+
return toToolResult(await runCli(args, cwd));
|
|
2404
|
+
}
|
|
2405
|
+
finally {
|
|
2406
|
+
temp.cleanup();
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
async function callBankruptcy(input, cwd) {
|
|
2410
|
+
const args = ["bankruptcy", "--json"];
|
|
2411
|
+
const historyDays = integerArg(input, "history_days");
|
|
2412
|
+
if (historyDays !== undefined)
|
|
2413
|
+
args.push("--history", `${String(historyDays)}d`);
|
|
2414
|
+
if (booleanArg(input, "explain"))
|
|
2415
|
+
args.push("--explain");
|
|
2416
|
+
return toToolResult(await runCli(args, cwd));
|
|
2417
|
+
}
|
|
2418
|
+
async function callQueryAst(input, cwd) {
|
|
2419
|
+
const query = stringArg(input, "query", true);
|
|
2420
|
+
const args = ["query", query, "--json"];
|
|
2421
|
+
const limit = integerArg(input, "limit");
|
|
2422
|
+
if (limit !== undefined)
|
|
2423
|
+
args.push("--limit", String(limit));
|
|
2424
|
+
return toToolResult(await runCli(args, cwd));
|
|
2425
|
+
}
|
|
2426
|
+
async function callSandbox(input, cwd) {
|
|
2427
|
+
const file = stringArg(input, "file", true);
|
|
2428
|
+
const args = ["sandbox", "run", file, "--json"];
|
|
2429
|
+
const timeout = integerArg(input, "timeout_ms");
|
|
2430
|
+
if (timeout !== undefined)
|
|
2431
|
+
args.push("--timeout", `${String(timeout)}ms`);
|
|
2432
|
+
const memory = integerArg(input, "memory_mb");
|
|
2433
|
+
if (memory !== undefined)
|
|
2434
|
+
args.push("--memory-limit", `${String(memory)}MB`);
|
|
2435
|
+
if (booleanArg(input, "allow_unsafe_exec"))
|
|
2436
|
+
args.push("--allow-unsafe-exec");
|
|
2437
|
+
return toToolResult(await runCli(args, cwd));
|
|
2438
|
+
}
|
|
2439
|
+
async function callHivemindStatus(_input, cwd) {
|
|
2440
|
+
return toToolResult(await runCli(["hivemind", "status", "--json"], cwd));
|
|
2441
|
+
}
|
|
2442
|
+
async function callContractDrift(input, cwd) {
|
|
2443
|
+
const temp = new TempFiles();
|
|
2444
|
+
try {
|
|
2445
|
+
const args = ["contract-drift", "--json"];
|
|
2446
|
+
const diff = stringArg(input, "diff");
|
|
2447
|
+
const base = baseArg(input);
|
|
2448
|
+
if (diff)
|
|
2449
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2450
|
+
if (base)
|
|
2451
|
+
args.push("--base", base);
|
|
2452
|
+
if (booleanArg(input, "staged"))
|
|
2453
|
+
args.push("--staged");
|
|
2454
|
+
if (booleanArg(input, "unstaged"))
|
|
2455
|
+
args.push("--unstaged");
|
|
2456
|
+
if (booleanArg(input, "include_untracked"))
|
|
2457
|
+
args.push("--include-untracked");
|
|
2458
|
+
const failOn = stringArg(input, "fail_on_drift");
|
|
2459
|
+
if (failOn)
|
|
2460
|
+
args.push("--fail-on-drift", failOn);
|
|
2461
|
+
args.push(...projectPaths(input, "paths"));
|
|
2462
|
+
return toToolResult(await runCli(args, cwd));
|
|
2463
|
+
}
|
|
2464
|
+
finally {
|
|
2465
|
+
temp.cleanup();
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
async function callAntiRegression(input, cwd) {
|
|
2469
|
+
const temp = new TempFiles();
|
|
2470
|
+
try {
|
|
2471
|
+
const args = ["anti-regression", "--json"];
|
|
2472
|
+
const diff = stringArg(input, "diff");
|
|
2473
|
+
const base = baseArg(input);
|
|
2474
|
+
if (diff)
|
|
2475
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2476
|
+
if (base)
|
|
2477
|
+
args.push("--base", base);
|
|
2478
|
+
if (booleanArg(input, "staged"))
|
|
2479
|
+
args.push("--staged");
|
|
2480
|
+
if (booleanArg(input, "unstaged"))
|
|
2481
|
+
args.push("--unstaged");
|
|
2482
|
+
if (booleanArg(input, "include_untracked"))
|
|
2483
|
+
args.push("--include-untracked");
|
|
2484
|
+
const maxCommits = integerArg(input, "max_commits");
|
|
2485
|
+
if (maxCommits !== undefined)
|
|
2486
|
+
args.push("--max-commits", String(maxCommits));
|
|
2487
|
+
const since = stringArg(input, "since");
|
|
2488
|
+
if (since)
|
|
2489
|
+
args.push("--since", since);
|
|
2490
|
+
if (booleanArg(input, "fail_on_regression"))
|
|
2491
|
+
args.push("--fail-on-regression");
|
|
2492
|
+
args.push(...projectPaths(input, "paths"));
|
|
2493
|
+
return toToolResult(await runCli(args, cwd));
|
|
2494
|
+
}
|
|
2495
|
+
finally {
|
|
2496
|
+
temp.cleanup();
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
async function callBoundaries(input, cwd) {
|
|
2500
|
+
const temp = new TempFiles();
|
|
2501
|
+
try {
|
|
2502
|
+
const args = ["boundaries", "--json"];
|
|
2503
|
+
const diff = stringArg(input, "diff");
|
|
2504
|
+
const base = baseArg(input);
|
|
2505
|
+
const config = stringArg(input, "config");
|
|
2506
|
+
if (diff)
|
|
2507
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2508
|
+
if (base)
|
|
2509
|
+
args.push("--base", base);
|
|
2510
|
+
if (booleanArg(input, "staged"))
|
|
2511
|
+
args.push("--staged");
|
|
2512
|
+
if (booleanArg(input, "unstaged"))
|
|
2513
|
+
args.push("--unstaged");
|
|
2514
|
+
if (booleanArg(input, "include_untracked"))
|
|
2515
|
+
args.push("--include-untracked");
|
|
2516
|
+
if (config)
|
|
2517
|
+
args.push("--config", config);
|
|
2518
|
+
if (booleanArg(input, "fail_on_violation"))
|
|
2519
|
+
args.push("--fail-on-violation");
|
|
2520
|
+
args.push(...projectPaths(input, "paths"));
|
|
2521
|
+
return toToolResult(await runCli(args, cwd));
|
|
2522
|
+
}
|
|
2523
|
+
finally {
|
|
2524
|
+
temp.cleanup();
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
async function callTestPlan(input, cwd) {
|
|
2528
|
+
const temp = new TempFiles();
|
|
2529
|
+
try {
|
|
2530
|
+
const args = ["test-plan", "--json"];
|
|
2531
|
+
const diff = stringArg(input, "diff");
|
|
2532
|
+
const base = baseArg(input);
|
|
2533
|
+
const strategy = stringArg(input, "strategy");
|
|
2534
|
+
const coverageFile = stringArg(input, "coverage_file");
|
|
2535
|
+
if (diff)
|
|
2536
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2537
|
+
if (base)
|
|
2538
|
+
args.push("--base", base);
|
|
2539
|
+
if (booleanArg(input, "staged"))
|
|
2540
|
+
args.push("--staged");
|
|
2541
|
+
if (booleanArg(input, "unstaged"))
|
|
2542
|
+
args.push("--unstaged");
|
|
2543
|
+
if (booleanArg(input, "include_untracked"))
|
|
2544
|
+
args.push("--include-untracked");
|
|
2545
|
+
if (strategy)
|
|
2546
|
+
args.push("--strategy", strategy);
|
|
2547
|
+
const maxTests = integerArg(input, "max_tests");
|
|
2548
|
+
if (maxTests !== undefined)
|
|
2549
|
+
args.push("--max-tests", String(maxTests));
|
|
2550
|
+
if (booleanArg(input, "fail_on_uncovered"))
|
|
2551
|
+
args.push("--fail-on-uncovered");
|
|
2552
|
+
if (coverageFile)
|
|
2553
|
+
args.push("--coverage-file", coverageFile);
|
|
2554
|
+
args.push(...projectPaths(input, "paths"));
|
|
2555
|
+
return toToolResult(await runCli(args, cwd));
|
|
2556
|
+
}
|
|
2557
|
+
finally {
|
|
2558
|
+
temp.cleanup();
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
async function callComplianceVerify(input, cwd) {
|
|
2562
|
+
const args = ["compliance", "--verify", "--json"];
|
|
2563
|
+
const manifest = stringArg(input, "manifest");
|
|
2564
|
+
const publicKey = stringArg(input, "public_key");
|
|
2565
|
+
if (manifest)
|
|
2566
|
+
args.push("--manifest", manifest);
|
|
2567
|
+
if (publicKey)
|
|
2568
|
+
args.push("--public-key", publicKey);
|
|
2569
|
+
return toToolResult(await runCli(args, cwd));
|
|
2570
|
+
}
|
|
2571
|
+
async function callBrag(input, cwd) {
|
|
2572
|
+
const args = ["brag", "--json"];
|
|
2573
|
+
const title = stringArg(input, "title");
|
|
2574
|
+
const watermark = stringArg(input, "watermark");
|
|
2575
|
+
const sinceDays = integerArg(input, "since_days");
|
|
2576
|
+
if (title)
|
|
2577
|
+
args.push("--title", title);
|
|
2578
|
+
if (watermark)
|
|
2579
|
+
args.push("--watermark", watermark);
|
|
2580
|
+
if (booleanArg(input, "no_paths"))
|
|
2581
|
+
args.push("--no-paths");
|
|
2582
|
+
if (sinceDays !== undefined)
|
|
2583
|
+
args.push("--since-days", String(sinceDays));
|
|
2584
|
+
if (booleanArg(input, "svg"))
|
|
2585
|
+
args.push("--svg");
|
|
2586
|
+
return toToolResult(await runCli(args, cwd));
|
|
2587
|
+
}
|
|
2588
|
+
async function callBattle(input, cwd) {
|
|
2589
|
+
const temp = new TempFiles();
|
|
2590
|
+
try {
|
|
2591
|
+
const task = stringArg(input, "task", true);
|
|
2592
|
+
const args = ["battle", task, "--json"];
|
|
2593
|
+
const diff = stringArg(input, "diff");
|
|
2594
|
+
const base = baseArg(input);
|
|
2595
|
+
const command = stringArg(input, "command");
|
|
2596
|
+
const context = stringArg(input, "context");
|
|
2597
|
+
const rounds = integerArg(input, "rounds");
|
|
2598
|
+
if (diff)
|
|
2599
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2600
|
+
if (base)
|
|
2601
|
+
args.push("--base", base);
|
|
2602
|
+
if (booleanArg(input, "staged"))
|
|
2603
|
+
args.push("--staged");
|
|
2604
|
+
if (booleanArg(input, "unstaged"))
|
|
2605
|
+
args.push("--unstaged");
|
|
2606
|
+
if (booleanArg(input, "include_untracked"))
|
|
2607
|
+
args.push("--include-untracked");
|
|
2608
|
+
if (command)
|
|
2609
|
+
args.push("--command", command);
|
|
2610
|
+
if (context)
|
|
2611
|
+
args.push("--context", context);
|
|
2612
|
+
if (rounds !== undefined)
|
|
2613
|
+
args.push("--rounds", String(rounds));
|
|
2614
|
+
if (booleanArg(input, "apply"))
|
|
2615
|
+
args.push("--apply");
|
|
2616
|
+
if (booleanArg(input, "shadow"))
|
|
2617
|
+
args.push("--shadow");
|
|
2618
|
+
const model = stringArg(input, "model");
|
|
2619
|
+
if (model)
|
|
2620
|
+
args.push("--model", model);
|
|
2621
|
+
const timeout = integerArg(input, "timeout");
|
|
2622
|
+
if (timeout !== undefined)
|
|
2623
|
+
args.push("--timeout", String(timeout));
|
|
2624
|
+
return toToolResult(await runCli(args, cwd));
|
|
2625
|
+
}
|
|
2626
|
+
finally {
|
|
2627
|
+
temp.cleanup();
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
async function callCleanup(input, cwd) {
|
|
2631
|
+
const temp = new TempFiles();
|
|
2632
|
+
try {
|
|
2633
|
+
const args = ["cleanup", "--json"];
|
|
2634
|
+
const diff = stringArg(input, "diff");
|
|
2635
|
+
const base = baseArg(input);
|
|
2636
|
+
const failOnTask = stringArg(input, "fail_on_task");
|
|
2637
|
+
if (diff)
|
|
2638
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2639
|
+
if (base)
|
|
2640
|
+
args.push("--base", base);
|
|
2641
|
+
if (booleanArg(input, "staged"))
|
|
2642
|
+
args.push("--staged");
|
|
2643
|
+
if (booleanArg(input, "unstaged"))
|
|
2644
|
+
args.push("--unstaged");
|
|
2645
|
+
if (booleanArg(input, "include_untracked"))
|
|
2646
|
+
args.push("--include-untracked");
|
|
2647
|
+
if (failOnTask)
|
|
2648
|
+
args.push("--fail-on-task", failOnTask);
|
|
2649
|
+
if (booleanArg(input, "apply"))
|
|
2650
|
+
args.push("--apply");
|
|
2651
|
+
args.push(...projectPaths(input, "paths"));
|
|
2652
|
+
return toToolResult(await runCli(args, cwd));
|
|
315
2653
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
function toToolResult(result) {
|
|
319
|
-
const parsed = parseJsonObject(result.stdout);
|
|
320
|
-
if (parsed) {
|
|
321
|
-
const policyFailed = result.exitCode === 3;
|
|
322
|
-
return {
|
|
323
|
-
content: [{ type: "text", text: JSON.stringify(parsed, null, 2) }],
|
|
324
|
-
structuredContent: parsed,
|
|
325
|
-
isError: result.exitCode >= 2 && !policyFailed,
|
|
326
|
-
_meta: { exitCode: result.exitCode, policyFailed },
|
|
327
|
-
};
|
|
2654
|
+
finally {
|
|
2655
|
+
temp.cleanup();
|
|
328
2656
|
}
|
|
329
|
-
const text = result.stdout.trim() || result.stderr.trim() || `oc-pilot exited with code ${result.exitCode}.`;
|
|
330
|
-
return {
|
|
331
|
-
content: [{ type: "text", text }],
|
|
332
|
-
isError: result.exitCode !== 0,
|
|
333
|
-
_meta: { exitCode: result.exitCode },
|
|
334
|
-
};
|
|
335
2657
|
}
|
|
336
|
-
async function
|
|
2658
|
+
async function callArchDiff(input, cwd) {
|
|
337
2659
|
const temp = new TempFiles();
|
|
338
2660
|
try {
|
|
339
|
-
const args = ["
|
|
2661
|
+
const args = ["arch-diff", "--json"];
|
|
340
2662
|
const diff = stringArg(input, "diff");
|
|
341
2663
|
const base = baseArg(input);
|
|
342
|
-
const context = contextArg(input);
|
|
343
2664
|
if (diff)
|
|
344
2665
|
args.push("--diff", temp.write("changes.diff", diff));
|
|
345
2666
|
if (base)
|
|
@@ -350,8 +2671,11 @@ async function callReview(input, cwd) {
|
|
|
350
2671
|
args.push("--unstaged");
|
|
351
2672
|
if (booleanArg(input, "include_untracked"))
|
|
352
2673
|
args.push("--include-untracked");
|
|
353
|
-
|
|
354
|
-
|
|
2674
|
+
const maxEdges = integerArg(input, "max_edges");
|
|
2675
|
+
if (maxEdges !== undefined)
|
|
2676
|
+
args.push("--max-edges", String(maxEdges));
|
|
2677
|
+
if (booleanArg(input, "fail_on_removed"))
|
|
2678
|
+
args.push("--fail-on-removed");
|
|
355
2679
|
args.push(...projectPaths(input, "paths"));
|
|
356
2680
|
return toToolResult(await runCli(args, cwd));
|
|
357
2681
|
}
|
|
@@ -359,95 +2683,153 @@ async function callReview(input, cwd) {
|
|
|
359
2683
|
temp.cleanup();
|
|
360
2684
|
}
|
|
361
2685
|
}
|
|
362
|
-
async function
|
|
2686
|
+
async function callRoast(input, cwd) {
|
|
363
2687
|
const temp = new TempFiles();
|
|
364
2688
|
try {
|
|
365
|
-
const args = ["
|
|
366
|
-
const command = stringArg(input, "command");
|
|
367
|
-
const log = stringArg(input, "log");
|
|
2689
|
+
const args = ["roast", ...commonArgs(input, { repoContext: true, secrets: true, failOn: false }), "--json"];
|
|
368
2690
|
const diff = stringArg(input, "diff");
|
|
369
2691
|
const base = baseArg(input);
|
|
370
2692
|
const context = contextArg(input);
|
|
371
|
-
const
|
|
372
|
-
if (command)
|
|
373
|
-
args.push("--command", command);
|
|
374
|
-
if (log)
|
|
375
|
-
args.push("--log", temp.write("test-output.log", log));
|
|
376
|
-
if (exitCode !== undefined)
|
|
377
|
-
args.push("--exit-code", String(exitCode));
|
|
378
|
-
if (booleanArg(input, "force"))
|
|
379
|
-
args.push("--force");
|
|
380
|
-
if (booleanArg(input, "no_diff"))
|
|
381
|
-
args.push("--no-diff");
|
|
2693
|
+
const persona = stringArg(input, "persona");
|
|
382
2694
|
if (diff)
|
|
383
2695
|
args.push("--diff", temp.write("changes.diff", diff));
|
|
384
2696
|
if (base)
|
|
385
2697
|
args.push("--base", base);
|
|
386
2698
|
if (booleanArg(input, "staged"))
|
|
387
2699
|
args.push("--staged");
|
|
2700
|
+
if (booleanArg(input, "unstaged"))
|
|
2701
|
+
args.push("--unstaged");
|
|
388
2702
|
if (booleanArg(input, "include_untracked"))
|
|
389
2703
|
args.push("--include-untracked");
|
|
390
2704
|
if (context)
|
|
391
2705
|
args.push("--context", context);
|
|
2706
|
+
if (persona)
|
|
2707
|
+
args.push("--persona", persona);
|
|
392
2708
|
return toToolResult(await runCli(args, cwd));
|
|
393
2709
|
}
|
|
394
2710
|
finally {
|
|
395
2711
|
temp.cleanup();
|
|
396
2712
|
}
|
|
397
2713
|
}
|
|
398
|
-
async function
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (
|
|
406
|
-
args.push("--
|
|
407
|
-
if (
|
|
408
|
-
args.push("--
|
|
2714
|
+
async function callCinema(input, cwd) {
|
|
2715
|
+
const args = ["cinema", "--json"];
|
|
2716
|
+
const title = stringArg(input, "title");
|
|
2717
|
+
const sinceDays = integerArg(input, "since_days");
|
|
2718
|
+
const maxFrames = integerArg(input, "max_frames");
|
|
2719
|
+
if (title)
|
|
2720
|
+
args.push("--title", title);
|
|
2721
|
+
if (sinceDays !== undefined)
|
|
2722
|
+
args.push("--since-days", String(sinceDays));
|
|
2723
|
+
if (maxFrames !== undefined)
|
|
2724
|
+
args.push("--max-frames", String(maxFrames));
|
|
2725
|
+
if (booleanArg(input, "no_paths"))
|
|
2726
|
+
args.push("--no-paths");
|
|
2727
|
+
if (booleanArg(input, "cast"))
|
|
2728
|
+
args.push("--cast");
|
|
409
2729
|
return toToolResult(await runCli(args, cwd));
|
|
410
2730
|
}
|
|
411
|
-
async function
|
|
412
|
-
const
|
|
2731
|
+
async function callBounty(input, cwd) {
|
|
2732
|
+
const args = ["bounty", "--json"];
|
|
2733
|
+
const limit = integerArg(input, "limit");
|
|
2734
|
+
const sinceDays = integerArg(input, "since_days");
|
|
2735
|
+
if (limit !== undefined)
|
|
2736
|
+
args.push("--limit", String(limit));
|
|
2737
|
+
if (sinceDays !== undefined)
|
|
2738
|
+
args.push("--since-days", String(sinceDays));
|
|
2739
|
+
return toToolResult(await runCli(args, cwd));
|
|
2740
|
+
}
|
|
2741
|
+
async function callAutopsy(input, cwd) {
|
|
2742
|
+
const args = ["autopsy", stringArg(input, "commit") ?? "HEAD", "--json"];
|
|
2743
|
+
const depth = integerArg(input, "depth");
|
|
2744
|
+
if (depth !== undefined)
|
|
2745
|
+
args.push("--depth", String(depth));
|
|
2746
|
+
if (booleanArg(input, "fail_on_risk"))
|
|
2747
|
+
args.push("--fail-on-risk");
|
|
2748
|
+
return toToolResult(await runCli(args, cwd));
|
|
2749
|
+
}
|
|
2750
|
+
async function callEcho(input, cwd) {
|
|
2751
|
+
const args = ["echo", "--json"];
|
|
2752
|
+
const name = stringArg(input, "name");
|
|
2753
|
+
const sinceDays = integerArg(input, "since_days");
|
|
2754
|
+
const endpoint = stringArg(input, "endpoint");
|
|
2755
|
+
if (name)
|
|
2756
|
+
args.push("--name", name);
|
|
2757
|
+
if (sinceDays !== undefined)
|
|
2758
|
+
args.push("--since-days", String(sinceDays));
|
|
2759
|
+
if (booleanArg(input, "submit"))
|
|
2760
|
+
args.push("--submit");
|
|
2761
|
+
if (endpoint)
|
|
2762
|
+
args.push("--endpoint", endpoint);
|
|
2763
|
+
if (booleanArg(input, "svg"))
|
|
2764
|
+
args.push("--svg");
|
|
2765
|
+
return toToolResult(await runCli(args, cwd));
|
|
2766
|
+
}
|
|
2767
|
+
async function callScan(input, cwd) {
|
|
413
2768
|
const temp = new TempFiles();
|
|
414
2769
|
try {
|
|
415
|
-
const args = [
|
|
416
|
-
"custom",
|
|
417
|
-
instruction,
|
|
418
|
-
...commonArgs(input, { repoContext: true, secrets: true, failOn: true }),
|
|
419
|
-
"--json",
|
|
420
|
-
];
|
|
2770
|
+
const args = ["scan", "--json"];
|
|
421
2771
|
const diff = stringArg(input, "diff");
|
|
422
2772
|
const base = baseArg(input);
|
|
423
|
-
const context = contextArg(input);
|
|
424
2773
|
if (diff)
|
|
425
2774
|
args.push("--diff", temp.write("changes.diff", diff));
|
|
426
|
-
if (booleanArg(input, "git_diff"))
|
|
427
|
-
args.push("--git-diff");
|
|
428
2775
|
if (base)
|
|
429
2776
|
args.push("--base", base);
|
|
430
2777
|
if (booleanArg(input, "staged"))
|
|
431
2778
|
args.push("--staged");
|
|
2779
|
+
if (booleanArg(input, "unstaged"))
|
|
2780
|
+
args.push("--unstaged");
|
|
2781
|
+
if (booleanArg(input, "include_untracked"))
|
|
2782
|
+
args.push("--include-untracked");
|
|
2783
|
+
if (booleanArg(input, "strict"))
|
|
2784
|
+
args.push("--strict");
|
|
2785
|
+
if (booleanArg(input, "all"))
|
|
2786
|
+
args.push("--all");
|
|
2787
|
+
args.push(...projectPaths(input, "paths"));
|
|
2788
|
+
return toToolResult(await runCli(args, cwd));
|
|
2789
|
+
}
|
|
2790
|
+
finally {
|
|
2791
|
+
temp.cleanup();
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
async function callCommit(input, cwd) {
|
|
2795
|
+
const temp = new TempFiles();
|
|
2796
|
+
try {
|
|
2797
|
+
const args = ["commit", "--json"];
|
|
2798
|
+
const diff = stringArg(input, "diff");
|
|
2799
|
+
const base = baseArg(input);
|
|
2800
|
+
if (diff)
|
|
2801
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2802
|
+
if (base)
|
|
2803
|
+
args.push("--base", base);
|
|
2804
|
+
if (booleanArg(input, "unstaged"))
|
|
2805
|
+
args.push("--unstaged");
|
|
432
2806
|
if (booleanArg(input, "include_untracked"))
|
|
433
2807
|
args.push("--include-untracked");
|
|
2808
|
+
if (booleanArg(input, "allow_secrets"))
|
|
2809
|
+
args.push("--allow-secrets");
|
|
2810
|
+
if (booleanArg(input, "apply"))
|
|
2811
|
+
args.push("--apply");
|
|
2812
|
+
const context = stringArg(input, "context");
|
|
434
2813
|
if (context)
|
|
435
2814
|
args.push("--context", context);
|
|
2815
|
+
const model = stringArg(input, "model");
|
|
2816
|
+
if (model)
|
|
2817
|
+
args.push("--model", model);
|
|
2818
|
+
const lang = stringArg(input, "lang");
|
|
2819
|
+
if (lang)
|
|
2820
|
+
args.push("--lang", lang);
|
|
436
2821
|
return toToolResult(await runCli(args, cwd));
|
|
437
2822
|
}
|
|
438
2823
|
finally {
|
|
439
2824
|
temp.cleanup();
|
|
440
2825
|
}
|
|
441
2826
|
}
|
|
442
|
-
async function
|
|
2827
|
+
async function callPreflight(input, cwd) {
|
|
443
2828
|
const temp = new TempFiles();
|
|
444
2829
|
try {
|
|
445
|
-
const args = ["
|
|
2830
|
+
const args = ["preflight", "--json"];
|
|
446
2831
|
const diff = stringArg(input, "diff");
|
|
447
2832
|
const base = baseArg(input);
|
|
448
|
-
const context = contextArg(input);
|
|
449
|
-
const command = stringArg(input, "command");
|
|
450
|
-
const rounds = integerArg(input, "rounds");
|
|
451
2833
|
if (diff)
|
|
452
2834
|
args.push("--diff", temp.write("changes.diff", diff));
|
|
453
2835
|
if (base)
|
|
@@ -458,16 +2840,27 @@ async function callFix(input, cwd) {
|
|
|
458
2840
|
args.push("--unstaged");
|
|
459
2841
|
if (booleanArg(input, "include_untracked"))
|
|
460
2842
|
args.push("--include-untracked");
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
if (
|
|
466
|
-
args.push(
|
|
2843
|
+
const since = stringArg(input, "since");
|
|
2844
|
+
if (since)
|
|
2845
|
+
args.push("--since", since);
|
|
2846
|
+
const maxDelta = signedIntegerArg(input, "max_delta");
|
|
2847
|
+
if (maxDelta !== undefined)
|
|
2848
|
+
args.push(`--max-delta=${maxDelta}`);
|
|
2849
|
+
const failOn = stringArg(input, "fail_on_issue");
|
|
2850
|
+
if (failOn)
|
|
2851
|
+
args.push("--fail-on-issue", failOn);
|
|
2852
|
+
if (booleanArg(input, "no_secret_scan"))
|
|
2853
|
+
args.push("--no-secret-scan");
|
|
2854
|
+
if (booleanArg(input, "no_sast"))
|
|
2855
|
+
args.push("--no-sast");
|
|
2856
|
+
if (booleanArg(input, "no_authz"))
|
|
2857
|
+
args.push("--no-authz");
|
|
2858
|
+
if (booleanArg(input, "no_deps"))
|
|
2859
|
+
args.push("--no-deps");
|
|
467
2860
|
if (booleanArg(input, "no_tests"))
|
|
468
2861
|
args.push("--no-tests");
|
|
469
|
-
if (
|
|
470
|
-
args.push("--
|
|
2862
|
+
if (booleanArg(input, "no_slop"))
|
|
2863
|
+
args.push("--no-slop");
|
|
471
2864
|
args.push(...projectPaths(input, "paths"));
|
|
472
2865
|
return toToolResult(await runCli(args, cwd));
|
|
473
2866
|
}
|
|
@@ -475,10 +2868,10 @@ async function callFix(input, cwd) {
|
|
|
475
2868
|
temp.cleanup();
|
|
476
2869
|
}
|
|
477
2870
|
}
|
|
478
|
-
async function
|
|
2871
|
+
async function callSast(input, cwd) {
|
|
479
2872
|
const temp = new TempFiles();
|
|
480
2873
|
try {
|
|
481
|
-
const args = ["
|
|
2874
|
+
const args = ["sast", "--json"];
|
|
482
2875
|
const diff = stringArg(input, "diff");
|
|
483
2876
|
const base = baseArg(input);
|
|
484
2877
|
if (diff)
|
|
@@ -491,8 +2884,19 @@ async function callScan(input, cwd) {
|
|
|
491
2884
|
args.push("--unstaged");
|
|
492
2885
|
if (booleanArg(input, "include_untracked"))
|
|
493
2886
|
args.push("--include-untracked");
|
|
494
|
-
|
|
495
|
-
|
|
2887
|
+
const failOn = stringArg(input, "fail_on_issue");
|
|
2888
|
+
if (failOn)
|
|
2889
|
+
args.push("--fail-on-issue", failOn);
|
|
2890
|
+
const ignore = stringArrayArg(input, "ignore");
|
|
2891
|
+
if (ignore.length > 0)
|
|
2892
|
+
args.push("--ignore", ignore.join(","));
|
|
2893
|
+
if (booleanArg(input, "no_suppressions"))
|
|
2894
|
+
args.push("--no-suppressions");
|
|
2895
|
+
const baseline = stringArg(input, "baseline");
|
|
2896
|
+
if (baseline)
|
|
2897
|
+
args.push("--baseline", baseline);
|
|
2898
|
+
if (booleanArg(input, "update_baseline"))
|
|
2899
|
+
args.push("--update-baseline");
|
|
496
2900
|
args.push(...projectPaths(input, "paths"));
|
|
497
2901
|
return toToolResult(await runCli(args, cwd));
|
|
498
2902
|
}
|
|
@@ -500,7 +2904,86 @@ async function callScan(input, cwd) {
|
|
|
500
2904
|
temp.cleanup();
|
|
501
2905
|
}
|
|
502
2906
|
}
|
|
503
|
-
async function
|
|
2907
|
+
async function callAuthz(input, cwd) {
|
|
2908
|
+
const temp = new TempFiles();
|
|
2909
|
+
try {
|
|
2910
|
+
const args = ["authz", "--json"];
|
|
2911
|
+
const diff = stringArg(input, "diff");
|
|
2912
|
+
const base = baseArg(input);
|
|
2913
|
+
if (diff)
|
|
2914
|
+
args.push("--diff", temp.write("changes.diff", diff));
|
|
2915
|
+
if (base)
|
|
2916
|
+
args.push("--base", base);
|
|
2917
|
+
if (booleanArg(input, "staged"))
|
|
2918
|
+
args.push("--staged");
|
|
2919
|
+
if (booleanArg(input, "unstaged"))
|
|
2920
|
+
args.push("--unstaged");
|
|
2921
|
+
if (booleanArg(input, "include_untracked"))
|
|
2922
|
+
args.push("--include-untracked");
|
|
2923
|
+
const failOn = stringArg(input, "fail_on_issue");
|
|
2924
|
+
if (failOn)
|
|
2925
|
+
args.push("--fail-on-issue", failOn);
|
|
2926
|
+
const ignore = stringArrayArg(input, "ignore");
|
|
2927
|
+
if (ignore.length > 0)
|
|
2928
|
+
args.push("--ignore", ignore.join(","));
|
|
2929
|
+
const publicRoutes = stringArrayArg(input, "public");
|
|
2930
|
+
if (publicRoutes.length > 0)
|
|
2931
|
+
args.push("--public", publicRoutes.join(","));
|
|
2932
|
+
if (booleanArg(input, "no_suppressions"))
|
|
2933
|
+
args.push("--no-suppressions");
|
|
2934
|
+
const baseline = stringArg(input, "baseline");
|
|
2935
|
+
if (baseline)
|
|
2936
|
+
args.push("--baseline", baseline);
|
|
2937
|
+
if (booleanArg(input, "update_baseline"))
|
|
2938
|
+
args.push("--update-baseline");
|
|
2939
|
+
args.push(...projectPaths(input, "paths"));
|
|
2940
|
+
return toToolResult(await runCli(args, cwd));
|
|
2941
|
+
}
|
|
2942
|
+
finally {
|
|
2943
|
+
temp.cleanup();
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
async function callReady(input, cwd) {
|
|
2947
|
+
const args = ["ready", "--json"];
|
|
2948
|
+
const failUnder = signedIntegerArg(input, "fail_under");
|
|
2949
|
+
if (failUnder !== undefined)
|
|
2950
|
+
args.push(`--fail-under=${String(failUnder)}`);
|
|
2951
|
+
return toToolResult(await runCli(args, cwd));
|
|
2952
|
+
}
|
|
2953
|
+
async function callReceiptVerify(input, cwd) {
|
|
2954
|
+
const args = ["receipt", "--verify", "--json"];
|
|
2955
|
+
const receipt = stringArg(input, "receipt");
|
|
2956
|
+
if (receipt)
|
|
2957
|
+
args.push("--receipt", receipt);
|
|
2958
|
+
const publicKey = stringArg(input, "public_key");
|
|
2959
|
+
if (publicKey)
|
|
2960
|
+
args.push("--public-key", publicKey);
|
|
2961
|
+
return toToolResult(await runCli(args, cwd));
|
|
2962
|
+
}
|
|
2963
|
+
async function callStats(input, cwd) {
|
|
2964
|
+
const args = ["stats", "--json"];
|
|
2965
|
+
if (booleanArg(input, "clear"))
|
|
2966
|
+
args.push("--clear");
|
|
2967
|
+
return toToolResult(await runCli(args, cwd));
|
|
2968
|
+
}
|
|
2969
|
+
async function callSlop(input, cwd) {
|
|
2970
|
+
const args = ["slop", "--json"];
|
|
2971
|
+
const since = stringArg(input, "since");
|
|
2972
|
+
if (since)
|
|
2973
|
+
args.push("--since", since);
|
|
2974
|
+
const days = integerArg(input, "since_days");
|
|
2975
|
+
if (days !== undefined)
|
|
2976
|
+
args.push("--since-days", String(days));
|
|
2977
|
+
const limit = integerArg(input, "limit");
|
|
2978
|
+
if (limit !== undefined)
|
|
2979
|
+
args.push("--limit", String(limit));
|
|
2980
|
+
if (booleanArg(input, "no_blame"))
|
|
2981
|
+
args.push("--no-blame");
|
|
2982
|
+
for (const pattern of stringArrayArg(input, "exclude"))
|
|
2983
|
+
args.push("--exclude", pattern);
|
|
2984
|
+
return toToolResult(await runCli(args, cwd));
|
|
2985
|
+
}
|
|
2986
|
+
async function dispatchTool(name, input, cwd, readOnly) {
|
|
504
2987
|
if (readOnly) {
|
|
505
2988
|
if (name === "fix" && booleanArg(input, "apply")) {
|
|
506
2989
|
throw new ToolInputError("fix apply is disabled in read-only mode; start the MCP server with --allow-write");
|
|
@@ -508,12 +2991,41 @@ async function callTool(name, input, cwd, readOnly) {
|
|
|
508
2991
|
if (name === "test_fix" && (stringArg(input, "command") || !stringArg(input, "log"))) {
|
|
509
2992
|
throw new ToolInputError("running tests is disabled in read-only mode; pass a captured log or start the server with --allow-write");
|
|
510
2993
|
}
|
|
2994
|
+
if (name === "commit" && booleanArg(input, "apply")) {
|
|
2995
|
+
throw new ToolInputError("commit apply is disabled in read-only mode; start the MCP server with --allow-write");
|
|
2996
|
+
}
|
|
2997
|
+
if (name === "stats" && booleanArg(input, "clear")) {
|
|
2998
|
+
throw new ToolInputError("stats clear is disabled in read-only mode; start the MCP server with --allow-write");
|
|
2999
|
+
}
|
|
3000
|
+
if ((name === "sast" || name === "authz") && booleanArg(input, "update_baseline")) {
|
|
3001
|
+
throw new ToolInputError("updating a baseline is disabled in read-only mode; start the MCP server with --allow-write");
|
|
3002
|
+
}
|
|
3003
|
+
if (name === "mutate") {
|
|
3004
|
+
throw new ToolInputError("mutation testing rewrites source files temporarily and is disabled in read-only mode; start the MCP server with --allow-write");
|
|
3005
|
+
}
|
|
3006
|
+
if (name === "battle" && booleanArg(input, "apply")) {
|
|
3007
|
+
throw new ToolInputError("battle apply rewrites the working tree and is disabled in read-only mode; start the MCP server with --allow-write");
|
|
3008
|
+
}
|
|
3009
|
+
if (name === "cleanup" && booleanArg(input, "apply")) {
|
|
3010
|
+
throw new ToolInputError("cleanup apply rewrites package.json and is disabled in read-only mode; start the MCP server with --allow-write");
|
|
3011
|
+
}
|
|
3012
|
+
if (name === "echo" && booleanArg(input, "submit")) {
|
|
3013
|
+
throw new ToolInputError("echo submit sends telemetry and is disabled in read-only mode; start the MCP server with --allow-write");
|
|
3014
|
+
}
|
|
3015
|
+
if (name === "test_gen" && booleanArg(input, "apply")) {
|
|
3016
|
+
throw new ToolInputError("test_gen apply writes test files and is disabled in read-only mode; start the MCP server with --allow-write");
|
|
3017
|
+
}
|
|
3018
|
+
if (name === "deps" && (booleanArg(input, "fix") || stringArg(input, "sbom"))) {
|
|
3019
|
+
throw new ToolInputError("deps fix and sbom write files and are disabled in read-only mode; start the MCP server with --allow-write");
|
|
3020
|
+
}
|
|
511
3021
|
}
|
|
512
3022
|
switch (name) {
|
|
513
3023
|
case "review":
|
|
514
3024
|
return callReview(input, cwd);
|
|
515
3025
|
case "test_fix":
|
|
516
3026
|
return callTestFix(input, cwd);
|
|
3027
|
+
case "test_gen":
|
|
3028
|
+
return callTestGen(input, cwd);
|
|
517
3029
|
case "docgen":
|
|
518
3030
|
return callDocgen(input, cwd);
|
|
519
3031
|
case "custom":
|
|
@@ -522,10 +3034,349 @@ async function callTool(name, input, cwd, readOnly) {
|
|
|
522
3034
|
return callFix(input, cwd);
|
|
523
3035
|
case "scan":
|
|
524
3036
|
return callScan(input, cwd);
|
|
3037
|
+
case "commit":
|
|
3038
|
+
return callCommit(input, cwd);
|
|
3039
|
+
case "stats":
|
|
3040
|
+
return callStats(input, cwd);
|
|
3041
|
+
case "slop":
|
|
3042
|
+
return callSlop(input, cwd);
|
|
3043
|
+
case "mutate":
|
|
3044
|
+
return callMutate(input, cwd);
|
|
3045
|
+
case "fuzz":
|
|
3046
|
+
return callFuzz(input, cwd);
|
|
3047
|
+
case "flaky":
|
|
3048
|
+
return callFlaky(input, cwd);
|
|
3049
|
+
case "impact":
|
|
3050
|
+
return callImpact(input, cwd);
|
|
3051
|
+
case "deps":
|
|
3052
|
+
return callDeps(input, cwd);
|
|
3053
|
+
case "coverage":
|
|
3054
|
+
return callCoverage(input, cwd);
|
|
3055
|
+
case "reviewers":
|
|
3056
|
+
return callReviewers(input, cwd);
|
|
3057
|
+
case "test_guard":
|
|
3058
|
+
return callTestGuard(input, cwd);
|
|
3059
|
+
case "env_sync":
|
|
3060
|
+
return callEnvSync(input, cwd);
|
|
3061
|
+
case "mock_leak":
|
|
3062
|
+
return callMockLeak(input, cwd);
|
|
3063
|
+
case "ecosystem_verify":
|
|
3064
|
+
return callEcosystemVerify(input, cwd);
|
|
3065
|
+
case "pack":
|
|
3066
|
+
return callPack(input, cwd);
|
|
3067
|
+
case "pack_hologram":
|
|
3068
|
+
return callPackHologram(input, cwd);
|
|
3069
|
+
case "zoom_symbol":
|
|
3070
|
+
return callZoomSymbol(input, cwd);
|
|
3071
|
+
case "invariants":
|
|
3072
|
+
return callInvariants(input, cwd);
|
|
3073
|
+
case "contract_drift":
|
|
3074
|
+
return callContractDrift(input, cwd);
|
|
3075
|
+
case "hivemind_status":
|
|
3076
|
+
return callHivemindStatus(input, cwd);
|
|
3077
|
+
case "sandbox":
|
|
3078
|
+
return callSandbox(input, cwd);
|
|
3079
|
+
case "query_ast":
|
|
3080
|
+
return callQueryAst(input, cwd);
|
|
3081
|
+
case "bankruptcy":
|
|
3082
|
+
return callBankruptcy(input, cwd);
|
|
3083
|
+
case "anti_regression":
|
|
3084
|
+
return callAntiRegression(input, cwd);
|
|
3085
|
+
case "boundaries":
|
|
3086
|
+
return callBoundaries(input, cwd);
|
|
3087
|
+
case "test_plan":
|
|
3088
|
+
return callTestPlan(input, cwd);
|
|
3089
|
+
case "compliance_verify":
|
|
3090
|
+
return callComplianceVerify(input, cwd);
|
|
3091
|
+
case "brag":
|
|
3092
|
+
return callBrag(input, cwd);
|
|
3093
|
+
case "battle":
|
|
3094
|
+
return callBattle(input, cwd);
|
|
3095
|
+
case "cleanup":
|
|
3096
|
+
return callCleanup(input, cwd);
|
|
3097
|
+
case "arch_diff":
|
|
3098
|
+
return callArchDiff(input, cwd);
|
|
3099
|
+
case "roast":
|
|
3100
|
+
return callRoast(input, cwd);
|
|
3101
|
+
case "cinema":
|
|
3102
|
+
return callCinema(input, cwd);
|
|
3103
|
+
case "bounty":
|
|
3104
|
+
return callBounty(input, cwd);
|
|
3105
|
+
case "autopsy":
|
|
3106
|
+
return callAutopsy(input, cwd);
|
|
3107
|
+
case "echo":
|
|
3108
|
+
return callEcho(input, cwd);
|
|
3109
|
+
case "preflight":
|
|
3110
|
+
return callPreflight(input, cwd);
|
|
3111
|
+
case "sast":
|
|
3112
|
+
return callSast(input, cwd);
|
|
3113
|
+
case "authz":
|
|
3114
|
+
return callAuthz(input, cwd);
|
|
3115
|
+
case "ready":
|
|
3116
|
+
return callReady(input, cwd);
|
|
3117
|
+
case "receipt_verify":
|
|
3118
|
+
return callReceiptVerify(input, cwd);
|
|
525
3119
|
default:
|
|
526
3120
|
throw new ToolInputError(`Unknown tool: ${name}`);
|
|
527
3121
|
}
|
|
528
3122
|
}
|
|
3123
|
+
/** Patches larger than this are cut down before they reach an agent's context. */
|
|
3124
|
+
const MAX_PATCH_CHARS = 200_000;
|
|
3125
|
+
/** Metadata a host needs to render or apply a patch without parsing it again. */
|
|
3126
|
+
export function patchSummary(patch) {
|
|
3127
|
+
let files = [];
|
|
3128
|
+
try {
|
|
3129
|
+
files = patchTargets(patch);
|
|
3130
|
+
}
|
|
3131
|
+
catch {
|
|
3132
|
+
files = [];
|
|
3133
|
+
}
|
|
3134
|
+
let additions = 0;
|
|
3135
|
+
let deletions = 0;
|
|
3136
|
+
let hunks = 0;
|
|
3137
|
+
for (const line of patch.split("\n")) {
|
|
3138
|
+
if (line.startsWith("@@"))
|
|
3139
|
+
hunks += 1;
|
|
3140
|
+
else if (line.startsWith("+") && !line.startsWith("+++"))
|
|
3141
|
+
additions += 1;
|
|
3142
|
+
else if (line.startsWith("-") && !line.startsWith("---"))
|
|
3143
|
+
deletions += 1;
|
|
3144
|
+
}
|
|
3145
|
+
return { files, additions, deletions, hunks, bytes: patch.length };
|
|
3146
|
+
}
|
|
3147
|
+
/** Applies the cross-tool result options: finding caps and compact text. */
|
|
3148
|
+
export function applyResultOptions(result, input) {
|
|
3149
|
+
const structured = result.structuredContent;
|
|
3150
|
+
if (!structured)
|
|
3151
|
+
return result;
|
|
3152
|
+
let payload = structured;
|
|
3153
|
+
const cap = integerArg(input, "max_findings") ?? 50;
|
|
3154
|
+
const findings = structured.findings;
|
|
3155
|
+
if (Array.isArray(findings) && findings.length > cap) {
|
|
3156
|
+
payload = { ...structured, findings: findings.slice(0, cap), truncated: true, totalFindings: findings.length };
|
|
3157
|
+
}
|
|
3158
|
+
const patch = payload.patch;
|
|
3159
|
+
if (typeof patch === "string" && patch.trim()) {
|
|
3160
|
+
const meta = patchSummary(patch);
|
|
3161
|
+
payload = { ...payload, patchStats: meta, files: meta.files };
|
|
3162
|
+
if (patch.length > MAX_PATCH_CHARS) {
|
|
3163
|
+
payload = { ...payload, patch: patch.slice(0, MAX_PATCH_CHARS), patchTruncated: true, patchBytes: patch.length };
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
const text = stringArg(input, "verbosity") === "compact" ? summarizeResult(payload) : JSON.stringify(payload, null, 2);
|
|
3167
|
+
return { ...result, structuredContent: payload, content: [{ type: "text", text }] };
|
|
3168
|
+
}
|
|
3169
|
+
/** One-line summary used by verbosity=compact. */
|
|
3170
|
+
function summarizeResult(value) {
|
|
3171
|
+
if ((value.identity === null || typeof value.identity === "object") && Array.isArray(value.peers)) {
|
|
3172
|
+
const identity = value.identity;
|
|
3173
|
+
const fingerprint = identity && typeof identity.fingerprint === "string" ? identity.fingerprint : "no identity";
|
|
3174
|
+
return `oc-pilot: hivemind ${fingerprint}, ${String(value.peers.length)} peer(s)`;
|
|
3175
|
+
}
|
|
3176
|
+
if (typeof value.status === "string" && Array.isArray(value.output) && typeof value.durationMs === "number") {
|
|
3177
|
+
return `oc-pilot: sandbox ${value.status} for ${typeof value.file === "string" ? value.file : "?"} (${String(value.durationMs)} ms)`;
|
|
3178
|
+
}
|
|
3179
|
+
if (typeof value.query === "string" && typeof value.count === "number" && Array.isArray(value.rows)) {
|
|
3180
|
+
return `oc-pilot: AQL ${String(value.count)} node(s) for ${typeof value.type === "string" && value.type ? value.type : "any type"}`;
|
|
3181
|
+
}
|
|
3182
|
+
if (typeof value.verdict === "string" && typeof value.currentScore === "number" && typeof value.commits === "number") {
|
|
3183
|
+
const forecast = typeof value.forecastDate === "string" ? `, forecast ${value.forecastDate}` : "";
|
|
3184
|
+
return `oc-pilot: bankruptcy score ${String(value.currentScore)}/100, ${value.verdict}${forecast}`;
|
|
3185
|
+
}
|
|
3186
|
+
if (Array.isArray(value.results) && typeof value.crashes === "number" && Array.isArray(value.targets)) {
|
|
3187
|
+
return `oc-pilot: fuzz ${String(value.targets.length)} target(s), ${String(value.crashes)} crash(es), ${String(value.blocked ?? 0)} blocked, ${String(value.timeouts ?? 0)} timeout(s)`;
|
|
3188
|
+
}
|
|
3189
|
+
if (Array.isArray(value.verdicts) && typeof value.violations === "number" && typeof value.checked === "number") {
|
|
3190
|
+
return `oc-pilot: invariants ${String(value.checked)} checked, ${String(value.skipped ?? 0)} skipped, ${String(value.violations)} violation(s)`;
|
|
3191
|
+
}
|
|
3192
|
+
if (Array.isArray(value.files) && typeof value.estimatedTokens === "number" && typeof value.maxTokens === "number") {
|
|
3193
|
+
return `oc-pilot: hologram ${String(value.files.length)} file(s), ~${String(value.estimatedTokens)} tokens (max ${String(value.maxTokens)})`;
|
|
3194
|
+
}
|
|
3195
|
+
if (typeof value.symbol === "string" && typeof value.body === "string" && typeof value.line === "number") {
|
|
3196
|
+
const file = typeof value.file === "string" ? value.file : "?";
|
|
3197
|
+
return `oc-pilot: symbol ${file}#${value.symbol} (line ${String(value.line)})`;
|
|
3198
|
+
}
|
|
3199
|
+
if (Array.isArray(value.badges) && asObject(value.stats)) {
|
|
3200
|
+
const stats = asObject(value.stats);
|
|
3201
|
+
const name = typeof value.name === "string" ? value.name : "anonymous";
|
|
3202
|
+
return `oc-pilot: ${String(value.badges.length)} badge(s) for ${name}, ${String(stats.commits ?? 0)} commits, ${String(stats.runs ?? 0)} runs`;
|
|
3203
|
+
}
|
|
3204
|
+
if (typeof value.verdict === "string" && Array.isArray(value.signals) && asObject(value.commit)) {
|
|
3205
|
+
const commit = asObject(value.commit);
|
|
3206
|
+
const short = typeof commit.shortHash === "string" ? commit.shortHash : "unknown";
|
|
3207
|
+
return `oc-pilot: autopsy ${value.verdict} for ${short}, ${String(value.signals.length)} signal(s)`;
|
|
3208
|
+
}
|
|
3209
|
+
if (Array.isArray(value.candidates) && typeof value.score === "number" && (typeof value.file === "string" || value.file === null)) {
|
|
3210
|
+
if (typeof value.file === "string") {
|
|
3211
|
+
return `oc-pilot: worst file ${value.file} (${String(value.score)} points, ${String(value.candidates.length)} suspect${value.candidates.length === 1 ? "" : "s"})`;
|
|
3212
|
+
}
|
|
3213
|
+
return `oc-pilot: no bounty (${String(value.scannedFiles ?? 0)} files scanned)`;
|
|
3214
|
+
}
|
|
3215
|
+
if (Array.isArray(value.frames) && typeof value.sinceDays === "number" && typeof value.format === "string") {
|
|
3216
|
+
const frames = String(value.frames.length);
|
|
3217
|
+
const days = String(value.sinceDays);
|
|
3218
|
+
const format = value.format === "cast" ? "cast" : "HTML";
|
|
3219
|
+
return `oc-pilot: ${frames} frame${frames === "1" ? "" : "s"} over ${days} day${days === "1" ? "" : "s"} (${format})`;
|
|
3220
|
+
}
|
|
3221
|
+
if (typeof value.index === "number") {
|
|
3222
|
+
return `oc-pilot: Slop Index ${value.index}/100 (${String(value.grade ?? "unknown")})`;
|
|
3223
|
+
}
|
|
3224
|
+
if (Array.isArray(value.added) && Array.isArray(value.removed) && typeof value.diagram === "string") {
|
|
3225
|
+
const added = String(value.added.length);
|
|
3226
|
+
const removed = String(value.removed.length);
|
|
3227
|
+
return `oc-pilot: ${added} added edge${added === "1" ? "" : "s"}, ${removed} removed edge${removed === "1" ? "" : "s"}`;
|
|
3228
|
+
}
|
|
3229
|
+
if (Array.isArray(value.tasks) && asObject(value.counts)) {
|
|
3230
|
+
const counts = asObject(value.counts);
|
|
3231
|
+
return `oc-pilot: ${String(counts.total)} cleanup task(s), ${String(counts.high)} high, ${String(counts.medium)} medium`;
|
|
3232
|
+
}
|
|
3233
|
+
if (typeof value.status === "string" && Array.isArray(value.rounds)) {
|
|
3234
|
+
const rounds = String(value.rounds.length);
|
|
3235
|
+
return `oc-pilot: battle ${value.status}, ${rounds} round${rounds === "1" ? "" : "s"}`;
|
|
3236
|
+
}
|
|
3237
|
+
if (typeof value.chars === "number" && typeof value.budget === "number" && Array.isArray(value.files)) {
|
|
3238
|
+
const files = String(value.files.length);
|
|
3239
|
+
const state = value.truncated === true ? "truncated" : "within budget";
|
|
3240
|
+
return `oc-pilot: ${files} file${files === "1" ? "" : "s"}, ${String(value.chars)} chars (${state})`;
|
|
3241
|
+
}
|
|
3242
|
+
const reportStats = asObject(value.stats);
|
|
3243
|
+
if (reportStats && typeof reportStats.commits === "number" && typeof reportStats.runs === "number" && typeof value.content === "string") {
|
|
3244
|
+
const findings = asObject(reportStats.findings);
|
|
3245
|
+
const total = (typeof findings?.critical === "number" ? findings.critical : 0) +
|
|
3246
|
+
(typeof findings?.warning === "number" ? findings.warning : 0) +
|
|
3247
|
+
(typeof findings?.style === "number" ? findings.style : 0);
|
|
3248
|
+
const format = value.content.trimStart().startsWith("<svg") ? "SVG" : "HTML";
|
|
3249
|
+
return `oc-pilot: ${String(reportStats.commits)} commits, ${String(reportStats.runs)} runs, ${String(total)} findings (${format})`;
|
|
3250
|
+
}
|
|
3251
|
+
const verification = asObject(value.verification);
|
|
3252
|
+
if (verification && typeof verification.ok === "boolean") {
|
|
3253
|
+
const state = verification.ok ? "ok" : "failed";
|
|
3254
|
+
const commit = typeof verification.commit === "string" ? verification.commit.slice(0, 12) : "unknown";
|
|
3255
|
+
return `oc-pilot: compliance ${state} for ${commit}`;
|
|
3256
|
+
}
|
|
3257
|
+
if (typeof value.strategy === "string" && Array.isArray(value.tests) && Array.isArray(value.uncovered)) {
|
|
3258
|
+
const tests = String(value.tests.length);
|
|
3259
|
+
const uncovered = String(value.uncovered.length);
|
|
3260
|
+
return `oc-pilot: ${tests} test file${tests === "1" ? "" : "s"} selected, ${uncovered} uncovered changed file${uncovered === "1" ? "" : "s"}`;
|
|
3261
|
+
}
|
|
3262
|
+
if (Array.isArray(value.references) && Array.isArray(value.missing) && Array.isArray(value.undocumented)) {
|
|
3263
|
+
const references = String(value.references.length);
|
|
3264
|
+
const missing = String(value.missing.length);
|
|
3265
|
+
const undocumented = String(value.undocumented.length);
|
|
3266
|
+
return `oc-pilot: ${references} env reference${references === "1" ? "" : "s"}, ${missing} missing, ${undocumented} undocumented`;
|
|
3267
|
+
}
|
|
3268
|
+
if (Array.isArray(value.violations) && typeof value.rules === "number" && typeof value.scannedFiles === "number") {
|
|
3269
|
+
const violations = String(value.violations.length);
|
|
3270
|
+
const files = String(new Set(value.violations.map((item) => asObject(item)?.file).filter(Boolean)).size);
|
|
3271
|
+
return `oc-pilot: ${violations} boundary violation${violations === "1" ? "" : "s"} in ${files} file${files === "1" ? "" : "s"}`;
|
|
3272
|
+
}
|
|
3273
|
+
if (Array.isArray(value.findings) && typeof value.checkedLines === "number") {
|
|
3274
|
+
const hashes = new Set();
|
|
3275
|
+
for (const finding of value.findings) {
|
|
3276
|
+
const commit = asObject(asObject(finding)?.commit);
|
|
3277
|
+
if (typeof commit?.hash === "string")
|
|
3278
|
+
hashes.add(commit.hash);
|
|
3279
|
+
}
|
|
3280
|
+
const lines = String(value.findings.length);
|
|
3281
|
+
const commits = String(hashes.size);
|
|
3282
|
+
return `oc-pilot: ${lines} re-added line${lines === "1" ? "" : "s"}, ${commits} commit${commits === "1" ? "" : "s"}`;
|
|
3283
|
+
}
|
|
3284
|
+
const counts = asObject(value.counts);
|
|
3285
|
+
if (counts &&
|
|
3286
|
+
typeof counts.high === "number" &&
|
|
3287
|
+
typeof counts.medium === "number" &&
|
|
3288
|
+
Array.isArray(value.contracts) &&
|
|
3289
|
+
Array.isArray(value.findings)) {
|
|
3290
|
+
const total = String(value.findings.length);
|
|
3291
|
+
return `oc-pilot: ${total} contract drift${total === "1" ? "" : "s"}, ${String(counts.high)} high, ${String(counts.medium)} medium`;
|
|
3292
|
+
}
|
|
3293
|
+
if (counts &&
|
|
3294
|
+
typeof counts.high === "number" &&
|
|
3295
|
+
typeof counts.medium === "number" &&
|
|
3296
|
+
typeof counts.low === "number" &&
|
|
3297
|
+
Array.isArray(value.issues)) {
|
|
3298
|
+
const total = String(value.issues.length);
|
|
3299
|
+
return `oc-pilot: ${total} ecosystem issue${total === "1" ? "" : "s"}, ${String(counts.high)} high, ${String(counts.medium)} medium`;
|
|
3300
|
+
}
|
|
3301
|
+
if (counts &&
|
|
3302
|
+
typeof counts.high === "number" &&
|
|
3303
|
+
typeof counts.medium === "number" &&
|
|
3304
|
+
typeof counts.low === "number" &&
|
|
3305
|
+
Array.isArray(value.findings)) {
|
|
3306
|
+
const total = String(value.findings.length);
|
|
3307
|
+
return `oc-pilot: ${total} mock leak${total === "1" ? "" : "s"}, ${String(counts.high)} high, ${String(counts.medium)} medium`;
|
|
3308
|
+
}
|
|
3309
|
+
if (counts && typeof counts.total === "number" && Array.isArray(value.findings)) {
|
|
3310
|
+
const total = String(counts.total);
|
|
3311
|
+
return `oc-pilot: ${total} test weakening finding${total === "1" ? "" : "s"}, status ${String(value.status ?? "unknown")}`;
|
|
3312
|
+
}
|
|
3313
|
+
if (typeof value.status === "string" && typeof value.rounds === "number" && Array.isArray(value.files)) {
|
|
3314
|
+
const files = String(value.files.length);
|
|
3315
|
+
return `oc-pilot: tests ${value.status}, ${files} file${files === "1" ? "" : "s"}, ${String(value.rounds)} round${value.rounds === 1 ? "" : "s"}`;
|
|
3316
|
+
}
|
|
3317
|
+
if (typeof value.patch === "string") {
|
|
3318
|
+
const stats = asObject(value.patchStats);
|
|
3319
|
+
const files = Array.isArray(value.files) ? value.files.length : 0;
|
|
3320
|
+
const additions = typeof stats?.additions === "number" ? stats.additions : 0;
|
|
3321
|
+
const deletions = typeof stats?.deletions === "number" ? stats.deletions : 0;
|
|
3322
|
+
const state = value.applied === true ? "applied" : "not applied";
|
|
3323
|
+
const truncated = value.patchTruncated === true ? ", patch truncated" : "";
|
|
3324
|
+
return `oc-pilot: fix ${files} file${files === 1 ? "" : "s"}, +${additions}/-${deletions} (${state})${truncated}`;
|
|
3325
|
+
}
|
|
3326
|
+
if (Array.isArray(value.flaky) && typeof value.runs === "number") {
|
|
3327
|
+
const flaky = String(value.flaky.length);
|
|
3328
|
+
const failing = Array.isArray(value.failing) ? String(value.failing.length) : "0";
|
|
3329
|
+
return `oc-pilot: ${flaky} flaky, ${failing} failing across ${String(value.runs)} runs`;
|
|
3330
|
+
}
|
|
3331
|
+
if (typeof value.packageName === "string" && Array.isArray(value.issues) && typeof value.declared === "number") {
|
|
3332
|
+
const issues = String(value.issues.length);
|
|
3333
|
+
const audit = asObject(value.audit);
|
|
3334
|
+
const vulnerabilities = Array.isArray(audit?.vulnerabilities) ? String(audit.vulnerabilities.length) : "0";
|
|
3335
|
+
return `oc-pilot: ${issues} dependency issue${issues === "1" ? "" : "s"}, ${vulnerabilities} vulnerabilit${vulnerabilities === "1" ? "y" : "ies"} (${String(value.declared)} declared, ${String(value.imported)} imported)`;
|
|
3336
|
+
}
|
|
3337
|
+
if (Array.isArray(value.symbols) && Array.isArray(value.references) && typeof value.scannedFiles === "number") {
|
|
3338
|
+
const symbols = String(value.symbols.length);
|
|
3339
|
+
const references = String(value.references.length);
|
|
3340
|
+
const scanned = String(value.scannedFiles);
|
|
3341
|
+
return `oc-pilot: ${symbols} changed symbol${symbols === "1" ? "" : "s"}, ${references} reference${references === "1" ? "" : "s"} in ${scanned} scanned file${scanned === "1" ? "" : "s"}`;
|
|
3342
|
+
}
|
|
3343
|
+
if (Array.isArray(value.candidates) && Array.isArray(value.codeowners) && Array.isArray(value.files)) {
|
|
3344
|
+
const reviewers = String(value.candidates.length);
|
|
3345
|
+
const owners = String(value.codeowners.length);
|
|
3346
|
+
const files = String(value.files.length);
|
|
3347
|
+
return `oc-pilot: ${reviewers} reviewer candidate${reviewers === "1" ? "" : "s"}, ${owners} CODEOWNERS owner${owners === "1" ? "" : "s"} for ${files} file${files === "1" ? "" : "s"}`;
|
|
3348
|
+
}
|
|
3349
|
+
if (typeof value.uncoveredLines === "number" && Array.isArray(value.gaps)) {
|
|
3350
|
+
const files = value.gaps.length;
|
|
3351
|
+
const percent = typeof value.coveragePercent === "number" ? ` (${String(value.coveragePercent)}%)` : "";
|
|
3352
|
+
return `oc-pilot: ${String(value.uncoveredLines)} uncovered changed line${value.uncoveredLines === 1 ? "" : "s"} in ${String(files)} file${files === 1 ? "" : "s"}${percent}`;
|
|
3353
|
+
}
|
|
3354
|
+
if (typeof value.score === "number" && typeof value.survived === "number") {
|
|
3355
|
+
const score = String(value.score);
|
|
3356
|
+
const survived = String(value.survived);
|
|
3357
|
+
return `oc-pilot: mutation score ${score}/100, ${survived} survived`;
|
|
3358
|
+
}
|
|
3359
|
+
const findings = value.findings;
|
|
3360
|
+
if (Array.isArray(findings)) {
|
|
3361
|
+
const counts = new Map();
|
|
3362
|
+
for (const item of findings) {
|
|
3363
|
+
const severity = asObject(item)?.severity;
|
|
3364
|
+
if (typeof severity === "string")
|
|
3365
|
+
counts.set(severity, (counts.get(severity) ?? 0) + 1);
|
|
3366
|
+
}
|
|
3367
|
+
const total = typeof value.totalFindings === "number" ? value.totalFindings : findings.length;
|
|
3368
|
+
const breakdown = [...counts].map(([severity, count]) => `${count} ${severity}`).join(", ");
|
|
3369
|
+
const shown = value.truncated === true ? `, showing ${findings.length}` : "";
|
|
3370
|
+
return `oc-pilot: ${total} finding${total === 1 ? "" : "s"}${breakdown ? ` (${breakdown})` : ""}${shown}`;
|
|
3371
|
+
}
|
|
3372
|
+
const message = value.summary ?? value.message;
|
|
3373
|
+
if (typeof message === "string")
|
|
3374
|
+
return message;
|
|
3375
|
+
return `oc-pilot: ${JSON.stringify(value).slice(0, 200)}`;
|
|
3376
|
+
}
|
|
3377
|
+
async function callTool(name, input, cwd, readOnly) {
|
|
3378
|
+
return applyResultOptions(await dispatchTool(name, input, cwd, readOnly), input);
|
|
3379
|
+
}
|
|
529
3380
|
function asObject(value) {
|
|
530
3381
|
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
531
3382
|
}
|
|
@@ -538,6 +3389,101 @@ function rpcError(id, code, message, data) {
|
|
|
538
3389
|
function writeMessage(message) {
|
|
539
3390
|
process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
540
3391
|
}
|
|
3392
|
+
/**
|
|
3393
|
+
* Builds the user-visible text for a prompt template. Prompt names are fixed,
|
|
3394
|
+
* so the only untrusted part is the optional free-form argument.
|
|
3395
|
+
*/
|
|
3396
|
+
function promptBody(name, args) {
|
|
3397
|
+
const context = stringArg(args, "context");
|
|
3398
|
+
const log = stringArg(args, "log");
|
|
3399
|
+
const suffix = context ? `\n\nContext from the user:\n${context}` : "";
|
|
3400
|
+
switch (name) {
|
|
3401
|
+
case "review-my-diff":
|
|
3402
|
+
return `Review my current changes with the oc-pilot "review" tool. Then summarize the findings grouped by severity, and for every critical finding propose the smallest concrete fix. Do not change code yet.${suffix}`;
|
|
3403
|
+
case "fix-failing-tests":
|
|
3404
|
+
return log
|
|
3405
|
+
? `Here is the failing test output. Call the oc-pilot "test_fix" tool with this log, explain the root cause, and propose the minimal patch.\n\nTest output:\n${log}`
|
|
3406
|
+
: `Run the failing tests through the oc-pilot "test_fix" tool, explain the root cause, and propose the minimal patch. Do not weaken or delete assertions.${suffix}`;
|
|
3407
|
+
case "pre-commit-gate":
|
|
3408
|
+
return `Gate my staged changes before I commit. First call the oc-pilot "scan" tool with staged=true and stop if it reports critical secrets. Then call "review" with staged=true and report the findings. Only when both pass, call "commit" to generate the message. Do not commit anything yourself.${suffix}`;
|
|
3409
|
+
case "secure-change":
|
|
3410
|
+
return `Review my change with the oc-pilot "review" tool, focusing on security: injection, authorization and tenancy boundaries, dependency changes, and prompt-injection text inside the diff. Report each risk with file:line and the smallest mitigation.${suffix}`;
|
|
3411
|
+
case "preflight-before-commit":
|
|
3412
|
+
return `Before I commit, call the oc-pilot "preflight" tool with staged=true. It runs offline: secrets, dependency health scoped to the change, test weakening and the Slop Index delta. If the verdict is fail, fix the cheapest failing check first, then call "preflight" again. Do not disable a check with no_* flags or lower the gate just to get a pass. When it passes, report what was checked and any remaining skips.${suffix}`;
|
|
3413
|
+
case "compile-hologram":
|
|
3414
|
+
return `Call the oc-pilot "pack_hologram" tool with max_tokens=30000 and the current diff. The result is layered: LOD 0 files are complete, LOD 1 bodies are hidden behind markers, LOD 2 lists export names. Never invent a hidden body: if the implementation matters, call "zoom_symbol" with the file and symbol from the Zoom section and read the returned body before reasoning about it. Treat [Hologram: parse bailout] as a warning that the file is complete. Do not dump the whole repository or raise max_tokens just to avoid zooming; the hologram exists to save context. Report which files were LOD 0/1/2, any redacted secrets and every symbol you zoomed.${suffix}`;
|
|
3415
|
+
case "check-invariants":
|
|
3416
|
+
return `Call the oc-pilot "invariants" tool on the current diff with strict=false first. Read every verdict: [SKIPPED: Out of scope] rules were not checked and must not be reported as passed; [UNKNOWN] means the model lacked evidence. For each violation, quote the file:line and explain which invariant broke. Do not edit .oc-pilot/invariants.md, do not delete a rule to get a pass and do not hide a CRITICAL violation. If the user wants a hard gate, rerun with strict=true and report exit code 3. After a fix, rerun the tool and compare the verdicts.${suffix}`;
|
|
3417
|
+
case "fuzz-the-diff":
|
|
3418
|
+
return `Call the oc-pilot "fuzz" tool with duration_ms=2000, memory_mb=128 and fail_on_crash=true. Read every result: [CRASH] is a real unhandled input and needs a minimal fix plus a regression test; [TIMEOUT] is an infinite loop or pathological complexity; [BLOCKED] and [SKIPPED] were not executed and must not be reported as safe. Never remove the side-effect guard, never run the function in the main process and never widen the memory or timeout limits just to hide a crash. After the fix, rerun the same tool and compare.${suffix}`;
|
|
3419
|
+
case "forecast-tech-debt":
|
|
3420
|
+
return `Call the oc-pilot "bankruptcy" tool with history_days=90 and explain=true. Present the current score, the slope and the forecast date as a trend, not a verdict on people. If the answer is [INSUFFICIENT DATA: Need at least 10 commits to compute Slop Velocity], say so and do not invent a date. Explain that test weakening and security flaws carry the heaviest weights, so the useful repayment plan is real tests and fixes, not deleting TODO comments. Do not change weights, hide commits or claim precision beyond the linear model.${suffix}`;
|
|
3421
|
+
case "query-the-ast":
|
|
3422
|
+
return `Call the oc-pilot "query_ast" tool with an AQL chain such as find(CallExpression).where(name, 'stripe').select(file, line). Use the returned file:line evidence to plan the refactor, then verify each hit in the source before editing. Do not invent nodes, do not claim the graph is a full TypeScript AST and do not rewrite code based on a single name match; if the query returns nothing, say so and try a narrower or wider type.${suffix}`;
|
|
3423
|
+
case "sandbox-script":
|
|
3424
|
+
return `Call the oc-pilot "sandbox" tool on the generated file with timeout_ms=2000 and memory_mb=128. Report the captured output and the exact status: ok, blocked, timeout or error. A blocked import or require means the script tried a side effect and must not be run outside the sandbox without review. Do not claim the vm is an OS sandbox, do not pass allow_unsafe_exec without the user's explicit approval, and do not run native binaries through this tool.${suffix}`;
|
|
3425
|
+
case "join-the-swarm":
|
|
3426
|
+
return `Call the oc-pilot "hivemind_status" tool first and report the identity fingerprint and trusted peer count. If there is no identity, run oc-pilot hivemind init; if the peer is not trusted, ask the user for its public key and run oc-pilot hivemind trust before sending anything. Over the mesh send only diffs, AST and LLM requests; executable payloads are forbidden. Never trust an unverified peer, never disable the trust check and never run a command received from the network.${suffix}`;
|
|
3427
|
+
case "get-ready":
|
|
3428
|
+
return `Call the oc-pilot "ready" tool with fail_under=85. If the score is below the threshold, fix the cheapest gap first: install the pre-commit hook for hooks, create AGENTS.md for docs, add .oc-pilot.json for config, run the suite for tests, and run oc-pilot receipt for the receipt check. Do not lower fail_under to get a pass. Rerun "ready" until the score is at least 85 or only checks that need a human remain, then report the score, the fixed items and the remaining gaps.${suffix}`;
|
|
3429
|
+
case "fix-security":
|
|
3430
|
+
return `Call the oc-pilot "fix" tool with from_sast=true (or from_authz=true for route authorization) and apply=true. It asks the model for a patch that removes the findings, applies it, runs the tests and rescans; the result reports sast or authz as {before, after, remaining}. If the server is read-only, call the same tool without apply, show the patch and ask me to apply it. Do not add suppressions, do not edit .oc-pilot/baseline.json, and do not claim success while remaining is above zero. If high findings remain, report them with file:line and the smallest next step.${suffix}`;
|
|
3431
|
+
case "stabilize-flaky":
|
|
3432
|
+
return `Call the oc-pilot "flaky" tool with runs=5 and shuffle=true (the command must contain {files}; pass a fixed seed when you have one). If it reports no flaky tests, say so and stop. For each flaky test, inspect shared state, ordering, time, randomness and network assumptions, then make the smallest deterministic fix in the test or its setup. Never hide the problem with retries, sleeps, longer timeouts, skipped tests or by reordering files. Rerun with the same seed and runs to prove the test is stable. Report the test names, the cause, the fix and the before/after evidence.${suffix}`;
|
|
3433
|
+
case "check-impact":
|
|
3434
|
+
return `Call the oc-pilot "impact" tool on the current diff. It lists the declarations the change added or removed and every untouched file that still mentions those names. Inspect each reference: update call sites, imports, types, docs and tests that the change invalidated. Do not rename a public symbol or delete a call site only to make the report empty, and remember the search is textual, so confirm each hit before editing. Rerun "impact" after the edits and report the changed symbols, the files updated and any references you deliberately left.${suffix}`;
|
|
3435
|
+
case "triage-deps":
|
|
3436
|
+
return `Call the oc-pilot "deps" tool with audit=true, offline=false, fail_on_issue=medium and fail_on_vuln=high. Separate real problems from noise: a phantom import, a missing declaration, an unused package, a floating version, a broken lockfile, then a vulnerability. Fix the cheapest real problem first and rerun the same call. Never add an ignore, lower a gate or remove a dependency only to silence the report; if a vulnerability has no fixed version, say so and propose a mitigation. If the server is read-only, show the planned package.json change and ask me to apply it. Report what was fixed, what remains and the exact versions.${suffix}`;
|
|
3437
|
+
case "cover-changed-lines":
|
|
3438
|
+
return `Call the oc-pilot "coverage" tool on the current diff with fail_under=80 (or max_uncovered=0 when the project has no meaningful percentage). Add tests that execute the uncovered changed lines, then call "coverage" again with the same gate. Do not lower the threshold, add coverage-ignore comments, or delete the uncovered branch only to get a pass. Report the covered percentage, the files and lines you covered, and any gap you deliberately left.${suffix}`;
|
|
3439
|
+
case "generate-tests":
|
|
3440
|
+
return `Call the oc-pilot "test_gen" tool with apply=true, coverage=true and a coverage_file when one exists. Without a coverage report it targets the changed code; with from_slop=true it targets untested files instead. The tool writes only test files, runs the suite and reverts failing tests. If the server is read-only, call it without apply, show the patch and ask me to apply it. Do not edit production code, weaken assertions, add skips or change the test command only to get a pass. Rerun until status is pass, then report the files, rounds, test results and any coverage gaps that remain.${suffix}`;
|
|
3441
|
+
case "pick-reviewers":
|
|
3442
|
+
return `Call the oc-pilot "reviewers" tool on the current diff. Use the candidates and CODEOWNERS owners to choose the smallest set of reviewers who actually know the changed files. Do not add bots or people only because they committed once; explain each choice with the files and commit counts, and say when the history is too thin to be useful.${suffix}`;
|
|
3443
|
+
case "guard-tests":
|
|
3444
|
+
return `Call the oc-pilot "test_guard" tool on the current diff with fail_on_weakening=true. For every finding, restore the protection or fix the production cause: remove skips and focus, restore deleted assertions, replace existence checks with the original strict matcher, remove @ts-ignore and empty catch blocks, and bring timeouts back. Never add skip/focus, delete a test, weaken a matcher, suppress a type error or swallow an assertion to get a pass. Rerun "test_guard" until status is pass, then report each finding and the real fix.${suffix}`;
|
|
3445
|
+
case "sync-env":
|
|
3446
|
+
return `Call the oc-pilot "env_sync" tool on the current diff with fail_on_missing=true. Add every missing variable to the real configuration and every new variable to .env.example (and .env.sample or .env.template when the project uses them). Document what each variable configures and which value a human must supply; never invent production secrets, never commit a real .env with secrets, and never delete a reference only to silence the report. Rerun "env_sync" until status is pass, then report the variables, files and any value the human still has to provide.${suffix}`;
|
|
3447
|
+
case "remove-mocks":
|
|
3448
|
+
return `Call the oc-pilot "mock_leak" tool on the current diff with fail_on_leak=medium. For every finding, replace the stub with the real implementation: wire the actual request or service, take the local path and host from configuration, load the secret from the environment, and delete always-true or always-false branches instead of keeping dead code. Never move a mock into a comment, rename it to hide it, add an ignore marker or delete the production call to silence the report. Rerun "mock_leak" until status is pass, then report each finding, the file and the real behavior that replaced it.${suffix}`;
|
|
3449
|
+
case "verify-ecosystem":
|
|
3450
|
+
return `Call the oc-pilot "ecosystem_verify" tool on the current diff with fail_on_issue=high, and add online=true when the change introduces a new package. For a missing export, correct the call to the real API of the installed version or install and declare the package that actually provides it; never replace the import with any or a local shim only to silence the report. For a package that is missing, too new or has too few downloads, stop and ask me before installing it. Rerun offline until status is pass, then report each package, the missing names, the files and the online evidence you used.${suffix}`;
|
|
3451
|
+
case "pack-context":
|
|
3452
|
+
return `Call the oc-pilot "pack" tool with the task and the current diff. Use only the returned bundle instead of dumping whole files into the prompt, and ask for more context only when a named signature or file is missing. Do not treat the pack as proof that the code is correct; run the checks after the change.${suffix}`;
|
|
3453
|
+
case "check-contracts":
|
|
3454
|
+
return `Call the oc-pilot "contract_drift" tool on the current diff with fail_on_drift=high. For every finding, inspect the listed consumers and either update them to the new shape or keep a compatible field, alias or migration. Do not silently rename or delete a field, and do not edit the contract only to make the report empty. Rerun "contract_drift" until no high findings remain, then report each contract, field, consumer and the compatibility decision.${suffix}`;
|
|
3455
|
+
case "guard-regressions":
|
|
3456
|
+
return `Call the oc-pilot "anti_regression" tool on the current diff with fail_on_regression=true. Every finding is a line a human already deleted, with the commit that removed it. Read that commit and the surrounding change before deciding: reintroduce the line only when the original reason no longer applies or the task explicitly requires it, and say why. Never rewrite the line only to dodge the exact match, never rewrite git history, and never suppress the finding. Rerun until status is pass, then report each commit, file:line and the decision.${suffix}`;
|
|
3457
|
+
case "respect-boundaries":
|
|
3458
|
+
return `Call the oc-pilot "boundaries" tool with fail_on_violation=true. If it reports violations, fix the dependency direction instead of widening the rule: move shared logic behind a service or interface, inject the dependency, or move the file into the allowed layer. Change .oc-pilot/architecture.json only when the architecture intentionally changed, and say why. Never rename or ignore an import just to dodge the check. Rerun until it passes and report each violation, the file:line and the fix.${suffix}`;
|
|
3459
|
+
case "plan-tests":
|
|
3460
|
+
return `Call the oc-pilot "test_plan" tool with strategy=affected and fail_on_uncovered=true on the current change. Run only the returned test files, not the whole suite. For every file in uncovered, add the smallest test that exercises the changed behavior or explain why it cannot be tested here. Do not lower the strategy, delete a test, or run the full suite just to hide an uncovered file. Rerun "test_plan" after adding tests and report the selected files, the reasons, the tests you ran and any remaining uncovered files.${suffix}`;
|
|
3461
|
+
case "prove-compliance":
|
|
3462
|
+
return `Call the oc-pilot "compliance_verify" tool for HEAD. A valid proof has ok=true, hashOk=true, chainOk=true, commitOk=true and a signature that is valid or unchecked because no public key is available. If the manifest is missing, create one with oc-pilot compliance (and sign it when a key exists), then verify again. Never edit a manifest, never reorder the chain and never claim completion while ok is false; report the exact reason and the commit.${suffix}`;
|
|
3463
|
+
case "battle-hardening":
|
|
3464
|
+
return `Call the oc-pilot "battle" tool with the task and apply=true when the server has write access. It alternates a coder patch with a hacker review plus SAST, authorization and test-weakening checks, and stops only when no high findings remain and the tests pass. Do not lower rounds, skip the hacker, or hide findings to reach a pass. If the status is fail or stopped, report the round, the finding source and the smallest next step. If the server is read-only, call battle without apply and show the proposed patch.${suffix}`;
|
|
3465
|
+
case "cleanup-plan":
|
|
3466
|
+
return `Call the oc-pilot "cleanup" tool with fail_on_task=high. Fix the high and medium tasks in order: dependency issues and safe package.json fixes first, then environment variables, weakened tests and the Slop Index. Use apply=true only for the safe dependency fixes and only when the server is writable; for code changes make the smallest real fix yourself. Do not delete tests, suppress environment variables, ignore dependencies or lower the gate to get a pass. Rerun "cleanup" and report the applied fixes and the tasks that still need a human.${suffix}`;
|
|
3467
|
+
case "show-arch-diff":
|
|
3468
|
+
return `Call the oc-pilot "arch_diff" tool on the current diff. Put the returned Mermaid block into the pull request description so reviewers see the architecture change: green edges were added, red dashed edges were removed. For every removed edge, explain why the dependency disappeared and whether the behavior moved elsewhere. Do not hide a removed edge, hand-edit the diagram or widen max_edges only to make the picture look smaller. If the tool reports truncation, say so.${suffix}`;
|
|
3469
|
+
case "roast-my-code":
|
|
3470
|
+
return `Call the oc-pilot "roast" tool on the current diff with the persona the user asked for (default linus). Use the returned roast as-is, or sharpen the wording without inventing code that is not in the diff. Keep every joke tied to file:line and finish with the single most valuable fix. Never turn the roast into personal harassment, slurs or threats.${suffix}`;
|
|
3471
|
+
case "play-session":
|
|
3472
|
+
return `Call the oc-pilot "cinema" tool with since_days=30 and max_frames=120 (or cast=true for asciinema). Present the returned player as a replay of real local activity: commits, agent runs, API calls, findings and the receipt. Do not invent frames, hide failed runs or claim the timelapse proves correctness; it is a report, not a test. Report the format, the frame count, the window and any gaps in the history.${suffix}`;
|
|
3473
|
+
case "wanted-poster":
|
|
3474
|
+
return `Call the oc-pilot "bounty" tool with limit=5 and since_days=30. Present the WANTED poster as a playful but factual ranking, then explain the reasons behind the worst file in plain language and name the single highest-weight reason to fix first. Do not invent issues, shame people or paste the poster into a public channel without checking the file names; do not rewrite production code just to lower the score. After a fix, call "bounty" again and compare the score.${suffix}`;
|
|
3475
|
+
case "autopsy-commit":
|
|
3476
|
+
return `Call the oc-pilot "autopsy" tool for the commit (default HEAD) with depth=5 and fail_on_risk=true. Read the signals, the ranked suspects and the before/after timeline: identify which added line introduced each signal and which earlier commit removed a protection. Propose the smallest fix and the test that would have caught it. Do not blame authors, rewrite git history, hide signals or change the timeline; after the fix, call "autopsy" again and compare. Report the commit, verdict, signal file:line and timeline evidence.${suffix}`;
|
|
3477
|
+
case "share-progress":
|
|
3478
|
+
return `Call the oc-pilot "echo" tool with since_days=30 and the nickname the user chose. Show the SVG badges and the exact aggregate payload. Do not call submit=true unless the user explicitly approved the endpoint; the payload must contain only nickname, window and aggregates, never paths, diffs, secrets or finding text. If the user approves, call "echo" again with submit=true and that endpoint, then report the status. Never invent numbers, inflate badges or claim a leaderboard rank you did not receive.${suffix}`;
|
|
3479
|
+
case "prove-tests":
|
|
3480
|
+
return `Call the oc-pilot "mutate" tool on the current change with max_mutants=20 and min_score=80. Each surviving mutant is a behavior the tests do not protect: add or strengthen the smallest test that would kill it, then call "mutate" again. Do not change production code only to satisfy a mutant, do not weaken assertions, and do not lower min_score to get a pass. Report the mutation score, the surviving mutants with file:line, and the tests you added.${suffix}`;
|
|
3481
|
+
case "prove-done":
|
|
3482
|
+
return `Do not say the work is done yet. Call the oc-pilot "receipt_verify" tool. A valid proof has ok=true, hashOk=true, stale=false and tests.passed=true for the current HEAD. If the receipt is missing, stale or reports failing tests, run the suite and create a fresh receipt with oc-pilot receipt, then call "receipt_verify" again. Use oc-pilot receipt --no-run only when tests cannot run here, and say so explicitly. Never edit a receipt by hand and never claim completion while ok is false or tests.passed is false. Report the receipt path, the commit and what was verified.${suffix}`;
|
|
3483
|
+
default:
|
|
3484
|
+
return `Review my change with the oc-pilot "review" tool.${suffix}`;
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
541
3487
|
async function processMessage(message, cwd, readOnly) {
|
|
542
3488
|
const id = message.id;
|
|
543
3489
|
const method = message.method;
|
|
@@ -547,27 +3493,127 @@ async function processMessage(message, cwd, readOnly) {
|
|
|
547
3493
|
}
|
|
548
3494
|
if (method.startsWith("notifications/"))
|
|
549
3495
|
return undefined;
|
|
3496
|
+
if (method === "logging/setLevel") {
|
|
3497
|
+
const level = params?.level;
|
|
3498
|
+
if (typeof level !== "string") {
|
|
3499
|
+
return rpcError(id, -32602, "Invalid params: logging/setLevel requires a level");
|
|
3500
|
+
}
|
|
3501
|
+
logLevel = level;
|
|
3502
|
+
return rpcResult(id, {});
|
|
3503
|
+
}
|
|
550
3504
|
if (method === "initialize") {
|
|
551
3505
|
const requested = params?.protocolVersion;
|
|
552
3506
|
return rpcResult(id, {
|
|
553
3507
|
protocolVersion: typeof requested === "string" ? requested : PROTOCOL_VERSION,
|
|
554
|
-
capabilities: {
|
|
3508
|
+
capabilities: {
|
|
3509
|
+
logging: {},
|
|
3510
|
+
tools: { listChanged: false },
|
|
3511
|
+
prompts: { listChanged: false },
|
|
3512
|
+
resources: { listChanged: false },
|
|
3513
|
+
resourceTemplates: { listChanged: false },
|
|
3514
|
+
},
|
|
555
3515
|
serverInfo: { name: SERVER_NAME, version: VERSION },
|
|
556
|
-
instructions: `oc-pilot analyzes diffs and failing tests. Tools return JSON; secret values are redacted before model calls whenever possible.${readOnly ? " This server is read-only: fix apply
|
|
3516
|
+
instructions: `oc-pilot analyzes diffs and failing tests. Tools return JSON; secret values are redacted before model calls whenever possible. Prompts (prompts/list) wrap the common flows: review my diff, fix failing tests, pre-commit gate, security review, agent readiness, prove done. Read-only context is also exposed as resources (oc-pilot://config, oc-pilot://rules, oc-pilot://prompt/en|ru, oc-pilot://version) and resource templates (oc-pilot://diff/{mode}, oc-pilot://scan/{mode}, oc-pilot://file/{+path}). Offline tools (scan, stats, slop, sast, authz, preflight, ready, receipt_verify) never call a model and keep working even after the call budget is exhausted; oc-pilot://slop exposes the Slop Index report and the slop-check prompt frames the entropy question.${readOnly ? " This server is read-only: fix apply, commit apply, running new tests and clearing stats are disabled." : ""}`,
|
|
557
3517
|
});
|
|
558
3518
|
}
|
|
559
3519
|
if (method === "ping")
|
|
560
3520
|
return rpcResult(id, {});
|
|
561
3521
|
if (method === "tools/list")
|
|
562
3522
|
return rpcResult(id, { tools: TOOLS });
|
|
3523
|
+
if (method === "prompts/list") {
|
|
3524
|
+
return rpcResult(id, {
|
|
3525
|
+
prompts: PROMPTS.map((prompt) => ({
|
|
3526
|
+
name: prompt.name,
|
|
3527
|
+
title: prompt.title,
|
|
3528
|
+
description: prompt.description,
|
|
3529
|
+
arguments: prompt.arguments,
|
|
3530
|
+
})),
|
|
3531
|
+
});
|
|
3532
|
+
}
|
|
3533
|
+
if (method === "prompts/get") {
|
|
3534
|
+
const name = params?.name;
|
|
3535
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
3536
|
+
return rpcError(id, -32602, "Invalid params: prompts/get requires a prompt name");
|
|
3537
|
+
}
|
|
3538
|
+
const prompt = PROMPTS.find((candidate) => candidate.name === name);
|
|
3539
|
+
if (!prompt)
|
|
3540
|
+
return rpcError(id, -32602, `Unknown prompt: ${name}`);
|
|
3541
|
+
const args = asObject(params?.arguments) ?? {};
|
|
3542
|
+
return rpcResult(id, {
|
|
3543
|
+
description: prompt.description,
|
|
3544
|
+
messages: [{ role: "user", content: { type: "text", text: promptBody(prompt.name, args) } }],
|
|
3545
|
+
});
|
|
3546
|
+
}
|
|
3547
|
+
if (method === "resources/list")
|
|
3548
|
+
return rpcResult(id, { resources: RESOURCES });
|
|
3549
|
+
if (method === "resources/templates/list") {
|
|
3550
|
+
return rpcResult(id, { resourceTemplates: RESOURCE_TEMPLATES });
|
|
3551
|
+
}
|
|
3552
|
+
if (method === "resources/read") {
|
|
3553
|
+
const uri = params?.uri;
|
|
3554
|
+
if (typeof uri !== "string" || uri.trim() === "") {
|
|
3555
|
+
return rpcError(id, -32602, "Invalid params: resources/read requires a uri");
|
|
3556
|
+
}
|
|
3557
|
+
try {
|
|
3558
|
+
const resource = readResourceText(uri, cwd);
|
|
3559
|
+
return rpcResult(id, {
|
|
3560
|
+
contents: [{ uri, mimeType: resource.mimeType, text: resource.text }],
|
|
3561
|
+
});
|
|
3562
|
+
}
|
|
3563
|
+
catch (error) {
|
|
3564
|
+
return rpcError(id, -32002, error instanceof Error ? error.message : String(error));
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
563
3567
|
if (method === "tools/call") {
|
|
564
3568
|
const name = params?.name;
|
|
565
3569
|
if (typeof name !== "string") {
|
|
566
3570
|
return rpcError(id, -32602, "Invalid params: tools/call requires a tool name");
|
|
567
3571
|
}
|
|
3572
|
+
if (!OFFLINE_TOOLS.has(name) && maxToolCalls > 0 && toolCallsUsed >= maxToolCalls) {
|
|
3573
|
+
return rpcResult(id, {
|
|
3574
|
+
content: [
|
|
3575
|
+
{
|
|
3576
|
+
type: "text",
|
|
3577
|
+
text: `Tool budget exhausted (${maxToolCalls} model-backed calls per session). Restart the server to reset, raise --max-calls, or use the offline tools: scan, stats, slop, sast, authz, preflight, ready, receipt_verify.`,
|
|
3578
|
+
},
|
|
3579
|
+
],
|
|
3580
|
+
isError: true,
|
|
3581
|
+
});
|
|
3582
|
+
}
|
|
568
3583
|
const input = asObject(params?.arguments) ?? {};
|
|
3584
|
+
const token = asObject(params?._meta)?.progressToken;
|
|
3585
|
+
const notify = typeof token === "number" || typeof token === "string";
|
|
3586
|
+
const startedAt = Date.now();
|
|
3587
|
+
if (emitNotification && notify) {
|
|
3588
|
+
emitNotification({
|
|
3589
|
+
jsonrpc: "2.0",
|
|
3590
|
+
method: "notifications/progress",
|
|
3591
|
+
params: { progressToken: token, progress: 0, total: 1, message: `Running ${name}` },
|
|
3592
|
+
});
|
|
3593
|
+
}
|
|
3594
|
+
if (!OFFLINE_TOOLS.has(name))
|
|
3595
|
+
toolCallsUsed += 1;
|
|
569
3596
|
try {
|
|
570
|
-
|
|
3597
|
+
const result = await callTool(name, input, cwd, readOnly);
|
|
3598
|
+
if (emitNotification && notify) {
|
|
3599
|
+
emitNotification({
|
|
3600
|
+
jsonrpc: "2.0",
|
|
3601
|
+
method: "notifications/progress",
|
|
3602
|
+
params: { progressToken: token, progress: 1, total: 1, message: `${name} finished` },
|
|
3603
|
+
});
|
|
3604
|
+
}
|
|
3605
|
+
if (emitNotification && logLevel) {
|
|
3606
|
+
emitNotification({
|
|
3607
|
+
jsonrpc: "2.0",
|
|
3608
|
+
method: "notifications/message",
|
|
3609
|
+
params: {
|
|
3610
|
+
level: "info",
|
|
3611
|
+
logger: "oc-pilot",
|
|
3612
|
+
data: { tool: name, exitCode: result._meta?.exitCode ?? 0, durationMs: Date.now() - startedAt },
|
|
3613
|
+
},
|
|
3614
|
+
});
|
|
3615
|
+
}
|
|
3616
|
+
return rpcResult(id, result);
|
|
571
3617
|
}
|
|
572
3618
|
catch (error) {
|
|
573
3619
|
if (error instanceof ToolInputError) {
|
|
@@ -603,6 +3649,7 @@ async function processPayload(message, cwd, readOnly) {
|
|
|
603
3649
|
return processMessage(message, cwd, readOnly);
|
|
604
3650
|
}
|
|
605
3651
|
async function serveStdio(cwd, readOnly) {
|
|
3652
|
+
emitNotification = (message) => writeMessage(message);
|
|
606
3653
|
const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
607
3654
|
for await (const line of lines) {
|
|
608
3655
|
const trimmed = line.trim();
|
|
@@ -620,6 +3667,7 @@ async function serveStdio(cwd, readOnly) {
|
|
|
620
3667
|
if (response)
|
|
621
3668
|
writeMessage(response);
|
|
622
3669
|
}
|
|
3670
|
+
emitNotification = undefined;
|
|
623
3671
|
}
|
|
624
3672
|
function corsHeaders(config) {
|
|
625
3673
|
if (!config.cors)
|
|
@@ -761,6 +3809,7 @@ Options:
|
|
|
761
3809
|
--path <path> HTTP endpoint path (default: /mcp)
|
|
762
3810
|
--token <token> Require Authorization: Bearer <token>
|
|
763
3811
|
--cors <origin> Add CORS headers for an origin, or *
|
|
3812
|
+
--max-calls <n> Model-backed tool calls per session, 0 disables the limit (default: 50)
|
|
764
3813
|
--read-only Disable fix --apply and running new tests (default)
|
|
765
3814
|
--allow-write Enable fix --apply and running new tests
|
|
766
3815
|
-h, --help Show this help
|
|
@@ -768,7 +3817,11 @@ Options:
|
|
|
768
3817
|
|
|
769
3818
|
Environment:
|
|
770
3819
|
OC_PILOT_MCP_HOST, OC_PILOT_MCP_PORT, OC_PILOT_MCP_PATH,
|
|
771
|
-
OC_PILOT_MCP_TOKEN, OC_PILOT_MCP_CORS, OC_PILOT_MCP_READ_ONLY
|
|
3820
|
+
OC_PILOT_MCP_TOKEN, OC_PILOT_MCP_CORS, OC_PILOT_MCP_READ_ONLY,
|
|
3821
|
+
OC_PILOT_MCP_MAX_CALLS
|
|
3822
|
+
|
|
3823
|
+
The offline tools (scan, stats, slop, sast, authz, preflight, ready, receipt_verify) never call a model and keep working
|
|
3824
|
+
after the call budget is exhausted. oc-pilot://slop exposes the Slop Index.
|
|
772
3825
|
|
|
773
3826
|
Examples:
|
|
774
3827
|
oc-pilot-mcp --cwd /path/to/project
|
|
@@ -788,12 +3841,13 @@ function parseServerArgs(argv) {
|
|
|
788
3841
|
const envReadOnly = process.env.OC_PILOT_MCP_READ_ONLY;
|
|
789
3842
|
let readOnly = envReadOnly === "1" || envReadOnly === "true" ? true : envReadOnly === "0" || envReadOnly === "false" ? false : undefined;
|
|
790
3843
|
let allowWrite = false;
|
|
3844
|
+
let maxCalls = Number(process.env.OC_PILOT_MCP_MAX_CALLS ?? "50");
|
|
791
3845
|
for (let index = 0; index < argv.length; index += 1) {
|
|
792
3846
|
const arg = argv[index];
|
|
793
3847
|
if (arg === "--help" || arg === "-h")
|
|
794
|
-
return { cwd, action: "help", readOnly: false };
|
|
3848
|
+
return { cwd, action: "help", readOnly: false, maxCalls };
|
|
795
3849
|
if (arg === "--version" || arg === "-V")
|
|
796
|
-
return { cwd, action: "version", readOnly: false };
|
|
3850
|
+
return { cwd, action: "version", readOnly: false, maxCalls };
|
|
797
3851
|
if (arg === "--http") {
|
|
798
3852
|
http = true;
|
|
799
3853
|
continue;
|
|
@@ -837,6 +3891,10 @@ function parseServerArgs(argv) {
|
|
|
837
3891
|
cors = next();
|
|
838
3892
|
continue;
|
|
839
3893
|
}
|
|
3894
|
+
if (arg === "--max-calls") {
|
|
3895
|
+
maxCalls = Number(next());
|
|
3896
|
+
continue;
|
|
3897
|
+
}
|
|
840
3898
|
throw new Error(`Unknown option: ${arg}`);
|
|
841
3899
|
}
|
|
842
3900
|
if (!existsSync(cwd) || !statSync(cwd).isDirectory()) {
|
|
@@ -851,10 +3909,14 @@ function parseServerArgs(argv) {
|
|
|
851
3909
|
if (readOnly === true && allowWrite) {
|
|
852
3910
|
throw new Error("--read-only and --allow-write are mutually exclusive");
|
|
853
3911
|
}
|
|
3912
|
+
if (!Number.isInteger(maxCalls) || maxCalls < 0) {
|
|
3913
|
+
throw new Error(`Invalid max calls: ${process.env.OC_PILOT_MCP_MAX_CALLS ?? "not a number"}`);
|
|
3914
|
+
}
|
|
854
3915
|
const effectiveReadOnly = readOnly ?? !allowWrite;
|
|
855
3916
|
return {
|
|
856
3917
|
cwd,
|
|
857
3918
|
readOnly: effectiveReadOnly,
|
|
3919
|
+
maxCalls,
|
|
858
3920
|
...(http ? { http: { host, port, path, token, cors, readOnly: effectiveReadOnly } } : {}),
|
|
859
3921
|
};
|
|
860
3922
|
}
|
|
@@ -862,6 +3924,7 @@ export async function runMcp(argv = process.argv.slice(2)) {
|
|
|
862
3924
|
let parsed;
|
|
863
3925
|
try {
|
|
864
3926
|
parsed = parseServerArgs(argv);
|
|
3927
|
+
maxToolCalls = parsed.maxCalls;
|
|
865
3928
|
}
|
|
866
3929
|
catch (error) {
|
|
867
3930
|
process.stderr.write(`oc-pilot-mcp: ${error instanceof Error ? error.message : String(error)}\n\n${usage()}`);
|