@xhulz/nina 0.34.0 → 0.36.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 (267) hide show
  1. package/bin/nina.mjs +222 -48
  2. package/package.json +1 -1
  3. package/releases/0.35.0/core/GAPS.md +125 -0
  4. package/releases/0.35.0/core/answered.json +29 -0
  5. package/releases/0.35.0/core/defaults/tree/.claude/agents/architect.md +1 -0
  6. package/releases/0.35.0/core/defaults/tree/.claude/agents/dba.md +3 -0
  7. package/releases/0.35.0/core/defaults/tree/.claude/agents/devops.md +1 -0
  8. package/releases/0.35.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
  9. package/releases/0.35.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  10. package/releases/0.35.0/core/defaults/tree/.claude/agents/qa.md +2 -0
  11. package/releases/0.35.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  12. package/releases/0.35.0/core/defaults/tree/.claude/agents/secops.md +3 -0
  13. package/releases/0.35.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  14. package/releases/0.35.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  15. package/releases/0.35.0/core/tree/.claude/agents/architect.md +127 -0
  16. package/releases/0.35.0/core/tree/.claude/agents/dba.md +110 -0
  17. package/releases/0.35.0/core/tree/.claude/agents/devops.md +114 -0
  18. package/releases/0.35.0/core/tree/.claude/agents/implementer.md +156 -0
  19. package/releases/0.35.0/core/tree/.claude/agents/integration-tester.md +168 -0
  20. package/releases/0.35.0/core/tree/.claude/agents/planner.md +132 -0
  21. package/releases/0.35.0/core/tree/.claude/agents/qa.md +153 -0
  22. package/releases/0.35.0/core/tree/.claude/agents/reviewer.md +183 -0
  23. package/releases/0.35.0/core/tree/.claude/agents/secops.md +121 -0
  24. package/releases/0.35.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
  25. package/releases/0.35.0/core/tree/.claude/agents/solidity-dev.md +123 -0
  26. package/releases/0.35.0/core/tree/.claude/agents-overview.md +64 -0
  27. package/releases/0.35.0/core/tree/.claude/frontend.md +10 -0
  28. package/releases/0.35.0/core/tree/.claude/graph.md +75 -0
  29. package/releases/0.35.0/core/tree/.claude/patterns.md +213 -0
  30. package/releases/0.35.0/core/tree/.claude/pills/README.md +146 -0
  31. package/releases/0.35.0/core/tree/.claude/pipeline.md +101 -0
  32. package/releases/0.35.0/core/tree/.claude/retrieval.md +103 -0
  33. package/releases/0.35.0/core/tree/.claude/router.md +198 -0
  34. package/releases/0.35.0/core/tree/.claude/templates/integration.md +45 -0
  35. package/releases/0.35.0/core/tree/CLAUDE.md +161 -0
  36. package/releases/0.35.0/core/tree/scripts/cost-watch.mjs +18 -0
  37. package/releases/0.35.0/core/tree/scripts/edit-guard.mjs +16 -0
  38. package/releases/0.35.0/core/tree/scripts/harness-check.mjs +71 -0
  39. package/releases/0.35.0/core/tree/scripts/loop-gate.mjs +19 -0
  40. package/releases/0.35.0/core/vocabulary.json +13 -0
  41. package/releases/0.35.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  42. package/releases/0.35.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  43. package/releases/0.35.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  44. package/releases/0.35.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  45. package/releases/0.35.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  46. package/releases/0.35.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  47. package/releases/0.35.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
  48. package/releases/0.35.0/surfaces/blockchain/tree/.claude/router.md +6 -0
  49. package/releases/0.35.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
  50. package/releases/0.35.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
  51. package/releases/0.35.0/surfaces/db/tree/.claude/agents/dba.md +50 -0
  52. package/releases/0.35.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
  53. package/releases/0.35.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
  54. package/releases/0.35.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
  55. package/releases/0.35.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
  56. package/releases/0.35.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
  57. package/releases/0.35.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
  58. package/releases/0.35.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
  59. package/releases/0.35.0/surfaces/db/tree/.claude/graph.md +8 -0
  60. package/releases/0.35.0/surfaces/db/tree/.claude/patterns.md +19 -0
  61. package/releases/0.35.0/surfaces/db/tree/.claude/pipeline.md +13 -0
  62. package/releases/0.35.0/surfaces/db/tree/.claude/retrieval.md +5 -0
  63. package/releases/0.35.0/surfaces/db/tree/.claude/router.md +4 -0
  64. package/releases/0.35.0/surfaces/db/tree/CLAUDE.md +17 -0
  65. package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  66. package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  67. package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/devops.md +32 -0
  68. package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  69. package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  70. package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  71. package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  72. package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  73. package/releases/0.35.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  74. package/releases/0.35.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  75. package/releases/0.35.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
  76. package/releases/0.35.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  77. package/releases/0.35.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  78. package/releases/0.35.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  79. package/releases/0.35.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  80. package/releases/0.35.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
  81. package/releases/0.35.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
  82. package/releases/0.35.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
  83. package/releases/0.35.0/surfaces/frontend/tree/.claude/router.md +14 -0
  84. package/releases/0.35.0/surfaces/frontend/tree/CLAUDE.md +5 -0
  85. package/releases/0.35.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  86. package/releases/0.35.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  87. package/releases/0.35.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  88. package/releases/0.35.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  89. package/releases/0.35.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  90. package/releases/0.35.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  91. package/releases/0.35.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  92. package/releases/0.35.0/surfaces/integrations/tree/.claude/graph.md +10 -0
  93. package/releases/0.35.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
  94. package/releases/0.35.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  95. package/releases/0.35.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  96. package/releases/0.35.0/surfaces/integrations/tree/.claude/router.md +6 -0
  97. package/releases/0.35.0/surfaces/integrations/tree/CLAUDE.md +17 -0
  98. package/releases/0.35.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
  99. package/releases/0.35.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
  100. package/releases/0.35.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  101. package/releases/0.35.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  102. package/releases/0.35.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  103. package/releases/0.35.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
  104. package/releases/0.35.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  105. package/releases/0.35.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  106. package/releases/0.35.0/surfaces/money/tree/.claude/graph.md +2 -0
  107. package/releases/0.35.0/surfaces/money/tree/.claude/patterns.md +49 -0
  108. package/releases/0.35.0/surfaces/money/tree/.claude/pipeline.md +2 -0
  109. package/releases/0.35.0/surfaces/money/tree/.claude/retrieval.md +2 -0
  110. package/releases/0.35.0/surfaces/money/tree/CLAUDE.md +11 -0
  111. package/releases/0.35.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  112. package/releases/0.35.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  113. package/releases/0.35.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  114. package/releases/0.35.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  115. package/releases/0.35.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  116. package/releases/0.35.0/surfaces/pii/tree/CLAUDE.md +5 -0
  117. package/releases/0.35.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
  118. package/releases/0.35.0/surfaces/prisma/tree/.claude/agents/dba.md +39 -0
  119. package/releases/0.35.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
  120. package/releases/0.35.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
  121. package/releases/0.35.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
  122. package/releases/0.35.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
  123. package/releases/0.35.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
  124. package/releases/0.35.0/surfaces/prisma/tree/CLAUDE.md +5 -0
  125. package/releases/0.36.0/core/GAPS.md +125 -0
  126. package/releases/0.36.0/core/answered.json +29 -0
  127. package/releases/0.36.0/core/defaults/tree/.claude/agents/architect.md +1 -0
  128. package/releases/0.36.0/core/defaults/tree/.claude/agents/dba.md +3 -0
  129. package/releases/0.36.0/core/defaults/tree/.claude/agents/devops.md +1 -0
  130. package/releases/0.36.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
  131. package/releases/0.36.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  132. package/releases/0.36.0/core/defaults/tree/.claude/agents/qa.md +2 -0
  133. package/releases/0.36.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  134. package/releases/0.36.0/core/defaults/tree/.claude/agents/secops.md +3 -0
  135. package/releases/0.36.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  136. package/releases/0.36.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  137. package/releases/0.36.0/core/tree/.claude/agents/architect.md +127 -0
  138. package/releases/0.36.0/core/tree/.claude/agents/dba.md +110 -0
  139. package/releases/0.36.0/core/tree/.claude/agents/devops.md +114 -0
  140. package/releases/0.36.0/core/tree/.claude/agents/implementer.md +156 -0
  141. package/releases/0.36.0/core/tree/.claude/agents/integration-tester.md +168 -0
  142. package/releases/0.36.0/core/tree/.claude/agents/planner.md +132 -0
  143. package/releases/0.36.0/core/tree/.claude/agents/qa.md +153 -0
  144. package/releases/0.36.0/core/tree/.claude/agents/reviewer.md +183 -0
  145. package/releases/0.36.0/core/tree/.claude/agents/secops.md +121 -0
  146. package/releases/0.36.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
  147. package/releases/0.36.0/core/tree/.claude/agents/solidity-dev.md +123 -0
  148. package/releases/0.36.0/core/tree/.claude/agents-overview.md +64 -0
  149. package/releases/0.36.0/core/tree/.claude/frontend.md +10 -0
  150. package/releases/0.36.0/core/tree/.claude/graph.md +75 -0
  151. package/releases/0.36.0/core/tree/.claude/patterns.md +213 -0
  152. package/releases/0.36.0/core/tree/.claude/pills/README.md +146 -0
  153. package/releases/0.36.0/core/tree/.claude/pipeline.md +101 -0
  154. package/releases/0.36.0/core/tree/.claude/retrieval.md +103 -0
  155. package/releases/0.36.0/core/tree/.claude/router.md +198 -0
  156. package/releases/0.36.0/core/tree/.claude/templates/integration.md +45 -0
  157. package/releases/0.36.0/core/tree/CLAUDE.md +161 -0
  158. package/releases/0.36.0/core/tree/scripts/cost-watch.mjs +18 -0
  159. package/releases/0.36.0/core/tree/scripts/edit-guard.mjs +16 -0
  160. package/releases/0.36.0/core/tree/scripts/harness-check.mjs +71 -0
  161. package/releases/0.36.0/core/tree/scripts/loop-gate.mjs +19 -0
  162. package/releases/0.36.0/core/vocabulary.json +13 -0
  163. package/releases/0.36.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  164. package/releases/0.36.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  165. package/releases/0.36.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  166. package/releases/0.36.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  167. package/releases/0.36.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  168. package/releases/0.36.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  169. package/releases/0.36.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
  170. package/releases/0.36.0/surfaces/blockchain/tree/.claude/router.md +6 -0
  171. package/releases/0.36.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
  172. package/releases/0.36.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
  173. package/releases/0.36.0/surfaces/db/tree/.claude/agents/dba.md +50 -0
  174. package/releases/0.36.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
  175. package/releases/0.36.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
  176. package/releases/0.36.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
  177. package/releases/0.36.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
  178. package/releases/0.36.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
  179. package/releases/0.36.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
  180. package/releases/0.36.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
  181. package/releases/0.36.0/surfaces/db/tree/.claude/graph.md +8 -0
  182. package/releases/0.36.0/surfaces/db/tree/.claude/patterns.md +19 -0
  183. package/releases/0.36.0/surfaces/db/tree/.claude/pipeline.md +13 -0
  184. package/releases/0.36.0/surfaces/db/tree/.claude/retrieval.md +5 -0
  185. package/releases/0.36.0/surfaces/db/tree/.claude/router.md +4 -0
  186. package/releases/0.36.0/surfaces/db/tree/CLAUDE.md +17 -0
  187. package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  188. package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  189. package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/devops.md +32 -0
  190. package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  191. package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  192. package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  193. package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  194. package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  195. package/releases/0.36.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  196. package/releases/0.36.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  197. package/releases/0.36.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
  198. package/releases/0.36.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  199. package/releases/0.36.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  200. package/releases/0.36.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  201. package/releases/0.36.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  202. package/releases/0.36.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
  203. package/releases/0.36.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
  204. package/releases/0.36.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
  205. package/releases/0.36.0/surfaces/frontend/tree/.claude/router.md +14 -0
  206. package/releases/0.36.0/surfaces/frontend/tree/CLAUDE.md +5 -0
  207. package/releases/0.36.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  208. package/releases/0.36.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  209. package/releases/0.36.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  210. package/releases/0.36.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  211. package/releases/0.36.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  212. package/releases/0.36.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  213. package/releases/0.36.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  214. package/releases/0.36.0/surfaces/integrations/tree/.claude/graph.md +10 -0
  215. package/releases/0.36.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
  216. package/releases/0.36.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  217. package/releases/0.36.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  218. package/releases/0.36.0/surfaces/integrations/tree/.claude/router.md +6 -0
  219. package/releases/0.36.0/surfaces/integrations/tree/CLAUDE.md +17 -0
  220. package/releases/0.36.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
  221. package/releases/0.36.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
  222. package/releases/0.36.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  223. package/releases/0.36.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  224. package/releases/0.36.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  225. package/releases/0.36.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
  226. package/releases/0.36.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  227. package/releases/0.36.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  228. package/releases/0.36.0/surfaces/money/tree/.claude/graph.md +2 -0
  229. package/releases/0.36.0/surfaces/money/tree/.claude/patterns.md +49 -0
  230. package/releases/0.36.0/surfaces/money/tree/.claude/pipeline.md +2 -0
  231. package/releases/0.36.0/surfaces/money/tree/.claude/retrieval.md +2 -0
  232. package/releases/0.36.0/surfaces/money/tree/CLAUDE.md +11 -0
  233. package/releases/0.36.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  234. package/releases/0.36.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  235. package/releases/0.36.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  236. package/releases/0.36.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  237. package/releases/0.36.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  238. package/releases/0.36.0/surfaces/pii/tree/CLAUDE.md +5 -0
  239. package/releases/0.36.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
  240. package/releases/0.36.0/surfaces/prisma/tree/.claude/agents/dba.md +39 -0
  241. package/releases/0.36.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
  242. package/releases/0.36.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
  243. package/releases/0.36.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
  244. package/releases/0.36.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
  245. package/releases/0.36.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
  246. package/releases/0.36.0/surfaces/prisma/tree/CLAUDE.md +5 -0
  247. package/src/agentrun.mjs +89 -9
  248. package/src/commands/check.mjs +12 -8
  249. package/src/commands/compose.mjs +85 -9
  250. package/src/commands/export.mjs +8 -16
  251. package/src/commands/gate.mjs +19 -13
  252. package/src/commands/init.mjs +13 -4
  253. package/src/commands/learn.mjs +17 -18
  254. package/src/commands/pipeline.mjs +4 -4
  255. package/src/commands/release.mjs +21 -3
  256. package/src/commands/runs.mjs +14 -8
  257. package/src/commands/snapshot.mjs +63 -64
  258. package/src/commands/stats.mjs +98 -30
  259. package/src/commands/status.mjs +2 -2
  260. package/src/commands/upgrade.mjs +131 -25
  261. package/src/guard.mjs +7 -37
  262. package/src/paths.mjs +50 -1
  263. package/src/prices.mjs +7 -7
  264. package/src/store.mjs +158 -0
  265. package/src/transcripts.mjs +15 -4
  266. package/src/vocabulary.mjs +12 -1
  267. package/src/wiring.mjs +106 -41
