pi-dev-team 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (780) hide show
  1. package/LICENSE +21 -0
  2. package/PORTING.md +134 -0
  3. package/README.md +207 -0
  4. package/UPSTREAM.json +64 -0
  5. package/agents/Explore.md +15 -0
  6. package/agents/a11y-review.md +118 -0
  7. package/agents/adr-author.md +70 -0
  8. package/agents/ai-provenance-review.md +120 -0
  9. package/agents/angular-reactivity-review.md +95 -0
  10. package/agents/arch-review.md +135 -0
  11. package/agents/architect.md +78 -0
  12. package/agents/autoship-batch-proposer.md +69 -0
  13. package/agents/claude-setup-review.md +136 -0
  14. package/agents/codebase-recon.md +184 -0
  15. package/agents/component-architecture-review.md +119 -0
  16. package/agents/concurrency-review.md +109 -0
  17. package/agents/correctness-review.md +290 -0
  18. package/agents/data-flow-tracer.md +120 -0
  19. package/agents/doc-review.md +165 -0
  20. package/agents/domain-review.md +136 -0
  21. package/agents/general-purpose.md +10 -0
  22. package/agents/gherkin-quality-critic.md +113 -0
  23. package/agents/js-fp-review.md +114 -0
  24. package/agents/mutation-kill.md +684 -0
  25. package/agents/naming-review.md +142 -0
  26. package/agents/orchestrator.md +339 -0
  27. package/agents/performance-review.md +105 -0
  28. package/agents/plan-review-acceptance.md +115 -0
  29. package/agents/plan-review-design.md +90 -0
  30. package/agents/plan-review-parallelization.md +84 -0
  31. package/agents/plan-review-strategic.md +96 -0
  32. package/agents/plan-review-ux.md +110 -0
  33. package/agents/platform-engineer.md +64 -0
  34. package/agents/product-manager.md +68 -0
  35. package/agents/progress-guardian.md +79 -0
  36. package/agents/qa-engineer.md +289 -0
  37. package/agents/quality-reviewer.md +132 -0
  38. package/agents/react-reactivity-review.md +102 -0
  39. package/agents/refactor-opportunity-review.md +128 -0
  40. package/agents/security-engineer.md +60 -0
  41. package/agents/security-review.md +218 -0
  42. package/agents/session-analysis.md +95 -0
  43. package/agents/software-engineer.md +105 -0
  44. package/agents/spec-compliance-review.md +100 -0
  45. package/agents/spec-reviewer.md +114 -0
  46. package/agents/structure-review.md +146 -0
  47. package/agents/tech-writer.md +84 -0
  48. package/agents/test-review.md +246 -0
  49. package/agents/test-smell-review.md +188 -0
  50. package/agents/token-efficiency-review.md +139 -0
  51. package/agents/ui-ux-designer.md +54 -0
  52. package/agents/vue-reactivity-review.md +95 -0
  53. package/bin/__pycache__/claudecpython-314.pyc +0 -0
  54. package/bin/claude +258 -0
  55. package/docs/upstream/.pages +1 -0
  56. package/docs/upstream/CHANGELOG.md +2586 -0
  57. package/docs/upstream/README.md +155 -0
  58. package/docs/upstream/agent-architecture.md +214 -0
  59. package/docs/upstream/agent_info.md +187 -0
  60. package/docs/upstream/artifact-migration.md +124 -0
  61. package/docs/upstream/code-intelligence-nudge.md +149 -0
  62. package/docs/upstream/code-review-process.md +294 -0
  63. package/docs/upstream/concurrent-use.md +73 -0
  64. package/docs/upstream/context-management.md +111 -0
  65. package/docs/upstream/developer-notes.md +280 -0
  66. package/docs/upstream/diagrams/architecture-overview.svg +101 -0
  67. package/docs/upstream/diagrams/review-dispatch.svg +139 -0
  68. package/docs/upstream/diagrams/team-agents.svg +128 -0
  69. package/docs/upstream/diagrams/test-improve-flow.svg +166 -0
  70. package/docs/upstream/diagrams/workflow-linear.svg +66 -0
  71. package/docs/upstream/diagrams/workflow-three-phase.svg +200 -0
  72. package/docs/upstream/eval-maintenance.md +95 -0
  73. package/docs/upstream/eval-running-guide.md +147 -0
  74. package/docs/upstream/eval-system.md +291 -0
  75. package/docs/upstream/session-review-oss-complements.md +75 -0
  76. package/docs/upstream/session-review.md +212 -0
  77. package/docs/upstream/skills.md +188 -0
  78. package/docs/upstream/team-structure.md +21 -0
  79. package/docs/upstream/telemetry-ci-access.md +129 -0
  80. package/docs/upstream/telemetry-repo-security.md +120 -0
  81. package/docs/upstream/test-evaluation.md +277 -0
  82. package/docs/upstream/test-improve.md +154 -0
  83. package/docs/upstream/triage-workflow.md +282 -0
  84. package/docs/upstream/workflows.md +289 -0
  85. package/extensions/dev-team/index.ts +539 -0
  86. package/extensions/dev-team/lib/agents.ts +272 -0
  87. package/extensions/dev-team/lib/ai-credits.ts +92 -0
  88. package/extensions/dev-team/lib/autocompact.ts +81 -0
  89. package/extensions/dev-team/lib/child-run.ts +102 -0
  90. package/extensions/dev-team/lib/config.ts +236 -0
  91. package/extensions/dev-team/lib/gh-command.ts +103 -0
  92. package/extensions/dev-team/lib/github-style.ts +307 -0
  93. package/extensions/dev-team/lib/hooks.ts +350 -0
  94. package/extensions/dev-team/lib/metrics.ts +115 -0
  95. package/extensions/dev-team/lib/safe-read.ts +49 -0
  96. package/extensions/dev-team/lib/session-files.ts +57 -0
  97. package/extensions/dev-team/lib/session-spend.ts +123 -0
  98. package/extensions/dev-team/lib/shell-scan.ts +205 -0
  99. package/extensions/dev-team/lib/skills.ts +213 -0
  100. package/extensions/dev-team/lib/subagent-render.ts +245 -0
  101. package/extensions/dev-team/lib/subagent-types.ts +164 -0
  102. package/extensions/dev-team/lib/subagent.ts +596 -0
  103. package/extensions/dev-team/lib/terminal-text.ts +54 -0
  104. package/extensions/dev-team/lib/tools-misc.ts +152 -0
  105. package/extensions/dev-team/lib/transcript.ts +110 -0
  106. package/extensions/dev-team/lib/trust.ts +52 -0
  107. package/extensions/dev-team/lib/usage-breakdown.ts +176 -0
  108. package/extensions/dev-team/lib/usage-chart.ts +153 -0
  109. package/extensions/dev-team/lib/usage-command.ts +107 -0
  110. package/extensions/dev-team/lib/usage-history.ts +203 -0
  111. package/extensions/dev-team/lib/usage-render.ts +225 -0
  112. package/extensions/dev-team/lib/usage-split-bar.ts +127 -0
  113. package/extensions/dev-team/lib/usage-state.ts +116 -0
  114. package/extensions/dev-team/lib/usage-text.ts +159 -0
  115. package/extensions/dev-team/lib/usage-view.ts +109 -0
  116. package/hooks/__pycache__/refactor_test_freeze_guard.cpython-314.pyc +0 -0
  117. package/hooks/agent_dispatch_ledger.py +190 -0
  118. package/hooks/autocompact_setup_nudge.py +99 -0
  119. package/hooks/bash_retry_guard.py +228 -0
  120. package/hooks/boundary_events_write_guard.py +352 -0
  121. package/hooks/code_intelligence_nudge.py +293 -0
  122. package/hooks/code_intelligence_turn_mark.py +317 -0
  123. package/hooks/codegraph_bootstrap.py +139 -0
  124. package/hooks/contract_version_guard.py +362 -0
  125. package/hooks/cost_meter.py +106 -0
  126. package/hooks/destructive-commands.json +62 -0
  127. package/hooks/destructive_guard.py +477 -0
  128. package/hooks/eval_compliance_check.py +440 -0
  129. package/hooks/guards.json +17 -0
  130. package/hooks/hooks.json +323 -0
  131. package/hooks/internal_double_gate.py +296 -0
  132. package/hooks/js_fp_review.py +212 -0
  133. package/hooks/knowledge_index.py +119 -0
  134. package/hooks/lib/__pycache__/artifact_paths.cpython-314.pyc +0 -0
  135. package/hooks/lib/__pycache__/atomic_state.cpython-314.pyc +0 -0
  136. package/hooks/lib/__pycache__/autocompact_config.cpython-314.pyc +0 -0
  137. package/hooks/lib/__pycache__/boundary_events.cpython-314.pyc +0 -0
  138. package/hooks/lib/__pycache__/doc_classification.cpython-314.pyc +0 -0
  139. package/hooks/lib/__pycache__/gh_pr_create_detect.cpython-314.pyc +0 -0
  140. package/hooks/lib/__pycache__/git_safe_diff.cpython-314.pyc +0 -0
  141. package/hooks/lib/__pycache__/instrument_log.cpython-314.pyc +0 -0
  142. package/hooks/lib/__pycache__/metrics_query.cpython-314.pyc +0 -0
  143. package/hooks/lib/__pycache__/plugin_version.cpython-314.pyc +0 -0
  144. package/hooks/lib/__pycache__/pre_commit_doc_classifier.cpython-314.pyc +0 -0
  145. package/hooks/lib/__pycache__/review_agent_registry.cpython-314.pyc +0 -0
  146. package/hooks/lib/__pycache__/review_gate_corroboration.cpython-314.pyc +0 -0
  147. package/hooks/lib/__pycache__/review_gate_hash.cpython-314.pyc +0 -0
  148. package/hooks/lib/__pycache__/review_verdicts.cpython-314.pyc +0 -0
  149. package/hooks/lib/__pycache__/stdin_json.cpython-314.pyc +0 -0
  150. package/hooks/lib/__pycache__/stryker_invocation.cpython-314.pyc +0 -0
  151. package/hooks/lib/__pycache__/telemetry_consent.cpython-314.pyc +0 -0
  152. package/hooks/lib/__pycache__/test_file_classify.cpython-314.pyc +0 -0
  153. package/hooks/lib/__pycache__/token_efficiency_limits.cpython-314.pyc +0 -0
  154. package/hooks/lib/__pycache__/verify_guard_state.cpython-314.pyc +0 -0
  155. package/hooks/lib/__pycache__/xunit_v3_operator_gate.cpython-314.pyc +0 -0
  156. package/hooks/lib/agent_skill_hints.py +74 -0
  157. package/hooks/lib/artifact_paths.py +263 -0
  158. package/hooks/lib/atomic_state.py +557 -0
  159. package/hooks/lib/autocompact_config.py +103 -0
  160. package/hooks/lib/autoship_log.py +106 -0
  161. package/hooks/lib/banned_scripts_policy.py +51 -0
  162. package/hooks/lib/boundary_events.py +436 -0
  163. package/hooks/lib/build_knowledge_index.py +504 -0
  164. package/hooks/lib/build_skills_index.py +361 -0
  165. package/hooks/lib/build_state.py +116 -0
  166. package/hooks/lib/classify_ship_outcome.py +126 -0
  167. package/hooks/lib/config_changelog_schema.py +115 -0
  168. package/hooks/lib/cost_meter.py +955 -0
  169. package/hooks/lib/doc_classification.py +116 -0
  170. package/hooks/lib/gh_pr_create_detect.py +136 -0
  171. package/hooks/lib/git_safe_diff.py +123 -0
  172. package/hooks/lib/instrument_log.py +66 -0
  173. package/hooks/lib/iteration_journal_gate.py +197 -0
  174. package/hooks/lib/knowledge_index_paths.py +88 -0
  175. package/hooks/lib/mcp_json_repowise.py +177 -0
  176. package/hooks/lib/metrics_query.py +202 -0
  177. package/hooks/lib/minimal_yaml.py +434 -0
  178. package/hooks/lib/plugin_version.py +142 -0
  179. package/hooks/lib/pre_commit_detect.py +537 -0
  180. package/hooks/lib/pre_commit_doc_classifier.py +126 -0
  181. package/hooks/lib/pricing.py +118 -0
  182. package/hooks/lib/report_pdf.py +371 -0
  183. package/hooks/lib/review_agent_registry.py +142 -0
  184. package/hooks/lib/review_dispatch_ledger.py +101 -0
  185. package/hooks/lib/review_gate_corroboration.py +521 -0
  186. package/hooks/lib/review_gate_hash.py +252 -0
  187. package/hooks/lib/review_gate_normalized_hash.py +1115 -0
  188. package/hooks/lib/review_verdicts.py +301 -0
  189. package/hooks/lib/run_report.py +160 -0
  190. package/hooks/lib/skill_categories.yaml +125 -0
  191. package/hooks/lib/stdin_json.py +57 -0
  192. package/hooks/lib/stryker_invocation.py +102 -0
  193. package/hooks/lib/telemetry_consent.py +41 -0
  194. package/hooks/lib/telemetry_report.py +108 -0
  195. package/hooks/lib/test_file_classify.py +160 -0
  196. package/hooks/lib/token_efficiency_limits.py +51 -0
  197. package/hooks/lib/turn_identity.py +77 -0
  198. package/hooks/lib/verify_guard_state.py +110 -0
  199. package/hooks/lib/workflow_state.py +206 -0
  200. package/hooks/lib/xunit_v3_operator_gate.py +596 -0
  201. package/hooks/mcp_json_repowise_nudge.py +74 -0
  202. package/hooks/mutation_adapters/__init__.py +7 -0
  203. package/hooks/mutation_adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  204. package/hooks/mutation_adapters/__pycache__/lib.cpython-314.pyc +0 -0
  205. package/hooks/mutation_adapters/__pycache__/mutmut.cpython-314.pyc +0 -0
  206. package/hooks/mutation_adapters/__pycache__/pitest.cpython-314.pyc +0 -0
  207. package/hooks/mutation_adapters/__pycache__/stryker.cpython-314.pyc +0 -0
  208. package/hooks/mutation_adapters/__pycache__/stryker_net.cpython-314.pyc +0 -0
  209. package/hooks/mutation_adapters/lib.py +478 -0
  210. package/hooks/mutation_adapters/mutmut.py +188 -0
  211. package/hooks/mutation_adapters/pitest.py +266 -0
  212. package/hooks/mutation_adapters/stryker.py +157 -0
  213. package/hooks/mutation_adapters/stryker_net.py +264 -0
  214. package/hooks/mutation_gate.py +193 -0
  215. package/hooks/mutation_testing_smoke_gate.py +371 -0
  216. package/hooks/pending_review_notify.py +121 -0
  217. package/hooks/phase_marker.py +138 -0
  218. package/hooks/post_compact_state_reinject.py +180 -0
  219. package/hooks/post_format.py +115 -0
  220. package/hooks/pre_commit_knowledge_index.py +128 -0
  221. package/hooks/pre_commit_review.py +66 -0
  222. package/hooks/pre_pr_review.py +694 -0
  223. package/hooks/pre_tool_guard.py +405 -0
  224. package/hooks/py.sh +73 -0
  225. package/hooks/refactor-bash-write-patterns.json +29 -0
  226. package/hooks/refactor_test_bash_guard.py +253 -0
  227. package/hooks/refactor_test_freeze_guard.py +139 -0
  228. package/hooks/refactor_test_revert_guard.py +186 -0
  229. package/hooks/repo_review_nudge.py +287 -0
  230. package/hooks/review_verdict_recorder.py +464 -0
  231. package/hooks/scan_bash_command_for_banned_scripts.py +428 -0
  232. package/hooks/scan_worktree_for_banned_scripts.py +238 -0
  233. package/hooks/session_learning_trigger.py +248 -0
  234. package/hooks/skills_index.py +126 -0
  235. package/hooks/stryker_xunit_shim_guard.py +571 -0
  236. package/hooks/subagent_completion_guard.py +309 -0
  237. package/hooks/subagent_skill_context.py +139 -0
  238. package/hooks/task_completion_metrics.py +216 -0
  239. package/hooks/tdd_guard.py +229 -0
  240. package/hooks/telemetry.py +341 -0
  241. package/hooks/token_efficiency_review.py +194 -0
  242. package/hooks/verify_guard.py +183 -0
  243. package/hooks/verify_guard_edit_marker.py +73 -0
  244. package/hooks/version_check.py +173 -0
  245. package/knowledge/accepted-risks-schema.md +98 -0
  246. package/knowledge/adr-decision-criteria.md +64 -0
  247. package/knowledge/adversarial-review-protocol.md +139 -0
  248. package/knowledge/agent-registry.md +228 -0
  249. package/knowledge/agent-review-methodology.md +80 -0
  250. package/knowledge/ai-friendly-repo-guidelines.md +67 -0
  251. package/knowledge/architecture-assessment.md +96 -0
  252. package/knowledge/artifact-lifecycle.md +57 -0
  253. package/knowledge/cd-maturity-model.md +82 -0
  254. package/knowledge/cd-test-architecture.md +190 -0
  255. package/knowledge/ci-cd-file-scope.md +24 -0
  256. package/knowledge/codegraph-vs-graphify.md +192 -0
  257. package/knowledge/component-test-patterns.md +139 -0
  258. package/knowledge/database-change-management.md +80 -0
  259. package/knowledge/database-test-patterns.md +79 -0
  260. package/knowledge/decision-defaults.md +88 -0
  261. package/knowledge/dependency-breaking-techniques.md +116 -0
  262. package/knowledge/deployment-pipeline.md +86 -0
  263. package/knowledge/design-smells.md +122 -0
  264. package/knowledge/directory-enumeration.md +38 -0
  265. package/knowledge/domain-modeling.md +123 -0
  266. package/knowledge/evidence-bundle.md +90 -0
  267. package/knowledge/exploratory-testing-field-guide.md +122 -0
  268. package/knowledge/failure-routing.md +28 -0
  269. package/knowledge/fixture-construction.md +56 -0
  270. package/knowledge/frontend-component-architecture.md +139 -0
  271. package/knowledge/gherkin-quality-review-dispatch.md +135 -0
  272. package/knowledge/index.json +6766 -0
  273. package/knowledge/internal-collaborator-doubling.md +101 -0
  274. package/knowledge/legacy-test-strategy.md +71 -0
  275. package/knowledge/long-run-waiting.md +66 -0
  276. package/knowledge/microservice-testing.md +71 -0
  277. package/knowledge/model-pricing.json +23 -0
  278. package/knowledge/mutation-score-formulas.md +60 -0
  279. package/knowledge/object-calisthenics.md +147 -0
  280. package/knowledge/oracle-provenance.md +94 -0
  281. package/knowledge/orchestrator-script-implementation.md +185 -0
  282. package/knowledge/owasp-detection.md +148 -0
  283. package/knowledge/plan-review-rubric.md +56 -0
  284. package/knowledge/proxy-connectivity.md +62 -0
  285. package/knowledge/reactive-effect-patterns.md +73 -0
  286. package/knowledge/recon-inventory-excludes.txt +32 -0
  287. package/knowledge/references/bdd-value-guide.md +61 -0
  288. package/knowledge/references/csharp-http-client-testing.md +264 -0
  289. package/knowledge/release-strategies.md +74 -0
  290. package/knowledge/report-output-location.md +117 -0
  291. package/knowledge/report-pdf-integration.md +63 -0
  292. package/knowledge/report-print.css +129 -0
  293. package/knowledge/report-template.md +114 -0
  294. package/knowledge/report-to-pdf.md +69 -0
  295. package/knowledge/request-processing-flow.md +63 -0
  296. package/knowledge/result-verification.md +52 -0
  297. package/knowledge/review-agent-output-contract.md +121 -0
  298. package/knowledge/review-lens-classification.md +113 -0
  299. package/knowledge/review-rubric.md +62 -0
  300. package/knowledge/review-template.md +104 -0
  301. package/knowledge/rule-fixtures/A02.insecure-random-js/negative.js +1 -0
  302. package/knowledge/rule-fixtures/A02.insecure-random-js/positive.js +1 -0
  303. package/knowledge/rule-fixtures/A02.weak-hashing-md5/negative.py +1 -0
  304. package/knowledge/rule-fixtures/A02.weak-hashing-md5/positive.py +1 -0
  305. package/knowledge/rule-fixtures/A03.command-injection/negative.js +1 -0
  306. package/knowledge/rule-fixtures/A03.command-injection/positive.js +1 -0
  307. package/knowledge/rule-fixtures/A03.sql-injection/negative.js +1 -0
  308. package/knowledge/rule-fixtures/A03.sql-injection/positive.js +1 -0
  309. package/knowledge/rule-fixtures/A03.xss-innerhtml/negative.js +1 -0
  310. package/knowledge/rule-fixtures/A03.xss-innerhtml/positive.js +1 -0
  311. package/knowledge/rule-fixtures/A05.cors-wildcard/negative.js +1 -0
  312. package/knowledge/rule-fixtures/A05.cors-wildcard/positive.js +1 -0
  313. package/knowledge/rule-fixtures/A05.default-credentials/negative.js +1 -0
  314. package/knowledge/rule-fixtures/A05.default-credentials/positive.js +1 -0
  315. package/knowledge/rule-fixtures/A07.jwt-alg-none/negative.js +1 -0
  316. package/knowledge/rule-fixtures/A07.jwt-alg-none/positive.js +1 -0
  317. package/knowledge/rule-fixtures/A08.binary-formatter/negative.cs +1 -0
  318. package/knowledge/rule-fixtures/A08.binary-formatter/positive.cs +1 -0
  319. package/knowledge/rule-fixtures/A08.js-eval/negative.js +1 -0
  320. package/knowledge/rule-fixtures/A08.js-eval/positive.js +1 -0
  321. package/knowledge/rule-fixtures/A08.object-input-stream/negative.java +1 -0
  322. package/knowledge/rule-fixtures/A08.object-input-stream/positive.java +1 -0
  323. package/knowledge/schemas/disposition-register-v1.json +65 -0
  324. package/knowledge/schemas/recon-envelope-v1.json +198 -0
  325. package/knowledge/schemas/unified-finding-v1.json +72 -0
  326. package/knowledge/security-primitives-contract.md +301 -0
  327. package/knowledge/security-review-rule-map.yaml +107 -0
  328. package/knowledge/skills-registry.md +72 -0
  329. package/knowledge/task-size-classifier.md +103 -0
  330. package/knowledge/telemetry-schema.md +881 -0
  331. package/knowledge/test-automation-maturity.md +56 -0
  332. package/knowledge/test-automation-principles.md +71 -0
  333. package/knowledge/test-cadence-tradeoffs.md +68 -0
  334. package/knowledge/test-doubles.md +105 -0
  335. package/knowledge/test-file-indicators.md +22 -0
  336. package/knowledge/test-layer-gates.md +35 -0
  337. package/knowledge/test-matrix-examples/django-batch.md +24 -0
  338. package/knowledge/test-matrix-examples/dotnet-grpc-fronting-api.md +90 -0
  339. package/knowledge/test-matrix-examples/dotnet-http-consumer.md +131 -0
  340. package/knowledge/test-matrix-examples/react-node-spa.md +24 -0
  341. package/knowledge/test-matrix-examples/spring-boot-service.md +25 -0
  342. package/knowledge/test-matrix-examples/ssr-htmx.md +24 -0
  343. package/knowledge/test-organization.md +70 -0
  344. package/knowledge/test-pyramid.md +84 -0
  345. package/knowledge/test-refactoring.md +67 -0
  346. package/knowledge/test-review-division-of-labor.md +85 -0
  347. package/knowledge/test-smells.md +80 -0
  348. package/knowledge/test-stack-profiles/bdd-frameworks.md +235 -0
  349. package/knowledge/test-stack-profiles/django.md +13 -0
  350. package/knowledge/test-stack-profiles/dotnet.md +18 -0
  351. package/knowledge/test-stack-profiles/go.md +16 -0
  352. package/knowledge/test-stack-profiles/node.md +16 -0
  353. package/knowledge/test-stack-profiles/react.md +12 -0
  354. package/knowledge/test-stack-profiles/spring-boot.md +16 -0
  355. package/knowledge/test-stack-profiles/ssr-htmx.md +14 -0
  356. package/knowledge/test-stack-profiles/vue.md +12 -0
  357. package/knowledge/test-strategy.md +70 -0
  358. package/knowledge/testability-patterns.md +240 -0
  359. package/knowledge/testing-quadrants.md +44 -0
  360. package/knowledge/testing-techniques/approval.md +15 -0
  361. package/knowledge/testing-techniques/chaos.md +17 -0
  362. package/knowledge/testing-techniques/fuzz.md +15 -0
  363. package/knowledge/testing-techniques/property-based.md +15 -0
  364. package/knowledge/testing-techniques/schema-validation.md +15 -0
  365. package/knowledge/testing-techniques/screenshot.md +15 -0
  366. package/knowledge/three-phase-workflow.md +198 -0
  367. package/knowledge/value-patterns.md +55 -0
  368. package/knowledge/verification-mode.md +116 -0
  369. package/knowledge/virtual-service-libraries.md +75 -0
  370. package/knowledge/wave-consolidation-guidance.md +21 -0
  371. package/overrides/agents/Explore.md +15 -0
  372. package/overrides/agents/general-purpose.md +10 -0
  373. package/overrides/notes/autoship.md +6 -0
  374. package/overrides/notes/issues-from-assessment.md +3 -0
  375. package/overrides/notes/issues-from-plan.md +3 -0
  376. package/overrides/notes/mutation-night-watch.md +3 -0
  377. package/overrides/notes/mutation-testing.md +3 -0
  378. package/overrides/notes/pr.md +7 -0
  379. package/overrides/notes/project-init.md +6 -0
  380. package/overrides/notes/setup.md +13 -0
  381. package/overrides/notes/specs.md +3 -0
  382. package/overrides/skills/headless-run/SKILL.md +45 -0
  383. package/overrides/skills/upgrade/SKILL.md +30 -0
  384. package/overrides/skills/version/SKILL.md +25 -0
  385. package/package.json +36 -0
  386. package/scripts/authoring_digest.py +93 -0
  387. package/scripts/autoship_discover.py +121 -0
  388. package/scripts/autoship_group.py +409 -0
  389. package/scripts/autoship_proposals.py +494 -0
  390. package/scripts/autoship_queue.py +291 -0
  391. package/scripts/autoship_reclaim.py +495 -0
  392. package/scripts/build_jobs.py +108 -0
  393. package/scripts/build_rollback_point.py +240 -0
  394. package/scripts/build_slice_scope.py +157 -0
  395. package/scripts/build_wave.py +109 -0
  396. package/scripts/build_wave_reconcile.py +252 -0
  397. package/scripts/build_worktree_baseref.py +113 -0
  398. package/scripts/check_agent_scope.py +117 -0
  399. package/scripts/check_agent_tool_mapping.py +213 -0
  400. package/scripts/check_review_agent_mcp_tools.py +317 -0
  401. package/scripts/check_security_assessment_mcp_tools.py +165 -0
  402. package/scripts/checkpoint_abort.py +502 -0
  403. package/scripts/claude_setup_review.py +438 -0
  404. package/scripts/codebase_recon.py +556 -0
  405. package/scripts/coverage_config.py +623 -0
  406. package/scripts/coverage_delta_steering.py +330 -0
  407. package/scripts/coverage_discovery_dotnet.py +315 -0
  408. package/scripts/coverage_discovery_java.py +742 -0
  409. package/scripts/coverage_discovery_js.py +546 -0
  410. package/scripts/coverage_gap_ranking.py +556 -0
  411. package/scripts/coverage_readiness.py +455 -0
  412. package/scripts/coverage_report_parse.py +521 -0
  413. package/scripts/detect_bdd_convention.py +252 -0
  414. package/scripts/eval_ablation.py +376 -0
  415. package/scripts/gherkin_analysis_coverage_gate.py +306 -0
  416. package/scripts/gherkin_cross_feature_duplicate_titles_gate.py +173 -0
  417. package/scripts/gherkin_effectiveness_rollup.py +238 -0
  418. package/scripts/gherkin_failure_path_gate.py +206 -0
  419. package/scripts/gherkin_feature_merge.py +720 -0
  420. package/scripts/gherkin_stub_gate.py +163 -0
  421. package/scripts/gherkin_stub_merge.py +479 -0
  422. package/scripts/git_origin_host.py +88 -0
  423. package/scripts/install-java-static-analysis.py +110 -0
  424. package/scripts/issue_deps.py +74 -0
  425. package/scripts/lib/_bdd_markers.py +28 -0
  426. package/scripts/lib/_gherkin_text.py +93 -0
  427. package/scripts/lib/_vendored_tree.py +70 -0
  428. package/scripts/lib/autoship_state.py +397 -0
  429. package/scripts/lib/claude_md_guard.py +226 -0
  430. package/scripts/lib/deterministic_recon.py +446 -0
  431. package/scripts/lib/mcp_tool_grants.py +211 -0
  432. package/scripts/lib/plan_parse.py +386 -0
  433. package/scripts/lib/review_result.py +84 -0
  434. package/scripts/lib/review_roster.py +86 -0
  435. package/scripts/lib/session_log/__init__.py +34 -0
  436. package/scripts/lib/session_log/__pycache__/__init__.cpython-314.pyc +0 -0
  437. package/scripts/lib/session_log/__pycache__/records.cpython-314.pyc +0 -0
  438. package/scripts/lib/session_log/classify.py +231 -0
  439. package/scripts/lib/session_log/corrections.py +194 -0
  440. package/scripts/lib/session_log/discovery.py +108 -0
  441. package/scripts/lib/session_log/records.py +218 -0
  442. package/scripts/lib/session_log/redact.py +76 -0
  443. package/scripts/lib/session_log/signals.py +373 -0
  444. package/scripts/lib/session_report_downstream.py +614 -0
  445. package/scripts/lib/session_report_maintainer.py +1273 -0
  446. package/scripts/lib/session_report_shared.py +262 -0
  447. package/scripts/lib/settings_hook_guard.py +157 -0
  448. package/scripts/lib/slug.py +33 -0
  449. package/scripts/lib/stub_extractors/__init__.py +82 -0
  450. package/scripts/lib/stub_extractors/_common.py +328 -0
  451. package/scripts/lib/stub_extractors/csharp.py +19 -0
  452. package/scripts/lib/stub_extractors/go.py +173 -0
  453. package/scripts/lib/stub_extractors/java.py +18 -0
  454. package/scripts/lib/stub_extractors/jsts.py +126 -0
  455. package/scripts/mutation_stack_sections.py +149 -0
  456. package/scripts/mutation_yield_steering.py +345 -0
  457. package/scripts/orchestrator.py +895 -0
  458. package/scripts/plan_gherkin_export.py +227 -0
  459. package/scripts/plan_waves.py +208 -0
  460. package/scripts/pr_close_keyword_lint.py +108 -0
  461. package/scripts/progress_guardian.py +888 -0
  462. package/scripts/recon_inventory.py +273 -0
  463. package/scripts/review_findings_log.py +93 -0
  464. package/scripts/run_invariants.py +124 -0
  465. package/scripts/select_lenses.py +640 -0
  466. package/scripts/session_report.py +486 -0
  467. package/scripts/set_autocompact_env.py +221 -0
  468. package/scripts/ship_resume_guard.py +135 -0
  469. package/scripts/ship_review_gate.py +63 -0
  470. package/scripts/specs_convention_marker.py +103 -0
  471. package/scripts/test_improve_resume.py +277 -0
  472. package/scripts/test_review_mechanics.py +958 -0
  473. package/scripts/token_efficiency_review.py +322 -0
  474. package/scripts/verdict_scope.py +285 -0
  475. package/scripts/verify_gherkin_quality_critic_isolation.py +296 -0
  476. package/scripts/verify_tier.py +157 -0
  477. package/skills/adr-tools/SKILL.md +118 -0
  478. package/skills/agent-readiness/SKILL.md +105 -0
  479. package/skills/agent-readiness/ai_friendly_analyzers.py +326 -0
  480. package/skills/agent-readiness/scanner.py +441 -0
  481. package/skills/agent-readiness/scorecard.yaml +88 -0
  482. package/skills/api-design/SKILL.md +115 -0
  483. package/skills/apply-fixes/SKILL.md +171 -0
  484. package/skills/apply-test-doubles/SKILL.md +321 -0
  485. package/skills/artifact-lifecycle/SKILL.md +127 -0
  486. package/skills/autoship/SKILL.md +1124 -0
  487. package/skills/benchmark/SKILL.md +105 -0
  488. package/skills/branch-workflow/SKILL.md +89 -0
  489. package/skills/browse/SKILL.md +184 -0
  490. package/skills/browser-testing/SKILL.md +62 -0
  491. package/skills/browser-testing/references/playwright-patterns.md +216 -0
  492. package/skills/build/SKILL.md +422 -0
  493. package/skills/build/references/static-self-heal.md +245 -0
  494. package/skills/careful/SKILL.md +72 -0
  495. package/skills/cd-test-architecture/SKILL.md +371 -0
  496. package/skills/ci-debugging/SKILL.md +105 -0
  497. package/skills/co-evolution-audit/SKILL.md +269 -0
  498. package/skills/code-review/SKILL.md +1015 -0
  499. package/skills/code-review/examples/aggregated-sample.json +56 -0
  500. package/skills/code-review/examples/sample-report.md +41 -0
  501. package/skills/code-review/output-format.md +478 -0
  502. package/skills/code-review/scripts/activation.py +86 -0
  503. package/skills/code-review/scripts/change_impact.py +357 -0
  504. package/skills/code-review/scripts/change_shape.py +372 -0
  505. package/skills/code-review/scripts/change_size.py +212 -0
  506. package/skills/code-review/scripts/changed_file_list.py +141 -0
  507. package/skills/code-review/scripts/closing_pass.py +187 -0
  508. package/skills/code-review/scripts/consolidate.py +277 -0
  509. package/skills/code-review/scripts/contract_failure_report.py +185 -0
  510. package/skills/code-review/scripts/dispatch_reconcile.py +66 -0
  511. package/skills/code-review/scripts/dispatch_waves.py +164 -0
  512. package/skills/code-review/scripts/finding_signature.py +446 -0
  513. package/skills/code-review/scripts/ledger.py +283 -0
  514. package/skills/code-review/scripts/partition.py +169 -0
  515. package/skills/code-review/scripts/render_tiered_findings.py +274 -0
  516. package/skills/code-review/scripts/repo_invariants.py +1066 -0
  517. package/skills/code-review/scripts/review_context_pack.py +306 -0
  518. package/skills/code-review/scripts/review_round_log.py +345 -0
  519. package/skills/code-review/scripts/review_value_coverage.py +297 -0
  520. package/skills/code-review/scripts/validate_review_output.py +467 -0
  521. package/skills/code-review/sliced-mode.md +205 -0
  522. package/skills/competitive-analysis/SKILL.md +191 -0
  523. package/skills/context-loading-protocol/SKILL.md +157 -0
  524. package/skills/continue/SKILL.md +90 -0
  525. package/skills/cost-report/SKILL.md +178 -0
  526. package/skills/coverage-baseline/SKILL.md +335 -0
  527. package/skills/coverage-baseline/references/multi-project-discovery.md +202 -0
  528. package/skills/coverage-delta/SKILL.md +181 -0
  529. package/skills/coverage-delta/references/mutation-gate.md +70 -0
  530. package/skills/design-doc/SKILL.md +95 -0
  531. package/skills/design-interrogation/SKILL.md +89 -0
  532. package/skills/design-it-twice/SKILL.md +91 -0
  533. package/skills/docker-image-audit/SKILL.md +108 -0
  534. package/skills/docker-image-audit/references/install-guide.md +64 -0
  535. package/skills/docker-image-audit/references/report-template.md +73 -0
  536. package/skills/docker-image-create/SKILL.md +185 -0
  537. package/skills/domain-analysis/SKILL.md +183 -0
  538. package/skills/domain-driven-design/SKILL.md +194 -0
  539. package/skills/exploratory-testing/SKILL.md +108 -0
  540. package/skills/explore/SKILL.md +51 -0
  541. package/skills/farley-score/SKILL.md +165 -0
  542. package/skills/feature-file-validation/SKILL.md +78 -0
  543. package/skills/feature-file-validation/references/validation-rules.md +115 -0
  544. package/skills/feedback-learning/SKILL.md +414 -0
  545. package/skills/fix/SKILL.md +450 -0
  546. package/skills/freeze/SKILL.md +68 -0
  547. package/skills/frontend-architecture/SKILL.md +113 -0
  548. package/skills/gherkin-derive/SKILL.md +630 -0
  549. package/skills/gherkin-public/SKILL.md +266 -0
  550. package/skills/governance-compliance/SKILL.md +150 -0
  551. package/skills/guard/SKILL.md +75 -0
  552. package/skills/handoff/SKILL.md +139 -0
  553. package/skills/handoff/references/summary-templates.md +242 -0
  554. package/skills/harness-audit/SKILL.md +751 -0
  555. package/skills/harness-audit/scripts/lesson_validate.py +386 -0
  556. package/skills/harness-audit/scripts/redundancy_criterion.py +188 -0
  557. package/skills/headless-run/SKILL.md +45 -0
  558. package/skills/headless-run/scripts/isolated_dispatch.py +381 -0
  559. package/skills/help/SKILL.md +72 -0
  560. package/skills/hexagonal-architecture/SKILL.md +85 -0
  561. package/skills/human-oversight-protocol/SKILL.md +224 -0
  562. package/skills/issues-from-assessment/SKILL.md +223 -0
  563. package/skills/issues-from-plan/SKILL.md +133 -0
  564. package/skills/legacy-code/SKILL.md +132 -0
  565. package/skills/mermaid-diagramming/SKILL.md +120 -0
  566. package/skills/mutation-night-watch/SKILL.md +154 -0
  567. package/skills/mutation-night-watch/references/scheduling.md +135 -0
  568. package/skills/mutation-testing/SKILL.md +396 -0
  569. package/skills/mutation-testing/references/languages/csharp-stryker-net.md +676 -0
  570. package/skills/mutation-testing/references/languages/go-go-mutesting.md +95 -0
  571. package/skills/mutation-testing/references/languages/java-pitest.md +77 -0
  572. package/skills/mutation-testing/references/languages/javascript-stryker.md +188 -0
  573. package/skills/mutation-testing/references/languages/python-mutmut.md +97 -0
  574. package/skills/mutation-testing/references/time-estimation.md +34 -0
  575. package/skills/mutation-testing/references/tool-detection.md +15 -0
  576. package/skills/mutation-testing/references/workflow-callers.md +23 -0
  577. package/skills/mutation-testing/scripts/__pycache__/xunit_v3_feature_detector.cpython-314.pyc +0 -0
  578. package/skills/mutation-testing/scripts/csharp_stryker_net_slice_runner.py +635 -0
  579. package/skills/mutation-testing/scripts/csharp_stryker_net_status_loop.py +525 -0
  580. package/skills/mutation-testing/scripts/csharp_stryker_net_wrapper.py +681 -0
  581. package/skills/mutation-testing/scripts/mutation_baseline_reuse.py +292 -0
  582. package/skills/mutation-testing/scripts/mutation_exclude_policy.py +268 -0
  583. package/skills/mutation-testing/scripts/mutation_feasibility_gate.py +463 -0
  584. package/skills/mutation-testing/scripts/mutation_kill_headless.py +331 -0
  585. package/skills/mutation-testing/scripts/mutation_kill_insert.py +199 -0
  586. package/skills/mutation-testing/scripts/mutation_kill_insert_python.py +150 -0
  587. package/skills/mutation-testing/scripts/mutation_kill_loop.py +869 -0
  588. package/skills/mutation-testing/scripts/mutation_kill_loop_python.py +949 -0
  589. package/skills/mutation-testing/scripts/mutation_kill_retry.py +592 -0
  590. package/skills/mutation-testing/scripts/mutation_kill_shared.py +620 -0
  591. package/skills/mutation-testing/scripts/mutation_nightwatch.py +462 -0
  592. package/skills/mutation-testing/scripts/mutation_nightwatch_stacks.py +425 -0
  593. package/skills/mutation-testing/scripts/mutation_report.py +743 -0
  594. package/skills/mutation-testing/scripts/mutation_report_cli.py +175 -0
  595. package/skills/mutation-testing/scripts/mutation_safety_gate.py +69 -0
  596. package/skills/mutation-testing/scripts/stryker_shard_pipeline.py +847 -0
  597. package/skills/mutation-testing/scripts/stryker_shard_setup.py +440 -0
  598. package/skills/mutation-testing/scripts/stryker_timeout_retry.py +142 -0
  599. package/skills/mutation-testing/scripts/xunit_v3_feature_detector.py +341 -0
  600. package/skills/performance-benchmark/SKILL.md +174 -0
  601. package/skills/performance-benchmark/examples/report-format.md +43 -0
  602. package/skills/performance-benchmark/references/benchmark-script.md +169 -0
  603. package/skills/performance-metrics/SKILL.md +265 -0
  604. package/skills/plan/SKILL.md +199 -0
  605. package/skills/plan/references/gherkin-persistence.md +43 -0
  606. package/skills/plan/references/plan-template.md +182 -0
  607. package/skills/pr/SKILL.md +289 -0
  608. package/skills/pr/scripts/gate_retry_state.py +368 -0
  609. package/skills/project-init/README.md +141 -0
  610. package/skills/project-init/SKILL.md +1197 -0
  611. package/skills/project-init/evals/evals.json +200 -0
  612. package/skills/project-init/references/capability-tools.md +55 -0
  613. package/skills/project-init/references/configs.md +221 -0
  614. package/skills/property-based-testing/SKILL.md +121 -0
  615. package/skills/property-based-testing/fixtures/invariant_fixture.py +15 -0
  616. package/skills/property-based-testing/fixtures/js-roundtrip/README.md +42 -0
  617. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/LICENSE +21 -0
  618. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/README.md +263 -0
  619. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/cjs/fast-check.d.ts +5165 -0
  620. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/cjs/fast-check.js +12147 -0
  621. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/cjs/package.json +3 -0
  622. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/cjs/types57/fast-check.d.ts +5165 -0
  623. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/fast-check.d.ts +5165 -0
  624. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/fast-check.js +12011 -0
  625. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/rolldown-runtime-D7D4PA-g.js +13 -0
  626. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/lib/types57/fast-check.d.ts +5165 -0
  627. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/fast-check/package.json +94 -0
  628. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/LICENSE +21 -0
  629. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/README.md +168 -0
  630. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/RandomGenerator-DcXj09Ch.d.ts +14 -0
  631. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformBigInt.d.ts +15 -0
  632. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformBigInt.js +38 -0
  633. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformFloat32.d.ts +15 -0
  634. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformFloat32.js +18 -0
  635. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformFloat64.d.ts +15 -0
  636. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformFloat64.js +22 -0
  637. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformInt.d.ts +15 -0
  638. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/distribution/uniformInt.js +134 -0
  639. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/RandomGenerator-DcXj09Ch.d.ts +14 -0
  640. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformBigInt.d.ts +15 -0
  641. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformBigInt.js +37 -0
  642. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformFloat32.d.ts +15 -0
  643. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformFloat32.js +17 -0
  644. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformFloat64.d.ts +15 -0
  645. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformFloat64.js +21 -0
  646. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformInt.d.ts +15 -0
  647. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/distribution/uniformInt.js +133 -0
  648. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/congruential32.d.ts +7 -0
  649. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/congruential32.js +44 -0
  650. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/mersenne.d.ts +7 -0
  651. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/mersenne.js +90 -0
  652. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/xoroshiro128plus.d.ts +7 -0
  653. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/xoroshiro128plus.js +80 -0
  654. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/xorshift128plus.d.ts +7 -0
  655. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/generator/xorshift128plus.js +78 -0
  656. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/package.json +3 -0
  657. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/types/JumpableRandomGenerator.d.ts +16 -0
  658. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/types/JumpableRandomGenerator.js +0 -0
  659. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/types/RandomGenerator.d.ts +2 -0
  660. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/types/RandomGenerator.js +0 -0
  661. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/generateN.d.ts +6 -0
  662. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/generateN.js +8 -0
  663. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/purify.d.ts +12 -0
  664. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/purify.js +9 -0
  665. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/skipN.d.ts +6 -0
  666. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/esm/utils/skipN.js +6 -0
  667. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/congruential32.d.ts +7 -0
  668. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/congruential32.js +46 -0
  669. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/mersenne.d.ts +7 -0
  670. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/mersenne.js +92 -0
  671. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/xoroshiro128plus.d.ts +7 -0
  672. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/xoroshiro128plus.js +82 -0
  673. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/xorshift128plus.d.ts +7 -0
  674. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/generator/xorshift128plus.js +80 -0
  675. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/types/JumpableRandomGenerator.d.ts +16 -0
  676. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/types/JumpableRandomGenerator.js +0 -0
  677. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/types/RandomGenerator.d.ts +2 -0
  678. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/types/RandomGenerator.js +0 -0
  679. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/generateN.d.ts +6 -0
  680. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/generateN.js +9 -0
  681. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/purify.d.ts +12 -0
  682. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/purify.js +10 -0
  683. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/skipN.d.ts +6 -0
  684. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/lib/utils/skipN.js +7 -0
  685. package/skills/property-based-testing/fixtures/js-roundtrip/node_modules/pure-rand/package.json +133 -0
  686. package/skills/property-based-testing/fixtures/js-roundtrip/package-lock.json +1179 -0
  687. package/skills/property-based-testing/fixtures/js-roundtrip/package.json +14 -0
  688. package/skills/property-based-testing/fixtures/js-roundtrip/roundtrip.js +29 -0
  689. package/skills/property-based-testing/fixtures/js-roundtrip/roundtrip.properties.test.js +16 -0
  690. package/skills/property-based-testing/fixtures/no_property_fixture.py +10 -0
  691. package/skills/property-based-testing/fixtures/roundtrip_fixture.py +16 -0
  692. package/skills/property-based-testing/references/languages/javascript.md +54 -0
  693. package/skills/property-based-testing/scripts/detect_and_dispatch.py +80 -0
  694. package/skills/property-based-testing/scripts/hypothesis_scaffold.py +276 -0
  695. package/skills/proxy-resilience/SKILL.md +84 -0
  696. package/skills/quality-gate-pipeline/SKILL.md +184 -0
  697. package/skills/quality-targets-converge/SKILL.md +254 -0
  698. package/skills/repo-review/SKILL.md +159 -0
  699. package/skills/report-pdf/SKILL.md +66 -0
  700. package/skills/review/SKILL.md +47 -0
  701. package/skills/review-agent/SKILL.md +152 -0
  702. package/skills/review-summary/SKILL.md +73 -0
  703. package/skills/run-report/SKILL.md +70 -0
  704. package/skills/semantic-duplication-scan/SKILL.md +337 -0
  705. package/skills/semantic-scan/SKILL.md +53 -0
  706. package/skills/semgrep-analyze/SKILL.md +139 -0
  707. package/skills/setup/SKILL.md +1122 -0
  708. package/skills/ship/SKILL.md +240 -0
  709. package/skills/source-verification/SKILL.md +210 -0
  710. package/skills/source-verification/scripts/claim_extractor.py +155 -0
  711. package/skills/specs/.size-baseline.json +4 -0
  712. package/skills/specs/SKILL.md +243 -0
  713. package/skills/specs/references/completeness-checklist.md +83 -0
  714. package/skills/specs/references/extraction.md +58 -0
  715. package/skills/specs/references/glossary.md +59 -0
  716. package/skills/specs/references/persistence.md +115 -0
  717. package/skills/specs/references/predictability-check.md +77 -0
  718. package/skills/static-analysis-integration/SKILL.md +235 -0
  719. package/skills/static-analysis-integration/adapters/_envelope.py +26 -0
  720. package/skills/static-analysis-integration/adapters/jscpd-adapter.py +66 -0
  721. package/skills/static-analysis-integration/adapters/lizard-adapter.py +81 -0
  722. package/skills/static-analysis-integration/adapters/mypy-adapter.py +50 -0
  723. package/skills/static-analysis-integration/adapters/mypy-src-layout.py +93 -0
  724. package/skills/static-analysis-integration/adapters/security-review-adapter.py +212 -0
  725. package/skills/static-analysis-integration/maintenance.md +23 -0
  726. package/skills/static-analysis-integration/references/language-setup.md +228 -0
  727. package/skills/static-analysis-integration/references/sarif-parser.md +124 -0
  728. package/skills/static-analysis-integration/references/security-review-adapter.md +118 -0
  729. package/skills/static-analysis-integration/references/tool-configs.md +617 -0
  730. package/skills/static-analysis-integration/rulesets/pmd-quickstart.xml +24 -0
  731. package/skills/stryker-xunit-v2-shim/SKILL.md +274 -0
  732. package/skills/stryker-xunit-v2-shim/references/shim-howto.md +256 -0
  733. package/skills/stryker-xunit-v2-shim/scripts/generate_shim.py +143 -0
  734. package/skills/systematic-debugging/SKILL.md +130 -0
  735. package/skills/telemetry/SKILL.md +75 -0
  736. package/skills/test-audit-disable/SKILL.md +129 -0
  737. package/skills/test-design/SKILL.md +177 -0
  738. package/skills/test-design/scripts/__pycache__/internal_double_detector.cpython-314.pyc +0 -0
  739. package/skills/test-design/scripts/internal_double_detector.py +631 -0
  740. package/skills/test-design-advisor/SKILL.md +166 -0
  741. package/skills/test-driven-development/SKILL.md +169 -0
  742. package/skills/test-health/SKILL.md +262 -0
  743. package/skills/test-improve/SKILL.md +239 -0
  744. package/skills/test-improve/references/phase-0-approach-contract.md +228 -0
  745. package/skills/test-improve/references/phase-1-analyze.md +131 -0
  746. package/skills/test-improve/references/phase-2-baseline.md +121 -0
  747. package/skills/test-improve/references/phase-3-derive-gherkin.md +53 -0
  748. package/skills/test-improve/references/phase-4-plan-fixes.md +34 -0
  749. package/skills/test-improve/references/phase-5-improve.md +215 -0
  750. package/skills/test-improve/references/phase-6-refactor-decision.md +45 -0
  751. package/skills/test-improve/references/phase-7-refactor.md +44 -0
  752. package/skills/test-improve/references/phase-8-validate.md +66 -0
  753. package/skills/test-improve/references/phase-9-close-out-prompt.md +11 -0
  754. package/skills/test-improve/references/phase-9-report.md +62 -0
  755. package/skills/test-improve/references/review-loop.md +92 -0
  756. package/skills/test-improve/templates/executive-summary.md +123 -0
  757. package/skills/threat-modeling/SKILL.md +108 -0
  758. package/skills/triage/SKILL.md +211 -0
  759. package/skills/ubiquitous-language/SKILL.md +192 -0
  760. package/skills/ubiquitous-language/scripts/collect_domain_signals.py +300 -0
  761. package/skills/unfreeze/SKILL.md +37 -0
  762. package/skills/upgrade/SKILL.md +31 -0
  763. package/skills/upgrade/scripts/check_version_drift.py +113 -0
  764. package/skills/upgrade/scripts/enable_autoupdate.py +149 -0
  765. package/skills/version/SKILL.md +25 -0
  766. package/sync/__pycache__/sync_upstream.cpython-314.pyc +0 -0
  767. package/sync/sync_upstream.py +293 -0
  768. package/templates/ACCEPTED-RISKS.md.tmpl +46 -0
  769. package/templates/agents/agent-template.md +151 -0
  770. package/templates/agents/angular-testing.md +66 -0
  771. package/templates/agents/csharp-quality.md +63 -0
  772. package/templates/agents/esm-enforcer.md +52 -0
  773. package/templates/agents/front-end-testing.md +65 -0
  774. package/templates/agents/go-quality.md +65 -0
  775. package/templates/agents/python-quality.md +62 -0
  776. package/templates/agents/react-testing.md +61 -0
  777. package/templates/agents/ts-enforcer.md +60 -0
  778. package/templates/agents/twelve-factor-audit.md +49 -0
  779. package/tools/entropy-check.py +250 -0
  780. package/tools/model-hash-verify.py +213 -0
