mulmoterminal 6.4.0 → 6.6.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 (338) hide show
  1. package/README.md +17 -12
  2. package/bin/cli-args.d.ts +2 -0
  3. package/bin/cli-args.js +41 -3
  4. package/bin/mulmoterminal.js +10 -0
  5. package/bin/node-install.d.ts +6 -0
  6. package/bin/node-install.js +81 -0
  7. package/blueprints/ask/checks/base.mjs +12 -0
  8. package/blueprints/ask/checks/replies.mjs +99 -0
  9. package/blueprints/ask/hearing.json +23 -0
  10. package/blueprints/ask/manifest.json +9 -0
  11. package/blueprints/ask/presets/keihi/keihi.md +19 -0
  12. package/blueprints/ask/presets.json +15 -0
  13. package/blueprints/ask/skills/answer/SKILL.md +60 -0
  14. package/blueprints/ask/skills/keep/SKILL.md +25 -0
  15. package/blueprints/ask/steps.json +19 -0
  16. package/blueprints/docs/checks/chaff.mjs +91 -0
  17. package/blueprints/docs/checks/chaff.sh +7 -0
  18. package/blueprints/docs/checks/dismissals.d.mts +10 -0
  19. package/blueprints/docs/checks/dismissals.mjs +75 -0
  20. package/blueprints/docs/checks/documents.mjs +33 -0
  21. package/blueprints/docs/checks/drafts.mjs +63 -0
  22. package/blueprints/docs/checks/markdown.mjs +84 -0
  23. package/blueprints/docs/checks/workspace.sh +26 -0
  24. package/blueprints/docs/manifest.json +12 -0
  25. package/blueprints/docs/plan.json +11 -0
  26. package/blueprints/docs/skills/workspace/SKILL.md +22 -0
  27. package/blueprints/firebase/checks/deploy-check.sh +18 -0
  28. package/blueprints/firebase/checks/emulator-test.sh +11 -0
  29. package/blueprints/firebase/checks/google-signin.sh +14 -0
  30. package/blueprints/firebase/checks/java21.sh +28 -0
  31. package/blueprints/firebase/checks/page-renders.sh +28 -0
  32. package/blueprints/firebase/checks/prerequisites.sh +18 -0
  33. package/blueprints/firebase/checks/project-id.sh +9 -0
  34. package/blueprints/firebase/checks/projects.sh +12 -0
  35. package/blueprints/firebase/checks/protect.sh +30 -0
  36. package/blueprints/firebase/checks/scaffold.sh +12 -0
  37. package/blueprints/firebase/checks/spec.sh +8 -0
  38. package/blueprints/firebase/guides/app-check.md +13 -0
  39. package/blueprints/firebase/guides/billing.md +13 -0
  40. package/blueprints/firebase/guides/google-signin.md +14 -0
  41. package/blueprints/firebase/guides/sign-in.md +14 -0
  42. package/blueprints/firebase/guides/try-it.md +6 -0
  43. package/blueprints/firebase/infra/firebase.json +59 -0
  44. package/blueprints/firebase/infra/firestore.indexes.json +4 -0
  45. package/blueprints/firebase/infra/firestore.rules +9 -0
  46. package/blueprints/firebase/manifest.json +39 -0
  47. package/blueprints/firebase/plan.json +71 -0
  48. package/blueprints/firebase/security/checklist.md +11 -0
  49. package/blueprints/firebase/skills/auth/SKILL.md +22 -0
  50. package/blueprints/firebase/skills/data-rules/SKILL.md +28 -0
  51. package/blueprints/firebase/skills/deploy-dev/SKILL.md +25 -0
  52. package/blueprints/firebase/skills/deploy-production/SKILL.md +24 -0
  53. package/blueprints/firebase/skills/prerequisites/SKILL.md +29 -0
  54. package/blueprints/firebase/skills/projects/SKILL.md +35 -0
  55. package/blueprints/firebase/skills/protect/SKILL.md +23 -0
  56. package/blueprints/firebase/skills/scaffold/SKILL.md +33 -0
  57. package/blueprints/firebase/skills/spec/SKILL.md +30 -0
  58. package/blueprints/firebase/spec/overview.md +34 -0
  59. package/blueprints/internal/checks/auth-config.sh +7 -0
  60. package/blueprints/internal/checks/blocking-registered.sh +12 -0
  61. package/blueprints/internal/checks/identity-platform.sh +11 -0
  62. package/blueprints/internal/guides/blocking-functions.md +13 -0
  63. package/blueprints/internal/guides/identity-platform.md +11 -0
  64. package/blueprints/internal/guides/try-refusal.md +15 -0
  65. package/blueprints/internal/hearing.json +112 -0
  66. package/blueprints/internal/infra/firestore.rules +38 -0
  67. package/blueprints/internal/manifest.json +8 -0
  68. package/blueprints/internal/presets.json +26 -0
  69. package/blueprints/internal/security/threats.md +9 -0
  70. package/blueprints/internal/skills/audit-log/SKILL.md +23 -0
  71. package/blueprints/internal/skills/domain-restriction/SKILL.md +44 -0
  72. package/blueprints/internal/skills/offboarding/SKILL.md +27 -0
  73. package/blueprints/internal/skills/production-auth/SKILL.md +26 -0
  74. package/blueprints/internal/skills/roles/SKILL.md +26 -0
  75. package/blueprints/internal/skills/verify-dev/SKILL.md +22 -0
  76. package/blueprints/internal/spec/requirements.md +32 -0
  77. package/blueprints/internal/steps.json +53 -0
  78. package/blueprints/local/checks/handover.sh +6 -0
  79. package/blueprints/local/checks/node-sqlite.sh +6 -0
  80. package/blueprints/local/checks/scaffold.sh +12 -0
  81. package/blueprints/local/checks/smoke.sh +34 -0
  82. package/blueprints/local/checks/spec.sh +8 -0
  83. package/blueprints/local/checks/tests-pass.sh +7 -0
  84. package/blueprints/local/manifest.json +21 -0
  85. package/blueprints/local/plan.json +68 -0
  86. package/blueprints/local/security/checklist.md +9 -0
  87. package/blueprints/local/skills/api/SKILL.md +22 -0
  88. package/blueprints/local/skills/auth/SKILL.md +27 -0
  89. package/blueprints/local/skills/data/SKILL.md +22 -0
  90. package/blueprints/local/skills/handover/SKILL.md +23 -0
  91. package/blueprints/local/skills/prerequisites/SKILL.md +22 -0
  92. package/blueprints/local/skills/run-check/SKILL.md +19 -0
  93. package/blueprints/local/skills/scaffold/SKILL.md +35 -0
  94. package/blueprints/local/skills/spec/SKILL.md +29 -0
  95. package/blueprints/local/skills/ui/SKILL.md +22 -0
  96. package/blueprints/local/spec/overview.md +33 -0
  97. package/blueprints/polish/checks/base.mjs +12 -0
  98. package/blueprints/polish/checks/feedback.mjs +9 -0
  99. package/blueprints/polish/checks/report.mjs +34 -0
  100. package/blueprints/polish/checks/targets.mjs +115 -0
  101. package/blueprints/polish/hearing.json +37 -0
  102. package/blueprints/polish/manifest.json +9 -0
  103. package/blueprints/polish/presets/oshirase/oshirase.md +7 -0
  104. package/blueprints/polish/presets.json +17 -0
  105. package/blueprints/polish/skills/polish/SKILL.md +60 -0
  106. package/blueprints/polish/skills/report/SKILL.md +34 -0
  107. package/blueprints/polish/skills/survey/SKILL.md +43 -0
  108. package/blueprints/polish/steps.json +27 -0
  109. package/blueprints/product/hearing.json +92 -0
  110. package/blueprints/product/manifest.json +8 -0
  111. package/blueprints/product/presets.json +101 -0
  112. package/blueprints/product/security/threats.md +15 -0
  113. package/blueprints/product/skills/acceptance/SKILL.md +26 -0
  114. package/blueprints/product/skills/acceptance-firebase/SKILL.md +27 -0
  115. package/blueprints/product/skills/features-firebase/SKILL.md +28 -0
  116. package/blueprints/product/spec/requirements.md +42 -0
  117. package/blueprints/product/steps.json +30 -0
  118. package/blueprints/refactor/checks/prs.sh +22 -0
  119. package/blueprints/refactor/checks/report.sh +6 -0
  120. package/blueprints/refactor/checks/targets.mjs +114 -0
  121. package/blueprints/refactor/checks/tranche.sh +10 -0
  122. package/blueprints/refactor/hearing.json +44 -0
  123. package/blueprints/refactor/manifest.json +9 -0
  124. package/blueprints/refactor/skills/report/SKILL.md +26 -0
  125. package/blueprints/refactor/skills/survey/SKILL.md +87 -0
  126. package/blueprints/refactor/skills/tranche/SKILL.md +172 -0
  127. package/blueprints/refactor/skills/tranche/cross-review.md +72 -0
  128. package/blueprints/refactor/steps.json +27 -0
  129. package/blueprints/repo/checks/ci-check.sh +36 -0
  130. package/blueprints/repo/checks/ci-recorded.sh +10 -0
  131. package/blueprints/repo/checks/default-branch.sh +4 -0
  132. package/blueprints/repo/checks/gates.sh +24 -0
  133. package/blueprints/repo/checks/repo-check.sh +14 -0
  134. package/blueprints/repo/guides/gh-login.md +12 -0
  135. package/blueprints/repo/guides/trust-repo.md +10 -0
  136. package/blueprints/repo/manifest.json +13 -0
  137. package/blueprints/repo/plan.json +25 -0
  138. package/blueprints/repo/skills/baseline/SKILL.md +42 -0
  139. package/blueprints/repo/skills/ci/SKILL.md +35 -0
  140. package/blueprints/repo/skills/state/SKILL.md +32 -0
  141. package/blueprints/review/checks/base.mjs +12 -0
  142. package/blueprints/review/checks/candidates.d.mts +11 -0
  143. package/blueprints/review/checks/candidates.mjs +43 -0
  144. package/blueprints/review/checks/feedback.mjs +14 -0
  145. package/blueprints/review/checks/findings.mjs +114 -0
  146. package/blueprints/review/checks/report.mjs +29 -0
  147. package/blueprints/review/hearing.json +31 -0
  148. package/blueprints/review/manifest.json +9 -0
  149. package/blueprints/review/presets/itaku-keiyaku/contract.txt +31 -0
  150. package/blueprints/review/presets.json +16 -0
  151. package/blueprints/review/skills/propose/SKILL.md +36 -0
  152. package/blueprints/review/skills/read/SKILL.md +77 -0
  153. package/blueprints/review/skills/report/SKILL.md +34 -0
  154. package/blueprints/review/steps.json +26 -0
  155. package/blueprints/style/checks/base.mjs +12 -0
  156. package/blueprints/style/checks/counter.mjs +29 -0
  157. package/blueprints/style/checks/report.mjs +18 -0
  158. package/blueprints/style/checks/rules.mjs +56 -0
  159. package/blueprints/style/checks/sources.mjs +37 -0
  160. package/blueprints/style/hearing.json +42 -0
  161. package/blueprints/style/manifest.json +9 -0
  162. package/blueprints/style/presets/shanai/tehon-1.md +19 -0
  163. package/blueprints/style/presets/shanai/tehon-2.md +15 -0
  164. package/blueprints/style/presets/shanai/tehon-3.md +24 -0
  165. package/blueprints/style/presets/shanai/tehon-4.md +23 -0
  166. package/blueprints/style/presets/shanai/tehon-5.md +20 -0
  167. package/blueprints/style/presets/shanai/tehon-6.md +20 -0
  168. package/blueprints/style/presets/shanai/tehon-7.md +25 -0
  169. package/blueprints/style/presets/shanai/tehon-8.md +24 -0
  170. package/blueprints/style/presets.json +17 -0
  171. package/blueprints/style/skills/counter/SKILL.md +29 -0
  172. package/blueprints/style/skills/report/SKILL.md +17 -0
  173. package/blueprints/style/skills/rules/SKILL.md +80 -0
  174. package/blueprints/style/skills/sources/SKILL.md +35 -0
  175. package/blueprints/style/steps.json +34 -0
  176. package/blueprints/verify/checks/base.mjs +12 -0
  177. package/blueprints/verify/checks/extract.mjs +35 -0
  178. package/blueprints/verify/checks/facts.d.mts +13 -0
  179. package/blueprints/verify/checks/facts.mjs +187 -0
  180. package/blueprints/verify/checks/report.mjs +51 -0
  181. package/blueprints/verify/checks/rules.d.mts +21 -0
  182. package/blueprints/verify/checks/rules.mjs +142 -0
  183. package/blueprints/verify/hearing.json +31 -0
  184. package/blueprints/verify/manifest.json +9 -0
  185. package/blueprints/verify/presets/osaka-kyoto/itinerary.md +29 -0
  186. package/blueprints/verify/presets.json +16 -0
  187. package/blueprints/verify/skills/extract/SKILL.md +64 -0
  188. package/blueprints/verify/skills/report/SKILL.md +39 -0
  189. package/blueprints/verify/steps.json +19 -0
  190. package/blueprints/write/checks/base.mjs +12 -0
  191. package/blueprints/write/checks/brief.mjs +42 -0
  192. package/blueprints/write/checks/feedback.mjs +10 -0
  193. package/blueprints/write/checks/parts.mjs +102 -0
  194. package/blueprints/write/checks/report.mjs +36 -0
  195. package/blueprints/write/hearing.json +35 -0
  196. package/blueprints/write/manifest.json +9 -0
  197. package/blueprints/write/presets.json +19 -0
  198. package/blueprints/write/skills/brief/SKILL.md +43 -0
  199. package/blueprints/write/skills/draft/SKILL.md +63 -0
  200. package/blueprints/write/skills/outline/SKILL.md +30 -0
  201. package/blueprints/write/skills/report/SKILL.md +35 -0
  202. package/blueprints/write/steps.json +35 -0
  203. package/common/blueprint/changedFiles.ts +38 -0
  204. package/common/blueprint/executorPolicy.ts +56 -0
  205. package/common/blueprint/hearing.ts +98 -0
  206. package/common/blueprint/manifest.ts +53 -0
  207. package/common/blueprint/plan.ts +88 -0
  208. package/common/blueprint/presets.ts +24 -0
  209. package/common/blueprint/refusal.ts +44 -0
  210. package/common/blueprint/registry.ts +82 -0
  211. package/common/blueprint/relativePath.ts +7 -0
  212. package/common/blueprint/run.ts +75 -0
  213. package/common/blueprint/samples.ts +24 -0
  214. package/common/blueprint/specRevisionPrompt.ts +36 -0
  215. package/common/blueprint/state.ts +193 -0
  216. package/common/blueprint/stepNotice.ts +29 -0
  217. package/common/blueprint/stepPrompt.ts +62 -0
  218. package/common/githubIcons.ts +36 -0
  219. package/common/keymap.ts +1 -0
  220. package/common/markedUnread.ts +6 -0
  221. package/dist/assets/{abnfDiagram-O67JEVCF-0EIO6Rib.js → abnfDiagram-O67JEVCF-BypT3U3a.js} +1 -1
  222. package/dist/assets/architecture-7GRP2DOG-Bbv-2KeL.js +1 -0
  223. package/dist/assets/{architectureDiagram-NJMV4G6O-Dv4vmoGJ.js → architectureDiagram-NJMV4G6O-CyObEvXl.js} +1 -1
  224. package/dist/assets/{blockDiagram-BEXU5L5S-DtAUYedX.js → blockDiagram-BEXU5L5S-D-HVBeq1.js} +1 -1
  225. package/dist/assets/{c4Diagram-YGBWAQC7-BUjpgg36.js → c4Diagram-YGBWAQC7-Qgd--us_.js} +1 -1
  226. package/dist/assets/channel-BHcWpvnw.js +1 -0
  227. package/dist/assets/{chunk-3FUC2YCW-BRaUCmDn.js → chunk-3FUC2YCW-BR-5aCbb.js} +2 -2
  228. package/dist/assets/{chunk-5DYCD2WN-BcCVJxNU.js → chunk-5DYCD2WN-Bo8hecoJ.js} +1 -1
  229. package/dist/assets/{chunk-742MDFTN-r7J0AnEC.js → chunk-742MDFTN-CD8LfZUq.js} +1 -1
  230. package/dist/assets/{chunk-7INBJB4K-BgjDPpSK.js → chunk-7INBJB4K-CX2KLcpU.js} +1 -1
  231. package/dist/assets/{chunk-7M6MHVWA-BkohiMAa.js → chunk-7M6MHVWA-EAATiixY.js} +1 -1
  232. package/dist/assets/{chunk-7PRAP22T-BgtGs2QT.js → chunk-7PRAP22T-kBBJ0KJk.js} +1 -1
  233. package/dist/assets/{chunk-GTNCS2PH-CabGHxDI.js → chunk-GTNCS2PH-BMtqOq18.js} +1 -1
  234. package/dist/assets/{chunk-GWA4HPMP-ByBWBjP8.js → chunk-GWA4HPMP-B0Mi72WR.js} +1 -1
  235. package/dist/assets/{chunk-MBY4JIJT-CoqxPGVJ.js → chunk-MBY4JIJT-DmHkAdrQ.js} +1 -1
  236. package/dist/assets/{chunk-NETBCI7D-C3TzGKdM.js → chunk-NETBCI7D-CXGvJJei.js} +1 -1
  237. package/dist/assets/{chunk-O7XYJQB3-C_ibJVHM.js → chunk-O7XYJQB3-WBMmFQya.js} +1 -1
  238. package/dist/assets/{chunk-UA2S7LBM-cdcwqQWa.js → chunk-UA2S7LBM-DaQ23UEn.js} +1 -1
  239. package/dist/assets/{chunk-WEXAMYUT-DpgQkB4Y.js → chunk-WEXAMYUT-DviQkb8I.js} +1 -1
  240. package/dist/assets/{chunk-Z7XXMR3K-43mpKAmk.js → chunk-Z7XXMR3K-DOtBujlQ.js} +1 -1
  241. package/dist/assets/{chunk-ZIGJFQKS-BqjohiQA.js → chunk-ZIGJFQKS-VtmgG3xL.js} +1 -1
  242. package/dist/assets/{classDiagram-v2-NBCMYWYE--RyRI2eN.js → classDiagram-v2-NBCMYWYE-ndrP6enZ.js} +1 -1
  243. package/dist/assets/{cynefin-OW5HDTMX-Dr5PAqCO.js → cynefin-OW5HDTMX-5iOYoFuE.js} +1 -1
  244. package/dist/assets/{cynefinDiagram-VND7K2PF-CsJyNlep.js → cynefinDiagram-VND7K2PF-DWaNHdmF.js} +1 -1
  245. package/dist/assets/{dagre-6A5THRUB-C5ja50BF.js → dagre-6A5THRUB-CRI-Ki8R.js} +1 -1
  246. package/dist/assets/{diagram-22UHCM2B-C4cl0V63.js → diagram-22UHCM2B-Cb4_8V3f.js} +1 -1
  247. package/dist/assets/{diagram-3UASUU5V-DV_eMiwc.js → diagram-3UASUU5V--mTYdfxz.js} +1 -1
  248. package/dist/assets/{diagram-ATOU4E4O-jzKf3iFX.js → diagram-ATOU4E4O-BwY4-yUt.js} +1 -1
  249. package/dist/assets/{diagram-CDSNMT55-BwFqusNo.js → diagram-CDSNMT55-CW8ENZKa.js} +1 -1
  250. package/dist/assets/{diagram-MLGK6HIB-CoMwibZN.js → diagram-MLGK6HIB-Ew0oj4NU.js} +1 -1
  251. package/dist/assets/{diagram-MPIPVDR6-CR1GehW4.js → diagram-MPIPVDR6-BK0uRGQB.js} +1 -1
  252. package/dist/assets/{dist-DzuN4wOr.js → dist-CNs34037.js} +1 -1
  253. package/dist/assets/{dist-Cl-PfToj.js → dist-Cp7rDLig.js} +1 -1
  254. package/dist/assets/{dist-BtevH0MI.js → dist-DRZ3y7AE.js} +1 -1
  255. package/dist/assets/{dist-4y2pKI__.js → dist-jO1cPpPw.js} +1 -1
  256. package/dist/assets/{ebnfDiagram-ZINNZB2B-DofcoTlD.js → ebnfDiagram-ZINNZB2B-DGu-I1_-.js} +1 -1
  257. package/dist/assets/{elk-276RUBZZ-BPEoPGYi.js → elk-276RUBZZ-IKdDujmV.js} +1 -1
  258. package/dist/assets/{erDiagram-OPXOYQCR-BW9UZdWF.js → erDiagram-OPXOYQCR-CLcXgJdC.js} +1 -1
  259. package/dist/assets/eventmodeling-NTZA5JFV-9UzRuTrz.js +1 -0
  260. package/dist/assets/flowDiagram-KWPJA3E3-Bq9iQFMs.js +1 -0
  261. package/dist/assets/{ganttDiagram-FUAMR5RP-QI2xjALT.js → ganttDiagram-FUAMR5RP-lfct59GT.js} +1 -1
  262. package/dist/assets/{gitGraph-4MIJSDKK-DwWl22rs.js → gitGraph-4MIJSDKK-Hr_vAl58.js} +1 -1
  263. package/dist/assets/{gitGraphDiagram-X574FWY7-OdBtq_J3.js → gitGraphDiagram-X574FWY7-DyTAObOG.js} +1 -1
  264. package/dist/assets/index-BHeY7dDr.css +1 -0
  265. package/dist/assets/{index-DZ3P7G29.js → index-BfCtnfM3.js} +410 -400
  266. package/dist/assets/{info-A6RAGUB7-D4dgNXV5.js → info-A6RAGUB7-C2RIrvn7.js} +1 -1
  267. package/dist/assets/{infoDiagram-VRGFBTTK-BLafPiGw.js → infoDiagram-VRGFBTTK-Cehr7nT0.js} +1 -1
  268. package/dist/assets/{ishikawaDiagram-OU5B5YK6-hNUEv0D2.js → ishikawaDiagram-OU5B5YK6-DNo8VfZy.js} +1 -1
  269. package/dist/assets/{journeyDiagram-ZHPQQLJL-COYfA96w.js → journeyDiagram-ZHPQQLJL-BjQHzZxG.js} +1 -1
  270. package/dist/assets/{kanban-definition-PNTS6WVX-C5jDklyw.js → kanban-definition-PNTS6WVX-CSr7kyll.js} +1 -1
  271. package/dist/assets/{line-D8KBsMtt.js → line-CmNgViQc.js} +1 -1
  272. package/dist/assets/{mermaid-parser.core-Dlf5XX-m.js → mermaid-parser.core-C_qcrA5s.js} +3 -3
  273. package/dist/assets/{mermaid.core-77q0N4Kj.js → mermaid.core-BG82qqEB.js} +4 -4
  274. package/dist/assets/{mindmap-definition-NLK3R4M7-Bh3ST-uc.js → mindmap-definition-NLK3R4M7-psSKkeLb.js} +1 -1
  275. package/dist/assets/{packet-AYTQ26CC-dzdfTl_4.js → packet-AYTQ26CC-BS5zpaST.js} +1 -1
  276. package/dist/assets/{pegDiagram-GJSIUBJH-HUFfoKUw.js → pegDiagram-GJSIUBJH-DhpL7ygV.js} +1 -1
  277. package/dist/assets/{pie-WAS4IAKB-0S5KvqDy.js → pie-WAS4IAKB-T5-dog1P.js} +1 -1
  278. package/dist/assets/{pieDiagram-5QR66LMP-BAUKrsuB.js → pieDiagram-5QR66LMP-CmXcDzDT.js} +1 -1
  279. package/dist/assets/{quadrantDiagram-O4NWA36T-WKXQPbHy.js → quadrantDiagram-O4NWA36T-BIDYLhBi.js} +1 -1
  280. package/dist/assets/{radar-RG4KPBEZ-CmvfbUxs.js → radar-RG4KPBEZ-_895RNt8.js} +1 -1
  281. package/dist/assets/{railroad-74A4TZTK-B1eU1nZI.js → railroad-74A4TZTK-Cz2ZyH7U.js} +1 -1
  282. package/dist/assets/railroad-abnf-HS5TGJTU-CTBfwbDI.js +1 -0
  283. package/dist/assets/railroad-ebnf-LZEXJU2U-BxMnP09p.js +1 -0
  284. package/dist/assets/railroad-peg-WCYAUIDC-VQLBtQ7q.js +1 -0
  285. package/dist/assets/{railroadDiagram-XR7U4H2S-DI3y2Box.js → railroadDiagram-XR7U4H2S-BQ1Mc95v.js} +1 -1
  286. package/dist/assets/{requirementDiagram-PLB6GJNP-BHVnyaGP.js → requirementDiagram-PLB6GJNP-ChVue_nk.js} +1 -1
  287. package/dist/assets/{sankeyDiagram-IPEJSGJF-DPhG8F4e.js → sankeyDiagram-IPEJSGJF-Cd9K0txb.js} +1 -1
  288. package/dist/assets/{sequenceDiagram-PO4LG4MO-BNnXeMyc.js → sequenceDiagram-PO4LG4MO-kiPuazKP.js} +1 -1
  289. package/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  290. package/dist/assets/{stateDiagram-v2-GCMORJYK-Dg80rzkM.js → stateDiagram-v2-GCMORJYK-BrYqOUDC.js} +1 -1
  291. package/dist/assets/{swimlanes-2SLR337P-BjIVtOvB.js → swimlanes-2SLR337P-BGv93v9F.js} +1 -1
  292. package/dist/assets/swimlanesDiagram-TC7HE7FX-XHEHPbk9.js +8 -0
  293. package/dist/assets/{timeline-definition-EJHVYXUP-CWVsm4DO.js → timeline-definition-EJHVYXUP-B2m2999q.js} +1 -1
  294. package/dist/assets/{treeView-Q6P3EWNA-VIzH5Ryp.js → treeView-Q6P3EWNA-Bp2HlEG5.js} +1 -1
  295. package/dist/assets/{treemap-WGGIJYW6-DIurGTrO.js → treemap-WGGIJYW6-BBhRxIiH.js} +1 -1
  296. package/dist/assets/{usecaseDiagram-POWQR4AR-CGjjyVXQ.js → usecaseDiagram-POWQR4AR-uFIx7ToF.js} +1 -1
  297. package/dist/assets/{vennDiagram-UO4OBE2U-D25_R90R.js → vennDiagram-UO4OBE2U-BQGzVBTB.js} +1 -1
  298. package/dist/assets/{wardley-WFR3VGLG-AZXL1jj9.js → wardley-WFR3VGLG-BQk6DDg6.js} +1 -1
  299. package/dist/assets/{wardleyDiagram-VNRHLVJA-D3g7xxxA.js → wardleyDiagram-VNRHLVJA-q-_GY-qU.js} +1 -1
  300. package/dist/assets/{xychartDiagram-PMCCYNJV-C5FxR_Fd.js → xychartDiagram-PMCCYNJV-B8D1QFx-.js} +1 -1
  301. package/dist/index.html +2 -2
  302. package/package.json +22 -21
  303. package/server/blueprint/answersFile.ts +11 -0
  304. package/server/blueprint/checkRunner.ts +78 -0
  305. package/server/blueprint/executor.ts +490 -0
  306. package/server/blueprint/executorLock.ts +96 -0
  307. package/server/blueprint/installer.ts +142 -0
  308. package/server/blueprint/marketRoutes.ts +104 -0
  309. package/server/blueprint/packs.ts +141 -0
  310. package/server/blueprint/projectFiles.ts +112 -0
  311. package/server/blueprint/registry.ts +68 -0
  312. package/server/blueprint/routes.ts +222 -0
  313. package/server/blueprint/runStore.ts +49 -0
  314. package/server/blueprint/samples.ts +53 -0
  315. package/server/blueprint/trust.ts +99 -0
  316. package/server/blueprint/wiring.ts +167 -0
  317. package/server/config/app-config.ts +2 -2
  318. package/server/config/config-schema.ts +25 -1
  319. package/server/config/dir-config.ts +2 -2
  320. package/server/config/header-config.ts +87 -28
  321. package/server/config/header-resolve.ts +27 -5
  322. package/server/git/spawn-collect.ts +11 -2
  323. package/server/index.ts +1 -1
  324. package/server/routes/app-routes.ts +3 -0
  325. package/server/session/pty-connection.ts +31 -12
  326. package/server/skills/mulmoterminal-header/SKILL.md +37 -13
  327. package/server/skills/mulmoterminal-keys/SKILL.md +4 -2
  328. package/common/githubPaneOrder.ts +0 -83
  329. package/dist/assets/architecture-7GRP2DOG-BrJq_YrR.js +0 -1
  330. package/dist/assets/channel-1p-80xtZ.js +0 -1
  331. package/dist/assets/eventmodeling-NTZA5JFV-B13TyR96.js +0 -1
  332. package/dist/assets/flowDiagram-KWPJA3E3-CU0tsDCp.js +0 -1
  333. package/dist/assets/index-BMqji566.css +0 -1
  334. package/dist/assets/railroad-abnf-HS5TGJTU-Buwlc453.js +0 -1
  335. package/dist/assets/railroad-ebnf-LZEXJU2U-CTzd-w0L.js +0 -1
  336. package/dist/assets/railroad-peg-WCYAUIDC-BzSzMdhx.js +0 -1
  337. package/dist/assets/sizeCapture-INFHLROL-B0uUizjq.js +0 -1
  338. package/dist/assets/swimlanesDiagram-TC7HE7FX-_i8YJGRx.js +0 -8
