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,1273 @@
1
+ """session_report_maintainer.py — the maintainer profile of session_report.py
2
+ (predecessor: scripts/session_extract.py).
3
+
4
+ Split out of session_report.py (issue #2098): everything specific to
5
+ `--profile maintainer` — monorepo-only developer tooling that feeds
6
+ `/session-review` and the `session-digest.jsonl` trend stream this repo uses
7
+ to judge its own harness. Emits `session-digest/v4` (`extract_maintainer`)
8
+ plus the `--rollup`/`--cost-log`/`--escalate`/`--correlate`/`--sync-out`
9
+ modes. See session_report.py's own module docstring for the full profile
10
+ contract; this module holds the implementation, not the CLI surface.
11
+
12
+ PATH RESOLUTION (ADR 0032): this module lives at
13
+ plugins/dev-team/scripts/lib/, one level deeper than session_report.py
14
+ itself, so its own __file__-relative path resolution needs one more
15
+ `.parent` than the top-level CLI's did before the split.
16
+
17
+ Stdlib only (Python 3.10+ floor, ADR 0031). Deliberately uses
18
+ `timezone.utc`, not `datetime.UTC` (a 3.11+ addition) — see
19
+ session_report.py's module docstring for why.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import hashlib
25
+ import json
26
+ import math
27
+ import os
28
+ import re
29
+ import sys
30
+ from collections import Counter, defaultdict
31
+ from datetime import datetime, timedelta, timezone
32
+ from pathlib import Path
33
+
34
+ _LIB_DIR = Path(__file__).resolve().parent
35
+ if str(_LIB_DIR) not in sys.path:
36
+ sys.path.insert(0, str(_LIB_DIR))
37
+ from session_report_shared import (
38
+ _COMMIT_BYPASS_TOKENS,
39
+ _DIGEST_SCHEMA,
40
+ _HARNESS_ATTRIBUTIONS,
41
+ _MAIN_LABEL,
42
+ _SYNC_SCHEMA,
43
+ _UNATTRIBUTED_LABEL,
44
+ _UNSAFE_NAME,
45
+ _VERSION_RE,
46
+ SYNC_SCHEMAS,
47
+ _accumulate_skill_agent_signals,
48
+ _all_transcripts_under,
49
+ _bash_segments,
50
+ _classify_tool_result,
51
+ _correction_rate_map,
52
+ _detect_correction_turn,
53
+ _finalize_correction_causes,
54
+ _first_cwd,
55
+ _is_git_commit_argv,
56
+ _is_subagent_transcript,
57
+ _load_plugin_version,
58
+ _new_correction_causes_state,
59
+ _new_correction_context,
60
+ _observe_assistant_turn,
61
+ _record_correction_cause,
62
+ _redact,
63
+ _slim,
64
+ _strip_ns,
65
+ _text_of,
66
+ _track_bash,
67
+ _track_edit,
68
+ _track_tool_call,
69
+ records,
70
+ resolve_session_plugin_version,
71
+ signals,
72
+ )
73
+
74
+ # hooks/lib/pricing.py, not session_log/pricing.py (see hooks/lib/cost_meter.py's
75
+ # established rule, #1461/#2045): a hook must be import-safe without any
76
+ # scripts/ module on its path, so the dependency direction is scripts/ ->
77
+ # hooks/lib/, never the reverse. This module lives at plugins/dev-team/
78
+ # scripts/lib/, so parent.parent.parent is plugins/dev-team/, then hooks/lib.
79
+ sys.path.insert(
80
+ 0, str(Path(__file__).resolve().parent.parent.parent / "hooks" / "lib")
81
+ )
82
+ from pricing import cost as _cost
83
+ from pricing import rate as _rate
84
+
85
+ # --- gate-run correlation (#2037) -------------------------------------------
86
+ _GATE_RAN_HOOK = "pre-commit-gate"
87
+ _GATE_RAN_PREFIX = "gate-ran-"
88
+ GATE_RAN_WINDOW_SECONDS = 120
89
+
90
+
91
+ def _parse_event_ts(value) -> datetime | None:
92
+ """Parse a `boundary-events.jsonl`/transcript timestamp
93
+ (`%Y-%m-%dT%H:%M:%SZ`) into an aware UTC `datetime`, or `None` when
94
+ `value` isn't a string in that exact shape."""
95
+ if not isinstance(value, str):
96
+ return None
97
+ try:
98
+ return datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ").replace(
99
+ tzinfo=timezone.utc
100
+ )
101
+ except ValueError:
102
+ return None
103
+
104
+
105
+ def _read_gate_ran_events(path: Path | None) -> list[tuple[datetime, str]]:
106
+ """Every `gate_ran` boundary event's `(ts, verdict)` pair from
107
+ `boundary-events.jsonl`, oldest first."""
108
+ if path is None or not path.is_file():
109
+ return []
110
+ out: list[tuple[datetime, str]] = []
111
+ for rec in _iter_records([path]):
112
+ if not isinstance(rec, dict) or rec.get("hook") != _GATE_RAN_HOOK:
113
+ continue
114
+ if rec.get("decision") != "record":
115
+ continue
116
+ rule = rec.get("matched_rule")
117
+ if not isinstance(rule, str) or not rule.startswith(_GATE_RAN_PREFIX):
118
+ continue
119
+ ts = _parse_event_ts(rec.get("ts"))
120
+ if ts is None:
121
+ continue
122
+ out.append((ts, rule[len(_GATE_RAN_PREFIX) :]))
123
+ out.sort(key=lambda pair: pair[0])
124
+ return out
125
+
126
+
127
+ def _classify_gate_run(
128
+ commit_ts: datetime | None, gate_events: list[tuple[datetime, str]]
129
+ ) -> str:
130
+ """Classify one non-bypassed commit attempt against the gate-run event
131
+ timeline: `"absent"`, `"errored"`, or `"clean"`."""
132
+ if commit_ts is None:
133
+ return "absent"
134
+ window = timedelta(seconds=GATE_RAN_WINDOW_SECONDS)
135
+ best_verdict: str | None = None
136
+ best_delta: timedelta | None = None
137
+ for ts, verdict in gate_events:
138
+ delta = abs(ts - commit_ts)
139
+ if delta <= window and (best_delta is None or delta < best_delta):
140
+ best_verdict, best_delta = verdict, delta
141
+ if best_verdict is None:
142
+ return "absent"
143
+ return "errored" if best_verdict == "errored" else "clean"
144
+
145
+
146
+ def _rewrite_name_keys(mapping: dict) -> dict:
147
+ """Rewrite a peer-supplied name-bearing dict's keys to something safe to
148
+ aggregate. See scripts/session_extract.py's original docstring for the
149
+ full rationale — unchanged here."""
150
+ out: dict = {}
151
+ for k, v in mapping.items():
152
+ key = _redact(_strip_ns(str(k))) if isinstance(k, str) else _UNSAFE_NAME
153
+ out[key] = out.get(key, 0) + _safe_number(v)
154
+ return out
155
+
156
+
157
+ def _iter_records(paths: list[Path]):
158
+ """Yield every decodable JSON record across `paths`, in order."""
159
+ for path in sorted(paths, key=lambda x: str(x)):
160
+ yield from records.iter_file_records(path)
161
+
162
+
163
+ def _accumulate_token_signals_maintainer(
164
+ usage: dict,
165
+ raw_model,
166
+ model,
167
+ skill,
168
+ pricing: dict,
169
+ tokens_total: Counter,
170
+ by_model: dict[str, Counter],
171
+ by_skill: dict[str, Counter],
172
+ ) -> tuple[float, dict]:
173
+ """Token-accounting concern: usage/cost totals split by model and skill,
174
+ on top of `signals.accumulate_token_signals`'s shared core. Returns
175
+ `(cost, safe_usage)`."""
176
+ safe_usage = {f: _safe_number(v) for f, v in records.usage_fields(usage).items()}
177
+ cost = _cost(safe_usage, _rate(pricing, raw_model or ""), pricing)
178
+ signals.accumulate_token_signals(safe_usage, model, tokens_total, by_model)
179
+ if skill:
180
+ for f in records.USAGE_FIELDS:
181
+ by_skill[skill][f] += safe_usage[f]
182
+ if model:
183
+ by_model[model]["cost_micro"] += round(cost * 1e6)
184
+ if skill:
185
+ by_skill[skill]["cost_micro"] += round(cost * 1e6)
186
+ return cost, safe_usage
187
+
188
+
189
+ def extract_maintainer(
190
+ paths: list[Path],
191
+ pricing: dict,
192
+ registry: dict,
193
+ plugin_version: str = "unknown",
194
+ projects_root: Path | None = None,
195
+ boundary_events_path: Path | None = None,
196
+ ) -> dict:
197
+ tokens_total = Counter()
198
+ cost_total = 0.0
199
+ by_model: dict[str, Counter] = defaultdict(Counter)
200
+ by_skill: dict[str, Counter] = defaultdict(Counter)
201
+ by_agent_type: dict[str, dict] = {}
202
+ agent_runs = Counter()
203
+ agent_dispatches = Counter()
204
+ subagent_transcripts = 0
205
+ main_transcripts = 0
206
+ subagent_layout_present = False
207
+ sessions: set[str] = set()
208
+ edits_per_file = Counter()
209
+ retried_bash_by_skill = Counter()
210
+ retried_bash_by_agent = Counter()
211
+ bash_signal_counts = Counter()
212
+ commit_attempt_events: list[tuple[str | None, bool]] = []
213
+ error_counts = Counter()
214
+ compaction_events = 0
215
+ tool_errors = Counter()
216
+ tool_calls = Counter()
217
+ correction_turns = 0
218
+ correction_by_skill = Counter()
219
+ correction_by_agent = Counter()
220
+ correction_causes = _new_correction_causes_state()
221
+
222
+ skills_invoked = Counter()
223
+
224
+ pending_tool: dict[str, str] = {}
225
+ active: dict[str, str | None] = {"skill": None, "agent": None}
226
+
227
+ root = projects_root or Path.home() / ".claude" / "projects"
228
+ for path in paths:
229
+ is_subagent = _is_subagent_transcript(root, path)
230
+ if is_subagent:
231
+ subagent_layout_present = True
232
+ agent_name: str | None = None
233
+ thread_msgs = 0
234
+ thread_usage = signals.new_agent_bucket()
235
+ records_seen = 0
236
+ thread = signals.new_thread()
237
+ pending_tool = {}
238
+ active = {"skill": None, "agent": None}
239
+ turn_context = _new_correction_context()
240
+
241
+ for rec in _iter_records([path]):
242
+ records_seen += 1
243
+ sid = rec.get("sessionId") or rec.get("session_id")
244
+ if sid:
245
+ sessions.add(str(sid))
246
+ if is_subagent and agent_name is None:
247
+ attributed = rec.get("attributionAgent")
248
+ if isinstance(attributed, str) and attributed:
249
+ stripped = _strip_ns(attributed)
250
+ if stripped not in _HARNESS_ATTRIBUTIONS:
251
+ agent_name = _redact(stripped)
252
+ rtype = rec.get("type")
253
+ is_sidechain = bool(rec.get("isSidechain")) or is_subagent
254
+ raw_skill = rec.get("attributionSkill") or rec.get("attribution_skill")
255
+ skill = (
256
+ _redact(_strip_ns(raw_skill))
257
+ if isinstance(raw_skill, str) and raw_skill
258
+ else None
259
+ )
260
+
261
+ if (
262
+ rtype in ("compaction", "summary")
263
+ or rec.get("isCompactSummary")
264
+ or rec.get("compactMetadata")
265
+ ):
266
+ compaction_events += 1
267
+
268
+ msg = rec.get("message") if isinstance(rec.get("message"), dict) else {}
269
+ usage = records.usage_of(rec)
270
+ raw_model = msg.get("model") or rec.get("model")
271
+ raw_model = raw_model if isinstance(raw_model, str) and raw_model else None
272
+ model = _redact(raw_model) if raw_model else None
273
+
274
+ if usage:
275
+ cost, safe_usage = _accumulate_token_signals_maintainer(
276
+ usage,
277
+ raw_model,
278
+ model,
279
+ skill,
280
+ pricing,
281
+ tokens_total,
282
+ by_model,
283
+ by_skill,
284
+ )
285
+ cost_total += cost
286
+ if is_subagent:
287
+ thread_msgs += 1
288
+ thread_usage["messages"] += 1
289
+ for f in signals.CONTEXT_TOKEN_FIELDS:
290
+ thread_usage[f] += safe_usage[f]
291
+ thread_usage["output_tokens"] += safe_usage["output_tokens"]
292
+ else:
293
+ rec_agent = rec.get("attributionAgent")
294
+ if isinstance(rec_agent, str) and rec_agent:
295
+ stripped_rec = _strip_ns(rec_agent)
296
+ inline_label = (
297
+ _UNATTRIBUTED_LABEL
298
+ if stripped_rec in _HARNESS_ATTRIBUTIONS
299
+ else _redact(stripped_rec)
300
+ )
301
+ elif is_sidechain:
302
+ inline_label = "sidechain"
303
+ else:
304
+ inline_label = _MAIN_LABEL
305
+ bucket = by_agent_type.setdefault(inline_label, signals.new_agent_bucket())
306
+ bucket["messages"] += 1
307
+ for f in signals.CONTEXT_TOKEN_FIELDS:
308
+ bucket[f] += safe_usage[f]
309
+ bucket["output_tokens"] += safe_usage["output_tokens"]
310
+
311
+ content = msg.get("content")
312
+ observed_turn = _observe_assistant_turn(rec, content)
313
+ if observed_turn is not None:
314
+ turn_context = observed_turn
315
+ _accumulate_skill_agent_signals(
316
+ skill, content, skills_invoked, agent_dispatches, active
317
+ )
318
+
319
+ if isinstance(content, list):
320
+ for block in content:
321
+ if not isinstance(block, dict):
322
+ continue
323
+ btype = block.get("type")
324
+ if btype == "tool_use":
325
+ _track_tool_call(block, pending_tool, tool_calls)
326
+ _track_edit(block, edits_per_file, thread)
327
+ _track_bash(
328
+ block,
329
+ bash_signal_counts,
330
+ thread,
331
+ active=active,
332
+ retried_by_skill=retried_bash_by_skill,
333
+ retried_by_agent=retried_bash_by_agent,
334
+ )
335
+ bblock_input = (
336
+ block.get("input", {})
337
+ if isinstance(block.get("input"), dict)
338
+ else {}
339
+ )
340
+ if block.get("name") == "Bash" and isinstance(
341
+ bblock_input.get("command"), str
342
+ ):
343
+ for segment in _bash_segments(bblock_input["command"].strip()):
344
+ if _is_git_commit_argv(segment):
345
+ is_bypass = any(
346
+ tok in _COMMIT_BYPASS_TOKENS for tok in segment[1:]
347
+ )
348
+ commit_attempt_events.append(
349
+ (rec.get("timestamp"), is_bypass)
350
+ )
351
+ elif btype == "tool_result":
352
+ _classify_tool_result(
353
+ block, pending_tool, tool_errors, error_counts
354
+ )
355
+
356
+ if not is_subagent and _detect_correction_turn(rec, content):
357
+ correction_turns += 1
358
+ correction_by_skill[active["skill"] or "unattributed"] += 1
359
+ correction_by_agent[active["agent"] or "unattributed"] += 1
360
+ _record_correction_cause(
361
+ correction_causes, turn_context, active.get("last"), _text_of(content)
362
+ )
363
+
364
+ label = agent_name or (_UNATTRIBUTED_LABEL if is_subagent else _MAIN_LABEL)
365
+ if thread_msgs:
366
+ bucket = by_agent_type.setdefault(label, signals.new_agent_bucket())
367
+ bucket["messages"] += thread_usage["messages"]
368
+ for f in signals.CONTEXT_TOKEN_FIELDS:
369
+ bucket[f] += thread_usage[f]
370
+ bucket["output_tokens"] += thread_usage["output_tokens"]
371
+ if is_subagent:
372
+ bucket["dispatches"] += 1
373
+ if records_seen and is_subagent:
374
+ subagent_transcripts += 1
375
+ agent_runs[label] += 1
376
+ elif records_seen:
377
+ main_transcripts += 1
378
+
379
+ repeated_file_edits = {f: n for f, n in edits_per_file.items() if n > 1}
380
+ # Single source of truth (#2110): the scalar is the sum of the live,
381
+ # per-event attribution below, not a second independent computation —
382
+ # the two could otherwise drift the way the churn-report window key did
383
+ # (#2108).
384
+ retried_bash = sum(retried_bash_by_skill.values())
385
+ failed_edits = error_counts["failed_edits"]
386
+ permission_denials = error_counts["permission_denials"]
387
+ repeated_verify_runs = bash_signal_counts["repeated_verify_runs"]
388
+ commit_attempts = bash_signal_counts["commit_attempts"]
389
+ commit_bypasses = bash_signal_counts["commit_bypasses"]
390
+
391
+ gate_ran_events = _read_gate_ran_events(boundary_events_path)
392
+ gate_ran_absent = gate_ran_errored = gate_ran_clean = 0
393
+ for ts_str, is_bypass in commit_attempt_events:
394
+ if is_bypass:
395
+ continue
396
+ outcome = _classify_gate_run(_parse_event_ts(ts_str), gate_ran_events)
397
+ if outcome == "absent":
398
+ gate_ran_absent += 1
399
+ elif outcome == "errored":
400
+ gate_ran_errored += 1
401
+ else:
402
+ gate_ran_clean += 1
403
+
404
+ agents_invoked = agent_runs if subagent_layout_present else agent_dispatches
405
+ reg_skills = set(registry.get("skills", []))
406
+ reg_agents = set(registry.get("agents", []))
407
+ never_skills = sorted(reg_skills - set(skills_invoked))
408
+ never_agents = sorted(reg_agents - set(agents_invoked) - set(agent_dispatches))
409
+
410
+ cr = tokens_total["cache_read_input_tokens"]
411
+ cc = tokens_total["cache_creation_input_tokens"]
412
+ cache_hit_ratio = round(cr / (cr + cc), 4) if (cr + cc) else 0.0
413
+
414
+ total_errors = sum(tool_errors.values())
415
+ total_calls = sum(tool_calls.values())
416
+
417
+ return {
418
+ "schema": _DIGEST_SCHEMA,
419
+ "plugin_version": plugin_version,
420
+ "sessions": len(sessions),
421
+ "transcripts": main_transcripts,
422
+ "subagent_transcripts": subagent_transcripts,
423
+ "token": {
424
+ "totals": dict(sorted(tokens_total.items())),
425
+ "cost_usd": round(cost_total, 4),
426
+ "cache_hit_ratio": cache_hit_ratio,
427
+ "by_model": _slim(by_model),
428
+ "by_skill": _slim(by_skill),
429
+ "by_agent_type": signals.finalize_agent_buckets(by_agent_type),
430
+ },
431
+ "rework": {
432
+ "failed_edits": failed_edits,
433
+ "repeated_file_edits": dict(sorted(repeated_file_edits.items())),
434
+ "retried_bash_commands": retried_bash,
435
+ "retried_bash_commands_by_skill": dict(sorted(retried_bash_by_skill.items())),
436
+ "retried_bash_commands_by_agent": dict(sorted(retried_bash_by_agent.items())),
437
+ "repeated_verify_runs": repeated_verify_runs,
438
+ "permission_denials": permission_denials,
439
+ "compaction_events": compaction_events,
440
+ },
441
+ "accuracy": {
442
+ "tool_errors_by_tool": dict(sorted(tool_errors.items())),
443
+ "tool_calls": total_calls,
444
+ "tool_error_rate": round(total_errors / total_calls, 4)
445
+ if total_calls
446
+ else 0.0,
447
+ "user_correction_turns": correction_turns,
448
+ "by_skill": dict(sorted(correction_by_skill.items())),
449
+ "by_agent": dict(sorted(correction_by_agent.items())),
450
+ "correction_rate_by_skill": _correction_rate_map(correction_by_skill, skills_invoked),
451
+ "correction_rate_by_agent": _correction_rate_map(
452
+ correction_by_agent, agent_dispatches
453
+ ),
454
+ "correction_causes": _finalize_correction_causes(correction_causes, correction_turns),
455
+ },
456
+ "gate": {
457
+ "commit_attempts": commit_attempts,
458
+ "commit_bypasses": commit_bypasses,
459
+ "bypass_rate": round(commit_bypasses / commit_attempts, 4)
460
+ if commit_attempts
461
+ else 0.0,
462
+ "gate_ran_absent": gate_ran_absent,
463
+ "gate_ran_errored": gate_ran_errored,
464
+ "gate_ran_clean": gate_ran_clean,
465
+ },
466
+ "utilization": {
467
+ "skills_invoked": dict(sorted(skills_invoked.items())),
468
+ "agents_invoked": dict(sorted(agents_invoked.items())),
469
+ "agent_dispatches": dict(sorted(agent_dispatches.items())),
470
+ "never_observed_skills": never_skills,
471
+ "never_observed_agents": never_agents,
472
+ },
473
+ }
474
+
475
+
476
+ def resolve_transcripts(args) -> list[Path]:
477
+ """Robustly find the current project's transcript files."""
478
+ if args.transcript:
479
+ return [Path(p) for p in args.transcript]
480
+ if args.project_dir:
481
+ return sorted(Path(args.project_dir).glob("*.jsonl"))
482
+
483
+ root = Path(args.projects_root or (Path.home() / ".claude" / "projects"))
484
+ target_cwd = os.path.abspath(args.cwd or os.getcwd())
485
+ if not root.is_dir():
486
+ return []
487
+ matches: list[Path] = []
488
+ for jsonl in _all_transcripts_under(root):
489
+ try:
490
+ with jsonl.open(encoding="utf-8", errors="replace") as fh:
491
+ for _ in range(20):
492
+ line = fh.readline()
493
+ if not line:
494
+ break
495
+ try:
496
+ rec = json.loads(line)
497
+ except json.JSONDecodeError:
498
+ continue
499
+ rec_cwd = rec.get("cwd")
500
+ if rec_cwd and os.path.abspath(rec_cwd) == target_cwd:
501
+ matches.append(jsonl)
502
+ break
503
+ except (OSError, ValueError):
504
+ continue
505
+ return sorted(matches, key=lambda x: str(x))
506
+
507
+
508
+ def resolve_all_transcripts(args) -> list[Path]:
509
+ """Every transcript across ALL projects under projects-root (Delta D, #178)."""
510
+ root = Path(args.projects_root or (Path.home() / ".claude" / "projects"))
511
+ if not root.is_dir():
512
+ return []
513
+ return _all_transcripts_under(root)
514
+
515
+
516
+ def _opaque_session_id(session_id: str) -> str:
517
+ """A safe session id that stays UNIQUE."""
518
+ safe = _redact(session_id)
519
+ if safe != _UNSAFE_NAME:
520
+ return safe
521
+ digest = hashlib.sha256(session_id.encode("utf-8")).hexdigest()[:8]
522
+ return f"other-{digest}"
523
+
524
+
525
+ def _owning_session_dir(root: Path, path: Path) -> str:
526
+ """The session a transcript belongs to, as a stable id."""
527
+ try:
528
+ parts = path.relative_to(root).parts
529
+ except ValueError:
530
+ parts = path.parts
531
+ if "subagents" in parts:
532
+ i = parts.index("subagents")
533
+ return parts[i - 1] if i > 0 else path.stem
534
+ return path.stem
535
+
536
+
537
+ def _project_and_ts(path: Path) -> tuple[str, str]:
538
+ """Project label (basename of the recorded cwd) and the latest record
539
+ timestamp, both from transcript DATA."""
540
+ project = ""
541
+ last_ts = ""
542
+ for rec in _iter_records([path]):
543
+ if not project:
544
+ cwd = rec.get("cwd")
545
+ if isinstance(cwd, str) and cwd:
546
+ project = _redact(str(cwd).rstrip("/\\"), from_path=True)
547
+ ts = rec.get("timestamp")
548
+ if isinstance(ts, str) and ts > last_ts:
549
+ last_ts = ts
550
+ return project or "unknown", last_ts
551
+
552
+
553
+ def sync_record(
554
+ digest: dict, host: str, project: str, session_id: str, ts: str
555
+ ) -> dict:
556
+ """One per-session, metrics-only record for cross-machine aggregation (#178)."""
557
+ base = slim_record(digest)
558
+ tok = digest.get("token", {})
559
+ rew = digest.get("rework", {})
560
+ acc = digest.get("accuracy", {})
561
+ util = digest.get("utilization", {})
562
+ by_model = {
563
+ m: dict(sorted(v.items())) for m, v in sorted(tok.get("by_model", {}).items())
564
+ }
565
+ return {
566
+ "schema": _SYNC_SCHEMA,
567
+ "plugin_version": digest.get("plugin_version", "unknown"),
568
+ "host": host,
569
+ "project": project,
570
+ "session_id": session_id,
571
+ "ts": ts,
572
+ "synced_at": base["recorded_at"],
573
+ "sessions": digest.get("sessions", 0),
574
+ "tokens": base["tokens"],
575
+ "cost_usd": base["cost_usd"],
576
+ "cache_hit_ratio": base["cache_hit_ratio"],
577
+ "by_model": by_model,
578
+ "by_thread": tok.get("by_agent_type", tok.get("by_subagent", {})),
579
+ "rework": {
580
+ **base["rework"],
581
+ "retried_bash_commands_by_skill": dict(
582
+ sorted(rew.get("retried_bash_commands_by_skill", {}).items())
583
+ ),
584
+ "retried_bash_commands_by_agent": dict(
585
+ sorted(rew.get("retried_bash_commands_by_agent", {}).items())
586
+ ),
587
+ },
588
+ "accuracy": {
589
+ **base["accuracy"],
590
+ "by_skill": dict(sorted(acc.get("by_skill", {}).items())),
591
+ "by_agent": dict(sorted(acc.get("by_agent", {}).items())),
592
+ },
593
+ "gate": base["gate"],
594
+ "utilization": {
595
+ "skills_invoked": dict(sorted(util.get("skills_invoked", {}).items())),
596
+ "agents_invoked": dict(sorted(util.get("agents_invoked", {}).items())),
597
+ "agent_dispatches": dict(
598
+ sorted(util.get("agent_dispatches", {}).items())
599
+ ),
600
+ },
601
+ }
602
+
603
+
604
+ def _load_watermark(path: Path) -> dict:
605
+ """Read the sync watermark. Missing or malformed -> a fresh empty watermark."""
606
+ if path.is_file():
607
+ try:
608
+ data = json.loads(path.read_text())
609
+ if isinstance(data, dict) and isinstance(data.get("synced"), dict):
610
+ return data
611
+ except (OSError, json.JSONDecodeError):
612
+ pass
613
+ return {"synced": {}}
614
+
615
+
616
+ def cmd_sync(
617
+ args, pricing: dict, registry: dict, host: str, plugin_version: str
618
+ ) -> int:
619
+ """Incremental, cross-project sync (#178)."""
620
+ out = Path(args.sync_out)
621
+ wm_path = (
622
+ Path(args.watermark)
623
+ if args.watermark
624
+ else (Path.home() / ".claude" / ".dev-team" / "telemetry-sync.json")
625
+ )
626
+ wm = _load_watermark(wm_path)
627
+ synced = wm["synced"]
628
+
629
+ if args.transcript:
630
+ paths = [Path(p) for p in args.transcript]
631
+ elif args.project_dir:
632
+ paths = sorted(Path(args.project_dir).glob("*.jsonl"), key=lambda x: x.name)
633
+ else:
634
+ paths = resolve_all_transcripts(args)
635
+
636
+ root = Path(args.projects_root or (Path.home() / ".claude" / "projects"))
637
+
638
+ by_session: dict[str, list[Path]] = {}
639
+ for path in paths:
640
+ by_session.setdefault(_owning_session_dir(root, path), []).append(path)
641
+
642
+ emitted = 0
643
+ out.parent.mkdir(parents=True, exist_ok=True)
644
+ with out.open("a", encoding="utf-8") as fh:
645
+ for session_id, session_paths in sorted(by_session.items()):
646
+ session_paths = sorted(session_paths, key=lambda p: str(p))
647
+ size = 0
648
+ for p in session_paths:
649
+ try:
650
+ size += p.stat().st_size
651
+ except OSError:
652
+ continue
653
+ if not size:
654
+ continue
655
+ prev = synced.get(session_id)
656
+ if isinstance(prev, int) and prev >= size:
657
+ continue
658
+ main = next(
659
+ (p for p in session_paths if not _is_subagent_transcript(root, p)),
660
+ session_paths[0],
661
+ )
662
+ project, ts = _project_and_ts(main)
663
+ # The session's own .claude/metrics/boundary-events.jsonl -- used
664
+ # below both to correlate `gate_ran` events against this
665
+ # session's commit attempts (#2106: omitting this argument here
666
+ # left EVERY synced session's gate_ran_events read as `None` ->
667
+ # `[]`, so every non-bypassed commit attempt classified
668
+ # "absent" regardless of whether the git-native pre-commit hook
669
+ # actually ran -- the digest's "100% gate_ran_absent" reflected
670
+ # this sync path never looking, not the gate never firing) and
671
+ # to resolve the session's own plugin_version below (#2018).
672
+ raw_cwd = _first_cwd(main)
673
+ boundary_events_path = (
674
+ Path(raw_cwd) / ".claude" / "metrics" / "boundary-events.jsonl"
675
+ if raw_cwd
676
+ else None
677
+ )
678
+ digest = extract_maintainer(
679
+ session_paths,
680
+ pricing,
681
+ registry,
682
+ plugin_version,
683
+ projects_root=root,
684
+ boundary_events_path=boundary_events_path,
685
+ )
686
+ # #2018: this per-session sync record is what durably archives
687
+ # onto .claude/metrics/session-digest.jsonl at every
688
+ # SessionStart (.claude/ensure_session_archive.py) -- exactly
689
+ # the path where extraction-time `plugin_version` (the value
690
+ # threaded through as the `plugin_version` parameter above)
691
+ # mislabels a historical session with today's checked-out
692
+ # version. Override the digest's own field with the SESSION's
693
+ # resolved version -- its own project's boundary-events.jsonl,
694
+ # keyed by this loop's raw session_id -- before building the
695
+ # sync record; falls back to "unknown" when the session's real
696
+ # cwd can't be determined or has no matching boundary event.
697
+ digest["plugin_version"] = (
698
+ resolve_session_plugin_version(session_id, boundary_events_path)
699
+ if raw_cwd
700
+ else "unknown"
701
+ )
702
+ rec = sync_record(digest, host, project, _opaque_session_id(session_id), ts)
703
+ fh.write(json.dumps(rec, sort_keys=True) + "\n")
704
+ synced[session_id] = size
705
+ emitted += 1
706
+
707
+ wm_path.parent.mkdir(parents=True, exist_ok=True)
708
+ wm_path.write_text(json.dumps(wm, indent=2, sort_keys=True) + "\n")
709
+ print(
710
+ f"synced {emitted} new/changed session(s) of {len(by_session)} considered -> {out}"
711
+ )
712
+ return 0
713
+
714
+
715
+ def _normalize_plugin_version(value) -> str | None:
716
+ """Bound + validate a `plugin_version` read back from a PEER's synced digest."""
717
+ return value if isinstance(value, str) and _VERSION_RE.match(value) else None
718
+
719
+
720
+ _NUM_MAX = 2**53
721
+
722
+
723
+ def _safe_number(value) -> int | float:
724
+ """Bound a peer-supplied numeric field read back from a synced digest."""
725
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
726
+ return 0
727
+ if isinstance(value, float) and not math.isfinite(value):
728
+ return 0
729
+ if value < 0 or abs(value) > _NUM_MAX:
730
+ return 0
731
+ return value
732
+
733
+
734
+ def _normalize_name_dicts(container: dict, fields: tuple) -> None:
735
+ """Rewrite each named dict-valued field of `container` through
736
+ `_rewrite_name_keys`, in place."""
737
+ for field in fields:
738
+ value = container.get(field)
739
+ container[field] = _rewrite_name_keys(value) if isinstance(value, dict) else {}
740
+
741
+
742
+ def _normalize_numeric_fields(container: dict, fields: tuple) -> None:
743
+ """Coerce each named field of `container` that's present through `_safe_number`."""
744
+ for field in fields:
745
+ if field in container:
746
+ container[field] = _safe_number(container[field])
747
+
748
+
749
+ def _read_synced_records(digests_root: Path) -> list[dict]:
750
+ """Union read + dedup (#178): every host's per-session record (schema-
751
+ versioned, see `SYNC_SCHEMAS`) under
752
+ `digests_root/<host>/session-digest.jsonl`, keeping the LAST record for
753
+ a session_id seen on multiple host files."""
754
+ by_id: dict[str, dict] = {}
755
+ for f in sorted(digests_root.glob("*/session-digest.jsonl")):
756
+ for rec in _iter_records([f]):
757
+ if not isinstance(rec, dict):
758
+ continue
759
+ if rec.get("schema") not in SYNC_SCHEMAS:
760
+ continue
761
+ sid = rec.get("session_id")
762
+ if not sid:
763
+ continue
764
+ rec["plugin_version"] = _normalize_plugin_version(
765
+ rec.get("plugin_version")
766
+ )
767
+ rec["host"] = _redact(str(rec.get("host") or "unknown"))
768
+ rec["project"] = _redact(str(rec.get("project") or "unknown"))
769
+ rec["ts"] = _redact(rec["ts"]) if isinstance(rec.get("ts"), str) else ""
770
+ rec["cost_usd"] = _safe_number(rec.get("cost_usd", 0))
771
+ utilization = (
772
+ rec.get("utilization") if isinstance(rec.get("utilization"), dict) else {}
773
+ )
774
+ accuracy = rec.get("accuracy") if isinstance(rec.get("accuracy"), dict) else {}
775
+ tokens = rec.get("tokens") if isinstance(rec.get("tokens"), dict) else {}
776
+ rework = rec.get("rework") if isinstance(rec.get("rework"), dict) else {}
777
+ gate = rec.get("gate") if isinstance(rec.get("gate"), dict) else {}
778
+
779
+ _normalize_name_dicts(
780
+ utilization, ("skills_invoked", "agents_invoked", "agent_dispatches")
781
+ )
782
+ _normalize_name_dicts(accuracy, ("by_skill", "by_agent"))
783
+ _normalize_name_dicts(
784
+ rework,
785
+ ("retried_bash_commands_by_skill", "retried_bash_commands_by_agent"),
786
+ )
787
+ _normalize_numeric_fields(
788
+ tokens,
789
+ (
790
+ "input_tokens",
791
+ "output_tokens",
792
+ "cache_creation_input_tokens",
793
+ "cache_read_input_tokens",
794
+ ),
795
+ )
796
+ # The two dict-valued fields just sanitized above are captured
797
+ # before the numeric-only comprehension below, which would
798
+ # otherwise drop them (`_REWORK_KEYS` is numeric-scalar-only,
799
+ # like `repeated_file_edits`'s already-summarized length) or
800
+ # crash `_safe_number` on a dict value.
801
+ retried_by_skill = rework["retried_bash_commands_by_skill"]
802
+ retried_by_agent = rework["retried_bash_commands_by_agent"]
803
+ rework = {k: _safe_number(v) for k, v in rework.items() if k in _REWORK_KEYS}
804
+ rework["retried_bash_commands_by_skill"] = retried_by_skill
805
+ rework["retried_bash_commands_by_agent"] = retried_by_agent
806
+ _normalize_numeric_fields(
807
+ accuracy, ("tool_calls", "tool_error_rate", "user_correction_turns")
808
+ )
809
+ _normalize_numeric_fields(
810
+ gate,
811
+ (
812
+ "commit_attempts",
813
+ "commit_bypasses",
814
+ "gate_ran_absent",
815
+ "gate_ran_errored",
816
+ "gate_ran_clean",
817
+ ),
818
+ )
819
+
820
+ rec["utilization"] = utilization
821
+ rec["accuracy"] = accuracy
822
+ rec["tokens"] = tokens
823
+ rec["rework"] = rework
824
+ rec["gate"] = gate
825
+ by_id[str(sid)] = rec
826
+ return list(by_id.values())
827
+
828
+
829
+ def _filter_by_version(
830
+ records: list[dict], version_window: set[str] | None
831
+ ) -> list[dict]:
832
+ """Drop records whose `plugin_version` isn't in `version_window` (#1480)."""
833
+ if version_window is None:
834
+ return records
835
+ return [r for r in records if r.get("plugin_version") in version_window]
836
+
837
+
838
+ def _parse_semver_key(version: str) -> tuple:
839
+ return tuple(int(p) for p in re.findall(r"\d{1,9}", version or "")) or (0,)
840
+
841
+
842
+ def compute_version_window(records: list[dict], current: str) -> set[str]:
843
+ """The current plugin version plus the newest version OBSERVED in
844
+ `records` that is strictly OLDER than it (#1480)."""
845
+ if current == "unknown":
846
+ return set()
847
+ current_key = _parse_semver_key(current)
848
+ older = sorted(
849
+ (
850
+ r.get("plugin_version")
851
+ for r in records
852
+ if r.get("plugin_version")
853
+ and r.get("plugin_version") != "unknown"
854
+ and _parse_semver_key(r.get("plugin_version")) < current_key
855
+ ),
856
+ key=_parse_semver_key,
857
+ )
858
+ window = {current}
859
+ if older:
860
+ window.add(older[-1])
861
+ return window
862
+
863
+
864
+ def rollup(
865
+ records: list[dict], registry: dict, version_window: set[str] | None = None
866
+ ) -> dict:
867
+ """Aggregate cross-machine `session-sync` records (schema-versioned, see
868
+ `SYNC_SCHEMAS`; #178)."""
869
+ records = _filter_by_version(records, version_window)
870
+ hosts: set[str] = set()
871
+ projects: set[str] = set()
872
+ tok = Counter()
873
+ cost = 0.0
874
+ cr = cc = 0
875
+ rew = Counter()
876
+ retried_bash_by_skill = Counter()
877
+ retried_bash_by_agent = Counter()
878
+ tool_calls = 0
879
+ err_weighted = 0.0
880
+ corrections = 0
881
+ correction_by_skill = Counter()
882
+ correction_by_agent = Counter()
883
+ skills_invoked = Counter()
884
+ agents_invoked = Counter()
885
+ agent_dispatches = Counter()
886
+ by_host: dict[str, Counter] = defaultdict(Counter)
887
+ by_project: dict[str, Counter] = defaultdict(Counter)
888
+
889
+ for r in records:
890
+ host = r.get("host")
891
+ project = r.get("project")
892
+ hosts.add(host)
893
+ projects.add(project)
894
+ t = r.get("tokens", {}) if isinstance(r.get("tokens"), dict) else {}
895
+ for k in (
896
+ "input_tokens",
897
+ "output_tokens",
898
+ "cache_creation_input_tokens",
899
+ "cache_read_input_tokens",
900
+ ):
901
+ tok[k] += t.get(k, 0) or 0
902
+ cr += t.get("cache_read_input_tokens", 0) or 0
903
+ cc += t.get("cache_creation_input_tokens", 0) or 0
904
+ c = r.get("cost_usd", 0.0) or 0.0
905
+ cost += c
906
+ for hk, agg in ((host, by_host), (project, by_project)):
907
+ agg[hk]["sessions"] += 1
908
+ agg[hk]["cost_micro"] += round(c * 1e6)
909
+ agg[hk]["input_tokens"] += t.get("input_tokens", 0) or 0
910
+ agg[hk]["output_tokens"] += t.get("output_tokens", 0) or 0
911
+ rwk = r.get("rework", {}) if isinstance(r.get("rework"), dict) else {}
912
+ for k, v in rwk.items():
913
+ if isinstance(v, (int, float)):
914
+ rew[k] += v
915
+ for name, k in (rwk.get("retried_bash_commands_by_skill", {}) or {}).items():
916
+ retried_bash_by_skill[name] += k
917
+ for name, k in (rwk.get("retried_bash_commands_by_agent", {}) or {}).items():
918
+ retried_bash_by_agent[name] += k
919
+ acc = r.get("accuracy", {}) if isinstance(r.get("accuracy"), dict) else {}
920
+ n = acc.get("tool_calls", 0) or 0
921
+ tool_calls += n
922
+ err_weighted += (acc.get("tool_error_rate", 0.0) or 0.0) * n
923
+ corrections += acc.get("user_correction_turns", 0) or 0
924
+ for name, k in (acc.get("by_skill", {}) or {}).items():
925
+ correction_by_skill[name] += k
926
+ for name, k in (acc.get("by_agent", {}) or {}).items():
927
+ correction_by_agent[name] += k
928
+ u = r.get("utilization", {}) if isinstance(r.get("utilization"), dict) else {}
929
+ for name, k in (u.get("skills_invoked", {}) or {}).items():
930
+ skills_invoked[_strip_ns(name)] += k
931
+ for name, k in (u.get("agents_invoked", {}) or {}).items():
932
+ agents_invoked[_strip_ns(name)] += k
933
+ for name, k in (u.get("agent_dispatches", {}) or {}).items():
934
+ agent_dispatches[_strip_ns(name)] += k
935
+
936
+ invoked_skills = {n for n, c in skills_invoked.items() if c > 0}
937
+ invoked_agents = {n for n, c in agents_invoked.items() if c > 0}
938
+ dispatched_agents = {n for n, c in agent_dispatches.items() if c > 0}
939
+ never_skills = sorted(set(registry.get("skills", [])) - invoked_skills)
940
+ never_agents = sorted(
941
+ set(registry.get("agents", [])) - invoked_agents - dispatched_agents
942
+ )
943
+
944
+ def _hostmap(d: dict) -> dict:
945
+ return {k: dict(sorted(v.items())) for k, v in sorted(d.items())}
946
+
947
+ return {
948
+ "schema": "telemetry-rollup/v1",
949
+ "version_window": sorted(version_window) if version_window is not None else [],
950
+ "hosts": sorted(hosts),
951
+ "projects": sorted(projects),
952
+ "sessions": len(records),
953
+ "tokens": dict(sorted(tok.items())),
954
+ "cost_usd": round(cost, 4),
955
+ "cache_hit_ratio": round(cr / (cr + cc), 4) if (cr + cc) else 0.0,
956
+ "by_host": _hostmap(by_host),
957
+ "by_project": _hostmap(by_project),
958
+ "rework": {
959
+ **dict(sorted(rew.items())),
960
+ "retried_bash_commands_by_skill": dict(sorted(retried_bash_by_skill.items())),
961
+ "retried_bash_commands_by_agent": dict(sorted(retried_bash_by_agent.items())),
962
+ },
963
+ "accuracy": {
964
+ "tool_calls": tool_calls,
965
+ "tool_error_rate": round(err_weighted / tool_calls, 4)
966
+ if tool_calls
967
+ else 0.0,
968
+ "user_correction_turns": corrections,
969
+ "by_skill": dict(sorted(correction_by_skill.items())),
970
+ "by_agent": dict(sorted(correction_by_agent.items())),
971
+ },
972
+ "utilization": {
973
+ "skills_invoked": dict(sorted(skills_invoked.items())),
974
+ "agents_invoked": dict(sorted(agents_invoked.items())),
975
+ "agent_dispatches": dict(sorted(agent_dispatches.items())),
976
+ "never_observed_skills": never_skills,
977
+ "never_observed_agents": never_agents,
978
+ },
979
+ }
980
+
981
+
982
+ def _resolve_version_window(
983
+ args, records: list[dict], plugin_root: Path | None
984
+ ) -> set[str] | None:
985
+ """#1480: only when --version-scope current-and-previous is requested,
986
+ compute the window from the CURRENT plugin.json version plus whichever
987
+ version immediately precedes it among `records`."""
988
+ if args.version_scope != "current-and-previous":
989
+ return None
990
+ current = _load_plugin_version(plugin_root)
991
+ return compute_version_window(records, current)
992
+
993
+
994
+ def cmd_rollup(args, registry: dict, plugin_root: Path | None) -> int:
995
+ root = Path(args.rollup)
996
+ if not root.is_dir():
997
+ print(
998
+ json.dumps(
999
+ {
1000
+ "schema": "telemetry-rollup/v1",
1001
+ "version_window": [],
1002
+ "sessions": 0,
1003
+ "hosts": [],
1004
+ "projects": [],
1005
+ },
1006
+ indent=2,
1007
+ sort_keys=True,
1008
+ )
1009
+ )
1010
+ return 0
1011
+ records = _read_synced_records(root)
1012
+ window = _resolve_version_window(args, records, plugin_root)
1013
+ out = json.dumps(rollup(records, registry, window), indent=2, sort_keys=True)
1014
+ if args.out:
1015
+ Path(args.out).write_text(out + "\n")
1016
+ else:
1017
+ print(out)
1018
+ return 0
1019
+
1020
+
1021
+ _REWORK_KEYS = (
1022
+ "failed_edits",
1023
+ "repeated_file_edits",
1024
+ "retried_bash_commands",
1025
+ "repeated_verify_runs",
1026
+ "permission_denials",
1027
+ "compaction_events",
1028
+ )
1029
+
1030
+
1031
+ def _session_rework(rec: dict) -> int:
1032
+ rw = rec.get("rework", {}) if isinstance(rec.get("rework"), dict) else {}
1033
+ return sum(int(rw.get(k, 0) or 0) for k in _REWORK_KEYS)
1034
+
1035
+
1036
+ def correlate_gate_rework(
1037
+ records: list[dict], version_window: set[str] | None = None
1038
+ ) -> dict:
1039
+ """Across all sessions that committed, compare mean rework between those
1040
+ that bypassed the review gate and those that didn't (#111)."""
1041
+ records = _filter_by_version(records, version_window)
1042
+
1043
+ bypass_rework: list[int] = []
1044
+ clean_rework: list[int] = []
1045
+ for rec in records:
1046
+ gate = rec.get("gate", {}) if isinstance(rec.get("gate"), dict) else {}
1047
+ if int(gate.get("commit_attempts", 0) or 0) <= 0:
1048
+ continue
1049
+ (
1050
+ bypass_rework
1051
+ if int(gate.get("commit_bypasses", 0) or 0) > 0
1052
+ else clean_rework
1053
+ ).append(_session_rework(rec))
1054
+
1055
+ def _mean(xs):
1056
+ return round(sum(xs) / len(xs), 4) if xs else 0.0
1057
+
1058
+ mb, mc = _mean(bypass_rework), _mean(clean_rework)
1059
+ if not bypass_rework or not clean_rework:
1060
+ interp = "insufficient data — need committing sessions in BOTH groups"
1061
+ elif mb > mc:
1062
+ interp = (
1063
+ "bypassing the review gate correlates with MORE rework "
1064
+ f"({mb} vs {mc}) — evidence the gate guards real risk"
1065
+ )
1066
+ elif mb < mc:
1067
+ interp = (
1068
+ "bypassing correlates with LESS rework "
1069
+ f"({mb} vs {mc}) — the gate may be ceremony for these cases"
1070
+ )
1071
+ else:
1072
+ interp = "no difference in rework between bypass and non-bypass sessions"
1073
+
1074
+ return {
1075
+ "schema": "gate-correlation/v1",
1076
+ "version_window": sorted(version_window) if version_window is not None else [],
1077
+ "committing_sessions": len(bypass_rework) + len(clean_rework),
1078
+ "bypass_sessions": len(bypass_rework),
1079
+ "clean_sessions": len(clean_rework),
1080
+ "mean_rework_when_bypassed": mb,
1081
+ "mean_rework_when_gated": mc,
1082
+ "interpretation": interp,
1083
+ }
1084
+
1085
+
1086
+ def cmd_correlate(args, plugin_root: Path | None) -> int:
1087
+ root = Path(args.correlate)
1088
+ if root.is_dir():
1089
+ records = _read_synced_records(root)
1090
+ window = _resolve_version_window(args, records, plugin_root)
1091
+ result = correlate_gate_rework(records, window)
1092
+ else:
1093
+ result = {
1094
+ "schema": "gate-correlation/v1",
1095
+ "version_window": [],
1096
+ "committing_sessions": 0,
1097
+ "interpretation": "no digests directory",
1098
+ }
1099
+ out = json.dumps(result, indent=2, sort_keys=True)
1100
+ if args.out:
1101
+ Path(args.out).write_text(out + "\n")
1102
+ else:
1103
+ print(out)
1104
+ return 0
1105
+
1106
+
1107
+ def cost_log(digests_root: Path) -> list[dict]:
1108
+ """Per-session cost SERIES for the cost-meter regression gate (#171)."""
1109
+ recs = sorted(
1110
+ _read_synced_records(digests_root),
1111
+ key=lambda r: (r.get("ts") or "", str(r.get("session_id"))),
1112
+ )
1113
+ return [
1114
+ {"ts": r.get("ts"), "total": {"cost_usd": r.get("cost_usd", 0.0) or 0.0}}
1115
+ for r in recs
1116
+ ]
1117
+
1118
+
1119
+ def cmd_cost_log(args) -> int:
1120
+ root = Path(args.cost_log)
1121
+ lines = (
1122
+ "\n".join(json.dumps(rec, sort_keys=True) for rec in cost_log(root))
1123
+ if root.is_dir()
1124
+ else ""
1125
+ )
1126
+ if args.out:
1127
+ Path(args.out).write_text(lines + ("\n" if lines else ""))
1128
+ elif lines:
1129
+ print(lines)
1130
+ return 0
1131
+
1132
+
1133
+ _FRICTION_SIGNALS = [
1134
+ ("rework", "permission_denials", True, "permission denials"),
1135
+ ("rework", "retried_bash_commands", True, "retried bash commands"),
1136
+ ("rework", "repeated_verify_runs", True, "repeated verify runs"),
1137
+ ("rework", "failed_edits", False, "failed edits (old_string not found)"),
1138
+ ("rework", "compaction_events", False, "context compaction events"),
1139
+ ("accuracy", "user_correction_turns", False, "user-correction turns"),
1140
+ ]
1141
+
1142
+
1143
+ def _lever_for(
1144
+ rate: float, matchable: bool, rare_rate: float, frequent_rate: float
1145
+ ) -> tuple[str, str]:
1146
+ if rate < rare_rate:
1147
+ return "hint", "rare — surface as a hint only"
1148
+ if matchable and rate >= frequent_rate:
1149
+ return (
1150
+ "hook",
1151
+ "frequent and deterministically matchable — promote to a hook (validate via /agent-eval)",
1152
+ )
1153
+ if matchable:
1154
+ return (
1155
+ "instruction-rule",
1156
+ "recurring and matchable but below the hook threshold — an instruction-file rule for now (/feedback-learning)",
1157
+ )
1158
+ return (
1159
+ "instruction-rule",
1160
+ "recurring but judgment-shaped (no reliable matcher) — an instruction-file rule (/feedback-learning)",
1161
+ )
1162
+
1163
+
1164
+ def escalate(roll: dict, rare_rate: float = 0.25, frequent_rate: float = 1.0) -> dict:
1165
+ """Turn rollup recurrence into ranked lever recommendations (#179)."""
1166
+ sessions = max(int(roll.get("sessions", 0)), 0)
1167
+ recs = []
1168
+ for section, key, matchable, label in _FRICTION_SIGNALS:
1169
+ count = roll.get(section, {}).get(key, 0) or 0
1170
+ if not count:
1171
+ continue
1172
+ rate = round(count / sessions, 4) if sessions else 0.0
1173
+ lever, rationale = _lever_for(rate, matchable, rare_rate, frequent_rate)
1174
+ recs.append(
1175
+ {
1176
+ "signal": key,
1177
+ "label": label,
1178
+ "count": count,
1179
+ "per_session_rate": rate,
1180
+ "matchable": matchable,
1181
+ "lever": lever,
1182
+ "rationale": rationale,
1183
+ }
1184
+ )
1185
+ recs.sort(key=lambda r: (-r["per_session_rate"], -r["count"]))
1186
+ return {
1187
+ "schema": "telemetry-escalation/v1",
1188
+ "sessions": sessions,
1189
+ "version_window": roll.get("version_window", []),
1190
+ "thresholds": {"rare_rate": rare_rate, "frequent_rate": frequent_rate},
1191
+ "recommendations": recs,
1192
+ }
1193
+
1194
+
1195
+ def cmd_escalate(args, registry: dict, plugin_root: Path | None) -> int:
1196
+ root = Path(args.escalate)
1197
+ if root.is_dir():
1198
+ records = _read_synced_records(root)
1199
+ window = _resolve_version_window(args, records, plugin_root)
1200
+ roll = rollup(records, registry, window)
1201
+ else:
1202
+ roll = {"sessions": 0, "version_window": []}
1203
+ out = json.dumps(
1204
+ escalate(roll, rare_rate=args.rare_rate, frequent_rate=args.frequent_rate),
1205
+ indent=2,
1206
+ sort_keys=True,
1207
+ )
1208
+ if args.out:
1209
+ Path(args.out).write_text(out + "\n")
1210
+ else:
1211
+ print(out)
1212
+ return 0
1213
+
1214
+
1215
+ def slim_record(digest: dict) -> dict:
1216
+ """A compact, AGGREGATE-COUNTS-ONLY trend record (#129)."""
1217
+ tok = digest.get("token", {})
1218
+ rew = digest.get("rework", {})
1219
+ acc = digest.get("accuracy", {})
1220
+ gate = digest.get("gate", {})
1221
+ util = digest.get("utilization", {})
1222
+ totals = tok.get("totals", {})
1223
+ return {
1224
+ "recorded_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
1225
+ "schema": _DIGEST_SCHEMA,
1226
+ "plugin_version": digest.get("plugin_version", "unknown"),
1227
+ "sessions": digest.get("sessions", 0),
1228
+ "transcripts": digest.get("transcripts", 0),
1229
+ "tokens": {
1230
+ k: totals.get(k, 0)
1231
+ for k in (
1232
+ "input_tokens",
1233
+ "output_tokens",
1234
+ "cache_creation_input_tokens",
1235
+ "cache_read_input_tokens",
1236
+ )
1237
+ },
1238
+ "cost_usd": tok.get("cost_usd", 0.0),
1239
+ "cache_hit_ratio": tok.get("cache_hit_ratio", 0.0),
1240
+ "rework": {
1241
+ "failed_edits": rew.get("failed_edits", 0),
1242
+ "repeated_file_edits": len(rew.get("repeated_file_edits", {})),
1243
+ "retried_bash_commands": rew.get("retried_bash_commands", 0),
1244
+ "repeated_verify_runs": rew.get("repeated_verify_runs", 0),
1245
+ "permission_denials": rew.get("permission_denials", 0),
1246
+ "compaction_events": rew.get("compaction_events", 0),
1247
+ },
1248
+ "accuracy": {
1249
+ "tool_calls": acc.get("tool_calls", 0),
1250
+ "tool_error_rate": acc.get("tool_error_rate", 0.0),
1251
+ "user_correction_turns": acc.get("user_correction_turns", 0),
1252
+ },
1253
+ "gate": {
1254
+ "commit_attempts": gate.get("commit_attempts", 0),
1255
+ "commit_bypasses": gate.get("commit_bypasses", 0),
1256
+ "bypass_rate": gate.get("bypass_rate", 0.0),
1257
+ "gate_ran_absent": gate.get("gate_ran_absent", 0),
1258
+ "gate_ran_errored": gate.get("gate_ran_errored", 0),
1259
+ "gate_ran_clean": gate.get("gate_ran_clean", 0),
1260
+ },
1261
+ "utilization": {
1262
+ "skills_invoked": len(util.get("skills_invoked", {})),
1263
+ "agents_invoked": len(util.get("agents_invoked", {})),
1264
+ "never_observed_skills": len(util.get("never_observed_skills", [])),
1265
+ "never_observed_agents": len(util.get("never_observed_agents", [])),
1266
+ },
1267
+ }
1268
+
1269
+
1270
+ def _append_trend(log: Path, digest: dict) -> None:
1271
+ log.parent.mkdir(parents=True, exist_ok=True)
1272
+ with log.open("a", encoding="utf-8") as fh:
1273
+ fh.write(json.dumps(slim_record(digest), sort_keys=True) + "\n")