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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import { analyzeEnvSync, extractEnvReferences, listEnvFiles } from "./env-sync.js";
|
|
7
|
+
const DIFF = [
|
|
8
|
+
"diff --git a/src/app.ts b/src/app.ts",
|
|
9
|
+
"--- a/src/app.ts",
|
|
10
|
+
"+++ b/src/app.ts",
|
|
11
|
+
"@@ -1,2 +1,8 @@",
|
|
12
|
+
" const a = 1;",
|
|
13
|
+
"+const key = process.env.NEW_SERVICE_KEY;",
|
|
14
|
+
'+const quoted = process.env["QUOTED_KEY"];',
|
|
15
|
+
"+const vite = import.meta.env.VITE_API_URL;",
|
|
16
|
+
'+const deno = Deno.env.get("DENO_TOKEN");',
|
|
17
|
+
"+const bun = Bun.env.BUN_FLAG;",
|
|
18
|
+
"+const ignored = process.env.NODE_ENV;",
|
|
19
|
+
" const b = 2;",
|
|
20
|
+
"",
|
|
21
|
+
].join("\n");
|
|
22
|
+
test("extractEnvReferences reads every supported env syntax", () => {
|
|
23
|
+
const references = extractEnvReferences(DIFF);
|
|
24
|
+
assert.deepEqual(references.map((reference) => reference.name), ["NEW_SERVICE_KEY", "QUOTED_KEY", "VITE_API_URL", "DENO_TOKEN", "BUN_FLAG"]);
|
|
25
|
+
assert.deepEqual(references.map((reference) => reference.line), [2, 3, 4, 5, 6]);
|
|
26
|
+
assert.deepEqual(references.map((reference) => reference.source), ["process.env", "process.env", "import.meta.env", "Deno.env", "Bun.env"]);
|
|
27
|
+
assert.ok(references.every((reference) => reference.file === "src/app.ts"));
|
|
28
|
+
assert.ok(!references.some((reference) => reference.name === "NODE_ENV"));
|
|
29
|
+
});
|
|
30
|
+
test("analyzeEnvSync warns about defined but undocumented variables", () => {
|
|
31
|
+
const dir = mkdtempSync(join(tmpdir(), "oc-pilot-env-sync-"));
|
|
32
|
+
try {
|
|
33
|
+
writeFileSync(join(dir, ".env"), "NEW_SERVICE_KEY=1\nQUOTED_KEY=2\nVITE_API_URL=https://example.com\nDENO_TOKEN=3\nBUN_FLAG=1\n", "utf8");
|
|
34
|
+
writeFileSync(join(dir, ".env.example"), "NEW_SERVICE_KEY=\n", "utf8");
|
|
35
|
+
const report = analyzeEnvSync(dir, DIFF);
|
|
36
|
+
assert.equal(report.status, "warn");
|
|
37
|
+
assert.deepEqual(report.envFiles, [".env", ".env.example"]);
|
|
38
|
+
assert.deepEqual(report.exampleFiles, [".env.example"]);
|
|
39
|
+
assert.deepEqual(report.missing, []);
|
|
40
|
+
assert.deepEqual(report.undocumented, ["BUN_FLAG", "DENO_TOKEN", "QUOTED_KEY", "VITE_API_URL"]);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
rmSync(dir, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
test("analyzeEnvSync fails on a referenced variable that is not defined", () => {
|
|
47
|
+
const dir = mkdtempSync(join(tmpdir(), "oc-pilot-env-sync-"));
|
|
48
|
+
try {
|
|
49
|
+
writeFileSync(join(dir, ".env"), "NEW_SERVICE_KEY=1\nQUOTED_KEY=2\nVITE_API_URL=x\n", "utf8");
|
|
50
|
+
writeFileSync(join(dir, ".env.example"), "NEW_SERVICE_KEY=\nQUOTED_KEY=\nVITE_API_URL=\nDENO_TOKEN=\n", "utf8");
|
|
51
|
+
const report = analyzeEnvSync(dir, DIFF);
|
|
52
|
+
assert.equal(report.status, "fail");
|
|
53
|
+
assert.deepEqual(report.missing, ["BUN_FLAG"]);
|
|
54
|
+
assert.deepEqual(report.undocumented, []);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
rmSync(dir, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
test("analyzeEnvSync passes when every variable is defined and documented", () => {
|
|
61
|
+
const dir = mkdtempSync(join(tmpdir(), "oc-pilot-env-sync-"));
|
|
62
|
+
try {
|
|
63
|
+
writeFileSync(join(dir, ".env"), "NEW_SERVICE_KEY=1\nQUOTED_KEY=2\nVITE_API_URL=x\nDENO_TOKEN=y\nBUN_FLAG=1\n", "utf8");
|
|
64
|
+
writeFileSync(join(dir, ".env.example"), "NEW_SERVICE_KEY=\nQUOTED_KEY=\nVITE_API_URL=\nDENO_TOKEN=\nBUN_FLAG=\n", "utf8");
|
|
65
|
+
const report = analyzeEnvSync(dir, DIFF);
|
|
66
|
+
assert.equal(report.status, "pass");
|
|
67
|
+
assert.deepEqual(report.missing, []);
|
|
68
|
+
assert.deepEqual(report.undocumented, []);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
rmSync(dir, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
test("listEnvFiles parses export lines and detects example files", () => {
|
|
75
|
+
const dir = mkdtempSync(join(tmpdir(), "oc-pilot-env-sync-"));
|
|
76
|
+
try {
|
|
77
|
+
writeFileSync(join(dir, ".env.local"), "export SECRET_TOKEN=abc\n", "utf8");
|
|
78
|
+
writeFileSync(join(dir, ".env.example"), "SECRET_TOKEN=\n", "utf8");
|
|
79
|
+
writeFileSync(join(dir, "README.md"), "# demo\n", "utf8");
|
|
80
|
+
assert.deepEqual(listEnvFiles(dir), [".env.example", ".env.local"]);
|
|
81
|
+
const diff = [
|
|
82
|
+
"diff --git a/src/auth.ts b/src/auth.ts",
|
|
83
|
+
"--- a/src/auth.ts",
|
|
84
|
+
"+++ b/src/auth.ts",
|
|
85
|
+
"@@ -1,1 +1,2 @@",
|
|
86
|
+
" const a = 1;",
|
|
87
|
+
"+const token = process.env.SECRET_TOKEN;",
|
|
88
|
+
"",
|
|
89
|
+
].join("\n");
|
|
90
|
+
const report = analyzeEnvSync(dir, diff);
|
|
91
|
+
assert.equal(report.status, "pass");
|
|
92
|
+
assert.deepEqual(report.defined, ["SECRET_TOKEN"]);
|
|
93
|
+
assert.deepEqual(report.documented, ["SECRET_TOKEN"]);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
rmSync(dir, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
//# sourceMappingURL=env-sync.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-sync.test.js","sourceRoot":"","sources":["../../src/core/env-sync.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
3
|
import { truncateText } from "./diff.js";
|
|
4
|
-
import { scanText } from "./secrets.js";
|
|
4
|
+
import { loadSecretIgnore, scanText } from "./secrets.js";
|
|
5
5
|
const DEFAULT_TOTAL = 60_000;
|
|
6
6
|
const DEFAULT_FILE = 20_000;
|
|
7
7
|
const MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
@@ -41,7 +41,9 @@ export function buildFileContext(cwd, changedFiles, options = {}) {
|
|
|
41
41
|
omitted.push(rel);
|
|
42
42
|
continue;
|
|
43
43
|
}
|
|
44
|
-
const scan = redact
|
|
44
|
+
const scan = redact
|
|
45
|
+
? scanText(content, rel, { ignore: loadSecretIgnore(cwd) })
|
|
46
|
+
: { findings: [], redacted: content };
|
|
45
47
|
findings.push(...scan.findings);
|
|
46
48
|
const perFile = truncateText(scan.redacted, maxFile, { mode: "head" });
|
|
47
49
|
const block = `--- FILE: ${rel} ---\n${perFile.text.trimEnd()}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-context.js","sourceRoot":"","sources":["../../src/core/file-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"file-context.js","sourceRoot":"","sources":["../../src/core/file-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { PilotError } from "./errors.js";
|
|
5
|
+
export const BASELINE_VERSION = 1;
|
|
6
|
+
export const DEFAULT_BASELINE_PATH = ".oc-pilot/baseline.json";
|
|
7
|
+
function fingerprint(parts) {
|
|
8
|
+
return createHash("sha256").update(parts.join("\u0000")).digest("hex").slice(0, 32);
|
|
9
|
+
}
|
|
10
|
+
/** A SAST finding keeps its identity when the file moves lines or gains whitespace. */
|
|
11
|
+
export function sastFingerprint(finding) {
|
|
12
|
+
return fingerprint(["sast", finding.rule, finding.file, finding.snippet.trim()]);
|
|
13
|
+
}
|
|
14
|
+
/** A route finding is identified by the rule and the route it protects. */
|
|
15
|
+
export function authzFingerprint(finding) {
|
|
16
|
+
return fingerprint(["authz", finding.rule, finding.file, finding.method, finding.route]);
|
|
17
|
+
}
|
|
18
|
+
export function emptyBaseline() {
|
|
19
|
+
return { version: BASELINE_VERSION, sast: [], authz: [] };
|
|
20
|
+
}
|
|
21
|
+
function stringArray(value) {
|
|
22
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
23
|
+
}
|
|
24
|
+
export function loadFindingsBaseline(cwd, file = DEFAULT_BASELINE_PATH) {
|
|
25
|
+
const path = resolve(cwd, file);
|
|
26
|
+
if (!existsSync(path))
|
|
27
|
+
return { path, baseline: emptyBaseline(), exists: false };
|
|
28
|
+
let data;
|
|
29
|
+
try {
|
|
30
|
+
data = JSON.parse(readFileSync(path, "utf8").replace(/^\uFEFF/, ""));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new PilotError(`Не удалось прочитать baseline: ${file}`, {
|
|
34
|
+
hint: "Файл baseline должен содержать JSON, созданный через --update-baseline.",
|
|
35
|
+
exitCode: 2,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const record = typeof data === "object" && data !== null ? data : {};
|
|
39
|
+
return {
|
|
40
|
+
path,
|
|
41
|
+
exists: true,
|
|
42
|
+
baseline: {
|
|
43
|
+
version: BASELINE_VERSION,
|
|
44
|
+
sast: stringArray(record.sast),
|
|
45
|
+
authz: stringArray(record.authz),
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function saveFindingsBaseline(cwd, file, baseline) {
|
|
50
|
+
const path = resolve(cwd, file);
|
|
51
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
52
|
+
const payload = {
|
|
53
|
+
version: BASELINE_VERSION,
|
|
54
|
+
sast: [...new Set(baseline.sast)].sort(),
|
|
55
|
+
authz: [...new Set(baseline.authz)].sort(),
|
|
56
|
+
};
|
|
57
|
+
writeFileSync(path, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
58
|
+
return path;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=findings-baseline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings-baseline.js","sourceRoot":"","sources":["../../src/core/findings-baseline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { BASELINE_VERSION, authzFingerprint, loadFindingsBaseline, sastFingerprint, saveFindingsBaseline, } from "./findings-baseline.js";
|
|
7
|
+
const SAST = {
|
|
8
|
+
rule: "command-injection",
|
|
9
|
+
severity: "high",
|
|
10
|
+
file: "src/app.ts",
|
|
11
|
+
line: 4,
|
|
12
|
+
column: 3,
|
|
13
|
+
message: "user input reaches exec",
|
|
14
|
+
snippet: "exec(req.query.cmd)",
|
|
15
|
+
};
|
|
16
|
+
const AUTHZ = {
|
|
17
|
+
rule: "idor",
|
|
18
|
+
severity: "high",
|
|
19
|
+
file: "src/routes.ts",
|
|
20
|
+
line: 2,
|
|
21
|
+
column: 3,
|
|
22
|
+
method: "GET",
|
|
23
|
+
route: "/users/:id",
|
|
24
|
+
message: "object by id without an owner check",
|
|
25
|
+
};
|
|
26
|
+
function makeRepo() {
|
|
27
|
+
return mkdtempSync(join(tmpdir(), "oc-pilot-baseline-"));
|
|
28
|
+
}
|
|
29
|
+
test("fingerprints survive line moves and distinguish targets", () => {
|
|
30
|
+
assert.equal(sastFingerprint(SAST), sastFingerprint({ ...SAST, line: 40, column: 9 }));
|
|
31
|
+
assert.notEqual(sastFingerprint(SAST), sastFingerprint({ ...SAST, rule: "sql-injection" }));
|
|
32
|
+
assert.notEqual(sastFingerprint(SAST), sastFingerprint({ ...SAST, file: "src/other.ts" }));
|
|
33
|
+
assert.equal(authzFingerprint(AUTHZ), authzFingerprint({ ...AUTHZ, line: 30 }));
|
|
34
|
+
assert.notEqual(authzFingerprint(AUTHZ), authzFingerprint({ ...AUTHZ, route: "/orders/:id" }));
|
|
35
|
+
});
|
|
36
|
+
test("loadFindingsBaseline treats a missing file as empty", () => {
|
|
37
|
+
const dir = makeRepo();
|
|
38
|
+
try {
|
|
39
|
+
const loaded = loadFindingsBaseline(dir);
|
|
40
|
+
assert.equal(loaded.exists, false);
|
|
41
|
+
assert.equal(loaded.baseline.version, BASELINE_VERSION);
|
|
42
|
+
assert.deepEqual(loaded.baseline.sast, []);
|
|
43
|
+
assert.deepEqual(loaded.baseline.authz, []);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
rmSync(dir, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
test("saveFindingsBaseline sorts and deduplicates entries", () => {
|
|
50
|
+
const dir = makeRepo();
|
|
51
|
+
try {
|
|
52
|
+
const path = saveFindingsBaseline(dir, "base.json", {
|
|
53
|
+
version: BASELINE_VERSION,
|
|
54
|
+
sast: ["b", "a", "a"],
|
|
55
|
+
authz: ["z", "y", "z"],
|
|
56
|
+
});
|
|
57
|
+
assert.match(path, /base\.json$/);
|
|
58
|
+
const loaded = loadFindingsBaseline(dir, "base.json");
|
|
59
|
+
assert.equal(loaded.exists, true);
|
|
60
|
+
assert.deepEqual(loaded.baseline.sast, ["a", "b"]);
|
|
61
|
+
assert.deepEqual(loaded.baseline.authz, ["y", "z"]);
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
rmSync(dir, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
test("loadFindingsBaseline rejects broken JSON", () => {
|
|
68
|
+
const dir = makeRepo();
|
|
69
|
+
try {
|
|
70
|
+
writeFileSync(join(dir, "baseline.json"), "{not json", "utf8");
|
|
71
|
+
assert.throws(() => loadFindingsBaseline(dir, "baseline.json"), /baseline/);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
rmSync(dir, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
//# sourceMappingURL=findings-baseline.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings-baseline.test.js","sourceRoot":"","sources":["../../src/core/findings-baseline.test.ts"],"names":[],"mappings":"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"}
|
package/dist/core/findings.js
CHANGED
|
@@ -35,6 +35,40 @@ function detectSeverity(text) {
|
|
|
35
35
|
return "style";
|
|
36
36
|
return undefined;
|
|
37
37
|
}
|
|
38
|
+
const LINE_RANGE = /^(.*?):(\d+)(?::(\d+))?(?:\s*[-–—,]\s*\d+(?::\d+)?)*$/;
|
|
39
|
+
const HASH_LINE = /^(.*?)#L(\d+)(?:-L?\d+)?$/i;
|
|
40
|
+
const WORD_LINE = /^(.*?)\s*\(?\s*(?:line|строк[аи])\s*(\d+)\s*\)?$/i;
|
|
41
|
+
/**
|
|
42
|
+
* Brings the location formats models actually produce to `file:line[:column]`:
|
|
43
|
+
* `a.ts:10-14` → `a.ts:10`, `a.ts:10,12` → `a.ts:10`, `a.ts#L10` → `a.ts:10`,
|
|
44
|
+
* `a.ts (line 10)` → `a.ts:10`. Ranges collapse to their first line because the
|
|
45
|
+
* PR/MR APIs anchor a comment to a single line. Unknown shapes are kept as-is.
|
|
46
|
+
*/
|
|
47
|
+
export function normalizeLocation(value) {
|
|
48
|
+
const raw = value.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
49
|
+
if (!raw)
|
|
50
|
+
return undefined;
|
|
51
|
+
const range = LINE_RANGE.exec(raw);
|
|
52
|
+
if (range) {
|
|
53
|
+
const file = (range[1] ?? "").trim();
|
|
54
|
+
if (!file)
|
|
55
|
+
return undefined;
|
|
56
|
+
return `${file}:${range[2]}${range[3] ? `:${range[3]}` : ""}`;
|
|
57
|
+
}
|
|
58
|
+
const hash = HASH_LINE.exec(raw);
|
|
59
|
+
if (hash) {
|
|
60
|
+
const file = (hash[1] ?? "").trim();
|
|
61
|
+
if (file)
|
|
62
|
+
return `${file}:${hash[2]}`;
|
|
63
|
+
}
|
|
64
|
+
const word = WORD_LINE.exec(raw);
|
|
65
|
+
if (word) {
|
|
66
|
+
const file = (word[1] ?? "").trim();
|
|
67
|
+
if (file)
|
|
68
|
+
return `${file}:${word[2]}`;
|
|
69
|
+
}
|
|
70
|
+
return raw;
|
|
71
|
+
}
|
|
38
72
|
function stripMarkdown(value) {
|
|
39
73
|
return value
|
|
40
74
|
.replace(/`/g, "")
|
|
@@ -47,11 +81,36 @@ function stripMarkdown(value) {
|
|
|
47
81
|
* Actions`) section of an analysis response. The output schema is intentionally
|
|
48
82
|
* simple, but models vary in spacing and emphasis, so the parser is permissive.
|
|
49
83
|
*/
|
|
84
|
+
/** Parses a single findings bullet; returns undefined for non-finding lines. */
|
|
85
|
+
export function parseFindingBullet(line) {
|
|
86
|
+
const bullet = /^\s*[-*]\s+(.+)$/.exec(line);
|
|
87
|
+
if (!bullet)
|
|
88
|
+
return undefined;
|
|
89
|
+
const body = bullet[1].trim();
|
|
90
|
+
const severity = detectSeverity(body);
|
|
91
|
+
if (!severity)
|
|
92
|
+
return undefined;
|
|
93
|
+
const locationMatch = /`([^`]+)`/.exec(body);
|
|
94
|
+
const location = locationMatch ? normalizeLocation(locationMatch[1]) : undefined;
|
|
95
|
+
let rawText = body;
|
|
96
|
+
if (locationMatch)
|
|
97
|
+
rawText = rawText.replace(locationMatch[0], "");
|
|
98
|
+
let text = stripMarkdown(rawText);
|
|
99
|
+
text = text
|
|
100
|
+
.replace(/^🔴\s*/, "")
|
|
101
|
+
.replace(/^🟡\s*/, "")
|
|
102
|
+
.replace(/^🔵\s*/, "")
|
|
103
|
+
.replace(/^(?:critical|warning|style|note|критич\w*|предупрежд\w*|стиль)(?:\s*\/\s*(?:critical|warning|style|note|критич\w*|предупрежд\w*|стиль))?\s*[:—-]?\s*/i, "")
|
|
104
|
+
.replace(/^\/\s*/, "")
|
|
105
|
+
.replace(/^[:\s—-]+/, "")
|
|
106
|
+
.trim();
|
|
107
|
+
return { severity, location, text: text || "замечание без описания" };
|
|
108
|
+
}
|
|
50
109
|
export function parseFindings(markdown) {
|
|
51
110
|
const normalized = markdown.replace(/\r\n?/g, "\n");
|
|
52
111
|
if (/LGTM:\s*(?:критических проблем не обнаружено|no critical issues found)/i.test(normalized))
|
|
53
112
|
return [];
|
|
54
|
-
const heading = /^####\s+(
|
|
113
|
+
const heading = /^####\s+(?:Замечания|Findings)(?:\s*\/\s*(?:Действия|Actions))?\s*:?\s*$/im.exec(normalized);
|
|
55
114
|
if (!heading)
|
|
56
115
|
return [];
|
|
57
116
|
const start = heading.index + heading[0].length;
|
|
@@ -61,29 +120,17 @@ export function parseFindings(markdown) {
|
|
|
61
120
|
if (!section)
|
|
62
121
|
return [];
|
|
63
122
|
const findings = [];
|
|
123
|
+
let fenced = false;
|
|
64
124
|
for (const line of section.split("\n")) {
|
|
65
|
-
|
|
66
|
-
|
|
125
|
+
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
126
|
+
fenced = !fenced;
|
|
67
127
|
continue;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (!severity)
|
|
128
|
+
}
|
|
129
|
+
if (fenced)
|
|
71
130
|
continue;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (locationMatch)
|
|
76
|
-
rawText = rawText.replace(locationMatch[0], "");
|
|
77
|
-
let text = stripMarkdown(rawText);
|
|
78
|
-
text = text
|
|
79
|
-
.replace(/^🔴\s*/, "")
|
|
80
|
-
.replace(/^🟡\s*/, "")
|
|
81
|
-
.replace(/^🔵\s*/, "")
|
|
82
|
-
.replace(/^(?:critical|warning|style|note|критич\w*|предупрежд\w*|стиль)(?:\s*\/\s*(?:critical|warning|style|note|критич\w*|предупрежд\w*|стиль))?\s*[:—-]?\s*/i, "")
|
|
83
|
-
.replace(/^\/\s*/, "")
|
|
84
|
-
.replace(/^[:\s—-]+/, "")
|
|
85
|
-
.trim();
|
|
86
|
-
findings.push({ severity, location, text: text || "замечание без описания" });
|
|
131
|
+
const finding = parseFindingBullet(line);
|
|
132
|
+
if (finding)
|
|
133
|
+
findings.push(finding);
|
|
87
134
|
}
|
|
88
135
|
return findings;
|
|
89
136
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"findings.js","sourceRoot":"","sources":["../../src/core/findings.ts"],"names":[],"mappings":"AAEA,MAAM,aAAa,GAAoC;IACrD,QAAQ,EAAE,CAAC;IACX,OAAO,EAAE,CAAC;IACV,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,MAAM,cAAc,GAAoC;IACtD,QAAQ,EAAE,aAAa;IACvB,OAAO,EAAE,YAAY;IACrB,KAAK,EAAE,UAAU;CAClB,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,QAAyB;IACpD,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAyB;IACrD,OAAO,cAAc,CAAC,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACvC,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,OAAO;QAAE,OAAO,MAAM,CAAC;IAC5E,IAAI,KAAK,KAAK,UAAU,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,OAAO;QAAE,OAAO,UAAU,CAAC;IACrF,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9D,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,OAAO,CAAC;IAChG,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,+CAA+C,CAAC,CAAC;AAC9F,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IAClC,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC;IACxD,IAAI,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAC1D,IAAI,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC;IAC9D,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,
|
|
1
|
+
{"version":3,"file":"findings.js","sourceRoot":"","sources":["../../src/core/findings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const ANSI = /\u001B\[[0-9;]*m/g;
|
|
2
|
+
/** Strips ANSI colors so status markers survive any reporter. */
|
|
3
|
+
export function stripAnsi(text) {
|
|
4
|
+
return text.replace(ANSI, "");
|
|
5
|
+
}
|
|
6
|
+
const PATTERNS = [
|
|
7
|
+
// node:test spec reporter
|
|
8
|
+
{ status: "pass", regex: /^\s*\u2714\s+(.+?)(?:\s+\(\d+(?:\.\d+)?\s*m?s\))?\s*$/ },
|
|
9
|
+
{ status: "fail", regex: /^\s*[\u2716\u2717]\s+(.+?)(?:\s+\(\d+(?:\.\d+)?\s*m?s\))?\s*$/ },
|
|
10
|
+
// TAP
|
|
11
|
+
{ status: "pass", regex: /^ok\s+\d+\s+-\s+(.+?)\s*$/ },
|
|
12
|
+
{ status: "fail", regex: /^not ok\s+\d+\s+-\s+(.+?)\s*$/ },
|
|
13
|
+
// go test
|
|
14
|
+
{ status: "pass", regex: /^--- PASS:\s+(\S+)/ },
|
|
15
|
+
{ status: "fail", regex: /^--- FAIL:\s+(\S+)/ },
|
|
16
|
+
// pytest -v
|
|
17
|
+
{ status: "pass", regex: /^PASSED\s+(\S+)/ },
|
|
18
|
+
{ status: "fail", regex: /^FAILED\s+(\S+)/ },
|
|
19
|
+
// jest / vitest
|
|
20
|
+
{ status: "pass", regex: /^\s*\u2713\s+(.+?)(?:\s+\(\d+(?:\.\d+)?\s*m?s\))?\s*$/ },
|
|
21
|
+
{ status: "fail", regex: /^\s*[\u2715\u00d7]\s+(.+?)(?:\s+\(\d+(?:\.\d+)?\s*m?s\))?\s*$/ },
|
|
22
|
+
];
|
|
23
|
+
/** Extracts per-test pass/fail markers from common reporter output. */
|
|
24
|
+
export function parseTestOutput(output) {
|
|
25
|
+
const results = new Map();
|
|
26
|
+
for (const rawLine of stripAnsi(output).split(/\r?\n/)) {
|
|
27
|
+
for (const { status, regex } of PATTERNS) {
|
|
28
|
+
const match = regex.exec(rawLine);
|
|
29
|
+
if (match?.[1]) {
|
|
30
|
+
results.set(match[1].trim(), status);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
/** Aggregates per-run results into stable, failing and flaky tests. */
|
|
38
|
+
export function classifyRuns(runs) {
|
|
39
|
+
const totals = new Map();
|
|
40
|
+
for (const run of runs) {
|
|
41
|
+
for (const [name, status] of run) {
|
|
42
|
+
const entry = totals.get(name) ?? { passes: 0, failures: 0 };
|
|
43
|
+
if (status === "pass")
|
|
44
|
+
entry.passes += 1;
|
|
45
|
+
else
|
|
46
|
+
entry.failures += 1;
|
|
47
|
+
totals.set(name, entry);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const outcomes = [];
|
|
51
|
+
for (const [name, entry] of totals) {
|
|
52
|
+
const status = entry.passes > 0 && entry.failures > 0 ? "flaky" : entry.failures > 0 ? "failing" : "passing";
|
|
53
|
+
outcomes.push({ name, passes: entry.passes, failures: entry.failures, status });
|
|
54
|
+
}
|
|
55
|
+
const rank = { flaky: 0, failing: 1, passing: 2 };
|
|
56
|
+
outcomes.sort((a, b) => rank[a.status] - rank[b.status] || a.name.localeCompare(b.name));
|
|
57
|
+
return outcomes;
|
|
58
|
+
}
|
|
59
|
+
/** Builds the report from completed runs. */
|
|
60
|
+
export function buildFlakyReport(command, runs) {
|
|
61
|
+
const parsedRuns = runs.map((run) => parseTestOutput(run.output));
|
|
62
|
+
const parsed = parsedRuns.some((run) => run.size > 0);
|
|
63
|
+
const tests = parsed ? classifyRuns(parsedRuns) : [];
|
|
64
|
+
return {
|
|
65
|
+
command,
|
|
66
|
+
runs: runs.length,
|
|
67
|
+
completed: runs.filter((run) => run.exitCode !== null && !run.timedOut).length,
|
|
68
|
+
parsed,
|
|
69
|
+
tests,
|
|
70
|
+
flaky: tests.filter((test) => test.status === "flaky"),
|
|
71
|
+
failing: tests.filter((test) => test.status === "failing"),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Human-readable summary. */
|
|
75
|
+
export function renderFlaky(report) {
|
|
76
|
+
const lines = [
|
|
77
|
+
`Команда: ${report.command}`,
|
|
78
|
+
`Прогонов: ${String(report.runs)} (завершено ${String(report.completed)}).`,
|
|
79
|
+
];
|
|
80
|
+
if (!report.parsed) {
|
|
81
|
+
lines.push("Не удалось распознать отдельные тесты в выводе — проверьте коды возврата вручную.");
|
|
82
|
+
return `${lines.join("\n")}\n`;
|
|
83
|
+
}
|
|
84
|
+
lines.push(`Тестов распознано: ${String(report.tests.length)}.`);
|
|
85
|
+
if (report.flaky.length === 0) {
|
|
86
|
+
lines.push("Нестабильных тестов не найдено.");
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
lines.push(`Нестабильные (${String(report.flaky.length)}):`);
|
|
90
|
+
for (const test of report.flaky) {
|
|
91
|
+
lines.push(` ${test.name} — упал в ${String(test.failures)} из ${String(report.runs)} прогонов`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (report.failing.length > 0) {
|
|
95
|
+
lines.push(`Всегда падают (${String(report.failing.length)}):`);
|
|
96
|
+
for (const test of report.failing)
|
|
97
|
+
lines.push(` ${test.name}`);
|
|
98
|
+
}
|
|
99
|
+
return `${lines.join("\n")}\n`;
|
|
100
|
+
}
|
|
101
|
+
/** Deterministic PRNG so the same seed always reproduces the same order. */
|
|
102
|
+
function mulberry32(seed) {
|
|
103
|
+
let state = seed >>> 0;
|
|
104
|
+
return () => {
|
|
105
|
+
state = (state + 0x6d2b79f5) >>> 0;
|
|
106
|
+
let value = state;
|
|
107
|
+
value = Math.imul(value ^ (value >>> 15), value | 1);
|
|
108
|
+
value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
|
|
109
|
+
return ((value ^ (value >>> 14)) >>> 0) / 4_294_967_296;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function hashSeed(seed) {
|
|
113
|
+
let hash = 0x811c9dc5;
|
|
114
|
+
for (let index = 0; index < seed.length; index += 1) {
|
|
115
|
+
hash ^= seed.charCodeAt(index);
|
|
116
|
+
hash = Math.imul(hash, 0x01000193);
|
|
117
|
+
}
|
|
118
|
+
return hash >>> 0;
|
|
119
|
+
}
|
|
120
|
+
export function seededShuffle(items, seed) {
|
|
121
|
+
const random = mulberry32(hashSeed(seed));
|
|
122
|
+
const result = [...items];
|
|
123
|
+
for (let index = result.length - 1; index > 0; index -= 1) {
|
|
124
|
+
const swap = Math.floor(random() * (index + 1));
|
|
125
|
+
const current = result[index];
|
|
126
|
+
result[index] = result[swap];
|
|
127
|
+
result[swap] = current;
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
function shellQuote(value) {
|
|
132
|
+
return /[\s"'$`\\]/.test(value) ? `"${value.replace(/["\\$`]/g, "\\$&")}"` : value;
|
|
133
|
+
}
|
|
134
|
+
export function buildShuffleCommand(command, files, seed) {
|
|
135
|
+
return command.replaceAll("{files}", seededShuffle(files, seed).map(shellQuote).join(" "));
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=flaky.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flaky.js","sourceRoot":"","sources":["../../src/core/flaky.ts"],"names":[],"mappings":"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"}
|