@xhulz/nina 0.48.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/README.md +4 -0
  2. package/bin/nina.mjs +10 -1
  3. package/package.json +1 -1
  4. package/releases/0.49.0/core/GAPS.md +125 -0
  5. package/releases/0.49.0/core/answered.json +53 -0
  6. package/releases/0.49.0/core/defaults/tree/.claude/agents/architect.md +1 -0
  7. package/releases/0.49.0/core/defaults/tree/.claude/agents/dba.md +3 -0
  8. package/releases/0.49.0/core/defaults/tree/.claude/agents/devops.md +1 -0
  9. package/releases/0.49.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
  10. package/releases/0.49.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  11. package/releases/0.49.0/core/defaults/tree/.claude/agents/qa.md +7 -0
  12. package/releases/0.49.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  13. package/releases/0.49.0/core/defaults/tree/.claude/agents/secops.md +3 -0
  14. package/releases/0.49.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  15. package/releases/0.49.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  16. package/releases/0.49.0/core/tree/.claude/agents/architect.md +129 -0
  17. package/releases/0.49.0/core/tree/.claude/agents/dba.md +117 -0
  18. package/releases/0.49.0/core/tree/.claude/agents/devops.md +118 -0
  19. package/releases/0.49.0/core/tree/.claude/agents/implementer.md +162 -0
  20. package/releases/0.49.0/core/tree/.claude/agents/integration-tester.md +168 -0
  21. package/releases/0.49.0/core/tree/.claude/agents/planner.md +132 -0
  22. package/releases/0.49.0/core/tree/.claude/agents/qa.md +150 -0
  23. package/releases/0.49.0/core/tree/.claude/agents/reviewer.md +184 -0
  24. package/releases/0.49.0/core/tree/.claude/agents/secops.md +120 -0
  25. package/releases/0.49.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
  26. package/releases/0.49.0/core/tree/.claude/agents/solidity-dev.md +123 -0
  27. package/releases/0.49.0/core/tree/.claude/agents-overview.md +64 -0
  28. package/releases/0.49.0/core/tree/.claude/frontend.md +10 -0
  29. package/releases/0.49.0/core/tree/.claude/graph.md +75 -0
  30. package/releases/0.49.0/core/tree/.claude/patterns.md +251 -0
  31. package/releases/0.49.0/core/tree/.claude/pills/README.md +147 -0
  32. package/releases/0.49.0/core/tree/.claude/pipeline.md +101 -0
  33. package/releases/0.49.0/core/tree/.claude/retrieval.md +103 -0
  34. package/releases/0.49.0/core/tree/.claude/router.md +202 -0
  35. package/releases/0.49.0/core/tree/.claude/templates/integration.md +45 -0
  36. package/releases/0.49.0/core/tree/CLAUDE.md +164 -0
  37. package/releases/0.49.0/core/tree/scripts/cost-watch.mjs +25 -0
  38. package/releases/0.49.0/core/tree/scripts/edit-guard.mjs +24 -0
  39. package/releases/0.49.0/core/tree/scripts/harness-check.mjs +71 -0
  40. package/releases/0.49.0/core/tree/scripts/loop-gate.mjs +19 -0
  41. package/releases/0.49.0/core/vocabulary.json +13 -0
  42. package/releases/0.49.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  43. package/releases/0.49.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  44. package/releases/0.49.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  45. package/releases/0.49.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  46. package/releases/0.49.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  47. package/releases/0.49.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  48. package/releases/0.49.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
  49. package/releases/0.49.0/surfaces/blockchain/tree/.claude/router.md +6 -0
  50. package/releases/0.49.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
  51. package/releases/0.49.0/surfaces/d1/tree/.claude/agents/dba.md +13 -0
  52. package/releases/0.49.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
  53. package/releases/0.49.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
  54. package/releases/0.49.0/surfaces/d1/tree/.claude/patterns.md +8 -0
  55. package/releases/0.49.0/surfaces/d1/tree/CLAUDE.md +2 -0
  56. package/releases/0.49.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
  57. package/releases/0.49.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
  58. package/releases/0.49.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
  59. package/releases/0.49.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
  60. package/releases/0.49.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
  61. package/releases/0.49.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
  62. package/releases/0.49.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
  63. package/releases/0.49.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
  64. package/releases/0.49.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
  65. package/releases/0.49.0/surfaces/db/tree/.claude/graph.md +8 -0
  66. package/releases/0.49.0/surfaces/db/tree/.claude/patterns.md +22 -0
  67. package/releases/0.49.0/surfaces/db/tree/.claude/pipeline.md +13 -0
  68. package/releases/0.49.0/surfaces/db/tree/.claude/retrieval.md +5 -0
  69. package/releases/0.49.0/surfaces/db/tree/.claude/router.md +4 -0
  70. package/releases/0.49.0/surfaces/db/tree/CLAUDE.md +17 -0
  71. package/releases/0.49.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
  72. package/releases/0.49.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
  73. package/releases/0.49.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
  74. package/releases/0.49.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
  75. package/releases/0.49.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  76. package/releases/0.49.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  77. package/releases/0.49.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
  78. package/releases/0.49.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  79. package/releases/0.49.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  80. package/releases/0.49.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  81. package/releases/0.49.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  82. package/releases/0.49.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  83. package/releases/0.49.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  84. package/releases/0.49.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  85. package/releases/0.49.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
  86. package/releases/0.49.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  87. package/releases/0.49.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  88. package/releases/0.49.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  89. package/releases/0.49.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  90. package/releases/0.49.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
  91. package/releases/0.49.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
  92. package/releases/0.49.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
  93. package/releases/0.49.0/surfaces/frontend/tree/.claude/router.md +14 -0
  94. package/releases/0.49.0/surfaces/frontend/tree/CLAUDE.md +5 -0
  95. package/releases/0.49.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  96. package/releases/0.49.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  97. package/releases/0.49.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  98. package/releases/0.49.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  99. package/releases/0.49.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  100. package/releases/0.49.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  101. package/releases/0.49.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  102. package/releases/0.49.0/surfaces/integrations/tree/.claude/graph.md +10 -0
  103. package/releases/0.49.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
  104. package/releases/0.49.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  105. package/releases/0.49.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  106. package/releases/0.49.0/surfaces/integrations/tree/.claude/router.md +6 -0
  107. package/releases/0.49.0/surfaces/integrations/tree/CLAUDE.md +17 -0
  108. package/releases/0.49.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
  109. package/releases/0.49.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
  110. package/releases/0.49.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  111. package/releases/0.49.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  112. package/releases/0.49.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  113. package/releases/0.49.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
  114. package/releases/0.49.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  115. package/releases/0.49.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  116. package/releases/0.49.0/surfaces/money/tree/.claude/graph.md +2 -0
  117. package/releases/0.49.0/surfaces/money/tree/.claude/patterns.md +49 -0
  118. package/releases/0.49.0/surfaces/money/tree/.claude/pipeline.md +2 -0
  119. package/releases/0.49.0/surfaces/money/tree/.claude/retrieval.md +2 -0
  120. package/releases/0.49.0/surfaces/money/tree/CLAUDE.md +11 -0
  121. package/releases/0.49.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  122. package/releases/0.49.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  123. package/releases/0.49.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  124. package/releases/0.49.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  125. package/releases/0.49.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  126. package/releases/0.49.0/surfaces/pii/tree/CLAUDE.md +5 -0
  127. package/releases/0.49.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
  128. package/releases/0.49.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
  129. package/releases/0.49.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
  130. package/releases/0.49.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
  131. package/releases/0.49.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
  132. package/releases/0.49.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
  133. package/releases/0.49.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
  134. package/releases/0.49.0/surfaces/prisma/tree/CLAUDE.md +5 -0
  135. package/releases/0.50.0/core/GAPS.md +125 -0
  136. package/releases/0.50.0/core/answered.json +53 -0
  137. package/releases/0.50.0/core/defaults/tree/.claude/agents/architect.md +1 -0
  138. package/releases/0.50.0/core/defaults/tree/.claude/agents/dba.md +3 -0
  139. package/releases/0.50.0/core/defaults/tree/.claude/agents/devops.md +1 -0
  140. package/releases/0.50.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
  141. package/releases/0.50.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  142. package/releases/0.50.0/core/defaults/tree/.claude/agents/qa.md +7 -0
  143. package/releases/0.50.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  144. package/releases/0.50.0/core/defaults/tree/.claude/agents/secops.md +3 -0
  145. package/releases/0.50.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  146. package/releases/0.50.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  147. package/releases/0.50.0/core/tree/.claude/agents/architect.md +129 -0
  148. package/releases/0.50.0/core/tree/.claude/agents/dba.md +117 -0
  149. package/releases/0.50.0/core/tree/.claude/agents/devops.md +118 -0
  150. package/releases/0.50.0/core/tree/.claude/agents/implementer.md +162 -0
  151. package/releases/0.50.0/core/tree/.claude/agents/integration-tester.md +168 -0
  152. package/releases/0.50.0/core/tree/.claude/agents/planner.md +132 -0
  153. package/releases/0.50.0/core/tree/.claude/agents/qa.md +150 -0
  154. package/releases/0.50.0/core/tree/.claude/agents/reviewer.md +184 -0
  155. package/releases/0.50.0/core/tree/.claude/agents/secops.md +120 -0
  156. package/releases/0.50.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
  157. package/releases/0.50.0/core/tree/.claude/agents/solidity-dev.md +123 -0
  158. package/releases/0.50.0/core/tree/.claude/agents-overview.md +64 -0
  159. package/releases/0.50.0/core/tree/.claude/frontend.md +10 -0
  160. package/releases/0.50.0/core/tree/.claude/graph.md +75 -0
  161. package/releases/0.50.0/core/tree/.claude/patterns.md +251 -0
  162. package/releases/0.50.0/core/tree/.claude/pills/README.md +147 -0
  163. package/releases/0.50.0/core/tree/.claude/pipeline.md +101 -0
  164. package/releases/0.50.0/core/tree/.claude/retrieval.md +103 -0
  165. package/releases/0.50.0/core/tree/.claude/router.md +202 -0
  166. package/releases/0.50.0/core/tree/.claude/templates/integration.md +46 -0
  167. package/releases/0.50.0/core/tree/CLAUDE.md +164 -0
  168. package/releases/0.50.0/core/tree/scripts/cost-watch.mjs +25 -0
  169. package/releases/0.50.0/core/tree/scripts/edit-guard.mjs +24 -0
  170. package/releases/0.50.0/core/tree/scripts/harness-check.mjs +71 -0
  171. package/releases/0.50.0/core/tree/scripts/loop-gate.mjs +19 -0
  172. package/releases/0.50.0/core/vocabulary.json +13 -0
  173. package/releases/0.50.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  174. package/releases/0.50.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  175. package/releases/0.50.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  176. package/releases/0.50.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  177. package/releases/0.50.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  178. package/releases/0.50.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  179. package/releases/0.50.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
  180. package/releases/0.50.0/surfaces/blockchain/tree/.claude/router.md +6 -0
  181. package/releases/0.50.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
  182. package/releases/0.50.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
  183. package/releases/0.50.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
  184. package/releases/0.50.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
  185. package/releases/0.50.0/surfaces/d1/tree/.claude/patterns.md +8 -0
  186. package/releases/0.50.0/surfaces/d1/tree/CLAUDE.md +2 -0
  187. package/releases/0.50.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
  188. package/releases/0.50.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
  189. package/releases/0.50.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
  190. package/releases/0.50.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
  191. package/releases/0.50.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
  192. package/releases/0.50.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
  193. package/releases/0.50.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
  194. package/releases/0.50.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
  195. package/releases/0.50.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
  196. package/releases/0.50.0/surfaces/db/tree/.claude/graph.md +8 -0
  197. package/releases/0.50.0/surfaces/db/tree/.claude/patterns.md +22 -0
  198. package/releases/0.50.0/surfaces/db/tree/.claude/pipeline.md +13 -0
  199. package/releases/0.50.0/surfaces/db/tree/.claude/retrieval.md +5 -0
  200. package/releases/0.50.0/surfaces/db/tree/.claude/router.md +4 -0
  201. package/releases/0.50.0/surfaces/db/tree/CLAUDE.md +17 -0
  202. package/releases/0.50.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
  203. package/releases/0.50.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
  204. package/releases/0.50.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
  205. package/releases/0.50.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
  206. package/releases/0.50.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  207. package/releases/0.50.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  208. package/releases/0.50.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
  209. package/releases/0.50.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  210. package/releases/0.50.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  211. package/releases/0.50.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  212. package/releases/0.50.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  213. package/releases/0.50.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  214. package/releases/0.50.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  215. package/releases/0.50.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  216. package/releases/0.50.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
  217. package/releases/0.50.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  218. package/releases/0.50.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  219. package/releases/0.50.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  220. package/releases/0.50.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  221. package/releases/0.50.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
  222. package/releases/0.50.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
  223. package/releases/0.50.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
  224. package/releases/0.50.0/surfaces/frontend/tree/.claude/router.md +14 -0
  225. package/releases/0.50.0/surfaces/frontend/tree/CLAUDE.md +5 -0
  226. package/releases/0.50.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  227. package/releases/0.50.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  228. package/releases/0.50.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  229. package/releases/0.50.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  230. package/releases/0.50.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  231. package/releases/0.50.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  232. package/releases/0.50.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  233. package/releases/0.50.0/surfaces/integrations/tree/.claude/graph.md +10 -0
  234. package/releases/0.50.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
  235. package/releases/0.50.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  236. package/releases/0.50.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  237. package/releases/0.50.0/surfaces/integrations/tree/.claude/router.md +6 -0
  238. package/releases/0.50.0/surfaces/integrations/tree/CLAUDE.md +17 -0
  239. package/releases/0.50.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
  240. package/releases/0.50.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
  241. package/releases/0.50.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  242. package/releases/0.50.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  243. package/releases/0.50.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  244. package/releases/0.50.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
  245. package/releases/0.50.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  246. package/releases/0.50.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  247. package/releases/0.50.0/surfaces/money/tree/.claude/graph.md +2 -0
  248. package/releases/0.50.0/surfaces/money/tree/.claude/patterns.md +49 -0
  249. package/releases/0.50.0/surfaces/money/tree/.claude/pipeline.md +2 -0
  250. package/releases/0.50.0/surfaces/money/tree/.claude/retrieval.md +2 -0
  251. package/releases/0.50.0/surfaces/money/tree/CLAUDE.md +11 -0
  252. package/releases/0.50.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  253. package/releases/0.50.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  254. package/releases/0.50.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  255. package/releases/0.50.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  256. package/releases/0.50.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  257. package/releases/0.50.0/surfaces/pii/tree/CLAUDE.md +5 -0
  258. package/releases/0.50.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
  259. package/releases/0.50.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
  260. package/releases/0.50.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
  261. package/releases/0.50.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
  262. package/releases/0.50.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
  263. package/releases/0.50.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
  264. package/releases/0.50.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
  265. package/releases/0.50.0/surfaces/prisma/tree/CLAUDE.md +5 -0
  266. package/src/commands/check.mjs +146 -1
  267. package/src/commands/gate.mjs +5 -11
  268. package/src/commands/pipeline.mjs +22 -1
  269. package/src/commands/upgrade.mjs +8 -0
  270. package/src/gate.mjs +48 -3
  271. package/src/office.html +1069 -0
  272. package/src/office.mjs +434 -0
  273. package/src/wiring.mjs +11 -1
