@xhulz/nina 0.62.0 → 0.63.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 (269) hide show
  1. package/bin/nina.mjs +7 -0
  2. package/package.json +1 -1
  3. package/releases/0.63.0/core/GAPS.md +125 -0
  4. package/releases/0.63.0/core/answered.json +77 -0
  5. package/releases/0.63.0/core/defaults/tree/.claude/agents/architect.md +1 -0
  6. package/releases/0.63.0/core/defaults/tree/.claude/agents/dba.md +3 -0
  7. package/releases/0.63.0/core/defaults/tree/.claude/agents/devops.md +1 -0
  8. package/releases/0.63.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
  9. package/releases/0.63.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  10. package/releases/0.63.0/core/defaults/tree/.claude/agents/qa.md +7 -0
  11. package/releases/0.63.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  12. package/releases/0.63.0/core/defaults/tree/.claude/agents/secops.md +3 -0
  13. package/releases/0.63.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  14. package/releases/0.63.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  15. package/releases/0.63.0/core/tree/.claude/agents/architect.md +133 -0
  16. package/releases/0.63.0/core/tree/.claude/agents/dba.md +119 -0
  17. package/releases/0.63.0/core/tree/.claude/agents/design-reviewer.md +76 -0
  18. package/releases/0.63.0/core/tree/.claude/agents/devops.md +118 -0
  19. package/releases/0.63.0/core/tree/.claude/agents/implementer.md +164 -0
  20. package/releases/0.63.0/core/tree/.claude/agents/integration-tester.md +170 -0
  21. package/releases/0.63.0/core/tree/.claude/agents/planner.md +132 -0
  22. package/releases/0.63.0/core/tree/.claude/agents/qa.md +150 -0
  23. package/releases/0.63.0/core/tree/.claude/agents/reviewer.md +187 -0
  24. package/releases/0.63.0/core/tree/.claude/agents/secops.md +120 -0
  25. package/releases/0.63.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
  26. package/releases/0.63.0/core/tree/.claude/agents/solidity-dev.md +123 -0
  27. package/releases/0.63.0/core/tree/.claude/agents-overview.md +65 -0
  28. package/releases/0.63.0/core/tree/.claude/frontend.md +10 -0
  29. package/releases/0.63.0/core/tree/.claude/graph.md +79 -0
  30. package/releases/0.63.0/core/tree/.claude/patterns.md +251 -0
  31. package/releases/0.63.0/core/tree/.claude/pills/README.md +150 -0
  32. package/releases/0.63.0/core/tree/.claude/pipeline.md +101 -0
  33. package/releases/0.63.0/core/tree/.claude/retrieval.md +103 -0
  34. package/releases/0.63.0/core/tree/.claude/router.md +211 -0
  35. package/releases/0.63.0/core/tree/.claude/templates/integration.md +48 -0
  36. package/releases/0.63.0/core/tree/CLAUDE.md +166 -0
  37. package/releases/0.63.0/core/tree/scripts/cost-watch.mjs +25 -0
  38. package/releases/0.63.0/core/tree/scripts/edit-guard.mjs +24 -0
  39. package/releases/0.63.0/core/tree/scripts/harness-check.mjs +80 -0
  40. package/releases/0.63.0/core/tree/scripts/loop-gate.mjs +19 -0
  41. package/releases/0.63.0/core/vocabulary.json +14 -0
  42. package/releases/0.63.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  43. package/releases/0.63.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  44. package/releases/0.63.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  45. package/releases/0.63.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  46. package/releases/0.63.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  47. package/releases/0.63.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  48. package/releases/0.63.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
  49. package/releases/0.63.0/surfaces/blockchain/tree/.claude/router.md +6 -0
  50. package/releases/0.63.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
  51. package/releases/0.63.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
  52. package/releases/0.63.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
  53. package/releases/0.63.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
  54. package/releases/0.63.0/surfaces/d1/tree/.claude/patterns.md +8 -0
  55. package/releases/0.63.0/surfaces/d1/tree/CLAUDE.md +2 -0
  56. package/releases/0.63.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
  57. package/releases/0.63.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
  58. package/releases/0.63.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
  59. package/releases/0.63.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
  60. package/releases/0.63.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
  61. package/releases/0.63.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
  62. package/releases/0.63.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
  63. package/releases/0.63.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
  64. package/releases/0.63.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
  65. package/releases/0.63.0/surfaces/db/tree/.claude/graph.md +8 -0
  66. package/releases/0.63.0/surfaces/db/tree/.claude/patterns.md +22 -0
  67. package/releases/0.63.0/surfaces/db/tree/.claude/pipeline.md +13 -0
  68. package/releases/0.63.0/surfaces/db/tree/.claude/retrieval.md +5 -0
  69. package/releases/0.63.0/surfaces/db/tree/.claude/router.md +4 -0
  70. package/releases/0.63.0/surfaces/db/tree/CLAUDE.md +17 -0
  71. package/releases/0.63.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
  72. package/releases/0.63.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
  73. package/releases/0.63.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
  74. package/releases/0.63.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
  75. package/releases/0.63.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  76. package/releases/0.63.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  77. package/releases/0.63.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
  78. package/releases/0.63.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  79. package/releases/0.63.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  80. package/releases/0.63.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  81. package/releases/0.63.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  82. package/releases/0.63.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  83. package/releases/0.63.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  84. package/releases/0.63.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  85. package/releases/0.63.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
  86. package/releases/0.63.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  87. package/releases/0.63.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  88. package/releases/0.63.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  89. package/releases/0.63.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  90. package/releases/0.63.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
  91. package/releases/0.63.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
  92. package/releases/0.63.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
  93. package/releases/0.63.0/surfaces/frontend/tree/.claude/router.md +14 -0
  94. package/releases/0.63.0/surfaces/frontend/tree/CLAUDE.md +5 -0
  95. package/releases/0.63.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  96. package/releases/0.63.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  97. package/releases/0.63.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  98. package/releases/0.63.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  99. package/releases/0.63.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  100. package/releases/0.63.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  101. package/releases/0.63.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  102. package/releases/0.63.0/surfaces/integrations/tree/.claude/graph.md +10 -0
  103. package/releases/0.63.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
  104. package/releases/0.63.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  105. package/releases/0.63.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  106. package/releases/0.63.0/surfaces/integrations/tree/.claude/router.md +6 -0
  107. package/releases/0.63.0/surfaces/integrations/tree/CLAUDE.md +17 -0
  108. package/releases/0.63.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
  109. package/releases/0.63.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
  110. package/releases/0.63.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  111. package/releases/0.63.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  112. package/releases/0.63.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  113. package/releases/0.63.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
  114. package/releases/0.63.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  115. package/releases/0.63.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  116. package/releases/0.63.0/surfaces/money/tree/.claude/graph.md +2 -0
  117. package/releases/0.63.0/surfaces/money/tree/.claude/patterns.md +49 -0
  118. package/releases/0.63.0/surfaces/money/tree/.claude/pipeline.md +2 -0
  119. package/releases/0.63.0/surfaces/money/tree/.claude/retrieval.md +2 -0
  120. package/releases/0.63.0/surfaces/money/tree/CLAUDE.md +11 -0
  121. package/releases/0.63.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  122. package/releases/0.63.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  123. package/releases/0.63.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  124. package/releases/0.63.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  125. package/releases/0.63.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  126. package/releases/0.63.0/surfaces/pii/tree/CLAUDE.md +5 -0
  127. package/releases/0.63.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
  128. package/releases/0.63.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
  129. package/releases/0.63.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
  130. package/releases/0.63.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
  131. package/releases/0.63.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
  132. package/releases/0.63.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
  133. package/releases/0.63.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
  134. package/releases/0.63.0/surfaces/prisma/tree/CLAUDE.md +5 -0
  135. package/releases/0.63.1/core/GAPS.md +125 -0
  136. package/releases/0.63.1/core/answered.json +77 -0
  137. package/releases/0.63.1/core/defaults/tree/.claude/agents/architect.md +1 -0
  138. package/releases/0.63.1/core/defaults/tree/.claude/agents/dba.md +3 -0
  139. package/releases/0.63.1/core/defaults/tree/.claude/agents/devops.md +1 -0
  140. package/releases/0.63.1/core/defaults/tree/.claude/agents/implementer.md +1 -0
  141. package/releases/0.63.1/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  142. package/releases/0.63.1/core/defaults/tree/.claude/agents/qa.md +7 -0
  143. package/releases/0.63.1/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  144. package/releases/0.63.1/core/defaults/tree/.claude/agents/secops.md +3 -0
  145. package/releases/0.63.1/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  146. package/releases/0.63.1/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  147. package/releases/0.63.1/core/tree/.claude/agents/architect.md +133 -0
  148. package/releases/0.63.1/core/tree/.claude/agents/dba.md +119 -0
  149. package/releases/0.63.1/core/tree/.claude/agents/design-reviewer.md +76 -0
  150. package/releases/0.63.1/core/tree/.claude/agents/devops.md +118 -0
  151. package/releases/0.63.1/core/tree/.claude/agents/implementer.md +164 -0
  152. package/releases/0.63.1/core/tree/.claude/agents/integration-tester.md +170 -0
  153. package/releases/0.63.1/core/tree/.claude/agents/planner.md +132 -0
  154. package/releases/0.63.1/core/tree/.claude/agents/qa.md +150 -0
  155. package/releases/0.63.1/core/tree/.claude/agents/reviewer.md +187 -0
  156. package/releases/0.63.1/core/tree/.claude/agents/secops.md +120 -0
  157. package/releases/0.63.1/core/tree/.claude/agents/solidity-auditor.md +126 -0
  158. package/releases/0.63.1/core/tree/.claude/agents/solidity-dev.md +123 -0
  159. package/releases/0.63.1/core/tree/.claude/agents-overview.md +65 -0
  160. package/releases/0.63.1/core/tree/.claude/frontend.md +10 -0
  161. package/releases/0.63.1/core/tree/.claude/graph.md +79 -0
  162. package/releases/0.63.1/core/tree/.claude/patterns.md +251 -0
  163. package/releases/0.63.1/core/tree/.claude/pills/README.md +150 -0
  164. package/releases/0.63.1/core/tree/.claude/pipeline.md +101 -0
  165. package/releases/0.63.1/core/tree/.claude/retrieval.md +103 -0
  166. package/releases/0.63.1/core/tree/.claude/router.md +211 -0
  167. package/releases/0.63.1/core/tree/.claude/templates/integration.md +48 -0
  168. package/releases/0.63.1/core/tree/CLAUDE.md +166 -0
  169. package/releases/0.63.1/core/tree/scripts/cost-watch.mjs +25 -0
  170. package/releases/0.63.1/core/tree/scripts/edit-guard.mjs +24 -0
  171. package/releases/0.63.1/core/tree/scripts/harness-check.mjs +80 -0
  172. package/releases/0.63.1/core/tree/scripts/loop-gate.mjs +19 -0
  173. package/releases/0.63.1/core/vocabulary.json +14 -0
  174. package/releases/0.63.1/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  175. package/releases/0.63.1/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  176. package/releases/0.63.1/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  177. package/releases/0.63.1/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  178. package/releases/0.63.1/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  179. package/releases/0.63.1/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  180. package/releases/0.63.1/surfaces/blockchain/tree/.claude/graph.md +11 -0
  181. package/releases/0.63.1/surfaces/blockchain/tree/.claude/router.md +6 -0
  182. package/releases/0.63.1/surfaces/blockchain/tree/CLAUDE.md +12 -0
  183. package/releases/0.63.1/surfaces/d1/tree/.claude/agents/dba.md +15 -0
  184. package/releases/0.63.1/surfaces/d1/tree/.claude/agents/devops.md +2 -0
  185. package/releases/0.63.1/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
  186. package/releases/0.63.1/surfaces/d1/tree/.claude/patterns.md +8 -0
  187. package/releases/0.63.1/surfaces/d1/tree/CLAUDE.md +2 -0
  188. package/releases/0.63.1/surfaces/db/tree/.claude/agents/architect.md +5 -0
  189. package/releases/0.63.1/surfaces/db/tree/.claude/agents/dba.md +54 -0
  190. package/releases/0.63.1/surfaces/db/tree/.claude/agents/devops.md +5 -0
  191. package/releases/0.63.1/surfaces/db/tree/.claude/agents/implementer.md +2 -0
  192. package/releases/0.63.1/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
  193. package/releases/0.63.1/surfaces/db/tree/.claude/agents/planner.md +2 -0
  194. package/releases/0.63.1/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
  195. package/releases/0.63.1/surfaces/db/tree/.claude/agents/secops.md +2 -0
  196. package/releases/0.63.1/surfaces/db/tree/.claude/agents-overview.md +5 -0
  197. package/releases/0.63.1/surfaces/db/tree/.claude/graph.md +8 -0
  198. package/releases/0.63.1/surfaces/db/tree/.claude/patterns.md +22 -0
  199. package/releases/0.63.1/surfaces/db/tree/.claude/pipeline.md +13 -0
  200. package/releases/0.63.1/surfaces/db/tree/.claude/retrieval.md +5 -0
  201. package/releases/0.63.1/surfaces/db/tree/.claude/router.md +4 -0
  202. package/releases/0.63.1/surfaces/db/tree/CLAUDE.md +17 -0
  203. package/releases/0.63.1/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
  204. package/releases/0.63.1/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
  205. package/releases/0.63.1/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
  206. package/releases/0.63.1/surfaces/drizzle/tree/.claude/patterns.md +7 -0
  207. package/releases/0.63.1/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  208. package/releases/0.63.1/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  209. package/releases/0.63.1/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
  210. package/releases/0.63.1/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  211. package/releases/0.63.1/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  212. package/releases/0.63.1/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  213. package/releases/0.63.1/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  214. package/releases/0.63.1/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  215. package/releases/0.63.1/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  216. package/releases/0.63.1/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  217. package/releases/0.63.1/surfaces/edge-cf/tree/CLAUDE.md +27 -0
  218. package/releases/0.63.1/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  219. package/releases/0.63.1/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  220. package/releases/0.63.1/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  221. package/releases/0.63.1/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  222. package/releases/0.63.1/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
  223. package/releases/0.63.1/surfaces/frontend/tree/.claude/patterns.md +8 -0
  224. package/releases/0.63.1/surfaces/frontend/tree/.claude/retrieval.md +2 -0
  225. package/releases/0.63.1/surfaces/frontend/tree/.claude/router.md +14 -0
  226. package/releases/0.63.1/surfaces/frontend/tree/CLAUDE.md +5 -0
  227. package/releases/0.63.1/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  228. package/releases/0.63.1/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  229. package/releases/0.63.1/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  230. package/releases/0.63.1/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  231. package/releases/0.63.1/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  232. package/releases/0.63.1/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  233. package/releases/0.63.1/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  234. package/releases/0.63.1/surfaces/integrations/tree/.claude/graph.md +10 -0
  235. package/releases/0.63.1/surfaces/integrations/tree/.claude/patterns.md +38 -0
  236. package/releases/0.63.1/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  237. package/releases/0.63.1/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  238. package/releases/0.63.1/surfaces/integrations/tree/.claude/router.md +6 -0
  239. package/releases/0.63.1/surfaces/integrations/tree/CLAUDE.md +17 -0
  240. package/releases/0.63.1/surfaces/money/tree/.claude/agents/architect.md +12 -0
  241. package/releases/0.63.1/surfaces/money/tree/.claude/agents/dba.md +14 -0
  242. package/releases/0.63.1/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  243. package/releases/0.63.1/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  244. package/releases/0.63.1/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  245. package/releases/0.63.1/surfaces/money/tree/.claude/agents/secops.md +5 -0
  246. package/releases/0.63.1/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  247. package/releases/0.63.1/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  248. package/releases/0.63.1/surfaces/money/tree/.claude/graph.md +2 -0
  249. package/releases/0.63.1/surfaces/money/tree/.claude/patterns.md +49 -0
  250. package/releases/0.63.1/surfaces/money/tree/.claude/pipeline.md +2 -0
  251. package/releases/0.63.1/surfaces/money/tree/.claude/retrieval.md +2 -0
  252. package/releases/0.63.1/surfaces/money/tree/CLAUDE.md +11 -0
  253. package/releases/0.63.1/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  254. package/releases/0.63.1/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  255. package/releases/0.63.1/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  256. package/releases/0.63.1/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  257. package/releases/0.63.1/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  258. package/releases/0.63.1/surfaces/pii/tree/CLAUDE.md +5 -0
  259. package/releases/0.63.1/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
  260. package/releases/0.63.1/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
  261. package/releases/0.63.1/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
  262. package/releases/0.63.1/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
  263. package/releases/0.63.1/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
  264. package/releases/0.63.1/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
  265. package/releases/0.63.1/surfaces/prisma/tree/.claude/patterns.md +15 -0
  266. package/releases/0.63.1/surfaces/prisma/tree/CLAUDE.md +5 -0
  267. package/src/commands/sizes.mjs +356 -0
  268. package/src/commands/upgrade.mjs +5 -0
  269. package/src/gate.mjs +138 -4
