@xhulz/nina 0.52.0 → 0.53.1

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 (275) hide show
  1. package/README.md +4 -0
  2. package/bin/nina.mjs +29 -5
  3. package/package.json +1 -1
  4. package/releases/0.53.0/core/GAPS.md +125 -0
  5. package/releases/0.53.0/core/answered.json +68 -0
  6. package/releases/0.53.0/core/defaults/tree/.claude/agents/architect.md +1 -0
  7. package/releases/0.53.0/core/defaults/tree/.claude/agents/dba.md +3 -0
  8. package/releases/0.53.0/core/defaults/tree/.claude/agents/devops.md +1 -0
  9. package/releases/0.53.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
  10. package/releases/0.53.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  11. package/releases/0.53.0/core/defaults/tree/.claude/agents/qa.md +7 -0
  12. package/releases/0.53.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  13. package/releases/0.53.0/core/defaults/tree/.claude/agents/secops.md +3 -0
  14. package/releases/0.53.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  15. package/releases/0.53.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  16. package/releases/0.53.0/core/tree/.claude/agents/architect.md +129 -0
  17. package/releases/0.53.0/core/tree/.claude/agents/dba.md +117 -0
  18. package/releases/0.53.0/core/tree/.claude/agents/devops.md +118 -0
  19. package/releases/0.53.0/core/tree/.claude/agents/implementer.md +163 -0
  20. package/releases/0.53.0/core/tree/.claude/agents/integration-tester.md +168 -0
  21. package/releases/0.53.0/core/tree/.claude/agents/planner.md +132 -0
  22. package/releases/0.53.0/core/tree/.claude/agents/qa.md +150 -0
  23. package/releases/0.53.0/core/tree/.claude/agents/reviewer.md +184 -0
  24. package/releases/0.53.0/core/tree/.claude/agents/secops.md +120 -0
  25. package/releases/0.53.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
  26. package/releases/0.53.0/core/tree/.claude/agents/solidity-dev.md +123 -0
  27. package/releases/0.53.0/core/tree/.claude/agents-overview.md +64 -0
  28. package/releases/0.53.0/core/tree/.claude/frontend.md +10 -0
  29. package/releases/0.53.0/core/tree/.claude/graph.md +75 -0
  30. package/releases/0.53.0/core/tree/.claude/patterns.md +251 -0
  31. package/releases/0.53.0/core/tree/.claude/pills/README.md +150 -0
  32. package/releases/0.53.0/core/tree/.claude/pipeline.md +101 -0
  33. package/releases/0.53.0/core/tree/.claude/retrieval.md +103 -0
  34. package/releases/0.53.0/core/tree/.claude/router.md +202 -0
  35. package/releases/0.53.0/core/tree/.claude/templates/integration.md +46 -0
  36. package/releases/0.53.0/core/tree/CLAUDE.md +164 -0
  37. package/releases/0.53.0/core/tree/scripts/cost-watch.mjs +25 -0
  38. package/releases/0.53.0/core/tree/scripts/edit-guard.mjs +24 -0
  39. package/releases/0.53.0/core/tree/scripts/harness-check.mjs +71 -0
  40. package/releases/0.53.0/core/tree/scripts/loop-gate.mjs +19 -0
  41. package/releases/0.53.0/core/vocabulary.json +13 -0
  42. package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  43. package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  44. package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  45. package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  46. package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  47. package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  48. package/releases/0.53.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
  49. package/releases/0.53.0/surfaces/blockchain/tree/.claude/router.md +6 -0
  50. package/releases/0.53.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
  51. package/releases/0.53.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
  52. package/releases/0.53.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
  53. package/releases/0.53.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
  54. package/releases/0.53.0/surfaces/d1/tree/.claude/patterns.md +8 -0
  55. package/releases/0.53.0/surfaces/d1/tree/CLAUDE.md +2 -0
  56. package/releases/0.53.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
  57. package/releases/0.53.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
  58. package/releases/0.53.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
  59. package/releases/0.53.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
  60. package/releases/0.53.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
  61. package/releases/0.53.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
  62. package/releases/0.53.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
  63. package/releases/0.53.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
  64. package/releases/0.53.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
  65. package/releases/0.53.0/surfaces/db/tree/.claude/graph.md +8 -0
  66. package/releases/0.53.0/surfaces/db/tree/.claude/patterns.md +22 -0
  67. package/releases/0.53.0/surfaces/db/tree/.claude/pipeline.md +13 -0
  68. package/releases/0.53.0/surfaces/db/tree/.claude/retrieval.md +5 -0
  69. package/releases/0.53.0/surfaces/db/tree/.claude/router.md +4 -0
  70. package/releases/0.53.0/surfaces/db/tree/CLAUDE.md +17 -0
  71. package/releases/0.53.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
  72. package/releases/0.53.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
  73. package/releases/0.53.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
  74. package/releases/0.53.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
  75. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  76. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  77. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
  78. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  79. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  80. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  81. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  82. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  83. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  84. package/releases/0.53.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  85. package/releases/0.53.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
  86. package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  87. package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  88. package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  89. package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  90. package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
  91. package/releases/0.53.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
  92. package/releases/0.53.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
  93. package/releases/0.53.0/surfaces/frontend/tree/.claude/router.md +14 -0
  94. package/releases/0.53.0/surfaces/frontend/tree/CLAUDE.md +5 -0
  95. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  96. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  97. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  98. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  99. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  100. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  101. package/releases/0.53.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  102. package/releases/0.53.0/surfaces/integrations/tree/.claude/graph.md +10 -0
  103. package/releases/0.53.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
  104. package/releases/0.53.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  105. package/releases/0.53.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  106. package/releases/0.53.0/surfaces/integrations/tree/.claude/router.md +6 -0
  107. package/releases/0.53.0/surfaces/integrations/tree/CLAUDE.md +17 -0
  108. package/releases/0.53.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
  109. package/releases/0.53.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
  110. package/releases/0.53.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  111. package/releases/0.53.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  112. package/releases/0.53.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  113. package/releases/0.53.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
  114. package/releases/0.53.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  115. package/releases/0.53.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  116. package/releases/0.53.0/surfaces/money/tree/.claude/graph.md +2 -0
  117. package/releases/0.53.0/surfaces/money/tree/.claude/patterns.md +49 -0
  118. package/releases/0.53.0/surfaces/money/tree/.claude/pipeline.md +2 -0
  119. package/releases/0.53.0/surfaces/money/tree/.claude/retrieval.md +2 -0
  120. package/releases/0.53.0/surfaces/money/tree/CLAUDE.md +11 -0
  121. package/releases/0.53.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  122. package/releases/0.53.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  123. package/releases/0.53.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  124. package/releases/0.53.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  125. package/releases/0.53.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  126. package/releases/0.53.0/surfaces/pii/tree/CLAUDE.md +5 -0
  127. package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
  128. package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
  129. package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
  130. package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
  131. package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
  132. package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
  133. package/releases/0.53.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
  134. package/releases/0.53.0/surfaces/prisma/tree/CLAUDE.md +5 -0
  135. package/releases/0.53.1/core/GAPS.md +125 -0
  136. package/releases/0.53.1/core/answered.json +68 -0
  137. package/releases/0.53.1/core/defaults/tree/.claude/agents/architect.md +1 -0
  138. package/releases/0.53.1/core/defaults/tree/.claude/agents/dba.md +3 -0
  139. package/releases/0.53.1/core/defaults/tree/.claude/agents/devops.md +1 -0
  140. package/releases/0.53.1/core/defaults/tree/.claude/agents/implementer.md +1 -0
  141. package/releases/0.53.1/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  142. package/releases/0.53.1/core/defaults/tree/.claude/agents/qa.md +7 -0
  143. package/releases/0.53.1/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  144. package/releases/0.53.1/core/defaults/tree/.claude/agents/secops.md +3 -0
  145. package/releases/0.53.1/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  146. package/releases/0.53.1/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  147. package/releases/0.53.1/core/tree/.claude/agents/architect.md +129 -0
  148. package/releases/0.53.1/core/tree/.claude/agents/dba.md +117 -0
  149. package/releases/0.53.1/core/tree/.claude/agents/devops.md +118 -0
  150. package/releases/0.53.1/core/tree/.claude/agents/implementer.md +163 -0
  151. package/releases/0.53.1/core/tree/.claude/agents/integration-tester.md +168 -0
  152. package/releases/0.53.1/core/tree/.claude/agents/planner.md +132 -0
  153. package/releases/0.53.1/core/tree/.claude/agents/qa.md +150 -0
  154. package/releases/0.53.1/core/tree/.claude/agents/reviewer.md +184 -0
  155. package/releases/0.53.1/core/tree/.claude/agents/secops.md +120 -0
  156. package/releases/0.53.1/core/tree/.claude/agents/solidity-auditor.md +126 -0
  157. package/releases/0.53.1/core/tree/.claude/agents/solidity-dev.md +123 -0
  158. package/releases/0.53.1/core/tree/.claude/agents-overview.md +64 -0
  159. package/releases/0.53.1/core/tree/.claude/frontend.md +10 -0
  160. package/releases/0.53.1/core/tree/.claude/graph.md +75 -0
  161. package/releases/0.53.1/core/tree/.claude/patterns.md +251 -0
  162. package/releases/0.53.1/core/tree/.claude/pills/README.md +150 -0
  163. package/releases/0.53.1/core/tree/.claude/pipeline.md +101 -0
  164. package/releases/0.53.1/core/tree/.claude/retrieval.md +103 -0
  165. package/releases/0.53.1/core/tree/.claude/router.md +202 -0
  166. package/releases/0.53.1/core/tree/.claude/templates/integration.md +46 -0
  167. package/releases/0.53.1/core/tree/CLAUDE.md +164 -0
  168. package/releases/0.53.1/core/tree/scripts/cost-watch.mjs +25 -0
  169. package/releases/0.53.1/core/tree/scripts/edit-guard.mjs +24 -0
  170. package/releases/0.53.1/core/tree/scripts/harness-check.mjs +71 -0
  171. package/releases/0.53.1/core/tree/scripts/loop-gate.mjs +19 -0
  172. package/releases/0.53.1/core/vocabulary.json +13 -0
  173. package/releases/0.53.1/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  174. package/releases/0.53.1/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  175. package/releases/0.53.1/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  176. package/releases/0.53.1/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  177. package/releases/0.53.1/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  178. package/releases/0.53.1/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  179. package/releases/0.53.1/surfaces/blockchain/tree/.claude/graph.md +11 -0
  180. package/releases/0.53.1/surfaces/blockchain/tree/.claude/router.md +6 -0
  181. package/releases/0.53.1/surfaces/blockchain/tree/CLAUDE.md +12 -0
  182. package/releases/0.53.1/surfaces/d1/tree/.claude/agents/dba.md +15 -0
  183. package/releases/0.53.1/surfaces/d1/tree/.claude/agents/devops.md +2 -0
  184. package/releases/0.53.1/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
  185. package/releases/0.53.1/surfaces/d1/tree/.claude/patterns.md +8 -0
  186. package/releases/0.53.1/surfaces/d1/tree/CLAUDE.md +2 -0
  187. package/releases/0.53.1/surfaces/db/tree/.claude/agents/architect.md +5 -0
  188. package/releases/0.53.1/surfaces/db/tree/.claude/agents/dba.md +54 -0
  189. package/releases/0.53.1/surfaces/db/tree/.claude/agents/devops.md +5 -0
  190. package/releases/0.53.1/surfaces/db/tree/.claude/agents/implementer.md +2 -0
  191. package/releases/0.53.1/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
  192. package/releases/0.53.1/surfaces/db/tree/.claude/agents/planner.md +2 -0
  193. package/releases/0.53.1/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
  194. package/releases/0.53.1/surfaces/db/tree/.claude/agents/secops.md +2 -0
  195. package/releases/0.53.1/surfaces/db/tree/.claude/agents-overview.md +5 -0
  196. package/releases/0.53.1/surfaces/db/tree/.claude/graph.md +8 -0
  197. package/releases/0.53.1/surfaces/db/tree/.claude/patterns.md +22 -0
  198. package/releases/0.53.1/surfaces/db/tree/.claude/pipeline.md +13 -0
  199. package/releases/0.53.1/surfaces/db/tree/.claude/retrieval.md +5 -0
  200. package/releases/0.53.1/surfaces/db/tree/.claude/router.md +4 -0
  201. package/releases/0.53.1/surfaces/db/tree/CLAUDE.md +17 -0
  202. package/releases/0.53.1/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
  203. package/releases/0.53.1/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
  204. package/releases/0.53.1/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
  205. package/releases/0.53.1/surfaces/drizzle/tree/.claude/patterns.md +7 -0
  206. package/releases/0.53.1/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  207. package/releases/0.53.1/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  208. package/releases/0.53.1/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
  209. package/releases/0.53.1/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  210. package/releases/0.53.1/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  211. package/releases/0.53.1/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  212. package/releases/0.53.1/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  213. package/releases/0.53.1/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  214. package/releases/0.53.1/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  215. package/releases/0.53.1/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  216. package/releases/0.53.1/surfaces/edge-cf/tree/CLAUDE.md +27 -0
  217. package/releases/0.53.1/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  218. package/releases/0.53.1/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  219. package/releases/0.53.1/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  220. package/releases/0.53.1/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  221. package/releases/0.53.1/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
  222. package/releases/0.53.1/surfaces/frontend/tree/.claude/patterns.md +8 -0
  223. package/releases/0.53.1/surfaces/frontend/tree/.claude/retrieval.md +2 -0
  224. package/releases/0.53.1/surfaces/frontend/tree/.claude/router.md +14 -0
  225. package/releases/0.53.1/surfaces/frontend/tree/CLAUDE.md +5 -0
  226. package/releases/0.53.1/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  227. package/releases/0.53.1/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  228. package/releases/0.53.1/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  229. package/releases/0.53.1/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  230. package/releases/0.53.1/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  231. package/releases/0.53.1/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  232. package/releases/0.53.1/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  233. package/releases/0.53.1/surfaces/integrations/tree/.claude/graph.md +10 -0
  234. package/releases/0.53.1/surfaces/integrations/tree/.claude/patterns.md +38 -0
  235. package/releases/0.53.1/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  236. package/releases/0.53.1/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  237. package/releases/0.53.1/surfaces/integrations/tree/.claude/router.md +6 -0
  238. package/releases/0.53.1/surfaces/integrations/tree/CLAUDE.md +17 -0
  239. package/releases/0.53.1/surfaces/money/tree/.claude/agents/architect.md +12 -0
  240. package/releases/0.53.1/surfaces/money/tree/.claude/agents/dba.md +14 -0
  241. package/releases/0.53.1/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  242. package/releases/0.53.1/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  243. package/releases/0.53.1/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  244. package/releases/0.53.1/surfaces/money/tree/.claude/agents/secops.md +5 -0
  245. package/releases/0.53.1/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  246. package/releases/0.53.1/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  247. package/releases/0.53.1/surfaces/money/tree/.claude/graph.md +2 -0
  248. package/releases/0.53.1/surfaces/money/tree/.claude/patterns.md +49 -0
  249. package/releases/0.53.1/surfaces/money/tree/.claude/pipeline.md +2 -0
  250. package/releases/0.53.1/surfaces/money/tree/.claude/retrieval.md +2 -0
  251. package/releases/0.53.1/surfaces/money/tree/CLAUDE.md +11 -0
  252. package/releases/0.53.1/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  253. package/releases/0.53.1/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  254. package/releases/0.53.1/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  255. package/releases/0.53.1/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  256. package/releases/0.53.1/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  257. package/releases/0.53.1/surfaces/pii/tree/CLAUDE.md +5 -0
  258. package/releases/0.53.1/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
  259. package/releases/0.53.1/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
  260. package/releases/0.53.1/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
  261. package/releases/0.53.1/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
  262. package/releases/0.53.1/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
  263. package/releases/0.53.1/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
  264. package/releases/0.53.1/surfaces/prisma/tree/.claude/patterns.md +15 -0
  265. package/releases/0.53.1/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 +199 -107
  272. package/src/dashboard.html +331 -0
  273. package/src/dashboard.mjs +191 -0
  274. package/src/office.mjs +48 -83
  275. package/src/serve.mjs +108 -0