package/src/office.mjs ADDED
@@ -0,0 +1,434 @@
1
+ /**
2
+ * `nina pipeline --view` — the pipeline as an office seen from above, drawn in a browser page.
3
+ *
4
+ * Each stage of the project's composed graph has a desk, and each agent the loop gate saw launched is someone
5
+ * sitting at one. The gate's ledger is the only thing read, and it holds metadata only, so the office shows who
6
+ * worked, what they answered and the ids of the issues they named — never what anyone wrote:
7
+ *
8
+ * a dispatch someone sits down to work, the folder carried over by whoever handed it on
9
+ * a verdict a check over their head, or a cross when it sends work back
10
+ * a verdict the graph sends to the a question mark over the one who asked, until the owner speaks
11
+ * owner, or the orchestrator asking
12
+ * the gate asking at a cap the phone on the reception's counter rings, the owner's line
13
+ * a pass that ends the work a party
14
+ *
15
+ * The page is served from this machine alone, on the loopback address, for as long as the command runs, and its
16
+ * address is printed for a click — opened in the system's browser only when asked. A Claude Code mod was the first
17
+ * idea, and a probe on 2.1.294 settled it: in the editor's chat panel a mod's hooks run, its session reports no surface
18
+ * to draw on, and no drawing is ever asked for. A mod draws in the terminal and the desktop app only. VS Code's own
19
+ * browser opens a page only for an extension — and an extension is not NINA's to install in anyone's editor — or for
20
+ * a click on a link, beside the conversation once `workbench.browser.openLocalhostLinks` is on (1.140).
21
+ */
22
+
23
+ import { spawn } from 'node:child_process';
24
+ import { readFileSync, statSync } from 'node:fs';
25
+ import { createServer } from 'node:http';
26
+ import { basename, dirname, join } from 'node:path';
27
+ import { fileURLToPath } from 'node:url';
28
+ import { TERMINALS, parseGraph } from './graph.mjs';
29
+ import { readLedger, sessionLedgers } from './gate.mjs';
30
+ import { readProfile } from './paths.mjs';
31
+ import { isLoopBack } from './transcripts.mjs';
32
+ import { shapeOf } from './commands/pipeline.mjs';
33
+
34
+ /** The page, one self-contained file: the room, the sprites and the animation, with nothing fetched from anywhere. */
35
+ const PAGE = join(dirname(fileURLToPath(import.meta.url)), 'office.html');
36
+
37
+ /** The port it asks for first: NINA on a phone's keypad. Taken, any free one. */
38
+ export const OFFICE_PORT = 6462;
39
+
40
+ /**
41
+ * How long after a pass that could end the work the office waits for another dispatch before it celebrates. A pass
42
+ * from qa may go on to devops or secops, and the orchestrator commits before it dispatches either; a pass the graph
43
+ * sends nowhere but `done` is celebrated at once.
44
+ */
45
+ export const PARTY_QUIET_MS = 20_000;
46
+
47
+ /** How far back, when a live client moves to a session already running, beats still play rather than set the room. */
48
+ const SWITCH_GRACE_MS = 10_000;
49
+
50
+ /**
51
+ * How long a run may go without reporting before the office stops counting it as at work: the longest of 227 runs
52
+ * measured across three projects took 108 minutes, and the 99th percentile 73. A run that stopped without a verdict
53
+ * kept every later party away for the rest of its session.
54
+ */
55
+ export const STALE_MS = 3 * 60 * 60 * 1000;
56
+
57
+ /**
58
+ * A dispatch the gate asked about that wrote no `dispatch` entry is taken as refused once the orchestrator launched
59
+ * something else this long after it. A foreground run still going holds the orchestrator, so nothing else goes out;
60
+ * dispatches sent together go out within the same moment.
61
+ */
62
+ const REFUSED_AFTER_MS = 5000;
63
+
64
+ /** How often a live office looks at the ledger. The gate appends to it as each fact happens. */
65
+ const POLL_MS = 700;
66
+
67
+ /** The longest a replay waits between two beats, and the shortest: hours of a session play in minutes. */
68
+ const REPLAY_MAX_GAP_MS = 3000;
69
+ const REPLAY_MIN_GAP_MS = 500;
70
+
71
+ /**
72
+ * A session's ledger as the office's beats, in the order they happened.
73
+ *
74
+ * A foreground dispatch's `dispatch` entry is written when the agent has finished, after its own verdict, so work
75
+ * starts where its `pre` entry stands, joined to the agent by the tool_use id; until that entry is written, the
76
+ * agent is not known yet, and the page binds the next verdict from that desk to it. The work ends — a party — on a
77
+ * pass when nothing since the last dispatch was sent back, something since then could route to `done`, nobody is
78
+ * still at work, and no dispatch follows within `PARTY_QUIET_MS`; at once when the pass routes only to a terminal.
79
+ * A party that waits on the quiet is only beaten once `now` has passed it.
80
+ *
81
+ * @param {object[]} entries - The ledger, oldest first.
82
+ * @param {{edges: {from: string, to: string, token: string}[]}} graph - The project's parsed graph.
83
+ * @param {number} [now] - The time a waiting party is judged against; `Infinity` for a session that has ended.
84
+ * @returns {object[]} Beats, each `{id, at, type, ...}`; the id is stable as the ledger grows.
85
+ */
86
+ export function officeBeats(ledger, graph, now = Date.now()) {
87
+ const entries = ledger.filter((e) => Number.isFinite(Date.parse(e?.at)));
88
+ const time = (e) => Date.parse(e.at);
89
+ const dispatches = new Map();
90
+ for (const e of entries) if (e.k === 'dispatch' && e.id && !dispatches.has(e.id)) dispatches.set(e.id, e);
91
+ const announced = new Set(entries.filter((e) => e.k === 'pre' && e.id).map((e) => e.id));
92
+ const asked = new Set(entries.filter((e) => e.k === 'ask' && e.id).map((e) => e.id));
93
+ const startsWork = (e) => Boolean(e.role) && (e.k === 'pre' || (e.k === 'dispatch' && !(e.id && announced.has(e.id))));
94
+ // What launched a start: its `dispatch` entry, or — until that is written — what its `pre` says of a message.
95
+ const launchOf = (e) => (e.k === 'pre' ? (dispatches.get(e.id) ?? (e.via ? { via: e.via, agent: e.agent ?? null } : undefined)) : e);
96
+ const verdicts = entries.filter((e) => e.k === 'verdict' && e.declared && e.agent && e.role);
97
+
98
+ // Each start, and how it ends: by a verdict from its agent, or from its stage while its agent is not known yet —
99
+ // the page binds them the same way; refused, when the gate asked, no dispatch was written, and the orchestrator
100
+ // went on to something else; or stale, a run that never reported within `STALE_MS`.
101
+ const starts = [];
102
+ entries.forEach((e, i) => {
103
+ if (!startsWork(e)) return;
104
+ const launched = launchOf(e);
105
+ const at = time(e);
106
+ const ranBefore = (agent) => {
107
+ const last = starts.findLast((s) => s.agent === agent && !s.message);
108
+ return last && !verdicts.some((v) => v.agent === agent && time(v) >= last.at && time(v) <= at);
109
+ };
110
+ // A message to an agent still running adds to its run; known for certain from the dispatch, before that from
111
+ // whether the agent it names had reported since it last started.
112
+ const message = launched?.via === 'SendMessage' && (launched.resumed === undefined ? Boolean(launched.agent) && Boolean(ranBefore(launched.agent)) : !launched.resumed);
113
+ const after = e.k === 'pre' && e.id && asked.has(e.id) && !dispatches.has(e.id) ? entries.slice(i + 1).find((x) => x.k === 'pre' && time(x) > at + REFUSED_AFTER_MS) : null;
114
+ starts.push({ i, at, role: e.role, agent: launched?.agent ?? null, message, refusedAt: after ? time(after) : null });
115
+ });
116
+ const endOf = (s) => {
117
+ const v = verdicts.find((x) => time(x) >= s.at && (s.agent ? x.agent === s.agent : x.role === s.role));
118
+ return v ? time(v) : null;
119
+ };
120
+ for (const s of starts) s.end = s.message ? s.at : endOf(s);
121
+ const busyAt = (moment) =>
122
+ starts.some((s) => !s.message && s.at <= moment && moment - s.at < STALE_MS && !(s.refusedAt !== null && s.refusedAt <= moment) && !(s.end !== null && s.end <= moment));
123
+
124
+ const beats = [];
125
+ const seen = new Map();
126
+ /** An id stable as the ledger grows: the tool_use id where there is one, else what the entry says and its count. */
127
+ const idOf = (e) => {
128
+ if (e.id) return `${e.k}:${e.id}`;
129
+ const key = `${e.k}:${e.at}:${e.agent ?? e.role ?? ''}`;
130
+ const nth = (seen.get(key) ?? 0) + 1;
131
+ seen.set(key, nth);
132
+ return `${key}:${nth}`;
133
+ };
134
+ let reported = null;
135
+ let batch = [];
136
+ entries.forEach((e, i) => {
137
+ const at = time(e);
138
+ const id = idOf(e);
139
+ if (startsWork(e)) {
140
+ const start = starts.find((s) => s.i === i);
141
+ const launched = launchOf(e);
142
+ if (!start.message) batch = [];
143
+ // The folder comes from whoever reported last only along an edge of the graph — a diff to its reviewer, a
144
+ // rejection back to its fixer; work the orchestrator hands out on its own plan, it carries itself.
145
+ const handed = reported && graph.edges.some((edge) => edge.from === reported.role && edge.token === reported.verdict && edge.to === e.role);
146
+ // A message is told apart by its id too: an agent that stopped without a declared verdict left nothing on the
147
+ // ledger, so until the dispatch says it was resumed, its resumption looks like a message — and the id that
148
+ // changes when the dispatch lands is what reaches a page that already has the guess.
149
+ // A refusal names the work by that same id, so the page finds whoever sat down for it.
150
+ const work = start.message ? `${id}:message` : id;
151
+ beats.push({ id: work, at, type: 'work', role: e.role, agent: start.agent, resumed: launched?.via === 'SendMessage' && !start.message, message: start.message, from: handed ? reported : null });
152
+ if (start.refusedAt !== null) beats.push({ id: `refused:${id}`, at: start.refusedAt, type: 'refused', role: e.role, work });
153
+ else if (!start.message && (start.end === null || start.end - start.at >= STALE_MS) && now >= start.at + STALE_MS) {
154
+ beats.push({ id: `stale:${id}`, at: start.at + STALE_MS, type: 'stale', role: e.role, agent: start.agent, work: id });
155
+ }
156
+ return;
157
+ }
158
+ if (e.k === 'verdict' && e.declared && e.role && e.agent) {
159
+ const routes = graph.edges.filter((edge) => edge.from === e.role && edge.token === e.verdict);
160
+ const back = isLoopBack(e.verdict);
161
+ beats.push({ id, at, type: 'verdict', role: e.role, agent: e.agent, verdict: e.verdict, back, asks: routes.some((r) => r.to === 'human'), issues: e.issues ?? [] });
162
+ reported = { role: e.role, agent: e.agent, verdict: e.verdict, back };
163
+ batch.push({ back, ends: routes.some((r) => r.to === 'done') });
164
+ // The work ends here when nothing sent back since the last dispatch, something since then could end it, and
165
+ // nobody is still at work: at once when this verdict goes nowhere but a terminal, or once the quiet passes.
166
+ if (back || batch.some((v) => v.back) || !batch.some((v) => v.ends)) return;
167
+ const final = routes.length > 0 && routes.every((r) => TERMINALS.has(r.to));
168
+ const moment = final ? at : at + PARTY_QUIET_MS;
169
+ const later = entries.slice(i + 1);
170
+ const next = later.find((x) => startsWork(x) && !starts.find((s) => s.i === entries.indexOf(x))?.message);
171
+ if (next && time(next) < moment) return;
172
+ // A verdict of the same batch that comes before the moment decides instead: a dba beside the reviewer still
173
+ // has its say, and the batch has one party, not one per pass.
174
+ if (later.some((x) => x.k === 'verdict' && x.declared && x.agent && time(x) <= moment && (!next || time(x) < time(next)))) return;
175
+ if (!final && !next && now < moment) return;
176
+ if (busyAt(moment)) return;
177
+ beats.push({ id: `party:${id}`, at: moment, type: 'party', role: e.role, verdict: e.verdict });
178
+ return;
179
+ }
180
+ if (e.k === 'question') beats.push({ id, at, type: 'question' });
181
+ else if (e.k === 'answered') beats.push({ id, at, type: 'answered' });
182
+ else if (e.k === 'ask') beats.push({ id, at, type: 'cap', role: e.role, source: e.source, token: e.token, round: e.round, max: e.max });
183
+ // A reset written for the model's own question, by ledgers older than the `question` entry, is not the owner.
184
+ else if (e.k === 'reset' && e.why !== 'ask') beats.push({ id, at, type: 'owner' });
185
+ });
186
+ // Stable: beats of one moment keep the ledger's order. A beat that waited — a party, a refusal — sorts where it fell.
187
+ return beats.sort((a, b) => a.at - b.at);
188
+ }
189
+
190
+ /**
191
+ * What the page is told about the room: the project, its stages in the order the line takes them, the stages that
192
+ * run as several agents at once, and what to call the owner.
193
+ *
194
+ * @param {string} target - The project directory.
195
+ * @returns {{project: string, stages: string[], many: string[], owner: string}}
196
+ */
197
+ export function officeRoom(target) {
198
+ const graph = parseGraph(readFileSync(join(target, '.claude', 'graph.md'), 'utf8'));
199
+ const { line, gates } = shapeOf(graph);
200
+ const stages = [...new Set([...line, ...gates.map((g) => g.gate), ...graph.stages])];
201
+ const owner = readProfile(target).profile?.vocabulary?.OWNER;
202
+ return { project: basename(target), stages, many: [...(graph.many ?? new Map()).keys()], owner: typeof owner === 'string' && owner.trim() ? owner.trim() : 'you' };
203
+ }
204
+
205
+ /** One server-sent event. */
206
+ const send = (res, event, data) => res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
207
+
208
+ /**
209
+ * Serves the office: the page at `/`, its beats at `/events` as server-sent events. Live, each client is handed the
210
+ * session's beats so far to set the room by, at once, and then each new one as the gate writes it, following the
211
+ * newest session unless one was named; a replay plays one session from its start, its gaps shortened.
212
+ *
213
+ * @param {{target: string, wanted?: string, replay?: boolean, port?: number}} options
214
+ * @returns {Promise<{url: string, close: () => Promise<void>}>}
215
+ */
216
+ export async function serveOffice({ target, wanted, replay = false, port = OFFICE_PORT }) {
217
+ const room = officeRoom(target);
218
+ const graph = parseGraph(readFileSync(join(target, '.claude', 'graph.md'), 'utf8'));
219
+ // A function, so a `$&` or `$'` in a project's name or its owner's is text and not a replacement pattern.
220
+ const roomJson = JSON.stringify({ ...room, replay }).replace(/</g, '\\u003c');
221
+ const page = readFileSync(PAGE, 'utf8').replace('/*NINA_ROOM*/null', () => roomJson);
222
+ const clients = new Set();
223
+ const timers = new Set();
224
+
225
+ /** The session to show, and its entries; read again only when its ledger changed. */
226
+ let cached = { path: null, size: -1, mtime: 0, entries: [] };
227
+ const current = () => {
228
+ // A session named by the start of its id stays the one picked when a later session's id starts the same way.
229
+ const picked = sessionLedgers(target, wanted).picked ?? (wanted && cached.path ? { session: cached.session, path: cached.path } : null);
230
+ if (!picked) return { session: null, entries: [] };
231
+ let stat;
232
+ try {
233
+ stat = statSync(picked.path);
234
+ } catch {
235
+ return { session: null, entries: [] };
236
+ }
237
+ if (cached.path !== picked.path || cached.size !== stat.size || cached.mtime !== stat.mtimeMs) {
238
+ cached = { session: picked.session, path: picked.path, size: stat.size, mtime: stat.mtimeMs, entries: readLedger(picked.path) };
239
+ }
240
+ return { session: picked.session, entries: cached.entries };
241
+ };
242
+
243
+ // A client is handed what came before it connected at once, to set the room by; a session that starts while it
244
+ // watches, it sees happen. Moving to a session that was already running — two sessions in one project take turns
245
+ // being the newest — what it did before the move sets the room too, rather than playing out again.
246
+ const greet = (client, state) => {
247
+ client.restoring = client.session === undefined;
248
+ client.since = client.restoring ? Number.NEGATIVE_INFINITY : Date.now() - SWITCH_GRACE_MS;
249
+ client.session = state.session;
250
+ client.sent = new Set();
251
+ send(client.res, 'hello', { session: state.session, replay });
252
+ };
253
+
254
+ const tick = () => {
255
+ const state = current();
256
+ for (const client of clients) {
257
+ if (client.replay) continue;
258
+ if (client.session !== state.session) greet(client, state);
259
+ for (const beat of officeBeats(state.entries, graph, Date.now())) {
260
+ if (client.sent.has(beat.id)) continue;
261
+ client.sent.add(beat.id);
262
+ send(client.res, 'beat', { ...beat, instant: client.restoring || beat.at < client.since });
263
+ }
264
+ client.restoring = false;
265
+ }
266
+ };
267
+
268
+ const play = (client) => {
269
+ const state = current();
270
+ greet(client, state);
271
+ const beats = officeBeats(state.entries, graph, Number.POSITIVE_INFINITY);
272
+ // What came before the first dispatch sets the room, at once: a session often opens on many of the owner's
273
+ // messages, and played out they kept the office empty for a minute.
274
+ let i = Math.max(0, beats.findIndex((b) => b.type === 'work'));
275
+ for (const beat of beats.slice(0, i)) send(client.res, 'beat', { ...beat, instant: true });
276
+ const next = () => {
277
+ if (!clients.has(client)) return;
278
+ if (i >= beats.length) {
279
+ send(client.res, 'end', { beats: beats.length });
280
+ return;
281
+ }
282
+ const beat = beats[i];
283
+ send(client.res, 'beat', beat);
284
+ i += 1;
285
+ // The owner speaking moves nobody across the room: it passes quickly. An answer keeps its wait, or the question
286
+ // it answers would not be seen.
287
+ const quick = i < beats.length && beats[i].type === 'owner';
288
+ const gap = i < beats.length && !quick ? beats[i].at - beat.at : 0;
289
+ const timer = setTimeout(() => {
290
+ timers.delete(timer);
291
+ next();
292
+ }, Math.min(REPLAY_MAX_GAP_MS, Math.max(REPLAY_MIN_GAP_MS, gap)));
293
+ timers.add(timer);
294
+ };
295
+ next();
296
+ };
297
+
298
+ const server = createServer((req, res) => {
299
+ // Only this machine's own names for itself: a page elsewhere that rebinds its domain to the loopback address
300
+ // reaches the port, and is refused here. Any port, since a forwarded one — an editor's port forwarding, a
301
+ // container's — reaches this one under another number.
302
+ if (!/^(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/i.test(String(req.headers.host ?? ''))) {
303
+ res.writeHead(403, { 'content-type': 'text/plain' });
304
+ res.end('not from here\n');
305
+ return;
306
+ }
307
+ const path = new URL(req.url ?? '/', 'http://localhost').pathname;
308
+ if (path === '/') {
309
+ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
310
+ res.end(page);
311
+ return;
312
+ }
313
+ if (path === '/events') {
314
+ res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-store', connection: 'keep-alive' });
315
+ res.write(': the office\n\n');
316
+ const client = { res, session: undefined, sent: new Set(), restoring: false, replay };
317
+ clients.add(client);
318
+ req.on('close', () => clients.delete(client));
319
+ if (replay) play(client);
320
+ else tick();
321
+ return;
322
+ }
323
+ res.writeHead(404, { 'content-type': 'text/plain' });
324
+ res.end('not here\n');
325
+ });
326
+
327
+ const poll = setInterval(tick, POLL_MS);
328
+ const listen = (p) =>
329
+ new Promise((resolveListen, reject) => {
330
+ const onError = (error) => {
331
+ server.off('listening', onListening);
332
+ reject(error);
333
+ };
334
+ const onListening = () => {
335
+ server.off('error', onError);
336
+ resolveListen();
337
+ };
338
+ server.once('error', onError);
339
+ server.once('listening', onListening);
340
+ server.listen(p, '127.0.0.1');
341
+ });
342
+ try {
343
+ await listen(port);
344
+ } catch (error) {
345
+ if (error?.code !== 'EADDRINUSE' || port === 0) {
346
+ clearInterval(poll);
347
+ throw error;
348
+ }
349
+ await listen(0);
350
+ }
351
+ const url = `http://127.0.0.1:${server.address().port}`;
352
+ const close = () =>
353
+ new Promise((resolveClose) => {
354
+ clearInterval(poll);
355
+ for (const timer of timers) clearTimeout(timer);
356
+ for (const client of clients) client.res.end();
357
+ clients.clear();
358
+ server.close(() => resolveClose());
359
+ });
360
+ return { url, close };
361
+ }
362
+
363
+ /**
364
+ * Hands an address to the system's opener. One that is not there fails at once, and one with no display to open on
365
+ * exits with an error soon after; one still running after two seconds is taken to have opened it. Windows' explorer
366
+ * answers 1 even when it opened the page.
367
+ *
368
+ * @param {string} url
369
+ * @returns {Promise<boolean>} Whether the browser was opened.
370
+ */
371
+ function openInBrowser(url) {
372
+ const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'explorer' : 'xdg-open';
373
+ return new Promise((resolveOpen) => {
374
+ let child;
375
+ try {
376
+ child = spawn(opener, [url], { stdio: 'ignore', detached: true });
377
+ } catch {
378
+ resolveOpen(false);
379
+ return;
380
+ }
381
+ const timer = setTimeout(() => resolveOpen(true), 2000);
382
+ child.once('error', () => {
383
+ clearTimeout(timer);
384
+ resolveOpen(false);
385
+ });
386
+ child.once('exit', (code) => {
387
+ clearTimeout(timer);
388
+ resolveOpen(code === 0 || process.platform === 'win32');
389
+ });
390
+ child.unref();
391
+ });
392
+ }
393
+
394
+ /**
395
+ * Runs the office until Ctrl+C: serves it, says where for a click, and opens it in the system's browser when asked.
396
+ *
397
+ * @param {{target: string, wanted?: string, replay?: boolean, port?: number, open?: boolean}} options
398
+ * @returns {Promise<number>} Process exit code.
399
+ */
400
+ export async function viewOffice({ target, wanted, replay = false, port = OFFICE_PORT, open = false }) {
401
+ if (wanted) {
402
+ const { picked, why, dir } = sessionLedgers(target, wanted);
403
+ if (!picked) {
404
+ console.error(` ${why ?? 'the gate has no ledger here yet'} (${dir})\n`);
405
+ return 2;
406
+ }
407
+ } else if (replay && !sessionLedgers(target).picked) {
408
+ console.error(' the gate has no ledger here yet, so there is no session to replay\n');
409
+ return 2;
410
+ }
411
+ let office;
412
+ try {
413
+ office = await serveOffice({ target, wanted, replay, port });
414
+ } catch (error) {
415
+ console.error(` the office could not open a port — ${error?.message ?? error}\n`);
416
+ return 1;
417
+ }
418
+ // Listening for Ctrl+C before saying it closes the office: pressed in between, with no listener yet, it would kill
419
+ // the process outright instead.
420
+ const stopped = new Promise((resolveStop) => {
421
+ process.once('SIGINT', resolveStop);
422
+ process.once('SIGTERM', resolveStop);
423
+ });
424
+ const what = replay ? `replaying session ${sessionLedgers(target, wanted).picked.session.slice(0, 8)}` : wanted ? `live, session ${wanted}` : 'live, following the newest session';
425
+ const opened = open ? await openInBrowser(office.url) : false;
426
+ // The address ends its line, with nothing after it, so a terminal makes it a link.
427
+ console.log(` the office · ${basename(target)} · ${what}`);
428
+ if (open && !opened) console.log(' No browser could be opened from here.');
429
+ console.log(` ${opened ? 'Opened in your browser' : 'Click to watch your agents at work'}: ${office.url}`);
430
+ console.log(' Ctrl+C closes the office.\n');
431
+ await stopped;
432
+ await office.close();
433
+ return 0;
434
+ }
package/src/wiring.mjs CHANGED
@@ -127,7 +127,7 @@ const HOOKS = [
127
127
  },
128
128
  { event: 'UserPromptSubmit', script: GATE, timeout: 10, why: "the owner's reply never gives an open loop its round more" },
129
129
  {
130
- event: 'PreToolUse', matcher: 'Agent|Task|SendMessage', script: GATE, timeout: 10, why: 'the caps in .claude/graph.md stay instructions nothing holds',
130
+ event: 'PreToolUse', matcher: 'Agent|Task|SendMessage|AskUserQuestion', script: GATE, timeout: 10, why: 'the caps in .claude/graph.md stay instructions nothing holds, and the office never sees a question asked',
131
131
  crash: (cannot) => ({ systemMessage: `NINA: the loop gate could not start, so the caps in .claude/graph.md are not held — ${cannot}` }),
132
132
  },
133
133
  { event: 'PostToolUse', matcher: 'Agent|Task|SendMessage|AskUserQuestion|SubagentHandback', script: GATE, timeout: 10, why: 'the gate never learns which rounds went out, or that the owner answered' },
@@ -380,6 +380,16 @@ export async function missingWiring(target, shipped) {
380
380
  );
381
381
  }