@@ -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,356 @@
1
+ /**
2
+ * `nina sizes` — each step's forecast against what it landed: the `Size:` line its step file carried before it was
3
+ * built, beside the lines of code its commits added, the rate that measures, and the steps still to land forecast
4
+ * again at it.
5
+ *
6
+ * The architect forecasts each step (`Size: ~N lines in M files`) and splits past the step limit, and until this
7
+ * nothing compared a forecast with what landed. In the project that asked for it, two steps landed about 1.5× their
8
+ * forecasts before a reviewer worked the factor out by hand, and a whole architect run — 28 minutes, 375k tokens —
9
+ * then re-derived the rates from `git diff --numstat` to re-forecast six steps. Measured at each commit, that is a
10
+ * line; and as a detector (`--check`) it speaks when the newest step overran and the steps after it still carry the
11
+ * forecasts made before it landed, so the re-forecast is set off by the data rather than noticed.
12
+ *
13
+ * A step is its file, `.claude/plans/specs/<spec>.step-<n>.md`; its commits are those whose message names the spec and
14
+ * `step <n>`, as the router asks a commit to; what it landed is the lines they added outside the harness's own files.
15
+ *
16
+ * nina sizes [--project <dir>] [--check]
17
+ */
18
+
19
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
20
+ import { spawnSync } from 'node:child_process';
21
+ import { dirname, join, relative, resolve } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ import { HARNESS } from '../paths.mjs';
24
+ import { defaultVocabulary } from '../vocabulary.mjs';
25
+ import { amber, bold, dim, pink } from '../look.mjs';
26
+
27
+ const PACKAGE = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
28
+
29
+ /** Where the architect writes a spec's steps. */
30
+ const SPECS = join('.claude', 'plans', 'specs');
31
+
32
+ /** A landed step past its forecast by more than this sets off the detector. */
33
+ export const OVERRUN = 1.25;
34
+
35
+ /** How many of a spec's newest landed steps its rate is measured over. */
36
+ const RATE_OVER = 5;
37
+
38
+ /** Files a step's commit carries that are not the step's code: the harness's own, and lockfiles. */
39
+ const NOT_CODE = /^(\.claude\/|\.nina\/|CLAUDE\.md$)|(^|\/)(pnpm-lock\.yaml|package-lock\.json|yarn\.lock|bun\.lockb?)$/;
40
+
41
+ /**
42
+ * The forecast a step file states, from its `Size:` line.
43
+ *
44
+ * @param {string} text - A step file.
45
+ * @returns {{lines: number, files: number|null}|null}
46
+ */
47
+ export function forecastIn(text) {
48
+ const m = /\bSize:[*_\s]*~?\s*(\d[\d,]*)\s*lines?\b([^\n]*)/i.exec(text);
49
+ if (!m) return null;
50
+ const files = /^[^.;]*?\bin\s+(\d+)\s+files?\b/i.exec(m[2]);
51
+ return { lines: Number(m[1].replace(/,/g, '')), files: files ? Number(files[1]) : null };
52
+ }
53
+
54
+ /** Runs git in the project, or says it could not. */
55
+ function git(dir, args) {
56
+ const r = spawnSync('git', ['-C', dir, ...args], { encoding: 'utf8', maxBuffer: 256 * 1024 * 1024 });
57
+ return r.status === 0 ? r.stdout : null;
58
+ }
59
+
60
+ /**
61
+ * Many files at many revisions, read by one git process: `<rev>:<path>` → its text, or null where it did not exist.
62
+ * The detector runs on every turn, and a process a version came to most of its second.
63
+ *
64
+ * @param {string} dir
65
+ * @param {string[]} wanted
66
+ * @returns {Map<string, string|null>}
67
+ */
68
+ function blobs(dir, wanted) {
69
+ const keys = [...new Set(wanted)];
70
+ const out = new Map(keys.map((k) => [k, null]));
71
+ if (keys.length === 0) return out;
72
+ const r = spawnSync('git', ['-C', dir, 'cat-file', '--batch'], { input: `${keys.join('\n')}\n`, maxBuffer: 256 * 1024 * 1024 });
73
+ if (r.status !== 0) return out;
74
+ const buffer = r.stdout;
75
+ let at = 0;
76
+ for (const key of keys) {
77
+ const end = buffer.indexOf(10, at);
78
+ if (end === -1) break;
79
+ const header = /^\S+ (\S+) (\d+)$/.exec(buffer.toString('utf8', at, end));
80
+ at = end + 1;
81
+ // `<rev>:<path> missing` carries no body.
82
+ if (!header) continue;
83
+ const size = Number(header[2]);
84
+ if (header[1] === 'blob') out.set(key, buffer.toString('utf8', at, at + size));
85
+ at += size + 1;
86
+ }
87
+ return out;
88
+ }
89
+
90
+ /** Each commit's code: the lines it added to each file that is not the harness's own, read by one git process. */
91
+ function codeOf(dir, hashes) {
92
+ const out = new Map(hashes.map((h) => [h, new Map()]));
93
+ if (hashes.length === 0) return out;
94
+ for (const block of (git(dir, ['show', '--no-renames', '--numstat', '--format=%x00%H', ...hashes]) ?? '').split('\0').filter((b) => b.trim())) {
95
+ const [hash, ...rows] = block.split('\n');
96
+ const code = out.get(hash.trim());
97
+ if (!code) continue;
98
+ for (const row of rows) {
99
+ const [added, , path] = row.split('\t');
100
+ if (!path || added === '-' || NOT_CODE.test(path)) continue;
101
+ code.set(path, (code.get(path) ?? 0) + Number(added));
102
+ }
103
+ }
104
+ return out;
105
+ }
106
+
107
+ /** The commits that changed each of these files, newest first, read by one git process. */
108
+ function changesTo(dir, paths) {
109
+ const out = new Map(paths.map((p) => [p, []]));
110
+ if (paths.length === 0) return out;
111
+ for (const block of (git(dir, ['log', '--no-renames', '--format=%x00%H%x09%ct', '--name-only', 'HEAD', '--', ...paths]) ?? '').split('\0').filter((b) => b.trim())) {
112
+ const [head, ...names] = block.split('\n');
113
+ const [hash, seconds] = head.split('\t');
114
+ for (const name of names) out.get(name.trim())?.push({ hash, t: Number(seconds) * 1000 });
115
+ }
116
+ return out;
117
+ }
118
+
119
+ /** A text as a pattern that matches it literally. */
120
+ const literal = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
121
+
122
+ /**
123
+ * Whether a commit message names this step of this spec: `step 10e2` names neither `step 10e2a` nor `step 10`, and
124
+ * `1.6-spec-c` is not `1.6-spec-c2`.
125
+ */
126
+ export function names(subject, spec, step) {
127
+ const whole = '(?![\\w.-]*\\w)';
128
+ return (
129
+ new RegExp(`(^|[^\\w.-])${literal(spec)}${whole}`, 'i').test(subject) && new RegExp(`\\bstep[\\s-]+${literal(step)}${whole}`, 'i').test(subject)
130
+ );
131
+ }
132
+
133
+ /** The step files in the working tree, as `{path, spec, step}`, in their natural order. */
134
+ function stepFiles(dir) {
135
+ const root = join(dir, SPECS);
136
+ if (!existsSync(root)) return [];
137
+ const out = [];
138
+ for (const entry of readdirSync(root, { recursive: true })) {
139
+ const m = /^(.*)\.step-([^/\\]+)\.md$/.exec(String(entry).split(/[/\\]/).at(-1));
140
+ if (!m) continue;
141
+ out.push({ path: relative(dir, join(root, String(entry))).split(/[/\\]/).join('/'), spec: m[1], step: m[2] });
142
+ }
143
+ return out.sort((a, b) => a.spec.localeCompare(b.spec) || a.step.localeCompare(b.step, undefined, { numeric: true }));
144
+ }
145
+
146
+ /** The middle value. */
147
+ const median = (xs) => {
148
+ const s = [...xs].sort((a, b) => a - b);
149
+ const m = Math.floor(s.length / 2);
150
+ return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
151
+ };
152
+
153
+ /**
154
+ * Every step with a forecast, landed or not: what it was forecast before it was built, and what its commits added.
155
+ *
156
+ * @param {string} dir - The project.
157
+ * @returns {{steps: object[], limit: number}|null} Null outside a git repository.
158
+ */
159
+ export function measure(dir) {
160
+ const profile = (() => {
161
+ try {
162
+ return JSON.parse(readFileSync(join(dir, HARNESS, 'profile.json'), 'utf8'));
163
+ } catch {
164
+ return {};
165
+ }
166
+ })();
167
+ const limit = Number(profile?.vocabulary?.STEP_LINES) || Number(defaultVocabulary(PACKAGE).STEP_LINES) || 400;
168
+ // It runs every turn: a project with no step file does not pay for its history.
169
+ const files = stepFiles(dir);
170
+ if (files.length === 0) return git(dir, ['rev-parse', '--verify', '--quiet', 'HEAD']) === null ? null : { steps: [], limit };
171
+ const log = git(dir, ['log', '--format=%H%x09%ct%x09%cI%x09%s', 'HEAD']);
172
+ if (log === null) return null;
173
+ // Times as instants: an ISO time carries its zone, and two zones compared as text order wrongly.
174
+ const commits = log
175
+ .split('\n')
176
+ .filter(Boolean)
177
+ .map((row) => {
178
+ const [hash, seconds, at, ...rest] = row.split('\t');
179
+ return { hash, t: Number(seconds) * 1000, at, subject: rest.join('\t') };
180
+ })
181
+ .reverse();
182
+ const order = new Map(commits.map((c, i) => [c.hash, i]));
183
+ const named = new Map(files.map((f) => [f.path, commits.filter((c) => names(c.subject, f.spec, f.step))]));
184
+ const code = codeOf(dir, [...new Set([...named.values()].flat().map((c) => c.hash))]);
185
+ const changes = changesTo(dir, files.map((f) => f.path));
186
+ const wanted = [];
187
+ for (const f of files) {
188
+ wanted.push(`HEAD:${f.path}`);
189
+ for (const c of changes.get(f.path)) wanted.push(`${c.hash}:${f.path}`);
190
+ for (const c of named.get(f.path)) wanted.push(`${c.hash}^:${f.path}`, `${c.hash}:${f.path}`);
191
+ }
192
+ const text = blobs(dir, wanted);
193
+ const at = (rev, path) => forecastIn(text.get(`${rev}:${path}`) ?? '');
194
+ // When a forecast was written: the oldest commit of the newest run, before a point in history, that carried it. An
195
+ // edit to the rest of the step file is not a forecast. Null where no commit is found to carry it.
196
+ const writtenAt = (path, before, lines) => {
197
+ let when = null;
198
+ for (const c of changes.get(path)) {
199
+ if ((order.get(c.hash) ?? -1) >= before) continue;
200
+ if (at(c.hash, path)?.lines !== lines) break;
201
+ when = c.t;
202
+ }
203
+ return when;
204
+ };
205
+ const steps = [];
206
+ for (const file of files) {
207
+ const now = forecastIn(readFileSync(join(dir, file.path), 'utf8'));
208
+ const landings = named.get(file.path).filter((c) => code.get(c.hash)?.size > 0);
209
+ if (landings.length === 0) {
210
+ if (!now) continue;
211
+ // Forecast again in the working tree, it is newer than every commit.
212
+ const since = at('HEAD', file.path)?.lines === now.lines ? writtenAt(file.path, Infinity, now.lines) : Infinity;
213
+ steps.push({ ...file, landed: false, forecast: now, since });
214
+ continue;
215
+ }
216
+ // The forecast the step was built to: its file before its first commit, or as that commit wrote it.
217
+ const first = landings[0];
218
+ const before = at(`${first.hash}^`, file.path);
219
+ const forecast = before ?? at(first.hash, file.path);
220
+ if (!forecast) continue;
221
+ const since = (before ? writtenAt(file.path, order.get(first.hash), forecast.lines) : null) ?? first.t;
222
+ const lines = new Map();
223
+ for (const c of landings) for (const [path, added] of code.get(c.hash)) lines.set(path, (lines.get(path) ?? 0) + added);
224
+ const total = [...lines.values()].reduce((a, n) => a + n, 0);
225
+ const last = landings.at(-1);
226
+ // A forecast changed while the step was built — a fix round's spec — is shown, and the step measured against the first.
227
+ const revised = at(last.hash, file.path);
228
+ steps.push({
229
+ ...file,
230
+ landed: true,
231
+ forecast,
232
+ revised: revised && revised.lines !== forecast.lines ? revised : null,
233
+ since,
234
+ lines: total,
235
+ files: lines.size,
236
+ ratio: total / forecast.lines,
237
+ t: last.t,
238
+ at: last.at,
239
+ hash: last.hash,
240
+ });
241
+ }
242
+ return { steps, limit };
243
+ }
244
+
245
+ /**
246
+ * The rate to forecast a step again at: the median actual over forecast of its spec's newest landed steps forecast no
247
+ * earlier than it was. A step forecast before the last re-forecast was measured against the old rates, which that
248
+ * re-forecast already corrected for, and counting it would correct for them twice.
249
+ *
250
+ * @param {object[]} steps
251
+ * @param {{spec: string, since?: string}} step - Its spec, and when its own forecast was written.
252
+ * @returns {{rate: number, over: number}|null}
253
+ */
254
+ export function rateOf(steps, { spec, since }) {
255
+ // A forecast not known to be written when is measured by nothing, rather than by everything.
256
+ if (since === null || since === undefined) return null;
257
+ const landed = steps
258
+ .filter((s) => s.landed && s.spec === spec && s.since >= since)
259
+ .sort((a, b) => a.t - b.t)
260
+ .slice(-RATE_OVER);
261
+ return landed.length ? { rate: median(landed.map((s) => s.ratio)), over: landed.length } : null;
262
+ }
263
+
264
+ /**
265
+ * What the detector says: the newest landed step, when it overran, and the steps of its spec still forecast as they
266
+ * were when it landed.
267
+ *
268
+ * @param {string} dir
269
+ * @param {{steps: object[], limit: number}} measured
270
+ * @returns {{step: object, rate: {rate: number, over: number}, stale: object[]}|null}
271
+ */
272
+ export function overrun({ steps }) {
273
+ const newest = steps.filter((s) => s.landed).sort((a, b) => a.t - b.t).at(-1);
274
+ if (!newest || newest.ratio <= OVERRUN) return null;
275
+ // Forecast no later than the overrun step was, so its rate measures them: forecast again since, they are not stale.
276
+ const stale = steps.filter((s) => !s.landed && s.spec === newest.spec && s.since !== null && s.since <= newest.since);
277
+ return stale.length ? { step: newest, rate: rateOf(steps, stale[0]), stale } : null;
278
+ }
279
+
280
+ const times = (x) => `×${x.toFixed(2)}`;
281
+
282
+ /**
283
+ * `nina sizes`.
284
+ *
285
+ * @param {string[]} argv
286
+ * @returns {Promise<number>}
287
+ */
288
+ export async function sizes(argv) {
289
+ const arg = (name) => (argv.includes(name) ? argv[argv.indexOf(name) + 1] : null);
290
+ const dir = resolve(arg('--project') ?? '.');
291
+ const check = argv.includes('--check');
292
+ if (!existsSync(join(dir, HARNESS, 'profile.json'))) {
293
+ console.error(` no ${HARNESS}/profile.json under ${dir} — run it inside a project, or name one with --project\n`);
294
+ return check ? 0 : 1;
295
+ }
296
+ const measured = measure(dir);
297
+ if (measured === null) {
298
+ if (check) console.log('sizes: current');
299
+ else console.error(' not a git repository, or one with no commit yet: a step is measured by its commits\n');
300
+ return check ? 0 : 1;
301
+ }
302
+ const { steps, limit } = measured;
303
+ if (check) {
304
+ // During `nina upgrade --apply`: a step that overran is the project's history, not the move — and the release that
305
+ // first shipped this detector rolled itself back in the project that asked for it. The move says it after.
306
+ if (process.env.NINA_UPGRADE) {
307
+ console.log('sizes: current');
308
+ return 0;
309
+ }
310
+ const found = overrun(measured);
311
+ if (!found) {
312
+ console.log('sizes: current');
313
+ return 0;
314
+ }
315
+ const { step, rate, stale } = found;
316
+ const again = stale.slice(0, 4).map((s) => {
317
+ const next = Math.round(s.forecast.lines * rate.rate);
318
+ return `${s.step} ~${s.forecast.lines} → ~${next}${next > limit ? ` (past ${limit}: split)` : ''}`;
319
+ });
320
+ console.log(
321
+ `step ${step.step} of ${step.spec} landed ${step.lines} lines against ~${step.forecast.lines} (${times(step.ratio)}); ` +
322
+ `${stale.length} step(s) forecast when it was have not been forecast again — at ${times(rate.rate)}, over ${rate.over} landed step(s) forecast the same way: ` +
323
+ `${again.join(', ')}${stale.length > again.length ? ', …' : ''}`,
324
+ );
325
+ return 1;
326
+ }
327
+ if (steps.length === 0) {
328
+ console.log(` no step file under ${SPECS} states a \`Size:\` line yet\n`);
329
+ return 0;
330
+ }
331
+ for (const spec of [...new Set(steps.map((s) => s.spec))]) {
332
+ const mine = steps.filter((s) => s.spec === spec);
333
+ console.log(`\n ${bold(pink(spec))}${dim(` landed against the forecast it was built to; limit ${limit} lines`)}`);
334
+ for (const s of mine.filter((x) => x.landed).sort((a, b) => a.t - b.t)) {
335
+ const mark = s.ratio > OVERRUN ? amber(times(s.ratio)) : times(s.ratio);
336
+ const files = s.forecast.files === null ? `${s.files} file(s)` : `${s.files} file(s) of ${s.forecast.files}`;
337
+ const revised = s.revised ? `, revised to ~${s.revised.lines} while built` : '';
338
+ console.log(` ${s.step.padEnd(10)} ~${String(s.forecast.lines).padEnd(5)} → ${String(s.lines).padEnd(5)} ${mark} ${dim(`${files}${revised} · ${s.hash.slice(0, 7)} ${s.at.slice(0, 10)}`)}`);
339
+ }
340
+ for (const s of mine.filter((x) => !x.landed)) {
341
+ const rate = rateOf(steps, s);
342
+ if (!rate) {
343
+ console.log(` ${s.step.padEnd(10)} ~${String(s.forecast.lines).padEnd(5)} ${dim('to land — forecast after every step that landed, so none measures it yet')}`);
344
+ continue;
345
+ }
346
+ const next = Math.round(s.forecast.lines * rate.rate);
347
+ console.log(
348
+ ` ${s.step.padEnd(10)} ~${String(s.forecast.lines).padEnd(5)} → ~${String(next).padEnd(5)} ` +
349
+ dim(`to land, at ${times(rate.rate)} over ${rate.over} step(s) forecast as it was`) +
350
+ (next > limit ? amber(` past ${limit}: split`) : ''),
351
+ );
352
+ }
353
+ }
354
+ console.log('');
355
+ return 0;
356
+ }
@@ -631,6 +631,11 @@ export async function upgrade(argv, ctx) {
631
631
  console.log('\n the learning cycle has something to act on — not caused by this move, now visible:');
632
632
  for (const line of lessons.out.trim().split('\n')) console.log(` ${line}`);
633
633
  }
634
+ const forecasts = nina(ctx, target, ['sizes', '--project', target, '--check']);
635
+ if (!forecasts.ok && forecasts.out.trim()) {
636
+ console.log('\n a step landed past its forecast — not caused by this move, now visible:');
637
+ for (const line of forecasts.out.trim().split('\n')) console.log(` ${line}`);
638
+ }
634
639
  // The move is done and the tree is sound, but it is not finished: until these are written
635
640
  // the composed text is quietly missing what they carry, which is the failure this harness
636
641
  // exists to remove. A success line that did not say so would be the same silence.