@xhulz/nina 0.28.19 → 0.28.21

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 (231) hide show
  1. package/package.json +1 -1
  2. package/releases/0.28.20/core/GAPS.md +125 -0
  3. package/releases/0.28.20/core/answered.json +29 -0
  4. package/releases/0.28.20/core/defaults/tree/.claude/agents/architect.md +1 -0
  5. package/releases/0.28.20/core/defaults/tree/.claude/agents/dba.md +3 -0
  6. package/releases/0.28.20/core/defaults/tree/.claude/agents/devops.md +1 -0
  7. package/releases/0.28.20/core/defaults/tree/.claude/agents/implementer.md +1 -0
  8. package/releases/0.28.20/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  9. package/releases/0.28.20/core/defaults/tree/.claude/agents/qa.md +2 -0
  10. package/releases/0.28.20/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  11. package/releases/0.28.20/core/defaults/tree/.claude/agents/secops.md +3 -0
  12. package/releases/0.28.20/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  13. package/releases/0.28.20/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  14. package/releases/0.28.20/core/tree/.claude/agents/architect.md +125 -0
  15. package/releases/0.28.20/core/tree/.claude/agents/dba.md +107 -0
  16. package/releases/0.28.20/core/tree/.claude/agents/devops.md +113 -0
  17. package/releases/0.28.20/core/tree/.claude/agents/implementer.md +155 -0
  18. package/releases/0.28.20/core/tree/.claude/agents/integration-tester.md +160 -0
  19. package/releases/0.28.20/core/tree/.claude/agents/planner.md +132 -0
  20. package/releases/0.28.20/core/tree/.claude/agents/qa.md +146 -0
  21. package/releases/0.28.20/core/tree/.claude/agents/reviewer.md +180 -0
  22. package/releases/0.28.20/core/tree/.claude/agents/secops.md +121 -0
  23. package/releases/0.28.20/core/tree/.claude/agents/solidity-auditor.md +126 -0
  24. package/releases/0.28.20/core/tree/.claude/agents/solidity-dev.md +123 -0
  25. package/releases/0.28.20/core/tree/.claude/agents-overview.md +64 -0
  26. package/releases/0.28.20/core/tree/.claude/graph.md +73 -0
  27. package/releases/0.28.20/core/tree/.claude/patterns.md +212 -0
  28. package/releases/0.28.20/core/tree/.claude/pills/README.md +145 -0
  29. package/releases/0.28.20/core/tree/.claude/pipeline.md +102 -0
  30. package/releases/0.28.20/core/tree/.claude/retrieval.md +104 -0
  31. package/releases/0.28.20/core/tree/.claude/router.md +201 -0
  32. package/releases/0.28.20/core/tree/.claude/templates/integration.md +45 -0
  33. package/releases/0.28.20/core/tree/CLAUDE.md +155 -0
  34. package/releases/0.28.20/core/tree/scripts/edit-guard.mjs +16 -0
  35. package/releases/0.28.20/core/tree/scripts/harness-check.mjs +71 -0
  36. package/releases/0.28.20/core/tree/scripts/loop-gate.mjs +19 -0
  37. package/releases/0.28.20/core/vocabulary.json +12 -0
  38. package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  39. package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  40. package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  41. package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  42. package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  43. package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  44. package/releases/0.28.20/surfaces/blockchain/tree/.claude/graph.md +11 -0
  45. package/releases/0.28.20/surfaces/blockchain/tree/.claude/router.md +9 -0
  46. package/releases/0.28.20/surfaces/blockchain/tree/CLAUDE.md +12 -0
  47. package/releases/0.28.20/surfaces/db/tree/.claude/agents/architect.md +8 -0
  48. package/releases/0.28.20/surfaces/db/tree/.claude/agents/dba.md +84 -0
  49. package/releases/0.28.20/surfaces/db/tree/.claude/agents/devops.md +14 -0
  50. package/releases/0.28.20/surfaces/db/tree/.claude/agents/implementer.md +5 -0
  51. package/releases/0.28.20/surfaces/db/tree/.claude/agents/integration-tester.md +13 -0
  52. package/releases/0.28.20/surfaces/db/tree/.claude/agents/planner.md +2 -0
  53. package/releases/0.28.20/surfaces/db/tree/.claude/agents/reviewer.md +25 -0
  54. package/releases/0.28.20/surfaces/db/tree/.claude/agents/secops.md +2 -0
  55. package/releases/0.28.20/surfaces/db/tree/.claude/agents-overview.md +5 -0
  56. package/releases/0.28.20/surfaces/db/tree/.claude/graph.md +8 -0
  57. package/releases/0.28.20/surfaces/db/tree/.claude/patterns.md +29 -0
  58. package/releases/0.28.20/surfaces/db/tree/.claude/pipeline.md +13 -0
  59. package/releases/0.28.20/surfaces/db/tree/.claude/retrieval.md +5 -0
  60. package/releases/0.28.20/surfaces/db/tree/.claude/router.md +10 -0
  61. package/releases/0.28.20/surfaces/db/tree/CLAUDE.md +23 -0
  62. package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  63. package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  64. package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/devops.md +31 -0
  65. package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  66. package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  67. package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  68. package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  69. package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  70. package/releases/0.28.20/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  71. package/releases/0.28.20/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  72. package/releases/0.28.20/surfaces/edge-cf/tree/.claude/router.md +21 -0
  73. package/releases/0.28.20/surfaces/edge-cf/tree/CLAUDE.md +27 -0
  74. package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  75. package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  76. package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  77. package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  78. package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/reviewer.md +62 -0
  79. package/releases/0.28.20/surfaces/frontend/tree/.claude/patterns.md +15 -0
  80. package/releases/0.28.20/surfaces/frontend/tree/.claude/retrieval.md +5 -0
  81. package/releases/0.28.20/surfaces/frontend/tree/.claude/router.md +17 -0
  82. package/releases/0.28.20/surfaces/frontend/tree/CLAUDE.md +5 -0
  83. package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  84. package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  85. package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  86. package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  87. package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  88. package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  89. package/releases/0.28.20/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  90. package/releases/0.28.20/surfaces/integrations/tree/.claude/graph.md +9 -0
  91. package/releases/0.28.20/surfaces/integrations/tree/.claude/patterns.md +38 -0
  92. package/releases/0.28.20/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  93. package/releases/0.28.20/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  94. package/releases/0.28.20/surfaces/integrations/tree/.claude/router.md +6 -0
  95. package/releases/0.28.20/surfaces/integrations/tree/CLAUDE.md +17 -0
  96. package/releases/0.28.20/surfaces/money/tree/.claude/agents/architect.md +12 -0
  97. package/releases/0.28.20/surfaces/money/tree/.claude/agents/dba.md +14 -0
  98. package/releases/0.28.20/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  99. package/releases/0.28.20/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  100. package/releases/0.28.20/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  101. package/releases/0.28.20/surfaces/money/tree/.claude/agents/secops.md +5 -0
  102. package/releases/0.28.20/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  103. package/releases/0.28.20/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  104. package/releases/0.28.20/surfaces/money/tree/.claude/graph.md +2 -0
  105. package/releases/0.28.20/surfaces/money/tree/.claude/patterns.md +49 -0
  106. package/releases/0.28.20/surfaces/money/tree/.claude/pipeline.md +2 -0
  107. package/releases/0.28.20/surfaces/money/tree/.claude/retrieval.md +2 -0
  108. package/releases/0.28.20/surfaces/money/tree/CLAUDE.md +11 -0
  109. package/releases/0.28.20/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  110. package/releases/0.28.20/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  111. package/releases/0.28.20/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  112. package/releases/0.28.20/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  113. package/releases/0.28.20/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  114. package/releases/0.28.20/surfaces/pii/tree/CLAUDE.md +5 -0
  115. package/releases/0.28.21/core/GAPS.md +125 -0
  116. package/releases/0.28.21/core/answered.json +29 -0
  117. package/releases/0.28.21/core/defaults/tree/.claude/agents/architect.md +1 -0
  118. package/releases/0.28.21/core/defaults/tree/.claude/agents/dba.md +3 -0
  119. package/releases/0.28.21/core/defaults/tree/.claude/agents/devops.md +1 -0
  120. package/releases/0.28.21/core/defaults/tree/.claude/agents/implementer.md +1 -0
  121. package/releases/0.28.21/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
  122. package/releases/0.28.21/core/defaults/tree/.claude/agents/qa.md +2 -0
  123. package/releases/0.28.21/core/defaults/tree/.claude/agents/reviewer.md +3 -0
  124. package/releases/0.28.21/core/defaults/tree/.claude/agents/secops.md +3 -0
  125. package/releases/0.28.21/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
  126. package/releases/0.28.21/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
  127. package/releases/0.28.21/core/tree/.claude/agents/architect.md +125 -0
  128. package/releases/0.28.21/core/tree/.claude/agents/dba.md +107 -0
  129. package/releases/0.28.21/core/tree/.claude/agents/devops.md +113 -0
  130. package/releases/0.28.21/core/tree/.claude/agents/implementer.md +155 -0
  131. package/releases/0.28.21/core/tree/.claude/agents/integration-tester.md +160 -0
  132. package/releases/0.28.21/core/tree/.claude/agents/planner.md +132 -0
  133. package/releases/0.28.21/core/tree/.claude/agents/qa.md +146 -0
  134. package/releases/0.28.21/core/tree/.claude/agents/reviewer.md +180 -0
  135. package/releases/0.28.21/core/tree/.claude/agents/secops.md +121 -0
  136. package/releases/0.28.21/core/tree/.claude/agents/solidity-auditor.md +126 -0
  137. package/releases/0.28.21/core/tree/.claude/agents/solidity-dev.md +123 -0
  138. package/releases/0.28.21/core/tree/.claude/agents-overview.md +64 -0
  139. package/releases/0.28.21/core/tree/.claude/graph.md +73 -0
  140. package/releases/0.28.21/core/tree/.claude/patterns.md +212 -0
  141. package/releases/0.28.21/core/tree/.claude/pills/README.md +145 -0
  142. package/releases/0.28.21/core/tree/.claude/pipeline.md +102 -0
  143. package/releases/0.28.21/core/tree/.claude/retrieval.md +104 -0
  144. package/releases/0.28.21/core/tree/.claude/router.md +201 -0
  145. package/releases/0.28.21/core/tree/.claude/templates/integration.md +45 -0
  146. package/releases/0.28.21/core/tree/CLAUDE.md +155 -0
  147. package/releases/0.28.21/core/tree/scripts/edit-guard.mjs +16 -0
  148. package/releases/0.28.21/core/tree/scripts/harness-check.mjs +71 -0
  149. package/releases/0.28.21/core/tree/scripts/loop-gate.mjs +19 -0
  150. package/releases/0.28.21/core/vocabulary.json +12 -0
  151. package/releases/0.28.21/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
  152. package/releases/0.28.21/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
  153. package/releases/0.28.21/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
  154. package/releases/0.28.21/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
  155. package/releases/0.28.21/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
  156. package/releases/0.28.21/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
  157. package/releases/0.28.21/surfaces/blockchain/tree/.claude/graph.md +11 -0
  158. package/releases/0.28.21/surfaces/blockchain/tree/.claude/router.md +9 -0
  159. package/releases/0.28.21/surfaces/blockchain/tree/CLAUDE.md +12 -0
  160. package/releases/0.28.21/surfaces/db/tree/.claude/agents/architect.md +8 -0
  161. package/releases/0.28.21/surfaces/db/tree/.claude/agents/dba.md +84 -0
  162. package/releases/0.28.21/surfaces/db/tree/.claude/agents/devops.md +14 -0
  163. package/releases/0.28.21/surfaces/db/tree/.claude/agents/implementer.md +5 -0
  164. package/releases/0.28.21/surfaces/db/tree/.claude/agents/integration-tester.md +13 -0
  165. package/releases/0.28.21/surfaces/db/tree/.claude/agents/planner.md +2 -0
  166. package/releases/0.28.21/surfaces/db/tree/.claude/agents/reviewer.md +25 -0
  167. package/releases/0.28.21/surfaces/db/tree/.claude/agents/secops.md +2 -0
  168. package/releases/0.28.21/surfaces/db/tree/.claude/agents-overview.md +5 -0
  169. package/releases/0.28.21/surfaces/db/tree/.claude/graph.md +8 -0
  170. package/releases/0.28.21/surfaces/db/tree/.claude/patterns.md +29 -0
  171. package/releases/0.28.21/surfaces/db/tree/.claude/pipeline.md +13 -0
  172. package/releases/0.28.21/surfaces/db/tree/.claude/retrieval.md +5 -0
  173. package/releases/0.28.21/surfaces/db/tree/.claude/router.md +10 -0
  174. package/releases/0.28.21/surfaces/db/tree/CLAUDE.md +23 -0
  175. package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
  176. package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
  177. package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/devops.md +31 -0
  178. package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
  179. package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
  180. package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
  181. package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
  182. package/releases/0.28.21/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
  183. package/releases/0.28.21/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
  184. package/releases/0.28.21/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
  185. package/releases/0.28.21/surfaces/edge-cf/tree/.claude/router.md +21 -0
  186. package/releases/0.28.21/surfaces/edge-cf/tree/CLAUDE.md +27 -0
  187. package/releases/0.28.21/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
  188. package/releases/0.28.21/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
  189. package/releases/0.28.21/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
  190. package/releases/0.28.21/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
  191. package/releases/0.28.21/surfaces/frontend/tree/.claude/agents/reviewer.md +62 -0
  192. package/releases/0.28.21/surfaces/frontend/tree/.claude/patterns.md +15 -0
  193. package/releases/0.28.21/surfaces/frontend/tree/.claude/retrieval.md +5 -0
  194. package/releases/0.28.21/surfaces/frontend/tree/.claude/router.md +17 -0
  195. package/releases/0.28.21/surfaces/frontend/tree/CLAUDE.md +5 -0
  196. package/releases/0.28.21/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
  197. package/releases/0.28.21/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
  198. package/releases/0.28.21/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
  199. package/releases/0.28.21/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
  200. package/releases/0.28.21/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
  201. package/releases/0.28.21/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
  202. package/releases/0.28.21/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
  203. package/releases/0.28.21/surfaces/integrations/tree/.claude/graph.md +9 -0
  204. package/releases/0.28.21/surfaces/integrations/tree/.claude/patterns.md +38 -0
  205. package/releases/0.28.21/surfaces/integrations/tree/.claude/pipeline.md +16 -0
  206. package/releases/0.28.21/surfaces/integrations/tree/.claude/retrieval.md +16 -0
  207. package/releases/0.28.21/surfaces/integrations/tree/.claude/router.md +6 -0
  208. package/releases/0.28.21/surfaces/integrations/tree/CLAUDE.md +17 -0
  209. package/releases/0.28.21/surfaces/money/tree/.claude/agents/architect.md +12 -0
  210. package/releases/0.28.21/surfaces/money/tree/.claude/agents/dba.md +14 -0
  211. package/releases/0.28.21/surfaces/money/tree/.claude/agents/implementer.md +11 -0
  212. package/releases/0.28.21/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
  213. package/releases/0.28.21/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
  214. package/releases/0.28.21/surfaces/money/tree/.claude/agents/secops.md +5 -0
  215. package/releases/0.28.21/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
  216. package/releases/0.28.21/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
  217. package/releases/0.28.21/surfaces/money/tree/.claude/graph.md +2 -0
  218. package/releases/0.28.21/surfaces/money/tree/.claude/patterns.md +49 -0
  219. package/releases/0.28.21/surfaces/money/tree/.claude/pipeline.md +2 -0
  220. package/releases/0.28.21/surfaces/money/tree/.claude/retrieval.md +2 -0
  221. package/releases/0.28.21/surfaces/money/tree/CLAUDE.md +11 -0
  222. package/releases/0.28.21/surfaces/pii/tree/.claude/agents/dba.md +2 -0
  223. package/releases/0.28.21/surfaces/pii/tree/.claude/agents/devops.md +8 -0
  224. package/releases/0.28.21/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
  225. package/releases/0.28.21/surfaces/pii/tree/.claude/agents/secops.md +2 -0
  226. package/releases/0.28.21/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
  227. package/releases/0.28.21/surfaces/pii/tree/CLAUDE.md +5 -0
  228. package/src/commands/pipeline.mjs +2 -4
  229. package/src/commands/stats.mjs +83 -48
  230. package/src/commands/upgrade.mjs +19 -4
  231. package/src/look.mjs +118 -0
