@xhulz/nina 0.51.0 → 0.53.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 (278) hide show
  1. package/README.md +7 -2
  2. package/bin/nina.mjs +29 -5
  3. package/package.json +1 -1
  4. package/releases/0.52.0/core/GAPS.md +125 -0
  5. package/releases/0.52.0/core/answered.json +68 -0
  6. package/releases/0.52.0/core/defaults/tree/.claude/agents/architect.md +1 -0
  7. package/releases/0.52.0/core/defaults/tree/.claude/agents/dba.md +3 -0
  8. package/releases/0.52.0/core/defaults/tree/.claude/agents/devops.md +1 -0
  9. package/releases/0.52.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
  10. package/releases/0.52.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  11. package/releases/0.52.0/core/defaults/tree/.claude/agents/qa.md +7 -0
  12. package/releases/0.52.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  13. package/releases/0.52.0/core/defaults/tree/.claude/agents/secops.md +3 -0
  14. package/releases/0.52.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  15. package/releases/0.52.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  16. package/releases/0.52.0/core/tree/.claude/agents/architect.md +129 -0
  17. package/releases/0.52.0/core/tree/.claude/agents/dba.md +117 -0
  18. package/releases/0.52.0/core/tree/.claude/agents/devops.md +118 -0
  19. package/releases/0.52.0/core/tree/.claude/agents/implementer.md +163 -0
  20. package/releases/0.52.0/core/tree/.claude/agents/integration-tester.md +168 -0
  21. package/releases/0.52.0/core/tree/.claude/agents/planner.md +132 -0
  22. package/releases/0.52.0/core/tree/.claude/agents/qa.md +150 -0
  23. package/releases/0.52.0/core/tree/.claude/agents/reviewer.md +184 -0
  24. package/releases/0.52.0/core/tree/.claude/agents/secops.md +120 -0
  25. package/releases/0.52.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
  26. package/releases/0.52.0/core/tree/.claude/agents/solidity-dev.md +123 -0
  27. package/releases/0.52.0/core/tree/.claude/agents-overview.md +64 -0
  28. package/releases/0.52.0/core/tree/.claude/frontend.md +10 -0
  29. package/releases/0.52.0/core/tree/.claude/graph.md +75 -0
  30. package/releases/0.52.0/core/tree/.claude/patterns.md +251 -0
  31. package/releases/0.52.0/core/tree/.claude/pills/README.md +150 -0
  32. package/releases/0.52.0/core/tree/.claude/pipeline.md +101 -0
  33. package/releases/0.52.0/core/tree/.claude/retrieval.md +103 -0
  34. package/releases/0.52.0/core/tree/.claude/router.md +202 -0
  35. package/releases/0.52.0/core/tree/.claude/templates/integration.md +46 -0
  36. package/releases/0.52.0/core/tree/CLAUDE.md +164 -0
  37. package/releases/0.52.0/core/tree/scripts/cost-watch.mjs +25 -0
  38. package/releases/0.52.0/core/tree/scripts/edit-guard.mjs +24 -0
  39. package/releases/0.52.0/core/tree/scripts/harness-check.mjs +71 -0
  40. package/releases/0.52.0/core/tree/scripts/loop-gate.mjs +19 -0
  41. package/releases/0.52.0/core/vocabulary.json +13 -0
  42. package/releases/0.52.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  43. package/releases/0.52.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  44. package/releases/0.52.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  45. package/releases/0.52.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  46. package/releases/0.52.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  47. package/releases/0.52.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  48. package/releases/0.52.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
  49. package/releases/0.52.0/surfaces/blockchain/tree/.claude/router.md +6 -0
  50. package/releases/0.52.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
  51. package/releases/0.52.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
  52. package/releases/0.52.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
  53. package/releases/0.52.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
  54. package/releases/0.52.0/surfaces/d1/tree/.claude/patterns.md +8 -0
  55. package/releases/0.52.0/surfaces/d1/tree/CLAUDE.md +2 -0
  56. package/releases/0.52.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
  57. package/releases/0.52.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
  58. package/releases/0.52.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
  59. package/releases/0.52.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
  60. package/releases/0.52.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
  61. package/releases/0.52.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
  62. package/releases/0.52.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
  63. package/releases/0.52.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
  64. package/releases/0.52.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
  65. package/releases/0.52.0/surfaces/db/tree/.claude/graph.md +8 -0
  66. package/releases/0.52.0/surfaces/db/tree/.claude/patterns.md +22 -0
  67. package/releases/0.52.0/surfaces/db/tree/.claude/pipeline.md +13 -0
  68. package/releases/0.52.0/surfaces/db/tree/.claude/retrieval.md +5 -0
  69. package/releases/0.52.0/surfaces/db/tree/.claude/router.md +4 -0
  70. package/releases/0.52.0/surfaces/db/tree/CLAUDE.md +17 -0
  71. package/releases/0.52.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
  72. package/releases/0.52.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
  73. package/releases/0.52.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
  74. package/releases/0.52.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
  75. package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  76. package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  77. package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
  78. package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  79. package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  80. package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  81. package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  82. package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  83. package/releases/0.52.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  84. package/releases/0.52.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  85. package/releases/0.52.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
  86. package/releases/0.52.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  87. package/releases/0.52.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  88. package/releases/0.52.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  89. package/releases/0.52.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  90. package/releases/0.52.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
  91. package/releases/0.52.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
  92. package/releases/0.52.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
  93. package/releases/0.52.0/surfaces/frontend/tree/.claude/router.md +14 -0
  94. package/releases/0.52.0/surfaces/frontend/tree/CLAUDE.md +5 -0
  95. package/releases/0.52.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  96. package/releases/0.52.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  97. package/releases/0.52.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  98. package/releases/0.52.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  99. package/releases/0.52.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  100. package/releases/0.52.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  101. package/releases/0.52.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  102. package/releases/0.52.0/surfaces/integrations/tree/.claude/graph.md +10 -0
  103. package/releases/0.52.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
  104. package/releases/0.52.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  105. package/releases/0.52.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  106. package/releases/0.52.0/surfaces/integrations/tree/.claude/router.md +6 -0
  107. package/releases/0.52.0/surfaces/integrations/tree/CLAUDE.md +17 -0
  108. package/releases/0.52.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
  109. package/releases/0.52.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
  110. package/releases/0.52.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  111. package/releases/0.52.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  112. package/releases/0.52.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  113. package/releases/0.52.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
  114. package/releases/0.52.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  115. package/releases/0.52.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  116. package/releases/0.52.0/surfaces/money/tree/.claude/graph.md +2 -0
  117. package/releases/0.52.0/surfaces/money/tree/.claude/patterns.md +49 -0
  118. package/releases/0.52.0/surfaces/money/tree/.claude/pipeline.md +2 -0
  119. package/releases/0.52.0/surfaces/money/tree/.claude/retrieval.md +2 -0
  120. package/releases/0.52.0/surfaces/money/tree/CLAUDE.md +11 -0
  121. package/releases/0.52.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  122. package/releases/0.52.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  123. package/releases/0.52.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  124. package/releases/0.52.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  125. package/releases/0.52.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  126. package/releases/0.52.0/surfaces/pii/tree/CLAUDE.md +5 -0
  127. package/releases/0.52.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
  128. package/releases/0.52.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
  129. package/releases/0.52.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
  130. package/releases/0.52.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
  131. package/releases/0.52.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
  132. package/releases/0.52.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
  133. package/releases/0.52.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
  134. package/releases/0.52.0/surfaces/prisma/tree/CLAUDE.md +5 -0
  135. package/releases/0.53.0/core/GAPS.md +125 -0
  136. package/releases/0.53.0/core/answered.json +68 -0
  137. package/releases/0.53.0/core/defaults/tree/.claude/agents/architect.md +1 -0
  138. package/releases/0.53.0/core/defaults/tree/.claude/agents/dba.md +3 -0
  139. package/releases/0.53.0/core/defaults/tree/.claude/agents/devops.md +1 -0
  140. package/releases/0.53.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
  141. package/releases/0.53.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  142. package/releases/0.53.0/core/defaults/tree/.claude/agents/qa.md +7 -0
  143. package/releases/0.53.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  144. package/releases/0.53.0/core/defaults/tree/.claude/agents/secops.md +3 -0
  145. package/releases/0.53.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  146. package/releases/0.53.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  147. package/releases/0.53.0/core/tree/.claude/agents/architect.md +129 -0
  148. package/releases/0.53.0/core/tree/.claude/agents/dba.md +117 -0
  149. package/releases/0.53.0/core/tree/.claude/agents/devops.md +118 -0
  150. package/releases/0.53.0/core/tree/.claude/agents/implementer.md +163 -0
  151. package/releases/0.53.0/core/tree/.claude/agents/integration-tester.md +168 -0
  152. package/releases/0.53.0/core/tree/.claude/agents/planner.md +132 -0
  153. package/releases/0.53.0/core/tree/.claude/agents/qa.md +150 -0
  154. package/releases/0.53.0/core/tree/.claude/agents/reviewer.md +184 -0
  155. package/releases/0.53.0/core/tree/.claude/agents/secops.md +120 -0
  156. package/releases/0.53.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
  157. package/releases/0.53.0/core/tree/.claude/agents/solidity-dev.md +123 -0
  158. package/releases/0.53.0/core/tree/.claude/agents-overview.md +64 -0
  159. package/releases/0.53.0/core/tree/.claude/frontend.md +10 -0
  160. package/releases/0.53.0/core/tree/.claude/graph.md +75 -0
  161. package/releases/0.53.0/core/tree/.claude/patterns.md +251 -0
  162. package/releases/0.53.0/core/tree/.claude/pills/README.md +150 -0
  163. package/releases/0.53.0/core/tree/.claude/pipeline.md +101 -0
  164. package/releases/0.53.0/core/tree/.claude/retrieval.md +103 -0
  165. package/releases/0.53.0/core/tree/.claude/router.md +202 -0
  166. package/releases/0.53.0/core/tree/.claude/templates/integration.md +46 -0
  167. package/releases/0.53.0/core/tree/CLAUDE.md +164 -0
  168. package/releases/0.53.0/core/tree/scripts/cost-watch.mjs +25 -0
  169. package/releases/0.53.0/core/tree/scripts/edit-guard.mjs +24 -0
  170. package/releases/0.53.0/core/tree/scripts/harness-check.mjs +71 -0
  171. package/releases/0.53.0/core/tree/scripts/loop-gate.mjs +19 -0
  172. package/releases/0.53.0/core/vocabulary.json +13 -0
  173. package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  174. package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  175. package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  176. package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  177. package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  178. package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  179. package/releases/0.53.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
  180. package/releases/0.53.0/surfaces/blockchain/tree/.claude/router.md +6 -0
  181. package/releases/0.53.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
  182. package/releases/0.53.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
  183. package/releases/0.53.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
  184. package/releases/0.53.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
  185. package/releases/0.53.0/surfaces/d1/tree/.claude/patterns.md +8 -0
  186. package/releases/0.53.0/surfaces/d1/tree/CLAUDE.md +2 -0
  187. package/releases/0.53.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
  188. package/releases/0.53.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
  189. package/releases/0.53.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
  190. package/releases/0.53.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
  191. package/releases/0.53.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
  192. package/releases/0.53.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
  193. package/releases/0.53.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
  194. package/releases/0.53.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
  195. package/releases/0.53.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
  196. package/releases/0.53.0/surfaces/db/tree/.claude/graph.md +8 -0
  197. package/releases/0.53.0/surfaces/db/tree/.claude/patterns.md +22 -0
  198. package/releases/0.53.0/surfaces/db/tree/.claude/pipeline.md +13 -0
  199. package/releases/0.53.0/surfaces/db/tree/.claude/retrieval.md +5 -0
  200. package/releases/0.53.0/surfaces/db/tree/.claude/router.md +4 -0
  201. package/releases/0.53.0/surfaces/db/tree/CLAUDE.md +17 -0
  202. package/releases/0.53.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
  203. package/releases/0.53.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
  204. package/releases/0.53.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
  205. package/releases/0.53.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
  206. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  207. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  208. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
  209. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  210. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  211. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  212. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  213. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  214. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  215. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  216. package/releases/0.53.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
  217. package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  218. package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  219. package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  220. package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  221. package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
  222. package/releases/0.53.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
  223. package/releases/0.53.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
  224. package/releases/0.53.0/surfaces/frontend/tree/.claude/router.md +14 -0
  225. package/releases/0.53.0/surfaces/frontend/tree/CLAUDE.md +5 -0
  226. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  227. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  228. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  229. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  230. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  231. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  232. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  233. package/releases/0.53.0/surfaces/integrations/tree/.claude/graph.md +10 -0
  234. package/releases/0.53.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
  235. package/releases/0.53.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  236. package/releases/0.53.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  237. package/releases/0.53.0/surfaces/integrations/tree/.claude/router.md +6 -0
  238. package/releases/0.53.0/surfaces/integrations/tree/CLAUDE.md +17 -0
  239. package/releases/0.53.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
  240. package/releases/0.53.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
  241. package/releases/0.53.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  242. package/releases/0.53.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  243. package/releases/0.53.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  244. package/releases/0.53.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
  245. package/releases/0.53.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  246. package/releases/0.53.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  247. package/releases/0.53.0/surfaces/money/tree/.claude/graph.md +2 -0
  248. package/releases/0.53.0/surfaces/money/tree/.claude/patterns.md +49 -0
  249. package/releases/0.53.0/surfaces/money/tree/.claude/pipeline.md +2 -0
  250. package/releases/0.53.0/surfaces/money/tree/.claude/retrieval.md +2 -0
  251. package/releases/0.53.0/surfaces/money/tree/CLAUDE.md +11 -0
  252. package/releases/0.53.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  253. package/releases/0.53.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  254. package/releases/0.53.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  255. package/releases/0.53.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  256. package/releases/0.53.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  257. package/releases/0.53.0/surfaces/pii/tree/CLAUDE.md +5 -0
  258. package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
  259. package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
  260. package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
  261. package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
  262. package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
  263. package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
  264. package/releases/0.53.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
  265. package/releases/0.53.0/surfaces/prisma/tree/CLAUDE.md +5 -0
  266. package/src/commands/dashboard.mjs +33 -0
  267. package/src/commands/learn.mjs +51 -19
  268. package/src/commands/pills.mjs +55 -28
  269. package/src/commands/pipeline.mjs +62 -33
  270. package/src/commands/runs.mjs +14 -3
  271. package/src/commands/stats.mjs +227 -115
  272. package/src/dashboard.html +328 -0
  273. package/src/dashboard.mjs +185 -0
  274. package/src/gate.mjs +14 -2
  275. package/src/office.html +142 -4
  276. package/src/office.mjs +192 -104
  277. package/src/serve.mjs +108 -0
  278. package/src/store.mjs +13 -1
