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,720 @@
1
+ #!/usr/bin/env python3
2
+ """gherkin_feature_merge.py — merge newly-derived scenarios into an existing
3
+ .feature file without destroying prior content (issue #1420).
4
+
5
+ `gherkin-derive` used to overwrite `features/<surface>.feature` unconditionally
6
+ on every run. This module gives it a safe alternative: read the existing file,
7
+ locate the named `Feature:` block, skip any candidate scenario whose title
8
+ already exists there (exact match, trimmed), and append only genuinely-new
9
+ scenarios after the block's last existing unit — never mid-block, never
10
+ rewriting untouched text.
11
+
12
+ Two failure causes are distinguished by construction, not inferred after the
13
+ fact: `feature-not-found` (the named `Feature:` title isn't in the existing
14
+ file at all — most likely a human renamed it) and `malformed-feature-block`
15
+ (the title is found, but the block's structure can't be bounded — a dangling
16
+ `@tag` line with no following `Scenario:`, or a `Scenario Outline:` whose
17
+ `Examples:` keyword has no table rows). Both refuse to write rather than
18
+ silently corrupting or duplicating content.
19
+
20
+ Stdlib-only. (ADR 0014/0015).
21
+
22
+ Usage:
23
+ python3 gherkin_feature_merge.py merge --existing <path> --candidates <path> \
24
+ --feature-title "<title>" [--dry-run] [--json]
25
+ python3 gherkin_feature_merge.py check-stale --existing <path> \
26
+ --observed "<title>=<value>" [--observed "<title>=<value>" ...] [--json]
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import argparse
32
+ import json
33
+ import os
34
+ import sys
35
+ import tempfile
36
+ from dataclasses import dataclass
37
+ from pathlib import Path
38
+
39
+ _HERE = Path(__file__).resolve().parent
40
+ sys.path.insert(0, str(_HERE / "lib"))
41
+
42
+ from _gherkin_text import FEATURE_PREFIX as _FEATURE_PREFIX
43
+ from _gherkin_text import SCENARIO_OUTLINE_PREFIX as _SCENARIO_OUTLINE_PREFIX
44
+ from _gherkin_text import SCENARIO_PREFIX as _SCENARIO_PREFIX
45
+ from _gherkin_text import is_tag_line as _is_tag_line
46
+ from _gherkin_text import safe_for_terminal as _safe_for_terminal
47
+ from _gherkin_text import stripped as _stripped
48
+ from _gherkin_text import trim_trailing_tag_run as _trim_trailing_tag_run
49
+
50
+ _BACKGROUND_PREFIX = "Background:"
51
+ _SCENARIO_PREFIXES = (_SCENARIO_OUTLINE_PREFIX, _SCENARIO_PREFIX)
52
+ _EXAMPLES_PREFIX = "Examples:"
53
+
54
+ # The three error sentinels, named once so every return site shares one
55
+ # source of truth instead of a repeated raw literal. (Test assertions also
56
+ # import these directly rather than repeating the string values.)
57
+ ERROR_FEATURE_NOT_FOUND = "feature-not-found"
58
+ ERROR_MALFORMED_FEATURE_BLOCK = "malformed-feature-block"
59
+ ERROR_UNSAFE_PATH = "unsafe-path"
60
+ # _cmd_merge-only: parse_candidate_units reuses ERROR_MALFORMED_FEATURE_BLOCK
61
+ # internally for a malformed --candidates fragment (it shares _parse_units
62
+ # with the existing-block parser, which has only one malformed-shape error).
63
+ # The CLI remaps it to this distinct code so a --json caller can tell "your
64
+ # scratch candidates file is broken" from "the existing .feature file's
65
+ # structure is broken" — two different files, two different remediations —
66
+ # without parse_candidate_units itself needing a second error value.
67
+ ERROR_MALFORMED_CANDIDATES = "malformed-candidates"
68
+
69
+
70
+ @dataclass
71
+ class ScenarioUnit:
72
+ """One `Scenario:`/`Scenario Outline:` unit — its title plus verbatim text
73
+ (including any preceding `@tag` lines and, for an Outline, its Examples
74
+ table) spanning from its start marker to the next unit or block end."""
75
+
76
+ title: str
77
+ line: int # 1-based line number of the Scenario:/Scenario Outline: line
78
+ text: str
79
+
80
+
81
+ @dataclass
82
+ class FeatureBlock:
83
+ """A bounded `Feature:` block: header line, optional Background text
84
+ (verbatim), and its ordered scenario units."""
85
+
86
+ header_line: int # 1-based
87
+ end_index: int # 0-based exclusive index (into split lines) where the
88
+ # block's body ends — the boundary `merge_scenarios` splices at.
89
+ # Carried on the parsed block itself so a caller that already has a
90
+ # `FeatureBlock` never needs a second `_find_feature_header`/`_block_end`
91
+ # scan to re-derive where it ends.
92
+ background: str | None
93
+ units: list
94
+
95
+
96
+ @dataclass
97
+ class ParseResult:
98
+ block: FeatureBlock | None
99
+ error: str | None
100
+
101
+
102
+ def _split_lines(text: str) -> list:
103
+ """Split preserving original line endings so reconstruction is byte-exact."""
104
+ return text.splitlines(keepends=True)
105
+
106
+
107
+ def _scenario_title(line: str) -> str | None:
108
+ """Return the title text if `line` is a Scenario:/Scenario Outline: line."""
109
+ stripped = _stripped(line).strip()
110
+ for prefix in _SCENARIO_PREFIXES:
111
+ if stripped.startswith(prefix):
112
+ return stripped[len(prefix) :].strip()
113
+ return None
114
+
115
+
116
+ def _is_unit_start(line: str) -> bool:
117
+ return _is_tag_line(line) or _scenario_title(line) is not None
118
+
119
+
120
+ def _find_feature_header(lines: list, feature_title: str) -> int | None:
121
+ target = feature_title.strip()
122
+ for i, line in enumerate(lines):
123
+ stripped = _stripped(line).strip()
124
+ if stripped.startswith(_FEATURE_PREFIX):
125
+ title = stripped[len(_FEATURE_PREFIX) :].strip()
126
+ if title == target:
127
+ return i
128
+ return None
129
+
130
+
131
+ def _block_end(lines: list, header_index: int) -> int:
132
+ """Index (exclusive) where the block ends: the next Feature: header
133
+ (any title), or end of file. A `@tag`/blank-line run immediately
134
+ preceding that next header belongs to the *next* block (Gherkin tags
135
+ attach to the declaration that follows them), so the boundary backs up
136
+ before it rather than swallowing it into this block's body — otherwise
137
+ a tagged Feature block anywhere but last in the file makes `_parse_units`
138
+ see a dangling tag line and wrongly refuse the merge as
139
+ malformed-feature-block."""
140
+ for j in range(header_index + 1, len(lines)):
141
+ if _stripped(lines[j]).strip().startswith(_FEATURE_PREFIX):
142
+ return _trim_trailing_tag_run(lines, header_index, j)
143
+ return len(lines)
144
+
145
+
146
+ def _find_next_unit_start(body: list, start: int, n: int) -> int:
147
+ """Return the index of the next unit-start line (a tag line or a
148
+ Scenario:/Scenario Outline: line) at or after `start`, or `n` if none
149
+ remains before the block ends. Shared by `_find_background` (where does
150
+ the Background section end) and `_locate_unit` (where does this unit
151
+ end) — both are the same "scan forward for the next unit marker"
152
+ question."""
153
+ for m in range(start, n):
154
+ if _is_unit_start(body[m]):
155
+ return m
156
+ return n
157
+
158
+
159
+ def _find_background(body: list) -> tuple:
160
+ """Return (background_text_or_None, body_start_index) for the region
161
+ of `body` before the first scenario unit."""
162
+ n = len(body)
163
+ for k, line in enumerate(body):
164
+ if _stripped(line).strip().startswith(_BACKGROUND_PREFIX):
165
+ bg_end = _find_next_unit_start(body, k + 1, n)
166
+ return "".join(body[k:bg_end]), bg_end
167
+ if _is_unit_start(line):
168
+ return None, k
169
+ return None, n
170
+
171
+
172
+ def _outline_missing_examples_table(body: list, title_offset: int, unit_end: int) -> bool:
173
+ """True when the Scenario Outline starting at `title_offset` declares an
174
+ `Examples:` keyword but the table has no rows before `unit_end` — the
175
+ one structurally malformed shape a Scenario Outline unit can take.
176
+ (A Scenario Outline with no `Examples:` keyword at all isn't this
177
+ function's concern — that's a content choice, not a structural error.)"""
178
+ examples_idx = None
179
+ for m in range(title_offset + 1, unit_end):
180
+ if _stripped(body[m]).strip().startswith(_EXAMPLES_PREFIX):
181
+ examples_idx = m
182
+ break
183
+ if examples_idx is None:
184
+ return False
185
+ return not any(
186
+ _stripped(body[m]).strip().startswith("|") for m in range(examples_idx + 1, unit_end)
187
+ )
188
+
189
+
190
+ def _locate_unit(body: list, idx: int, n: int) -> tuple:
191
+ """Find the `(title, title_offset, unit_end)` bounds of the unit
192
+ starting at `idx` — the optional leading `@tag` line(s) through the
193
+ `Scenario:`/`Scenario Outline:` line, up to the next unit-start marker
194
+ or block end. `title` is `None` for a dangling run of tag lines with no
195
+ following Scenario(-Outline) line before the block ends; the caller
196
+ treats that as `malformed-feature-block`."""
197
+ title = None
198
+ title_offset = idx
199
+ for scan in range(idx, n):
200
+ t = _scenario_title(body[scan])
201
+ if t is not None:
202
+ title = t
203
+ title_offset = scan
204
+ break
205
+
206
+ search_from = title_offset + 1 if title is not None else idx + 1
207
+ unit_end = _find_next_unit_start(body, search_from, n)
208
+ return title, title_offset, unit_end
209
+
210
+
211
+ def _parse_units(body: list, body_start: int, header_line_no: int):
212
+ """Parse ordered ScenarioUnits from `body[body_start:]`.
213
+
214
+ Returns (units, None) on success, or (None, "malformed-feature-block")
215
+ on a dangling tag line or an unterminated Scenario Outline (Examples:
216
+ keyword present with no table rows before the unit/block ends).
217
+ """
218
+ units = []
219
+ idx = body_start
220
+ n = len(body)
221
+ while idx < n:
222
+ if not _is_unit_start(body[idx]):
223
+ idx += 1
224
+ continue
225
+
226
+ unit_start = idx
227
+ title, title_offset, unit_end = _locate_unit(body, idx, n)
228
+
229
+ if title is None:
230
+ # A tag line (or run of tag lines) with no following
231
+ # Scenario:/Scenario Outline: before the block ends.
232
+ return None, ERROR_MALFORMED_FEATURE_BLOCK
233
+
234
+ is_outline = _stripped(body[title_offset]).strip().startswith(_SCENARIO_OUTLINE_PREFIX)
235
+ if is_outline and _outline_missing_examples_table(body, title_offset, unit_end):
236
+ return None, ERROR_MALFORMED_FEATURE_BLOCK
237
+
238
+ unit_text = "".join(body[unit_start:unit_end])
239
+ units.append(ScenarioUnit(title=title, line=header_line_no + title_offset + 1, text=unit_text))
240
+ idx = unit_end
241
+
242
+ return units, None
243
+
244
+
245
+ def parse_feature_block(text: str, feature_title: str) -> ParseResult:
246
+ """Locate and bound the `Feature: <feature_title>` block in `text`.
247
+
248
+ The "not found" check runs before any structural parse: if no `Feature:`
249
+ header matches `feature_title` anywhere, `error="feature-not-found"` is
250
+ returned immediately. Only once the header is located does a structural
251
+ failure (a dangling `@tag` line, or a `Scenario Outline:` missing its
252
+ `Examples:` table) yield `error="malformed-feature-block"`.
253
+ """
254
+ lines = _split_lines(text)
255
+ header_index = _find_feature_header(lines, feature_title)
256
+ if header_index is None:
257
+ return ParseResult(block=None, error=ERROR_FEATURE_NOT_FOUND)
258
+
259
+ end_index = _block_end(lines, header_index)
260
+ body = lines[header_index + 1 : end_index]
261
+
262
+ background, body_start = _find_background(body)
263
+ units, error = _parse_units(body, body_start, header_index + 1)
264
+ if error is not None:
265
+ return ParseResult(block=None, error=error)
266
+
267
+ block = FeatureBlock(
268
+ header_line=header_index + 1, end_index=end_index, background=background, units=units
269
+ )
270
+ return ParseResult(block=block, error=None)
271
+
272
+
273
+ def parse_candidate_units(text: str) -> tuple:
274
+ """Parse headerless candidate scenario text (no `Feature:` wrapper) —
275
+ the `--candidates` scratch-file shape — into ordered ScenarioUnits.
276
+ Reuses the same unit-splitting logic `parse_feature_block` uses for the
277
+ inside of a block, since a candidates file is exactly a block's body
278
+ with no Background section expected.
279
+
280
+ Returns `(units, error)`, mirroring `parse_feature_block`'s error
281
+ contract: a structurally malformed candidates fragment (a dangling
282
+ `@tag` line, or a `Scenario Outline:` missing its `Examples:` table) is
283
+ reported as `error="malformed-feature-block"` with `units == []`, never
284
+ silently collapsed to an empty, error-less list — a broken
285
+ `--candidates` scratch file must surface as a diagnosable failure, not
286
+ a silent "merged 0 scenarios" success."""
287
+ body = _split_lines(text)
288
+ units, error = _parse_units(body, 0, 0)
289
+ if error is not None:
290
+ return [], error
291
+ return units, None
292
+
293
+
294
+ @dataclass
295
+ class MergeResult:
296
+ text: str
297
+ added_titles: list
298
+ skipped_duplicate_titles: list
299
+ error: str | None
300
+
301
+
302
+ def _dedupe_candidate_units(units: list) -> tuple:
303
+ """Return `(deduped_units, self_duplicate_titles)`. Two candidates
304
+ sharing a trimmed title would otherwise both be appended — silently
305
+ making one invisible downstream (`find_then_step_text` keys its map by
306
+ title, last-writer-wins), so a self-collision is dropped and reported
307
+ the same way a collision against an existing title already is."""
308
+ seen = set()
309
+ deduped = []
310
+ duplicates = []
311
+ for unit in units:
312
+ key = unit.title.strip()
313
+ if key in seen:
314
+ duplicates.append(unit.title)
315
+ continue
316
+ seen.add(key)
317
+ deduped.append(unit)
318
+ return deduped, duplicates
319
+
320
+
321
+ def _last_line_ending(lines: list) -> str:
322
+ """Return the line ending of the last *terminated* line in `lines`
323
+ (scanning from the end), defaulting to `"\\n"` — so a separator
324
+ inserted at the splice point matches the existing file's convention
325
+ instead of always injecting LF into a CRLF file. This is a last-line
326
+ check, not a majority vote across the file (a mixed-ending file's
327
+ splice separator follows whichever ending is nearest the insertion
328
+ point, not whichever is more common) — only the inserted separator is
329
+ normalized this way; a new candidate unit's own interior line endings
330
+ (as authored) are passed through unchanged, so merging into a CRLF file
331
+ can still yield a file with mixed endings overall."""
332
+ for line in reversed(lines):
333
+ if line.endswith("\r\n"):
334
+ return "\r\n"
335
+ if line.endswith("\n"):
336
+ return "\n"
337
+ return "\n"
338
+
339
+
340
+ def _existing_units_use_blank_line_separator(units: list) -> bool:
341
+ """Infer whether this Feature block's existing scenarios are separated
342
+ by a blank line, from the joins between consecutive existing units —
343
+ excluding the boundary after the very last unit, which sits at the
344
+ block's end and may carry no trailing blank line for a reason unrelated
345
+ to the file's separator convention (there is simply nothing after it to
346
+ separate from). Defaults to `True` when there are fewer than two units
347
+ to compare, matching the blank-line-between-scenarios style most
348
+ existing Gherkin files already use — a majority vote (ties favor
349
+ inserting a blank line) otherwise, so `merge_scenarios` reproduces
350
+ whichever convention this specific file's own scenarios demonstrate
351
+ rather than assuming one."""
352
+ comparable = units[:-1]
353
+ if not comparable:
354
+ return True
355
+ separated = sum(1 for u in comparable if u.text.rstrip(" \t").endswith(("\n\n", "\r\n\r\n")))
356
+ return separated * 2 >= len(comparable)
357
+
358
+
359
+ def merge_scenarios(existing_text: str, feature_title: str, candidate_units: list) -> MergeResult:
360
+ """Append-only merge of `candidate_units` into the named Feature block.
361
+
362
+ Relays `parse_feature_block`'s error unchanged when the block can't be
363
+ located or bounded — this function never re-derives or guesses the
364
+ cause. When `existing_text` is empty, synthesizes a fresh
365
+ `Feature: <feature_title>` block containing exactly the candidates (no
366
+ parser call needed — nothing to locate).
367
+ """
368
+ candidate_units, self_duplicate_titles = _dedupe_candidate_units(candidate_units)
369
+
370
+ if not existing_text:
371
+ header = f"{_FEATURE_PREFIX} {feature_title}\n\n"
372
+ body = "".join(unit.text for unit in candidate_units)
373
+ return MergeResult(
374
+ text=header + body,
375
+ added_titles=[unit.title for unit in candidate_units],
376
+ skipped_duplicate_titles=self_duplicate_titles,
377
+ error=None,
378
+ )
379
+
380
+ result = parse_feature_block(existing_text, feature_title)
381
+ if result.error is not None:
382
+ return MergeResult(
383
+ text=existing_text,
384
+ added_titles=[],
385
+ skipped_duplicate_titles=[],
386
+ error=result.error,
387
+ )
388
+ # parse_feature_block's contract: block is set whenever error is None.
389
+ assert result.block is not None
390
+
391
+ existing_titles = {unit.title.strip() for unit in result.block.units}
392
+ new_units = [u for u in candidate_units if u.title.strip() not in existing_titles]
393
+ skipped = self_duplicate_titles + [
394
+ u.title for u in candidate_units if u.title.strip() in existing_titles
395
+ ]
396
+
397
+ if not new_units:
398
+ return MergeResult(
399
+ text=existing_text, added_titles=[], skipped_duplicate_titles=skipped, error=None
400
+ )
401
+
402
+ # Reuse the boundary `parse_feature_block` already computed above rather
403
+ # than re-deriving it with a second, type-unsafe `_find_feature_header`/
404
+ # `_block_end` pass — `result.block` is guaranteed non-None here since
405
+ # `result.error is None` was just checked.
406
+ lines = _split_lines(existing_text)
407
+ end_index = result.block.end_index
408
+ ending = _last_line_ending(lines)
409
+
410
+ insertion = "".join(unit.text for unit in new_units)
411
+ # Insert right at the block's end boundary, preserving everything before
412
+ # and after byte-for-byte — except that a missing line terminator right
413
+ # at the splice point (existing_text has no trailing newline, or the
414
+ # last new unit's text doesn't end in one) is restored, matching the
415
+ # file's own dominant line ending, rather than left to fuse two Gherkin
416
+ # lines into one. This never rewrites either line's content, only the
417
+ # separator whose absence would otherwise corrupt it.
418
+ prefix_lines = list(lines[:end_index])
419
+ if prefix_lines and not prefix_lines[-1].endswith("\n"):
420
+ prefix_lines[-1] += ending
421
+ if insertion and not insertion.endswith("\n"):
422
+ insertion += ending
423
+ # The last existing unit sits at the block's end with nothing after it
424
+ # to separate from, so its own text alone can't say whether this file
425
+ # puts a blank line between scenarios — infer that from the *other*
426
+ # existing joins instead, and reproduce it here rather than assuming
427
+ # one convention for every repo (issue: a splice landing flush against
428
+ # the last scenario's final line, with no separating blank line, even
429
+ # when every other scenario boundary in the file has one).
430
+ if (
431
+ prefix_lines
432
+ and prefix_lines[-1].strip() != ""
433
+ and _existing_units_use_blank_line_separator(result.block.units)
434
+ ):
435
+ prefix_lines.append(ending)
436
+ new_lines = prefix_lines + [insertion] + lines[end_index:]
437
+ merged_text = "".join(new_lines)
438
+
439
+ return MergeResult(
440
+ text=merged_text,
441
+ added_titles=[u.title for u in new_units],
442
+ skipped_duplicate_titles=skipped,
443
+ error=None,
444
+ )
445
+
446
+
447
+ def find_then_step_text(existing_text: str, feature_title: str) -> dict:
448
+ """Map each retained scenario's title to `(line, then_text)` — the
449
+ verbatim `Then`/`And`/`But`-continuation step text (joined) plus the
450
+ scenario's 1-based line number, for the stale-scenario check below.
451
+ The line is threaded through (not just the text) so `check-stale` can
452
+ report `<file>:<line>`, the format `gherkin-derive/SKILL.md` Step 6
453
+ mandates — without it the line number `ScenarioUnit.line` already
454
+ computes would be silently dropped on the floor. `But` is included
455
+ alongside `And` because Gherkin permits either as a Then-continuation
456
+ (e.g. `Then the request is rejected / But no partial record is
457
+ written`) — omitting it would silently drop part of the asserted
458
+ behavior from the comparison `is_stale` makes below."""
459
+ result = parse_feature_block(existing_text, feature_title)
460
+ if result.error is not None or result.block is None:
461
+ return {}
462
+
463
+ out = {}
464
+ for unit in result.block.units:
465
+ then_lines = []
466
+ capturing = False
467
+ for line in _split_lines(unit.text):
468
+ stripped = _stripped(line).strip()
469
+ if stripped.startswith("Then "):
470
+ capturing = True
471
+ then_lines.append(line)
472
+ elif capturing and stripped.startswith(("And ", "But ")):
473
+ then_lines.append(line)
474
+ elif capturing:
475
+ capturing = False
476
+ out[unit.title] = (unit.line, "".join(then_lines))
477
+ return out
478
+
479
+
480
+ def is_stale(then_text: str, observed_value: str) -> bool:
481
+ """Best-effort, case-insensitive substring containment check: `False`
482
+ (not stale) when `observed_value` appears in `then_text`, `True`
483
+ otherwise. A scenario with no Then step (`then_text == ""`) is never
484
+ reported stale — nothing to compare."""
485
+ if not then_text:
486
+ return False
487
+ return observed_value.lower() not in then_text.lower()
488
+
489
+
490
+ def _write_error(message: str) -> None:
491
+ sys.stderr.write(message + "\n")
492
+
493
+
494
+ def _reject_path_traversal(raw: str) -> str | None:
495
+ """Return an error message if `raw` contains a `..` path component,
496
+ else `None`. `--existing` is not always typed by a human — gherkin-
497
+ derive/SKILL.md Step 2 composes it from a surface name derived from the
498
+ *target repository's* own content (OpenAPI paths, route strings,
499
+ exported symbol names), so a hostile or malformed segment containing
500
+ `../` must not be allowed to steer the write (or the read) outside the
501
+ intended directory."""
502
+ if ".." in Path(raw).parts:
503
+ return f"--existing {raw!r} must not contain a '..' path component"
504
+ return None
505
+
506
+
507
+ def _merge_payload(written: bool, added=(), skipped=(), error=None) -> dict:
508
+ """The one JSON shape every `merge` response uses (`check-stale` has its
509
+ own distinct `{findings, unmatched_titles}` shape, built separately in
510
+ `_cmd_check_stale` — not this helper), named once so a future field
511
+ addition or rename to the `merge` response touches this single place
512
+ instead of every `merge`-side call site rebuilding the dict by hand."""
513
+ return {
514
+ "written": written,
515
+ "added_titles": list(added),
516
+ "skipped_duplicate_titles": list(skipped),
517
+ "error": error,
518
+ }
519
+
520
+
521
+ def _write_atomic(path: Path, text: str) -> None:
522
+ """Write `text` to `path` via a same-directory temp file + os.replace, so a
523
+ crash or kill mid-write can never leave a truncated/corrupted .feature
524
+ file — the one failure mode this module exists to rule out."""
525
+ fd, tmp_name = tempfile.mkstemp(dir=str(path.parent), prefix=f".{path.name}.", suffix=".tmp")
526
+ try:
527
+ with os.fdopen(fd, "w", encoding="utf-8") as tmp_file:
528
+ tmp_file.write(text)
529
+ os.replace(tmp_name, path)
530
+ except BaseException:
531
+ Path(tmp_name).unlink(missing_ok=True)
532
+ raise
533
+
534
+
535
+ def _cmd_merge(args: argparse.Namespace) -> int:
536
+ # Known limitation (concurrency-review, issue #1420 follow-up review):
537
+ # read-then-write here is still a TOCTOU race if two `merge` invocations
538
+ # ever target the same --existing path concurrently — whichever writes
539
+ # last silently wins (the atomic write below only rules out a truncated
540
+ # file, not a lost update between two racing invocations). Not fixed:
541
+ # gherkin-derive's own design invokes this once per surface,
542
+ # sequentially, never in parallel against the same file (see
543
+ # gherkin-derive/SKILL.md's Output step), so there is no known live
544
+ # trigger; a cross-platform lock (fcntl vs. msvcrt) would close this
545
+ # remaining gap but is a design call for a human to make, not a
546
+ # mechanical fix — deliberately left to a human decision rather than
547
+ # guessed at here.
548
+ path_error = _reject_path_traversal(args.existing)
549
+ if path_error is not None:
550
+ if args.json:
551
+ print(json.dumps(_merge_payload(written=False, error=ERROR_UNSAFE_PATH)))
552
+ else:
553
+ _write_error(f"gherkin_feature_merge: {path_error} — no scenarios merged")
554
+ return 2
555
+
556
+ existing_path = Path(args.existing)
557
+ existing_text = existing_path.read_text(encoding="utf-8") if existing_path.is_file() else ""
558
+ candidates_text = Path(args.candidates).read_text(encoding="utf-8")
559
+ candidate_units, candidates_error = parse_candidate_units(candidates_text)
560
+
561
+ if candidates_error is not None:
562
+ if args.json:
563
+ print(json.dumps(_merge_payload(written=False, error=ERROR_MALFORMED_CANDIDATES)))
564
+ else:
565
+ _write_error(
566
+ f"gherkin_feature_merge: candidates file {_safe_for_terminal(args.candidates)} "
567
+ f"is malformed (error={candidates_error}) — no scenarios merged, existing file "
568
+ f"left untouched"
569
+ )
570
+ return 2
571
+
572
+ result = merge_scenarios(existing_text, args.feature_title, candidate_units)
573
+
574
+ if result.error is not None:
575
+ if args.json:
576
+ print(json.dumps(_merge_payload(written=False, error=result.error)))
577
+ elif result.error == ERROR_FEATURE_NOT_FOUND:
578
+ _write_error(
579
+ f"gherkin_feature_merge: could not locate Feature: {args.feature_title!r} "
580
+ f"in {_safe_for_terminal(args.existing)} — no scenarios merged, existing file "
581
+ f"left untouched (error={result.error})"
582
+ )
583
+ else:
584
+ _write_error(
585
+ f"gherkin_feature_merge: found Feature: {args.feature_title!r} in "
586
+ f"{_safe_for_terminal(args.existing)} but its structure could not be parsed — "
587
+ f"no scenarios merged, existing file left untouched (error={result.error})"
588
+ )
589
+ return 2
590
+
591
+ if args.dry_run:
592
+ if args.json:
593
+ print(
594
+ json.dumps(
595
+ _merge_payload(
596
+ written=False,
597
+ added=result.added_titles,
598
+ skipped=result.skipped_duplicate_titles,
599
+ )
600
+ )
601
+ )
602
+ else:
603
+ sys.stdout.write(result.text)
604
+ return 0
605
+
606
+ existing_path.parent.mkdir(parents=True, exist_ok=True)
607
+ _write_atomic(existing_path, result.text)
608
+
609
+ if args.json:
610
+ print(
611
+ json.dumps(
612
+ _merge_payload(
613
+ written=True,
614
+ added=result.added_titles,
615
+ skipped=result.skipped_duplicate_titles,
616
+ )
617
+ )
618
+ )
619
+ else:
620
+ print(
621
+ f"OK: merged {len(result.added_titles)} new scenario(s) into "
622
+ f"{_safe_for_terminal(args.existing)}"
623
+ )
624
+ return 0
625
+
626
+
627
+ def _cmd_check_stale(args: argparse.Namespace) -> int:
628
+ # Same untrusted, SKILL.md-composed provenance as --existing in _cmd_merge
629
+ # (security-review) — the guard applies to both subcommands, not just the
630
+ # one that writes, so a future change that starts echoing parsed content
631
+ # here doesn't quietly reopen the gap.
632
+ path_error = _reject_path_traversal(args.existing)
633
+ if path_error is not None:
634
+ if args.json:
635
+ print(json.dumps({"findings": [], "unmatched_titles": [], "error": ERROR_UNSAFE_PATH}))
636
+ else:
637
+ _write_error(f"gherkin_feature_merge: {path_error}")
638
+ return 2
639
+
640
+ existing_path = Path(args.existing)
641
+ existing_text = existing_path.read_text(encoding="utf-8") if existing_path.is_file() else ""
642
+
643
+ observed = {}
644
+ for raw in args.observed:
645
+ # rpartition (not partition): the title is the untrusted, arbitrary-
646
+ # content half of "<title>=<value>", so a title containing its own
647
+ # "=" (e.g. "Create order fails when qty=0") must not be truncated
648
+ # at the first "=" — split at the last one instead.
649
+ title, _, value = raw.rpartition("=")
650
+ observed[title.strip()] = value
651
+
652
+ then_texts = find_then_step_text(existing_text, args.feature_title)
653
+
654
+ findings = []
655
+ unmatched_titles = []
656
+ for title, value in observed.items():
657
+ entry = then_texts.get(title)
658
+ if entry is None:
659
+ # The observed title isn't an exact key in the retained block —
660
+ # e.g. the caller's title-extraction diverged from the retained
661
+ # scenario's exact text. Report this distinctly rather than
662
+ # silently treating it the same as "nothing to compare"; drift
663
+ # detection is the entire point of this command, so a
664
+ # title-match failure must be diagnosable, not invisible.
665
+ unmatched_titles.append(title)
666
+ continue
667
+ line, then_text = entry
668
+ if is_stale(then_text, value):
669
+ findings.append(
670
+ {
671
+ "title": title,
672
+ "line": line,
673
+ "then_text": then_text.strip(),
674
+ "observed": value,
675
+ }
676
+ )
677
+
678
+ if args.json:
679
+ print(json.dumps({"findings": findings, "unmatched_titles": unmatched_titles}))
680
+ else:
681
+ for entry in findings:
682
+ print(
683
+ f"possibly stale: {_safe_for_terminal(args.existing)}:{entry['line']} "
684
+ f"({entry['title']!r}) — asserts {entry['then_text']!r}, "
685
+ f"code now does {entry['observed']!r}"
686
+ )
687
+ for title in unmatched_titles:
688
+ print(
689
+ f"unmatched: --observed title {title!r} not found among retained scenarios "
690
+ f"in {args.feature_title!r} — check for a title mismatch between the "
691
+ f"observed title and the scenario it should describe"
692
+ )
693
+ return 0
694
+
695
+
696
+ def main(argv: list | None = None) -> int:
697
+ parser = argparse.ArgumentParser(prog="gherkin_feature_merge.py")
698
+ sub = parser.add_subparsers(dest="command", required=True)
699
+
700
+ merge_parser = sub.add_parser("merge")
701
+ merge_parser.add_argument("--existing", required=True)
702
+ merge_parser.add_argument("--candidates", required=True)
703
+ merge_parser.add_argument("--feature-title", required=True)
704
+ merge_parser.add_argument("--dry-run", action="store_true")
705
+ merge_parser.add_argument("--json", action="store_true")
706
+ merge_parser.set_defaults(func=_cmd_merge)
707
+
708
+ stale_parser = sub.add_parser("check-stale")
709
+ stale_parser.add_argument("--existing", required=True)
710
+ stale_parser.add_argument("--feature-title", required=True)
711
+ stale_parser.add_argument("--observed", action="append", default=[])
712
+ stale_parser.add_argument("--json", action="store_true")
713
+ stale_parser.set_defaults(func=_cmd_check_stale)
714
+
715
+ args = parser.parse_args(argv)
716
+ return args.func(args)
717
+
718
+
719
+ if __name__ == "__main__": # pragma: no cover
720
+ sys.exit(main())