@@ -11,6 +11,7 @@
11
11
  * loop-backs it was supposed to harvest were being counted right here.
12
12
  */
13
13
 
14
+ import { amber, bar, bold, dim, heading, note, pink, stacked } from '../look.mjs';
14
15
  import { readFile, readdir } from 'node:fs/promises';
15
16
  import { spawnSync } from 'node:child_process';
16
17
  import { createReadStream, existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
@@ -183,18 +184,21 @@ function costReport(records) {
183
184
  // The same convention as the duration column beside it: the upper middle of an even count.
184
185
  const median = (xs) => [...xs].sort((a, b) => a - b)[Math.floor(xs.length / 2)];
185
186
  console.log(
186
- `\n cost — at API list prices of ${PRICES_AS_OF}; ${measured.length} of ${records.length} runs have a token record` +
187
- (unpriced > 0 ? `, ${unpriced} on a model the price table does not know` : ''),
187
+ heading(
188
+ 'cost',
189
+ `at API list prices of ${PRICES_AS_OF}; ${measured.length} of ${records.length} runs have a token record` +
190
+ (unpriced > 0 ? `, ${unpriced} on a model the price table does not know` : ''),
191
+ ),
188
192
  );
189
- console.log(` ${'stage'.padEnd(20)}${'runs'.padStart(6)}${'median'.padStart(10)}${'total'.padStart(10)}${'share'.padStart(8)}`);
193
+ console.log(dim(` ${'stage'.padEnd(20)}${'runs'.padStart(6)}${'median'.padStart(10)}${'total'.padStart(10)}${'share'.padStart(8)}`));
190
194
  for (const [role, costs] of [...byRole].sort((a, b) => b[1].reduce((x, y) => x + y, 0) - a[1].reduce((x, y) => x + y, 0))) {
191
195
  const sum = costs.reduce((a, b) => a + b, 0);
192
196
  console.log(
193
- ` ${role.padEnd(20)}${String(costs.length).padStart(6)}${money(median(costs)).padStart(10)}` +
194
- `${money(sum).padStart(10)}${pct(sum, total).padStart(8)}`,
197
+ ` ${pink(role)}${' '.repeat(Math.max(20 - role.length, 1))}${String(costs.length).padStart(6)}${money(median(costs)).padStart(10)}` +
198
+ `${bold(money(sum).padStart(10))}${pct(sum, total).padStart(8)} ${pink(bar(sum / total, 20))}`,
195
199
  );
196
200
  }
197
- console.log(` ${'all stages'.padEnd(20)}${String([...byRole.values()].flat().length).padStart(6)}${''.padStart(10)}${money(total).padStart(10)}`);
201
+ console.log(bold(` ${'all stages'.padEnd(20)}${String([...byRole.values()].flat().length).padStart(6)}${''.padStart(10)}${money(total).padStart(10)}`));
198
202
  }
199
203
 
200
204
  /**
@@ -271,7 +275,7 @@ function modelReport(records) {
271
275
  const costs = runs.map((r) => (r.tokens ? costOf(r.tokens, r.usage_model) : null)).filter((c) => c !== null);
272
276
  const dates = runs.map((r) => String(r.ts).slice(0, 10)).sort();
273
277
  lines.push(
274
- ` ${(i === 0 ? role : '').padEnd(20)}${model.padEnd(26)}${String(runs.length).padStart(5)} run(s) ${dates[0]} → ${dates.at(-1)}` +
278
+ ` ${i === 0 ? `${pink(role)}${' '.repeat(Math.max(20 - role.length, 1))}` : ''.padEnd(20)}${model.padEnd(26)}${String(runs.length).padStart(5)} run(s) ${dim(`${dates[0]} → ${dates.at(-1)}`)}` +
275
279
  (clear.length ? ` loop-back ${pct(loops, clear.length)} of ${clear.length}` : ' no readable verdict') +
276
280
  (costs.length ? ` median ${money(median(costs))}` : ''),
277
281
  );
@@ -303,9 +307,9 @@ function modelReport(records) {
303
307
  }
304
308
 
305
309
  if (lines.length === 0 && drift.length === 0) return;
306
- console.log('\n models — a stage that ran on more than one model or effort level, one line each; different weeks, not an experiment');
310
+ console.log(heading('models', 'a stage that ran on more than one model or effort level, one line each; different weeks, not an experiment'));
307
311
  for (const line of lines) console.log(line);
308
- for (const line of drift) console.log(line);
312
+ for (const line of drift) console.log(amber(line));
309
313
  }
310
314
 
311
315
  /** The verdicts that close a pipeline cycle: the change passed its tests, reached preview, or cleared the audit. */
@@ -390,15 +394,19 @@ function proportionReport(records, stepLimit = () => null) {
390
394
  }
391
395
  if (built === 0) return;
392
396
  console.log(
393
- `\n proportion — ${built} cycle(s) that wrote code; a cycle closes at qa PASS, devops DEPLOYED or secops SECURE, or at the next design after code` +
394
- (unreadable > 0 ? `; ${unreadable} closed by a run whose verdict could not be read` : '') +
395
- (open > 0 ? `; ${open} still open when their session's record ends` : ''),
397
+ heading(
398
+ 'proportion',
399
+ `${built} cycle(s) that wrote code; a cycle closes at qa PASS, devops DEPLOYED or secops SECURE, or at the next design after code` +
400
+ (unreadable > 0 ? `; ${unreadable} closed by a run whose verdict could not be read` : '') +
401
+ (open > 0 ? `; ${open} still open when their session's record ends` : ''),
402
+ ),
396
403
  );
397
- console.log(` ${'largest write'.padEnd(16)}${'cycles'.padStart(8)}${'with a planner or architect'.padStart(30)}`);
404
+ console.log(dim(` ${'largest write'.padEnd(16)}${'cycles'.padStart(8)}${'with a planner or architect'.padStart(30)}`));
398
405
  for (const row of rows) {
399
- console.log(` ${row.label.padEnd(16)}${String(row.cycles).padStart(8)}${`${row.designed} (${pct(row.designed, row.cycles)})`.padStart(30)}`);
406
+ const designed = `${row.designed} (${pct(row.designed, row.cycles)})`.padStart(30);
407
+ console.log(` ${row.label.padEnd(16)}${String(row.cycles).padStart(8)}${row.cycles ? designed : dim(designed)} ${pink(bar(row.cycles ? row.designed / row.cycles : 0, 10))}`);
400
408
  }
401
- console.log(' A shape, not a verdict: a critical path is gated in full at any size, and one spec may cover sibling steps.');
409
+ console.log(dim(' A shape, not a verdict: a critical path is gated in full at any size, and one spec may cover sibling steps.'));
402
410
 
403
411
  // The one size the harness does limit: what one implementer run writes. A run re-reads the context it
404
412
  // has built on every turn, so its cost grows faster than its size — measured over two projects, the
@@ -413,7 +421,10 @@ function proportionReport(records, stepLimit = () => null) {
413
421
  const largest = over.reduce((a, b) => (b.files_touched > a.files_touched ? b : a));
414
422
  const read = typeof largest.tokens?.read === 'number' ? `, ${Math.round(largest.tokens.read / 1e6)}M tokens read from cache` : '';
415
423
  console.log(
416
- ` ${over.length} implementer run(s) wrote more files than one step may (${stepLimit(largest.project)}); the largest wrote ${largest.files_touched}${read}. The architect splits such a spec into steps.`,
424
+ note(
425
+ `${over.length} implementer run(s) wrote more files than one step may (${stepLimit(largest.project)}); the largest wrote ${largest.files_touched}${read}. The architect splits such a spec into steps.`,
426
+ 'warn',
427
+ ),
417
428
  );
418
429
  }
419
430
  }