package/README.md CHANGED
@@ -63,7 +63,9 @@ npx mulmoterminal@latest # starts on http://localhost:34567 and opens you
63
63
  Requires **Node ≥ 22.12**, and by default the [`claude`](https://claude.com/claude-code) CLI —
64
64
  on your `PATH` or named by `CLAUDE_BIN`, already logged in. Declare a different default agent
65
65
  (`--agent codex`, or `"defaultAgent"` in `~/.mulmoterminal/config.json`) and start-up checks that
66
- one instead. `npx mulmoterminal@latest init` reports what it can't find.
66
+ one instead. `npx mulmoterminal@latest init` reports what it can't find. On an older Node it stops
67
+ before starting the server, with a banner and the upgrade commands for however that Node was
68
+ installed.
67
69
 
68
70
  ### Why not tmux + iTerm panes?
69
71
 
@@ -277,7 +279,7 @@ Needs **Node ≥ 22.12**, plus these CLIs — on your `PATH`, or named by the ma
277
279
  | Optional | any other agent CLI | a cell can run **Codex**, **Antigravity** (`agy`), **Grok**, **Muse**, **GitHub Copilot CLI** or **Cursor CLI** instead of Claude — install only the ones you use, and a missing one simply fails to start that cell. What each can do is [the capability matrix](docs/agent-capability-matrix.md); how to install and pick one is the [agents guide](https://receptron.github.io/mulmoterminal/guide/en/agents.html) | e.g. `npm i -g @openai/codex` |
278
280
  | Optional | `ffmpeg` | video rendering from the [mulmo-script panel](#wiki-collections--the-gui-panel) (its plugin ships enabled) | `brew install ffmpeg` · `sudo apt install ffmpeg` · `sudo dnf install ffmpeg` |
279
281
  | Optional | `ollama` | [`claude-ollama`](https://receptron.github.io/mulmoterminal/guide/en/claude-ollama.html) — Claude Code against a fully local model | [ollama.com/download](https://ollama.com/download) |
280
- | Linux only | a file dialog | the **Choose a folder / Insert a file path** buttons, which open an OS dialog on the machine the server runs on. macOS and Windows have one built in; **WSL** uses the Windows one over interop and needs nothing installed. A Linux desktop needs one of these — without any, the buttons say so and you type the path instead (#1447) | `sudo apt install zenity` · `sudo dnf install zenity` · `kdialog`, `qarma` and `yad` also work |
282
+ | Linux only | a file dialog | the launcher's **Choose a folder** button and the path menu's **Insert a file path**, which open an OS dialog on the machine the server runs on. macOS and Windows have one built in; **WSL** uses the Windows one over interop and needs nothing installed. A Linux desktop needs one of these — without any, they say so and you type the path instead (#1447) | `sudo apt install zenity` · `sudo dnf install zenity` · `kdialog`, `qarma` and `yad` also work |
281
283
 
282
284
  The server starts without any of the non-required rows; you just lose that row's feature,
283
285
  and the header/panel for it says so. `git` and `gh` are marked required because losing them
@@ -810,9 +812,9 @@ easier to debug than one that silently vanished. See the
810
812
  #### Header buttons
811
813
 
812
814
  Each terminal header shows configurable **action buttons**. Omitting `buttons` (globally or per-dir)
813
- keeps the built-in **starter set**: a file-path picker (📎), an OS file-manager reveal (📂), an in-app
814
- file explorer (📁), a new terminal here (🖥), this branch's PR (🔗, git repos, only when a PR exists),
815
- and open-on-GitHub (🌐, git repos). Setting `buttons` (at either level) **replaces the whole default
815
+ keeps the built-in **starter set**: this branch's PR (git repos, only when a PR exists). Inserting a
816
+ file path, revealing the folder, the in-app file explorer, a new terminal here and the GitHub links
817
+ are items in the **path menu** (click the directory path on the terminal header). Setting `buttons` (at either level) **replaces the whole default
816
818
  set** with your list (it is not merged on top), so listing your own — even a **shorter** one — is how
817
819
  you drop, reorder, or swap them.
818
820
  A button has an `id`, `label`, and a `run` of `"shell"` (run a command), `"input"` (send text to the
@@ -822,6 +824,8 @@ change takes effect; it costs a resume and asks nothing first). An `open` button
822
824
  (in-app explorer) / `view` (a built-in overlay) / `terminal` (a dir → a new cell running `$SHELL`,
823
825
  opened next to the current one) / `pr: true` (open the current branch's PR — the button is hidden when
824
826
  there's no open PR) / `pickFile: true` (OS file dialog → insert the path).
827
+ An entry with `items` instead of `run` is a **folder**: one icon that opens a menu of the buttons
828
+ inside it (one level only — a folder cannot hold a folder).
825
829
  `${dir}`, `${branch}`, `${repo}`, … substitute live context, and `when` (e.g. `"isGitRepo"`) gates
826
830
  visibility. The `/mulmoterminal-header` skill writes a valid config interactively; per-dir buttons
827
831
  merge over the global ones by `id`, while `chips` replace the global list wholesale.
@@ -988,7 +992,7 @@ Settings → Directory settings names both files and lists which keys the local
988
992
  | Field | Meaning |
989
993
  | ------------ | ------- |
990
994
  | `name` | Label shown as a badge in the terminal/cell header. |
991
- | `icon` | An **image** marking this directory — shown in the cell header, the cockpit roster, the filmstrip thumbnails, the launcher's directory chips, and the phone's terminal list and terminal screen. Either a path **relative to this directory** (an absolute path, or a `../` that escapes it, is rejected), an `http(s)://` URL, or a `data:image/…` URI. PNG / JPEG / **GIF (animated plays)** / WebP / AVIF / SVG / ICO / BMP. Not to be confused with a header **button's** `icon`, which is a Material Symbols name, or with the small glyph a chat started from a **collection** wears beside its status dot — that one is the collection's own `icon`, and it says which collection the cell was opened for while this one says which directory it runs in. **Omit it and the repository's own favicon is used** (`public/favicon.svg`, `apple-touch-icon.png`, a web manifest — see `autoDirIcon`); `false` means no icon here and stops that search. |
995
+ | `icon` | An **image** marking this directory — shown in the cell header, the cockpit roster, the filmstrip thumbnails, the launcher's directory chips, and the phone's terminal list and terminal screen. Either a path **relative to this directory** (an absolute path, or a `../` that escapes it, is rejected), an `http(s)://` URL, or a `data:image/…` URI. PNG / JPEG / **GIF (animated plays)** / WebP / AVIF / SVG / ICO / BMP. Not to be confused with a header **button's** `icon`, which is a Material Symbols name (or `github:<name>` for one of GitHub's icons), or with the small glyph a chat started from a **collection** wears beside its status dot — that one is the collection's own `icon`, and it says which collection the cell was opened for while this one says which directory it runs in. **Omit it and the repository's own favicon is used** (`public/favicon.svg`, `apple-touch-icon.png`, a web manifest — see `autoDirIcon`); `false` means no icon here and stops that search. |
992
996
  | `backgroundImage` | A **picture shown faintly behind this directory's terminals**, blended with the text so it stays readable on either theme. A string is the image (15% opaque, fills the terminal); `{ "image", "opacity", "fit" }` sets how faint (above 0, at most 1) and whether it crops to fill (`cover`) or shows whole (`contain`). The image follows `icon`'s rules. |
993
997
  | `badgeColor` | Badge background color (`#rrggbb`); text auto-contrasts. |
994
998
  | `headerColor` | Header **background** color (`#rrggbb`) — the grid cell's header row and the terminal's own header row (grid row 2). While a terminal is working/blocked the status tint still shows; the custom color applies when idle. |
@@ -1422,8 +1426,8 @@ A worktree cell's header carries a **diff badge** (`+<commits> ●<dirty>`); cli
1422
1426
  Closing a worktree cell asks whether to **keep** the worktree or **discard & remove** it
1423
1427
  (a dirty worktree is never removed unless you confirm).
1424
1428
 
1425
- **PRs & Issues (cross-repo).** The toolbar's **Pull requests** button opens a full-screen
1426
- view that aggregates open PRs **and** issues across the repos listed in Settings →
1429
+ **PRs & Issues (cross-repo).** The toolbar's **Pull requests** button (shown once at least one
1430
+ repository is listed) opens a full-screen view that aggregates open PRs **and** issues across the repos listed in Settings →
1427
1431
  **Pull request repos** (`prRepos`, `owner/repo` entries, or `gitlab.com/group/project` — plus any host declared in `gitlabHosts`) via your server-side `gh` / `glab` login.
1428
1432
  PRs show a CI-rollup / review-decision / draft badge; each repo lists its latest open
1429
1433
  issues. Rows are real links, per-repo errors don't sink the view, and the two lists load
@@ -1504,8 +1508,8 @@ The **Settings** modal (the gear button) shows an **estimated $ cost** — Sessi
1504
1508
  0.1×, cache writes at 1.25× input). It's an estimate: real billing differs, **flat-plan
1505
1509
  (Max) usage isn't reflected**, and turns on unpriced models are flagged and excluded.
1506
1510
 
1507
- A separate, full **double-entry accounting** book (the `account_balance` toolbar button →
1508
- `/accounting`) is provided by the bundled `@mulmoclaude/accounting-plugin` and stores its
1511
+ A separate, full **double-entry accounting** book (the `account_balance` button first on the
1512
+ Collections screen's top row → `/accounting`) is provided by the bundled `@mulmoclaude/accounting-plugin` and stores its
1509
1513
  books under `<workspace>/data/accounting`. It's a bookkeeping app — unrelated to the LLM
1510
1514
  cost estimate above — and is also exposed to Claude as the `manageAccounting` GUI tool.
1511
1515
 
@@ -1694,7 +1698,8 @@ narrow and tall for one record you are discussing — and each position keeps it
1694
1698
  receives those as input. A cell that **stops for a permission prompt comes back to full strength
1695
1699
  on its own**, so setting one aside can never hide a session that is waiting on you; a merely *finished* turn
1696
1700
  does not, since that is the expected outcome of setting a running agent aside.
1697
- - **Timeline** (🕘) — a read-only per-session activity timeline (tools run, newest first),
1701
+ - **History menu** (on the cell header) — the Prompts and Conversation panes below, and the
1702
+ **Timeline**: a read-only per-session activity timeline (tools run, newest first),
1698
1703
  from `GET /api/transcript/timeline`.
1699
1704
  - **Bring another cell's turn here** (💬) — pick another terminal in the grid and its
1700
1705
  **last completed turn** is pasted into *this* cell's input box, so you can have Claude
@@ -1703,7 +1708,7 @@ narrow and tall for one record you are discussing — and each position keeps it
1703
1708
  ANSI debris and nothing lost to scrollback. It is **pasted, never sent** — you read
1704
1709
  what arrived and press Enter, in the cell you were already in. A turn still running
1705
1710
  isn't available yet (Codex writes its rollout only once the turn ends).
1706
- - **Tools pane** — the available GUI tools plus a live tool-call history for the active
1711
+ - **Tools pane** (the cell header's **Tools** menu, beside the Canvas, Collections and **Restart the agent**) — the available GUI tools plus a live tool-call history for the active
1707
1712
  session.
1708
1713
  - **Prompts pane** — the prompts *you* sent the enlarged cell's session, newest first, from
1709
1714
  `GET /api/transcript/prompts`. The mirror of the Timeline above: that one is what the agent
package/bin/cli-args.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { NodeUpgradeGuide } from "./node-install.js";
1
2
  export type PortChoice = { port: number; explicit: boolean } | { error: string };
2
3
  export type CwdChoice = { path: string; mustExist: boolean } | { error: string };
3
4
  export declare function parsePortArg(args: string[], env: Record<string, string | undefined>, defaultPort: number): PortChoice;
@@ -16,6 +17,7 @@ export declare function saysYes(answer: unknown): boolean;
16
17
  export declare const SECOND_INSTANCE_NOTE: string;
17
18
  export declare const MIN_NODE_LABEL: string;
18
19
  export declare function nodeMeetsMinimum(version: string): boolean;
20
+ export declare function unsupportedNodeMessage(version: string, execPath: string, upgrade: NodeUpgradeGuide): string;
19
21
  export declare function serverNodeArgs(serverEntry: string, launchDir: string, port: number, declaredAgent?: string | null): string[];
20
22
  export declare function serverSpawnEnv(env: Record<string, string | undefined>, cwd: string): Record<string, string | undefined>;
21
23
  export interface RunningInstance {
package/bin/cli-args.js CHANGED
@@ -11,6 +11,7 @@
11
11
  // environment or the filesystem.
12
12
  import { isIP } from "node:net";
13
13
  import { join } from "node:path";
14
+ import { NODE_DOWNLOAD_URL } from "./node-install.js";
14
15
 
15
16
  // The v4 loopback every local client of this server dials by literal — `guiMcpUrlTemplate` in
16
17
  // server/infra/gui-mcp-registration.ts builds `http://127.0.0.1:<port>/api/mcp/...`. Duplicated
@@ -390,24 +391,61 @@ const MIN_NODE_MAJOR = 22;
390
391
  const MIN_NODE_MINOR = 12;
391
392
 
392
393
  /**
393
- * The lowest Node the `init` check reports as good, for the "needs ≥ x.y" line.
394
+ * The lowest Node MulmoTerminal runs on, for the "needs ≥ x.y" line.
394
395
  */
395
396
  export const MIN_NODE_LABEL = `${MIN_NODE_MAJOR}.${MIN_NODE_MINOR}`;
396
397
 
397
398
  /**
398
- * Whether the running Node is new enough for the `init` pre-flight tick.
399
+ * Whether the running Node is new enough — the `init` tick, and the gate on a normal launch.
399
400
  *
400
401
  * `process.versions.node` is "major.minor.patch", with a "-prerelease" tag on the patch for
401
402
  * nightlies ("22.12.0-nightly…"). Only major.minor gate, and Number.parseInt stops at the
402
403
  * first non-digit, so that tag never reaches the comparison. A string that is not a version
403
404
  * parses to NaN, and every comparison against NaN is false, so an unreadable version reads as
404
- * "below minimum" — the safe direction for a display-only check.
405
+ * "below minimum". Node's own `process.versions.node` is always readable, so that only ever
406
+ * decides a malformed string passed in a test.
405
407
  */
406
408
  export function nodeMeetsMinimum(version) {
407
409
  const [major, minor] = version.split(".").map((part) => Number.parseInt(part, 10));
408
410
  return major > MIN_NODE_MAJOR || (major === MIN_NODE_MAJOR && minor >= MIN_NODE_MINOR);
409
411
  }
410
412
 
413
+ const NODE_TOO_OLD_ART = [
414
+ " ## ## ###### ######## ##########",
415
+ " #### ## ## ## ## ## ##",
416
+ " ## ## ## ## ## ## ## ########",
417
+ " ## #### ## ## ## ## ##",
418
+ " ## ## ###### ######## ##########",
419
+ "",
420
+ " ########## ###### ###### ###### ## ########",
421
+ " ## ## ## ## ## ## ## ## ## ##",
422
+ " ## ## ## ## ## ## ## ## ## ##",
423
+ " ## ## ## ## ## ## ## ## ## ##",
424
+ " ## ###### ###### ###### ########## ########",
425
+ ];
426
+
427
+ /**
428
+ * What a normal launch prints before stopping on a Node below the minimum.
429
+ *
430
+ * Without it the first thing to fail is the server spawn itself — Node 20 rejects
431
+ * `--env-file-if-exists` with "bad option", which reads as a `.env` problem rather than an old
432
+ * Node (#2351). The banner is large so it is not mistaken for one more line of startup output.
433
+ * `upgrade` is `nodeUpgradeGuide`'s answer for this Node.
434
+ */
435
+ export function unsupportedNodeMessage(version, execPath, upgrade) {
436
+ const lines = ["", ...NODE_TOO_OLD_ART, "", ` Node ${version} is not supported. MulmoTerminal needs Node ≥ ${MIN_NODE_LABEL}.`, ` Running: ${execPath}`];
437
+ if (upgrade.via) lines.push(` Installed with: ${upgrade.via}`);
438
+ lines.push("");
439
+ if (upgrade.commands.length > 0) {
440
+ lines.push(" To upgrade, run:", "", ...upgrade.commands.map((command) => ` ${command}`), "");
441
+ lines.push(` Node LTS downloads: ${NODE_DOWNLOAD_URL}`);
442
+ } else {
443
+ lines.push(` To upgrade, install the LTS from ${NODE_DOWNLOAD_URL}`);
444
+ }
445
+ lines.push(" Then open a new terminal and run the same command again.", "");
446
+ return lines.join("\n");
447
+ }
448
+
411
449
  /**
412
450
  * The node arguments the launcher spawns the server with.
413
451
  *
@@ -31,6 +31,7 @@ import {
31
31
  stopCommandFor,
32
32
  SECOND_INSTANCE_NOTE,
33
33
  nodeMeetsMinimum,
34
+ unsupportedNodeMessage,
34
35
  MIN_NODE_LABEL,
35
36
  serverNodeArgs,
36
37
  serverSpawnEnv,
@@ -41,6 +42,7 @@ import { liveInstances, servingInstances } from "./instances.js";
41
42
  import { agentBin, AGENT_BIN_SPEC } from "./agent-bins.js";
42
43
  import { configuredDefaultAgent, gateFor, isKnownAgent, missingAgentMessage, parseAgentArg, resolveDeclaredAgent } from "./default-agent.js";
43
44
  import { setProcessTitle } from "./process-title.js";
45
+ import { nodeUpgradeGuide } from "./node-install.js";
44
46
 
45
47
  const __dirname = dirname(fileURLToPath(import.meta.url));
46
48
  const PKG_DIR = join(__dirname, "..");
@@ -740,6 +742,14 @@ async function main() {
740
742
  return;
741
743
  }
742
744
 
745
+ // Before anything that spawns: on an old Node the server dies on its own node flags, and what
746
+ // it prints says nothing about the version.
747
+ if (!nodeMeetsMinimum(process.versions.node)) {
748
+ const upgrade = nodeUpgradeGuide(process.execPath, process.platform, process.env);
749
+ console.error(unsupportedNodeMessage(process.versions.node, process.execPath, upgrade));
750
+ process.exit(1);
751
+ }
752
+
743
753
  checkForUpdate();
744
754
 
745
755
  const declaredAgent = passStartupGate(args);
@@ -0,0 +1,6 @@
1
+ export interface NodeUpgradeGuide {
2
+ via: string | null;
3
+ commands: string[];
4
+ }
5
+ export declare const NODE_DOWNLOAD_URL: string;
6
+ export declare function nodeUpgradeGuide(execPath: string, platform: string, env: Record<string, string | undefined>): NodeUpgradeGuide;
@@ -0,0 +1,81 @@
1
+ // How to upgrade THIS Node, worked out from where it is installed (#2351).
2
+ //
3
+ // "Install the LTS from nodejs.org" is wrong advice for most people who hit the version gate: a
4
+ // Node that came from a version manager is shadowed by it, so a fresh installer leaves `node` on
5
+ // PATH exactly as old as before. The binary's own path says which tool put it there.
6
+ //
7
+ // Every command below was checked against that tool's own help or README. A tool whose upgrade
8
+ // command could not be confirmed gets no command — its readers still get the download link.
9
+
10
+ export const NODE_DOWNLOAD_URL = "https://nodejs.org/en/download";
11
+
12
+ // fnm's `default` takes a version, and it has no documented name for "the latest LTS".
13
+ // Any major at or above the minimum works; this one was the Active LTS when written.
14
+ const SUGGESTED_NODE_MAJOR = 24;
15
+
16
+ const toMatchable = (execPath) => execPath.replaceAll("\\", "/").toLowerCase();
17
+
18
+ const withoutTrailingSlashes = (path) => (path.endsWith("/") ? withoutTrailingSlashes(path.slice(0, -1)) : path);
19
+
20
+ // Boundary-aware, so `C:\Program Files\nodejs-old` is not inside `C:\Program Files\nodejs`.
21
+ function isInsideDir(path, dir) {
22
+ if (typeof dir !== "string") return false;
23
+ const prefix = withoutTrailingSlashes(toMatchable(dir));
24
+ return prefix !== "" && path.startsWith(`${prefix}/`);
25
+ }
26
+
27
+ // The app id is read off a path and printed as a command to paste, so anything beyond a plain
28
+ // manifest name gets no command rather than a second one smuggled in by `&` or a space.
29
+ const isPlainScoopAppId = (id) => /^[a-z0-9][a-z0-9._-]*$/.test(id);
30
+
31
+ const guide = (via, commands) => ({ via, commands });
32
+
33
+ function versionManagerGuide(path) {
34
+ if (path.includes("/.nodebrew/")) return guide("nodebrew", ["nodebrew install stable", "nodebrew use stable"]);
35
+ if (path.includes("/.nvm/versions/node/")) return guide("nvm", ["nvm install --lts", "nvm alias default 'lts/*'"]);
36
+ if (path.includes("volta/tools/image/node/")) return guide("Volta", ["volta install node"]);
37
+ if (path.includes("/fnm/") || path.includes("/fnm_multishells/")) {
38
+ return guide("fnm", [`fnm install ${SUGGESTED_NODE_MAJOR}`, `fnm default ${SUGGESTED_NODE_MAJOR}`]);
39
+ }
40
+ if (path.includes("/mise/installs/node/")) return guide("mise", ["mise use -g node@lts"]);
41
+ if (path.includes("/.asdf/installs/nodejs/")) return guide("asdf", ["asdf install nodejs latest", "asdf set -u nodejs $(asdf latest nodejs)"]);
42
+ return null;
43
+ }
44
+
45
+ function packageManagerGuide(path) {
46
+ const scoopApp = /\/scoop\/apps\/([^/]+)\//.exec(path)?.[1];
47
+ if (scoopApp) return guide("Scoop", isPlainScoopAppId(scoopApp) ? [`scoop update ${scoopApp}`] : []);
48
+ // Homebrew resolves `node` to its keg, so the formula is in the path: `node`, or a pinned `node@20`.
49
+ const formula = /\/cellar\/(node|node@\d+)\//.exec(path)?.[1];
50
+ if (formula === "node") return guide("Homebrew", ["brew upgrade node"]);
51
+ if (formula) return guide(`Homebrew (${formula})`, [`brew unlink ${formula}`, "brew install node"]);
52
+ return null;
53
+ }
54
+
55
+ // nvm-windows points one fixed symlink (NVM_SYMLINK, by default C:\Program Files\nodejs — the
56
+ // installer's own directory) at the active version, so only its environment tells the two apart.
57
+ function nvmWindowsGuide(path, env) {
58
+ if (!isInsideDir(path, env.NVM_SYMLINK) && !isInsideDir(path, env.NVM_HOME)) return null;
59
+ return guide("nvm-windows", ["nvm install lts", "nvm use lts"]);
60
+ }
61
+
62
+ // The .msi installs to `Program Files\nodejs` (or its x86 twin) on whichever drive Windows is on.
63
+ const isWindowsInstallerPath = (path) => /\/program files( \(x86\))?\/nodejs\/node\.exe$/.test(path);
64
+
65
+ function installerGuide(path, platform) {
66
+ if (platform === "win32" && isWindowsInstallerPath(path)) return guide("the Windows installer", []);
67
+ if (platform === "darwin" && path === "/usr/local/bin/node") return guide("the macOS installer from nodejs.org", []);
68
+ if (platform === "linux" && path === "/usr/bin/node") return guide("your system's package manager", []);
69
+ return guide(null, []);
70
+ }
71
+
72
+ /**
73
+ * How the running Node was installed, and the commands that upgrade it there.
74
+ *
75
+ * `via` is null when the path matches nothing known; `commands` is empty when the only honest
76
+ * advice is the download page.
77
+ */
78
+ export function nodeUpgradeGuide(execPath, platform, env) {
79
+ const path = toMatchable(execPath);
80
+ return (platform === "win32" ? nvmWindowsGuide(path, env) : null) ?? versionManagerGuide(path) ?? packageManagerGuide(path) ?? installerGuide(path, platform);
81
+ }
@@ -0,0 +1,12 @@
1
+ // The docs base holds the helpers every usecase on it shares (running chaff, reading Markdown sections).
2
+ // A usecase loads them from BLUEPRINT_BASE, which the executor sets for every check.
3
+ import { join } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+
6
+ if (!process.env.BLUEPRINT_BASE) {
7
+ console.error("BLUEPRINT_BASE is not set: run this check through the blueprint executor");
8
+ process.exit(1);
9
+ }
10
+
11
+ /** The URL of a module in the docs base's checks/, for `await import(...)`. */
12
+ export const fromBase = (file) => pathToFileURL(join(process.env.BLUEPRINT_BASE, "checks", file)).href;
@@ -0,0 +1,99 @@
1
+ // Reads .blueprint/replies.json — an answer to each question the person asked — and answers one question
2
+ // per mode. Exit 0 is yes.
3
+ // answer every question has exactly one reply; a reply found in the documents quotes them (chaff cite)
4
+ // and names every address it quotes; a reply not found says what was searched. Records the
5
+ // documents' and FAQ.md's fingerprints.
6
+ // keep the documents are unchanged; FAQ.md holds every question after what it held before when the
7
+ // person asked to keep the answers, and is untouched otherwise
8
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
9
+ import { fromBase } from "./base.mjs";
10
+ const { fail, quotationProblems, readJson } = await import(fromBase("chaff.mjs"));
11
+ const { digest, documentSource, documentsNamed, fingerprint } = await import(fromBase("documents.mjs"));
12
+
13
+ const REPLIES = ".blueprint/replies.json";
14
+ const REPLIES_PAGE = ".blueprint/replies.md";
15
+ const RECORDED = ".blueprint/.replies-before.json";
16
+ const FAQ = "FAQ.md";
17
+ const KEEP = "このフォルダの FAQ.md に書き足す";
18
+
19
+ const answers = readJson(".blueprint/answers.json", "the interview answers");
20
+ const documents = documentsNamed(answers?.documents);
21
+ const questions = String(answers?.questions ?? "")
22
+ .split("\n")
23
+ .map((line) => line.trim())
24
+ .filter((line) => line !== "");
25
+ if (questions.length === 0) fail("the interview asks no question");
26
+ if (new Set(questions).size !== questions.length) fail("the interview asks the same question twice");
27
+ if (answers?.keep === KEEP && documents.includes(FAQ)) fail(`${FAQ} is one of the documents, so the answers cannot be added to it`);
28
+
29
+ const indented = (line) => " " + line;
30
+ const nonEmpty = (value) => typeof value === "string" && value.trim() !== "";
31
+
32
+ const replyProblem = (reply) => {
33
+ if (typeof reply !== "object" || reply === null) return "a reply is not an object";
34
+ if (!nonEmpty(reply.question)) return "a reply names no question";
35
+ const which = JSON.stringify(reply.question);
36
+ if (typeof reply.found !== "boolean") return `${which}: "found" must be true or false`;
37
+ if (!nonEmpty(reply.answer)) return `${which}: no answer`;
38
+ const citations = reply.citations ?? [];
39
+ if (!Array.isArray(citations)) return `${which}: citations must be an array`;
40
+ if (reply.found && citations.length === 0) return `${which}: an answer found in the documents quotes them`;
41
+ if (!citations.every((citation) => nonEmpty(citation?.address))) return `${which}: every quotation needs an "address"`;
42
+ const unnamed = citations.filter((citation) => !reply.answer.includes(citation.address.trim()));
43
+ if (unnamed.length > 0) return `${which}: the answer does not name ${unnamed.map((citation) => citation.address).join(", ")}, which it quotes`;
44
+ if (!reply.found && (!Array.isArray(reply.searched) || !reply.searched.some(nonEmpty))) return `${which}: say what was searched ("searched")`;
45
+ return null;
46
+ };
47
+
48
+ const readReplies = () => {
49
+ const record = readJson(REPLIES, '{ "replies": [{ "question", "found", "answer", "citations", "searched" }] }');
50
+ const replies = Array.isArray(record?.replies) ? record.replies : fail(`${REPLIES} needs a "replies" array`);
51
+ const problem = replies.map(replyProblem).find((found) => found !== null);
52
+ if (problem) fail(`${REPLIES}: ${problem}`);
53
+ const asked = replies.map((reply) => reply.question.trim());
54
+ const unanswered = questions.filter((question) => !asked.includes(question));
55
+ if (unanswered.length > 0) fail(`no reply to:\n${unanswered.map(indented).join("\n")}`);
56
+ const extra = asked.filter((question) => !questions.includes(question));
57
+ if (extra.length > 0) fail(`replies to questions nobody asked:\n${extra.map(indented).join("\n")}`);
58
+ if (new Set(asked).size !== asked.length) fail(`${REPLIES}: a question is answered twice`);
59
+ return replies;
60
+ };
61
+
62
+ const faqState = () => (existsSync(FAQ) ? { bytes: readFileSync(FAQ).length, hash: fingerprint(FAQ) } : null);
63
+
64
+ const mode = process.argv[2];
65
+
66
+ if (mode === "answer") {
67
+ const replies = readReplies();
68
+ const quoted = replies.flatMap((reply) => quotationProblems(JSON.stringify(reply.question), reply.citations ?? [], documentSource(documents)));
69
+ if (quoted.length > 0) fail(quoted.join("\n"));
70
+ if (!existsSync(REPLIES_PAGE)) fail(`${REPLIES_PAGE} is missing: the replies for the person to read`);
71
+ const page = readFileSync(REPLIES_PAGE, "utf8");
72
+ const absent = questions.filter((question) => !page.includes(question));
73
+ if (absent.length > 0) fail(`${REPLIES_PAGE} does not show: ${absent.join(" / ")}`);
74
+ writeFileSync(RECORDED, JSON.stringify({ documents: Object.fromEntries(documents.map((file) => [file, fingerprint(file)])), faq: faqState() }));
75
+ console.log(`${replies.length} repl(ies): ${replies.filter((reply) => reply.found).length} found in the documents`);
76
+ } else if (mode === "keep") {
77
+ const recorded = readJson(RECORDED, "what the answer step recorded");
78
+ const readThen = recorded?.documents;
79
+ if (typeof readThen !== "object" || readThen === null || Array.isArray(readThen))
80
+ fail(`${RECORDED} is not what the answer step records: run the answer check again`);
81
+ if (JSON.stringify(Object.keys(readThen).sort()) !== JSON.stringify([...documents].sort()))
82
+ fail(`the documents named now are not the ones that were read (${Object.keys(readThen).join(", ")}): run the answer check again`);
83
+ const changed = documents.filter((file) => readThen[file] !== fingerprint(file));
84
+ if (changed.length > 0) fail(`changed since they were read — the documents must stay as they are: ${changed.join(", ")}`);
85
+ const before = recorded.faq ?? null;
86
+ if (answers?.keep === KEEP) {
87
+ if (!existsSync(FAQ)) fail(`${FAQ} is missing`);
88
+ const now = readFileSync(FAQ);
89
+ if (before !== null && digest(now.subarray(0, before.bytes)) !== before.hash) fail(`${FAQ}: what it held before must stay as it was; add after it`);
90
+ const text = now.toString("utf8");
91
+ const absent = questions.filter((question) => !text.includes(question));
92
+ if (absent.length > 0) fail(`${FAQ} does not hold: ${absent.join(" / ")}`);
93
+ } else if (JSON.stringify(faqState()) !== JSON.stringify(before)) {
94
+ fail(`${FAQ} changed, but the person chose not to keep the answers`);
95
+ }
96
+ console.log("the documents are untouched");
97
+ } else {
98
+ fail(`usage: replies.mjs answer | keep (got ${JSON.stringify(mode)})`);
99
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "questions": [
3
+ {
4
+ "id": "documents",
5
+ "label": "質問する相手の文書(このフォルダの中のファイルを 1 行に 1 つ)",
6
+ "why": "答えはこの文書だけから出し、どこに書いてあるかを示す",
7
+ "kind": "text"
8
+ },
9
+ {
10
+ "id": "questions",
11
+ "label": "質問(1 行に 1 つ)",
12
+ "why": "質問ごとに一つ答える",
13
+ "kind": "text"
14
+ },
15
+ {
16
+ "id": "keep",
17
+ "label": "答えを残しますか",
18
+ "why": "読んで確かめた答えを、このフォルダの FAQ.md に書き足すかどうか",
19
+ "kind": "select",
20
+ "options": ["このフォルダの FAQ.md に書き足す", "残さない(.blueprint の中だけ)"]
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "slug": "ask",
3
+ "kind": "usecase",
4
+ "title": "文書に尋ねる(どこに書いてあるかを添えて)",
5
+ "version": "0.1.0",
6
+ "description": "込み入ったマニュアル・規程・契約書に質問すると、文書のどこに書いてあるかを原文の引用で示して答える。書いていないことは「書いていない」と答える。引用は chaff cite で原文にあることを確かめる。答えは、人が読んでから FAQ.md に残せる。",
7
+ "bases": ["docs"],
8
+ "report": ".blueprint/replies.md"
9
+ }
@@ -0,0 +1,19 @@
1
+ # 経費精算の手引き
2
+
3
+ ## 対象になるもの
4
+
5
+ 仕事のために使った交通費、宿泊費、会議の飲食代が対象です。私用の買い物や、会社のカードで払ったものは対象になりません。
6
+
7
+ ## 申請のしかた
8
+
9
+ 1. 領収書を写真に撮り、経費精算システムに登録します。
10
+ 2. 使った日から30日以内に申請します。30日を過ぎたものは、上長の承認があるときだけ受け付けます。
11
+ 3. 上長が承認すると、経理部が確かめます。
12
+
13
+ ## 支払い
14
+
15
+ 経理部の確認が済んだ申請は、毎月25日にまとめて給与口座へ振り込みます。25日が土日祝日のときは、その前の営業日に振り込みます。
16
+
17
+ ## 領収書が無いとき
18
+
19
+ 交通系ICカードの利用履歴を印刷したものは、領収書の代わりになります。それ以外で領収書が無いときは、「支払証明書」に理由を書いて上長の承認を受けてください。
@@ -0,0 +1,15 @@
1
+ {
2
+ "presets": [
3
+ {
4
+ "id": "keihi",
5
+ "title": "経費精算の手引きに尋ねる",
6
+ "description": "見本の手引き(keihi.md)に 3 つの質問をすると、どこに書いてあるかを原文の引用で示して答える。書いていないこと(海外出張の日当)には「書いていない」と答える。",
7
+ "base": "docs",
8
+ "answers": {
9
+ "documents": "keihi.md",
10
+ "questions": "申請の期限はいつまで?\n領収書をなくしたときはどうする?\n海外出張の日当はいくら?",
11
+ "keep": "このフォルダの FAQ.md に書き足す"
12
+ }
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: blueprint-ask-answer
3
+ description: "Answer each question from the named documents only, quoting where the answer is written, or saying plainly that the documents do not say — changing nothing yet."
4
+ ---
5
+
6
+ # Answer from the documents
7
+
8
+ `.blueprint/answers.json` names the documents (`documents`, one path per line), the questions
9
+ (`questions`, one per line) and whether to keep the answers (`keep`). The person may not know the
10
+ documents well: answer in their language and in plain words.
11
+
12
+ Change nothing in the repository: this step only reads. Write only under `.blueprint/`; the person reads
13
+ the answers before anything is kept.
14
+
15
+ ## How to answer
16
+
17
+ - **Only from the documents.** Not from general knowledge, not from what such documents usually say. If the
18
+ documents do not answer a question, the answer is that they do not — and what they do say nearby, if it
19
+ helps.
20
+ - **Find where it is written.** `chaff tree <document>` (through the base pack's wrapper,
21
+ `sh <base pack>/checks/chaff.sh tree <document>`) shows the document's addresses — articles, sections,
22
+ items — so an answer can say "第4条第2項" or "Section 3.1". Read the whole relevant part, including
23
+ exceptions and definitions elsewhere that change its meaning.
24
+ - **When provisions disagree**, say so and quote both rather than choosing one.
25
+
26
+ ## Write `.blueprint/replies.json`
27
+
28
+ ```json
29
+ {
30
+ "replies": [
31
+ {
32
+ "question": "委託料はいつまでに払う?",
33
+ "found": true,
34
+ "answer": "検収後30日以内です(第4条第2項)。",
35
+ "citations": [{ "source": "contract.txt", "address": "第4条第2項", "quote": "成果物の検収後30日以内に" }]
36
+ },
37
+ {
38
+ "question": "消費税は含まれる?",
39
+ "found": false,
40
+ "answer": "文書には書かれていません。第4条第1項は「金50万円」とだけ定めています。",
41
+ "citations": [{ "source": "contract.txt", "address": "第4条第1項", "quote": "委託料として金50万円を支払う" }],
42
+ "searched": ["消費税", "税", "税込", "税別"]
43
+ }
44
+ ]
45
+ }
46
+ ```
47
+
48
+ - `question`: the question exactly as it is in `questions`. One reply per question, none extra.
49
+ - `found`: `true` when the documents answer it. Then at least one citation.
50
+ - `citations`: `source` is the document's path as `documents` names it; `address` is where; `quote` is
51
+ copied character for character — the check runs `chaff cite` on every one. **Write every cited address in
52
+ the `answer` text** so the person can open the document there.
53
+ - `searched`: when `found` is `false`, the words and places you looked for.
54
+
55
+ Also write `.blueprint/replies.md` for the person: each question as a heading, the answer, and the quotations.
56
+
57
+ ## Done when
58
+
59
+ `node <usecase pack>/checks/replies.mjs answer` (with `BLUEPRINT_BASE` and `BLUEPRINT_USECASE` set to the
60
+ pack folders from your prompt) passes. It also records a fingerprint of the documents and of `FAQ.md`.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: blueprint-ask-keep
3
+ description: "Add the approved answers to FAQ.md when the person chose to keep them, after what it already holds; change nothing otherwise."
4
+ ---
5
+
6
+ # Keep the answers
7
+
8
+ The person has read the answers and approved them. Read `.blueprint/answers.json` and
9
+ `.blueprint/replies.json`.
10
+
11
+ **Never change a document.** The check compares each one with the fingerprint recorded when the answers were
12
+ written.
13
+
14
+ If `keep` is `残さない(.blueprint の中だけ)`, there is nothing to write: leave `FAQ.md` exactly as it is,
15
+ run the check and stop.
16
+
17
+ Otherwise add to `FAQ.md` in this folder (create it if there is none). **Add after what it already holds;
18
+ never edit or reorder what is there** — the check compares the beginning of the file with what it held
19
+ before. For each question: the question as a `##` heading, word for word; the answer; and where it is
20
+ written (document and address). For a question the documents do not answer, say so — that is worth keeping
21
+ too.
22
+
23
+ ## Done when
24
+
25
+ `node <usecase pack>/checks/replies.mjs keep` passes.
@@ -0,0 +1,19 @@
1
+ {
2
+ "steps": [
3
+ {
4
+ "id": "answer",
5
+ "title": "文書から答える",
6
+ "description": "質問ごとに、文書のどこに書いてあるかを引用で示して答える。書いていなければ、そう答えて何を探したかを残す。",
7
+ "skill": "skills/answer",
8
+ "check": "node \"$BLUEPRINT_USECASE/checks/replies.mjs\" answer"
9
+ },
10
+ {
11
+ "id": "keep",
12
+ "title": "答えを残す",
13
+ "description": "残すと決めたなら、確かめた答えを FAQ.md に書き足す。",
14
+ "skill": "skills/keep",
15
+ "check": "node \"$BLUEPRINT_USECASE/checks/replies.mjs\" keep",
16
+ "gates": ["review"]
17
+ }
18
+ ]
19
+ }