@@ -66,12 +66,25 @@ function summarize(cycle) {
66
66
  unpriced,
67
67
  name: String(rounds[0]?.desc ?? '').trim() || '(no description)',
68
68
  how: ENDS[cycle.end](rounds.at(-1)),
69
+ end_kind: cycle.end,
69
70
  stages: [...stages.values()].map((s) => ({ ...s, runs: s.runs.size })),
70
71
  files: files.length ? Math.max(...files) : null,
71
72
  peak: peaks.length ? Math.max(...peaks) : null,
72
73
  };
73
74
  }
74
75
 
76
+ /**
77
+ * A project's pieces of work, newest first: what `nina runs` prints and the dashboard's tasks tab shows.
78
+ *
79
+ * @param {object[]} records - The project's measured rounds.
80
+ * @returns {ReturnType<typeof summarize>[]}
81
+ */
82
+ export function taskList(records) {
83
+ return cyclesOf(records.filter((r) => r.status !== 'denied'))
84
+ .map(summarize)
85
+ .sort((a, b) => b.start.localeCompare(a.start));
86
+ }
87
+
75
88
  /** A duration in minutes, as hours and minutes. */
76
89
  const span = (m) => (m === null ? '—' : m >= 60 ? `${Math.floor(m / 60)}h${String(m % 60).padStart(2, '0')}` : `${m}m`);