@@ -551,34 +562,53 @@ export async function stats(argv, ctx) {
551
562
  const span = [records[0]?.ts, records.at(-1)?.ts].map((t) => String(t).slice(0, 10));
552
563
  const projectCount = new Set(records.map((r) => r.project)).size;
553
564
  console.log(
554
- ` ${records.length} dispatches · ${projectCount} project${projectCount === 1 ? '' : 's'} · ` +
565
+ ` ${projectCount === 1 ? `${bold(pink(projectName(records[0].project)))} · ` : ''}${bold(records.length)} dispatches · ${projectCount} project${projectCount === 1 ? '' : 's'} · ` +
555
566
  `${span[0]} → ${span[1]}` +
556
- (here ? ' (this project; --all for every project)' : '') +
557
- (skipped > 0 ? ` (${skipped} non-harness project${skipped === 1 ? '' : 's'} hidden, --all to include)` : '') +
558
- (held > 0 ? ` · ${held} dispatch(es) denied by a hook, not counted as runs` : '') +
559
- '\n',
567
+ (here ? dim(' (this project; --all for every project)') : '') +
568
+ (skipped > 0 ? dim(` (${skipped} non-harness project${skipped === 1 ? '' : 's'} hidden, --all to include)`) : '') +
569
+ (held > 0 ? ` · ${held} dispatch(es) denied by a hook, not counted as runs` : ''),
560
570
  );
571
+ console.log(heading('stages', 'what each ran, and what came of it'));
561
572
  console.log(
562
- ` ${'stage'.padEnd(20)}${'runs'.padStart(6)}${'verdict'.padStart(9)}${'loop-back'.padStart(11)}` +
563
- `${'rate'.padStart(7)}${'declared'.padStart(10)}${'unreadable'.padStart(12)}` +
564
- `${'median'.padStart(9)}`,
573
+ dim(
574
+ ` ${'stage'.padEnd(20)}${'runs'.padStart(6)}${'verdict'.padStart(9)}${'loop-back'.padStart(11)}` +
575
+ `${'rate'.padStart(7)}${'declared'.padStart(10)}${'unreadable'.padStart(12)}` +
576
+ `${'median'.padStart(9)}`,
577
+ ),
565
578
  );
566
579
 
567
580
  const rows = [...byRole.entries()].sort((a, b) => b[1].n - a[1].n);
581
+ // Each stage's runs as one bar, as long as its share of the busiest stage's: what went forward, what
582
+ // was sent back, and what said nothing readable (or is still running).
583
+ const busiest = Math.max(...rows.map(([, s]) => s.n), 1);
584
+ const zero = (text, n) => (n === 0 ? dim(text) : text);
568
585
  for (const [role, s] of rows) {
586
+ const runsBar = stacked(
587
+ [
588
+ { n: s.clear - s.loop, char: '█', paint: pink },
589
+ { n: s.loop, char: '▓', paint: amber },
590
+ { n: s.n - s.clear, char: '░', paint: dim },
591
+ ],
592
+ Math.max(1, Math.round((16 * s.n) / busiest)),
593
+ );
594
+ const declared = pct(s.declared, s.done).padStart(10);
595
+ const unread = pct(s.unclear, s.done).padStart(12);
569
596
  console.log(
570
- ` ${role.padEnd(20)}${String(s.n).padStart(6)}${String(s.clear).padStart(9)}` +
571
- `${String(s.loop).padStart(11)}${pct(s.loop, s.clear).padStart(7)}` +
572
- `${pct(s.declared, s.done).padStart(10)}${pct(s.unclear, s.done).padStart(12)}` +
573
- `${medianMin(s.durations).padStart(9)}`,
597
+ ` ${pink(role)}${' '.repeat(Math.max(20 - role.length, 1))}${String(s.n).padStart(6)}${String(s.clear).padStart(9)}` +
598
+ `${zero(String(s.loop).padStart(11), s.loop)}${zero(pct(s.loop, s.clear).padStart(7), s.loop)}` +
599
+ `${s.declared < s.done ? amber(declared) : dim(declared)}${s.unclear > 0 ? amber(unread) : dim(unread)}` +
600
+ `${medianMin(s.durations).padStart(9)} ${runsBar}`,
574
601
  );
575
602
  }
603
+ console.log(dim(` ${' '.repeat(86)}█ forward ▓ sent back ░ no verdict read`));
576
604
 
577
605
  const unreadable = rows.reduce((a, [, s]) => a + s.unclear, 0);
578
606
  const done = rows.reduce((a, [, s]) => a + s.done, 0);
607
+ console.log('');
579
608
  console.log(
580
- `\n ${pct(unreadable, done)} of finished runs report no machine-readable verdict.` +
581
- ' Those stages need a verdict token on the report\'s first line.',
609
+ unreadable === 0
610
+ ? note("every finished run's verdict could be read.", 'ok')
611
+ : note(`${pct(unreadable, done)} of finished runs report no machine-readable verdict. Those stages need a verdict token on the report's first line.`, 'warn'),
582
612
  );
583
613
 
584
614
  // Whether the stages that send work back say what they send back. Counted only over reports read
@@ -587,8 +617,7 @@ export async function stats(argv, ctx) {
587
617
  if (named.length > 0) {
588
618
  const withIds = named.filter((r) => r.issues > 0).length;
589
619
  console.log(
590
- ` ${withIds} of ${named.length} declared loop-back(s) (${pct(withIds, named.length)}) named their issues` +
591
- ' on the `ISSUES` line under the verdict.',
620
+ note(`${withIds} of ${named.length} declared loop-back(s) (${pct(withIds, named.length)}) named their issues on the \`ISSUES\` line under the verdict.`, withIds < named.length ? 'warn' : 'ok'),
592
621
  );
593
622
  }
594
623
 
@@ -600,9 +629,11 @@ export async function stats(argv, ctx) {
600
629
  for (const r of withSkills) for (const k of r.skills) tally.set(k, (tally.get(k) ?? 0) + 1);
601
630
  const list = [...tally].sort((a, b) => b[1] - a[1]).map(([k, n]) => `${k} ×${n}`);
602
631
  console.log(
603
- ` ${withSkills.length} of ${observable} runs with a readable transcript` +
604
- ` (${pct(withSkills.length, observable)}) invoked a Skill` +
605
- (list.length > 0 ? ` — ${list.join(', ')}` : ' — none at all'),
632
+ note(
633
+ `${withSkills.length} of ${observable} runs with a readable transcript (${pct(withSkills.length, observable)}) invoked a Skill` +
634
+ (list.length > 0 ? ` — ${list.join(', ')}` : ' — none at all'),
635
+ withSkills.length === 0 ? 'warn' : 'info',
636
+ ),
606
637
  );
607
638
  }
608
639
 
@@ -617,7 +648,8 @@ export async function stats(argv, ctx) {
617
648
  others.reduce((a, [, o]) => a + o.clear, 0),
618
649
  );
619
650
  console.log(
620
- ` ${role}: ${s.loop} loop-back(s) in ${s.clear} readable verdict(s) (${pct(s.loop, s.clear)}),` +
651
+ note(
652
+ `${role}: ${s.loop} loop-back(s) in ${s.clear} readable verdict(s) (${pct(s.loop, s.clear)}),` +
621
653
  ` against ${elsewhere} across the other gates — check whether it still gates anything.` +
622
654
  (s.n - s.clear > 0
623
655
  ? ` ${s.n - s.clear} further run(s) produced no readable verdict at all, so the rate may understate it.`
@@ -627,6 +659,8 @@ export async function stats(argv, ctx) {
627
659
  (s.declared < s.clear
628
660
  ? ` Only ${s.declared} of the ${s.clear} were declared by the stage; the rest were inferred from its report.`
629
661
  : ''),
662
+ 'warn',
663
+ ),
630
664
  );
631
665
  }
632
666
 
@@ -642,30 +676,31 @@ export async function stats(argv, ctx) {
642
676
  const newest = pills.dates.at(-1);
643
677
  const inWindow = pills.dates.filter((d) => d >= span[0] && d <= span[1]).length;
644
678
 
645
- console.log('\n learning');
679
+ console.log(heading('learning', 'what the loop-backs taught, and what became a rule'));
646
680
  if (pills.total === 0) {
647
- console.log(
648
- ` ${loopBacks} loop-back(s) and not one pill — either nothing was learned, or nothing was written down.`,
649
- );
681
+ console.log(note(`${loopBacks} loop-back(s) and not one pill — either nothing was learned, or nothing was written down.`, loopBacks > 0 ? 'warn' : 'info'));
650
682
  } else {
651
683
  console.log(
652
- // A share over 100% says nothing: the pills were written for something other than these loop-backs.
653
- ` ${loopBacks} loop-back(s) in the window → ${inWindow} pill(s) written${inWindow <= loopBacks ? ` (${pct(inWindow, loopBacks)})` : ''}` +
654
- ' — every loop-back is looked at for a lesson; `nina learn` shows which roles are owed one.' +
655
- (pills.undated > 0 ? ` ${pills.undated} pill(s) carry no date and cannot be placed.` : ''),
684
+ note(
685
+ // A share over 100% says nothing: the pills were written for something other than these loop-backs.
686
+ `${loopBacks} loop-back(s) in the window → ${inWindow} pill(s) written${inWindow <= loopBacks ? ` (${pct(inWindow, loopBacks)})` : ''}` +
687
+ ' — every loop-back is looked at for a lesson; `nina learn` shows which roles are owed one.' +
688
+ (pills.undated > 0 ? ` ${pills.undated} pill(s) carry no date and cannot be placed.` : ''),
689
+ ),
656
690
  );
657
691
  console.log(
658
692
  pills.retired === 0
659
- ? ` 0 of ${pills.total} pill(s) retired — no correction has ever graduated into a rule.`
660
- : ` ${pills.retired} of ${pills.total} pill(s) retired into a rule.`,
693
+ ? note(`0 of ${pills.total} pill(s) retired — no correction has ever graduated into a rule.`, 'warn')
694
+ : note(`${pills.retired} of ${pills.total} pill(s) retired into a rule.`, 'ok'),
661
695
  );
662
696
  if (newest) {
663
697
  const since = records.filter((r) => String(r.ts).slice(0, 10) > newest).length;
664
- if (since > 0) console.log(` ${since} dispatch(es) since the newest pill (${newest}).`);
698
+ if (since > 0) console.log(note(`${since} dispatch(es) since the newest pill (${newest}).`));
665
699
  }
666
700
  }
667
701
  for (const name of pills.unresolved) {
668
- console.log(` ${name} could not be located on disk, so its pills are not counted.`);
702
+ console.log(note(`${name} could not be located on disk, so its pills are not counted.`, 'warn'));
669
703
  }
704
+ console.log('');
670
705
  return 0;
671
706
  }
@@ -184,12 +184,27 @@ export async function upgrade(argv, ctx) {
184
184
  }
185
185
 
186
186
  const from = layerRootFor(ctx.root, profile.core);
187
+ if (from.error) {
188
+ console.error(` ${from.error}\n`);
189
+ return 1;
190
+ }
191
+ // The release asked for is not one this install carries: the installed package is older than it. Said
192
+ // as that, with the install that fixes it. It used to read "profile pins core <version>", about a pin the
193
+ // profile did not hold, right after an install that had failed.
187
194
  const onto = layerRootFor(ctx.root, to);
188
- for (const r of [from, onto]) {
189
- if (r.error) {
190
- console.error(` ${r.error}\n`);
191
- return 1;
195
+ if (onto.error) {
196
+ let installed = '?';
197
+ try {
198
+ installed = JSON.parse(await readFile(join(ctx.root, 'package.json'), 'utf8')).version;
199
+ } catch {
200
+ // An install with no manifest still says what to run.
192
201
  }
202
+ console.error(
203
+ to === 'dev'
204
+ ? ` ${onto.error}\n`
205
+ : ` ${to} is not a release the installed NINA (${installed}) carries — install it first: pnpm add -D -E @xhulz/nina@${to}\n`,
206
+ );
207
+ return 1;
193
208
  }
194
209
 
195
210
  const surfaces = profile.surfaces ?? [];
package/src/look.mjs ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * How a report looks on a terminal: the banner's pink for stage names and section marks, dim for what
3
+ * explains, amber for what needs looking at, bars for shares, and lines wrapped to the window.
4
+ *
5
+ * Only on a terminal, and never under `NO_COLOR`. Piped, a report is the same lines, uncoloured and
6
+ * unwrapped, so `grep` still finds a whole sentence and a test still reads it; the bars stay, since a
7
+ * block character reads the same anywhere.
8
+ */
9
+
10
+ import { PINK, useColor } from './banner.mjs';
11
+
12
+ /** An amber for what needs looking at, and a green for what is sound: bright enough on dark and light. */
13
+ const AMBER = [230, 160, 40];
14
+ const GREEN = [90, 180, 110];
15
+
16
+ const rgb = ([r, g, b], s) => (useColor() ? `\x1b[38;2;${r};${g};${b}m${s}\x1b[0m` : String(s));
17
+
18
+ /** Text in the banner's pink: a stage's name, a section's mark. */
19
+ export const pink = (s) => rgb(PINK, s);
20
+ /** Text that explains rather than reports. */
21
+ export const dim = (s) => (useColor() ? `\x1b[2m${s}\x1b[0m` : String(s));
22
+ /** Text that carries the number a line is about. */
23
+ export const bold = (s) => (useColor() ? `\x1b[1m${s}\x1b[0m` : String(s));
24
+ /** Text that needs looking at. */
25
+ export const amber = (s) => rgb(AMBER, s);
26
+ /** Text that says something is sound. */
27
+ export const green = (s) => rgb(GREEN, s);
28
+
29
+ /** The width lines wrap at on a terminal; a pipe is never wrapped. */
30
+ const width = () => Math.max(80, Math.min(process.stdout.columns ?? 120, 140));
31
+
32
+ /**
33
+ * A section's heading: its name, and what the section is about. Printed plain it reads as it always did,
34
+ * "name — what"; on a terminal the name is marked and the explanation dimmed.
35
+ *
36
+ * @param {string} name - The section.
37
+ * @param {string} [about] - What it covers.
38
+ * @returns {string}
39
+ */
40
+ export function heading(name, about = '') {
41
+ if (!useColor()) return `\n ${name}${about ? ` — ${about}` : ''}`;
42
+ const lead = ` ▌ ${name} `;
43
+ return `\n${pink(' ▌')} ${bold(pink(name))}${about ? ` ${dim(wrapped(`— ${about}`, lead.length, lead.length + 2))}` : ''}`;
44
+ }
45
+
46
+ /**
47
+ * A line of prose under a section, wrapped to the window on a terminal with its continuation indented
48
+ * under its first word, and marked: `·` for what it reports, `!` in amber for what needs looking at, `✓`
49
+ * in green for what is sound.
50
+ *
51
+ * @param {string} text - The sentence.
52
+ * @param {'info'|'warn'|'ok'} [kind] - What it is.
53
+ * @returns {string}
54
+ */
55
+ export function note(text, kind = 'info') {
56
+ if (!useColor()) return ` ${text}`;
57
+ const mark = kind === 'warn' ? amber('!') : kind === 'ok' ? green('✓') : dim('·');
58
+ const body = wrapped(text, 6, 6);
59
+ return ` ${mark} ${kind === 'warn' ? amber(body) : body}`;
60
+ }
61
+
62
+ /**
63
+ * Wraps text at the window's width. The first line starts `first` columns in; the rest are indented by
64
+ * `rest`. Words are never split.
65
+ *
66
+ * @param {string} text - The text.
67
+ * @param {number} first - Where the first line starts.
68
+ * @param {number} rest - The indent of every other line.
69
+ * @returns {string}
70
+ */
71
+ export function wrapped(text, first, rest) {
72
+ const room = width();
73
+ const lines = [];
74
+ let line = '';
75
+ let at = first;
76
+ for (const word of text.split(' ')) {
77
+ if (line && at + line.length + 1 + word.length > room) {
78
+ lines.push(line);
79
+ line = word;
80
+ at = rest;
81
+ } else line = line ? `${line} ${word}` : word;
82
+ }
83
+ lines.push(line);
84
+ return lines.join(`\n${' '.repeat(rest)}`);
85
+ }
86
+
87
+ /** Eighths of a block, for the fraction a bar ends on. */
88
+ const EIGHTHS = ['', '▏', '▎', '▍', '▌', '▋', '▊', '▉'];
89
+
90
+ /**
91
+ * A bar as long as a share of `cells`, ending on an eighth of a block.
92
+ *
93
+ * @param {number} fraction - From 0 to 1.
94
+ * @param {number} cells - How long a whole one is.
95
+ * @returns {string}
96
+ */
97
+ export function bar(fraction, cells) {
98
+ const eighths = Math.round(Math.max(0, Math.min(1, fraction)) * cells * 8);
99
+ return `${'█'.repeat(Math.floor(eighths / 8))}${EIGHTHS[eighths % 8]}`;
100
+ }
101
+
102
+ /**
103
+ * A bar split into parts, one character per part so it reads uncoloured: `█` for what went forward, `▓`
104
+ * for what was sent back, `░` for what said nothing readable. Each part is at least one cell when it is
105
+ * not zero, so a single run sent back in a hundred still shows.
106
+ *
107
+ * @param {{n: number, char: string, paint: (s: string) => string}[]} parts - In order.
108
+ * @param {number} cells - How long the bar is.
109
+ * @returns {string}
110
+ */
111
+ export function stacked(parts, cells) {
112
+ const total = parts.reduce((a, p) => a + p.n, 0);
113
+ if (total === 0 || cells <= 0) return '';
114
+ const sizes = parts.map((p) => (p.n > 0 ? Math.max(1, Math.round((p.n / total) * cells)) : 0));
115
+ // Trim the rounding back to the length asked for, from the largest part.
116
+ while (sizes.reduce((a, b) => a + b, 0) > cells) sizes[sizes.indexOf(Math.max(...sizes))] -= 1;
117
+ return parts.map((p, i) => p.paint(p.char.repeat(sizes[i]))).join('');
118
+ }