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,958 @@
1
+ #!/usr/bin/env python3
2
+ """Mechanical pre-phase for `agents/test-review.md` (#2169 Step 2.2).
3
+
4
+ `test-review.md` (Step 2.1, #2169) annotates each of its own `## Detect`
5
+ checks as `[MECHANICAL]` (an explicit per-language detection signature or
6
+ grep/threshold rule) or `[JUDGMENT]` (requires semantic reasoning about
7
+ intent). This script computes every `[MECHANICAL]` check that is cheap to
8
+ run as a script, for one test file at a time, so the agent cites raw counts
9
+ instead of re-deriving them in prose:
10
+
11
+ - tests with no assertion call — `error` (matches test-review.md's Severity
12
+ Anchors table)
13
+ - missing `await` on async test bodies (JS/TS/C#/Java) — `warning`
14
+ - mocks/stubs created without a reset/clear call, or a same-shaped
15
+ re-instantiation/re-initialization in `[SetUp]`/`[TestInitialize]`/
16
+ `@BeforeEach`/`@BeforeAll`, in the same file — `warning`
17
+ - unstubbed clock/RNG/timer access (JS/TS/C#/Java), suppressed when a
18
+ fake-timer/injected-clock marker (`jest.useFakeTimers()`, an injected
19
+ `IClock`/`TimeProvider`, `Clock.fixed`, ...) is present in the same
20
+ file — `warning`
21
+ - reflection into private members as primary test strategy (Java/C#/
22
+ Python/JS/TS) — `warning`, NOT promoted to `error` by this migration
23
+ - the Tolerated-Deviation Hunt's >=3-artifact consolidation rule, with a
24
+ cheap same-line/adjacent-line ticket-reference qualifier suppressing the
25
+ disabled-test/aged-marker/suppressed-warning categories — `warning`
26
+
27
+ Test-region boundaries (the `it()`/`test()` call for JS/TS, the annotated
28
+ method for C#/Java) are found by depth-counting over a MASKED copy of the
29
+ file (`_mask_code`) with string/template/char literals and comments blanked
30
+ out first — a stray `)`/`(`/`{`/`}` inside a title, comment, or string body
31
+ can no longer early-close a region or drive the balance past EOF. For JS/TS,
32
+ the no-assertion and missing-await checks additionally never see the
33
+ description-string argument itself — `_js_content_slice` derives a
34
+ callback-only CONTENT slice from the boundary-only region, so a title like
35
+ `'should render'` or `'awaits the response'` can never satisfy the
36
+ assertion/await keyword search on its own words (see `_GATING_CATEGORIES`
37
+ below for why this specifically matters: no-assertion is one of the two
38
+ categories that sets `mechanicalFail`).
39
+
40
+ Internal-collaborator-doubling detection is NOT reimplemented here — it is
41
+ reused via subprocess against `skills/test-design/scripts/
42
+ internal_double_detector.py`, whose own docstring documents `--files` as
43
+ the seam built for exactly this kind of caller (the subprocess call itself
44
+ goes through an injectable `double_detector_runner` parameter so unit tests
45
+ for unrelated checks can stub it out rather than paying real spawn cost).
46
+ That detector has no `severity` field; its findings carry a `verdict` of
47
+ `"high"`, `"informational"`, or `"advisory"`. Only `verdict == "high"` is
48
+ translated here into this script's own `error`-severity finding — never a
49
+ literal `severity == "error"` check against the reused script's raw JSON,
50
+ since no such field exists there.
51
+
52
+ Two disclosure gaps are surfaced explicitly rather than failing open to a
53
+ plain clean bill:
54
+
55
+ - `internal_double_detector.py`'s own `analyze()` only scans files under a
56
+ `test`/`tests` path segment — narrower than this repo's own test-file
57
+ conventions (`*.test.*`, `*.spec.*`, `__tests__/`, see
58
+ `knowledge/test-file-indicators.md`). When the analyzed file's path has
59
+ no `test`/`tests` segment, the doubling check never runs for it; this
60
+ script emits a named `internal-collaborator-doubling-out-of-scope`
61
+ finding instead of silently reporting zero doubling findings.
62
+ - A subprocess spawn/timeout failure or invalid-JSON response from the
63
+ detector is reported as `internal-collaborator-doubling-unavailable` at
64
+ `warning` severity (raised from `suggestion` — a run that never happened
65
+ must not read the same as a clean pass).
66
+
67
+ Both gaps also set the top-level `doublingCheckRan: false` key so a
68
+ downstream consumer can distinguish "mechanically clean except the doubling
69
+ gate, which did not run" from a plain clean bill.
70
+
71
+ `mechanicalFail` is `true` ONLY when a no-assertion-test finding is present
72
+ OR a translated `internal-collaborator-doubling` `error` finding is
73
+ present. Every other finding this script emits is `warning`-severity: it is
74
+ reported, with counts, but never gates the qualitative pass on its own.
75
+
76
+ A file this script cannot decode/parse produces a single `parse-failure`
77
+ finding (no severity that participates in `mechanicalFail`) and falls
78
+ through as mechanically clean — this script never crashes and never
79
+ silently drops a file.
80
+
81
+ This script does NOT own an ambient-API marker table of its own for
82
+ `internal_double_detector.py`'s B2-waiver-evidence purpose — that table
83
+ (`_AMBIENT_API_MARKERS` in that module) stays there; see the REFACTOR note
84
+ at `_CLOCK_RNG_TIMER_MARKERS` below for why this script's own marker table
85
+ is a different list, not a duplicate.
86
+
87
+ The Tolerated-Deviation Hunt (`_check_tolerated_deviation`) runs against
88
+ whichever single file the CLI is given, test or non-test — this script's
89
+ own per-file CLI shape is unopinionated about which files it's called
90
+ with. Step 2.3's actual wiring (`skills/code-review/SKILL.md` step 2b)
91
+ calls this script only for test files in scope, so in practice the Hunt
92
+ currently sees test files, not the "core-flow, non-test source" scope
93
+ `test-review.md`'s prose describes — non-test files get no Phase 0 result
94
+ and fall through to that agent's own manual-judgment fallback. Extending
95
+ the wiring to also cover non-test core-flow files is a separate change,
96
+ tracked at the wiring layer (`test-review.md`'s Hunt section), not here.
97
+
98
+ Stdlib-only (ADR 0014/0015). See docs/python-hook-contract.md.
99
+ """
100
+
101
+ from __future__ import annotations
102
+
103
+ import argparse
104
+ import json
105
+ import re
106
+ import subprocess
107
+ import sys
108
+ from collections.abc import Callable
109
+ from pathlib import Path
110
+
111
+ #: `internal_double_detector.py` lives under `skills/test-design/scripts/`;
112
+ #: this script lives under `scripts/` — both are children of the plugin
113
+ #: root (`plugins/dev-team/`), so `parents[1]` from this file reaches it.
114
+ _DETECTOR_PATH = (
115
+ Path(__file__).resolve().parents[1]
116
+ / "skills"
117
+ / "test-design"
118
+ / "scripts"
119
+ / "internal_double_detector.py"
120
+ )
121
+
122
+ LANG_BY_EXT: dict[str, str] = {
123
+ ".py": "python",
124
+ ".js": "js_ts",
125
+ ".jsx": "js_ts",
126
+ ".mjs": "js_ts",
127
+ ".cjs": "js_ts",
128
+ ".ts": "js_ts",
129
+ ".tsx": "js_ts",
130
+ ".cs": "csharp",
131
+ ".java": "java",
132
+ }
133
+
134
+ CONSOLIDATION_THRESHOLD = 3
135
+
136
+ #: "No specific line available" sentinel — used for parse-failure/decode
137
+ #: findings and translated-detector findings with no line info of their own.
138
+ _UNKNOWN_LINE = 1
139
+
140
+ #: Passed as `timeout=` to the `internal_double_detector.py` subprocess call.
141
+ _DOUBLE_DETECTOR_TIMEOUT_SECONDS = 60
142
+
143
+ #: `test`/`tests` path-segment names — mirrors `internal_double_detector.py`'s
144
+ #: own `_TEST_DIR_NAMES`/`_is_test_tree` scoping exactly (this is a
145
+ #: disclosure check on top of that scoping, not a reimplementation of the
146
+ #: detector itself — see the module docstring's out-of-scope paragraph).
147
+ _TEST_DIR_NAMES = frozenset({"test", "tests"})
148
+
149
+ # --- Generic assertion-call detector -----------------------------------------
150
+
151
+ #: "no Assert, expect, should, verify, or equivalent assertion call" —
152
+ #: test-review.md's own generic (language-agnostic) wording for the
153
+ #: no-assertion check.
154
+ _ASSERTION_RE = re.compile(r"\b(assert\w*|expect|should\w*|verify\w*)\b", re.IGNORECASE)
155
+
156
+
157
+ # --- Test-region extraction (brace/paren-balanced, per language) ------------
158
+
159
+ #: Matches a plain `it(`/`test(` call (with optional `.only`/`.skip`) as
160
+ #: well as an `it.each(<table>)(`/`test.each(<table>)(` parameterized-table
161
+ #: call — group 1 always captures the FINAL call's own opening paren (the
162
+ #: description+callback call), never the `.each` table's paren, so
163
+ #: extraction downstream is identical for both forms. The `.each(...)`
164
+ #: table itself may contain one level of nested parens (e.g. a function
165
+ #: call inside the table) but no more — a reasonable approximation of the
166
+ #: common forms, not a full parser. The leading `(?<![.\w$])` (not a plain
167
+ #: `\b`) excludes member-access calls like `pattern.test(value)` or
168
+ #: `/re/.test(s)` — ordinary RegExp usage, not a test declaration. A `\b`
169
+ #: alone still matches there, because `.` and `t` sit on either side of a
170
+ #: word boundary regardless of what the non-word character is.
171
+ _JS_TEST_CALL_RE = re.compile(
172
+ r"(?<![.\w$])(?:it|test)(?:\.only|\.skip)?"
173
+ r"(?:\.each\s*\((?:[^()]|\([^()]*\))*\))?"
174
+ r"\s*(\()"
175
+ )
176
+ _JS_ASYNC_CALLBACK_RE = re.compile(r"^\(\s*['\"`][^'\"`]*['\"`]\s*,\s*async\b")
177
+
178
+ _CSHARP_TEST_ATTR_RE = re.compile(
179
+ r"^[ \t]*\[(?:Test|Fact|TestMethod|Theory|TestCase|TestCaseSource)(?:\([^\]]*\))?\]",
180
+ re.MULTILINE,
181
+ )
182
+ _JAVA_TEST_ANNOT_RE = re.compile(r"^[ \t]*@(?:Test|ParameterizedTest)\b", re.MULTILINE)
183
+ _PY_TEST_DEF_RE = re.compile(r"^([ \t]*)(?:async\s+)?def\s+(test_\w+)\s*\(", re.MULTILINE)
184
+
185
+ #: Setup/init method markers — used only by `_mock_reinitialized` (Fix 4) to
186
+ #: find a `[SetUp]`/`[TestInitialize]`/`@BeforeEach`/`@BeforeAll` method's
187
+ #: own body, never treated as a test method for any other check.
188
+ _CSHARP_SETUP_ATTR_RE = re.compile(r"^[ \t]*\[(?:SetUp|TestInitialize)\]", re.MULTILINE)
189
+ _JAVA_SETUP_ANNOT_RE = re.compile(r"^[ \t]*@(?:BeforeEach|BeforeAll)\b", re.MULTILINE)
190
+ _SETUP_ANNOT_RE: dict[str, re.Pattern] = {"csharp": _CSHARP_SETUP_ATTR_RE, "java": _JAVA_SETUP_ANNOT_RE}
191
+
192
+ _CSHARP_ASYNC_TASK_RE = re.compile(r"\basync\s+Task\b")
193
+ _JAVA_FUTURE_TYPE_RE = re.compile(r"\b(?:Future|CompletableFuture)\b")
194
+ _JAVA_FUTURE_RESOLVED_RE = re.compile(r"\.(?:get|join)\s*\(")
195
+
196
+
197
+ class ParseFailure(Exception):
198
+ """A test-region boundary (parens/braces) never closed before EOF.
199
+
200
+ Raised internally by the region-extraction helpers and caught once, at
201
+ the top of `analyze_file`, which turns it into a `parse-failure`
202
+ finding — this exception must never propagate out of `analyze_file`."""
203
+
204
+
205
+ def _line_no(text: str, idx: int) -> int:
206
+ return text.count("\n", 0, idx) + 1
207
+
208
+
209
+ def _consume_escape(out: list[str], text: str, j: int) -> int:
210
+ """Blank a backslash-escape pair (`text[j:j + 2]`, `text[j] == "\\\\"`)
211
+ in `out`, one character at a time so an embedded newline is preserved
212
+ (never blanked) and line numbers stay aligned with `text`. Returns the
213
+ index just past the pair. Split out of `_mask_code`'s quoted-string
214
+ branch to keep that branch's `while` body at one level of nesting."""
215
+ if text[j] != "\n":
216
+ out[j] = " "
217
+ if text[j + 1] != "\n":
218
+ out[j + 1] = " "
219
+ return j + 2
220
+
221
+
222
+ def _mask_code(text: str) -> str:
223
+ """Same-length copy of `text` with string/template/char literals and
224
+ `//`/`/* */` comments blanked to spaces (newlines preserved), so
225
+ paren/brace depth-counting and top-level-comma scanning never trip on
226
+ a stray `)`/`(`/`{`/`}`/`,` sitting inside a title, comment, or string
227
+ body (Fix 1). Character offsets and line numbers are identical to
228
+ `text` — callers index into the ORIGINAL text using positions computed
229
+ against this masked copy.
230
+
231
+ Limitation: a JS/TS template literal's `${...}` interpolation is
232
+ masked along with the rest of the template, not treated as live code —
233
+ blanket-masking the whole template is the safer default (a stray brace
234
+ inside an interpolation could otherwise miscount), at the cost of not
235
+ recognizing real code inside `${}`. Out of scope for this fix pass."""
236
+ out = list(text)
237
+ i, n = 0, len(text)
238
+ while i < n:
239
+ two = text[i : i + 2]
240
+ if two == "//":
241
+ j = i
242
+ while j < n and text[j] != "\n":
243
+ out[j] = " "
244
+ j += 1
245
+ i = j
246
+ elif two == "/*":
247
+ end = text.find("*/", i + 2)
248
+ j_end = end + 2 if end != -1 else n
249
+ for j in range(i, j_end):
250
+ if text[j] != "\n":
251
+ out[j] = " "
252
+ i = j_end
253
+ elif text[i] in ("'", '"', "`"):
254
+ quote = text[i]
255
+ out[i] = " "
256
+ j = i + 1
257
+ while j < n:
258
+ if text[j] == "\\" and j + 1 < n:
259
+ j = _consume_escape(out, text, j)
260
+ continue
261
+ closing = text[j] == quote
262
+ if text[j] != "\n":
263
+ out[j] = " "
264
+ j += 1
265
+ if closing:
266
+ break
267
+ i = j
268
+ else:
269
+ i += 1
270
+ return "".join(out)
271
+
272
+
273
+ def _matching_close_index(text: str, masked: str, open_idx: int, open_ch: str, close_ch: str) -> int | None:
274
+ """Index into `text` of the character matching `text[open_idx]` (which
275
+ must be `open_ch`), found by depth-counting over `masked` (same length
276
+ as `text`, string/template/char literals and comments already blanked
277
+ there — Fix 1). Returns `None` if depth never returns to 0 before EOF."""
278
+ depth = 0
279
+ i = open_idx
280
+ n = len(masked)
281
+ while i < n:
282
+ c = masked[i]
283
+ if c == open_ch:
284
+ depth += 1
285
+ elif c == close_ch:
286
+ depth -= 1
287
+ if depth == 0:
288
+ return i
289
+ i += 1
290
+ return None
291
+
292
+
293
+ def _js_content_slice(region: str, masked_region: str) -> str:
294
+ """Slice of `region` (a full `it()`/`test()` call region, its own
295
+ parens included) starting just after the description-string
296
+ argument's closing quote and the following top-level comma — the
297
+ callback argument only, title/description stripped out (Fix 1:
298
+ `_check_no_assertion`/`_check_missing_await` must never match a
299
+ keyword sitting in the title itself, e.g. `'should render'` or
300
+ `'awaits the response'`). Falls back to the full `region` when no
301
+ leading string-literal argument is found (e.g. an `it(name, fn)` form
302
+ using a variable title) or no top-level comma follows it."""
303
+ n = len(region)
304
+ j = 1
305
+ while j < n and region[j] in " \t\r\n":
306
+ j += 1
307
+ if j >= n or region[j] not in "'\"`":
308
+ return region
309
+ quote = region[j]
310
+ end = j + 1
311
+ closed = False
312
+ while end < n:
313
+ if region[end] == "\\" and end + 1 < n:
314
+ end += 2
315
+ continue
316
+ if region[end] == quote:
317
+ end += 1
318
+ closed = True
319
+ break
320
+ end += 1
321
+ if not closed:
322
+ return region
323
+ depth = 0
324
+ k = end
325
+ while k < n:
326
+ c = masked_region[k]
327
+ if c in "([{":
328
+ depth += 1
329
+ elif c in ")]}":
330
+ if depth == 0:
331
+ return region
332
+ depth -= 1
333
+ elif c == "," and depth == 0:
334
+ return region[k + 1 :]
335
+ k += 1
336
+ return region
337
+
338
+
339
+ def _js_ts_test_regions(text: str, masked: str) -> list[dict]:
340
+ """`{"line", "is_async", "content"}` for each `it()`/`test()` call
341
+ (including `.each` parameterized-table forms) — shared by
342
+ `_check_no_assertion` and `_check_missing_await` (Fix 11) so the
343
+ region/content boundaries are computed exactly once per file.
344
+ `is_async` is read off the FULL region (it needs the title text
345
+ structurally present to match); `content` is the title-stripped
346
+ slice used for keyword search."""
347
+ out = []
348
+ for m in _JS_TEST_CALL_RE.finditer(masked):
349
+ open_idx = m.start(1)
350
+ close_idx = _matching_close_index(text, masked, open_idx, "(", ")")
351
+ if close_idx is None:
352
+ raise ParseFailure("unbalanced parens in an it()/test() call")
353
+ region = text[open_idx : close_idx + 1]
354
+ masked_region = masked[open_idx : close_idx + 1]
355
+ is_async = bool(_JS_ASYNC_CALLBACK_RE.match(region))
356
+ content = _js_content_slice(region, masked_region)
357
+ out.append({"line": _line_no(text, m.start()), "is_async": is_async, "content": content})
358
+ return out
359
+
360
+
361
+ def _annotated_test_regions(text: str, masked: str, annot_re: re.Pattern) -> list[dict]:
362
+ """`{"line", "signature", "body"}` for each method matching `annot_re`
363
+ (a `[Test]`/`@Test`-style attribute/annotation, or a
364
+ `[SetUp]`/`@BeforeEach`-style one when reused by `_mock_reinitialized`)
365
+ — `signature` is the annotation through the parameter list, `body` is
366
+ the brace-balanced method body. Boundaries are found via `masked`
367
+ (Fix 1) so a stray brace/paren inside a body's own string literal can't
368
+ drive the balance past EOF."""
369
+ out = []
370
+ for m in annot_re.finditer(masked):
371
+ paren_idx = masked.find("(", m.end())
372
+ if paren_idx == -1:
373
+ raise ParseFailure("no parameter list found after a test attribute")
374
+ close_paren = _matching_close_index(text, masked, paren_idx, "(", ")")
375
+ if close_paren is None:
376
+ raise ParseFailure("unbalanced parens in an annotated test method's signature")
377
+ brace_idx = masked.find("{", close_paren)
378
+ if brace_idx == -1:
379
+ raise ParseFailure("no method body found after a test attribute")
380
+ close_brace = _matching_close_index(text, masked, brace_idx, "{", "}")
381
+ if close_brace is None:
382
+ raise ParseFailure("unbalanced braces in an annotated test method's body")
383
+ signature = text[m.start() : brace_idx]
384
+ body = text[brace_idx : close_brace + 1]
385
+ out.append({"line": _line_no(text, m.start()), "signature": signature, "body": body})
386
+ return out
387
+
388
+
389
+ def _extract_regions(text: str, masked: str, lang: str) -> list[dict]:
390
+ """Single per-language test-region extraction pass, shared by
391
+ `_check_no_assertion` and `_check_missing_await` (Fix 11)."""
392
+ if lang == "js_ts":
393
+ return _js_ts_test_regions(text, masked)
394
+ if lang == "csharp":
395
+ return _annotated_test_regions(text, masked, _CSHARP_TEST_ATTR_RE)
396
+ if lang == "java":
397
+ return _annotated_test_regions(text, masked, _JAVA_TEST_ANNOT_RE)
398
+ return []
399
+
400
+
401
+ def _python_test_regions(text: str, masked: str) -> list[tuple[int, str]]:
402
+ """`(line_no, body_text)` for each `def test_*(...)`/`async def
403
+ test_*(...)` — body is every line after the parameter list's closing
404
+ `)` line, indented deeper than the `def` line, up to the first dedent
405
+ or EOF (blank lines don't count as a dedent).
406
+
407
+ The closing paren is found by depth-counting over `masked` (like every
408
+ other region helper in this module) rather than assumed to be on the
409
+ `def` line itself — a black-formatted multi-line signature (`def
410
+ test_x(\\n tmp_path, cfg\\n):`) puts the body's first line right
411
+ after a `):` line whose OWN indent equals the `def` line's, which a
412
+ naive "next line" walk would misread as an immediate dedent and return
413
+ an empty body (a false no-assertion finding on a test that does
414
+ assert)."""
415
+ lines = text.split("\n")
416
+ regions = []
417
+ for m in _PY_TEST_DEF_RE.finditer(text):
418
+ indent = m.group(1)
419
+ open_idx = m.end() - 1 # the `(` the regex itself just matched
420
+ close_idx = _matching_close_index(text, masked, open_idx, "(", ")")
421
+ if close_idx is None:
422
+ raise ParseFailure("unbalanced parens in a def test_*(...) signature")
423
+ def_line_idx = text.count("\n", 0, m.start())
424
+ sig_close_line_idx = text.count("\n", 0, close_idx)
425
+ body_lines = []
426
+ for line in lines[sig_close_line_idx + 1 :]:
427
+ if line.strip() == "":
428
+ body_lines.append(line)
429
+ continue
430
+ cur_indent_len = len(line) - len(line.lstrip(" \t"))
431
+ if cur_indent_len <= len(indent):
432
+ break
433
+ body_lines.append(line)
434
+ regions.append((def_line_idx + 1, "\n".join(body_lines)))
435
+ return regions
436
+
437
+
438
+ # --- Per-language signature tables (checks c/d/e) ----------------------------
439
+
440
+ #: (a) no-assertion and (c) mock-not-reset are file/test-scanned per
441
+ #: language; JS/TS/C#/Java cover (a)+(b), Python covers (a) only —
442
+ #: test-review.md documents no missing-await/mock-reset/clock-RNG-timer
443
+ #: signatures for Python.
444
+
445
+ _MOCK_CONSTRUCT_MARKERS: dict[str, tuple[re.Pattern, ...]] = {
446
+ "js_ts": (re.compile(r"\bjest\.fn\s*\("), re.compile(r"\bjest\.mock\s*\(")),
447
+ "csharp": (re.compile(r"\bMock<"), re.compile(r"\bSubstitute\.For<")),
448
+ "java": (re.compile(r"\bMockito\.mock\s*\("), re.compile(r"@Mock\b")),
449
+ }
450
+ _MOCK_RESET_MARKERS: dict[str, re.Pattern] = {
451
+ "js_ts": re.compile(r"\bjest\.clearAllMocks\s*\("),
452
+ "csharp": re.compile(r"\.Reset\s*\(\)|\bClearReceivedCalls\s*\("),
453
+ # Mockito-scoped (Fix 4): the previous bare `\breset\s*\(` matched ANY
454
+ # method literally named `reset(` in the file, including production
455
+ # code under test, which could silently suppress a real finding. This
456
+ # narrower form misses an unqualified `reset()` call reached via
457
+ # `import static org.mockito.Mockito.reset;` — a deliberate, documented
458
+ # trade (see the finding this fixes: an over-broad marker in the wrong
459
+ # direction is worse than a narrow miss here).
460
+ "java": re.compile(r"\bMockito\.reset\s*\("),
461
+ }
462
+
463
+ #: Re-instantiation/re-initialization alternative to an explicit reset call
464
+ #: (Fix 4) — test-review.md documents BOTH forms ("Moq `Mock<T>` reused
465
+ #: without `Reset()` OR RE-INSTANTIATION"; "Mockito missing `reset()` OR
466
+ #: `@BeforeEach` RE-INITIALIZATION"), but `_MOCK_RESET_MARKERS` only ever
467
+ #: covered the explicit-call half. `_mock_reinitialized` checks whether a
468
+ #: `[SetUp]`/`[TestInitialize]`/`@BeforeEach`/`@BeforeAll` method's own body
469
+ #: contains a mock-construct marker — re-creating the mock counts the same
470
+ #: as resetting it. JS/TS has no re-instantiation alternative documented in
471
+ #: test-review.md (only `jest.clearAllMocks()`), so it has no entry here.
472
+
473
+ #: REFACTOR (Step 2.2): this table is data-shaped like
474
+ #: `internal_double_detector.py`'s `_AMBIENT_API_MARKERS`, but it is NOT the
475
+ #: same list and does not share a use case with it, so it is kept separate
476
+ #: rather than factored together — see that module's table for the
477
+ #: comparison and this file's own module docstring for the one-line
478
+ #: pointer. `_AMBIENT_API_MARKERS` answers "does this COLLABORATOR's own
479
+ #: declaring file reference ambient state" (evidence for a B2 double
480
+ #: waiver: env/hostname/cwd/locale included, no timer/interval markers).
481
+ #: This table answers "does this TEST body call an unstubbed clock/RNG/
482
+ #: timer API directly" (test-review.md's own non-determinism-sources
483
+ #: signatures: no env/hostname/cwd/locale, but adds setTimeout/
484
+ #: setInterval/setImmediate/Task.Delay/Thread.Sleep, which
485
+ #: `_AMBIENT_API_MARKERS` has no equivalent for). The two tables overlap on
486
+ #: three literal substrings (Date/DateTime/Random-family) because both are
487
+ #: independently describing "the clock and RNG", not because one was
488
+ #: copied from the other — merging them would either strip timer markers
489
+ #: this check needs or leak env/hostname markers into a check
490
+ #: test-review.md never documented for it.
491
+ _CLOCK_RNG_TIMER_MARKERS: dict[str, tuple[re.Pattern, ...]] = {
492
+ "js_ts": (
493
+ re.compile(r"\bDate\.now\s*\("),
494
+ re.compile(r"\bDate\s*\("),
495
+ re.compile(r"\bMath\.random\s*\("),
496
+ re.compile(r"\bsetTimeout\s*\("),
497
+ re.compile(r"\bsetInterval\s*\("),
498
+ re.compile(r"\bsetImmediate\s*\("),
499
+ ),
500
+ "csharp": (
501
+ re.compile(r"\bDateTime\.Now\b"),
502
+ re.compile(r"\bDateTime\.UtcNow\b"),
503
+ re.compile(r"\bDateTimeOffset\.Now\b"),
504
+ re.compile(r"\bnew\s+Random\s*\("),
505
+ re.compile(r"\bTask\.Delay\s*\("),
506
+ re.compile(r"\bThread\.Sleep\s*\("),
507
+ ),
508
+ "java": (
509
+ re.compile(r"\bnew\s+Date\s*\("),
510
+ re.compile(r"\bLocalDateTime\.now\s*\("),
511
+ re.compile(r"\bInstant\.now\s*\("),
512
+ re.compile(r"\bSystem\.currentTimeMillis\s*\("),
513
+ re.compile(r"\bnew\s+Random\s*\("),
514
+ re.compile(r"\bMath\.random\s*\("),
515
+ re.compile(r"\bThread\.sleep\s*\("),
516
+ ),
517
+ }
518
+
519
+ #: Fake-timer/injected-clock markers (Fix 3) — test-review.md's own bullets
520
+ #: exempt stubbed usage ("WITHOUT FAKE TIMERS", "WITHOUT INJECTION"), and
521
+ #: its Severity Anchors table names `jest.useFakeTimers()` as the remedy.
522
+ #: Presence anywhere in the file suppresses the WHOLE
523
+ #: unstubbed-clock-rng-timer finding for that file (the same file-level
524
+ #: granularity `_MOCK_RESET_MARKERS` already uses for mock-not-reset, not a
525
+ #: per-hit check).
526
+ _CLOCK_STUB_MARKERS: dict[str, tuple[re.Pattern, ...]] = {
527
+ "js_ts": (
528
+ re.compile(r"\bjest\.useFakeTimers\s*\("),
529
+ re.compile(r"\bvi\.useFakeTimers\s*\("),
530
+ re.compile(r"\bsinon\.useFakeTimers\s*\("),
531
+ ),
532
+ "csharp": (re.compile(r"\bIClock\b"), re.compile(r"\bTimeProvider\b")),
533
+ "java": (re.compile(r"\bClock\.fixed\s*\("),),
534
+ }
535
+
536
+ _REFLECTION_MARKERS: dict[str, tuple[re.Pattern, ...]] = {
537
+ "java": (
538
+ re.compile(r"\bgetDeclaredMethod\b"),
539
+ re.compile(r"\bgetDeclaredField\b"),
540
+ re.compile(r"\.setAccessible\s*\(\s*true\s*\)"),
541
+ re.compile(r"\bMethod\.invoke\b"),
542
+ ),
543
+ "csharp": (
544
+ re.compile(r"\.GetMethod\s*\([^)]*BindingFlags\.NonPublic"),
545
+ re.compile(r"\bInvokeMember\s*\("),
546
+ ),
547
+ "python": (
548
+ re.compile(r"\bgetattr\s*\([^,]+,\s*['\"]_\w+['\"]"),
549
+ re.compile(r"\bsetattr\s*\([^,]+,\s*['\"]_\w+['\"]"),
550
+ re.compile(r"\bhasattr\s*\([^,]+,\s*['\"]_\w+['\"]"),
551
+ ),
552
+ "js_ts": (
553
+ re.compile(r"\[\s*['\"]_\w+['\"]\s*\]"),
554
+ re.compile(r"\bObject\.getOwnPropertyDescriptor\s*\("),
555
+ re.compile(r"\bObject\.defineProperty\s*\("),
556
+ ),
557
+ }
558
+
559
+ #: Tolerated-Deviation Hunt categories, sourced from test-review.md's own
560
+ #: grep-pattern prose (language-agnostic — this check has never been
561
+ #: per-language in the agent file). NOT "migrated verbatim" (correction —
562
+ #: see the module docstring's Tolerated-Deviation Hunt paragraph for the
563
+ #: file-scoping disclosure, and below for the qualifier-suppression Fix 8
564
+ #: adds on top of the raw grep patterns): each category in test-review.md
565
+ #: carries a qualifying clause this table only partially implements —
566
+ #: "disabled tests" count only *with no linked issue or expiry*; "aged
567
+ #: markers" only *when there is no linked ticket or follow-up action*;
568
+ #: "suppressed warnings" only *with no explanatory comment naming the
569
+ #: specific approved exception*. For those three categories,
570
+ #: `_line_is_qualified` suppresses a hit whose own line or an immediately
571
+ #: adjacent line carries a ticket/issue reference or explanatory comment —
572
+ #: a cheap same-line-or-adjacent-line approximation, not a real
573
+ #: linked-issue lookup. "Relaxed assertions" has no qualifying clause in
574
+ #: test-review.md (unconditional). "Widened tolerances" (`changed without
575
+ #: a comment explaining the regression`) is inherently diff-based — this
576
+ #: single-file scan cannot tell whether a tolerance constant was recently
577
+ #: *changed* at all, so that category remains an unqualified upper-bound
578
+ #: approximation here; a genuine diff-aware check is out of scope for this
579
+ #: fix pass.
580
+ _DEVIATION_MARKERS: tuple[tuple[str, re.Pattern], ...] = (
581
+ (
582
+ "disabled-test",
583
+ re.compile(
584
+ r"@Ignore\b|@Disabled\b|\bxit\s*\(|\bxdescribe\s*\(|\btest\.skip\s*\("
585
+ r"|\bit\.skip\s*\(|\[Ignore\]|\[Skip\]|pytest\.mark\.skip\b|pytest\.mark\.xfail\b"
586
+ ),
587
+ ),
588
+ ("aged-marker", re.compile(r"\b(?:TODO|FIXME|HACK|XXX)\b")),
589
+ (
590
+ "suppressed-warning",
591
+ re.compile(
592
+ r"@SuppressWarnings\b|#pragma warning disable|#\s*noqa\b|#\s*type:\s*ignore"
593
+ r"|eslint-disable|pylint:\s*disable"
594
+ ),
595
+ ),
596
+ (
597
+ "relaxed-assertion",
598
+ re.compile(r"either\s+.{0,40}?\s+or\b|\bat least\b|\bapproximately\b|\bdelta\s*=|\bplaces\s*=\s*\d"),
599
+ ),
600
+ ("widened-tolerance", re.compile(r"\b(?:epsilon|tolerance)\s*=")),
601
+ )
602
+
603
+ #: Categories whose test-review.md wording carries a "no linked
604
+ #: issue/ticket/exception" qualifier (Fix 8) — checked via
605
+ #: `_line_is_qualified`.
606
+ _QUALIFIED_DEVIATION_CATEGORIES = frozenset({"disabled-test", "aged-marker", "suppressed-warning"})
607
+
608
+ #: A ticket/issue reference or explanatory link — `#123`, `PROJ-456`, or a
609
+ #: URL/word naming an issue/ticket tracker.
610
+ _QUALIFIER_RE = re.compile(r"#\d+|\b[A-Za-z]{2,}-\d+\b|\bissue\b|\bticket\b", re.IGNORECASE)
611
+
612
+
613
+ def _finding(category: str, severity: str | None, line: int, message: str, count: int = 1) -> dict:
614
+ return {"category": category, "severity": severity, "line": line, "message": message, "count": count}
615
+
616
+
617
+ # --- Checks -------------------------------------------------------------------
618
+
619
+
620
+ def _check_no_assertion(regions: list[dict], lang: str) -> list[dict]:
621
+ key = "content" if lang == "js_ts" else "body"
622
+ findings = []
623
+ for entry in regions:
624
+ if not _ASSERTION_RE.search(entry[key]):
625
+ findings.append(
626
+ _finding("no-assertion", "error", entry["line"], "Test has no assertion call — zero regression protection.")
627
+ )
628
+ return findings
629
+
630
+
631
+ def _check_no_assertion_python(text: str, masked: str) -> list[dict]:
632
+ findings = []
633
+ for line_no, body in _python_test_regions(text, masked):
634
+ if not _ASSERTION_RE.search(body):
635
+ findings.append(
636
+ _finding("no-assertion", "error", line_no, "Test has no assertion call — zero regression protection.")
637
+ )
638
+ return findings
639
+
640
+
641
+ def _check_missing_await(regions: list[dict], lang: str) -> list[dict]:
642
+ findings = []
643
+ if lang == "js_ts":
644
+ for entry in regions:
645
+ if entry["is_async"] and "await" not in entry["content"]:
646
+ findings.append(
647
+ _finding(
648
+ "missing-await", "warning", entry["line"], "Async test body has no `await` — likely an unawaited promise."
649
+ )
650
+ )
651
+ elif lang == "csharp":
652
+ for entry in regions:
653
+ if _CSHARP_ASYNC_TASK_RE.search(entry["signature"]) and "await" not in entry["body"]:
654
+ findings.append(
655
+ _finding(
656
+ "missing-await",
657
+ "warning",
658
+ entry["line"],
659
+ "`async Task` test method has no `await` — likely an unresolved Task.",
660
+ )
661
+ )
662
+ elif lang == "java":
663
+ for entry in regions:
664
+ if _JAVA_FUTURE_TYPE_RE.search(entry["body"]) and not _JAVA_FUTURE_RESOLVED_RE.search(entry["body"]):
665
+ findings.append(
666
+ _finding(
667
+ "missing-await",
668
+ "warning",
669
+ entry["line"],
670
+ "Test body references Future/CompletableFuture with no `.get()`/`.join()` resolution.",
671
+ )
672
+ )
673
+ return findings
674
+
675
+
676
+ def _mock_reinitialized(text: str, masked: str, lang: str) -> bool:
677
+ """True when a `[SetUp]`/`[TestInitialize]`/`@BeforeEach`/`@BeforeAll`
678
+ method's own body contains a mock-construct marker (Fix 4) — the
679
+ re-instantiation/re-initialization alternative test-review.md
680
+ documents alongside an explicit reset call."""
681
+ setup_re = _SETUP_ANNOT_RE.get(lang)
682
+ construct_markers = _MOCK_CONSTRUCT_MARKERS.get(lang)
683
+ if not setup_re or not construct_markers:
684
+ return False
685
+ for entry in _annotated_test_regions(text, masked, setup_re):
686
+ if any(marker.search(entry["body"]) for marker in construct_markers):
687
+ return True
688
+ return False
689
+
690
+
691
+ def _check_mock_not_reset(text: str, masked: str, lang: str) -> list[dict]:
692
+ construct_markers = _MOCK_CONSTRUCT_MARKERS.get(lang)
693
+ if not construct_markers:
694
+ return []
695
+ hit_lines = [_line_no(text, m.start()) for marker in construct_markers for m in marker.finditer(text)]
696
+ if not hit_lines:
697
+ return []
698
+ reset_marker = _MOCK_RESET_MARKERS[lang]
699
+ if reset_marker.search(text):
700
+ return []
701
+ if _mock_reinitialized(text, masked, lang):
702
+ return []
703
+ return [
704
+ _finding(
705
+ "mock-not-reset",
706
+ "warning",
707
+ min(hit_lines),
708
+ f"{len(hit_lines)} mock/stub construction site(s) with no reset/clear call found in this file.",
709
+ count=len(hit_lines),
710
+ )
711
+ ]
712
+
713
+
714
+ def _check_unstubbed_clock_rng_timer(text: str, lang: str) -> list[dict]:
715
+ if any(marker.search(text) for marker in _CLOCK_STUB_MARKERS.get(lang, ())):
716
+ return []
717
+ findings = []
718
+ for marker in _CLOCK_RNG_TIMER_MARKERS.get(lang, ()):
719
+ for m in marker.finditer(text):
720
+ findings.append(
721
+ _finding(
722
+ "unstubbed-clock-rng-timer",
723
+ "warning",
724
+ _line_no(text, m.start()),
725
+ f"Unstubbed clock/RNG/timer access: {m.group(0)!r}.",
726
+ )
727
+ )
728
+ return findings
729
+
730
+
731
+ def _check_reflection_primary_strategy(text: str, lang: str) -> list[dict]:
732
+ findings = []
733
+ for marker in _REFLECTION_MARKERS.get(lang, ()):
734
+ for m in marker.finditer(text):
735
+ findings.append(
736
+ _finding(
737
+ "reflection-primary-strategy",
738
+ "warning",
739
+ _line_no(text, m.start()),
740
+ f"Reflection into private members: {m.group(0)!r}. Architecture/encapsulation issue, not a test-hygiene nit.",
741
+ )
742
+ )
743
+ return findings
744
+
745
+
746
+ def _line_is_qualified(lines: list[str], line_no: int) -> bool:
747
+ """True when the marker's own line, or the immediately preceding/
748
+ following line, carries a ticket/issue reference or explanatory
749
+ comment (Fix 8)."""
750
+ for idx in (line_no - 2, line_no - 1, line_no):
751
+ if 0 <= idx < len(lines) and _QUALIFIER_RE.search(lines[idx]):
752
+ return True
753
+ return False
754
+
755
+
756
+ def _check_tolerated_deviation(text: str) -> list[dict]:
757
+ lines = text.split("\n")
758
+ hits = []
759
+ for name, marker in _DEVIATION_MARKERS:
760
+ for m in marker.finditer(text):
761
+ line_no = _line_no(text, m.start())
762
+ if name in _QUALIFIED_DEVIATION_CATEGORIES and _line_is_qualified(lines, line_no):
763
+ continue
764
+ hits.append((name, line_no))
765
+ if len(hits) < CONSOLIDATION_THRESHOLD:
766
+ return []
767
+ hits.sort(key=lambda h: h[1])
768
+ listing = ", ".join(f"{name}@L{line}" for name, line in hits)
769
+ return [
770
+ _finding(
771
+ "tolerated-deviation-consolidation",
772
+ "warning",
773
+ hits[0][1],
774
+ f"Fail-safe posture erosion: {len(hits)} tolerated-deviation artifacts co-located in this file ({listing}).",
775
+ count=len(hits),
776
+ )
777
+ ]
778
+
779
+
780
+ def _is_under_test_dir(root: Path, file_path: Path) -> bool:
781
+ try:
782
+ rel = file_path.resolve().relative_to(root.resolve())
783
+ except ValueError:
784
+ rel = file_path
785
+ return any(part.lower() in _TEST_DIR_NAMES for part in rel.parts)
786
+
787
+
788
+ def _translate_double_detector_findings(
789
+ root: Path,
790
+ file_path: Path,
791
+ runner: Callable[..., subprocess.CompletedProcess] = subprocess.run,
792
+ ) -> tuple[list[dict], bool]:
793
+ """Run `internal_double_detector.py --files <file> --json` (via the
794
+ injectable `runner`, defaulting to the real `subprocess.run` — Fix 13)
795
+ and translate each `verdict == "high"` finding into this script's own
796
+ `error`-severity `internal-collaborator-doubling` finding. The reused
797
+ script's raw JSON is never checked for a `severity` field — it doesn't
798
+ have one.
799
+
800
+ Returns `(findings, doubling_check_ran)`. `doubling_check_ran` is
801
+ `False` when the file's path has no `test`/`tests` segment (the
802
+ detector's own `analyze()` never scans it — Fix 6's out-of-scope
803
+ disclosure) or when the subprocess call/JSON parse fails (Fix 5); it
804
+ is `True` only when the detector actually ran and reported."""
805
+ if not _is_under_test_dir(root, file_path):
806
+ return (
807
+ [
808
+ _finding(
809
+ "internal-collaborator-doubling-out-of-scope",
810
+ "warning",
811
+ _UNKNOWN_LINE,
812
+ "internal_double_detector.py only scans files under a "
813
+ "'test'/'tests' path segment; this file's path has no "
814
+ "such segment, so the doubling check did not run for it "
815
+ "— even though this repo's own test-file conventions "
816
+ "(*.test.*, *.spec.*, __tests__/) would still recognize "
817
+ "it as a test file. mechanicalFail is unaffected by the "
818
+ "doubling category specifically for this file.",
819
+ )
820
+ ],
821
+ False,
822
+ )
823
+ try:
824
+ completed = runner(
825
+ [sys.executable, str(_DETECTOR_PATH), str(root), "--files", str(file_path), "--json"],
826
+ capture_output=True,
827
+ text=True,
828
+ timeout=_DOUBLE_DETECTOR_TIMEOUT_SECONDS,
829
+ check=False,
830
+ )
831
+ except (OSError, subprocess.TimeoutExpired) as exc:
832
+ return (
833
+ [
834
+ _finding(
835
+ "internal-collaborator-doubling-unavailable",
836
+ "warning",
837
+ _UNKNOWN_LINE,
838
+ f"internal_double_detector.py could not be run: {exc}",
839
+ )
840
+ ],
841
+ False,
842
+ )
843
+ try:
844
+ payload = json.loads(completed.stdout)
845
+ except json.JSONDecodeError:
846
+ return (
847
+ [
848
+ _finding(
849
+ "internal-collaborator-doubling-unavailable",
850
+ "warning",
851
+ _UNKNOWN_LINE,
852
+ "internal_double_detector.py did not emit valid JSON: "
853
+ f"exit={completed.returncode} stderr={completed.stderr.strip()!r}",
854
+ )
855
+ ],
856
+ False,
857
+ )
858
+ findings = []
859
+ for entry in payload.get("findings", []):
860
+ if entry.get("verdict") == "high":
861
+ findings.append(
862
+ _finding(
863
+ "internal-collaborator-doubling",
864
+ "error",
865
+ entry.get("line", _UNKNOWN_LINE),
866
+ entry.get("message", "internal_double_detector.py reported a high-verdict finding."),
867
+ )
868
+ )
869
+ return findings, True
870
+
871
+
872
+ _GATING_CATEGORIES = frozenset({"no-assertion", "internal-collaborator-doubling"})
873
+
874
+
875
+ def _parse_failure_result(file_path: Path, message: str) -> dict:
876
+ """The shared `analyze_file` early-return shape for a file that could
877
+ not be read/decoded or whose test-region boundaries never closed
878
+ before EOF — a `parse-failure` finding, `mechanicalFail: False` (the
879
+ file falls through to the qualitative pass), and the doubling check
880
+ never ran."""
881
+ return {
882
+ "file": str(file_path),
883
+ "mechanicalFail": False,
884
+ "findings": [_finding("parse-failure", None, _UNKNOWN_LINE, message)],
885
+ "skippedQualitative": False,
886
+ "doublingCheckRan": False,
887
+ }
888
+
889
+
890
+ def analyze_file(
891
+ root: Path,
892
+ file_path: Path,
893
+ double_detector_runner: Callable[..., subprocess.CompletedProcess] = subprocess.run,
894
+ ) -> dict:
895
+ """Compute the mechanical pre-phase result for `file_path` (scanned
896
+ against `root` for `internal_double_detector.py`'s first-party index).
897
+ `double_detector_runner` is injectable (Fix 13) — defaults to the real
898
+ `subprocess.run`; tests exercising unrelated checks can pass a stub to
899
+ avoid the real subprocess-spawn cost.
900
+
901
+ Returns `{"file", "mechanicalFail", "findings", "skippedQualitative",
902
+ "doublingCheckRan"}`. Never raises — a decode/parse failure becomes a
903
+ `parse-failure` finding with `mechanicalFail: False` (the file falls
904
+ through to the qualitative pass rather than being dropped)."""
905
+ lang = LANG_BY_EXT.get(file_path.suffix)
906
+
907
+ try:
908
+ text = file_path.read_text(encoding="utf-8")
909
+ except (OSError, UnicodeDecodeError) as exc:
910
+ return _parse_failure_result(file_path, f"Could not read/decode file: {exc}")
911
+
912
+ findings: list[dict] = []
913
+ try:
914
+ masked = _mask_code(text)
915
+ regions = _extract_regions(text, masked, lang) if lang else []
916
+ if lang == "python":
917
+ findings += _check_no_assertion_python(text, masked)
918
+ elif lang in ("js_ts", "csharp", "java"):
919
+ findings += _check_no_assertion(regions, lang)
920
+ findings += _check_missing_await(regions, lang)
921
+ if lang:
922
+ findings += _check_mock_not_reset(text, masked, lang)
923
+ findings += _check_unstubbed_clock_rng_timer(text, lang)
924
+ findings += _check_reflection_primary_strategy(text, lang)
925
+ findings += _check_tolerated_deviation(text)
926
+ except ParseFailure as exc:
927
+ return _parse_failure_result(file_path, str(exc))
928
+
929
+ doubling_findings, doubling_check_ran = _translate_double_detector_findings(root, file_path, double_detector_runner)
930
+ findings += doubling_findings
931
+
932
+ mechanical_fail = any(f["category"] in _GATING_CATEGORIES and f["severity"] == "error" for f in findings)
933
+ return {
934
+ "file": str(file_path),
935
+ "mechanicalFail": mechanical_fail,
936
+ "findings": findings,
937
+ "skippedQualitative": mechanical_fail,
938
+ "doublingCheckRan": doubling_check_ran,
939
+ }
940
+
941
+
942
+ def main(argv: list[str] | None = None) -> int:
943
+ parser = argparse.ArgumentParser(description=__doc__)
944
+ parser.add_argument("root", help="Project root (passed through to internal_double_detector.py)")
945
+ parser.add_argument("file", help="Test file to analyze (absolute, or relative to root)")
946
+ args = parser.parse_args(argv)
947
+
948
+ root = Path(args.root)
949
+ file_path = Path(args.file)
950
+ if not file_path.is_absolute():
951
+ file_path = root / file_path
952
+
953
+ print(json.dumps(analyze_file(root, file_path)))
954
+ return 0
955
+
956
+
957
+ if __name__ == "__main__":
958
+ raise SystemExit(main(sys.argv[1:]))