@xhulz/nina 0.83.0 → 0.85.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 +2 -0
  2. package/bin/nina.mjs +6 -1
  3. package/package.json +1 -1
  4. package/releases/0.84.0/core/GAPS.md +125 -0
  5. package/releases/0.84.0/core/answered.json +134 -0
  6. package/releases/0.84.0/core/defaults/tree/.claude/agents/architect.md +1 -0
  7. package/releases/0.84.0/core/defaults/tree/.claude/agents/dba.md +3 -0
  8. package/releases/0.84.0/core/defaults/tree/.claude/agents/devops.md +1 -0
  9. package/releases/0.84.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
  10. package/releases/0.84.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  11. package/releases/0.84.0/core/defaults/tree/.claude/agents/qa.md +7 -0
  12. package/releases/0.84.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  13. package/releases/0.84.0/core/defaults/tree/.claude/agents/secops.md +3 -0
  14. package/releases/0.84.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  15. package/releases/0.84.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  16. package/releases/0.84.0/core/tiers.json +5 -0
  17. package/releases/0.84.0/core/tree/.claude/agents/architect.md +140 -0
  18. package/releases/0.84.0/core/tree/.claude/agents/dba.md +123 -0
  19. package/releases/0.84.0/core/tree/.claude/agents/design-reviewer.md +82 -0
  20. package/releases/0.84.0/core/tree/.claude/agents/devops.md +122 -0
  21. package/releases/0.84.0/core/tree/.claude/agents/implementer.md +208 -0
  22. package/releases/0.84.0/core/tree/.claude/agents/integration-tester.md +174 -0
  23. package/releases/0.84.0/core/tree/.claude/agents/planner.md +132 -0
  24. package/releases/0.84.0/core/tree/.claude/agents/qa.md +154 -0
  25. package/releases/0.84.0/core/tree/.claude/agents/reviewer.md +191 -0
  26. package/releases/0.84.0/core/tree/.claude/agents/secops.md +124 -0
  27. package/releases/0.84.0/core/tree/.claude/agents/solidity-auditor.md +130 -0
  28. package/releases/0.84.0/core/tree/.claude/agents/solidity-dev.md +127 -0
  29. package/releases/0.84.0/core/tree/.claude/agents-overview.md +65 -0
  30. package/releases/0.84.0/core/tree/.claude/frontend.md +10 -0
  31. package/releases/0.84.0/core/tree/.claude/graph.md +79 -0
  32. package/releases/0.84.0/core/tree/.claude/patterns.md +256 -0
  33. package/releases/0.84.0/core/tree/.claude/pills/README.md +150 -0
  34. package/releases/0.84.0/core/tree/.claude/pipeline.md +101 -0
  35. package/releases/0.84.0/core/tree/.claude/retrieval.md +103 -0
  36. package/releases/0.84.0/core/tree/.claude/router.md +224 -0
  37. package/releases/0.84.0/core/tree/.claude/templates/integration.md +48 -0
  38. package/releases/0.84.0/core/tree/CLAUDE.md +167 -0
  39. package/releases/0.84.0/core/tree/scripts/cost-watch.mjs +27 -0
  40. package/releases/0.84.0/core/tree/scripts/edit-guard.mjs +24 -0
  41. package/releases/0.84.0/core/tree/scripts/harness-check.mjs +84 -0
  42. package/releases/0.84.0/core/tree/scripts/loop-gate.mjs +19 -0
  43. package/releases/0.84.0/core/vocabulary.json +18 -0
  44. package/releases/0.84.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  45. package/releases/0.84.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  46. package/releases/0.84.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  47. package/releases/0.84.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  48. package/releases/0.84.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  49. package/releases/0.84.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  50. package/releases/0.84.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
  51. package/releases/0.84.0/surfaces/blockchain/tree/.claude/router.md +6 -0
  52. package/releases/0.84.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
  53. package/releases/0.84.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
  54. package/releases/0.84.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
  55. package/releases/0.84.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
  56. package/releases/0.84.0/surfaces/d1/tree/.claude/patterns.md +8 -0
  57. package/releases/0.84.0/surfaces/d1/tree/CLAUDE.md +2 -0
  58. package/releases/0.84.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
  59. package/releases/0.84.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
  60. package/releases/0.84.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
  61. package/releases/0.84.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
  62. package/releases/0.84.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
  63. package/releases/0.84.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
  64. package/releases/0.84.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
  65. package/releases/0.84.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
  66. package/releases/0.84.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
  67. package/releases/0.84.0/surfaces/db/tree/.claude/graph.md +8 -0
  68. package/releases/0.84.0/surfaces/db/tree/.claude/patterns.md +22 -0
  69. package/releases/0.84.0/surfaces/db/tree/.claude/pipeline.md +13 -0
  70. package/releases/0.84.0/surfaces/db/tree/.claude/retrieval.md +5 -0
  71. package/releases/0.84.0/surfaces/db/tree/.claude/router.md +4 -0
  72. package/releases/0.84.0/surfaces/db/tree/CLAUDE.md +17 -0
  73. package/releases/0.84.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
  74. package/releases/0.84.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
  75. package/releases/0.84.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
  76. package/releases/0.84.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
  77. package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  78. package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  79. package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
  80. package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  81. package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  82. package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  83. package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  84. package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  85. package/releases/0.84.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  86. package/releases/0.84.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  87. package/releases/0.84.0/surfaces/edge-cf/tree/CLAUDE.md +29 -0
  88. package/releases/0.84.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  89. package/releases/0.84.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  90. package/releases/0.84.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  91. package/releases/0.84.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  92. package/releases/0.84.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
  93. package/releases/0.84.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
  94. package/releases/0.84.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
  95. package/releases/0.84.0/surfaces/frontend/tree/.claude/router.md +14 -0
  96. package/releases/0.84.0/surfaces/frontend/tree/CLAUDE.md +5 -0
  97. package/releases/0.84.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  98. package/releases/0.84.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  99. package/releases/0.84.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  100. package/releases/0.84.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  101. package/releases/0.84.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  102. package/releases/0.84.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  103. package/releases/0.84.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  104. package/releases/0.84.0/surfaces/integrations/tree/.claude/graph.md +10 -0
  105. package/releases/0.84.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
  106. package/releases/0.84.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  107. package/releases/0.84.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  108. package/releases/0.84.0/surfaces/integrations/tree/.claude/router.md +6 -0
  109. package/releases/0.84.0/surfaces/integrations/tree/CLAUDE.md +17 -0
  110. package/releases/0.84.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
  111. package/releases/0.84.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
  112. package/releases/0.84.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  113. package/releases/0.84.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  114. package/releases/0.84.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  115. package/releases/0.84.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
  116. package/releases/0.84.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  117. package/releases/0.84.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  118. package/releases/0.84.0/surfaces/money/tree/.claude/graph.md +2 -0
  119. package/releases/0.84.0/surfaces/money/tree/.claude/patterns.md +49 -0
  120. package/releases/0.84.0/surfaces/money/tree/.claude/pipeline.md +2 -0
  121. package/releases/0.84.0/surfaces/money/tree/.claude/retrieval.md +2 -0
  122. package/releases/0.84.0/surfaces/money/tree/CLAUDE.md +11 -0
  123. package/releases/0.84.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  124. package/releases/0.84.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  125. package/releases/0.84.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  126. package/releases/0.84.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  127. package/releases/0.84.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  128. package/releases/0.84.0/surfaces/pii/tree/CLAUDE.md +5 -0
  129. package/releases/0.84.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
  130. package/releases/0.84.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
  131. package/releases/0.84.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
  132. package/releases/0.84.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
  133. package/releases/0.84.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
  134. package/releases/0.84.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
  135. package/releases/0.84.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
  136. package/releases/0.84.0/surfaces/prisma/tree/CLAUDE.md +5 -0
  137. package/releases/0.85.0/core/GAPS.md +125 -0
  138. package/releases/0.85.0/core/answered.json +137 -0
  139. package/releases/0.85.0/core/defaults/tree/.claude/agents/architect.md +1 -0
  140. package/releases/0.85.0/core/defaults/tree/.claude/agents/dba.md +3 -0
  141. package/releases/0.85.0/core/defaults/tree/.claude/agents/devops.md +1 -0
  142. package/releases/0.85.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
  143. package/releases/0.85.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  144. package/releases/0.85.0/core/defaults/tree/.claude/agents/qa.md +7 -0
  145. package/releases/0.85.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  146. package/releases/0.85.0/core/defaults/tree/.claude/agents/secops.md +3 -0
  147. package/releases/0.85.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  148. package/releases/0.85.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  149. package/releases/0.85.0/core/tiers.json +5 -0
  150. package/releases/0.85.0/core/tree/.claude/agents/architect.md +140 -0
  151. package/releases/0.85.0/core/tree/.claude/agents/dba.md +123 -0
  152. package/releases/0.85.0/core/tree/.claude/agents/design-reviewer.md +82 -0
  153. package/releases/0.85.0/core/tree/.claude/agents/devops.md +122 -0
  154. package/releases/0.85.0/core/tree/.claude/agents/implementer.md +208 -0
  155. package/releases/0.85.0/core/tree/.claude/agents/integration-tester.md +174 -0
  156. package/releases/0.85.0/core/tree/.claude/agents/planner.md +132 -0
  157. package/releases/0.85.0/core/tree/.claude/agents/qa.md +154 -0
  158. package/releases/0.85.0/core/tree/.claude/agents/reviewer.md +191 -0
  159. package/releases/0.85.0/core/tree/.claude/agents/secops.md +124 -0
  160. package/releases/0.85.0/core/tree/.claude/agents/solidity-auditor.md +130 -0
  161. package/releases/0.85.0/core/tree/.claude/agents/solidity-dev.md +127 -0
  162. package/releases/0.85.0/core/tree/.claude/agents-overview.md +65 -0
  163. package/releases/0.85.0/core/tree/.claude/frontend.md +10 -0
  164. package/releases/0.85.0/core/tree/.claude/graph.md +79 -0
  165. package/releases/0.85.0/core/tree/.claude/patterns.md +256 -0
  166. package/releases/0.85.0/core/tree/.claude/pills/README.md +150 -0
  167. package/releases/0.85.0/core/tree/.claude/pipeline.md +101 -0
  168. package/releases/0.85.0/core/tree/.claude/retrieval.md +103 -0
  169. package/releases/0.85.0/core/tree/.claude/router.md +224 -0
  170. package/releases/0.85.0/core/tree/.claude/templates/integration.md +48 -0
  171. package/releases/0.85.0/core/tree/CLAUDE.md +167 -0
  172. package/releases/0.85.0/core/tree/scripts/cost-watch.mjs +27 -0
  173. package/releases/0.85.0/core/tree/scripts/edit-guard.mjs +24 -0
  174. package/releases/0.85.0/core/tree/scripts/harness-check.mjs +84 -0
  175. package/releases/0.85.0/core/tree/scripts/loop-gate.mjs +19 -0
  176. package/releases/0.85.0/core/vocabulary.json +18 -0
  177. package/releases/0.85.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  178. package/releases/0.85.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  179. package/releases/0.85.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  180. package/releases/0.85.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  181. package/releases/0.85.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  182. package/releases/0.85.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  183. package/releases/0.85.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
  184. package/releases/0.85.0/surfaces/blockchain/tree/.claude/router.md +6 -0
  185. package/releases/0.85.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
  186. package/releases/0.85.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
  187. package/releases/0.85.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
  188. package/releases/0.85.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
  189. package/releases/0.85.0/surfaces/d1/tree/.claude/patterns.md +8 -0
  190. package/releases/0.85.0/surfaces/d1/tree/CLAUDE.md +2 -0
  191. package/releases/0.85.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
  192. package/releases/0.85.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
  193. package/releases/0.85.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
  194. package/releases/0.85.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
  195. package/releases/0.85.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
  196. package/releases/0.85.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
  197. package/releases/0.85.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
  198. package/releases/0.85.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
  199. package/releases/0.85.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
  200. package/releases/0.85.0/surfaces/db/tree/.claude/graph.md +8 -0
  201. package/releases/0.85.0/surfaces/db/tree/.claude/patterns.md +22 -0
  202. package/releases/0.85.0/surfaces/db/tree/.claude/pipeline.md +13 -0
  203. package/releases/0.85.0/surfaces/db/tree/.claude/retrieval.md +5 -0
  204. package/releases/0.85.0/surfaces/db/tree/.claude/router.md +4 -0
  205. package/releases/0.85.0/surfaces/db/tree/CLAUDE.md +17 -0
  206. package/releases/0.85.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
  207. package/releases/0.85.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
  208. package/releases/0.85.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
  209. package/releases/0.85.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
  210. package/releases/0.85.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  211. package/releases/0.85.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  212. package/releases/0.85.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
  213. package/releases/0.85.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  214. package/releases/0.85.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  215. package/releases/0.85.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  216. package/releases/0.85.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  217. package/releases/0.85.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  218. package/releases/0.85.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  219. package/releases/0.85.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  220. package/releases/0.85.0/surfaces/edge-cf/tree/CLAUDE.md +29 -0
  221. package/releases/0.85.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  222. package/releases/0.85.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  223. package/releases/0.85.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  224. package/releases/0.85.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  225. package/releases/0.85.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
  226. package/releases/0.85.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
  227. package/releases/0.85.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
  228. package/releases/0.85.0/surfaces/frontend/tree/.claude/router.md +14 -0
  229. package/releases/0.85.0/surfaces/frontend/tree/CLAUDE.md +5 -0
  230. package/releases/0.85.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  231. package/releases/0.85.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  232. package/releases/0.85.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  233. package/releases/0.85.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  234. package/releases/0.85.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  235. package/releases/0.85.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  236. package/releases/0.85.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  237. package/releases/0.85.0/surfaces/integrations/tree/.claude/graph.md +10 -0
  238. package/releases/0.85.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
  239. package/releases/0.85.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  240. package/releases/0.85.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  241. package/releases/0.85.0/surfaces/integrations/tree/.claude/router.md +6 -0
  242. package/releases/0.85.0/surfaces/integrations/tree/CLAUDE.md +17 -0
  243. package/releases/0.85.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
  244. package/releases/0.85.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
  245. package/releases/0.85.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  246. package/releases/0.85.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  247. package/releases/0.85.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  248. package/releases/0.85.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
  249. package/releases/0.85.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  250. package/releases/0.85.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  251. package/releases/0.85.0/surfaces/money/tree/.claude/graph.md +2 -0
  252. package/releases/0.85.0/surfaces/money/tree/.claude/patterns.md +49 -0
  253. package/releases/0.85.0/surfaces/money/tree/.claude/pipeline.md +2 -0
  254. package/releases/0.85.0/surfaces/money/tree/.claude/retrieval.md +2 -0
  255. package/releases/0.85.0/surfaces/money/tree/CLAUDE.md +11 -0
  256. package/releases/0.85.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  257. package/releases/0.85.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  258. package/releases/0.85.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  259. package/releases/0.85.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  260. package/releases/0.85.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  261. package/releases/0.85.0/surfaces/pii/tree/CLAUDE.md +5 -0
  262. package/releases/0.85.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
  263. package/releases/0.85.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
  264. package/releases/0.85.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
  265. package/releases/0.85.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
  266. package/releases/0.85.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
  267. package/releases/0.85.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
  268. package/releases/0.85.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
  269. package/releases/0.85.0/surfaces/prisma/tree/CLAUDE.md +5 -0
  270. package/src/commands/dashboard.mjs +31 -6
  271. package/src/dashboard.mjs +11 -8
  272. package/src/demo.mjs +434 -0
  273. package/src/office.mjs +42 -9