77
90
 
@@ -103,9 +116,7 @@ export async function runs(argv) {
103
116
  );
104
117
  return 1;
105
118
  }
106
- const cycles = cyclesOf(records)
107
- .map(summarize)
108
- .sort((a, b) => b.start.localeCompare(a.start));
119
+ const cycles = taskList(records);
109
120
  const shown = cycles.slice(0, last);
110
121
  console.log(
111
122
  ` ${bold(pink(projectName(records[0].project)))} · ${cycles.length} cycle(s)` +
@@ -222,9 +222,15 @@ const running = (r, now) => !r.verdict && now - Date.parse(r.ts) <= RUNNING_HOUR
222
222
  *
223
223
  * @param {object[]} records - The runs in the window.
224
224
  */
225
- function costReport(records) {
225
+ /**
226
+ * What each stage's runs cost at API list prices, as data: what `stats` prints and the dashboard's stats tab shows.
227
+ * Null when no run has a token record, or none has a price.
228
+ *
229
+ * @param {object[]} records - The rounds in the window.
230
+ */
231
+ export function costData(records) {
226
232
  const measured = records.filter((r) => r.tokens && typeof r.tokens === 'object');
227
- if (measured.length === 0) return;
233
+ if (measured.length === 0) return null;
228
234
  // A run's cost is its rounds', so a reviewer resumed twice is one run at the price of three rounds.
229
235
  /** @type {Map<string, number[]>} */
230
236
  const byRole = new Map();
@@ -240,25 +246,33 @@ function costReport(records) {
240
246
  byRole.set(role, [...(byRole.get(role) ?? []), costs.reduce((a, b) => a + b, 0)]);
241
247
  }
242
248
  const total = [...byRole.values()].flat().reduce((a, b) => a + b, 0);
243
- if (total === 0) return;
249
+ if (total === 0) return null;
250
+ const rows = [...byRole]
251
+ .map(([role, costs]) => ({ role, runs: costs.length, median: median(costs), total: costs.reduce((a, b) => a + b, 0) }))
252
+ .sort((a, b) => b.total - a.total);
253
+ return { pricesAsOf: PRICES_AS_OF, measuredRuns: runs.size, allRuns: byRun(records).size, unpriced, total, rows };
254
+ }
255
+
256
+ function costReport(records) {
257
+ const d = costData(records);
258
+ if (!d) return;
244
259
  const money = (n) => `$${n >= 100 ? n.toFixed(0) : n.toFixed(2)}`;
245
260
  // The same convention as the duration column beside it: the upper middle of an even count.
246
261
  console.log(
247
262
  heading(
248
263
  'cost',
249
- `at API list prices of ${PRICES_AS_OF}; ${runs.size} of ${byRun(records).size} runs have a token record` +
250
- (unpriced > 0 ? `, ${unpriced} on a model the price table does not know` : ''),
264
+ `at API list prices of ${d.pricesAsOf}; ${d.measuredRuns} of ${d.allRuns} runs have a token record` +
265
+ (d.unpriced > 0 ? `, ${d.unpriced} on a model the price table does not know` : ''),
251
266
  ),
252
267
  );
253
268
  console.log(dim(` ${'stage'.padEnd(20)}${'runs'.padStart(6)}${'median'.padStart(10)}${'total'.padStart(10)}${'share'.padStart(8)}`));
254
- for (const [role, costs] of [...byRole].sort((a, b) => b[1].reduce((x, y) => x + y, 0) - a[1].reduce((x, y) => x + y, 0))) {
255
- const sum = costs.reduce((a, b) => a + b, 0);
269
+ for (const { role, runs, median: middle, total: sum } of d.rows) {
256
270
  console.log(
257
- ` ${pink(role)}${' '.repeat(Math.max(20 - role.length, 1))}${String(costs.length).padStart(6)}${money(median(costs)).padStart(10)}` +
258
- `${bold(money(sum).padStart(10))}${pct(sum, total).padStart(8)} ${pink(bar(sum / total, 20))}`,
271
+ ` ${pink(role)}${' '.repeat(Math.max(20 - role.length, 1))}${String(runs).padStart(6)}${money(middle).padStart(10)}` +
272
+ `${bold(money(sum).padStart(10))}${pct(sum, d.total).padStart(8)} ${pink(bar(sum / d.total, 20))}`,
259
273
  );
260
274
  }
261
- console.log(bold(` ${'all stages'.padEnd(20)}${String([...byRole.values()].flat().length).padStart(6)}${''.padStart(10)}${money(total).padStart(10)}`));
275
+ console.log(bold(` ${'all stages'.padEnd(20)}${String(d.rows.reduce((a, r) => a + r.runs, 0)).padStart(6)}${''.padStart(10)}${money(d.total).padStart(10)}`));
262
276
  }
263
277
 
264
278
  /**
@@ -270,30 +284,57 @@ function costReport(records) {
270
284
  * @param {object[]} records - The rounds in the window.
271
285
  */
272
286
  function contextReport(records) {
273
- const shaped = records.filter((r) => typeof r.turns === 'number' && r.turns > 0);
274
- if (shaped.length === 0) return;
287
+ const d = contextData(records);
288
+ if (!d) return;
275
289
  const k = (n) => (typeof n === 'number' ? `${Math.round(n / 1000)}k` : '—');
276
- const chars = (xs) => (xs.length ? median(xs).toLocaleString('en-US') : '—');
277
290
  console.log(heading('context', 'every turn re-reads the whole context: a round costs its turns times its size, and a resumed one starts where it ended; the prompt it was handed is in characters'));
278
291
  console.log(dim(` ${'stage'.padEnd(20)}${'rounds'.padEnd(8)}${'n'.padStart(4)}${'turns'.padStart(7)}${'calls/turn'.padStart(12)}${'starts at'.padStart(11)}${'peak'.padStart(8)}${'prompt'.padStart(9)}`));
292
+ let previous = null;
293
+ for (const row of d.rows) {
294
+ const name = row.role !== previous ? pink(row.role) + ' '.repeat(Math.max(20 - row.role.length, 1)) : ' '.repeat(20);
295
+ previous = row.role;
296
+ console.log(
297
+ ` ${name}${dim(row.label.padEnd(8))}${String(row.n).padStart(4)}${String(row.turns).padStart(7)}` +
298
+ `${row.callsPerTurn.toFixed(2).padStart(12)}${k(row.start).padStart(11)}${k(row.peak).padStart(8)}` +
299
+ `${(row.prompt === null ? '—' : row.prompt.toLocaleString('en-US')).padStart(9)}`,
300
+ );
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Each stage's rounds and how they spent their context, as data (see `contextReport`): the first rounds of a run
306
+ * and the resumed ones apart. Null when no round recorded its turns.
307
+ *
308
+ * @param {object[]} records - The rounds in the window.
309
+ */
310
+ export function contextData(records) {
311
+ const shaped = records.filter((r) => typeof r.turns === 'number' && r.turns > 0);
312
+ if (shaped.length === 0) return null;
279
313
  const byRole = new Map();
280
314
  for (const r of shaped) byRole.set(r.role, [...(byRole.get(r.role) ?? []), r]);
315
+ const rows = [];
281
316
  for (const [role, rounds] of [...byRole].sort((a, b) => b[1].reduce((x, r) => x + r.turns, 0) - a[1].reduce((x, r) => x + r.turns, 0))) {
282
- [
317
+ for (const [label, group] of [
283
318
  ['first', rounds.filter((r) => (r.round ?? 1) === 1)],
284
319
  ['resumed', rounds.filter((r) => (r.round ?? 1) > 1)],
285
- ].forEach(([label, group], i) => {
286
- if (group.length === 0) return;
320
+ ]) {
321
+ if (group.length === 0) continue;
287
322
  const turns = group.reduce((a, r) => a + r.turns, 0);
288
323
  const calls = group.reduce((a, r) => a + (r.tool_calls ?? 0), 0);
289
- const name = i === 0 || !rounds.some((r) => (r.round ?? 1) === 1) ? pink(role) + ' '.repeat(Math.max(20 - role.length, 1)) : ' '.repeat(20);
290
- console.log(
291
- ` ${name}${dim(label.padEnd(8))}${String(group.length).padStart(4)}${String(median(group.map((r) => r.turns))).padStart(7)}` +
292
- `${(calls / turns).toFixed(2).padStart(12)}${k(median(group.map((r) => r.context_start ?? 0))).padStart(11)}${k(median(group.map((r) => r.context_peak ?? 0))).padStart(8)}` +
293
- `${chars(group.map((r) => r.prompt_chars).filter((n) => typeof n === 'number')).padStart(9)}`,
294
- );
295
- });
324
+ const prompts = group.map((r) => r.prompt_chars).filter((n) => typeof n === 'number');
325
+ rows.push({
326
+ role,
327
+ label,
328
+ n: group.length,
329
+ turns: median(group.map((r) => r.turns)),
330
+ callsPerTurn: calls / turns,
331
+ start: median(group.map((r) => r.context_start ?? 0)),
332
+ peak: median(group.map((r) => r.context_peak ?? 0)),
333
+ prompt: prompts.length ? median(prompts) : null,
334
+ });
335
+ }
296
336
  }
337
+ return { rows };
297
338
  }
298
339
 
299
340
  /**
@@ -436,10 +477,17 @@ export const WRITERS = new Set(['implementer', 'solidity-dev']);
436
477
  /** The stages whose presence makes a cycle a designed one. */
437
478
  const DESIGNERS = new Set(['planner', 'architect']);
438
479
 
480
+ /**
481
+ * The verdict a run still at work stands in with, for a reader that cuts a session as it happens — the office's
482
+ * timeline: it closes nothing, being neither a closing verdict nor one that could not be read, and it lets no
483
+ * write through. A run with none at all would read as unreadable, and a qa still at work would close its cycle.
484
+ */
485
+ export const IN_FLIGHT = 'IN-FLIGHT';
486
+
439
487
  /**
440
488
  * A session's records cut into cycles: each up to the verdict that closes one, or to the next design stage
441
- * after code was written unless a loop-back sent the work there, or to a closing stage whose verdict cannot
442
- * be read (see `proportionReport`). What `stats` sizes and `nina runs` prices.
489
+ * after code was written and let through, or to a closing stage whose verdict cannot be read (see
490
+ * `proportionReport`). What `stats` sizes, `nina runs` prices and the office's timeline names.
443
491
  *
444
492
  * @param {object[]} records - Records of any number of sessions and projects.
445
493
  * @returns {{runs: object[], end: 'closed'|'unreadable'|'next'|'open'}[]} In the order they happened within
@@ -455,21 +503,33 @@ export function cyclesOf(records) {
455
503
  /** @type {{runs: object[], end: 'closed'|'unreadable'|'next'|'open'}[]} */
456
504
  const cycles = [];
457
505
  const unread = (verdict) => !verdict || verdict === 'UNCLEAR' || verdict === 'NONE';
506
+ // Code was written, and what reviewed its latest write let it through: a verdict read from a stage that reviews —
507
+ // neither one that writes nor one that designs — and nothing sent back since. Read off the one verdict dispatched
508
+ // before the design, the gates that run side by side made it a matter of which went out last — a rejection beside
509
+ // three approvals ended the change its own fix belonged to.
510
+ const letThrough = (cycle) => {
511
+ const write = cycle.findLastIndex((x) => WRITERS.has(x.role));
512
+ if (write < 0) return false;
513
+ const since = cycle
514
+ .slice(write + 1)
515
+ .filter((x) => !WRITERS.has(x.role) && !DESIGNERS.has(x.role))
516
+ .map((x) => x.verdict)
517
+ .filter((v) => !unread(v) && v !== IN_FLIGHT);
518
+ return since.length > 0 && !since.some(isLoopBack);
519
+ };
458
520
  for (const runs of bySession.values()) {
459
521
  let cycle = [];
460
- let previous = null;
461
522
  const end = (how) => {
462
523
  if (cycle.length > 0) cycles.push({ runs: cycle, end: how });
463
524
  cycle = [];
464
525
  };
465
526
  for (const r of runs) {
466
- if (DESIGNERS.has(r.role) && cycle.some((x) => WRITERS.has(x.role)) && !isLoopBack(previous)) end('next');
527
+ if (DESIGNERS.has(r.role) && letThrough(cycle)) end('next');
467
528
  cycle.push(r);
468
529
  if (r.role in CYCLE_ENDS) {
469
530
  if (r.verdict === CYCLE_ENDS[r.role]) end('closed');
470
531
  else if (unread(r.verdict)) end('unreadable');
471
532
  }
472
- if (!unread(r.verdict)) previous = r.verdict;
473
533
  }
474
534
  end('open');
475
535
  }
@@ -482,9 +542,10 @@ export function cyclesOf(records) {
482
542
  * (`CYCLE_ENDS`), so a loop-back's fix rounds stay with the design that preceded them — the owner's own
483
543
  * prompts were tried as the boundary first, and every "pode seguir" after a spec cut a pipeline in two.
484
544
  * Two more things end a cycle, both found in real sessions: a design stage dispatched after code was
485
- * written starts the next change — the light chain has no qa to close it, and it was absorbed into the
486
- * design that followed — unless the verdict before it sent work back, which the graph routes into both
487
- * designers; and a closing stage whose verdict cannot be read still closes it, counted as unreadable,
545
+ * written and let through starts the next change — the light chain has no qa to close it, and it was absorbed
546
+ * into the design that followed — but not while anything since the latest write sent work back, which the graph
547
+ * routes into both designers, or no stage that reviews has read that write yet, as when a fix and its spec's
548
+ * correction go out together; and a closing stage whose verdict cannot be read still closes it, counted as unreadable,
488
549
  * because a qa run with no verdict line otherwise let one cycle swallow a dozen pipelines. A cycle is
489
550
  * sized by the most files any one of its writers wrote, not their sum, so three fix rounds on two files
490
551
  * stay two files; and only edits through the edit tools count, so a file written from a shell is missed
@@ -498,8 +559,44 @@ export function cyclesOf(records) {
498
559
  * @param {object[]} records - The runs in the window.
499
560
  */
500
561
  function proportionReport(records, stepLimit = () => null) {
562
+ const d = proportionData(records, stepLimit);
563
+ if (!d) return;
564
+ console.log(
565
+ heading(
566
+ 'proportion',
567
+ `${d.built} cycle(s) that wrote code; a cycle closes at qa PASS, devops DEPLOYED or secops SECURE, or at the next design after code` +
568
+ (d.unreadable > 0 ? `; ${d.unreadable} closed by a run whose verdict could not be read` : '') +
569
+ (d.open > 0 ? `; ${d.open} still open when their session's record ends` : ''),
570
+ ),
571
+ );
572
+ console.log(dim(` ${'largest write'.padEnd(16)}${'cycles'.padStart(8)}${'with a planner or architect'.padStart(30)}`));
573
+ for (const row of d.rows) {
574
+ const designed = `${row.designed} (${pct(row.designed, row.cycles)})`.padStart(30);
575
+ console.log(` ${row.label.padEnd(16)}${String(row.cycles).padStart(8)}${row.cycles ? designed : dim(designed)} ${pink(bar(row.cycles ? row.designed / row.cycles : 0, 10))}`);
576
+ }
577
+ console.log(dim(' A shape, not a verdict: a critical path is gated in full at any size, and one spec may cover sibling steps.'));
578
+ if (d.over) {
579
+ const read = d.over.readM === null ? '' : `, ${d.over.readM}M tokens read from cache`;
580
+ console.log(
581
+ note(
582
+ `${d.over.count} implementer round(s) wrote more files than one step may (${d.over.limit}); the largest wrote ${d.over.largest}${read}. The architect splits such a spec into steps.`,
583
+ 'warn',
584
+ ),
585
+ );
586
+ }
587
+ }
588
+
589
+ /**
590
+ * The size of each change against the chain that carried it, as data (see `proportionReport`): cycles that wrote code
591
+ * by their largest write, how many had a design stage, and the implementer rounds that wrote more files than one step
592
+ * may. Null when no cycle wrote code that was counted.
593
+ *
594
+ * @param {object[]} records - The rounds in the window.
595
+ * @param {(project: string) => number|null} [stepLimit] - The most files one implementer run may write, by project.
596
+ */
597
+ export function proportionData(records, stepLimit = () => null) {
501
598
  const measured = records.filter((r) => WRITERS.has(r.role) && typeof r.files_touched === 'number');
502
- if (measured.length === 0) return;
599
+ if (measured.length === 0) return null;
503
600
  const cycles = cyclesOf(records);
504
601
  const rows = [
505
602
  { label: '1–2 files', from: 1, to: 2 },
@@ -521,22 +618,7 @@ function proportionReport(records, stepLimit = () => null) {
521
618
  row.cycles += 1;
522
619
  if (cycle.some((r) => DESIGNERS.has(r.role))) row.designed += 1;
523
620
  }
524
- if (built === 0) return;
525
- console.log(
526
- heading(
527
- 'proportion',
528
- `${built} cycle(s) that wrote code; a cycle closes at qa PASS, devops DEPLOYED or secops SECURE, or at the next design after code` +
529
- (unreadable > 0 ? `; ${unreadable} closed by a run whose verdict could not be read` : '') +
530
- (open > 0 ? `; ${open} still open when their session's record ends` : ''),
531
- ),
532
- );
533
- console.log(dim(` ${'largest write'.padEnd(16)}${'cycles'.padStart(8)}${'with a planner or architect'.padStart(30)}`));
534
- for (const row of rows) {
535
- const designed = `${row.designed} (${pct(row.designed, row.cycles)})`.padStart(30);
536
- console.log(` ${row.label.padEnd(16)}${String(row.cycles).padStart(8)}${row.cycles ? designed : dim(designed)} ${pink(bar(row.cycles ? row.designed / row.cycles : 0, 10))}`);
537
- }
538
- console.log(dim(' A shape, not a verdict: a critical path is gated in full at any size, and one spec may cover sibling steps.'));
539
-
621
+ if (built === 0) return null;
540
622
  // The one size the harness does limit: what one implementer run writes. A run re-reads the context it
541
623
  // has built on every turn, so its cost grows faster than its size — measured over two projects, the
542
624
  // cache read per file held near 1M up to 19 files and was 6.9M in a 52-file run. Asked only of a project
@@ -546,16 +628,16 @@ function proportionReport(records, stepLimit = () => null) {
546
628
  const limit = stepLimit(r.project);
547
629
  return limit !== null && r.files_touched > limit;
548
630
  });
549
- if (over.length > 0) {
550
- const largest = over.reduce((a, b) => (b.files_touched > a.files_touched ? b : a));
551
- const read = typeof largest.tokens?.read === 'number' ? `, ${Math.round(largest.tokens.read / 1e6)}M tokens read from cache` : '';
552
- console.log(
553
- note(
554
- `${over.length} implementer round(s) wrote more files than one step may (${stepLimit(largest.project)}); the largest wrote ${largest.files_touched}${read}. The architect splits such a spec into steps.`,
555
- 'warn',
556
- ),
557
- );
558
- }
631
+ const largest = over.length ? over.reduce((a, b) => (b.files_touched > a.files_touched ? b : a)) : null;
632
+ return {
633
+ rows: rows.map(({ label, cycles: n, designed }) => ({ label, cycles: n, designed })),
634
+ built,
635
+ unreadable,
636
+ open,
637
+ over: largest
638
+ ? { count: over.length, limit: stepLimit(largest.project), largest: largest.files_touched, readM: typeof largest.tokens?.read === 'number' ? Math.round(largest.tokens.read / 1e6) : null }
639
+ : null,
640
+ };
559
641
  }
560
642
 
561
643
  /**
@@ -587,13 +669,74 @@ function stepLimitOf(ctx) {
587
669
  };
588
670
  }
589
671
 
672
+ /**
673
+ * Each stage's rounds and what came of them, as data: what `stats` prints in its stages table and the dashboard's
674
+ * stats tab shows. Counted in rounds — the verdicts are the rounds', and the loop gate counts rounds — with the runs
675
+ * they belong to; and the rounds of agents outside the pipeline apart, which have no verdict to give.
676
+ *
677
+ * @param {object[]} records - The rounds in the window.
678
+ * @param {number} [now] - Against which a round with no report is still running or lost.
679
+ */
680
+ export function stageData(records, now = Date.now()) {
681
+ /** @type {Map<string, {n:number, runs:Set<string>, done:number, lost:number, clear:number, loop:number, unclear:number, declared:number, durations:number[]}>} */
682
+ const byRole = new Map();
683
+ // Agents outside the pipeline — Claude Code's own, a project's own — have no verdict to give. Read as
684
+ // stages they were 100% unreadable and were told to add a verdict token their spec never asked for.
685
+ const outside = new Map();
686
+ for (const r of records.filter((r) => r.status !== 'denied')) {
687
+ if (!PIPELINE_ROLES.has(r.role)) {
688
+ outside.set(r.role, (outside.get(r.role) ?? 0) + 1);
689
+ continue;
690
+ }
691
+ if (!byRole.has(r.role)) {
692
+ byRole.set(r.role, { n: 0, runs: new Set(), done: 0, lost: 0, clear: 0, loop: 0, unclear: 0, declared: 0, durations: [] });
693
+ }
694
+ const s = byRole.get(r.role);
695
+ s.n += 1;
696
+ s.runs.add(runOf(r));
697
+ if (!r.verdict && !running(r, now)) s.lost += 1;
698
+ if (r.verdict) s.done += 1;
699
+ if (r.verdict_source === 'declared' || r.verdict_source === 'handback') s.declared += 1;
700
+ if (r.verdict === 'UNCLEAR' || r.verdict === 'NONE') s.unclear += 1;
701
+ else if (r.verdict) {
702
+ s.clear += 1;
703
+ if (isLoopBack(r.verdict)) s.loop += 1;
704
+ }
705
+ if (typeof r.duration_s === 'number') s.durations.push(r.duration_s);
706
+ }
707
+ const rows = [...byRole.entries()]
708
+ .sort((a, b) => b[1].n - a[1].n)
709
+ .map(([role, s]) => {
710
+ const durations = s.durations.filter((v) => typeof v === 'number' && v >= 0);
711
+ return { role, runs: s.runs.size, rounds: s.n, done: s.done, lost: s.lost, clear: s.clear, loop: s.loop, unclear: s.unclear, declared: s.declared, median_s: durations.length ? median(durations) : null };
712
+ });
713
+ return { rows, outside: [...outside.entries()].sort((a, b) => b[1] - a[1]) };
714
+ }
715
+
590
716
  /** The middle of a list of numbers — the upper one of two — or undefined for none. */
591
717
  const median = (xs) => [...xs].sort((a, b) => a - b)[Math.floor(xs.length / 2)];
592
718
 
593
- /** Formats a median duration in minutes from a list of seconds. */
594
- function medianMin(values) {
595
- const xs = values.filter((v) => typeof v === 'number' && v >= 0);
596
- return xs.length ? `${(median(xs) / 60).toFixed(1)}m` : '—';
719
+ /**
720
+ * One project's measured history, as the dashboard's stats tab shows it: the same sections `stats` prints — its
721
+ * stages, what they cost, how they spent their context, and the size of each change against its chain — each from
722
+ * the function the report prints from. Null when nothing is recorded.
723
+ *
724
+ * @param {object[]} records - The project's rounds, oldest first.
725
+ * @param {{root: string}} ctx - CLI context, for the step limit its pinned release states.
726
+ */
727
+ export function statsData(records, ctx) {
728
+ if (records.length === 0) return null;
729
+ const ran = records.filter((r) => r.status !== 'denied');
730
+ return {
731
+ runs: byRun(ran).size,
732
+ rounds: ran.length,
733
+ denied: records.length - ran.length,
734
+ span: [records[0]?.ts, records.at(-1)?.ts].map(localDay),
735
+ stages: stageData(records),
736
+ cost: costData(ran),
737
+ context: contextData(ran),
738
+ proportion: proportionData(ran, stepLimitOf(ctx)),
739
+ };
597
740
  }
598
741
 
599
742
  /**
@@ -667,40 +810,9 @@ export async function stats(argv, ctx) {
667
810
 
668
811
  const skipped = new Set(all.map((r) => r.project)).size - new Set(records.map((r) => r.project)).size;
669
812
 
670
- /**
671
- * Per stage, counted in rounds: the verdicts are the rounds', and the loop gate counts rounds.
672
- *
673
- * @type {Map<string, {n:number, runs:Set<string>, done:number, lost:number, clear:number, loop:number,
674
- * unclear:number, declared:number, durations:number[]}>}
675
- */
676
- const byRole = new Map();
677
813
  // A dispatch a hook denied never ran, so it is neither a run nor a missing verdict.
678
814
  const held = records.filter((r) => r.status === 'denied').length;
679
- const now = Date.now();
680
- // Agents outside the pipeline — Claude Code's own, a project's own — have no verdict to give. Read as
681
- // stages they were 100% unreadable and were told to add a verdict token their spec never asked for.
682
- const outside = new Map();
683
- for (const r of records.filter((r) => r.status !== 'denied')) {
684
- if (!PIPELINE_ROLES.has(r.role)) {
685
- outside.set(r.role, (outside.get(r.role) ?? 0) + 1);
686
- continue;
687
- }
688
- if (!byRole.has(r.role)) {
689
- byRole.set(r.role, { n: 0, runs: new Set(), done: 0, lost: 0, clear: 0, loop: 0, unclear: 0, declared: 0, durations: [] });
690
- }
691
- const s = byRole.get(r.role);
692
- s.n += 1;
693
- s.runs.add(runOf(r));
694
- if (!r.verdict && !running(r, now)) s.lost += 1;
695
- if (r.verdict) s.done += 1;
696
- if (r.verdict_source === 'declared' || r.verdict_source === 'handback') s.declared += 1;
697
- if (r.verdict === 'UNCLEAR' || r.verdict === 'NONE') s.unclear += 1;
698
- else if (r.verdict) {
699
- s.clear += 1;
700
- if (isLoopBack(r.verdict)) s.loop += 1;
701
- }
702
- if (typeof r.duration_s === 'number') s.durations.push(r.duration_s);
703
- }
815
+ const { rows, outside } = stageData(records);
704
816
 
705
817
  const span = [records[0]?.ts, records.at(-1)?.ts].map(localDay);
706
818
  const projectCount = new Set(records.map((r) => r.project)).size;
@@ -721,38 +833,37 @@ export async function stats(argv, ctx) {
721
833
  ),
722
834
  );
723
835
 
724
- const rows = [...byRole.entries()].sort((a, b) => b[1].n - a[1].n);
725
836
  // Each stage's runs as one bar, as long as its share of the busiest stage's: what went forward, what
726
837
  // was sent back, and what said nothing readable (or is still running).
727
- const busiest = Math.max(...rows.map(([, s]) => s.n), 1);
838
+ const busiest = Math.max(...rows.map((s) => s.rounds), 1);
728
839
  const zero = (text, n) => (n === 0 ? dim(text) : text);
729
- for (const [role, s] of rows) {
840
+ for (const s of rows) {
730
841
  const runsBar = stacked(
731
842
  [
732
843
  { n: s.clear - s.loop, char: '█', paint: pink },
733
844
  { n: s.loop, char: '▓', paint: amber },
734
- { n: s.n - s.clear, char: '░', paint: dim },
845
+ { n: s.rounds - s.clear, char: '░', paint: dim },
735
846
  ],
736
- Math.max(1, Math.round((16 * s.n) / busiest)),
847
+ Math.max(1, Math.round((16 * s.rounds) / busiest)),
737
848
  );
738
849
  const declared = pct(s.declared, s.done).padStart(10);
739
850
  const unread = pct(s.unclear + s.lost, s.done + s.lost).padStart(12);
740
851
  console.log(
741
- ` ${pink(role)}${' '.repeat(Math.max(20 - role.length, 1))}${String(s.runs.size).padStart(6)}${String(s.n).padStart(9)}` +
852
+ ` ${pink(s.role)}${' '.repeat(Math.max(20 - s.role.length, 1))}${String(s.runs).padStart(6)}${String(s.rounds).padStart(9)}` +
742
853
  `${zero(String(s.loop).padStart(11), s.loop)}${zero(pct(s.loop, s.clear).padStart(7), s.loop)}` +
743
854
  `${s.declared < s.done ? amber(declared) : dim(declared)}${s.unclear + s.lost > 0 ? amber(unread) : dim(unread)}` +
744
- `${medianMin(s.durations).padStart(9)} ${runsBar}`,
855
+ `${(s.median_s === null ? '—' : `${(s.median_s / 60).toFixed(1)}m`).padStart(9)} ${runsBar}`,
745
856
  );
746
857
  }
747
858
  console.log(dim(` ${' '.repeat(86)}█ forward ▓ sent back ░ no verdict read`));
748
859
 
749
- const unreadable = rows.reduce((a, [, s]) => a + s.unclear, 0);
750
- const lost = rows.reduce((a, [, s]) => a + s.lost, 0);
751
- const done = rows.reduce((a, [, s]) => a + s.done, 0);
860
+ const unreadable = rows.reduce((a, s) => a + s.unclear, 0);
861
+ const lost = rows.reduce((a, s) => a + s.lost, 0);
862
+ const done = rows.reduce((a, s) => a + s.done, 0);
752
863
  console.log('');
753
- if (outside.size > 0) {
754
- const named = [...outside.entries()].sort((a, b) => b[1] - a[1]).map(([role, n]) => `${role} ×${n}`).join(', ');
755
- console.log(note(`${[...outside.values()].reduce((a, b) => a + b, 0)} round(s) by agents outside the pipeline (${named}) — not stages, so no verdict is asked of them.`, 'info'));
864
+ if (outside.length > 0) {
865
+ const named = outside.map(([role, n]) => `${role} ×${n}`).join(', ');
866
+ console.log(note(`${outside.reduce((a, [, n]) => a + n, 0)} round(s) by agents outside the pipeline (${named}) — not stages, so no verdict is asked of them.`, 'info'));
756
867
  }
757
868
  if (unreadable === 0 && lost === 0) console.log(note("every finished round's verdict could be read.", 'ok'));
758
869
  // The same share as the table's column: rounds that finished, or should have, with no verdict read.
@@ -799,20 +910,21 @@ export async function stats(argv, ctx) {
799
910
 
800
911
  // A gate that never stops anything is either upstream quality or a rubber stamp, and the
801
912
  // rate alone cannot say which — but it can say which gate to go and look at.
802
- const gates = rows.filter(([role]) => GATES.has(role));
803
- for (const [role, s] of gates) {
913
+ const gates = rows.filter((s) => GATES.has(s.role));
914
+ for (const s of gates) {
915
+ const { role } = s;
804
916
  if (s.clear < GATE_SAMPLE || s.loop / s.clear >= GATE_FLOOR) continue;
805
- const others = gates.filter(([other]) => other !== role);
917
+ const others = gates.filter((o) => o.role !== role);
806
918
  const elsewhere = pct(
807
- others.reduce((a, [, o]) => a + o.loop, 0),
808
- others.reduce((a, [, o]) => a + o.clear, 0),
919
+ others.reduce((a, o) => a + o.loop, 0),
920
+ others.reduce((a, o) => a + o.clear, 0),
809
921
  );
810
922
  console.log(
811
923
  note(
812
924
  `${role}: ${s.loop} loop-back(s) in ${s.clear} readable verdict(s) (${pct(s.loop, s.clear)}),` +
813
925
  ` against ${elsewhere} across the other gates — check whether it still gates anything.` +
814
- (s.n - s.clear > 0
815
- ? ` ${s.n - s.clear} further round(s) produced no readable verdict at all, so the rate may understate it.`
926
+ (s.rounds - s.clear > 0
927
+ ? ` ${s.rounds - s.clear} further round(s) produced no readable verdict at all, so the rate may understate it.`
816
928
  : '') +
817
929
  // A rate built from verdicts a parser guessed at is not the same claim as one built
818
930
  // from verdicts the stage declared, and the difference decides whether it can be acted on.
@@ -832,7 +944,7 @@ export async function stats(argv, ctx) {
832
944
  // What the pipeline learned, against what it had to learn from. A loop-back is the raw
833
945
  // material and a pill is the product, so the two numbers belong on the same screen: the
834
946
  // rule that turns one into the other is the only rule here that nothing else can check.
835
- const loopBacks = rows.reduce((a, [, s]) => a + s.loop, 0);
947
+ const loopBacks = rows.reduce((a, s) => a + s.loop, 0);
836
948
  const pills = await harvest([...new Set(records.map((r) => r.project))]);
837
949
  const newest = pills.dates.at(-1);
838
950
  const inWindow = pills.dates.filter((d) => d >= span[0] && d <= span[1]).length;