@@ -0,0 +1,869 @@
1
+ #!/usr/bin/env python3
2
+ """mutation_kill_loop.py — config-driven, deterministic survivor-kill loop.
3
+
4
+ Migrated from the ACI ``nextgen-test-upgrade-process`` mutation agent and
5
+ de-hardcoded: nothing repo-specific lives here. Project / test-project /
6
+ mutate targets come from ``stryker-config.json``; ``DOTNET_ROOT`` resolution
7
+ and ``.sln`` hide/restore reuse the shipped ``csharp_stryker_net_wrapper``;
8
+ scoring and survivor extraction reuse ``mutation_report``.
9
+
10
+ Generic, stdlib-only, cross-platform (macOS, Linux, Windows). The only
11
+ non-stdlib imports are the sibling plugin modules in this same ``scripts/``
12
+ directory.
13
+
14
+ **Scope (#1562).** This module owns config parsing, the scoped Stryker run,
15
+ verify/commit/revert, and ``run_for_file`` orchestration. Insertion mechanics
16
+ (detect-or-refuse C# source manipulation) live in ``mutation_kill_insert.py``,
17
+ a stdlib-only leaf this module imports from — never the reverse — mirroring
18
+ the wrapper/report split already established by
19
+ ``csharp_stryker_net_wrapper.py`` / ``mutation_report.py``. Headless
20
+ generation and the ``--headless`` CLI live in ``mutation_kill_headless.py``,
21
+ which is a different shape: a thin CLI layer built atop this module's public
22
+ API, so it imports back from here at module scope. This module only reaches
23
+ into it lazily, inside ``if __name__ == "__main__":``, specifically to avoid
24
+ the circular import that a module-scope import in both directions would
25
+ create.
26
+
27
+ **Shared mechanics (#1583).** ``_timeout_from_env``, ``git_revert``,
28
+ ``git_reset_and_revert``, ``git_commit``, and the "no improvement across
29
+ rounds" stop predicate are imported from ``mutation_kill_shared.py`` rather
30
+ than defined here — they were byte-for-byte duplicated with
31
+ ``mutation_kill_loop_python.py`` (the Python/mutmut sibling) before that
32
+ module existed. See its module docstring for the full rationale.
33
+
34
+ **Generation is a seam, not a mechanism.** The loop never decides *what*
35
+ tests to write — a caller supplies a ``generate`` callable that returns the
36
+ new test-method text. The default (interactive) path is agent-driven: the
37
+ ``mutation-kill`` agent calls :func:`run_for_file` directly, passing a
38
+ ``generate`` hook backed by a live agent turn. A ``--headless`` CLI mode
39
+ (``mutation_kill_headless.py``) shells to ``claude --print`` for unattended
40
+ (CI / shard-pipeline) runs. Invoking the CLI with neither an injected
41
+ generator nor ``--headless`` fails fast at startup — before any Stryker run
42
+ or file mutation.
43
+ """
44
+
45
+ from __future__ import annotations
46
+
47
+ import json
48
+ import os
49
+ import re
50
+ import subprocess
51
+ import sys
52
+ import tempfile
53
+ from collections.abc import Callable, Sequence
54
+ from dataclasses import dataclass
55
+ from pathlib import Path
56
+
57
+ # The `Generator` alias below is a real runtime expression
58
+ # (mutation_kill_headless imports the name), so `from __future__ import
59
+ # annotations` cannot defer it — it must be subscriptable at import time.
60
+ # collections.abc generics have been since 3.9, which the 3.10 floor
61
+ # (ADR 0031) clears.
62
+ from typing import NamedTuple
63
+
64
+ import csharp_stryker_net_wrapper as wrapper
65
+ import mutation_kill_shared
66
+ import mutation_report
67
+ import mutation_safety_gate
68
+ from mutation_kill_insert import apply_generated_methods, count_methods
69
+ from mutation_kill_shared import (
70
+ GIT_TIMEOUT_S, # noqa: F401 — re-exported for tests (loop.GIT_TIMEOUT_S)
71
+ _timeout_from_env,
72
+ git_commit,
73
+ git_reset_and_revert,
74
+ git_revert,
75
+ stop_reason,
76
+ )
77
+
78
+ # A generator is any callable the agent (or the headless CLI) supplies: given
79
+ # the source filename, the surviving mutants, the source text, and the
80
+ # current test text, it returns the raw text of new test methods to insert.
81
+ # The loop owns everything *around* this call; the callable owns the single
82
+ # genuinely-LLM step.
83
+ Generator = Callable[[str, list[dict], str, str], str]
84
+
85
+
86
+ class _Timeout(NamedTuple):
87
+ """A timeout value paired with the env var that overrides it — kept
88
+ together so the two can't drift out of sync at a ``_run_with_timeout``
89
+ call site (#1593 review)."""
90
+
91
+ seconds: float
92
+ env_var_name: str
93
+
94
+
95
+ def _make_timeout(env_var_name: str, default: int) -> _Timeout:
96
+ """Parse+pair in one step, so the env-var name is written once, not
97
+ once for ``_timeout_from_env`` and again for the ``_Timeout`` it feeds."""
98
+ return _Timeout(_timeout_from_env(env_var_name, default), env_var_name)
99
+
100
+
101
+ # Timeouts for the long-running subprocesses this loop shells out to. Each is
102
+ # overridable via env var since a legitimately slow project (a large solution,
103
+ # a heavyweight test suite) may need a larger budget than these defaults —
104
+ # unlike the 30s used by this codebase's git-shelling helpers, these
105
+ # subprocesses (a Stryker run, a dotnet build/test) routinely run for minutes.
106
+ _STRYKER_TIMEOUT = _make_timeout("DEV_TEAM_MUTATION_STRYKER_TIMEOUT_S", 3600)
107
+ _BUILD_TIMEOUT = _make_timeout("DEV_TEAM_MUTATION_BUILD_TIMEOUT_S", 600)
108
+ _TEST_TIMEOUT = _make_timeout("DEV_TEAM_MUTATION_TEST_TIMEOUT_S", 600)
109
+ # GIT_TIMEOUT_S itself comes from mutation_kill_shared (imported above) —
110
+ # shared verbatim with mutation_kill_loop_python.py (#1583). No local
111
+ # _GIT_TIMEOUT _Timeout pairing exists here anymore: git_revert/git_commit/
112
+ # git_reset_and_revert are themselves imported from that module, so they
113
+ # already close over its own GIT_TIMEOUT_S rather than taking one as a param.
114
+
115
+ STRYKER_RUN_TIMEOUT_S = _STRYKER_TIMEOUT.seconds
116
+ DOTNET_BUILD_TIMEOUT_S = _BUILD_TIMEOUT.seconds
117
+ DOTNET_TEST_TIMEOUT_S = _TEST_TIMEOUT.seconds
118
+
119
+
120
+ def _run_with_timeout(
121
+ argv: list[str],
122
+ timeout_spec: _Timeout,
123
+ *,
124
+ operation_label: str,
125
+ cwd: Path | None = None,
126
+ capture_output: bool = False,
127
+ text: bool = False,
128
+ env: dict[str, str] | None = None,
129
+ ) -> subprocess.CompletedProcess | None:
130
+ """Run ``argv`` with a bounded timeout. On ``TimeoutExpired``, logs to
131
+ stderr and returns ``None`` instead of raising, so ``dotnet_build`` and
132
+ ``dotnet_test`` share one timeout/log shape instead of two copies (#1593).
133
+ ``git_revert``/``git_commit``/``git_reset_and_revert`` moved to
134
+ ``mutation_kill_shared.py`` (#1583) with their own, behaviorally-identical
135
+ inline timeout handling — they no longer call this helper. No
136
+ ``shell``/``**kwargs`` passthrough: every ``argv`` runs as a list, never
137
+ through a shell.
138
+ ``env`` defaults to ``None`` (inherit the ambient process environment,
139
+ today's behavior) — tests pass a scrubbed env to keep real-git regression
140
+ tests hermetic (#1598/#1584 review, item 4 follow-up)."""
141
+ try:
142
+ return subprocess.run(
143
+ argv,
144
+ cwd=cwd,
145
+ timeout=timeout_spec.seconds,
146
+ capture_output=capture_output,
147
+ text=text,
148
+ check=False,
149
+ env=env,
150
+ )
151
+ except subprocess.TimeoutExpired:
152
+ sys.stderr.write(
153
+ f"error: {operation_label} timed out after {timeout_spec.seconds}s "
154
+ f"(set {timeout_spec.env_var_name} to raise it)\n"
155
+ )
156
+ return None
157
+
158
+
159
+ # =============================================================================
160
+ # Config — everything path-shaped comes from stryker-config.json.
161
+ # =============================================================================
162
+ @dataclass(frozen=True)
163
+ class LoopConfig:
164
+ """The subset of a ``stryker-config.json`` the loop needs.
165
+
166
+ ``project`` is the mutated project (optional — Stryker infers it from the
167
+ solution when omitted). ``test_projects`` drives every ``dotnet build`` and
168
+ ``dotnet test`` target — never a hardcoded path. ``solution`` is the main
169
+ ``.sln`` hidden during a scoped run.
170
+ """
171
+
172
+ project: str | None
173
+ test_projects: list[str]
174
+ mutate: list[str]
175
+ solution: str | None
176
+
177
+
178
+ def load_loop_config(config_path: Path) -> LoopConfig:
179
+ """Parse ``stryker-config.json``, tolerating the wrapper shape.
180
+
181
+ Stryker configs may nest everything under a top-level ``"stryker-config"``
182
+ key (the shape the ACI configs use) or place the keys at the top level.
183
+ Both are accepted.
184
+ """
185
+ raw = json.loads(Path(config_path).read_text(encoding="utf-8"))
186
+ inner = raw.get("stryker-config", raw)
187
+ return LoopConfig(
188
+ project=inner.get("project"),
189
+ test_projects=list(inner.get("test-projects", [])),
190
+ mutate=list(inner.get("mutate", [])),
191
+ solution=inner.get("solution"),
192
+ )
193
+
194
+
195
+ def dotnet_test_project_targets(config: LoopConfig) -> list[str]:
196
+ """Both the build and test targets are exactly the configured test-projects."""
197
+ return list(config.test_projects)
198
+
199
+
200
+ # =============================================================================
201
+ # Scoped Stryker run — goes THROUGH the wrapper (AC4).
202
+ # =============================================================================
203
+ def make_scoped_config(config: LoopConfig, source_file: str) -> dict:
204
+ """Build a Stryker config scoped to a single source file.
205
+
206
+ Carries the configured solution / project / test-projects forward and
207
+ narrows ``mutate`` to just the target file with per-test coverage for fast
208
+ feedback. Keys whose configured value is ``None`` are dropped so Stryker
209
+ infers them.
210
+ """
211
+ scoped = {
212
+ "solution": config.solution,
213
+ "project": config.project,
214
+ "test-projects": config.test_projects,
215
+ "mutate": [f"**/{source_file}"],
216
+ "coverage-analysis": "perTest",
217
+ "reporters": ["json"],
218
+ }
219
+ scoped = {k: v for k, v in scoped.items() if v is not None}
220
+ return {"stryker-config": scoped}
221
+
222
+
223
+ def make_confirm_scoped_config(
224
+ config: LoopConfig,
225
+ source_file: str,
226
+ lines: Sequence[int],
227
+ *,
228
+ total_file_lines: int | None = None,
229
+ degenerate_ratio: float = 0.5,
230
+ ) -> dict:
231
+ """Build a Stryker config scoped to specific SOURCE LINES of one file.
232
+
233
+ #2031: every file that converges pays one full scoped mutation run to
234
+ learn it has converged, and that run generates nothing. The terminal
235
+ round only needs to re-test the handful of mutants that survived the
236
+ previous round, whose lines ``mutation_report.survivor_lines`` already
237
+ computes.
238
+
239
+ **Soundness invariant.** The mutation-kill loop never edits production
240
+ source — every round only ever *adds* test methods to the test file
241
+ (``apply_generated_methods`` / ``run_for_file``; ``mutation_kill_insert``
242
+ refuses anything that looks like a source-file edit). So the set of
243
+ mutation targets in ``source_file`` is fixed for the file's entire loop
244
+ lifetime: no mutant can appear at a line outside the previous round's
245
+ survivor set, because nothing the loop does between rounds can create
246
+ one. That is what makes re-testing only the previous round's survivor
247
+ lines a strictly narrower question with the same answer a full re-scan
248
+ would give for those specific mutants, rather than merely a convenient
249
+ shortcut.
250
+
251
+ **Verified (#2031) — and the verification says this span form is
252
+ WRONG.** Checked against Stryker.NET's own current documentation
253
+ (stryker-mutator.io/docs/stryker-net/configuration/, § ``mutate``,
254
+ fetched live during this work): the ``{start..end}`` span suffix on
255
+ ``mutate`` (``dotnet stryker -m "File.cs{10..100}"``) is real and
256
+ supported. But "a span is defined by the indices of the **first
257
+ character** and the **last character**" — i.e. CHARACTER offsets into
258
+ the file, not line numbers. The span form emitted below
259
+ (``"**/File.cs{10..10}"`` for a survivor on line 10) feeds a *line*
260
+ number into a slot Stryker parses as a *character* offset — it points at
261
+ an unrelated, essentially arbitrary character range near the start of
262
+ the file, not the intended line. Wiring this in as-is risks the exact
263
+ "confirm-run that silently mutates more than it claims" failure this
264
+ issue's Risk section warns against: at best the wrong span matches zero
265
+ mutants (caught by ``_score_round``'s #1606 zero-mutants guard — see
266
+ ``test_narrowed_confirm_config_resolving_to_nothing_still_trips_the_zero_mutants_guard``
267
+ in ``tests/scripts/test_mutation_kill_loop_orchestration.py``); at worst
268
+ it silently overlaps a *different* mutant than the one being confirmed.
269
+
270
+ Translating a survivor line number into the character-offset span
271
+ Stryker.NET actually expects is mechanically computable (read the file,
272
+ sum preceding line lengths) — but the exact indexing convention (0- vs
273
+ 1-based, inclusive/exclusive end, how CRLF line endings count toward the
274
+ offset) is not pinned by the documentation alone, and confirming it needs
275
+ a live ``dotnet``/``dotnet-stryker`` run against a probe file with a
276
+ known survivor line. No such toolchain is available in every environment
277
+ that develops this plugin (checked: ``dotnet`` is not always on ``PATH``).
278
+ Per this issue's own explicit fallback: **this function stays unwired**
279
+ from ``_score_round`` — see
280
+ ``test_confirm_scoped_config_is_not_wired_into_the_default_loop_path`` —
281
+ until a pass with a real Stryker.NET verifies the character-offset
282
+ conversion end-to-end. See ``references/languages/csharp-stryker-net.md``
283
+ § "`--mutate` line-span narrowing" for the full recorded finding and what
284
+ that verification pass needs to do.
285
+
286
+ **Degenerate-narrowing guard (#2031 AC4).** When ``total_file_lines`` is
287
+ supplied, narrowing that would touch ``degenerate_ratio`` or more of the
288
+ file's own lines (default 50%) degrades to :func:`make_scoped_config`
289
+ instead of emitting one span per line — re-testing most of a file one
290
+ line at a time is no cheaper than re-scoping the whole file, and produces
291
+ a far larger ``mutate`` array for no savings. ``total_file_lines=None``
292
+ (the default) skips the comparison, matching today's unconditional
293
+ per-line-span behavior for a caller that doesn't have the file's line
294
+ count handy.
295
+
296
+ Empty ``lines`` returns the ordinary whole-file scoped config, so a caller
297
+ that finds no clustered survivors degrades to today's behavior rather than
298
+ emitting a config that matches nothing.
299
+ """
300
+ if not lines:
301
+ return make_scoped_config(config, source_file)
302
+ unique_lines = sorted(set(lines))
303
+ if (
304
+ total_file_lines is not None
305
+ and total_file_lines > 0
306
+ and len(unique_lines) / total_file_lines >= degenerate_ratio
307
+ ):
308
+ return make_scoped_config(config, source_file)
309
+ spans = [f"**/{source_file}{{{line}..{line}}}" for line in unique_lines]
310
+ scoped = {
311
+ "solution": config.solution,
312
+ "project": config.project,
313
+ "test-projects": config.test_projects,
314
+ "mutate": spans,
315
+ "coverage-analysis": "perTest",
316
+ "reporters": ["json"],
317
+ }
318
+ scoped = {k: v for k, v in scoped.items() if v is not None}
319
+ return {"stryker-config": scoped}
320
+
321
+
322
+ def _write_scoped_config(config: LoopConfig, source_file: str) -> Path:
323
+ with tempfile.NamedTemporaryFile(
324
+ suffix=".json", delete=False, mode="w", prefix="stryker-scoped-"
325
+ ) as tmp:
326
+ json.dump(make_scoped_config(config, source_file), tmp)
327
+ path = Path(tmp.name)
328
+ return path
329
+
330
+
331
+ def _validate_solution_path(solution: str, *, cwd: Path | None = None) -> Path:
332
+ """Reject a ``stryker-config.json`` ``solution`` value that would escape
333
+ the working tree once used to derive ``sln_hidden``, and return the
334
+ resolved, validated absolute path.
335
+
336
+ ``solution`` is read verbatim from ``stryker-config.json`` and, before
337
+ this check existed, was concatenated straight into a filename
338
+ (``f"{config.solution}.stryker-hidden"``) with no validation — a
339
+ traversal-shaped value (e.g. ``"../../etc/cron.d/evil"``) would let a
340
+ hostile config target a file outside the repo if the config is ever read
341
+ from an untrusted checkout (#1598). Resolves ``solution`` against
342
+ ``cwd`` (or the current working directory) and rejects it unless the
343
+ resolved path stays under that root.
344
+
345
+ Callers must build ``sln``/``sln_hidden`` from THIS function's return
346
+ value, not by independently re-deriving ``Path(config.solution)`` — the
347
+ latter resolves against the *process* working directory when
348
+ ``wrapper.hide_sln``/``restore_sln``'s bare ``Path.rename()`` runs, not
349
+ against ``cwd`` (which only ever reaches ``subprocess.run``), so the
350
+ validated path and the actually-renamed path could silently diverge
351
+ whenever a caller passes a ``cwd`` different from the real process cwd
352
+ (caught in #1598/#1584 review).
353
+
354
+ **Requires a relative ``solution``; an absolute value is rejected in
355
+ every practical case (#1598/#1584 review, item 11).** ``pathlib``'s ``/``
356
+ operator silently discards ``base`` outright when the right-hand operand
357
+ is itself absolute (``base / "/etc/passwd" == Path("/etc/passwd")``), so
358
+ ``candidate`` becomes that absolute value verbatim — not "``base`` plus
359
+ the absolute path" as the name might suggest. The ``relative_to(base)``
360
+ check below then rejects it as escaping, UNLESS the absolute value
361
+ happens to already lie under ``base`` (a degenerate case with no
362
+ practical use, since a relative value achieves the same result without
363
+ depending on ``base``'s exact prefix). This narrows what a bare
364
+ ``Path(config.solution)`` used to accept before this validation existed
365
+ — an absolute ``solution`` is not a supported input; pass a
366
+ repo-relative path.
367
+ """
368
+ base = (cwd or Path.cwd()).resolve()
369
+ candidate = (base / solution).resolve()
370
+ try:
371
+ candidate.relative_to(base)
372
+ except ValueError:
373
+ raise ValueError(
374
+ f"stryker-config.json 'solution' escapes the working tree: {solution!r}"
375
+ ) from None
376
+ return candidate
377
+
378
+
379
+ def run_scoped_stryker(
380
+ config: LoopConfig,
381
+ source_file: str,
382
+ *,
383
+ output_dir: Path,
384
+ stryker_bin: str = "dotnet",
385
+ cwd: Path | None = None,
386
+ ) -> Path:
387
+ """Run Stryker scoped to one file; return the report path.
388
+
389
+ DOTNET_ROOT resolution and ``.sln`` hide/restore are delegated to
390
+ ``csharp_stryker_net_wrapper`` — this function never re-implements either
391
+ (AC4). The ``.sln`` is hidden for the duration of the run and restored on
392
+ every exit path via ``finally``.
393
+ """
394
+ dotnet_root, err = wrapper.resolve_dotnet_root(
395
+ preset=os.environ.get("DOTNET_ROOT"),
396
+ candidates=wrapper.default_probe_candidates(),
397
+ )
398
+ if err is not None:
399
+ raise RuntimeError(err)
400
+ assert dotnet_root is not None
401
+ env = {**os.environ, "DOTNET_ROOT": dotnet_root}
402
+
403
+ sln: Path | None = None
404
+ if config.solution:
405
+ # Use the validated, resolved path for BOTH the check and the actual
406
+ # rename below — never re-derive a second, independently-resolved
407
+ # Path(config.solution) (#1598/#1584 review: the two could diverge).
408
+ sln = _validate_solution_path(config.solution, cwd=cwd)
409
+
410
+ config_path = _write_scoped_config(config, source_file)
411
+ # Fixed name, no PID suffix. A PID-suffixed name was tried (#1584) to
412
+ # guard two processes scoped-running Stryker against the same solution
413
+ # concurrently, but reverted after review: stryker_shard_pipeline.py
414
+ # runs shards sequentially, not concurrently (see its own module
415
+ # docstring), so that race doesn't exist today — and a PID-suffixed name
416
+ # is invisible to csharp_stryker_net_wrapper.py's check_stale_hidden_sln()
417
+ # crash-recovery, which looks for the exact literal ".stryker-hidden"
418
+ # suffix, so a crashed run would leave an orphaned hidden .sln that
419
+ # existing recovery can no longer find or restore. If true concurrent
420
+ # execution is ever introduced, add uniqueness at the
421
+ # csharp_stryker_net_wrapper level (shared with
422
+ # csharp_stryker_net_slice_runner.py) with a matching update to
423
+ # check_stale_hidden_sln's stale-file scan.
424
+ sln_hidden = Path(f"{sln}.stryker-hidden") if sln is not None else None
425
+ if sln is not None and sln_hidden is not None:
426
+ # Crash-recovery refusal (#1598/#1584 review, item 6): the wrapper's
427
+ # own main() and csharp_stryker_net_slice_runner.py both call this
428
+ # before hide_sln — a stale hidden .sln left over from a prior crash
429
+ # is exactly what the fixed (no-PID) hidden-filename choice above is
430
+ # meant to make findable by this check. This caller never wired it
431
+ # in, silently skipping the recovery it depends on.
432
+ stale_err = wrapper.check_stale_hidden_sln(sln, sln_hidden)
433
+ if stale_err is not None:
434
+ config_path.unlink(missing_ok=True)
435
+ raise RuntimeError(stale_err)
436
+ try:
437
+ if sln is not None and sln_hidden is not None:
438
+ wrapper.hide_sln(sln, sln_hidden)
439
+ try:
440
+ # Deliberately not routed through _run_with_timeout: a Stryker
441
+ # timeout aborts the whole file (raise), it doesn't return a
442
+ # None-means-failure signal for a caller to revert-and-continue
443
+ # like dotnet_build/dotnet_test/git_revert/git_commit do.
444
+ subprocess.run(
445
+ wrapper.build_stryker_argv(
446
+ stryker_bin,
447
+ ["--config-file", str(config_path), "--output", str(output_dir)],
448
+ ),
449
+ env=env,
450
+ cwd=cwd,
451
+ check=False,
452
+ timeout=_STRYKER_TIMEOUT.seconds,
453
+ )
454
+ except subprocess.TimeoutExpired as exc:
455
+ raise RuntimeError(
456
+ f"error: Stryker run timed out after {_STRYKER_TIMEOUT.seconds}s for "
457
+ f"{source_file} (set {_STRYKER_TIMEOUT.env_var_name} to raise it)"
458
+ ) from exc
459
+ finally:
460
+ restored = True
461
+ if sln is not None and sln_hidden is not None:
462
+ restored = wrapper.restore_sln(sln, sln_hidden)
463
+ config_path.unlink(missing_ok=True)
464
+ if not restored:
465
+ # Mirrors #1939's git_revert handling: a failed restore leaves
466
+ # the .sln hidden/renamed, which will break the next build — a
467
+ # fatal, working-tree-possibly-broken outcome, not a silent
468
+ # no-op (#1955).
469
+ raise mutation_kill_shared.RevertFailed(
470
+ f"failed to restore {sln} from {sln_hidden} after the "
471
+ "Stryker run — the solution file is left hidden/renamed, "
472
+ "which will break the next build"
473
+ )
474
+
475
+ return Path(output_dir) / "reports" / "mutation-report.json"
476
+
477
+
478
+ def extract_survivors(report_path: Path, source_file: str) -> list[dict]:
479
+ """Return the surviving mutants for one source file (flattened).
480
+
481
+ Delegates grouping/extraction to :func:`mutation_report.survivors_by_mutator`.
482
+ """
483
+ grouped = mutation_report.survivors_by_mutator(report_path, source_file)
484
+ return [mutant for mutants in grouped.values() for mutant in mutants]
485
+
486
+
487
+ # =============================================================================
488
+ # Verify — dotnet build/test go through subprocess here; git_revert/
489
+ # git_reset_and_revert/git_commit are imported from mutation_kill_shared.py
490
+ # (#1583) rather than defined in this section.
491
+ # =============================================================================
492
+ def dotnet_build(targets: Sequence[str], *, cwd: Path | None = None) -> bool:
493
+ """Build every configured test-project. False if any target fails or
494
+ times out."""
495
+ for target in targets:
496
+ result = _run_with_timeout(
497
+ ["dotnet", "build", target, "-c", "Debug", "--nologo"],
498
+ _BUILD_TIMEOUT,
499
+ operation_label=f"dotnet build for {target}",
500
+ capture_output=True,
501
+ text=True,
502
+ cwd=cwd,
503
+ )
504
+ if result is None or result.returncode != 0:
505
+ return False
506
+ return True
507
+
508
+
509
+ # Capital-F "Failed:" only — a per-assembly `dotnet test` summary line, NOT
510
+ # the newer SDK's trailing lowercase rollup ("Test summary: ... failed: 0 ...").
511
+ # Kept case-sensitive (no re.IGNORECASE) deliberately: matching the rollup
512
+ # line too would double-count every assembly's failures on top of the
513
+ # rollup's own total (#1598/#1584 review, item 12 — see
514
+ # test_sum_failed_counts_is_case_sensitive_not_double_counting_the_rollup_line
515
+ # for a fixture that actually distinguishes the two, unlike a fixture where
516
+ # both a correct and a buggy (case-insensitive) scan sum to 0).
517
+ _FAILED_COUNT_RE = re.compile(r"Failed:\s*(\d+)")
518
+
519
+
520
+ def _sum_failed_counts(text: str) -> int:
521
+ """Sum every capital-F "Failed: N" occurrence in ``text``.
522
+
523
+ Accumulates, not overwrites: a multi-assembly `dotnet test` run prints
524
+ one "Failed: N" line per assembly, and a last-match-wins scan would let
525
+ an early assembly's failures be silently masked by a later, clean
526
+ assembly's "Failed: 0" line (#1598).
527
+ """
528
+ return sum(int(m.group(1)) for m in _FAILED_COUNT_RE.finditer(text))
529
+
530
+
531
+ def dotnet_test(
532
+ targets: Sequence[str], test_filter: str, *, cwd: Path | None = None
533
+ ) -> bool:
534
+ """Run the scoped test filter across every test-project. False on any
535
+ non-zero exit, reported failure, or timeout."""
536
+ for target in targets:
537
+ result = _run_with_timeout(
538
+ [
539
+ "dotnet",
540
+ "test",
541
+ target,
542
+ "-c",
543
+ "Debug",
544
+ "--no-build",
545
+ "--filter",
546
+ f"FullyQualifiedName~{test_filter}",
547
+ ],
548
+ _TEST_TIMEOUT,
549
+ operation_label=f"dotnet test for {target}",
550
+ capture_output=True,
551
+ text=True,
552
+ cwd=cwd,
553
+ )
554
+ if result is None:
555
+ return False
556
+ if result.returncode != 0 or _sum_failed_counts(result.stdout + result.stderr) > 0:
557
+ return False
558
+ return True
559
+
560
+
561
+ def _commit_message(
562
+ round_num: int,
563
+ source_file: str,
564
+ survivors: int,
565
+ new_methods: str,
566
+ *,
567
+ generator_label: str | None = None,
568
+ label_override: str | None = None,
569
+ ) -> str:
570
+ count = count_methods(new_methods)
571
+ # Whitespace-collapsed the same way append_generator_trailer sanitizes
572
+ # generator_label (#1607): source_file is caller-supplied, and a value
573
+ # containing a newline could otherwise forge an extra "Generator:"
574
+ # trailer line into the commit message.
575
+ safe_source_file = " ".join(str(source_file).split())
576
+ message = (
577
+ f"test(mutation): kill round {round_num} — {safe_source_file}\n\n"
578
+ f"{count} new test method(s) targeting {survivors} surviving mutant(s)"
579
+ )
580
+ return mutation_safety_gate.append_generator_trailer(
581
+ message, generator_label, label_override=label_override
582
+ )
583
+
584
+
585
+ # =============================================================================
586
+ # Per-file loop — run → score → check → generate → insert → verify → commit.
587
+ # =============================================================================
588
+ @dataclass(frozen=True)
589
+ class RunContext:
590
+ """The run-shaped inputs to :func:`run_for_file` — what to run, where, and
591
+ how to report progress.
592
+
593
+ Bundles the clump that already travels together at every call site
594
+ (``main()`` here and the ``_loop_fixture`` test helper), separating "how
595
+ to run this file" from ``run_for_file``'s own ``generate``/``max_rounds``
596
+ controls (#1561). ``generator_label``, when set, is recorded in the
597
+ commit message as an audit trail (e.g. distinguishing an unattended
598
+ ``--headless`` commit from an agent-driven one).
599
+
600
+ ``label_override_provider``, when set, is called with no arguments
601
+ before building each round's commit message; a non-``None`` result
602
+ replaces ``generator_label`` for that commit AND every subsequent commit
603
+ in this file (#1908 Step 3.2b) — the seam a model-downgrade event uses to
604
+ record itself in the audit trail without mutating this frozen,
605
+ file-level dataclass. The lifetime is sticky, not per-commit: once a
606
+ downgrade fires at round N, the stored label is never cleared, so every
607
+ later commit in this file also carries the downgrade label (with the
608
+ round number frozen at N, not the commit's own round) — intentional,
609
+ since the downgraded model really does stay in use for the rest of the
610
+ file. ``None`` (the default) leaves today's ``generator_label``-only
611
+ behavior unchanged.
612
+ """
613
+
614
+ config: LoopConfig
615
+ test_file: Path
616
+ source_path: Path
617
+ output_dir: Path
618
+ stryker_bin: str = "dotnet"
619
+ cwd: Path | None = None
620
+ log: Callable[[str], None] = print
621
+ initial_report_path: Path | None = None
622
+ generator_label: str | None = None
623
+ label_override_provider: Callable[[], str | None] | None = None
624
+ # #2030 stop controls, both default-off: with these None the loop's stop
625
+ # behavior is byte-identical to pre-#2030. target_honest_score is the
626
+ # Phase-0 mutation target Phase 8 gates on; min_kills_per_round is the
627
+ # marginal-yield floor (>=1 absolute kills, 0<v<1 a fraction of the
628
+ # round's starting survivors).
629
+ target_honest_score: float | None = None
630
+ min_kills_per_round: float | None = None
631
+
632
+
633
+ def _score_round(
634
+ round_num: int,
635
+ source_file: str,
636
+ ctx: RunContext,
637
+ *,
638
+ prev_survivor_count: int | None,
639
+ ) -> tuple[list[dict], int] | None:
640
+ """Score one round: baseline-seed-or-scoped-run → survivor extraction →
641
+ log → stop-checks.
642
+
643
+ Returns ``(survivors, survivor_count)`` to continue the round, or
644
+ ``None`` when the file is done — no survivors, or no improvement over
645
+ the previous round.
646
+ """
647
+ if ctx.initial_report_path is not None and round_num == 1:
648
+ report_path = Path(ctx.initial_report_path)
649
+ else:
650
+ report_path = run_scoped_stryker(
651
+ ctx.config,
652
+ source_file,
653
+ output_dir=ctx.output_dir,
654
+ stryker_bin=ctx.stryker_bin,
655
+ cwd=ctx.cwd,
656
+ )
657
+
658
+ survivors = extract_survivors(report_path, source_file)
659
+ survivor_count = len(survivors)
660
+ # File-scoped, not score_report(): a baseline-seeded round 1 report can
661
+ # cover multiple files (#1545) — score_report() would leak another
662
+ # file's score into this line.
663
+ summary = mutation_report.score_report_for_file(report_path, source_file)
664
+ ctx.log(
665
+ f" round {round_num}: honest={summary.honest_score:.1f}% "
666
+ f"survivors={survivor_count}"
667
+ )
668
+
669
+ # Mirrors the Python loop's equivalent guard (#1359/#1606): mutation_report's
670
+ # own documented contract is to return a fully-zeroed ScoreSummary (never
671
+ # raise) for an absent/empty report or an unmatched file key, and
672
+ # run_scoped_stryker runs Stryker with check=False — a crashed Stryker
673
+ # process, or a scoped-config file-key mismatch, can therefore yield
674
+ # survivor_count == 0 here with zero mutants ever actually having run.
675
+ # Without this check that would be indistinguishable from a genuine
676
+ # "no survivors — done" convergence.
677
+ total_mutants = summary.killed + summary.survived + summary.timeout + summary.no_coverage
678
+ if total_mutants == 0:
679
+ ctx.log(
680
+ " zero mutants generated — this is NOT convergence. Stryker "
681
+ "produced no results at all for this file (a crashed run, or a "
682
+ "scoped-config file-key mismatch). Stopping without declaring "
683
+ "survivors == 0 (#1606)."
684
+ )
685
+ return None
686
+
687
+ decision = stop_reason(
688
+ survivor_count,
689
+ prev_survivor_count,
690
+ honest_score=summary.honest_score,
691
+ target_honest_score=ctx.target_honest_score,
692
+ min_kills_per_round=ctx.min_kills_per_round,
693
+ )
694
+ if decision is not None:
695
+ # A non-terminal decision is the #2030 marginal-yield floor: the round
696
+ # DID make progress, the file may still be below target, and whether
697
+ # another round is worth its price is the operator's call. Prefixing it
698
+ # distinctly is what keeps it from reading as a convergence stop in the
699
+ # run log — the agent layer routes a YIELD FLOOR line to Phase 5's
700
+ # existing [c]ontinue / [r]etry / [w]aive / [q]uit prompt.
701
+ ctx.log(f" {decision}" if decision.terminal else f" YIELD FLOOR — {decision}")
702
+ return None
703
+ return survivors, survivor_count
704
+
705
+
706
+ def _revert_or_raise(ctx: RunContext, reason: str, *, after_commit: bool = False) -> None:
707
+ """Revert ``ctx.test_file``, raising if the revert itself fails.
708
+
709
+ ``after_commit=True`` routes through :func:`git_reset_and_revert`
710
+ (unstage + checkout), not plain :func:`git_revert` — ``git_commit``
711
+ already staged ``ctx.test_file`` before the commit attempt failed, so a
712
+ plain checkout alone would restore from that still-mutated index, not
713
+ HEAD (#1598/#1584 review). A revert that itself fails is fatal: it
714
+ leaves the working tree in an unknown, possibly-mutated state, so this
715
+ raises rather than letting the loop continue silently.
716
+ """
717
+ revert_ok = (
718
+ git_reset_and_revert(ctx.test_file, cwd=ctx.cwd)
719
+ if after_commit
720
+ else git_revert(ctx.test_file, cwd=ctx.cwd)
721
+ )
722
+ if not revert_ok:
723
+ raise mutation_kill_shared.RevertFailed(
724
+ f"revert failed for {ctx.test_file} after {reason} — the "
725
+ "working tree is left in an unknown state (mutated test "
726
+ "content may still be on disk, uncommitted)"
727
+ )
728
+
729
+
730
+ def _verify_and_commit(
731
+ round_num: int,
732
+ source_file: str,
733
+ survivor_count: int,
734
+ new_methods: str,
735
+ ctx: RunContext,
736
+ ) -> int | None:
737
+ """Build → scoped test → commit-on-green / revert-on-failure.
738
+
739
+ Returns ``survivor_count`` on a successful commit, or ``None`` when the
740
+ round is abandoned (build failure, test failure, or commit failure —
741
+ each reverted via :func:`_revert_or_raise`, which raises if the revert
742
+ itself fails).
743
+ """
744
+ if not dotnet_build(dotnet_test_project_targets(ctx.config), cwd=ctx.cwd):
745
+ ctx.log(" build failed — reverting")
746
+ _revert_or_raise(ctx, "a failed build")
747
+ return None
748
+ if not dotnet_test(dotnet_test_project_targets(ctx.config), ctx.test_file.stem, cwd=ctx.cwd):
749
+ ctx.log(" tests failed — reverting")
750
+ _revert_or_raise(ctx, "a failed test run")
751
+ return None
752
+
753
+ ctx.log(" green — committing")
754
+ label_override = (
755
+ ctx.label_override_provider() if ctx.label_override_provider is not None else None
756
+ )
757
+ committed = git_commit(
758
+ _commit_message(
759
+ round_num,
760
+ source_file,
761
+ survivor_count,
762
+ new_methods,
763
+ generator_label=ctx.generator_label,
764
+ label_override=label_override,
765
+ ),
766
+ ctx.test_file,
767
+ cwd=ctx.cwd,
768
+ )
769
+ if not committed:
770
+ # A failed/timed-out commit is a round failure, not a silent
771
+ # success — without this check the loop would advance to the next
772
+ # round believing this one landed, while the test methods sit
773
+ # uncommitted (and possibly still staged) on disk (#1598).
774
+ ctx.log(" commit failed — reverting")
775
+ _revert_or_raise(ctx, "a failed commit", after_commit=True)
776
+ return None
777
+ return survivor_count
778
+
779
+
780
+ def _run_round(
781
+ round_num: int,
782
+ source_file: str,
783
+ ctx: RunContext,
784
+ generate: Generator,
785
+ *,
786
+ prev_survivor_count: int | None,
787
+ ) -> int | None:
788
+ """Run one round: score (via :func:`_score_round`) → generate → insert →
789
+ verify → commit (via :func:`_verify_and_commit`).
790
+
791
+ Returns this round's survivor count (to seed the next round's
792
+ no-improvement check), or ``None`` when the file is done.
793
+ """
794
+ scored = _score_round(round_num, source_file, ctx, prev_survivor_count=prev_survivor_count)
795
+ if scored is None:
796
+ return None
797
+ survivors, survivor_count = scored
798
+
799
+ # Read once and thread the text through both the generation prompt and
800
+ # the insertion helper below — apply_generated_methods (and, inside it,
801
+ # insert_before_class_close) accept the already-read text instead of
802
+ # re-reading test_file from disk a second and third time (#1584).
803
+ test_text = ctx.test_file.read_text(encoding="utf-8")
804
+ new_methods = generate(
805
+ source_file,
806
+ survivors,
807
+ ctx.source_path.read_text(encoding="utf-8"),
808
+ test_text,
809
+ )
810
+
811
+ # No test_text= here on purpose (#1598/#1584 review, item 7):
812
+ # apply_generated_methods always does its own fresh read now, shared
813
+ # between its duplicate-name check and the write — this pre-generation
814
+ # `test_text` snapshot (read above, before the possibly multi-minute
815
+ # `generate()` call) is only for the generation prompt.
816
+ outcome = apply_generated_methods(ctx.test_file, new_methods)
817
+ if not outcome.inserted:
818
+ ctx.log(f" not inserted ({outcome.reason}) — stopping")
819
+ return None
820
+
821
+ return _verify_and_commit(round_num, source_file, survivor_count, new_methods, ctx)
822
+
823
+
824
+ def run_for_file(
825
+ source_file: str,
826
+ ctx: RunContext,
827
+ *,
828
+ generate: Generator,
829
+ max_rounds: int = 5,
830
+ ) -> None:
831
+ """Drive the deterministic survivor-kill loop for one source file.
832
+
833
+ ``generate`` is the sole non-deterministic step: given survivors + context
834
+ it returns the raw new-method text. Everything else — scoped run, scoring,
835
+ duplicate/insert guards, build/test verification, revert-on-failure,
836
+ commit-on-green, and the no-improvement stop — is mechanical, driven one
837
+ round at a time by :func:`_run_round`.
838
+
839
+ A failed revert (after a build failure, a test failure, or a failed
840
+ commit) is fatal: it raises :class:`mutation_kill_shared.RevertFailed`
841
+ rather than returning silently, because a revert that can't be verified
842
+ as having succeeded means the working tree is left in an unknown,
843
+ possibly-mutated state —
844
+ silently continuing to the next round or file would risk committing
845
+ mutated content later under a false assumption of a clean tree (#1598).
846
+ A failed commit itself is also a round failure, not a silent success: it
847
+ is reverted (unstage + restore, via :func:`git_reset_and_revert`) and the
848
+ round stops without advancing.
849
+ """
850
+ prev_survivor_count: int | None = None
851
+ for round_num in range(1, max_rounds + 1):
852
+ prev_survivor_count = _run_round(
853
+ round_num, source_file, ctx, generate, prev_survivor_count=prev_survivor_count
854
+ )
855
+ if prev_survivor_count is None:
856
+ return
857
+
858
+
859
+ if __name__ == "__main__":
860
+ # Register this already-executing module under its real import name
861
+ # before mutation_kill_headless's `from mutation_kill_loop import ...`
862
+ # runs — otherwise Python can't find "mutation_kill_loop" in sys.modules
863
+ # (this process is running as "__main__") and re-executes this file from
864
+ # disk as a second, distinct module object (two RunContext/LoopConfig
865
+ # class identities). Aliasing first makes the import a cache hit.
866
+ sys.modules.setdefault("mutation_kill_loop", sys.modules[__name__])
867
+ import mutation_kill_headless
868
+
869
+ sys.exit(mutation_kill_headless.main())