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,949 @@
1
+ #!/usr/bin/env python3
2
+ """mutation_kill_loop_python.py — deterministic survivor-kill loop for
3
+ Python/mutmut, the Python counterpart to ``mutation_kill_loop.py`` (#1357).
4
+
5
+ mutmut has no project/solution structure to load from a config file the way
6
+ Stryker.NET does — a scoped run only needs the source file and a test
7
+ command, so there is no config-file abstraction here (unlike
8
+ ``mutation_kill_loop.py``'s ``LoopConfig``/``stryker-config.json``). Scoring
9
+ and survivor extraction reuse ``mutation_report``'s mutmut-junitxml support
10
+ (#1357).
11
+
12
+ **Generation is a seam, not a mechanism** (same contract as the C# loop):
13
+ the loop never decides *what* tests to write — a caller supplies a
14
+ ``generate`` callable that returns the new pytest function text. The
15
+ default (interactive) path is agent-driven: the ``mutation-kill`` agent
16
+ calls :func:`run_for_file` directly, passing a ``generate`` hook backed by a
17
+ live agent turn. A ``--headless`` CLI mode shells to ``claude --print`` for
18
+ unattended (CI) runs.
19
+
20
+ **Scope (#1583).** This module owns the scoped ``mutmut run``,
21
+ verify/commit/revert, and ``run_for_file`` orchestration — mirroring
22
+ ``mutation_kill_loop.py``'s post-#1562 scope exactly. Insertion mechanics
23
+ (detect-or-refuse end-of-file test-function appending) live in
24
+ ``mutation_kill_insert_python.py``, a stdlib-only leaf this module imports
25
+ from — never the reverse — mirroring the C# split
26
+ (``mutation_kill_insert.py``). Headless generation's shared ``claude --print``
27
+ invocation glue and the generic (non-language-specific) helpers
28
+ (``strip_code_fences``, ``resolve_model``, ``claude_cli_available``,
29
+ ``CLAUDE_CLI``, ``run_claude_headless``) live in ``mutation_kill_shared.py``
30
+ (#1601) and are reused, not duplicated, here — only the Python-flavored
31
+ prompt (``build_generation_prompt``/``build_survivor_summary``) and the
32
+ ``--headless`` CLI argument parsing for THIS loop stay local, since mutmut's
33
+ CLI args (``--test-command``, no ``--config``/``--stryker-bin``) genuinely
34
+ differ from the C# loop's.
35
+
36
+ **Shared mechanics (#1583).** ``_timeout_from_env``, ``git_revert``,
37
+ ``git_reset_and_revert``, ``git_commit``, and the "no improvement across
38
+ rounds" stop predicate are imported from ``mutation_kill_shared.py`` rather
39
+ than defined here — they were byte-for-byte duplicated with
40
+ ``mutation_kill_loop.py`` before that module existed.
41
+
42
+ **Import boundary (#1601).** ``resolve_model``, ``claude_cli_available``,
43
+ ``CLAUDE_CLI``, and ``run_claude_headless`` are imported directly from
44
+ ``mutation_kill_shared`` — never from ``mutation_kill_headless``, which is
45
+ the C#/Stryker.NET CLI module (it imports ``mutation_kill_loop`` at module
46
+ scope and owns the C#-only ``--config``/``--stryker-bin`` CLI surface).
47
+ (``strip_code_fences`` also moved to ``mutation_kill_shared`` but isn't
48
+ imported here directly — this module only reaches it indirectly, through
49
+ ``run_claude_headless``'s own internal call.) Reaching into
50
+ ``mutation_kill_headless`` for these language-neutral names used to
51
+ transitively pull the entire C# stack into this Python-only loop; importing
52
+ them from the neutral ``mutation_kill_shared`` module instead removes that
53
+ coupling entirely.
54
+ """
55
+
56
+ from __future__ import annotations
57
+
58
+ import argparse
59
+ import contextlib
60
+ import shutil
61
+ import subprocess
62
+ import sys
63
+ import time
64
+ from collections.abc import Callable, Sequence
65
+ from dataclasses import dataclass
66
+ from pathlib import Path
67
+
68
+ # typing, not collections.abc: the `Generator` alias below is a real runtime
69
+ # expression, so `from __future__ import annotations` cannot defer it — it
70
+ # must be subscriptable at import time. collections.abc generics have been
71
+ # since 3.9, which the 3.10 floor (ADR 0031) clears.
72
+ import mutation_kill_shared
73
+ import mutation_report
74
+ import mutation_safety_gate
75
+ from mutation_kill_insert_python import apply_generated_tests, count_tests
76
+ from mutation_kill_retry import (
77
+ EXIT_GENERATION_EXHAUSTED,
78
+ EXIT_REVERT_FAILED,
79
+ DowngradeEvent,
80
+ GenerationExhausted,
81
+ make_downgrade_audit_hook,
82
+ make_retrying_headless_call,
83
+ )
84
+ from mutation_kill_shared import (
85
+ CLAUDE_CLI,
86
+ GIT_TIMEOUT_S, # noqa: F401 — re-exported for tests (loop.GIT_TIMEOUT_S), matching the C# sibling's export name
87
+ _timeout_from_env,
88
+ claude_cli_available,
89
+ git_commit,
90
+ git_reset_and_revert,
91
+ git_revert,
92
+ resolve_model,
93
+ run_claude_headless, # noqa: F401 — re-exported for tests (loop.run_claude_headless identity check); make_headless_generator now calls it indirectly via make_retrying_headless_call (#1908)
94
+ stop_reason,
95
+ )
96
+
97
+ Generator = Callable[[str, list[dict], str, str], str]
98
+
99
+ # Mirrors mutation_kill_headless.NO_GENERATOR_MESSAGE — pinned so a contract test
100
+ # can assert it verbatim.
101
+ NO_GENERATOR_MESSAGE = (
102
+ "no test generator available — invoke via the mutation-kill agent "
103
+ "or pass --headless"
104
+ )
105
+
106
+ MISSING_CLAUDE_MESSAGE = (
107
+ f"--headless requires the Claude CLI but '{CLAUDE_CLI}' is not available. "
108
+ "Install Claude Code (`npm install -g @anthropic-ai/claude-code`) and "
109
+ "authenticate it (run `claude` once to log in, or set ANTHROPIC_API_KEY) — "
110
+ "or set CLAUDE_BIN to the CLI's path."
111
+ )
112
+
113
+
114
+ # =============================================================================
115
+ # Scoped mutmut run — mutmut has no native JSON report; junitxml is it.
116
+ # =============================================================================
117
+ def _mutmut_argv() -> list[str]:
118
+ """Return the argv prefix for invoking mutmut — `mutmut` or `python3 -m mutmut`."""
119
+ if shutil.which("mutmut") is not None:
120
+ return ["mutmut"]
121
+ return [sys.executable, "-m", "mutmut"]
122
+
123
+
124
+ # Bounds how long a caller waits to acquire the `.mutmut-cache` lock before
125
+ # giving up loudly rather than hanging forever behind a stuck/crashed holder.
126
+ # An override is legitimate for a large repo whose mutmut-cache lock is held
127
+ # longer than 300s by a slow, in-flight concurrent run.
128
+ _MUTMUT_CACHE_LOCK_TIMEOUT_S = _timeout_from_env(
129
+ "DEV_TEAM_MUTATION_MUTMUT_LOCK_TIMEOUT_S", 300
130
+ )
131
+
132
+ # How often _acquire_mutmut_cache_lock re-checks the lock directory while
133
+ # waiting. Small enough to notice a release promptly; large enough not to
134
+ # busy-loop.
135
+ _MUTMUT_CACHE_LOCK_POLL_INTERVAL_S = 0.1
136
+
137
+ # Timeouts for the mutmut subprocesses themselves (#1605) — previously
138
+ # unbounded, unlike the C# loop's DOTNET_BUILD_TIMEOUT_S/DOTNET_TEST_TIMEOUT_S
139
+ # equivalents. The scoped `mutmut run` mutates and re-tests every mutant for
140
+ # one file, so its budget mirrors STRYKER_RUN_TIMEOUT_S's order of magnitude;
141
+ # `mutmut junitxml` only reformats already-computed results, so it gets a
142
+ # short budget instead.
143
+ _MUTMUT_RUN_TIMEOUT_S = _timeout_from_env("DEV_TEAM_MUTATION_MUTMUT_TIMEOUT_S", 3600)
144
+ _MUTMUT_JUNITXML_TIMEOUT_S = _timeout_from_env(
145
+ "DEV_TEAM_MUTATION_MUTMUT_JUNITXML_TIMEOUT_S", 60
146
+ )
147
+
148
+
149
+ def _acquire_mutmut_cache_lock(root: Path, *, timeout: float = _MUTMUT_CACHE_LOCK_TIMEOUT_S) -> Path:
150
+ """Acquire a simple, cross-platform mutex directory guarding
151
+ ``.mutmut-cache`` for the duration of one scoped mutmut run (#1584).
152
+
153
+ Two concurrent ``run_scoped_mutmut`` invocations sharing the same repo
154
+ race on the single, fixed ``.mutmut-cache`` path — one run's cache
155
+ delete/mutmut-run/revert sequence can corrupt or invalidate another's
156
+ in-flight run. ``Path.mkdir()`` is atomic on both POSIX and Windows,
157
+ unlike ``fcntl``/``msvcrt`` file locks (only one of which is available on
158
+ any given platform), so a lock *directory* — created, then removed on
159
+ release — is the portable, stdlib-only mutex.
160
+ """
161
+ lock_dir = root / ".mutmut-cache.lock"
162
+ deadline = time.monotonic() + timeout
163
+ while True:
164
+ try:
165
+ lock_dir.mkdir()
166
+ return lock_dir
167
+ except FileExistsError:
168
+ if time.monotonic() >= deadline:
169
+ raise RuntimeError(
170
+ f"timed out after {timeout}s waiting for the .mutmut-cache "
171
+ f"lock at {lock_dir} — a concurrent run may be stuck "
172
+ "holding it (remove the directory manually to recover if "
173
+ "no run is actually in flight)"
174
+ ) from None
175
+ time.sleep(_MUTMUT_CACHE_LOCK_POLL_INTERVAL_S)
176
+
177
+
178
+ def _release_mutmut_cache_lock(lock_dir: Path) -> None:
179
+ """Release the lock directory acquired by :func:`_acquire_mutmut_cache_lock`.
180
+
181
+ Called from a ``finally`` block — swallows ``OSError``/``FileNotFoundError``
182
+ (e.g. the directory was already removed) so a release-time failure never
183
+ masks whatever exception the run itself was raising (#1598/#1584 review).
184
+ """
185
+ with contextlib.suppress(OSError, FileNotFoundError):
186
+ lock_dir.rmdir()
187
+
188
+
189
+ def _revert_file_for_cleanup(path: Path, *, cwd: Path | None) -> bool:
190
+ """Revert ``path`` for :func:`run_scoped_mutmut`'s cleanup ``finally``.
191
+
192
+ ``git_revert`` only converts ``subprocess.TimeoutExpired`` to False; any
193
+ other unexpected exception (e.g. ``FileNotFoundError`` if git isn't on
194
+ PATH) would otherwise propagate straight out of the caller's ``finally``
195
+ block, skipping the second revert attempt entirely. Treat it the same as
196
+ a False return so both reverts are genuinely attempted unconditionally.
197
+ """
198
+ try:
199
+ return git_revert(path, cwd=cwd)
200
+ except OSError:
201
+ return False
202
+
203
+
204
+ def run_scoped_mutmut(
205
+ source_file: str,
206
+ *,
207
+ test_command: str,
208
+ test_file: Path | None = None,
209
+ cwd: Path | None = None,
210
+ ) -> str:
211
+ """Run mutmut scoped to one file; return the ``mutmut junitxml`` output.
212
+
213
+ Clears any stale ``.mutmut-cache`` first — a cache from a *different*
214
+ scope (a prior run against another file, or a stale run from before this
215
+ file changed) is silently reused otherwise, which was a real trap hit
216
+ manually while dogfooding this loop by hand (#1354): every run must see
217
+ its own fresh baseline, not a leftover one.
218
+
219
+ **Always reverts ``source_file`` (and ``test_file``, when given) in a
220
+ ``finally``.** mutmut mutates the real source file on disk for the
221
+ duration of each mutant's test run and restores it when that mutant
222
+ finishes — but an internal mutmut crash (a real, reproducible one: mutmut
223
+ 2.5.1's own cache layer raises ``AssertionError``/``ValueError`` on some
224
+ files, confirmed while dogfooding this exact function against
225
+ ``hooks/mutation_adapters/mutmut.py`` — see #1357) skips that restore
226
+ and leaves the mutated content on disk. Unlike Stryker.NET (which
227
+ instruments a separate build, never the real file), mutmut's crash
228
+ failure mode is "corrupt the file under test," so every scoped run must
229
+ unconditionally `git checkout --` it afterward — succeeding, failing, or
230
+ raising.
231
+
232
+ The **test file** the ``--runner`` command exercises is exposed to the
233
+ same failure mode — mutmut 2.5.1 has also been observed to truncate the
234
+ runner's test file to empty via a crashed ``.bak``-restore (#1359),
235
+ which silently breaks the *next* round's baseline (mutmut then reports
236
+ zero mutants — a false "converged" positive, not real coverage). Passing
237
+ ``test_file`` reverts it alongside ``source_file`` in the same
238
+ ``finally``; each round's ``git checkout --`` restores exactly the
239
+ state committed at the end of the previous round, which is always the
240
+ correct baseline for the round about to run.
241
+
242
+ **Lock-guarded end to end** (#1584): the cache delete, the mutmut run
243
+ itself, and the revert are all held under ``.mutmut-cache.lock`` — not
244
+ just the delete — because mutmut's cache is shared, fixed-path state for
245
+ the whole repo; a second concurrent invocation reading/writing it
246
+ mid-run is exactly as corrupting as racing the delete alone.
247
+
248
+ Raises :class:`mutation_kill_shared.RevertFailed` when either cleanup
249
+ revert fails. Both cleanup reverts are attempted first regardless of
250
+ which one fails; the raised message names every file that failed to
251
+ revert.
252
+ """
253
+ root = cwd or Path(".")
254
+ lock_dir = _acquire_mutmut_cache_lock(root)
255
+ try:
256
+ (root / ".mutmut-cache").unlink(missing_ok=True)
257
+
258
+ prefix = _mutmut_argv()
259
+ argv = [
260
+ *prefix,
261
+ "run",
262
+ f"--paths-to-mutate={source_file}",
263
+ "--runner",
264
+ test_command,
265
+ "--no-progress",
266
+ "--simple-output",
267
+ ]
268
+ try:
269
+ try:
270
+ subprocess.run(
271
+ argv,
272
+ cwd=cwd,
273
+ capture_output=True,
274
+ text=True,
275
+ check=False,
276
+ timeout=_MUTMUT_RUN_TIMEOUT_S,
277
+ )
278
+ except (FileNotFoundError, OSError) as exc:
279
+ raise RuntimeError(f"mutmut run failed to start: {exc}") from exc
280
+ except subprocess.TimeoutExpired as exc:
281
+ raise RuntimeError(
282
+ f"mutmut run timed out after {_MUTMUT_RUN_TIMEOUT_S}s for "
283
+ f"{source_file} (set DEV_TEAM_MUTATION_MUTMUT_TIMEOUT_S to "
284
+ "raise it)"
285
+ ) from exc
286
+
287
+ try:
288
+ junit = subprocess.run(
289
+ [*prefix, "junitxml"],
290
+ cwd=cwd,
291
+ capture_output=True,
292
+ text=True,
293
+ check=False,
294
+ timeout=_MUTMUT_JUNITXML_TIMEOUT_S,
295
+ )
296
+ except subprocess.TimeoutExpired as exc:
297
+ raise RuntimeError(
298
+ "mutmut junitxml extraction timed out after "
299
+ f"{_MUTMUT_JUNITXML_TIMEOUT_S}s (set "
300
+ "DEV_TEAM_MUTATION_MUTMUT_JUNITXML_TIMEOUT_S to raise it)"
301
+ ) from exc
302
+ return junit.stdout or ""
303
+ finally:
304
+ source_reverted = _revert_file_for_cleanup(Path(source_file), cwd=cwd)
305
+ test_reverted = True
306
+ if test_file is not None:
307
+ test_reverted = _revert_file_for_cleanup(test_file, cwd=cwd)
308
+ failed = [
309
+ str(p)
310
+ for p, ok in (
311
+ (source_file, source_reverted),
312
+ (test_file, test_reverted),
313
+ )
314
+ if p is not None and not ok
315
+ ]
316
+ if failed:
317
+ raise mutation_kill_shared.RevertFailed(
318
+ "cleanup revert failed for "
319
+ f"{', '.join(failed)} after run_scoped_mutmut — the "
320
+ "working tree is left in an unknown state (mutated "
321
+ "content may still be on disk, uncommitted)"
322
+ )
323
+ finally:
324
+ _release_mutmut_cache_lock(lock_dir)
325
+
326
+
327
+ def extract_survivors(junitxml_text: str, source_file: str) -> list[dict]:
328
+ """Return the surviving mutants for one source file (flattened).
329
+
330
+ Delegates parsing to :func:`mutation_report.survivors_from_mutmut_junitxml`
331
+ — mutmut names no per-mutation operator, so every survivor's
332
+ ``mutatorName`` is the fixed literal ``"mutmut"`` (a single group).
333
+ """
334
+ grouped = mutation_report.survivors_from_mutmut_junitxml(
335
+ junitxml_text, source_file
336
+ )
337
+ return [mutant for mutants in grouped.values() for mutant in mutants]
338
+
339
+
340
+ # =============================================================================
341
+ # Verify — python_compiles/run_scoped_pytest go through subprocess here;
342
+ # git_revert/git_reset_and_revert/git_commit are imported from
343
+ # mutation_kill_shared.py (#1583) rather than defined in this section.
344
+ # =============================================================================
345
+ # Timeouts for the compile-check and scoped-test subprocesses (#1605) —
346
+ # previously unbounded, unlike the C# loop's DOTNET_BUILD_TIMEOUT_S/
347
+ # DOTNET_TEST_TIMEOUT_S equivalents.
348
+ _PYTHON_COMPILE_TIMEOUT_S = _timeout_from_env(
349
+ "DEV_TEAM_MUTATION_PYTHON_COMPILE_TIMEOUT_S", 600
350
+ )
351
+ _PYTEST_TIMEOUT_S = _timeout_from_env("DEV_TEAM_MUTATION_PYTEST_TIMEOUT_S", 600)
352
+
353
+
354
+ def _neutralize_leading_dash(path: Path) -> str:
355
+ """Return ``str(path)`` guarded against being parsed as a CLI flag
356
+ instead of a positional filename (#1607) — a ``test_file`` value like
357
+ ``-p`` would otherwise let pytest interpret it as ``-p <plugin>`` rather
358
+ than a (nonexistent) file. A ``--`` end-of-options marker is the usual
359
+ fix (and is what ``py_compile``'s own ``argparse``-based CLI honors),
360
+ but pytest's own argument parser does NOT treat ``--`` as ending option
361
+ parsing — a flag placed after it is still parsed, not treated as a bare
362
+ positional — so prefixing a relative, dash-leading path with ``./``
363
+ instead, which works regardless of a tool's own ``--`` support.
364
+ """
365
+ text = str(path)
366
+ return f"./{text}" if text.startswith("-") else text
367
+
368
+
369
+ def python_compiles(test_file: Path, *, cwd: Path | None = None) -> bool:
370
+ """Syntax-check the test file — Python's equivalent of a build step.
371
+
372
+ False (not raised) on a timeout, matching this function's existing
373
+ "non-zero returncode -> False" contract for a plain compile failure.
374
+ """
375
+ try:
376
+ rc = subprocess.run(
377
+ [sys.executable, "-m", "py_compile", _neutralize_leading_dash(test_file)],
378
+ capture_output=True,
379
+ text=True,
380
+ cwd=cwd,
381
+ check=False,
382
+ timeout=_PYTHON_COMPILE_TIMEOUT_S,
383
+ ).returncode
384
+ except subprocess.TimeoutExpired:
385
+ return False
386
+ return rc == 0
387
+
388
+
389
+ def run_scoped_pytest(test_file: Path, *, cwd: Path | None = None) -> bool:
390
+ """Run the test file under pytest. False on any non-zero exit or timeout."""
391
+ try:
392
+ rc = subprocess.run(
393
+ [sys.executable, "-m", "pytest", "-q", _neutralize_leading_dash(test_file)],
394
+ capture_output=True,
395
+ text=True,
396
+ cwd=cwd,
397
+ check=False,
398
+ timeout=_PYTEST_TIMEOUT_S,
399
+ ).returncode
400
+ except subprocess.TimeoutExpired:
401
+ return False
402
+ return rc == 0
403
+
404
+
405
+ def _commit_message(
406
+ round_num: int,
407
+ source_file: str,
408
+ survivors: int,
409
+ new_tests: str,
410
+ *,
411
+ generator_label: str | None = None,
412
+ label_override: str | None = None,
413
+ ) -> str:
414
+ count = count_tests(new_tests)
415
+ # Whitespace-collapsed the same way append_generator_trailer sanitizes
416
+ # generator_label (#1607): source_file is caller-supplied, and a value
417
+ # containing a newline could otherwise forge an extra "Generator:"
418
+ # trailer line into the commit message.
419
+ safe_source_file = " ".join(str(source_file).split())
420
+ message = (
421
+ f"test(mutation): kill round {round_num} — {safe_source_file}\n\n"
422
+ f"{count} new test(s) targeting {survivors} surviving mutant(s)"
423
+ )
424
+ return mutation_safety_gate.append_generator_trailer(
425
+ message, generator_label, label_override=label_override
426
+ )
427
+
428
+
429
+ # =============================================================================
430
+ # Per-file loop — run → score → check → generate → insert → verify → commit.
431
+ # =============================================================================
432
+ @dataclass(frozen=True)
433
+ class RunContext:
434
+ """The run-shaped inputs to :func:`run_for_file` — what to run, where, and
435
+ how to report progress.
436
+
437
+ Bundles the clump that already travels together at every call site
438
+ (``main()`` here and the ``mutation-kill`` agent's own driving code),
439
+ separating "how to run this file" from ``run_for_file``'s own
440
+ ``generate``/``max_rounds`` controls — mirrors
441
+ ``mutation_kill_loop.py``'s ``RunContext`` (#1561/#1583).
442
+ ``generator_label``, when set, is recorded in the commit message as an
443
+ audit trail (e.g. distinguishing an unattended ``--headless`` commit from
444
+ an agent-driven one).
445
+
446
+ ``label_override_provider``, when set, is called with no arguments
447
+ before building each round's commit message; a non-``None`` result
448
+ replaces ``generator_label`` for that commit AND every subsequent commit
449
+ in this file (#1908 Step 3.2b) — the seam a model-downgrade event uses to
450
+ record itself in the audit trail without mutating this frozen,
451
+ file-level dataclass. The lifetime is sticky, not per-commit: once a
452
+ downgrade fires at round N, the stored label is never cleared, so every
453
+ later commit in this file also carries the downgrade label (with the
454
+ round number frozen at N, not the commit's own round) — intentional,
455
+ since the downgraded model really does stay in use for the rest of the
456
+ file. ``None`` (the default) leaves today's ``generator_label``-only
457
+ behavior unchanged.
458
+ """
459
+
460
+ test_file: Path
461
+ source_path: Path
462
+ test_command: str
463
+ cwd: Path | None = None
464
+ log: Callable[[str], None] = print
465
+ initial_junitxml: str | None = None
466
+ generator_label: str | None = None
467
+ label_override_provider: Callable[[], str | None] | None = None
468
+ # #2030 stop controls, both default-off: with these None the loop's stop
469
+ # behavior is byte-identical to pre-#2030. target_honest_score is the
470
+ # Phase-0 mutation target Phase 8 gates on; min_kills_per_round is the
471
+ # marginal-yield floor (>=1 absolute kills, 0<v<1 a fraction of the
472
+ # round's starting survivors).
473
+ target_honest_score: float | None = None
474
+ min_kills_per_round: float | None = None
475
+
476
+
477
+ def _score_round(
478
+ round_num: int,
479
+ source_file: str,
480
+ ctx: RunContext,
481
+ *,
482
+ prev_survivor_count: int | None,
483
+ ) -> tuple[list[dict], int] | None:
484
+ """Score one round: scoped-run-or-seeded-report → survivor extraction →
485
+ log → stop-checks.
486
+
487
+ Returns ``(survivors, survivor_count)`` to continue the round, or
488
+ ``None`` when the file is done — zero mutants generated (not
489
+ convergence — see below), no survivors, or no improvement over the
490
+ previous round.
491
+ """
492
+ if ctx.initial_junitxml is not None and round_num == 1:
493
+ junitxml_text = ctx.initial_junitxml
494
+ else:
495
+ junitxml_text = run_scoped_mutmut(
496
+ source_file, test_command=ctx.test_command, test_file=ctx.test_file, cwd=ctx.cwd
497
+ )
498
+
499
+ survivors = extract_survivors(junitxml_text, source_file)
500
+ survivor_count = len(survivors)
501
+ summary = mutation_report.score_mutmut_junitxml(junitxml_text)
502
+ ctx.log(
503
+ f" round {round_num}: honest={summary.honest_score:.1f}% "
504
+ f"survivors={survivor_count}"
505
+ )
506
+
507
+ total_mutants = (
508
+ summary.killed + summary.survived + summary.timeout + summary.no_coverage
509
+ )
510
+ if total_mutants == 0:
511
+ ctx.log(
512
+ " zero mutants generated — this is NOT convergence. mutmut "
513
+ "produced no results at all (a real internal crash — e.g. "
514
+ "the known Python 3.13+ pickle incompatibility, 'TypeError: "
515
+ "cannot pickle itertools.count object' — or a file with no "
516
+ "executable statements). Stopping without declaring "
517
+ "survivors == 0 (#1359)."
518
+ )
519
+ return None
520
+
521
+ decision = stop_reason(
522
+ survivor_count,
523
+ prev_survivor_count,
524
+ honest_score=summary.honest_score,
525
+ target_honest_score=ctx.target_honest_score,
526
+ min_kills_per_round=ctx.min_kills_per_round,
527
+ )
528
+ if decision is not None:
529
+ # A non-terminal decision is the #2030 marginal-yield floor: the round
530
+ # DID make progress, the file may still be below target, and whether
531
+ # another round is worth its price is the operator's call. Prefixing it
532
+ # distinctly is what keeps it from reading as a convergence stop in the
533
+ # run log — the agent layer routes a YIELD FLOOR line to Phase 5's
534
+ # existing [c]ontinue / [r]etry / [w]aive / [q]uit prompt.
535
+ ctx.log(f" {decision}" if decision.terminal else f" YIELD FLOOR — {decision}")
536
+ return None
537
+
538
+ return survivors, survivor_count
539
+
540
+
541
+ def _revert_or_raise(ctx: RunContext, reason: str, *, after_commit: bool = False) -> None:
542
+ """Revert ``ctx.test_file``, raising if the revert itself fails.
543
+
544
+ ``after_commit=True`` routes through :func:`git_reset_and_revert`
545
+ (unstage + checkout), not plain :func:`git_revert` — ``git_commit``
546
+ already staged ``ctx.test_file`` before the commit attempt failed, so a
547
+ plain checkout alone would restore from that still-mutated index, not
548
+ HEAD (#1598/#1584 review). A revert that itself fails is fatal: it
549
+ leaves the working tree in an unknown, possibly-mutated state, so this
550
+ raises rather than letting the loop continue silently.
551
+
552
+ Explicit params (not a closure) — mirrors ``mutation_kill_loop.py``'s
553
+ ``_revert_or_raise`` shape and makes this independently
554
+ testable/monkeypatchable (#1598/#1584 review, item 3).
555
+ """
556
+ revert_ok = (
557
+ git_reset_and_revert(ctx.test_file, cwd=ctx.cwd)
558
+ if after_commit
559
+ else git_revert(ctx.test_file, cwd=ctx.cwd)
560
+ )
561
+ if not revert_ok:
562
+ raise mutation_kill_shared.RevertFailed(
563
+ f"revert failed for {ctx.test_file} after {reason} — the "
564
+ "working tree is left in an unknown state (mutated test "
565
+ "content may still be on disk, uncommitted)"
566
+ )
567
+
568
+
569
+ def _verify_and_commit(
570
+ round_num: int,
571
+ source_file: str,
572
+ survivor_count: int,
573
+ new_tests: str,
574
+ ctx: RunContext,
575
+ ) -> int | None:
576
+ """Compile-check → scoped pytest → commit-on-green / revert-on-failure.
577
+
578
+ Returns ``survivor_count`` on a successful commit, or ``None`` when the
579
+ round is abandoned (compile failure, test failure, or commit failure —
580
+ each reverted via :func:`_revert_or_raise`, which raises if the revert
581
+ itself fails).
582
+ """
583
+ if not python_compiles(ctx.test_file, cwd=ctx.cwd):
584
+ ctx.log(" compile check failed — reverting")
585
+ _revert_or_raise(ctx, "a failed compile check")
586
+ return None
587
+ if not run_scoped_pytest(ctx.test_file, cwd=ctx.cwd):
588
+ ctx.log(" tests failed — reverting")
589
+ _revert_or_raise(ctx, "a failed test run")
590
+ return None
591
+
592
+ ctx.log(" green — committing")
593
+ label_override = (
594
+ ctx.label_override_provider() if ctx.label_override_provider is not None else None
595
+ )
596
+ committed = git_commit(
597
+ _commit_message(
598
+ round_num,
599
+ source_file,
600
+ survivor_count,
601
+ new_tests,
602
+ generator_label=ctx.generator_label,
603
+ label_override=label_override,
604
+ ),
605
+ ctx.test_file,
606
+ cwd=ctx.cwd,
607
+ )
608
+ if not committed:
609
+ # A failed commit is a round failure, not a silent success —
610
+ # without this check the loop would advance believing this
611
+ # round landed, while the new tests sit uncommitted (and
612
+ # possibly still staged) on disk (#1598).
613
+ ctx.log(" commit failed — reverting")
614
+ _revert_or_raise(ctx, "a failed commit", after_commit=True)
615
+ return None
616
+ return survivor_count
617
+
618
+
619
+ def _run_round(
620
+ round_num: int,
621
+ source_file: str,
622
+ ctx: RunContext,
623
+ generate: Generator,
624
+ *,
625
+ prev_survivor_count: int | None,
626
+ ) -> int | None:
627
+ """Run one round: score (via :func:`_score_round`) → generate → insert →
628
+ verify → commit (via :func:`_verify_and_commit`).
629
+
630
+ Returns this round's survivor count (to seed the next round's
631
+ no-improvement check), or ``None`` when the file is done.
632
+ """
633
+ scored = _score_round(round_num, source_file, ctx, prev_survivor_count=prev_survivor_count)
634
+ if scored is None:
635
+ return None
636
+ survivors, survivor_count = scored
637
+
638
+ # Read once and thread the text through the generation prompt below —
639
+ # apply_generated_tests reads the file again, fresh, immediately before
640
+ # its own duplicate check and write (see its docstring): using a
641
+ # pre-generation snapshot there would widen a microseconds-wide
642
+ # read-before-write race into a multi-minute one, since generate() is an
643
+ # LLM call that can run for minutes (#1598/#1584 review).
644
+ test_text = ctx.test_file.read_text(encoding="utf-8")
645
+ new_tests = generate(
646
+ source_file,
647
+ survivors,
648
+ ctx.source_path.read_text(encoding="utf-8"),
649
+ test_text,
650
+ )
651
+
652
+ outcome = apply_generated_tests(ctx.test_file, new_tests)
653
+ if not outcome.inserted:
654
+ ctx.log(f" not inserted ({outcome.reason}) — stopping")
655
+ return None
656
+
657
+ return _verify_and_commit(round_num, source_file, survivor_count, new_tests, ctx)
658
+
659
+
660
+ def run_for_file(
661
+ source_file: str,
662
+ ctx: RunContext,
663
+ *,
664
+ generate: Generator,
665
+ max_rounds: int = 5,
666
+ ) -> None:
667
+ """Drive the deterministic survivor-kill loop for one Python source file.
668
+
669
+ ``generate`` is the sole non-deterministic step: given survivors +
670
+ context it returns the raw new-test text. Everything else — scoped run,
671
+ scoring, duplicate/insert guards, compile/test verification,
672
+ revert-on-failure, commit-on-green, and the no-improvement stop — is
673
+ mechanical, driven one round at a time by :func:`_run_round` — mirroring
674
+ :func:`mutation_kill_loop.run_for_file`'s contract exactly.
675
+
676
+ A failed revert (after a compile failure, a test failure, a failed
677
+ commit, or a failed mutmut cleanup revert inside
678
+ :func:`run_scoped_mutmut`) is fatal: it raises
679
+ :class:`mutation_kill_shared.RevertFailed` rather than returning
680
+ silently, because a revert that can't be verified as having succeeded
681
+ means the working tree is left in an unknown, possibly-mutated state
682
+ (#1598). A failed commit itself is also a round failure, not a silent
683
+ success: it is reverted (unstage + restore, via
684
+ :func:`git_reset_and_revert`) and the round stops without advancing.
685
+ """
686
+ prev_survivor_count: int | None = None
687
+ for round_num in range(1, max_rounds + 1):
688
+ prev_survivor_count = _run_round(
689
+ round_num, source_file, ctx, generate, prev_survivor_count=prev_survivor_count
690
+ )
691
+ if prev_survivor_count is None:
692
+ return
693
+
694
+
695
+ # =============================================================================
696
+ # Headless generation — shell to `claude --print` for unattended runs.
697
+ # =============================================================================
698
+ def build_survivor_summary(survivors: list[dict], *, limit: int = 40) -> str:
699
+ """Render surviving mutants as a compact list."""
700
+ lines = []
701
+ for mutant in survivors[:limit]:
702
+ line = mutant.get("location", {}).get("start", {}).get("line", "?")
703
+ lines.append(f"- L{line}")
704
+ if len(survivors) > limit:
705
+ lines.append(f"- … and {len(survivors) - limit} more")
706
+ return "\n".join(lines)
707
+
708
+
709
+ def build_generation_prompt(
710
+ source_file: str,
711
+ survivors: list[dict],
712
+ source_text: str,
713
+ test_text: str,
714
+ *,
715
+ source_limit: int = 8000,
716
+ ) -> str:
717
+ """Build the generation prompt.
718
+
719
+ The existing test file is the *only* pattern — assertion style and
720
+ fixture usage are inferred from it, never hardcoded here (mirrors
721
+ ``mutation_kill_headless.build_generation_prompt``, adapted for pytest's flat
722
+ ``def test_*():`` convention rather than a class/namespace-wrapped one).
723
+ """
724
+ return (
725
+ f"You are adding new pytest test functions that KILL surviving "
726
+ f"mutations in {source_file}.\n\n"
727
+ "Match the existing test file exactly: its imports, assertion style "
728
+ "(plain `assert`, pytest.approx, monkeypatch, etc.), fixtures, and "
729
+ "naming conventions are the pattern to follow. Do not introduce any "
730
+ "library, helper, or convention that does not already appear in it.\n\n"
731
+ f"## Surviving mutations ({len(survivors)})\n"
732
+ f"{build_survivor_summary(survivors)}\n\n"
733
+ f"## Source under test\n{source_text[:source_limit]}\n\n"
734
+ f"## Existing test file (the pattern to match)\n{test_text}\n\n"
735
+ "## Rules\n"
736
+ "1. Return ONLY the new top-level `def test_*():` function(s) — no "
737
+ "class wrapper, no imports, no module-level fixtures.\n"
738
+ "2. Each must run against the helpers/fixtures already in the "
739
+ "existing test file.\n"
740
+ "3. Reuse the existing file's assertion and fixture patterns exactly.\n"
741
+ "4. Match the existing naming convention.\n"
742
+ "5. Do not redeclare fixtures or helpers already present.\n"
743
+ "6. Every assertion must check a specific value — not just that a "
744
+ "call didn't raise.\n"
745
+ )
746
+
747
+
748
+ def make_headless_generator(
749
+ model: str | None = None,
750
+ *,
751
+ cwd: Path | None = None,
752
+ log: Callable[[str], None] = print,
753
+ on_downgrade: Callable[[DowngradeEvent], None] | None = None,
754
+ sleep: Callable[[float], None] = time.sleep,
755
+ ) -> Generator:
756
+ """Return a :data:`Generator` that shells to ``claude --print``.
757
+
758
+ Builds the Python-flavored prompt above, then delegates everything else
759
+ to :func:`mutation_kill_retry.make_retrying_headless_call` (#1908) —
760
+ the 3-consecutive-gateway-class-failures/1-same-model-retry/at-most-
761
+ once-per-file-downgrade wrapper around
762
+ :func:`mutation_kill_shared.run_claude_headless` (#1583, relocated from
763
+ ``mutation_kill_headless`` in #1601).
764
+
765
+ The retry/downgrade state (consecutive-failure counter, model in use,
766
+ whether this file already spent its one downgrade) lives in the
767
+ ``retrying_call`` closure below — constructed once per file, here, never
768
+ at module scope — so a new file's generator always starts fresh at the
769
+ top of the ladder regardless of a prior file's downgrade, and concurrent
770
+ files under ``--all --concurrency`` (each with their own closure) never
771
+ leak state to one another. ``round_num`` is derived from how many times
772
+ THIS closure has been invoked (``_run_round`` calls ``generate`` once per
773
+ round), since the shared :data:`Generator` signature carries no round
774
+ number of its own.
775
+
776
+ ``on_downgrade``, when given, is passed straight through to
777
+ :func:`mutation_kill_retry.make_retrying_headless_call`. Building the
778
+ ``on_downgrade``/``get_label_override`` audit-trail pair
779
+ (:func:`mutation_kill_retry.make_downgrade_audit_hook`) is this
780
+ module's own ``main()``'s job now (#1908 review) — this function no
781
+ longer constructs one internally or attaches a
782
+ ``label_override_provider`` attribute to the returned ``generate``;
783
+ ``main()`` has ``get_label_override`` directly in scope and wires it
784
+ into :class:`RunContext` itself, so no attribute-smuggling is needed.
785
+ """
786
+ retrying_call = make_retrying_headless_call(
787
+ initial_model=model, cwd=cwd, log=log, on_downgrade=on_downgrade, sleep=sleep
788
+ )
789
+ round_counter = {"n": 0}
790
+
791
+ def generate(
792
+ source_file: str,
793
+ survivors: list[dict],
794
+ source_text: str,
795
+ test_text: str,
796
+ ) -> str:
797
+ round_counter["n"] += 1
798
+ prompt = build_generation_prompt(source_file, survivors, source_text, test_text)
799
+ return retrying_call(prompt, source_file, round_counter["n"])
800
+
801
+ return generate
802
+
803
+
804
+ # =============================================================================
805
+ # CLI — startup preflight + --headless generation.
806
+ # =============================================================================
807
+ def parse_args(argv: Sequence[str]) -> argparse.Namespace:
808
+ p = argparse.ArgumentParser(
809
+ prog="mutation_kill_loop_python.py",
810
+ description=(
811
+ "Deterministic survivor-kill loop for Python/mutmut. Agent-driven "
812
+ "by default; --headless enables unattended generation via the "
813
+ "Claude CLI."
814
+ ),
815
+ )
816
+ p.add_argument("--file", required=False, help="Source file to target")
817
+ p.add_argument(
818
+ "--test-command",
819
+ default=None,
820
+ help="Scoped pytest command mutmut runs per mutant (required)",
821
+ )
822
+ p.add_argument("--max-rounds", type=int, default=5, help="Max rounds per file")
823
+ p.add_argument(
824
+ "--headless",
825
+ action="store_true",
826
+ help="Unattended generation via `claude --print` (CI runs).",
827
+ )
828
+ p.add_argument(
829
+ "--model",
830
+ help=(
831
+ "Generation model for --headless. Default: DEV_TEAM_MUTATION_MODEL "
832
+ "env var, else omitted so `claude --print` uses its own default."
833
+ ),
834
+ )
835
+ p.add_argument("--test-file", help="Test file to extend (required with --headless)")
836
+ p.add_argument("--source-path", help="Source file under test (required with --headless)")
837
+ p.add_argument(
838
+ "--target-honest-score",
839
+ type=float,
840
+ default=None,
841
+ help=(
842
+ "Phase-0 mutation target (percent). Stop a file once its honest "
843
+ "score reaches this, since work past the threshold cannot change "
844
+ "the Phase-8 verdict. Default off — unset reproduces pre-#2030 "
845
+ "behavior exactly."
846
+ ),
847
+ )
848
+ p.add_argument(
849
+ "--min-kills-per-round",
850
+ type=float,
851
+ default=None,
852
+ help=(
853
+ "Marginal-yield floor. >=1 is an absolute kill count; 0<v<1 is a "
854
+ "fraction of the round's starting survivors. A round below the "
855
+ "floor while still under target is surfaced to the operator "
856
+ "([c]ontinue / [r]etry / [w]aive / [q]uit), never stopped "
857
+ "silently. Default off."
858
+ ),
859
+ )
860
+ return p.parse_args(list(argv))
861
+
862
+
863
+ def main(argv: Sequence[str] | None = None) -> int:
864
+ """CLI entry point — see :func:`mutation_kill_headless.main` for the contract
865
+ this mirrors."""
866
+ argv = list(sys.argv[1:] if argv is None else argv)
867
+ args = parse_args(argv)
868
+
869
+ if not args.headless:
870
+ sys.stderr.write(f"error: {NO_GENERATOR_MESSAGE}\n")
871
+ return 1
872
+
873
+ model = resolve_model(args.model)
874
+
875
+ if not claude_cli_available():
876
+ sys.stderr.write(f"error: {MISSING_CLAUDE_MESSAGE}\n")
877
+ return 3
878
+
879
+ if not (args.file and args.test_file and args.source_path and args.test_command):
880
+ sys.stderr.write(
881
+ "error: --headless requires --file, --test-file, --source-path, "
882
+ "and --test-command\n"
883
+ )
884
+ return 2
885
+
886
+ on_downgrade, get_label_override = make_downgrade_audit_hook()
887
+ generate = make_headless_generator(model, on_downgrade=on_downgrade)
888
+ try:
889
+ run_for_file(
890
+ args.file,
891
+ RunContext(
892
+ test_file=Path(args.test_file),
893
+ source_path=Path(args.source_path),
894
+ test_command=args.test_command,
895
+ generator_label=f"headless ({model or 'default'})",
896
+ label_override_provider=get_label_override,
897
+ target_honest_score=args.target_honest_score,
898
+ min_kills_per_round=args.min_kills_per_round,
899
+ ),
900
+ generate=generate,
901
+ max_rounds=args.max_rounds,
902
+ )
903
+ except GenerationExhausted as exc:
904
+ # This file's retry-then-downgrade budget is fully spent (3
905
+ # consecutive gateway-class failures + 1 same-model retry, at the
906
+ # original model AND at most one fallback tier) — distinct from
907
+ # RevertFailed below (exit 4, working tree possibly mutated) and
908
+ # from the generic RuntimeError case below it (exit 5, clean but
909
+ # not exhausted — e.g. a non-gateway-class generation timeout). A
910
+ # clean exhaustion mutates nothing in the paths this covers:
911
+ # generation precedes insertion within a round, and a prior round's
912
+ # own insertion-revert failure (compile/test/commit paths, via
913
+ # _revert_or_raise) is itself fatal — raised as RevertFailed, never
914
+ # swallowed. run_scoped_mutmut's post-mutmut-crash cleanup revert
915
+ # (its own ``finally``) is also checked (#1928/#1939) and raises
916
+ # RevertFailed on its own failure. What isn't independently
917
+ # re-verified here is that a revert git reports as successful
918
+ # actually left the tree clean (#1955) — so callers
919
+ # (stryker_shard_pipeline.py's shard driver) can log this file as
920
+ # unfixed and continue to the next file without affecting the run's
921
+ # exit status, instead of aborting the whole shard (#1908 review).
922
+ sys.stderr.write(f"error: {exc}\n")
923
+ return EXIT_GENERATION_EXHAUSTED
924
+ except mutation_kill_shared.RevertFailed as exc:
925
+ # A failed revert (or a failed-commit round-abandonment's own
926
+ # revert) leaves the working tree in an unknown, possibly-mutated
927
+ # state (#1930) — narrower and more urgent than the generic
928
+ # RuntimeError case below: this is the only case that can't be
929
+ # trusted as clean.
930
+ sys.stderr.write(f"error: {exc}\n")
931
+ return EXIT_REVERT_FAILED
932
+ except RuntimeError as exc:
933
+ # Every other RuntimeError this loop raises (a mutmut-run timeout,
934
+ # a mutmut-start/junitxml-extraction failure, etc.) is clean:
935
+ # run_scoped_mutmut's cleanup-revert gap is closed (#1928/#1939) —
936
+ # a failed cleanup revert now raises RevertFailed instead of being
937
+ # silently discarded, so reaching this branch means the cleanup
938
+ # revert itself succeeded, same as the GenerationExhausted case
939
+ # above. Not a retry-budget exhaustion — reuses exit 5 (#1956: this
940
+ # is the OUTCOME class, not a specific cause) because the shard
941
+ # driver only distinguishes "fatal, stop" (4) from "clean, continue"
942
+ # (5), not why a file wasn't fixed.
943
+ sys.stderr.write(f"error: {exc} — generation failed cleanly, continuing\n")
944
+ return EXIT_GENERATION_EXHAUSTED
945
+ return 0
946
+
947
+
948
+ if __name__ == "__main__":
949
+ sys.exit(main())