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,895 @@
1
+ #!/usr/bin/env python3
2
+ """orchestrator.py — Python dispatcher for the dev-team three-phase pipeline.
3
+
4
+ CLI: python3 ${CLAUDE_PLUGIN_ROOT}/scripts/orchestrator.py [--resume] [--skip-llm]
5
+ [--memory-dir <path>] [--classify trivial|standard|complex] [--fail-wave]
6
+ [--dispatch-personas]
7
+
8
+ Flags:
9
+ --resume Skip phases whose state files already exist in memory-dir.
10
+ --skip-llm Use stubs for classify() and all LLM dispatch.
11
+ --memory-dir <path> Where to read/write phase state (default: .claude/memory/ relative to CWD).
12
+ --classify <size> Override classification (trivial|standard|complex). For testing only.
13
+ --fail-wave Simulate a wave barrier failure (for testing).
14
+ --dispatch-personas Dispatch plan-review personas (for testing).
15
+
16
+ Exit codes:
17
+ 0 = success
18
+ 1 = error (no prior state with --resume, wave barrier failure, etc.)
19
+
20
+ Module split: see ADR 0040.
21
+ """
22
+
23
+ # Module split (dispatch_primitives / phase_functions): evaluated and
24
+ # declined for now — see ADR 0040
25
+ # (docs/adr/0040-evaluate-splitting-orchestrator-py-no-go.md, issue #1723).
26
+ # The blocker is 58 `patch.object(orch, "dispatch_persona"/"dispatch_personas",
27
+ # ...)` sites in tests/scripts/test_orchestrator.py (multiline-aware count —
28
+ # a single-line grep undercounts to 45) that would stop intercepting dispatch
29
+ # calls if the phase functions imported those names from a separate module.
30
+ # Revisit if dispatch_primitives or phase_functions grows independently.
31
+
32
+ from __future__ import annotations
33
+
34
+ import argparse
35
+ import asyncio
36
+ import functools
37
+ import json
38
+ import subprocess
39
+ import sys
40
+ from pathlib import Path
41
+
42
+ SCRIPTS = Path(__file__).resolve().parent
43
+ sys.path.insert(0, str(SCRIPTS))
44
+ from lib.slug import derive_slug
45
+
46
+ # Default personas for plan review — the five plan-review-* critics.
47
+ DEFAULT_PERSONAS = [
48
+ "plan-review-acceptance",
49
+ "plan-review-design",
50
+ "plan-review-ux",
51
+ "plan-review-strategic",
52
+ "plan-review-parallelization",
53
+ ]
54
+
55
+ # Language-agnostic always-run code-review trio per docs/team-structure.md's
56
+ # review-dispatch fan-out. Conditional language-specific reviewers are out
57
+ # of scope for this iteration.
58
+ CODE_REVIEW_PANEL = ["doc-review", "arch-review", "token-efficiency-review"]
59
+
60
+ # Personas whose --output-format json envelope's "result" field is itself
61
+ # documented structured JSON (per knowledge/review-agent-output-contract.md)
62
+ # and should be parsed rather than stored as freeform prose.
63
+ JSON_CONTRACT_PERSONAS = DEFAULT_PERSONAS + CODE_REVIEW_PANEL
64
+
65
+ # Keyword heuristic for the Research phase's security-engineer dispatch
66
+ # decision. This tuple is the one normative source in CODE for the keyword
67
+ # list — _touches_security() consumes it, it is not duplicated in any other
68
+ # .py module. knowledge/orchestrator-script-implementation.md's "Security
69
+ # Engineer dispatch — script approximation" section (linked from
70
+ # agents/orchestrator.md's phase table) restates the same seven keywords in
71
+ # prose for its own (agent-facing, standalone) audience; a content-guard
72
+ # test (tests/agents/test_orchestrator_security_persona_prose_sync.py, #2067)
73
+ # now asserts the two stay in sync.
74
+ SECURITY_KEYWORDS = (
75
+ "auth",
76
+ "secret",
77
+ "crypto",
78
+ "password",
79
+ "token",
80
+ "credential",
81
+ "encrypt",
82
+ )
83
+
84
+ # Research-phase always-on persona roster (see agents/orchestrator.md §
85
+ # Phase 1: Research). Named module constant, matching the DEFAULT_PERSONAS/
86
+ # CODE_REVIEW_PANEL pattern above, so it has one definition instead of being
87
+ # re-typed at each call/test site. A tuple (like SECURITY_KEYWORDS), not a
88
+ # list: this is a fixed roster, so `list(RESEARCH_PERSONAS)` at its one call
89
+ # site is a genuine type conversion into a mutable working copy, not a
90
+ # defensive copy guarding against accidental in-place mutation of the
91
+ # constant itself.
92
+ RESEARCH_PERSONAS = ("codebase-recon", "architect", "data-flow-tracer")
93
+
94
+ # Plan-phase core-trio roster (see agents/orchestrator.md § Phase 2: Plan).
95
+ # Dispatched first, before the plan-review-* critics in DEFAULT_PERSONAS —
96
+ # see _default_phase_plan below. A tuple, matching RESEARCH_PERSONAS's own
97
+ # convention; unlike RESEARCH_PERSONAS, nothing is ever conditionally
98
+ # appended to this roster, so no defensive-copy note is needed here.
99
+ # knowledge/orchestrator-script-implementation.md's "Plan persona roster"
100
+ # section (linked from agents/orchestrator.md's phase table) restates this
101
+ # same trio (and CRITICS_SKIPPED_ALL_CORE_FAILED's value) in prose; a
102
+ # content-guard test
103
+ # (tests/agents/test_orchestrator_security_persona_prose_sync.py, #2067) now
104
+ # asserts the two stay in sync.
105
+ PLAN_CORE_PERSONAS = ("product-manager", "architect", "qa-engineer")
106
+
107
+ # Persisted-state vocabulary for _default_phase_plan's all-core-failed guard
108
+ # (see below) — named alongside the module's other cross-process vocabulary
109
+ # constants (SECURITY_KEYWORDS, RESEARCH_PERSONAS) so the sentinel has one
110
+ # definition instead of being re-typed at the production site and in tests.
111
+ CRITICS_SKIPPED_ALL_CORE_FAILED = "all_core_personas_failed"
112
+
113
+ # The conditionally-dispatched fourth Research persona (see _touches_security
114
+ # below). Named for the same reason RESEARCH_PERSONAS is: avoid re-typing the
115
+ # literal at each call/test site.
116
+ SECURITY_ENGINEER_PERSONA = "security-engineer"
117
+
118
+ # The Implement-phase wave persona and its post-success doc-verification
119
+ # persona (see _default_phase_implement below). Named for the same reason
120
+ # SECURITY_ENGINEER_PERSONA is: avoid re-typing the literal at each
121
+ # call/test site.
122
+ SOFTWARE_ENGINEER_PERSONA = "software-engineer"
123
+ TECH_WRITER_PERSONA = "tech-writer"
124
+
125
+ # Implement-phase wave slice roster (see _dispatch_implement_wave below). A
126
+ # tuple, matching RESEARCH_PERSONAS/PLAN_CORE_PERSONAS's own convention.
127
+ # Load-bearing, not decorative: persisted into orchestrator-implement.json
128
+ # and printed verbatim in the operator-facing "wave barrier failed on slice
129
+ # '<name>'" message. One definition on the production side (test_orchestrator
130
+ # pins its exact value directly below, alongside SOFTWARE_ENGINEER_PERSONA/
131
+ # TECH_WRITER_PERSONA's own pinning tests) — most test sites deliberately
132
+ # still pin the literal value independently rather than importing this
133
+ # constant, matching how this file's persona constants are pinned rather
134
+ # than merely referenced. A single synthetic slice representing "the whole
135
+ # task" today (see the Script gap in agents/orchestrator.md for why); the
136
+ # --fail-wave simulation branch below deliberately prints a different,
137
+ # unrelated slice name ("slice-1") since it doesn't go through this
138
+ # constant at all.
139
+ IMPLEMENT_WAVE_SLICES = ("implement-1",)
140
+
141
+ # Timeouts (seconds) for the two `claude -p` subprocess dispatch sites below.
142
+ # Unverified placeholders, not measured against a real dispatch — pinned by
143
+ # a direct test (test_orchestrator.py) per follow-up #1716 so an accidental
144
+ # edit fails fast instead of surfacing only as a flaky/slow-CLI symptom;
145
+ # the underlying values themselves remain unverified against real latency.
146
+ CLASSIFY_TIMEOUT_S = 30
147
+ PERSONA_DISPATCH_TIMEOUT_S = 60
148
+
149
+
150
+ def _warn_on_failed_personas(phase_label: str, results: list, fatal: bool = False) -> None:
151
+ """Print a single stderr WARNING naming every failed persona in results,
152
+ or nothing at all if none failed.
153
+
154
+ Shared by _default_phase_research, _default_phase_plan, and
155
+ _default_phase_implement so the WARNING message has one normative
156
+ formatting/behavior definition instead of independently maintained
157
+ copies. Research/Plan's failures (and Implement's post-success review
158
+ group) are genuinely recorded and non-fatal, which is the default
159
+ wording — but the Implement wave dispatch is a different case: a
160
+ failure there is about to raise WaveError uncaught (the state file is
161
+ never written) and end the process with exit code 1, so `fatal=True`
162
+ selects wording that says so instead of falsely claiming
163
+ "(recorded, non-fatal)".
164
+ """
165
+ failed_personas = [r["persona"] for r in results if r.get("status") == "failed"]
166
+ if failed_personas:
167
+ suffix = "wave barrier will fail" if fatal else "recorded, non-fatal"
168
+ print(
169
+ f"WARNING: {phase_label} persona dispatch failed ({suffix}): {', '.join(failed_personas)}",
170
+ file=sys.stderr,
171
+ )
172
+
173
+
174
+ def _touches_security(request: str) -> bool:
175
+ """Return True if request case-insensitively contains a security keyword.
176
+
177
+ Heuristic, not a precise classifier: substring matching means false
178
+ positives are expected and accepted (e.g. "cryptocurrency" matches via
179
+ "crypto") per the plan's Risks section.
180
+ """
181
+ lowered = request.lower()
182
+ return any(keyword in lowered for keyword in SECURITY_KEYWORDS)
183
+
184
+
185
+ # ---------------------------------------------------------------------------
186
+ # Helpers
187
+ # ---------------------------------------------------------------------------
188
+
189
+
190
+ def phase_state_path(phase: str, memory_dir: Path) -> Path:
191
+ """Return the canonical path for a phase's state file."""
192
+ return memory_dir / f"orchestrator-{phase}.json"
193
+
194
+
195
+ def write_progress(phase: str, result: dict, memory_dir: Path) -> None:
196
+ """Write phase result as JSON to memory_dir/orchestrator-<phase>.json."""
197
+ memory_dir.mkdir(parents=True, exist_ok=True)
198
+ phase_state_path(phase, memory_dir).write_text(json.dumps(result))
199
+
200
+
201
+ def read_progress(phase: str, memory_dir: Path):
202
+ """Return the parsed JSON for phase, or None if no state file exists."""
203
+ path = phase_state_path(phase, memory_dir)
204
+ if path.exists():
205
+ return json.loads(path.read_text())
206
+ return None
207
+
208
+
209
+ # ---------------------------------------------------------------------------
210
+ # Classification
211
+ # ---------------------------------------------------------------------------
212
+
213
+
214
+ async def classify(request: str, skip_llm: bool = False) -> dict:
215
+ """Return {size: trivial|standard|complex}. Falls back to standard on failure."""
216
+ if skip_llm:
217
+ return {"size": "standard"}
218
+ try:
219
+ # Offload the blocking call to a thread so an awaiting/gathered caller
220
+ # keeps a free event loop instead of serializing on subprocess.run (#1213).
221
+ loop = asyncio.get_running_loop()
222
+ result = await loop.run_in_executor(
223
+ None,
224
+ functools.partial(
225
+ subprocess.run,
226
+ [
227
+ "claude",
228
+ "-p",
229
+ (
230
+ "Classify this task as exactly one of: trivial, standard, or complex. "
231
+ f"Reply with only one word. Task: {request}"
232
+ ),
233
+ ],
234
+ capture_output=True,
235
+ text=True,
236
+ timeout=CLASSIFY_TIMEOUT_S,
237
+ ),
238
+ )
239
+ if result.returncode == 0 and result.stdout.strip():
240
+ raw = result.stdout.strip().lower()
241
+ for size in ("trivial", "standard", "complex"):
242
+ if size in raw:
243
+ return {"size": size}
244
+ except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
245
+ print(
246
+ "WARNING: LLM classify failed; defaulting to full pipeline",
247
+ file=sys.stderr,
248
+ )
249
+ return {"size": "standard"}
250
+
251
+
252
+ # ---------------------------------------------------------------------------
253
+ # Research phase
254
+ # ---------------------------------------------------------------------------
255
+
256
+
257
+ def _recon_artifact_path(root: Path) -> Path:
258
+ """Path to codebase-recon's JSON artifact for the repo at `root`.
259
+
260
+ Per agents/codebase-recon.md's Contract section: always
261
+ `.claude/memory/recon-<slug>.json`. `root` is deliberately the caller's
262
+ own CWD, not a git-root resolution (e.g.
263
+ hooks/lib/artifact_paths.py::memory_dir, used by other scripts in this
264
+ directory for that purpose) — the recon *agent*'s prompt writes this
265
+ path relative to its own CWD, which is orchestrator.py's CWD since
266
+ dispatch_persona's subprocess.run inherits it unchanged. Resolving
267
+ against the git root instead would disagree with the recon agent's own
268
+ write location whenever they differ (e.g. orchestrator.py invoked from
269
+ a subdirectory), which is the opposite of this function's purpose.
270
+ Also independent of orchestrator.py's own (configurable) --memory-dir;
271
+ if that flag points elsewhere, this path and the phase-state directory
272
+ diverge — inherent to the recon agent's contract, not something this
273
+ function can paper over.
274
+ """
275
+ return root / ".claude" / "memory" / f"recon-{derive_slug(root)}.json"
276
+
277
+
278
+ async def _resolve_recon_artifact(personas: list, results: list, cwd: Path) -> str | None:
279
+ """Link codebase-recon's own artifact (agents/codebase-recon.md's
280
+ Contract — .claude/memory/recon-<slug>.json) into Research state, so a
281
+ Plan-phase consumer doesn't need to independently know that naming
282
+ convention (follow-up #1716). Returns `None` when codebase-recon wasn't
283
+ dispatched, didn't succeed, or its artifact file isn't on disk (e.g.
284
+ --skip-llm, where no real agent ran).
285
+
286
+ `cwd` is captured by the caller before its own `await` rather than read
287
+ here via `Path.cwd()` directly — process-global state should not be
288
+ re-read across an await boundary in case a future concurrent coroutine
289
+ ever changes it.
290
+ """
291
+ if "codebase-recon" not in personas:
292
+ return None
293
+ recon_result = next((r for r in results if r.get("persona") == "codebase-recon"), None)
294
+ if recon_result is None or recon_result.get("status") != "success":
295
+ return None
296
+ candidate = _recon_artifact_path(cwd)
297
+ # Offload to a thread, matching classify()'s own run_in_executor use for
298
+ # its blocking call — the event loop shouldn't block on a filesystem
299
+ # stat any more than it should on subprocess.run.
300
+ loop = asyncio.get_running_loop()
301
+ if not await loop.run_in_executor(None, candidate.is_file):
302
+ return None
303
+ return str(candidate)
304
+
305
+
306
+ async def _default_phase_research(request: str, task: dict, skip_llm: bool) -> dict:
307
+ """Dispatch the Research-phase personas and aggregate their results.
308
+
309
+ Always dispatches RESEARCH_PERSONAS (codebase-recon, architect,
310
+ data-flow-tracer); additionally dispatches security-engineer when the
311
+ request text touches auth/secrets/crypto per _touches_security(). A
312
+ status: "failed" entry among the dispatched results is recorded
313
+ verbatim — reconcile()/WaveError are scoped to the Implement phase's
314
+ wave loop, not Research.
315
+ """
316
+ # Captured before the await below (see _resolve_recon_artifact's
317
+ # docstring) rather than read via Path.cwd() after it.
318
+ cwd = Path.cwd()
319
+ # RESEARCH_PERSONAS is an immutable tuple; list() converts it into the
320
+ # mutable working copy the conditional security-engineer append below
321
+ # needs (see the constant's own definition for why it's a tuple).
322
+ personas = list(RESEARCH_PERSONAS)
323
+ if _touches_security(request):
324
+ personas.append(SECURITY_ENGINEER_PERSONA)
325
+ # "task" here is the classify() output dict (e.g. {"size": "standard"}),
326
+ # not the request text — kept as a distinct key from "request" so a
327
+ # later Plan-phase slice reading this precedent doesn't conflate them.
328
+ results = await dispatch_personas(
329
+ personas, plan={"task": task, "request": request}, skip_llm=skip_llm
330
+ )
331
+ # Research records failures verbatim and never raises (see docstring
332
+ # above) — but a run where any persona failed must not look identical,
333
+ # on the console, to one that succeeded fully. Mirrors classify()'s own
334
+ # degraded-but-non-fatal WARNING.
335
+ _warn_on_failed_personas("Research", results)
336
+ return {
337
+ "personas": personas,
338
+ "results": results,
339
+ "skip_llm": skip_llm,
340
+ "recon_artifact": await _resolve_recon_artifact(personas, results, cwd),
341
+ }
342
+
343
+
344
+ # ---------------------------------------------------------------------------
345
+ # Plan phase
346
+ # ---------------------------------------------------------------------------
347
+
348
+
349
+ def _all_personas_failed(results: list) -> bool:
350
+ """True if results is non-empty and every entry has status "failed".
351
+
352
+ Deliberately False on an empty list: an empty core_results would make a
353
+ bare all(...) vacuously True and wrongly skip critic dispatch, so the
354
+ emptiness check is load-bearing, not defensive noise — unreachable
355
+ today (dispatch_personas always returns one entry per persona and
356
+ PLAN_CORE_PERSONAS is a fixed 3-tuple), but would matter the moment a
357
+ future slice makes the core roster dynamic.
358
+ """
359
+ return bool(results) and all(r.get("status") == "failed" for r in results)
360
+
361
+
362
+ async def _default_phase_plan(
363
+ request: str, task: dict, research_state: dict, skip_llm: bool
364
+ ) -> dict:
365
+ """Dispatch the Plan-phase core trio, then the plan-review-* critics.
366
+
367
+ Two-stage dispatch: PLAN_CORE_PERSONAS (product-manager, architect,
368
+ qa-engineer) drafts a plan using the Research phase's aggregated state
369
+ as context, then DEFAULT_PERSONAS (the five plan-review-* critics)
370
+ critiques that draft — unless every core-trio result has
371
+ status: "failed", in which case critic dispatch is skipped entirely
372
+ (see the all-core-failed guard below). A status: "failed" entry among
373
+ either group's results is recorded verbatim — reconcile()/WaveError
374
+ stay scoped to the Implement phase's wave loop, not Plan. Note this
375
+ phase still dispatches the core trio even when research_state's own
376
+ results are entirely failed: the raw request text is sufficient context
377
+ for the trio to draft from, unlike the critics, which genuinely have
378
+ nothing to critique when the trio itself produced nothing.
379
+ """
380
+ core_personas = list(PLAN_CORE_PERSONAS)
381
+ core_results = await dispatch_personas(
382
+ core_personas,
383
+ plan={"task": task, "request": request, "research": research_state},
384
+ skip_llm=skip_llm,
385
+ )
386
+ critics_skipped_reason = None
387
+ if _all_personas_failed(core_results):
388
+ # Every core-trio persona failed (most plausibly: the claude CLI is
389
+ # unreachable) — skip the five critic dispatches entirely rather
390
+ # than spend real LLM cost critiquing identical failure stubs.
391
+ critic_results = []
392
+ critics_skipped_reason = CRITICS_SKIPPED_ALL_CORE_FAILED
393
+ print(
394
+ "INFO: all Plan core personas failed — skipping critic dispatch",
395
+ file=sys.stderr,
396
+ )
397
+ else:
398
+ critic_results = await dispatch_personas(
399
+ DEFAULT_PERSONAS,
400
+ plan={"task": task, "request": request, "plan_draft": core_results},
401
+ skip_llm=skip_llm,
402
+ )
403
+ # Exactly one merged WARNING per Plan-phase run, naming every failed
404
+ # persona across both groups — not one line per group.
405
+ _warn_on_failed_personas("Plan", core_results + critic_results)
406
+ return {
407
+ "core_personas": core_personas,
408
+ "core_results": core_results,
409
+ # list(...), not a bare reference: critic_personas is persisted
410
+ # (json.dumps doesn't care, but a future in-memory consumer
411
+ # mutating this list would otherwise corrupt the shared module
412
+ # constant DEFAULT_PERSONAS for the rest of the process).
413
+ "critic_personas": list(DEFAULT_PERSONAS),
414
+ "critic_results": critic_results,
415
+ "critics_skipped_reason": critics_skipped_reason,
416
+ "skip_llm": skip_llm,
417
+ }
418
+
419
+
420
+ # ---------------------------------------------------------------------------
421
+ # Implement phase
422
+ # ---------------------------------------------------------------------------
423
+
424
+
425
+ async def _dispatch_implement_wave(
426
+ request: str, task: dict, plan_state: dict, skip_llm: bool
427
+ ) -> list:
428
+ """Dispatch the Implement-phase wave and reconcile its results.
429
+
430
+ Dispatches SOFTWARE_ENGINEER_PERSONA once per IMPLEMENT_WAVE_SLICES entry
431
+ via dispatch_personas — reused verbatim rather than a hand-rolled second
432
+ copy of its gather/BaseException-normalization logic. The `* len(...)`
433
+ below is what keeps `personas` index-aligned with IMPLEMENT_WAVE_SLICES
434
+ for the "slice" tagging that follows — a real invariant, not decorative,
435
+ even though both are length 1 today (see IMPLEMENT_WAVE_SLICES's own
436
+ comment for why). reconcile() raises WaveError uncaught (no try/except
437
+ here) on any failed slice, so _run_phase's write_progress call never
438
+ runs for a failed wave — the phase's state file stays absent and a
439
+ subsequent --resume run retries Implement from scratch.
440
+ """
441
+ results = await dispatch_personas(
442
+ [SOFTWARE_ENGINEER_PERSONA] * len(IMPLEMENT_WAVE_SLICES),
443
+ plan={"task": task, "request": request, "plan_state": plan_state},
444
+ skip_llm=skip_llm,
445
+ )
446
+ for slice_name, result in zip(IMPLEMENT_WAVE_SLICES, results):
447
+ result["slice"] = slice_name
448
+ # fatal=True: this failure is about to raise WaveError uncaught (state
449
+ # never persisted, exit code 1) — the opposite of the "recorded,
450
+ # non-fatal" wording _warn_on_failed_personas defaults to.
451
+ _warn_on_failed_personas("Implement", results, fatal=True)
452
+ await reconcile(results, list(IMPLEMENT_WAVE_SLICES)) # raises WaveError; propagates uncaught
453
+ return results
454
+
455
+
456
+ async def _dispatch_implement_verification(
457
+ request: str, task: dict, results: list, skip_llm: bool
458
+ ) -> tuple:
459
+ """Dispatch post-success review-panel + tech-writer verification.
460
+
461
+ Only reached after _dispatch_implement_wave's reconcile() succeeds.
462
+ Both go through dispatch_personas (never a bare dispatch_persona call),
463
+ so an unexpected throwable from either is normalized to a failure stub
464
+ rather than escaping past run_pipeline's `except WaveError` and
465
+ discarding a successful wave's results. A second, independent
466
+ _warn_on_failed_personas call ("Implement review", genuinely non-fatal)
467
+ surfaces a failed member of either dispatch as a stderr WARNING,
468
+ mirroring Research/Plan's own non-fatal-failure-visibility convention.
469
+ """
470
+ verification_payload = {
471
+ "task": task,
472
+ "request": request,
473
+ "implement_results": results,
474
+ }
475
+ review_results = await dispatch_personas(
476
+ CODE_REVIEW_PANEL, plan=verification_payload, skip_llm=skip_llm
477
+ )
478
+ (tech_writer_result,) = await dispatch_personas(
479
+ [TECH_WRITER_PERSONA],
480
+ plan=verification_payload,
481
+ skip_llm=skip_llm,
482
+ )
483
+ _warn_on_failed_personas("Implement review", review_results + [tech_writer_result])
484
+ return review_results, tech_writer_result
485
+
486
+
487
+ async def _default_phase_implement(
488
+ request: str, task: dict, plan_state: dict, skip_llm: bool
489
+ ) -> dict:
490
+ """Dispatch the Implement-phase wave, reconcile it, then verify on success.
491
+
492
+ Composes two independently-changing concerns (see their own docstrings):
493
+ _dispatch_implement_wave (the wave-dispatch/reconcile barrier, whose
494
+ per-step decomposition is tracked future work — see the Script gap in
495
+ agents/orchestrator.md) and _dispatch_implement_verification (a stable
496
+ concern that shouldn't need to move when that lands).
497
+ """
498
+ results = await _dispatch_implement_wave(request, task, plan_state, skip_llm)
499
+ review_results, tech_writer_result = await _dispatch_implement_verification(
500
+ request, task, results, skip_llm
501
+ )
502
+ return {
503
+ "wave_slices": list(IMPLEMENT_WAVE_SLICES),
504
+ "results": results,
505
+ # list(...), not a bare reference: review_personas is persisted,
506
+ # and a future in-memory consumer mutating this list would
507
+ # otherwise corrupt the shared module constant CODE_REVIEW_PANEL
508
+ # for the rest of the process (same rationale as critic_personas
509
+ # in _default_phase_plan above).
510
+ "review_personas": list(CODE_REVIEW_PANEL),
511
+ "review_results": review_results,
512
+ "tech_writer_result": tech_writer_result,
513
+ "skip_llm": skip_llm,
514
+ }
515
+
516
+
517
+ # ---------------------------------------------------------------------------
518
+ # Persona dispatch and wave barrier
519
+ # ---------------------------------------------------------------------------
520
+
521
+
522
+ class WaveError(Exception):
523
+ """Raised when a wave barrier fails (a slice returned status='failed')."""
524
+
525
+ def __init__(self, failing_slice: str, succeeded: list):
526
+ self.failing_slice = failing_slice
527
+ self.succeeded = succeeded
528
+ super().__init__(f"Wave barrier failed on slice '{failing_slice}'")
529
+
530
+
531
+ def _failed_result(persona: str, error: str) -> dict:
532
+ """Return the canonical dispatch-failure stub shared by every failure site.
533
+
534
+ One normative shape for {persona, status: "failed", error} so a future
535
+ change to the shape (e.g. adding a distinguishing field) touches one
536
+ definition instead of the four call sites that used to hand-construct it
537
+ independently. `error` is required, not defaulted, so a new call site
538
+ must name its cause rather than silently inheriting one that doesn't
539
+ describe it — the four callers today: a malformed dispatch envelope
540
+ ("malformed_envelope"), a non-serializable plan payload
541
+ ("unserializable_plan"), a subprocess/CLI failure ("llm_unavailable"),
542
+ and an unexpected throwable surfaced by asyncio.gather
543
+ ("dispatch_exception").
544
+ """
545
+ return {"persona": persona, "status": "failed", "error": error}
546
+
547
+
548
+ def _parse_dispatch_envelope(stdout: str, persona: str) -> dict:
549
+ """Parse a `claude -p --output-format json` envelope into a dispatch result.
550
+
551
+ Status is derived solely from the envelope's `is_error` field and is
552
+ never overwritten by the parsed payload. For personas in
553
+ JSON_CONTRACT_PERSONAS, the envelope's `result` field is additionally
554
+ parsed as JSON and its keys merged in, except `status` (exposed
555
+ separately as `review_status`, since CODE_REVIEW_PANEL's own contract
556
+ reuses that key name for an unrelated pass/warn/fail/skip vocabulary)
557
+ and `persona` (discarded — already dispatch-owned). For every other
558
+ persona, `result` is always stored verbatim under `output`. A malformed
559
+ or non-object payload — the top-level envelope itself, or (for a
560
+ JSON_CONTRACT_PERSONAS member) the inner `result` — degrades gracefully
561
+ rather than raising: a bad envelope maps to a `"malformed_envelope"`
562
+ failure stub (see `_failed_result`) with no `verdict` key, and a bad
563
+ inner `result` maps to `output` plus a
564
+ `parse_error: True` marker while the already-derived status is left
565
+ untouched.
566
+ """
567
+ try:
568
+ envelope = json.loads(stdout)
569
+ if not isinstance(envelope, dict):
570
+ raise TypeError("envelope is not a JSON object")
571
+ except (json.JSONDecodeError, TypeError, ValueError):
572
+ return _failed_result(persona, error="malformed_envelope")
573
+
574
+ data = {
575
+ "persona": persona,
576
+ # default True: an envelope that doesn't state whether it errored
577
+ # is not evidence of success.
578
+ "status": "failed" if envelope.get("is_error", True) else "success",
579
+ }
580
+ result_text = envelope.get("result", "")
581
+ if persona in JSON_CONTRACT_PERSONAS:
582
+ try:
583
+ parsed = json.loads(result_text)
584
+ if not isinstance(parsed, dict):
585
+ raise TypeError("parsed result is not a JSON object")
586
+ except (json.JSONDecodeError, TypeError, ValueError):
587
+ data["output"] = result_text
588
+ data["parse_error"] = True
589
+ else:
590
+ # status/persona stay dispatch-owned (AC #3): dispatch status is
591
+ # derived only from the envelope's is_error, never from the
592
+ # parsed payload. CODE_REVIEW_PANEL's own contract reuses the
593
+ # key name "status" for an unrelated pass/warn/fail/skip
594
+ # vocabulary, so expose it separately rather than merge it over.
595
+ for key, value in parsed.items():
596
+ if key == "status":
597
+ data["review_status"] = value
598
+ elif key == "persona":
599
+ continue
600
+ else:
601
+ data[key] = value
602
+ else:
603
+ data["output"] = result_text
604
+ return data
605
+
606
+
607
+ async def dispatch_persona(persona: str, plan: dict, skip_llm: bool = False) -> dict:
608
+ """Dispatch a persona via `claude -p --agent <persona> --output-format json`.
609
+
610
+ In --skip-llm mode, returns a stub success result without invoking the CLI.
611
+ """
612
+ print(f"INFO: dispatching persona {persona}", file=sys.stderr)
613
+ if skip_llm:
614
+ return {"persona": persona, "status": "success"}
615
+ try:
616
+ # A non-serializable plan value degrades to a failure stub, scoped
617
+ # to this one call, instead of raising out of this coroutine and
618
+ # breaking the asyncio.gather() fan-out in dispatch_personas() for
619
+ # every sibling persona in the same wave. Kept as its own try/except
620
+ # (distinct from the subprocess dispatch below) so a TypeError/
621
+ # ValueError from a genuine bug in the dispatch machinery itself is
622
+ # never mislabeled as this same, narrower serialization failure.
623
+ task_prompt = json.dumps(plan)
624
+ except (TypeError, ValueError):
625
+ return _failed_result(persona, error="unserializable_plan")
626
+ try:
627
+ # Offload to a thread so asyncio.gather over multiple personas actually
628
+ # overlaps instead of blocking the event loop on subprocess.run (#1213).
629
+ loop = asyncio.get_running_loop()
630
+ result = await loop.run_in_executor(
631
+ None,
632
+ functools.partial(
633
+ subprocess.run,
634
+ [
635
+ "claude",
636
+ "-p",
637
+ "--agent",
638
+ persona,
639
+ "--output-format",
640
+ "json",
641
+ task_prompt,
642
+ ],
643
+ capture_output=True,
644
+ text=True,
645
+ timeout=PERSONA_DISPATCH_TIMEOUT_S,
646
+ ),
647
+ )
648
+ except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
649
+ return _failed_result(persona, error="llm_unavailable")
650
+
651
+ return _parse_dispatch_envelope(result.stdout, persona)
652
+
653
+
654
+ async def dispatch_personas(personas: list, plan: dict, skip_llm: bool = False) -> list:
655
+ """Dispatch all personas concurrently and return their results.
656
+
657
+ return_exceptions=True keeps one persona's unexpected exception (any
658
+ throwable dispatch_persona's own try/except doesn't already convert to a
659
+ failure stub) from cancelling its siblings' in-flight dispatches —
660
+ Research's contract is to aggregate and persist every persona's outcome,
661
+ never to let one bad result silently discard the rest. Matched here on
662
+ BaseException, not Exception: asyncio.CancelledError has subclassed
663
+ BaseException directly (not Exception) since Python 3.8, and a cancelled
664
+ child task's result is exactly what return_exceptions=True aggregates
665
+ here rather than propagates — an Exception-only guard would let it
666
+ through un-normalized and fail JSON serialization downstream.
667
+ """
668
+ tasks = [dispatch_persona(p, plan, skip_llm) for p in personas]
669
+ results = await asyncio.gather(*tasks, return_exceptions=True)
670
+ return [
671
+ # Distinct from "llm_unavailable" (a CLI/subprocess-level failure,
672
+ # already handled inside dispatch_persona's own try/except): this
673
+ # branch means something threw out of the coroutine itself — a
674
+ # cancellation or an unforeseen bug — which is not evidence the LLM
675
+ # was unreachable, and the persisted research state must keep the
676
+ # two distinguishable.
677
+ _failed_result(p, error="dispatch_exception") if isinstance(r, BaseException) else r
678
+ for p, r in zip(personas, results)
679
+ ]
680
+
681
+
682
+ async def reconcile(results: list, wave_slices: list) -> None:
683
+ """Check wave results; raise WaveError if any slice failed."""
684
+ failed = [r for r in results if r.get("status") == "failed"]
685
+ if failed:
686
+ raise WaveError(
687
+ failing_slice=failed[0]["slice"],
688
+ succeeded=[r["slice"] for r in results if r.get("status") == "success"],
689
+ )
690
+
691
+
692
+ def _print_wave_failure(failing_slice: str) -> None:
693
+ """Print the shared two-line wave-barrier-failure message to stderr.
694
+
695
+ Shared by the --fail-wave simulation branch and the real WaveError-catch
696
+ site in run_pipeline so the resume-hint message has one normative
697
+ definition instead of two independently maintained copies of the same
698
+ two print() calls.
699
+ """
700
+ print(f"ERROR: wave barrier failed on slice '{failing_slice}'", file=sys.stderr)
701
+ print(f"Resume with: python3 {SCRIPTS / 'orchestrator.py'} --resume", file=sys.stderr)
702
+
703
+
704
+ def _resolve_default(fn, default_fn):
705
+ """Return fn if the caller supplied one, else default_fn.
706
+
707
+ Shared by run_pipeline's phase_research_fn/phase_plan_fn/
708
+ phase_implement_fn injection points so each doesn't add its own
709
+ hand-written `if X_fn is None: X_fn = ...` branch to run_pipeline's own
710
+ body, each one pushing its cyclomatic complexity and parameter count
711
+ higher.
712
+ """
713
+ return fn if fn is not None else default_fn
714
+
715
+
716
+ async def _run_phase(
717
+ phase_name: str, phase_fn, memory_dir: Path, resume: bool, *fn_args
718
+ ) -> dict:
719
+ """Resume-check/dispatch/write a phase's state, shared by every phase.
720
+
721
+ Reads the phase's prior state from disk when resuming; otherwise calls
722
+ phase_fn with fn_args and persists the result via write_progress. This is
723
+ the shape run_pipeline previously hand-inlined once for Research
724
+ (Slice 2) — extracted here so the Plan and Implement call sites reuse
725
+ one definition instead of re-copying it.
726
+ """
727
+ state = read_progress(phase_name, memory_dir) if resume else None
728
+ if state is None:
729
+ state = await phase_fn(*fn_args)
730
+ write_progress(phase_name, state, memory_dir)
731
+ return state
732
+
733
+
734
+ # ---------------------------------------------------------------------------
735
+ # Main pipeline
736
+ # ---------------------------------------------------------------------------
737
+
738
+
739
+ async def run_pipeline(
740
+ request: str,
741
+ memory_dir: Path,
742
+ skip_llm: bool = False,
743
+ resume: bool = False,
744
+ classify_fn=None,
745
+ phase_research_fn=None,
746
+ phase_plan_fn=None,
747
+ phase_implement_fn=None,
748
+ fail_wave: bool = False,
749
+ dispatch_personas_flag: bool = False,
750
+ ) -> int:
751
+ """Main orchestration pipeline. Returns exit code (0=success, 1=error)."""
752
+ # Resolve inject-able dependencies
753
+ if classify_fn is None:
754
+ task = await classify(request, skip_llm)
755
+ else:
756
+ task = classify_fn(request)
757
+ if asyncio.iscoroutine(task):
758
+ task = await task
759
+
760
+ phase_research_fn = _resolve_default(phase_research_fn, _default_phase_research)
761
+ phase_plan_fn = _resolve_default(phase_plan_fn, _default_phase_plan)
762
+ phase_implement_fn = _resolve_default(phase_implement_fn, _default_phase_implement)
763
+
764
+ # Fast path for trivial tasks
765
+ if task.get("size") == "trivial":
766
+ print("INFO: trivial task — taking fast path", file=sys.stderr)
767
+ return 0
768
+
769
+ # --resume guard: fail if no state exists at all
770
+ if resume:
771
+ state_files = list(memory_dir.glob("orchestrator-*.json"))
772
+ if not state_files:
773
+ print(
774
+ "ERROR: No prior phase state found; run without --resume to start a new pipeline",
775
+ file=sys.stderr,
776
+ )
777
+ return 1
778
+
779
+ # Wave barrier failure simulation (for testing)
780
+ if fail_wave:
781
+ _print_wave_failure("slice-1")
782
+ return 1
783
+
784
+ # Persona dispatch (for testing --dispatch-personas flag)
785
+ if dispatch_personas_flag:
786
+ await dispatch_personas(
787
+ DEFAULT_PERSONAS,
788
+ plan={"task": task, "request": request},
789
+ skip_llm=skip_llm,
790
+ )
791
+ return 0
792
+
793
+ # Phase 1: Research
794
+ research_state = await _run_phase(
795
+ "research", phase_research_fn, memory_dir, resume, request, task, skip_llm
796
+ )
797
+
798
+ # Phase 2: Plan
799
+ plan_state = await _run_phase(
800
+ "plan", phase_plan_fn, memory_dir, resume, request, task, research_state, skip_llm
801
+ )
802
+
803
+ # Phase 3: Implement
804
+ try:
805
+ await _run_phase(
806
+ "implement", phase_implement_fn, memory_dir, resume, request, task, plan_state, skip_llm
807
+ )
808
+ except WaveError as exc:
809
+ _print_wave_failure(exc.failing_slice)
810
+ return 1
811
+
812
+ return 0
813
+
814
+
815
+ # ---------------------------------------------------------------------------
816
+ # Entry point
817
+ # ---------------------------------------------------------------------------
818
+
819
+
820
+ def _resolve_request_from_stdin() -> str:
821
+ """Return the piped stdin request, or "default request" as fallback.
822
+
823
+ Tests stdin CONTENT, not just whether it's a tty: a piped-but-empty
824
+ stdin (e.g. `< /dev/null` in a hook/CI invocation) must still resolve
825
+ to "default request" — an empty request now drives live persona
826
+ dispatch (the Research phase), not just classify().
827
+ """
828
+ piped_text = sys.stdin.read().strip() if not sys.stdin.isatty() else ""
829
+ return piped_text or "default request"
830
+
831
+
832
+ def main(argv=None) -> int:
833
+ ap = argparse.ArgumentParser(description=__doc__)
834
+ ap.add_argument(
835
+ "--resume",
836
+ action="store_true",
837
+ help="Skip phases whose state files already exist",
838
+ )
839
+ ap.add_argument(
840
+ "--skip-llm",
841
+ action="store_true",
842
+ help="Use stubs for classify() and all LLM dispatch",
843
+ )
844
+ ap.add_argument(
845
+ "--memory-dir",
846
+ default=".claude/memory",
847
+ metavar="PATH",
848
+ help="Where to read/write phase state (default: .claude/memory/)",
849
+ )
850
+ ap.add_argument(
851
+ "--classify",
852
+ default=None,
853
+ metavar="SIZE",
854
+ choices=["trivial", "standard", "complex"],
855
+ help="Override classification (for testing)",
856
+ )
857
+ ap.add_argument(
858
+ "--fail-wave",
859
+ action="store_true",
860
+ help="Simulate a wave barrier failure (for testing)",
861
+ )
862
+ ap.add_argument(
863
+ "--dispatch-personas",
864
+ action="store_true",
865
+ help="Dispatch plan-review personas (for testing)",
866
+ )
867
+ args = ap.parse_args(argv)
868
+
869
+ request = _resolve_request_from_stdin()
870
+ memory_dir = Path(args.memory_dir)
871
+
872
+ # Build classify_fn: use CLI override if provided
873
+ classify_fn = None
874
+ if args.classify:
875
+ size = args.classify
876
+
877
+ def classify_fn(req, _size=size):
878
+ return {"size": _size}
879
+
880
+ exit_code = asyncio.run(
881
+ run_pipeline(
882
+ request=request,
883
+ memory_dir=memory_dir,
884
+ skip_llm=args.skip_llm,
885
+ resume=args.resume,
886
+ classify_fn=classify_fn,
887
+ fail_wave=args.fail_wave,
888
+ dispatch_personas_flag=args.dispatch_personas,
889
+ )
890
+ )
891
+ return exit_code
892
+
893
+
894
+ if __name__ == "__main__":
895
+ sys.exit(main())