@@ -0,0 +1,49 @@
1
+ <!-- nina:slot money.1 -->
2
+
3
+ ---
4
+
5
+ ## Monetary values & percentages
6
+
7
+ - **Money is an integer of the currency's minor unit**, end to end — {{MINOR_UNIT}}, never a float, never a type that can hold a fraction of one. Source of precision: `{{MONEY_MODULE}}`.
8
+ - **Percentages are basis points** (`40% → 4000`, `100% → 10000`) so a distribution is exact without floating point.
9
+ - **Parsing, formatting and percentage arithmetic live in one module and nowhere else.** A second place that turns a string into money is a second rounding rule, and the two will disagree on a value somebody is owed.
10
+ - **Never** convert to a floating-point type before formatting, and never let a display format round a stored value.
11
+ - Monetary database columns are integer. JSON responses serialize them as **strings**, not numbers, so no client parses them into a float. The service owns the conversion.
12
+
13
+ <!-- nina:slot money.2 -->
14
+
15
+ ---
16
+
17
+ ## Distribution invariants (enforced by reviewer + unit tests)
18
+
19
+ The distribution engine lives in `{{CORE_PKG}}`. It is pure and total over integer {{MINOR_UNIT}}:
20
+
21
+ - A destination is either a percentage (basis points of the amount) or a fixed value.
22
+ - `total = Σ destinations`, computed in {{MINOR_UNIT}}.
23
+ - `total > amount` → **reject.** Never clamp, never distribute what is not there.
24
+ - `total < amount` → **allow**, surface the shortfall, and leave the remainder **parked**. Silence is the defect here: an unexplained remainder is indistinguishable from a bug.
25
+ - `total = amount` → OK.
26
+ - A retention is just another destination — the same arithmetic applies to it, with no special case.
27
+
28
+ <!-- nina:slot money.3 -->
29
+
30
+ ---
31
+
32
+ ## State machines & idempotency (enforced by reviewer)
33
+
34
+ - **Every money-moving entity is a state machine.** Its states and legal transitions are declared in `.claude/architecture.md`; the code follows the declaration, not the other way round. Illegal transitions throw; they are never silently coerced.
35
+ - **Every transition writes an audit row** (entity type, entity id, from, to, actor). No transition without a trail — the trail is what makes a money bug diagnosable after the money has moved.
36
+ - **Outbound transfers are idempotent.** Each carries a unique key, and dedupe happens before the side effect, at the serialization point.
37
+ - **One serialization point per balance.** Whatever computes and dispatches against a single balance never runs concurrently with itself. Two concurrent runs over one balance is how a double send happens even when every individual write is correct.
38
+
39
+ <!-- nina:slot money.4 -->
40
+ - **Money computation and dispatch go through the serialization point.** Routes, queue consumers and tools never run a distribution or a transfer synchronously in the request path.
41
+
42
+ <!-- nina:slot money.5 -->
43
+ - **The distribution engine lives in `{{CORE_PKG}}`.** Services call it as a pure function; they do not embed money logic inline.
44
+
45
+ <!-- nina:slot money.6 -->
46
+ - Idempotency on money-moving writes: the record carries an idempotency key, and the serialization point refuses the second attempt *before* the side effect.
47
+
48
+ <!-- nina:slot money.7 -->
49
+ - `{{CORE_PKG}}` is the most-tested package — pure functions over fixtures. Cover the distribution invariants exhaustively: over, under, exact, mixed percentage and fixed, and rounding.
@@ -0,0 +1,2 @@
1
+ <!-- nina:slot money.1 -->
2
+ - **Money invariants respected** — a distribution never exceeds the amount; a shortfall is parked, not distributed; transfers idempotent; every transition audited.
@@ -0,0 +1,2 @@
1
+ <!-- nina:slot money.1 -->
2
+ | New distribution / transfer logic | `.claude/architecture.md` § *Money*, `.claude/patterns.md` § *Distribution invariants*, `{{CORE_PKG}}/` | architect → implementer → reviewer → qa |
@@ -0,0 +1,11 @@
1
+ <!-- nina:slot money.1 -->
2
+ 3. **An outbound transfer is idempotent — it is NEVER sent twice.** Every transfer carries an idempotency key, and deduplication happens at a single serialization point **before** the side effect, never after it. A retry, a re-delivered queue message, a double-click and a replayed webhook must all collapse to one transfer. This is the highest-blast-radius invariant in the system: almost everything else can be corrected afterwards, and a duplicated transfer cannot.
3
+
4
+ <!-- nina:slot money.2 -->
5
+ 4. **Conservation — what comes in equals what goes out plus what is retained.** A distribution may NEVER exceed the amount it distributes → **block**. If it is **less**, **alert** and leave the remainder **parked** and visible — never silently distributed, never silently absorbed. All arithmetic is integer {{MINOR_UNIT}}; percentages are basis points. The LLM never computes a monetary value.
6
+
7
+ <!-- nina:slot money.3 -->
8
+ 5. **State-machine integrity.** Every money-moving entity has an explicit set of states and legal transitions, declared in `.claude/architecture.md` § *State machines* — not inferred from whatever code happens to exist. Only declared transitions are legal; an illegal one throws and is never silently coerced. **Every transition writes an audit row** (entity, id, from → to, actor, when). Skipping a state, inventing one, or landing a transition with no audit row is a hard reject.
9
+
10
+ <!-- nina:slot money.4 -->
11
+ - **money movement**: anything that creates, moves, settles or records a balance
@@ -0,0 +1,2 @@
1
+ <!-- nina:slot pii.1 -->
2
+ - **Privacy.** No sensitive value in clear text where it must not be. The categories are declared in `.claude/architecture.md` § *Privacy* — they are not decided per query, and not decided by you. **Document bytes live only in the object store, referenced by key: the column holds the key, never the bytes.** Any column the architect's spec flagged for masking must be encrypted at rest. Reject a schema that widens what is stored in the clear.
@@ -0,0 +1,8 @@
1
+ <!-- nina:slot pii.1 -->
2
+
3
+ **Point it at an UNAUTHENTICATED route — a sign-in page is the right one.** Never sign in, and never
4
+ screenshot a signed-in page. Accounts on deployed environments hold real personal data, and a
5
+ screenshot or a console dump pulls it into agent transcripts and logs, where it persists and nobody
6
+ ever looks for it again. That is a privacy leak with no upside here: the sign-in page proves
7
+ everything this check is for. It renders from the same bundle, it exercises the same build-time env
8
+ inlining (a missing variable blanks it identically), and it needs no session.
@@ -0,0 +1,8 @@
1
+ <!-- nina:slot pii.1 -->
2
+ - Check for privacy regressions: **no sensitive value in a log, an error message — a library's own included — a URL or a test fixture**, per the categories in `.claude/architecture.md` § *Privacy*; document bytes only in the object store, never plaintext in the database; no leaked secret.
3
+
4
+ <!-- nina:slot pii.2 -->
5
+ - **privacy** — the privacy check above, in full: no sensitive value in a log, an error message, a
6
+ URL or a test fixture; document bytes only in the object store; no leaked secret. Where
7
+ **tenant-and-privacy** is dispatched, its reviewer owns this too and there is no separate one; where
8
+ it is not, this is a dimension of its own. Either way it is never nobody's.
@@ -0,0 +1,2 @@
1
+ <!-- nina:slot pii.1 -->
2
+ - **Privacy (Hard Rule #8)** — the categories declared in `.claude/architecture.md` § *Privacy* must never appear in logs, in responses that do not need them, in error paths, or in the database in clear text. Check every endpoint that returns a person's record for fields it includes by accident: a `select` that quietly grew a column is the usual way this leaks. Check the error paths too — they are written last and reviewed least — and what the libraries on them print or carry in their own errors, bound values included.
@@ -0,0 +1,5 @@
1
+ <!-- nina:slot pii.1 -->
2
+ - **Nothing personal reaches the chain.** What is written on-chain is public, permanent, and beyond
3
+ deletion by anyone — including by you, and including after a subject asks. A hash of a personal
4
+ identifier is not a protection: it is a lookup table for whoever holds the input set. Reject a
5
+ personal datum stored in state, emitted in an event, or passed as calldata.
@@ -0,0 +1,5 @@
1
+ <!-- nina:slot pii.1 -->
2
+ 8. **Sensitive personal data is classified, and the classification is enforced.** What counts as sensitive here — identity documents, financial identifiers, anything a person would not expect to be readable by whoever can read a log — is declared in `.claude/architecture.md` § *Privacy*, along with the regime that governs it ({{PRIVACY_REGIME}}). Encrypted at rest. **No sensitive value in a log, an error message, a URL, an analytics event, or an agent transcript** — the places nobody thinks about are exactly the ones that persist. That includes what a library puts in its own errors and prints itself: a database client's error can carry the query and every bound value, and a framework can log a whole error past the logger it was given. A database error is mapped at the data layer to a typed error from a closed list before anything logs or returns it, and a test proves that a bound value it writes reaches no thrown error. Document bytes live only in the object store, referenced by key, never in the database in clear text.
3
+
4
+ <!-- nina:slot pii.2 -->
5
+ - **personal data**: where it is collected, stored, logged or sent
@@ -0,0 +1,2 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ | `prisma-client-api` | a new or changed Prisma query |
@@ -0,0 +1,42 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ | `prisma-cli` | **before ANY `prisma` CLI command** — `validate`, `format`, `migrate dev`, `migrate diff`, `migrate deploy`. Flags differ between versions, and one of them (`migrate diff --shadow-database-url` against a live database) destroys the database it points at |
3
+ | `prisma-client-api` | auditing a new or changed query — filters, operators, `$transaction` semantics. Through a driver adapter, cite the adapter's installed source for what a transaction does: the skill describes the default engine, and an adapter can roll back less |
4
+
5
+ <!-- nina:slot prisma.2 -->
6
+ Neither skill covers a **caching or edge layer sitting on top of the client** — cache strategy,
7
+ edge-client behaviour and the rule about never caching a read that authorizes a write belong to that
8
+ layer's own integration doc, declared like any other integration. A skill does not replace it.
9
+
10
+ <!-- nina:slot prisma.3 -->
11
+ - **With Prisma, both offline.** Run `pnpm --filter {{DB_PKG_NAME}} exec prisma validate`; it must pass. Then run `prisma migrate diff` in its **offline, file-only form** and read the generated SQL, not just the Prisma delta. Its flags changed in Prisma 7, so read the installed major (`prisma --version`) before writing the command:
12
+
13
+ ```
14
+ # Prisma 6. Compare the PREVIOUS schema against the working one — pure file-to-file, no database.
15
+ git show HEAD:{{DB_PKG}}/prisma/schema.prisma > /tmp/schema-head.prisma
16
+ pnpm --filter {{DB_PKG_NAME}} exec prisma migrate diff \
17
+ --from-schema-datamodel /tmp/schema-head.prisma \
18
+ --to-schema-datamodel prisma/schema.prisma \
19
+ --script
20
+ ```
21
+
22
+ From Prisma 7 the two flags are `--from-schema` and `--to-schema`, and the schema engine starts from the
23
+ datasource in `prisma.config.ts` even for this diff: with none there, it prints nothing and exits 0. For
24
+ SQLite a placeholder the diff never opens is enough, a `file:` URL under a gitignored directory; for any
25
+ other provider, find out what the diff opens before you give it one. Never a real database's URL. A first schema has nothing at `HEAD`: diff it `--from-empty`. Compare the output against the
26
+ hand-written `migration.sql` to confirm the declarative parts match.
27
+
28
+ ⚠️ **Do NOT reach for `--from-migrations`.** It looks like the natural choice and it is a trap:
29
+ Prisma refuses it without a shadow database (`--shadow-database-url`, or from Prisma 7 a
30
+ `shadowDatabaseUrl` in `prisma.config.ts`), which pushes you toward supplying a database URL — the exact move that destroys databases (below). The file-to-file form above answers the
31
+ same question with no database involved.
32
+
33
+ ⛔ **NEVER point `--shadow-database-url`, `--from-url`, `--to-url`, `--from-schema-datasource` — or, from Prisma 7, a `shadowDatabaseUrl` or the config datasource behind `--from-config-datasource` — at a real database, most of all not the one `{{SECRETS_LOCAL}}` names.** Prisma **executes** the entire migration history against whatever database those flags name, which **DESTROYS ALL DATA** in it. A shadow database must be a disposable, empty database and nothing else. It does not announce itself: the command completes with a reassuring "empty migration" result and no error, having already destroyed everything in the database it was pointed at. If the offline form above cannot answer your question, **STOP and report that to the orchestrator** instead of reaching for a database URL.
34
+
35
+ <!-- nina:slot prisma.4 -->
36
+ - **Where reads are cached through Prisma Accelerate,** every `findUnique` / `findMany` on a hot read path names its `cacheStrategy` (`ttl` / `swr`), and one that feeds a correctness-critical write names none.
37
+
38
+ <!-- nina:slot prisma.5 -->
39
+ - **On an edge runtime,** new Prisma code paths use the edge client of the installed major: `@prisma/client/edge` up to Prisma 6; from 7, the client the `prisma-client` generator writes with the platform's `runtime` set, imported from where it is generated — `@prisma/client/edge` fails there with `MODULE_NOT_FOUND`.
40
+
41
+ <!-- nina:slot prisma.6 -->
42
+ - Run a Prisma command that writes to a real database — `prisma migrate dev`, `migrate deploy`, `migrate reset`, `migrate resolve`, `db push`, `db execute`, `$executeRaw*` — or **any `prisma migrate diff` variant carrying a database URL**, which silently wipes the database it points at.
@@ -0,0 +1,11 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ - **`prisma-cli`** — before `migrate deploy` or any other Prisma CLI command against a real database.
3
+
4
+ <!-- nina:slot prisma.2 -->
5
+ - **Prisma migrations go out with `prisma migrate deploy`**, and nothing else changes a shared database's schema.
6
+
7
+ <!-- nina:slot prisma.3 -->
8
+ - Run `prisma migrate diff --shadow-database-url <url>` against any live database. It **executes** the migration history against that database, destroying everything in it, and reports success. Use a disposable shadow database or don't run it.
9
+
10
+ <!-- nina:slot prisma.4 -->
11
+ - Run `prisma migrate reset` against a shared database, ever.
@@ -0,0 +1,2 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ | `prisma-client-api` | a new or changed Prisma query |
@@ -0,0 +1,2 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ | `prisma-cli` | a Prisma CLI command |
@@ -0,0 +1,2 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ | `prisma-client-api` | a new or changed Prisma query |
@@ -0,0 +1,15 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ - **Database table/column names** (Prisma `@map` / `@@map`): `snake_case` in SQL, `camelCase` in TS.
3
+
4
+ <!-- nina:slot prisma.2 -->
5
+ - Where Prisma reads are cached through Accelerate, `cacheStrategy` lives in `{{DB_PKG}}`: services do not add, strip, or override cache policies.
6
+
7
+ <!-- nina:slot prisma.3 -->
8
+
9
+ ### Prisma
10
+
11
+ - Schema lives in `{{DB_PKG}}/prisma/schema.prisma`. Nowhere else.
12
+ - **Never write raw SQL** unless justified in a code comment with perf data.
13
+ - N+1 is solved with `include` / `select`, chosen deliberately.
14
+ - Where reads are cached through Accelerate, every `findUnique` / `findMany` on a hot path defines its `cacheStrategy`, and no direct connection string is embedded in code.
15
+ - Tests run a real Prisma client against a real database; the client is never mocked.
@@ -0,0 +1,5 @@
1
+ <!-- nina:slot prisma.1 -->
2
+ | Run any `prisma` CLI command (the database gate, devops, and the integration gate where there is one) | `prisma-cli` | flags differ between versions; `migrate diff --shadow-database-url` against a live DB wiped dev/staging once |
3
+
4
+ <!-- nina:slot prisma.2 -->
5
+ | Write or audit a Prisma query (architect, implementer, the database gate, reviewer) | `prisma-client-api` | filters, operators, `$transaction` semantics. a caching or edge layer on top of the client is **NOT covered** — its cache strategy belongs to that layer's own integration doc |
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `nina dashboard` — the office, the tasks, the pipeline, the pills, the learning cycle and the stats in one page on
3
+ * this machine, its address printed for a click (src/dashboard.mjs).
4
+ *
5
+ * nina dashboard [--project <dir>] [--port <n>] [--open]
6
+ */
7
+
8
+ import { existsSync } from 'node:fs';
9
+ import { join, resolve } from 'node:path';
10
+ import { HARNESS } from '../paths.mjs';
11
+ import { viewDashboard } from '../dashboard.mjs';
12
+
13
+ /**
14
+ * Runs the command.
15
+ *
16
+ * @param {string[]} argv
17
+ * @param {{root: string}} ctx - CLI context.
18
+ * @returns {Promise<number>} Process exit code.
19
+ */
20
+ export async function dashboard(argv, ctx) {
21
+ const arg = (flag) => (argv.includes(flag) ? argv[argv.indexOf(flag) + 1] ?? '' : null);
22
+ const target = resolve(arg('--project') ?? '.');
23
+ if (!existsSync(join(target, HARNESS, 'profile.json'))) {
24
+ console.error(` no ${HARNESS}/profile.json under ${target} — run it inside a project, or name one with --project\n`);
25
+ return 1;
26
+ }
27
+ const port = arg('--port');
28
+ if (port !== null && !(/^\d+$/.test(port) && Number(port) <= 65535)) {
29
+ console.error(` --port takes a port number, 0 to 65535, and was given ${port}\n`);
30
+ return 2;
31
+ }
32
+ return viewDashboard({ target, ctx, port: port === null ? undefined : Number(port), open: argv.includes('--open') });
33
+ }
@@ -892,6 +892,49 @@ async function graduate(target, profile, layerRoot, known, which, here) {
892
892
  return 0;
893
893
  }
894
894
 
895
+ /**
896
+ * Whether a project's pipeline is learning, link by link, as data: what `nina learn` prints, what its detector acts
897
+ * on, and what the dashboard's learning tab shows. Read from the store and the pills as they stand — `learn` takes a
898
+ * snapshot first, the dashboard does not, since the project's hooks take one every turn.
899
+ *
900
+ * @param {string} target - The project directory.
901
+ * @param {string} layerRoot - Its pinned release, which says whether the loop gate ships.
902
+ * @param {{surfaces?: string[]}} profile - The project's profile.
903
+ * @param {{days?: number, records?: object[], known?: Awaited<ReturnType<typeof lessons>>, report?: boolean}} [options] -
904
+ * `report: false` reads only what the detector acts on: it runs every turn, and the rest — what was applied, what a
905
+ * lesson changed, the gate's ledgers, the release's scripts — is the report's.
906
+ */
907
+ export async function learningData(target, layerRoot, profile, { days = CAPTURE_DAYS, records, known, report = true } = {}) {
908
+ const rounds = records ?? (await recorded(target));
909
+ const pills = known ?? (await lessons(target));
910
+ // Who each loop-back sent work back to, read off the project's own graph.
911
+ const loops = loadProject(target)?.loops ?? null;
912
+ const owners = sentBackTo(rounds, loops);
913
+ const targets = loops ? new Set([...loops.values()].flatMap((byToken) => [...byToken.values()].flatMap((to) => [...to.keys()]))) : null;
914
+ const filed = await filedRequests(target);
915
+ const owed = { late: overdue(rounds, pills, new Date(), days, owners), filed, owed: undecided(pills, filed) };
916
+ if (!report) return owed;
917
+ const active = pills.filter((l) => !l.retired);
918
+ return {
919
+ ...owed,
920
+ days,
921
+ runs: new Set(rounds.map(runOf)).size,
922
+ rounds: rounds.length,
923
+ span: rounds.length ? [day(rounds[0].ts), day(rounds.at(-1).ts)] : null,
924
+ active,
925
+ ready: active.filter((l) => l.occurrences >= GRADUATION_AT),
926
+ open: filed.filter((r) => r.status === 'open'),
927
+ madeHere: filed.filter((r) => r.target === HERE).length,
928
+ relapses: relapsed(pills, filed),
929
+ use: applied(rounds, pills),
930
+ checks: verified(rounds, pills, { owners, targets }),
931
+ // The loops the lessons are about, as the gate counted them — beside the lessons, because a loop the
932
+ // gate held is a lesson waiting to be asked for.
933
+ activity: gateActivity(target, new Date(Date.now() - days * 86_400_000).toISOString()),
934
+ gated: (await shippedScripts(layerRoot, profile.surfaces ?? [])).has(GATE),
935
+ };
936
+ }
937
+
895
938
  /**
896
939
  * Runs the learning report, the detector, or a graduation.
897
940
  *
@@ -943,13 +986,8 @@ export async function learn(argv, ctx) {
943
986
  }
944
987
  if (argv.includes('--close')) return close(target, argv[argv.indexOf('--close') + 1] ?? '');
945
988
 
946
- // Who each loop-back sent work back to, read off the project's own graph.
947
- const loops = loadProject(target)?.loops ?? null;
948
- const owners = sentBackTo(records, loops);
949
- const targets = loops ? new Set([...loops.values()].flatMap((byToken) => [...byToken.values()].flatMap((to) => [...to.keys()]))) : null;
950
- const late = overdue(records, known, new Date(), days, owners);
951
- const filed = await filedRequests(target);
952
- const owed = undecided(known, filed);
989
+ const learning = await learningData(target, resolved.dir, profile, { days, records, known, report: !check });
990
+ const { late, filed, owed } = learning;
953
991
 
954
992
  if (check) {
955
993
  // During `nina upgrade --apply`: this is about history, not the move — see upgrade.mjs.
@@ -1048,17 +1086,13 @@ export async function learn(argv, ctx) {
1048
1086
  return 1;
1049
1087
  }
1050
1088
 
1051
- const active = known.filter((l) => !l.retired);
1052
- const ready = active.filter((l) => l.occurrences >= GRADUATION_AT);
1053
- const open = filed.filter((r) => r.status === 'open');
1054
- const use = applied(records, known);
1055
- const checks = verified(records, known, { owners, targets });
1056
- const span = records.length ? `${day(records[0].ts)} → ${day(records.at(-1).ts)}` : '';
1089
+ const { active, ready, open, use, checks, activity, gated } = learning;
1090
+ const span = learning.span ? `${learning.span[0]} → ${learning.span[1]}` : '';
1057
1091
 
1058
1092
  console.log(`\n learning — core ${profile.core}\n`);
1059
1093
  console.log(
1060
1094
  records.length
1061
- ? ` observe ${new Set(records.map(runOf)).size} run(s) in ${records.length} round(s) recorded, ${span}`
1095
+ ? ` observe ${learning.runs} run(s) in ${records.length} round(s) recorded, ${span}`
1062
1096
  : ' observe ✗ nothing recorded — no transcripts found for this project, so no link below can be measured',
1063
1097
  );
1064
1098
  if (late.length === 0) console.log(` capture ✓ no role has looped back ${CAPTURE_AT}+ times without a lesson in the last ${days} days`);
@@ -1074,7 +1108,7 @@ export async function learn(argv, ctx) {
1074
1108
  `${use.listed} only listed the directory — measured since ${use.from}. Reading is not obeying; nothing here can see that.`
1075
1109
  : ' apply not measured yet — runs recorded from now on say whether they read their pills',
1076
1110
  );
1077
- const madeHere = filed.filter((r) => r.target === HERE).length;
1111
+ const { madeHere } = learning;
1078
1112
  console.log(
1079
1113
  ` graduate ${ready.length} lesson(s) at ${GRADUATION_AT}+ occurrences · ${open.length} request(s) open` +
1080
1114
  (madeHere ? ` · ${madeHere} made a rule here` : '') +
@@ -1090,7 +1124,7 @@ export async function learn(argv, ctx) {
1090
1124
  );
1091
1125
  }
1092
1126
  for (const r of open) console.log(` open since ${r.date}: ${r.pill} → ${r.target}. \`nina upgrade\` to the release that answers it closes it`);
1093
- for (const r of relapsed(known, filed)) {
1127
+ for (const r of learning.relapses) {
1094
1128
  const shipped = `came back ${r.recurred} time(s) after ${ruleWent(r.request, false)}`;
1095
1129
  if (r.due) console.log(` ✗ ${r.lesson.rel} ${shipped} — the rule did not hold; \`harness:check\` reopens it`);
1096
1130
  else console.log(` · ${r.lesson.rel} ${shipped} — watched; it reopens at ${CAPTURE_AT}`);
@@ -1116,9 +1150,7 @@ export async function learn(argv, ctx) {
1116
1150
  }
1117
1151
  // The loops the lessons are about, as the gate counted them — beside the lessons, because a loop the
1118
1152
  // gate held is a lesson waiting to be asked for.
1119
- const since = new Date(Date.now() - days * 86_400_000).toISOString();
1120
- const activity = gateActivity(target, since);
1121
- if (!(await shippedScripts(resolved.dir, profile.surfaces ?? [])).has(GATE)) {
1153
+ if (!gated) {
1122
1154
  console.log(` loops core ${profile.core} has no loop gate — the caps in .claude/graph.md are instructions`);
1123
1155
  } else if (!activity) {
1124
1156
  console.log(' loops the gate has no ledger for this project — `nina gate --selftest` says whether it is wired');
@@ -336,43 +336,24 @@ export function validate(pill, text, roles, today) {
336
336
  }
337
337
 
338
338
  /**
339
- * `nina pills`.
339
+ * A project's pills, checked: what `nina pills` prints and the dashboard's pills tab shows — each pill's stage, status,
340
+ * count and trigger, and the problems and notes over the whole corpus. Null when the project has composed no agents.
340
341
  *
341
- * @param {string[]} argv - Command arguments; `--project <dir>` selects the target.
342
- * @param {{root: string}} [ctx] - CLI context; `root` is the NINA install directory.
343
- * @returns {Promise<number>} Process exit code.
342
+ * @param {string} target - The project.
343
+ * @param {{root?: string}} [ctx] - CLI context: where the layers are, to say where a recurring lesson belongs.
344
344
  */
345
- export async function pills(argv, ctx) {
346
- const target = resolve(argv.includes('--project') ? argv[argv.indexOf('--project') + 1] : '.');
347
- const quiet = argv.includes('--quiet');
345
+ export async function pillsData(target, ctx) {
348
346
  const pillsDir = join(target, '.claude', 'pills');
349
347
  const agentsDir = join(target, '.claude', 'agents');
350
-
351
- if (!existsSync(agentsDir)) {
352
- console.error(` no .claude/agents under ${target} — run \`nina compose\` first.\n`);
353
- return 1;
354
- }
348
+ if (!existsSync(agentsDir)) return null;
355
349
  const roles = (await readdir(agentsDir))
356
350
  .filter((f) => f.endsWith('.md'))
357
351
  .map((f) => f.replace(/\.md$/, ''))
358
352
  .sort();
359
-
360
353
  const today = new Date().toISOString().slice(0, 10);
361
- if (argv.includes('--tidy')) {
362
- const moved = await tidyRetired(target);
363
- for (const m of moved) console.log(` moved ${m} → ${RETIRED}/${m}`);
364
- console.log(moved.length ? `\npills: ${moved.length} retired pill(s) moved where no agent opens them\n` : 'pills: no retired pill where agents read\n');
365
- return 0;
366
- }
367
354
  const files = existsSync(pillsDir) ? await pillFiles(pillsDir) : [];
368
355
 
369
- // The directory itself is composed, so its presence says nothing. An empty corpus is the
370
- // normal state of a new project and the suspicious state of an old one — say which, and
371
- // leave the judgement to whoever knows how long the pipeline has been running.
372
- if (files.length === 0) {
373
- if (!quiet) console.log(` no pill written yet under ${target} — either nothing has been learned the expensive way, or nothing was written down.\n`);
374
- return 0;
375
- }
356
+ if (files.length === 0) return { files: 0, active: 0, retired: 0, problems: [], notes: [], pills: [] };
376
357
  const problems = [];
377
358
  const notes = [];
378
359
  const active = [];
@@ -404,8 +385,21 @@ export async function pills(argv, ctx) {
404
385
  let resolved = 0;
405
386
  let cited = 0;
406
387
 
388
+ const each = [];
407
389
  for (const pill of files) {
408
390
  const result = validate(pill, await readFile(pill.path, 'utf8'), roles, today);
391
+ const f = result.fields ?? {};
392
+ each.push({
393
+ label: result.label,
394
+ status: f.status ?? null,
395
+ roles: list(f.applies_to),
396
+ occurrences: Number(f.occurrences ?? 1) || 1,
397
+ date: f.date ?? null,
398
+ last_seen: f.last_seen ?? null,
399
+ severity: f.severity ?? null,
400
+ trigger: f.trigger ?? null,
401
+ problems: result.problems.length,
402
+ });
409
403
  problems.push(...result.problems);
410
404
  notes.push(...result.notes);
411
405
  if (pill.dir && pill.dir !== 'shared' && pill.dir !== RETIRED) taught.add(pill.dir);
@@ -478,14 +472,47 @@ export async function pills(argv, ctx) {
478
472
  const dated = active.map((p) => p.date).filter(Boolean).sort();
479
473
  if (dated.length > 0) notes.push(`oldest active pill is ${dated[0]}, newest ${dated[dated.length - 1]}`);
480
474
 
475
+ return { files: files.length, active: active.length, retired, problems, notes, pills: each };
476
+ }
477
+
478
+ /**
479
+ * `nina pills`.
480
+ *
481
+ * @param {string[]} argv - Command arguments; `--project <dir>` selects the target.
482
+ * @param {{root: string}} [ctx] - CLI context; `root` is the NINA install directory.
483
+ * @returns {Promise<number>} Process exit code.
484
+ */
485
+ export async function pills(argv, ctx) {
486
+ const target = resolve(argv.includes('--project') ? argv[argv.indexOf('--project') + 1] : '.');
487
+ const quiet = argv.includes('--quiet');
488
+ const agentsDir = join(target, '.claude', 'agents');
489
+
490
+ if (!existsSync(agentsDir)) {
491
+ console.error(` no .claude/agents under ${target} — run \`nina compose\` first.\n`);
492
+ return 1;
493
+ }
494
+ if (argv.includes('--tidy')) {
495
+ const moved = await tidyRetired(target);
496
+ for (const m of moved) console.log(` moved ${m} → ${RETIRED}/${m}`);
497
+ console.log(moved.length ? `\npills: ${moved.length} retired pill(s) moved where no agent opens them\n` : 'pills: no retired pill where agents read\n');
498
+ return 0;
499
+ }
500
+ const { files, active, retired, problems, notes } = await pillsData(target, ctx);
501
+ // The directory itself is composed, so its presence says nothing. An empty corpus is the
502
+ // normal state of a new project and the suspicious state of an old one — say which, and
503
+ // leave the judgement to whoever knows how long the pipeline has been running.
504
+ if (files === 0) {
505
+ if (!quiet) console.log(` no pill written yet under ${target} — either nothing has been learned the expensive way, or nothing was written down.\n`);
506
+ return 0;
507
+ }
481
508
  if (!(quiet && problems.length === 0)) {
482
- console.log(` ${files.length} pill(s): ${active.length} active, ${retired} retired\n`);
509
+ console.log(` ${files} pill(s): ${active} active, ${retired} retired\n`);
483
510
  for (const p of problems) console.log(` ✗ ${p}`);
484
511
  for (const n of notes) console.log(` · ${n}`);
485
512
  if (problems.length === 0 && notes.length === 0) console.log(' ✓ every pill is well formed, placed where it is read, and carries evidence');
486
513
  console.log(
487
514
  problems.length === 0
488
- ? `\npills: all ${files.length} well formed${notes.length > 0 ? ` (${notes.length} note(s))` : ''}\n`
515
+ ? `\npills: all ${files} well formed${notes.length > 0 ? ` (${notes.length} note(s))` : ''}\n`
489
516
  : `\npills: ${problems.length} problem(s)\n`,
490
517
  );
491
518
  }
@@ -165,6 +165,54 @@ export function historyOf(records, since) {
165
165
  return byRole;
166
166
  }
167
167
 
168
+ /**
169
+ * A composed pipeline, as data: what `nina pipeline` draws and the dashboard's pipeline tab shows — the line and where
170
+ * it ends, the gates, the stages that run as several agents, who sends work back to whom and how many rounds per
171
+ * issue, and each stage's model, effort, skills and what it ran since `since`; the project's own agents; and which
172
+ * chain each task shape takes. Null when the project has no composed graph.
173
+ *
174
+ * @param {string} dir - The project.
175
+ * @param {{since?: string}} [options] - The first day of the history each stage is measured over.
176
+ */
177
+ export function pipelineData(dir, { since } = {}) {
178
+ const graphPath = join(dir, '.claude', 'graph.md');
179
+ if (!existsSync(graphPath)) return null;
180
+ const graphText = readFileSync(graphPath, 'utf8');
181
+ const graph = parseGraph(graphText);
182
+ const words = stageWords(graphText);
183
+ const shape = shapeOf(graph);
184
+ const claude = join(dir, 'CLAUDE.md');
185
+ const profile = readProfile(dir).profile ?? {};
186
+ const agentsDir = join(dir, '.claude', 'agents');
187
+ const specs = new Map();
188
+ for (const file of existsSync(agentsDir) ? readdirSync(agentsDir).filter((f) => f.endsWith('.md')).sort() : []) {
189
+ specs.set(file.replace(/\.md$/, ''), readFileSync(join(agentsDir, file), 'utf8'));
190
+ }
191
+ const from = since || localDay(new Date(Date.now() - HISTORY_DAYS * 86_400_000).toISOString());
192
+ const records = projectRecords(dir);
193
+ const history = records ? historyOf(records, from) : null;
194
+ return {
195
+ project: basename(dir),
196
+ core: profile.core ?? null,
197
+ surfaces: (profile.surfaces ?? []).slice().sort(),
198
+ graph,
199
+ line: shape.line,
200
+ ends: shape.ends,
201
+ gates: shape.gates,
202
+ many: [...(graph.many ?? new Map())].map(([stage, { when }]) => ({ stage, when })),
203
+ back: shape.back,
204
+ since: from,
205
+ measured: history !== null,
206
+ stages: [...graph.stages].map((stage) => {
207
+ const spec = specs.get(stage) ?? '';
208
+ const front = frontmatter(spec) ?? {};
209
+ return { stage, model: front.model ?? null, effort: front.effort ?? null, words: words.get(stage) ?? '', history: history?.get(stage) ?? null, skills: [...required(spec).skills].sort() };
210
+ }),
211
+ own: [...specs.keys()].filter((role) => !graph.stages.has(role)).map((role) => ({ role, description: (frontmatter(specs.get(role)) ?? {}).description ?? '' })),
212
+ shapes: existsSync(claude) ? chainsByShape(readFileSync(claude, 'utf8')) : [],
213
+ };
214
+ }
215
+
168
216
  /**
169
217
  * Runs the command.
170
218
  *
@@ -200,12 +248,9 @@ export async function pipeline(argv) {
200
248
  }
201
249
  return viewOffice({ target: dir, wanted: arg('--session') || undefined, replay: argv.includes('--replay'), port: port === null ? undefined : Number(port), open: argv.includes('--open') });
202
250
  }
203
- const graphText = readFileSync(graphPath, 'utf8');
204
- const graph = parseGraph(graphText);
205
- const words = stageWords(graphText);
206
- const shape = shapeOf(graph);
207
- const claude = join(dir, 'CLAUDE.md');
208
- const shapes = existsSync(claude) ? chainsByShape(readFileSync(claude, 'utf8')) : [];
251
+ const data = pipelineData(dir, { since: arg('--since') || undefined });
252
+ const { graph, shapes } = data;
253
+ const shape = { line: data.line, ends: data.ends, gates: data.gates, back: data.back };
209
254
 
210
255
  // `--for`: one shape's chain, and only the stages on it.
211
256
  const query = arg('--for');
@@ -232,9 +277,7 @@ export async function pipeline(argv) {
232
277
  const padName = (s, n) => `${name(s)}${' '.repeat(Math.max(n - s.length, 1))}`;
233
278
 
234
279
  // A pipeline composed by hand draws all the same; only the header has less to say.
235
- const profile = readProfile(dir).profile ?? {};
236
- const surfaces = (profile.surfaces ?? []).slice().sort();
237
- console.log(` pipeline · ${basename(dir)}${profile.core ? ` · core ${profile.core}` : ''}${surfaces.length ? ` · surfaces ${surfaces.join(', ')}` : ''}`);
280
+ console.log(` pipeline · ${data.project}${data.core ? ` · core ${data.core}` : ''}${data.surfaces.length ? ` · surfaces ${data.surfaces.join(', ')}` : ''}`);
238
281
 
239
282
  // The stages this drawing covers: all of them, or those the chosen shape's chain names.
240
283
  const onChain = chosen ? (chosen.chain.match(/[a-z][a-z-]*/g) ?? []).filter((w) => graph.stages.has(w)) : null;
@@ -300,30 +343,18 @@ export async function pipeline(argv) {
300
343
  }
301
344
 
302
345
  // Each stage: what it does, on which model and effort, with which skills, and what it did lately.
303
- const agentsDir = join(dir, '.claude', 'agents');
304
- const specs = new Map();
305
- for (const file of existsSync(agentsDir) ? readdirSync(agentsDir).filter((f) => f.endsWith('.md')).sort() : []) {
306
- specs.set(file.replace(/\.md$/, ''), readFileSync(join(agentsDir, file), 'utf8'));
307
- }
308
- const since = arg('--since') || localDay(new Date(Date.now() - HISTORY_DAYS * 86_400_000).toISOString());
309
- const runs = projectRecords(dir);
310
- const history = runs ? historyOf(runs, since) : null;
311
- const stages = [...graph.stages].filter((s) => covered.has(s));
312
- const col = Math.max(...stages.map((s) => s.length), 10) + 2;
313
- console.log(`\n ${dim(history ? `stages · what each ran since ${since}, from the measured history` : 'stages · no measured history for this project yet')}`);
314
- for (const stage of stages) {
315
- const spec = specs.get(stage) ?? '';
316
- const front = frontmatter(spec) ?? {};
317
- const on = [front.model, front.effort].filter(Boolean).join(' · ') || 'no spec';
318
- console.log(` ${padName(stage, col)}${on.padEnd(26)}${dim(fit(words.get(stage) ?? '', width - col - 32))}`);
319
- if (history) {
320
- const h = history.get(stage);
346
+ const stages = data.stages.filter((s) => covered.has(s.stage));
347
+ const col = Math.max(...stages.map((s) => s.stage.length), 10) + 2;
348
+ console.log(`\n ${dim(data.measured ? `stages · what each ran since ${data.since}, from the measured history` : 'stages · no measured history for this project yet')}`);
349
+ for (const { stage, model, effort, words, history: h, skills } of stages) {
350
+ const on = [model, effort].filter(Boolean).join(' · ') || 'no spec';
351
+ console.log(` ${padName(stage, col)}${on.padEnd(26)}${dim(fit(words, width - col - 32))}`);
352
+ if (data.measured) {
321
353
  const said = !h
322
354
  ? 'no run'
323
355
  : `${h.runs} run(s)${h.rounds > h.runs ? ` in ${h.rounds} rounds` : ''}${h.read === 0 ? ', no verdict read' : `, sent back ${h.back} of ${h.read} (${Math.round((100 * h.back) / h.read)}%)`}`;
324
356
  console.log(` ${' '.repeat(col)}${dim('ran ')}${said}`);
325
357
  }
326
- const skills = [...required(spec).skills].sort();
327
358
  if (skills.length > 0) {
328
359
  const lines = [];
329
360
  let current = '';
@@ -341,12 +372,10 @@ export async function pipeline(argv) {
341
372
 
342
373
  if (!chosen) {
343
374
  // An agent the project wrote for itself is not a stage, and the graph does not route to it.
344
- const own = [...specs.keys()].filter((role) => !graph.stages.has(role));
345
- if (own.length > 0) {
375
+ if (data.own.length > 0) {
346
376
  console.log(`\n ${dim("the project's own agents, outside the pipeline")}`);
347
- for (const role of own) {
348
- const front = frontmatter(specs.get(role)) ?? {};
349
- console.log(` ${role}${' '.repeat(Math.max(col - role.length, 2))}${dim(fit(front.description ?? '', width - col - 6))}`);
377
+ for (const { role, description } of data.own) {
378
+ console.log(` ${role}${' '.repeat(Math.max(col - role.length, 2))}${dim(fit(description, width - col - 6))}`);
350
379
  }
351
380
  }
352
381
  if (shapes.length > 0) {