@@ -9,9 +9,10 @@
9
9
  * Releases are never rewritten. Fixing a release means cutting the next one.
10
10
  */
11
11
 
12
- import { cp, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
12
+ import { cp, mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
13
13
  import { existsSync } from 'node:fs';
14
14
  import { join } from 'node:path';
15
+ import { byVersion } from './compose.mjs';
15
16
 
16
17
  /** A release name: dotted numbers, so the directory sorts and reads like a version. */
17
18
  const VERSION = /^\d+\.\d+\.\d+$/;
@@ -80,11 +81,28 @@ export async function release(argv, ctx) {
80
81
  console.error(` ${version} already exists — a release is immutable; cut the next one.\n`);
81
82
  return 1;
82
83
  }
84
+ // Above the newest, or `init` pins something older than what the package says it is, and the package's
85
+ // version moves backwards with the cut.
86
+ const newest = (await readdir(join(ctx.root, 'releases'), { withFileTypes: true }).catch(() => []))
87
+ .filter((e) => e.isDirectory() && VERSION.test(e.name))
88
+ .map((e) => e.name)
89
+ .sort(byVersion)
90
+ .at(-1);
91
+ if (newest && byVersion(version, newest) < 0) {
92
+ console.error(` ${newest} is the newest release — a release is cut above it, not below.\n`);
93
+ return 1;
94
+ }
83
95
 
84
- await mkdir(dest, { recursive: true });
96
+ // Frozen beside the releases and moved in whole: a cut stopped half-way used to leave a partial release,
97
+ // immutable by name, which `init` then pinned as the newest.
98
+ const partial = join(ctx.root, `.release-${version}.partial`);
99
+ await rm(partial, { recursive: true, force: true });
100
+ await mkdir(partial, { recursive: true });
85
101
  for (const layer of ['core', 'surfaces']) {
86
- await cp(join(ctx.root, layer), join(dest, layer), { recursive: true });
102
+ await cp(join(ctx.root, layer), join(partial, layer), { recursive: true });
87
103
  }
104
+ await mkdir(join(ctx.root, 'releases'), { recursive: true });
105
+ await rename(partial, dest);
88
106
 
89
107
  const surfaces = (await readdir(join(dest, 'surfaces'), { withFileTypes: true }))
90
108
  .filter((e) => e.isDirectory())
@@ -16,8 +16,8 @@ import { HARNESS } from '../paths.mjs';
16
16
  import { costOf } from '../prices.mjs';
17
17
  import { isLoopBack, runOf } from '../transcripts.mjs';
18
18
  import { amber, bold, dim, pink } from '../look.mjs';
19
- import { storedRecords } from './snapshot.mjs';
20
- import { cyclesOf, projectName } from './stats.mjs';
19
+ import { localDay, localMinute, projectRecords, zone } from '../store.mjs';
20
+ import { WRITERS, cyclesOf, projectName } from './stats.mjs';
21
21
 
22
22
  /** What ended a cycle, in words. */
23
23
  const ENDS = {
@@ -55,7 +55,8 @@ export function summarize(cycle) {
55
55
  if (isLoopBack(r.verdict)) s.back += 1;
56
56
  stages.set(r.role, s);
57
57
  }
58
- const files = rounds.filter((r) => typeof r.files_touched === 'number').map((r) => r.files_touched);
58
+ // The code a cycle wrote, as `stats` counts it: an architect's spec or a tester's report is not a write of code.
59
+ const files = rounds.filter((r) => WRITERS.has(r.role) && typeof r.files_touched === 'number').map((r) => r.files_touched);
59
60
  const peaks = rounds.filter((r) => typeof r.context_peak === 'number').map((r) => r.context_peak);
60
61
  return {
61
62
  start,
@@ -92,9 +93,14 @@ export async function runs(argv) {
92
93
  }
93
94
  const since = arg('--since');
94
95
  const last = Math.max(1, Number(arg('--last') ?? 10) || 10);
95
- const records = (storedRecords(dir) ?? []).filter((r) => r.status !== 'denied' && (!since || String(r.ts).slice(0, 10) >= since));
96
+ const stored = (projectRecords(dir) ?? []).filter((r) => r.status !== 'denied');
97
+ const records = stored.filter((r) => !since || localDay(r.ts) >= since);
96
98
  if (records.length === 0) {
97
- console.error(' no measured history for this project yet — `nina snapshot` captures it\n');
99
+ console.error(
100
+ stored.length === 0
101
+ ? ' no measured history for this project yet — `nina snapshot` captures it\n'
102
+ : ` nothing measured since ${since} (${zone()}) — the newest round is from ${localDay(stored.map((r) => String(r.ts)).sort().at(-1))}\n`,
103
+ );
98
104
  return 1;
99
105
  }
100
106
  const cycles = cyclesOf(records)
@@ -103,10 +109,10 @@ export async function runs(argv) {
103
109
  const shown = cycles.slice(0, last);
104
110
  console.log(
105
111
  ` ${bold(pink(projectName(records[0].project)))} · ${cycles.length} cycle(s)` +
106
- dim(` newest first${cycles.length > shown.length ? `, the last ${shown.length} — --last N for more` : ''}; a cycle runs up to the verdict that closes it`),
112
+ dim(` newest first${cycles.length > shown.length ? `, the last ${shown.length} — --last N for more` : ''}; a cycle runs up to the verdict that closes it; times in ${zone()}`),
107
113
  );
108
114
  for (const c of shown) {
109
- const when = `${c.start.slice(0, 10)} ${c.start.slice(11, 16)}`;
115
+ const when = localMinute(c.start);
110
116
  console.log(`\n ${pink('▌')} ${bold(when)} · ${span(c.minutes)} · ${bold(money(c.cost))}${c.unpriced ? dim(` + ${c.unpriced} unpriced`) : ''} · ${dim(c.how)}`);
111
117
  console.log(` ${c.name.length > 96 ? `${c.name.slice(0, 95)}…` : c.name}`);
112
118
  const chain = c.stages.map((s) => {
@@ -122,7 +128,7 @@ export async function runs(argv) {
122
128
  const costliest = shown.reduce((a, c) => (c.cost > a.cost ? c : a), shown[0]);
123
129
  console.log(
124
130
  `\n ${shown.length} cycle(s): ${bold(money(total))} in all` +
125
- (shown.length > 1 ? `; the costliest, ${money(costliest.cost)}, ${costliest.start.slice(0, 10)} — ${costliest.name.slice(0, 60)}` : '') +
131
+ (shown.length > 1 ? `; the costliest, ${money(costliest.cost)}, ${localDay(costliest.start)} — ${costliest.name.slice(0, 60)}` : '') +
126
132
  '\n',
127
133
  );
128
134
  return 0;
@@ -9,9 +9,10 @@
9
9
  */
10
10
 
11
11
  import { copyFile, mkdir, open, readFile, rename, rm, writeFile } from 'node:fs/promises';
12
- import { slugFor, snapshotsDir } from '../paths.mjs';
13
- import { createReadStream, existsSync, readFileSync, realpathSync } from 'node:fs';
14
- import { createInterface } from 'node:readline';
12
+ import { HARNESS, snapshotsDir } from '../paths.mjs';
13
+ import { decodeProjectDir } from './stats.mjs';
14
+ import { readStoreFile } from '../store.mjs';
15
+ import { existsSync, readFileSync } from 'node:fs';
15
16
  import { join, resolve } from 'node:path';
16
17
  import { listProjects, scanProject } from '../transcripts.mjs';
17
18
 
@@ -21,7 +22,8 @@ const STATE_FILE = '.state.json';
21
22
  /**
22
23
  * Reads a JSON file, or an empty object when it is missing or unreadable. A state file cut short
23
24
  * by an interrupted write used to throw on every later snapshot, and observation stopped for every
24
- * project on the machine with nothing saying so. An unreadable cursor costs one re-read.
25
+ * project on the machine with nothing saying so. An unreadable cursor costs one re-read, which keeps what the
26
+ * store already holds (`snapshotProject`).
25
27
  *
26
28
  * @param {string} path - The file.
27
29
  * @returns {Promise<object>}
@@ -42,35 +44,10 @@ async function readJson(path) {
42
44
  */
43
45
  async function writeAtomicText(path, text) {
44
46
  const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
45
- await writeFile(tmp, text);
47
+ await writeFile(tmp, text, { mode: 0o600 });
46
48
  await rename(tmp, path);
47
49
  }
48
50
 
49
- /**
50
- * Reads the records already captured for a project.
51
- *
52
- * @param {string} file - Path to the snapshot JSONL.
53
- * @returns {Promise<object[]>} The records, or an empty list.
54
- */
55
- async function readRecords(file) {
56
- const records = [];
57
- try {
58
- await readFile(file, { flag: 'r' });
59
- } catch {
60
- return records;
61
- }
62
- const rl = createInterface({ input: createReadStream(file), crlfDelay: Infinity });
63
- for await (const line of rl) {
64
- if (!line.trim()) continue;
65
- try {
66
- records.push(JSON.parse(line));
67
- } catch {
68
- /* a torn line loses one record, never the file */
69
- }
70
- }
71
- return records;
72
- }
73
-
74
51
  /**
75
52
  * Writes the snapshot through a temp file, so an interrupted run cannot leave a
76
53
  * truncated one behind.
@@ -82,41 +59,11 @@ async function writeAtomic(file, records) {
82
59
  // Unique per writer: two snapshots of one project at once used the same temporary name, and
83
60
  // could interleave into it before either renamed it into place.
84
61
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
85
- await writeFile(tmp, `${records.map((r) => JSON.stringify(r)).join('\n')}\n`);
62
+ // Its owner's alone: it holds each dispatch's description, which the orchestrator wrote.
63
+ await writeFile(tmp, `${records.map((r) => JSON.stringify(r)).join('\n')}\n`, { mode: 0o600 });
86
64
  await rename(tmp, file);
87
65
  }
88
66
 
89
- /**
90
- * A project's records in the measurement store, under the name Claude Code gives its directory — as
91
- * given, or with its links resolved, since either may be the one the sessions ran in.
92
- *
93
- * @param {string} dir - The project.
94
- * @returns {object[]|null} Null when the store has nothing for it.
95
- */
96
- export function storedRecords(dir) {
97
- const names = new Set([slugFor(dir)]);
98
- try {
99
- names.add(slugFor(realpathSync(dir)));
100
- } catch {
101
- // A directory that cannot be resolved is looked up as given.
102
- }
103
- for (const name of names) {
104
- const file = join(snapshotsDir(), `${name}.jsonl`);
105
- if (!existsSync(file)) continue;
106
- return readFileSync(file, 'utf8')
107
- .split('\n')
108
- .filter(Boolean)
109
- .flatMap((l) => {
110
- try {
111
- return [JSON.parse(l)];
112
- } catch {
113
- return [];
114
- }
115
- });
116
- }
117
- return null;
118
- }
119
-
120
67
  /**
121
68
  * Takes a project's lock, or says it is taken. The records and the cursors are written one after the other,
122
69
  * and two snapshots of one project at once — a project's own detector and a global hook, both on every
@@ -204,6 +151,42 @@ export async function snapshot(argv, ctx) {
204
151
  return 0;
205
152
  }
206
153
 
154
+ /** Whether a round's verdict came from the report it handed back: the one reading nothing later corrects. */
155
+ const settledByHandback = (r) => r?.verdict_source === 'handback';
156
+
157
+ /**
158
+ * A round as a re-read after lost cursors leaves it. What the store held stands, field by field, and the
159
+ * re-read fills only what it lacked — unless the re-read settled the round from the report its stage handed
160
+ * back and the store had not: a verdict read from a notification is the one a handback corrects, and kept
161
+ * over it, a round the store had as REJECTED stayed so after its stage had said APPROVED, and stayed so for
162
+ * good once that stage's transcript was pruned.
163
+ *
164
+ * @param {object} held - The round as the store held it.
165
+ * @param {object} reread - The round as the re-read found it.
166
+ * @returns {object}
167
+ */
168
+ export function rereadOver(held, reread) {
169
+ const defined = (record) => Object.fromEntries(Object.entries(record).filter(([, v]) => v !== null && v !== undefined));
170
+ return settledByHandback(reread) && !settledByHandback(held) ? { ...held, ...defined(reread) } : { ...reread, ...defined(held) };
171
+ }
172
+
173
+ /**
174
+ * The release a project pins now, or null when its directory or profile cannot be found.
175
+ *
176
+ * @param {string} slug - Its store name.
177
+ * @returns {string|null}
178
+ */
179
+ function pinOf(slug) {
180
+ const dir = decodeProjectDir(slug);
181
+ if (!dir) return null;
182
+ try {
183
+ const core = JSON.parse(readFileSync(join(dir, HARNESS, 'profile.json'), 'utf8')).core;
184
+ return typeof core === 'string' ? core : null;
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
189
+
207
190
  /**
208
191
  * Snapshots one project, under its lock.
209
192
  *
@@ -214,12 +197,28 @@ async function snapshotProject(project, { outDir, rebuild, quiet, legacy }) {
214
197
  // The store is the only copy of history older than the transcripts, and a rebuild re-reads only what is
215
198
  // still on disk: the record it replaces is kept beside it first.
216
199
  if (rebuild && existsSync(file)) await copyFile(file, `${file}.before-rebuild-${new Date().toISOString().replace(/[:.]/g, '-')}`);
217
- const prior = rebuild ? [] : await readRecords(file);
200
+ const stored = readStoreFile(file, { raw: true });
201
+ const prior = rebuild ? [] : stored;
218
202
  const own = rebuild ? {} : await readJson(join(outDir, `${project.slug}.state.json`));
219
203
  const cursors = rebuild ? {} : (own.cursors ?? legacy[project.slug]?.cursors ?? {});
220
204
 
221
205
  const scanned = await scanProject(project.dir, { cursors, records: prior });
222
- const records = scanned.records.map((r) => ({ project: project.slug, ...r }));
206
+ // With its cursors gone — a state file deleted, or cut short — the transcripts are read again from the start
207
+ // over records the store already holds, and read that way a notification is taken a second time: one
208
+ // project had 34 verdicts, 639 return times and 79 durations rewritten. What the store held stands, field
209
+ // by field, and the re-read adds only what it lacked; the cursors it leaves hold from then on.
210
+ const reread = !rebuild && prior.length > 0 && Object.keys(cursors).length === 0;
211
+ const held = new Map(prior.map((r) => [r.dispatch_id, r]));
212
+ const kept = (r) => (reread && held.has(r.dispatch_id) ? rereadOver(held.get(r.dispatch_id), r) : r);
213
+ // The release the project pinned when a round was first captured, so a rule is judged only on the rounds
214
+ // that ran under it. A round already on record keeps what it had — a rebuild included, which would
215
+ // otherwise stamp today's pin on weeks of history — and only a new one takes today's.
216
+ const pinned = new Map(stored.map((r) => [r.dispatch_id, r.core ?? null]));
217
+ const pin = pinOf(project.slug);
218
+ const records = scanned.records.map(kept).map((r) => {
219
+ const core = pinned.has(r.dispatch_id) ? pinned.get(r.dispatch_id) : pin;
220
+ return { ...r, project: project.slug, ...(core ? { core } : {}) };
221
+ });
223
222
  if (records.length === 0) return 0;
224
223
 
225
224
  const fresh = records.length - prior.length;
@@ -14,15 +14,15 @@
14
14
  import { amber, bar, bold, dim, heading, note, pink, stacked } from '../look.mjs';
15
15
  import { readFile, readdir } from 'node:fs/promises';
16
16
  import { spawnSync } from 'node:child_process';
17
- import { createReadStream, existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
18
- import { createInterface } from 'node:readline';
17
+ import { closeSync, existsSync, openSync, readFileSync, readSync, readdirSync, realpathSync, statSync } from 'node:fs';
19
18
  import { homedir } from 'node:os';
20
19
  import { basename, join } from 'node:path';
21
- import { ROLE_TOKENS, isLoopBack, runOf } from '../transcripts.mjs';
20
+ import { ROLE_TOKENS, isLoopBack, runOf, transcriptsOf } from '../transcripts.mjs';
21
+ import { localDay, namesFor, readStoreFile } from '../store.mjs';
22
22
  import { frontmatter, pillFiles } from './pills.mjs';
23
- import { HARNESS, snapshotsDir } from '../paths.mjs';
23
+ import { HARNESS, slugFor, snapshotsDir } from '../paths.mjs';
24
24
  import { defaultVocabulary } from '../vocabulary.mjs';
25
- import { layerRootFor } from './compose.mjs';
25
+ import { byVersion, layerRootFor } from './compose.mjs';
26
26
  import { PRICES_AS_OF, costOf } from '../prices.mjs';
27
27
 
28
28
  /**
@@ -56,41 +56,77 @@ const PIPELINE_ROLES = new Set(Object.keys(ROLE_TOKENS));
56
56
  * Loads snapshot records, optionally filtered.
57
57
  *
58
58
  * @param {string} dir - Snapshot directory.
59
- * @param {{since: string|null, project: string|null}} opts - Filters.
59
+ * @param {{since: string|null, project: string|null}} opts - Filters; `project` as every report reads it.
60
60
  * @returns {Promise<object[]>} The matching records.
61
61
  */
62
62
  async function load(dir, opts) {
63
- const files = (await readdir(dir).catch(() => [])).filter((f) => f.endsWith('.jsonl'));
64
- const out = [];
65
- for (const file of files) {
66
- if (opts.project && !file.includes(opts.project)) continue;
67
- const rl = createInterface({ input: createReadStream(join(dir, file)), crlfDelay: Infinity });
68
- for await (const line of rl) {
69
- if (!line.trim()) continue;
63
+ const names = opts.project
64
+ ? namesFor(opts.project, dir)
65
+ : (await readdir(dir).catch(() => [])).filter((f) => f.endsWith('.jsonl')).map((f) => f.slice(0, -'.jsonl'.length));
66
+ return names
67
+ .flatMap((name) => readStoreFile(join(dir, `${name}.jsonl`)))
68
+ .filter((r) => !opts.since || localDay(r.ts) >= opts.since);
69
+ }
70
+
71
+
72
+ /** Directories already found for an encoded name: `stats` asks for the same project many times over. */
73
+ const decoded = new Map();
74
+
75
+ /**
76
+ * The directory a project's sessions ran in, as its own transcripts record it: every line carries the
77
+ * session's `cwd`. Taken only when it encodes back to the same name and is still there.
78
+ *
79
+ * @param {string} encoded - A snapshot's project name, which is also its transcript directory's.
80
+ * @returns {string|null}
81
+ */
82
+ function recordedDir(encoded) {
83
+ const dir = transcriptsOf(encoded);
84
+ let files = [];
85
+ try {
86
+ files = readdirSync(dir).filter((f) => f.endsWith('.jsonl'));
87
+ } catch {
88
+ return null;
89
+ }
90
+ for (const file of files.slice(0, 5)) {
91
+ let head = '';
92
+ try {
93
+ const fd = openSync(join(dir, file), 'r');
94
+ try {
95
+ const buffer = Buffer.alloc(64 * 1024);
96
+ head = buffer.toString('utf8', 0, readSync(fd, buffer, 0, buffer.length, 0));
97
+ } finally {
98
+ closeSync(fd);
99
+ }
100
+ } catch {
101
+ continue;
102
+ }
103
+ for (const line of head.split('\n')) {
104
+ let cwd;
70
105
  try {
71
- const r = JSON.parse(line);
72
- if (opts.since && String(r.ts).slice(0, 10) < opts.since) continue;
73
- out.push(r);
106
+ cwd = JSON.parse(line)?.cwd;
74
107
  } catch {
75
- /* skip */
108
+ continue;
76
109
  }
110
+ if (typeof cwd === 'string' && slugFor(cwd) === encoded && existsSync(cwd)) return cwd;
77
111
  }
78
112
  }
79
- return out;
113
+ return null;
80
114
  }
81
115
 
82
-
83
116
  /**
84
117
  * Turns a snapshot's encoded project name back into a directory.
85
118
  *
86
- * The encoding replaces every separator with a dash, so `Code-IA-harness` could be
87
- * `Code/IA/harness` or `Code/IA-harness` and the string alone cannot say which. The
88
- * filesystem can, so the split is resolved against it rather than guessed.
119
+ * The encoding makes every character that is not a letter or a digit a dash, so the name alone cannot say
120
+ * what it was. The project's transcripts can: each line records the directory its session ran in. Failing
121
+ * that, the split is resolved against the file system — which can only rebuild `/` from a dash, so a
122
+ * project whose path held a `.`, a space or an `_` was never found, and its pills, models and requests went
123
+ * missing from every report without a word.
89
124
  *
90
125
  * @param {string} encoded - The `project` field of a snapshot record.
91
126
  * @returns {string | null} The directory, or `null` when no split of the name exists.
92
127
  */
93
128
  export function decodeProjectDir(encoded) {
129
+ if (decoded.has(encoded)) return decoded.get(encoded);
94
130
  const parts = encoded.replace(/^-/, '').split('-');
95
131
  const walk = (base, i) => {
96
132
  if (i === parts.length) return base;
@@ -102,7 +138,10 @@ export function decodeProjectDir(encoded) {
102
138
  }
103
139
  return null;
104
140
  };
105
- return walk('/', 0);
141
+ const found = recordedDir(encoded) ?? walk('/', 0);
142
+ // Only what was found: a directory made after a miss is found the next time it is asked for.
143
+ if (found) decoded.set(encoded, found);
144
+ return found;
106
145
  }
107
146
 
108
147
  /**
@@ -335,7 +374,7 @@ function modelReport(records) {
335
374
  .map((rs) => rs.map((r) => (r.tokens ? costOf(r.tokens, r.usage_model) : null)).filter((c) => c !== null))
336
375
  .filter((cs) => cs.length > 0)
337
376
  .map((cs) => cs.reduce((a, b) => a + b, 0));
338
- const dates = rounds.map((r) => String(r.ts).slice(0, 10)).sort();
377
+ const dates = rounds.map((r) => localDay(r.ts)).sort();
339
378
  lines.push(
340
379
  ` ${i === 0 ? `${pink(role)}${' '.repeat(Math.max(20 - role.length, 1))}` : ''.padEnd(20)}${model.padEnd(26)}${String(runs.size).padStart(5)} run(s) ${dim(`${dates[0]} → ${dates.at(-1)}`)}` +
341
380
  (clear.length ? ` loop-back ${pct(loops, clear.length)} of ${clear.length}` : ' no readable verdict') +
@@ -377,8 +416,25 @@ function modelReport(records) {
377
416
  /** The verdicts that close a pipeline cycle: the change passed its tests, reached preview, or cleared the audit. */
378
417
  const CYCLE_ENDS = { qa: 'PASS', devops: 'DEPLOYED', secops: 'SECURE' };
379
418
 
419
+ /**
420
+ * The release each rule this report judges first shipped in. A round is judged on a rule only when the pin it
421
+ * was captured under (`core`) carries it: over every round, "0 of 16 loop-backs named their issues" read
422
+ * reports written before the rule existed as breaking it. Releases are never rewritten, so neither is this.
423
+ */
424
+ const RULE_SINCE = { issues: '0.22.0', stepFiles: '0.28.8' };
425
+
426
+ /**
427
+ * Whether a round ran under a release that carries a rule. One captured before rounds recorded their pin
428
+ * cannot say, and is left out rather than judged.
429
+ *
430
+ * @param {object} record - A round.
431
+ * @param {keyof RULE_SINCE} rule - The rule.
432
+ * @returns {boolean}
433
+ */
434
+ export const ranUnder = (record, rule) => typeof record.core === 'string' && (record.core === 'dev' || byVersion(record.core, RULE_SINCE[rule]) >= 0);
435
+
380
436
  /** The stages that write code, whose runs' file counts size a cycle. */
381
- const WRITERS = new Set(['implementer', 'solidity-dev']);
437
+ export const WRITERS = new Set(['implementer', 'solidity-dev']);
382
438
 
383
439
  /** The stages whose presence makes a cycle a designed one. */
384
440
  const DESIGNERS = new Set(['planner', 'architect']);
@@ -489,7 +545,7 @@ function proportionReport(records, stepLimit = () => null) {
489
545
  // 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
490
546
  // whose pinned release states the limit.
491
547
  const over = records.filter((r) => {
492
- if (r.role !== 'implementer' || typeof r.files_touched !== 'number') return false;
548
+ if (r.role !== 'implementer' || typeof r.files_touched !== 'number' || !ranUnder(r, 'stepFiles')) return false;
493
549
  const limit = stepLimit(r.project);
494
550
  return limit !== null && r.files_touched > limit;
495
551
  });
@@ -622,7 +678,14 @@ export async function stats(argv, ctx) {
622
678
  // A dispatch a hook denied never ran, so it is neither a run nor a missing verdict.
623
679
  const held = records.filter((r) => r.status === 'denied').length;
624
680
  const now = Date.now();
681
+ // Agents outside the pipeline — Claude Code's own, a project's own — have no verdict to give. Read as
682
+ // stages they were 100% unreadable and were told to add a verdict token their spec never asked for.
683
+ const outside = new Map();
625
684
  for (const r of records.filter((r) => r.status !== 'denied')) {
685
+ if (!PIPELINE_ROLES.has(r.role)) {
686
+ outside.set(r.role, (outside.get(r.role) ?? 0) + 1);
687
+ continue;
688
+ }
626
689
  if (!byRole.has(r.role)) {
627
690
  byRole.set(r.role, { n: 0, runs: new Set(), done: 0, lost: 0, clear: 0, loop: 0, unclear: 0, declared: 0, durations: [] });
628
691
  }
@@ -640,7 +703,7 @@ export async function stats(argv, ctx) {
640
703
  if (typeof r.duration_s === 'number') s.durations.push(r.duration_s);
641
704
  }
642
705
 
643
- const span = [records[0]?.ts, records.at(-1)?.ts].map((t) => String(t).slice(0, 10));
706
+ const span = [records[0]?.ts, records.at(-1)?.ts].map(localDay);
644
707
  const projectCount = new Set(records.map((r) => r.project)).size;
645
708
  const run = byRun(records.filter((r) => r.status !== 'denied')).size;
646
709
  console.log(
@@ -688,9 +751,14 @@ export async function stats(argv, ctx) {
688
751
  const lost = rows.reduce((a, [, s]) => a + s.lost, 0);
689
752
  const done = rows.reduce((a, [, s]) => a + s.done, 0);
690
753
  console.log('');
754
+ if (outside.size > 0) {
755
+ const named = [...outside.entries()].sort((a, b) => b[1] - a[1]).map(([role, n]) => `${role} ×${n}`).join(', ');
756
+ 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'));
757
+ }
691
758
  if (unreadable === 0 && lost === 0) console.log(note("every finished round's verdict could be read.", 'ok'));
759
+ // The same share as the table's column: rounds that finished, or should have, with no verdict read.
692
760
  if (unreadable > 0) {
693
- console.log(note(`${pct(unreadable, done + lost)} of finished rounds report no machine-readable verdict. Those stages need a verdict token on the report's first line.`, 'warn'));
761
+ console.log(note(`${pct(unreadable + lost, done + lost)} of finished rounds have no verdict that can be read. Those stages need a verdict token on the report's first line.`, 'warn'));
694
762
  }
695
763
  if (lost > 0) {
696
764
  console.log(
@@ -704,7 +772,7 @@ export async function stats(argv, ctx) {
704
772
 
705
773
  // Whether the stages that send work back say what they send back. Counted only over reports read
706
774
  // since the record learned the field, and only over declared loop-backs, which are all it asks of.
707
- const named = records.filter((r) => typeof r.issues === 'number');
775
+ const named = records.filter((r) => typeof r.issues === 'number' && ranUnder(r, 'issues'));
708
776
  if (named.length > 0) {
709
777
  const withIds = named.filter((r) => r.issues > 0).length;
710
778
  console.log(
@@ -788,7 +856,7 @@ export async function stats(argv, ctx) {
788
856
  : note(`${pills.retired} of ${pills.total} pill(s) retired into a rule.`, 'ok'),
789
857
  );
790
858
  if (newest) {
791
- const since = records.filter((r) => String(r.ts).slice(0, 10) > newest).length;
859
+ const since = records.filter((r) => localDay(r.ts) > newest).length;
792
860
  if (since > 0) console.log(note(`${since} round(s) since the newest pill (${newest}).`));
793
861
  }
794
862
  }
@@ -22,7 +22,7 @@ import { amber, bold, dim, green, pink } from '../look.mjs';
22
22
  import { byVersion, layerRootFor } from './compose.mjs';
23
23
  import { filedRequests } from './learn.mjs';
24
24
  import { frontmatter, pillFiles } from './pills.mjs';
25
- import { storedRecords } from './snapshot.mjs';
25
+ import { projectRecords } from '../store.mjs';
26
26
 
27
27
  /** How long the registry gets to say which version is newest; a status is not worth waiting on. */
28
28
  const REGISTRY_MS = 3000;
@@ -123,7 +123,7 @@ export async function status(argv, ctx) {
123
123
  const check = verdictOf(bin, ['check', '--project', target]);
124
124
  row('check', check.ok ? green(check.line) : warn(`${check.line} — \`nina check\` lists them`));
125
125
 
126
- const records = storedRecords(target) ?? [];
126
+ const records = projectRecords(target) ?? [];
127
127
  if (records.length === 0) {
128
128
  row('measured', dim('nothing yet — `nina snapshot` captures it'));
129
129
  } else {