382
382
  if (covered(merged, h)) continue;
383
+ // Running, but not for every tool it now needs: a hook from before the matcher grew still holds what it held.
384
+ const need = h.matcher ? h.matcher.split('|') : [];
385
+ const unmatched = need.filter((tool) => !running.some((g) => matcherReaches(g.matcher, tool)));
386
+ if (running.length > 0 && unmatched.length > 0 && unmatched.length < need.length) {
387
+ // `wire` leaves a group in the local file, or one matched by a regular expression, to its owner.
388
+ const foreign = running.find((g) => g.from !== '.claude/settings.json' || !plainMatcher(g.matcher));
389
+ const fix = foreign ? `${foreign.from} holds it, so widen it there to ${h.matcher}` : `\`nina wire --apply\` widens it to ${h.matcher}`;
390
+ out.push(`the ${h.event} hook running \`${h.script}${h.mode ? ` ${h.mode}` : ''}\` does not match ${unmatched.join(', ')} — ${fix}`);
391
+ continue;
392
+ }
383
393
  out.push(
384
394
  `.claude/settings.json has no ${h.event} hook${h.matcher ? ` for ${h.matcher}` : ''} running \`${h.script}${h.mode ? ` ${h.mode}` : ''}\` — without it ${h.why}`,
385
395
  );