package/src/demo.mjs ADDED
@@ -0,0 +1,434 @@
1
+ /**
2
+ * `nina dashboard --demo` — the dashboard on a project that does not exist: for showing someone how NINA works without
3
+ * opening a real project, running Claude or spending a token.
4
+ *
5
+ * A notes app is composed into a scratch directory at the release this package carries, and given three weeks of
6
+ * invented history: each session's transcripts, written the way Claude Code writes them, and today's session through
7
+ * the loop gate's own hook handler. The snapshot then reads the transcripts into the store as it reads any project's.
8
+ * Nothing here writes a ledger entry or a record of its own: a demo built from hand-written JSON would drift from the
9
+ * format the first time either changed, and the test that runs it would still pass (one mechanism per fact). Every
10
+ * word in it — the features, the issues, the reports — is invented, and nothing is read from a real project.
11
+ *
12
+ * `NINA_DATA` and `NINA_TRANSCRIPTS` point into the scratch directory for the rest of the process, so the store, the
13
+ * gate's ledgers and the transcripts every tab and the office read are the demo's; the command's process ends with the
14
+ * demo, which removes the directory.
15
+ */
16
+
17
+ import { mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
18
+ import { tmpdir } from 'node:os';
19
+ import { join } from 'node:path';
20
+ import { handle } from './gate.mjs';
21
+ import { slugFor } from './paths.mjs';
22
+ import { byVersion, composeProject } from './commands/compose.mjs';
23
+ import { frontmatter } from './commands/pills.mjs';
24
+ import { snapshot } from './commands/snapshot.mjs';
25
+
26
+ /** The project's name, which the dashboard and the office show. */
27
+ export const DEMO_NAME = 'notes';
28
+
29
+ /** The stages that run beside the reviewer on a designed change's diff: the database gate, since the notes have a table. */
30
+ const BESIDE_REVIEW = ['dba'];
31
+
32
+ /** A minute. */
33
+ const MIN = 60_000;
34
+
35
+ /**
36
+ * How many times faster than it happened the demo's office plays today's session. Squeezed as a replay of a real
37
+ * session is, each agent sat at its desk for three seconds at most, up again before anyone watching had seen it work;
38
+ * at this pace each of today's runs, lasting seven to thirteen minutes, sits there for 20 to 40.
39
+ */
40
+ export const DEMO_PACE = 20;
41
+
42
+ /** How long each of today's runs stays at its desk in the demo's office, in ms: at least, and at most. */
43
+ export const DEMO_AT_DESK = [20_000, 40_000];
44
+
45
+ /** How the demo's office plays (`nina dashboard --demo`): its one session, over and over, at `DEMO_PACE`. */
46
+ export const DEMO_OFFICE = { replay: true, loop: true, scale: 1 / DEMO_PACE };
47
+
48
+ /** A day. */
49
+ const DAY = 86_400_000;
50
+
51
+ /**
52
+ * The release the demo composes: the one this package carries as its own version, or its newest.
53
+ *
54
+ * @param {string} root - The NINA install directory.
55
+ * @returns {string}
56
+ */
57
+ function ownRelease(root) {
58
+ const version = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version;
59
+ const releases = readdirSync(join(root, 'releases')).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
60
+ if (releases.includes(version)) return version;
61
+ return releases.sort(byVersion).at(-1);
62
+ }
63
+
64
+ /** The demo's profile: a notes app with a database and a web page, its vocabulary invented. */
65
+ const profileAt = (core) => ({
66
+ core,
67
+ surfaces: ['db', 'frontend'],
68
+ vocabulary: {
69
+ PROJECT: 'Notes',
70
+ OWNER: 'the maintainer',
71
+ API_DIR: 'src/server',
72
+ APP_DIR: 'src/web',
73
+ PKG_SCOPE: '@notes',
74
+ EMITTING_PKGS: '`core`',
75
+ DB_PKG_NAME: '@notes/db',
76
+ TENANT_KEY: 'workspaceId',
77
+ SECRETS_LOCAL: '.dev.vars',
78
+ SECRETS_PROD: "the host's secret store",
79
+ VISUAL_SERVE: 'pnpm dev',
80
+ },
81
+ });
82
+
83
+ /**
84
+ * Two lessons the pipeline wrote: the implementer's, ten days ago, after which the reviewer sent its work back far less
85
+ * often, and the architect's, six days ago. Where each lives, when it was written, and its text.
86
+ *
87
+ * @param {number} now
88
+ * @returns {{path: string, since: number, text: string}[]}
89
+ */
90
+ const PILLS = (now) => {
91
+ const ago = (n) => {
92
+ const d = new Date(now - n * DAY);
93
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
94
+ };
95
+ return [
96
+ {
97
+ path: 'implementer/a-call-that-sets-something-sets-it.md',
98
+ since: now - 10 * DAY,
99
+ text: `---
100
+ id: implementer-a-call-that-sets-something-sets-it
101
+ applies_to: [implementer]
102
+ severity: medium
103
+ status: active
104
+ date: ${ago(10)}
105
+ last_seen: ${ago(3)}
106
+ occurrences: 2
107
+ trigger: writing a call that sets a flag, a state or a field on a stored row
108
+ ---
109
+ **What went wrong:** an endpoint meant to archive a note flipped the flag, so a second call unarchived it.
110
+ **Rule:** a call that sets something sets it: the same call twice leaves the row as once.
111
+ **Why:** a retry, a double click and a replayed request all call twice.
112
+ **How to apply:** write a test that calls it twice and reads the row after.
113
+ source: an invented review, for the demo
114
+ `,
115
+ },
116
+ {
117
+ path: 'architect/say-what-a-second-call-does.md',
118
+ since: now - 6 * DAY,
119
+ text: `---
120
+ id: architect-say-what-a-second-call-does
121
+ applies_to: [architect]
122
+ severity: low
123
+ status: active
124
+ date: ${ago(6)}
125
+ occurrences: 1
126
+ trigger: specifying an endpoint that changes a stored row
127
+ ---
128
+ **What went wrong:** a spec left open what a second call does, and the design-reviewer sent it back.
129
+ **Rule:** a spec that changes a row says what the same call does the second time.
130
+ **Why:** the implementer picks one if the spec does not, and the review finds out a round later.
131
+ **How to apply:** one line under the endpoint: a second call is a no-op, an error, or a new row.
132
+ source: an invented review, for the demo
133
+ `,
134
+ },
135
+ ];
136
+ };
137
+
138
+ /** The features of three weeks of invented work, one a task: what the task is called, and what its steps call it. */
139
+ const FEATURES = [
140
+ ['Add tags to notes', 'tags'],
141
+ ['Search notes by title', 'title search'],
142
+ ['Share a note by link', 'share link'],
143
+ ['Paginate the notes list', 'pagination'],
144
+ ['Export notes as Markdown', 'Markdown export'],
145
+ ['Pin a note to the top', 'pinning'],
146
+ ['Undo a deleted note', 'undo'],
147
+ ['Show a word count', 'word count'],
148
+ ['Sort notes by date', 'date sort'],
149
+ ['Rename a notebook', 'notebook rename'],
150
+ ['Color a notebook', 'notebook colors'],
151
+ ['Move a note between notebooks', 'note move'],
152
+ ['Restore an old version of a note', 'versions'],
153
+ ['Limit a note to 100 KB', 'size limit'],
154
+ ];
155
+
156
+ /** How long each stage works, in minutes, at least and at most. */
157
+ const WORKS = {
158
+ planner: [3, 5],
159
+ architect: [8, 14],
160
+ 'design-reviewer': [4, 7],
161
+ implementer: [10, 20],
162
+ reviewer: [4, 7],
163
+ dba: [3, 5],
164
+ qa: [3, 6],
165
+ };
166
+
167
+ /** What each stage does in its own transcript, as the office says it: a shell command's description, or a file it works on. */
168
+ const TOOLS = {
169
+ planner: [['Read', { file_path: '.claude/architecture.md' }], ['Grep', { pattern: 'notes' }], ['Write', { file_path: '.claude/plans/plan.md' }]],
170
+ architect: [['Read', { file_path: '.claude/architecture.md' }], ['Read', { file_path: 'src/server/notes.ts' }], ['Bash', { description: 'Check how the notes table is indexed' }], ['Write', { file_path: '.claude/plans/specs/spec.md' }]],
171
+ 'design-reviewer': [['Read', { file_path: '.claude/plans/specs/spec.md' }], ['Bash', { description: 'Try the spec on a second call' }]],
172
+ implementer: [['Read', { file_path: '.claude/plans/specs/spec.md' }], ['Edit', { file_path: 'src/server/notes.ts' }], ['Edit', { file_path: 'src/server/notes.test.ts' }], ['Bash', { description: 'Run the typecheck' }], ['Bash', { description: 'Run the mutations on a copy' }]],
173
+ reviewer: [['Bash', { description: 'Show the diff' }], ['Read', { file_path: 'src/server/notes.ts' }], ['Bash', { description: 'Run the lint' }]],
174
+ dba: [['Read', { file_path: 'src/server/db/schema.ts' }], ['Bash', { description: 'Read the migration the change adds' }]],
175
+ qa: [['Bash', { description: 'Run the notes tests' }], ['Bash', { description: 'Run the whole suite once' }]],
176
+ };
177
+
178
+ /** Tokens a run writes, in thousands, at least and at most; what it reads back from its cache is ten to forty times that. */
179
+ const WRITES = { planner: [2, 5], architect: [12, 30], 'design-reviewer': [4, 9], implementer: [10, 25], reviewer: [5, 12], dba: [2, 5], qa: [1, 3] };
180
+
181
+ /**
182
+ * A small generator of numbers that is the same on every run, so the demo, and the test that reads it, never vary.
183
+ *
184
+ * @param {number} seed
185
+ * @returns {() => number} A number in [0, 1).
186
+ */
187
+ function seeded(seed) {
188
+ let s = seed >>> 0;
189
+ return () => {
190
+ s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
191
+ return s / 2 ** 32;
192
+ };
193
+ }
194
+
195
+ /**
196
+ * The runs of one task, from `start`: who works, on what words, when, and what each reports. `sendBack` says which
197
+ * gate sends the work back once; `design` whether it goes through the planner, the architect and the design-reviewer.
198
+ *
199
+ * @returns {{runs: object[], end: number}}
200
+ */
201
+ function taskRuns(random, start, { name, noun, design, sendBack = null, between = 0.2, works = null }) {
202
+ const runs = [];
203
+ const slug = noun.toLowerCase().replace(/\s+/g, '-');
204
+ const span = (role) => {
205
+ if (works) return works();
206
+ const [lo, hi] = WORKS[role];
207
+ return Math.round((lo + random() * (hi - lo)) * MIN);
208
+ };
209
+ let at = start;
210
+ const run = (role, desc, report, { from = at, beside = false } = {}) => {
211
+ const r = { role, desc, report, start: from, end: from + span(role) };
212
+ runs.push(r);
213
+ if (!beside) at = r.end + between * MIN;
214
+ return r;
215
+ };
216
+ if (design) {
217
+ run('planner', name, 'VERDICT: PLAN-READY\n\nTwo steps: the endpoint, then the list.');
218
+ run('architect', `Spec the ${noun}`, 'VERDICT: SPEC-READY\n\nThe design is the question: the design-reviewer reads it first.');
219
+ if (sendBack === 'design-reviewer') {
220
+ run('design-reviewer', `Check the ${noun} design`, `VERDICT: REJECTED\nISSUES: ${slug}-second-call-undefined\n\nThe spec does not say what a second call does.`);
221
+ run('architect', `Correct the ${noun} spec`, 'VERDICT: SPEC-READY\n\nA second call is now a no-op, and a case reads it.');
222
+ }
223
+ run('design-reviewer', `Check the ${noun} design`, 'VERDICT: APPROVED\n\nThe design holds.');
224
+ }
225
+ run('implementer', design ? `Build the ${noun}` : name, 'VERDICT: DIFF-READY\n\nThe change and its tests, each with the mutation that turns it red.');
226
+ // The reviewer and the gates beside it go out together on the diff, and qa once each has approved.
227
+ const review = (round) => {
228
+ const from = at;
229
+ const sent = sendBack === 'reviewer' && round === 1;
230
+ const reviewer = run('reviewer', round === 1 ? `Review the ${noun} change` : `Review the ${noun} fix`, sent ? `VERDICT: REJECTED\nISSUES: ${slug}-not-idempotent\n\nA second call undoes the first.` : 'VERDICT: APPROVED\n\nThe diff matches its spec.', { from, beside: true });
231
+ const gates = round === 1 && design ? BESIDE_REVIEW.map((role) => run(role, `Check the ${noun} migration`, 'VERDICT: APPROVED\n\nThe migration is additive and indexed.', { from, beside: true })) : [];
232
+ at = Math.max(reviewer.end, ...gates.map((g) => g.end)) + between * MIN;
233
+ return sent;
234
+ };
235
+ if (review(1)) {
236
+ run('implementer', `Fix the ${noun} second call`, 'VERDICT: DIFF-READY\n\nA second call is a no-op now, and a test calls it twice.');
237
+ review(2);
238
+ }
239
+ if (sendBack === 'qa') {
240
+ run('qa', `Test the ${noun}`, `VERDICT: FAIL\nISSUES: ${slug}-test-order\n\nOne test reads a row another wrote.`);
241
+ run('implementer', `Fix the ${noun} test order`, 'VERDICT: DIFF-READY\n\nEach test makes its own rows.');
242
+ }
243
+ run('qa', `Test the ${noun}`, 'VERDICT: PASS\n\nThe tests the diff touches pass, and the suite once.');
244
+ return { runs, end: at };
245
+ }
246
+
247
+ /**
248
+ * Three weeks of invented sessions, and today's: each a list of runs. Today's is the one the office plays. Its first
249
+ * task goes the whole way, sent back once by the reviewer while the database gate approves beside it; its second
250
+ * starts while the first one's qa is at work, so two pieces of work are on the floor at once.
251
+ *
252
+ * @param {number} now - The moment the demo is made; today's last report came in three minutes before it.
253
+ * @returns {{id: string, prompt: string, at: number, runs: object[], asked?: [number, number]}[]}
254
+ */
255
+ export function demoSessions(now) {
256
+ const random = seeded(2026);
257
+ const sessions = [];
258
+ const days = [21, 20, 19, 18, 17, 15, 14, 13, 12, 11, 9, 8, 7, 6, 5, 3, 2, 1];
259
+ let feature = 0;
260
+ for (const back of days) {
261
+ const morning = new Date(now - back * DAY);
262
+ morning.setHours(9 + Math.floor(random() * 3), Math.floor(random() * 60), 0, 0);
263
+ const runs = [];
264
+ let at = morning.getTime();
265
+ for (let n = 0; n < 1 + Math.floor(random() * 3); n += 1) {
266
+ const [name, noun] = FEATURES[feature % FEATURES.length];
267
+ feature += 1;
268
+ // The reviewer sent the implementer's work back more than half the time before its lesson, ten days ago, and seldom after.
269
+ const roll = random();
270
+ const sendBack = roll < (back > 10 ? 0.6 : 0.1) ? 'reviewer' : roll > 0.92 ? 'qa' : roll > 0.85 ? 'design-reviewer' : null;
271
+ const task = taskRuns(random, at, { name, noun, design: random() < 0.7 || sendBack === 'design-reviewer', sendBack });
272
+ runs.push(...task.runs);
273
+ at = task.end + (5 + random() * 20) * MIN;
274
+ }
275
+ sessions.push({ id: sessionId(random), prompt: 'Next feature, please.', at: morning.getTime() - MIN, runs });
276
+ }
277
+ // Today's: the archive, sent back once, and the list's dates, begun while the archive's tests run. Each run lasts what
278
+ // sits it at its desk for 20 to 40 seconds of the office's replay. Laid out from zero, then moved so its last report
279
+ // came in three minutes ago: nothing in it happens after `now`.
280
+ const desk = seeded(13);
281
+ const works = () => Math.round((DEMO_AT_DESK[0] + desk() * (DEMO_AT_DESK[1] - DEMO_AT_DESK[0])) * DEMO_PACE);
282
+ const archive = taskRuns(seeded(7), MIN, { name: 'Add archiving to notes', noun: 'archive', design: true, sendBack: 'reviewer', works });
283
+ const qa = archive.runs.at(-1);
284
+ const dates = taskRuns(seeded(11), qa.start + 1.5 * MIN, { name: 'Fix the dates on the notes list', noun: 'list dates', design: false, works });
285
+ const runs = [...archive.runs, ...dates.runs].sort((a, b) => a.start - b.start);
286
+ const shift = now - 3 * MIN - Math.max(...runs.map((r) => r.end));
287
+ for (const r of runs) Object.assign(r, { start: r.start + shift, end: r.end + shift });
288
+ const planned = archive.runs[0];
289
+ sessions.push({
290
+ id: sessionId(random),
291
+ prompt: 'Add archiving to notes, and fix the dates on the list.',
292
+ at: shift,
293
+ runs,
294
+ // The orchestrator asks the owner which of the planner's two steps goes first, and is answered before the architect goes.
295
+ asked: [planned.end + 0.05 * MIN, planned.end + 0.15 * MIN],
296
+ });
297
+ return sessions;
298
+ }
299
+
300
+ /** A session's id, shaped as Claude Code's are. */
301
+ function sessionId(random) {
302
+ const hex = (n) => Array.from({ length: n }, () => Math.floor(random() * 16).toString(16)).join('');
303
+ return `${hex(8)}-${hex(4)}-4${hex(3)}-a${hex(3)}-${hex(12)}`;
304
+ }
305
+
306
+ /** An agent's id, as hexadecimal as Claude Code's, which the transcript parser reads by that. */
307
+ const agentId = (random) => `a${Array.from({ length: 16 }, () => Math.floor(random() * 16).toString(16)).join('')}`;
308
+
309
+ /** What a stage runs on: the model and effort its composed spec names. */
310
+ function ranOn(project, role) {
311
+ const fields = frontmatter(readFileSync(join(project, '.claude', 'agents', `${role}.md`), 'utf8')) ?? {};
312
+ return { model: fields.model ?? null, effort: fields.effort ?? null };
313
+ }
314
+
315
+ /**
316
+ * Writes one session as Claude Code writes it: the orchestrator's transcript, with each dispatch launched in the
317
+ * background and its notification when it ends, and each run's own transcript under `subagents/`, with its tool
318
+ * calls, what each message spent, and the report it handed back.
319
+ */
320
+ function writeTranscripts(dir, project, session, random, pills) {
321
+ const stamp = (t) => new Date(t).toISOString();
322
+ const main = [];
323
+ const row = (fields) => main.push({ ...fields, cwd: project, sessionId: session.id, version: '2.1.312' });
324
+ row({ type: 'user', uuid: `${session.id}-u0`, timestamp: stamp(session.at), message: { role: 'user', content: session.prompt } });
325
+ const subagents = join(dir, session.id, 'subagents');
326
+ mkdirSync(subagents, { recursive: true });
327
+ for (const [i, run] of session.runs.entries()) {
328
+ run.toolUseId = `toolu_${session.id.slice(0, 8)}${String(i).padStart(3, '0')}`;
329
+ run.agent = agentId(random);
330
+ row({ type: 'assistant', uuid: `${run.toolUseId}-d`, timestamp: stamp(run.start), message: { role: 'assistant', content: [{ type: 'tool_use', id: run.toolUseId, name: 'Agent', input: { subagent_type: run.role, description: run.desc, prompt: `${run.desc}.`, run_in_background: true } }] } });
331
+ row({ type: 'user', uuid: `${run.toolUseId}-l`, timestamp: stamp(run.start + 1000), message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: run.toolUseId, content: `Async agent launched successfully.\nagentId: ${run.agent}` }] } });
332
+ row({
333
+ type: 'user',
334
+ uuid: `${run.toolUseId}-n`,
335
+ timestamp: stamp(run.end + 2000),
336
+ message: { role: 'user', content: `<task-notification>\n<task-id>${run.agent}</task-id>\n<tool-use-id>${run.toolUseId}</tool-use-id>\n<status>completed</status>\n<result>${run.report}</result>\n<duration_ms>${run.end - run.start}</duration_ms>\n</task-notification>` },
337
+ });
338
+
339
+ const { model, effort } = ranOn(project, run.role);
340
+ // Each stage opens the lessons its directory held when it was dispatched, as its spec tells it to.
341
+ const lessons = pills.filter((p) => p.path.startsWith(`${run.role}/`) && p.since <= run.start).map((p) => ['Read', { file_path: join(project, '.claude', 'pills', p.path) }]);
342
+ const tools = [...lessons, ...TOOLS[run.role]];
343
+ const [lo, hi] = WRITES[run.role];
344
+ const written = Math.round((lo + random() * (hi - lo)) * 1000);
345
+ const steps = tools.length + 1;
346
+ const own = [{ type: 'user', uuid: `${run.agent}-p`, timestamp: stamp(run.start), agentId: run.agent, sessionId: session.id, message: { role: 'user', content: `${run.desc}.` } }];
347
+ const said = (k, content, share) => {
348
+ const usage = { input_tokens: 4 + k, output_tokens: Math.round(written * share), cache_read_input_tokens: Math.round(written * (8 + random() * 24) * share), cache_creation_input_tokens: Math.round(written * (0.5 + random()) * share) };
349
+ own.push({ type: 'assistant', uuid: `${run.agent}-m${k}`, timestamp: stamp(run.start + ((run.end - run.start) * (k + 1)) / (steps + 1)), agentId: run.agent, sessionId: session.id, effort, message: { id: `msg_${run.agent}${k}`, role: 'assistant', model, usage, content } });
350
+ };
351
+ tools.forEach(([name, input], k) => {
352
+ const lines = name === 'Edit' ? { old_string: 'export {};', new_string: Array.from({ length: 12 + Math.floor(random() * 40) }, (_, n) => `// line ${n + 1}`).join('\n') } : name === 'Write' ? { content: Array.from({ length: 20 + Math.floor(random() * 30) }, (_, n) => `line ${n + 1}`).join('\n') } : {};
353
+ said(k, [{ type: 'tool_use', id: `${run.agent}-t${k}`, name, input: { ...input, ...lines } }], 1 / steps);
354
+ });
355
+ said(tools.length, [{ type: 'tool_use', id: `${run.agent}-h`, name: 'SubagentHandback', input: { message: run.report } }], 1 / steps);
356
+ writeFileSync(join(subagents, `agent-${run.agent}.jsonl`), `${own.map((r) => JSON.stringify(r)).join('\n')}\n`);
357
+ writeFileSync(join(subagents, `agent-${run.agent}.meta.json`), JSON.stringify({ agentType: run.role, toolUseId: run.toolUseId }));
358
+ }
359
+ writeFileSync(join(dir, `${session.id}.jsonl`), `${main.map((r) => JSON.stringify(r)).join('\n')}\n`);
360
+ }
361
+
362
+ /**
363
+ * Today's session through the loop gate, event by event, each at its moment: what its hooks would have been handed.
364
+ * The ledger it writes is the one the office plays.
365
+ */
366
+ function throughTheGate(project, session, subagents) {
367
+ const events = [{ at: session.at, input: { hook_event_name: 'UserPromptSubmit', prompt: session.prompt } }];
368
+ if (session.asked) {
369
+ events.push({ at: session.asked[0], input: { hook_event_name: 'PreToolUse', tool_name: 'AskUserQuestion', tool_input: {} } });
370
+ events.push({ at: session.asked[1], input: { hook_event_name: 'PostToolUse', tool_name: 'AskUserQuestion', tool_input: {} } });
371
+ }
372
+ for (const run of session.runs) {
373
+ const tool_input = { subagent_type: run.role, description: run.desc, prompt: `${run.desc}.`, run_in_background: true };
374
+ events.push({ at: run.start, input: { hook_event_name: 'PreToolUse', tool_name: 'Agent', tool_input, tool_use_id: run.toolUseId } });
375
+ events.push({ at: run.start + 1000, input: { hook_event_name: 'PostToolUse', tool_name: 'Agent', tool_input, tool_use_id: run.toolUseId, tool_response: { agentId: run.agent, isAsync: true, status: 'async_launched' } } });
376
+ events.push({ at: run.end, input: { hook_event_name: 'PostToolUse', tool_name: 'SubagentHandback', agent_id: run.agent, agent_type: run.role, tool_input: { message: run.report } } });
377
+ events.push({ at: run.end + 500, input: { hook_event_name: 'SubagentStop', agent_id: run.agent, agent_type: run.role, last_assistant_message: 'Handed back.', agent_transcript_path: join(subagents, `agent-${run.agent}.jsonl`), stop_hook_active: false } });
378
+ events.push({ at: run.end + 2000, input: { hook_event_name: 'UserPromptSubmit', prompt: `<task-notification>\n<task-id>${run.agent}</task-id>\n<tool-use-id>${run.toolUseId}</tool-use-id>\n<status>completed</status>\n</task-notification>` } });
379
+ }
380
+ events.sort((a, b) => a.at - b.at);
381
+ for (const { at, input } of events) handle({ ...input, session_id: session.id }, { root: project, now: new Date(at) });
382
+ }
383
+
384
+ /**
385
+ * Makes the demo: the project, its history, and the environment that points every reader at them.
386
+ *
387
+ * @param {{root: string}} ctx - CLI context: where the releases are.
388
+ * @param {{now?: number}} [options]
389
+ * @returns {Promise<{project: string, dir: string, session: string, remove: () => void}>} `dir` holds all of it, and
390
+ * `remove` takes it away.
391
+ */
392
+ export async function makeDemo(ctx, { now = Date.now() } = {}) {
393
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), 'nina-demo-')));
394
+ const remove = () => rmSync(dir, { recursive: true, force: true });
395
+ const before = { NINA_DATA: process.env.NINA_DATA, NINA_TRANSCRIPTS: process.env.NINA_TRANSCRIPTS };
396
+ try {
397
+ const project = join(dir, DEMO_NAME);
398
+ const data = join(dir, 'data');
399
+ const transcripts = join(dir, 'transcripts');
400
+ mkdirSync(join(project, '.nina'), { recursive: true });
401
+ writeFileSync(join(project, '.nina', 'profile.json'), `${JSON.stringify(profileAt(ownRelease(ctx.root)), null, 2)}\n`);
402
+ const composed = await composeProject(project, ctx);
403
+ if (composed.error) throw new Error(composed.error);
404
+
405
+ const pills = PILLS(now);
406
+ for (const pill of pills) {
407
+ const path = join(project, '.claude', 'pills', pill.path);
408
+ mkdirSync(join(path, '..'), { recursive: true });
409
+ writeFileSync(path, pill.text);
410
+ }
411
+
412
+ process.env.NINA_DATA = data;
413
+ process.env.NINA_TRANSCRIPTS = transcripts;
414
+ const slug = slugFor(project);
415
+ const own = join(transcripts, slug);
416
+ mkdirSync(own, { recursive: true });
417
+ const random = seeded(29);
418
+ const sessions = demoSessions(now);
419
+ for (const session of sessions) writeTranscripts(own, project, session, random, pills);
420
+ const today = sessions.at(-1);
421
+ throughTheGate(project, today, join(own, today.id, 'subagents'));
422
+ const captured = await snapshot(['--project', slug, '--exact', '--quiet'], ctx);
423
+ if (captured !== 0) throw new Error('the snapshot could not read the demo');
424
+ return { project, dir, session: today.id, remove };
425
+ } catch (error) {
426
+ // Pointed at a directory about to be gone, every reader after this would find nothing.
427
+ for (const [name, value] of Object.entries(before)) {
428
+ if (value === undefined) delete process.env[name];
429
+ else process.env[name] = value;
430
+ }
431
+ remove();
432
+ throw error;
433
+ }
434
+ }
package/src/office.mjs CHANGED
@@ -302,6 +302,31 @@ export function busyFor(starts) {
302
302
  const REPLAY_MAX_GAP_MS = 3000;
303
303
  const REPLAY_MIN_GAP_MS = 500;
304
304
 
305
+ /** How long a replay in a loop rests on its last beat before it plays again from the start: the party is seen out. */
306
+ export const LOOP_REST_MS = 8000;
307
+
308
+ /** The longest a replay at a scale waits between two beats: a lunch break in the session is not a minute of an empty room. */
309
+ const REPLAY_SCALED_MAX_GAP_MS = 45_000;
310
+
311
+ /**
312
+ * How long a replay waits after each beat before the next. Each real gap is squeezed to `REPLAY_MIN_GAP_MS` to
313
+ * `REPLAY_MAX_GAP_MS`, so hours of a session play in minutes; at a `scale` it is the real gap times the scale instead,
314
+ * so a run stays at its desk in proportion to how long it worked — the demo's, which an audience watches, sit 20 to 40
315
+ * seconds where a squeezed replay had them up again in three. The owner speaking passes quickly either way: it moves
316
+ * nobody across the room, and an answer keeps its wait, or the question it answers would not be seen.
317
+ *
318
+ * @param {object[]} beats - The beats in the order they are played.
319
+ * @param {{scale?: number}} [options] - Replay time per real time.
320
+ * @returns {number[]} The wait after each beat, in ms.
321
+ */
322
+ export function replayDelays(beats, { scale } = {}) {
323
+ return beats.map((beat, k) => {
324
+ const following = beats[k + 1];
325
+ const gap = following && following.type !== 'owner' ? following.at - beat.at : 0;
326
+ return scale ? Math.min(REPLAY_SCALED_MAX_GAP_MS, Math.max(REPLAY_MIN_GAP_MS, gap * scale)) : Math.min(REPLAY_MAX_GAP_MS, Math.max(REPLAY_MIN_GAP_MS, gap));
327
+ });
328
+ }
329
+
305
330
  /**
306
331
  * Each run the ledger saw start, in order: when, at which stage, by which agent once known, whether it was a message
307
332
  * to a running agent, when it was refused, and when its verdict ended it. The beats and the timeline read the same.
@@ -626,10 +651,12 @@ const send = (res, event, data) => res.write(`event: ${event}\ndata: ${JSON.stri
626
651
  * anyone watches live. `serveOffice` mounts them at the root; the dashboard (`nina dashboard`) at `/office/`, where the
627
652
  * page's relative `events` reaches `/office/events`.
628
653
  *
629
- * @param {{target: string, wanted?: string, replay?: boolean}} options
654
+ * @param {{target: string, wanted?: string, replay?: boolean, loop?: boolean, rest?: number, scale?: number}} options - `loop`: a
655
+ * replay that ends plays again from the start, for an office left on a screen, after resting `rest` ms on its last
656
+ * beat. `scale`: a replay that keeps the session's proportions at that much replay time per real time (`replayDelays`).
630
657
  * @returns {{handle: (req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse, path: string) => boolean, close: () => void}}
631
658
  */
632
- export function officeRoutes({ target, wanted, replay = false }) {
659
+ export function officeRoutes({ target, wanted, replay = false, loop = false, rest = LOOP_REST_MS, scale }) {
633
660
  const room = officeRoom(target);
634
661
  const graph = parseGraph(readFileSync(join(target, '.claude', 'graph.md'), 'utf8'));
635
662
  // A function, so a `$&` or `$'` in a project's name or its owner's is text and not a replacement pattern.
@@ -714,6 +741,7 @@ export function officeRoutes({ target, wanted, replay = false }) {
714
741
  greet(client, state);
715
742
  const heard = clock(state.session);
716
743
  const beats = officeBeats(state.entries, graph, Number.POSITIVE_INFINITY, heard);
744
+ const delays = replayDelays(beats, { scale });
717
745
  // The timeline as it stood at each beat: cut from the ledger up to that moment, so a replay tells nothing early.
718
746
  const known = names(state.entries);
719
747
  const look = looks(state.session);
@@ -726,22 +754,27 @@ export function officeRoutes({ target, wanted, replay = false }) {
726
754
  const next = () => {
727
755
  if (!clients.has(client)) return;
728
756
  if (i >= beats.length) {
729
- send(client.res, 'end', { beats: beats.length });
757
+ if (!loop) {
758
+ send(client.res, 'end', { beats: beats.length });
759
+ return;
760
+ }
761
+ // Played again from its `hello`, which empties the room the page drew.
762
+ const timer = setTimeout(() => {
763
+ timers.delete(timer);
764
+ if (clients.has(client)) play(client);
765
+ }, rest);
766
+ timers.add(timer);
730
767
  return;
731
768
  }
732
769
  const beat = beats[i];
733
770
  send(client.res, 'beat', beat);
734
771
  tell(client, tasksAt(beat.at));
735
- i += 1;
736
- // The owner speaking moves nobody across the room: it passes quickly. An answer keeps its wait, or the question
737
- // it answers would not be seen.
738
- const quick = i < beats.length && beats[i].type === 'owner';
739
- const gap = i < beats.length && !quick ? beats[i].at - beat.at : 0;
740
772
  const timer = setTimeout(() => {
741
773
  timers.delete(timer);
742
774
  next();
743
- }, Math.min(REPLAY_MAX_GAP_MS, Math.max(REPLAY_MIN_GAP_MS, gap)));
775
+ }, delays[i]);
744
776
  timers.add(timer);
777
+ i += 1;
745
778
  };
746
779
  next();
747
780
  };