@latysh-development/monolith 0.0.0-stage → 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +179 -2
  3. package/dist/brief/cli.d.ts +30 -0
  4. package/dist/brief/cli.d.ts.map +1 -0
  5. package/dist/brief/cli.js +85 -0
  6. package/dist/brief/collect.d.ts +17 -0
  7. package/dist/brief/collect.d.ts.map +1 -0
  8. package/dist/brief/collect.js +342 -0
  9. package/dist/brief/config.d.ts +40 -0
  10. package/dist/brief/config.d.ts.map +1 -0
  11. package/dist/brief/config.js +85 -0
  12. package/dist/brief/constants.d.ts +12 -0
  13. package/dist/brief/constants.d.ts.map +1 -0
  14. package/dist/brief/constants.js +11 -0
  15. package/dist/brief/docs.d.ts +53 -0
  16. package/dist/brief/docs.d.ts.map +1 -0
  17. package/dist/brief/docs.js +229 -0
  18. package/dist/brief/freshness.d.ts +104 -0
  19. package/dist/brief/freshness.d.ts.map +1 -0
  20. package/dist/brief/freshness.js +263 -0
  21. package/dist/brief/git-signals.d.ts +17 -0
  22. package/dist/brief/git-signals.d.ts.map +1 -0
  23. package/dist/brief/git-signals.js +101 -0
  24. package/dist/brief/index.d.ts +11 -0
  25. package/dist/brief/index.d.ts.map +1 -0
  26. package/dist/brief/index.js +5 -0
  27. package/dist/brief/reconcile.d.ts +75 -0
  28. package/dist/brief/reconcile.d.ts.map +1 -0
  29. package/dist/brief/reconcile.js +259 -0
  30. package/dist/brief/render.d.ts +67 -0
  31. package/dist/brief/render.d.ts.map +1 -0
  32. package/dist/brief/render.js +458 -0
  33. package/dist/brief/sanitize.d.ts +38 -0
  34. package/dist/brief/sanitize.d.ts.map +1 -0
  35. package/dist/brief/sanitize.js +48 -0
  36. package/dist/brief/selectors.d.ts +36 -0
  37. package/dist/brief/selectors.d.ts.map +1 -0
  38. package/dist/brief/selectors.js +75 -0
  39. package/dist/brief/story.d.ts +18 -0
  40. package/dist/brief/story.d.ts.map +1 -0
  41. package/dist/brief/story.js +59 -0
  42. package/dist/brief/types.d.ts +126 -0
  43. package/dist/brief/types.d.ts.map +1 -0
  44. package/dist/brief/types.js +11 -0
  45. package/dist/cli/index.d.ts +3 -0
  46. package/dist/cli/index.d.ts.map +1 -0
  47. package/dist/cli/index.js +199 -0
  48. package/dist/cli/init-args.d.ts +16 -0
  49. package/dist/cli/init-args.d.ts.map +1 -0
  50. package/dist/cli/init-args.js +24 -0
  51. package/dist/cli/ui-args.d.ts +23 -0
  52. package/dist/cli/ui-args.d.ts.map +1 -0
  53. package/dist/cli/ui-args.js +66 -0
  54. package/dist/cli/ui.d.ts +40 -0
  55. package/dist/cli/ui.d.ts.map +1 -0
  56. package/dist/cli/ui.js +180 -0
  57. package/dist/coordinatorium/checkpoint.d.ts +44 -0
  58. package/dist/coordinatorium/checkpoint.d.ts.map +1 -0
  59. package/dist/coordinatorium/checkpoint.js +124 -0
  60. package/dist/coordinatorium/cli.d.ts +39 -0
  61. package/dist/coordinatorium/cli.d.ts.map +1 -0
  62. package/dist/coordinatorium/cli.js +203 -0
  63. package/dist/coordinatorium/errors.d.ts +112 -0
  64. package/dist/coordinatorium/errors.d.ts.map +1 -0
  65. package/dist/coordinatorium/errors.js +145 -0
  66. package/dist/coordinatorium/index.d.ts +11 -0
  67. package/dist/coordinatorium/index.d.ts.map +1 -0
  68. package/dist/coordinatorium/index.js +5 -0
  69. package/dist/coordinatorium/lock.d.ts +187 -0
  70. package/dist/coordinatorium/lock.d.ts.map +1 -0
  71. package/dist/coordinatorium/lock.js +659 -0
  72. package/dist/coordinatorium/mission.d.ts +32 -0
  73. package/dist/coordinatorium/mission.d.ts.map +1 -0
  74. package/dist/coordinatorium/mission.js +87 -0
  75. package/dist/coordinatorium/state-keeper.d.ts +115 -0
  76. package/dist/coordinatorium/state-keeper.d.ts.map +1 -0
  77. package/dist/coordinatorium/state-keeper.js +396 -0
  78. package/dist/coordinatorium/types.d.ts +69 -0
  79. package/dist/coordinatorium/types.d.ts.map +1 -0
  80. package/dist/coordinatorium/types.js +27 -0
  81. package/dist/discovery/compose.d.ts +28 -0
  82. package/dist/discovery/compose.d.ts.map +1 -0
  83. package/dist/discovery/compose.js +159 -0
  84. package/dist/discovery/contracts.d.ts +211 -0
  85. package/dist/discovery/contracts.d.ts.map +1 -0
  86. package/dist/discovery/contracts.js +555 -0
  87. package/dist/discovery/docs.d.ts +278 -0
  88. package/dist/discovery/docs.d.ts.map +1 -0
  89. package/dist/discovery/docs.js +1182 -0
  90. package/dist/discovery/facts.d.ts +49 -0
  91. package/dist/discovery/facts.d.ts.map +1 -0
  92. package/dist/discovery/facts.js +541 -0
  93. package/dist/discovery/index.d.ts +8 -0
  94. package/dist/discovery/index.d.ts.map +1 -0
  95. package/dist/discovery/index.js +9 -0
  96. package/dist/discovery/orchestrate.d.ts +35 -0
  97. package/dist/discovery/orchestrate.d.ts.map +1 -0
  98. package/dist/discovery/orchestrate.js +32 -0
  99. package/dist/discovery/render.d.ts +36 -0
  100. package/dist/discovery/render.d.ts.map +1 -0
  101. package/dist/discovery/render.js +278 -0
  102. package/dist/discovery/traverse.d.ts +197 -0
  103. package/dist/discovery/traverse.d.ts.map +1 -0
  104. package/dist/discovery/traverse.js +510 -0
  105. package/dist/durable/assess.d.ts +105 -0
  106. package/dist/durable/assess.d.ts.map +1 -0
  107. package/dist/durable/assess.js +239 -0
  108. package/dist/durable/canonical.d.ts +25 -0
  109. package/dist/durable/canonical.d.ts.map +1 -0
  110. package/dist/durable/canonical.js +78 -0
  111. package/dist/durable/cli.d.ts +37 -0
  112. package/dist/durable/cli.d.ts.map +1 -0
  113. package/dist/durable/cli.js +141 -0
  114. package/dist/durable/errors.d.ts +192 -0
  115. package/dist/durable/errors.d.ts.map +1 -0
  116. package/dist/durable/errors.js +323 -0
  117. package/dist/durable/fs-store.d.ts +254 -0
  118. package/dist/durable/fs-store.d.ts.map +1 -0
  119. package/dist/durable/fs-store.js +1493 -0
  120. package/dist/durable/index.d.ts +20 -0
  121. package/dist/durable/index.d.ts.map +1 -0
  122. package/dist/durable/index.js +13 -0
  123. package/dist/durable/log.d.ts +33 -0
  124. package/dist/durable/log.d.ts.map +1 -0
  125. package/dist/durable/log.js +157 -0
  126. package/dist/durable/memory-store.d.ts +51 -0
  127. package/dist/durable/memory-store.d.ts.map +1 -0
  128. package/dist/durable/memory-store.js +302 -0
  129. package/dist/durable/projection.d.ts +35 -0
  130. package/dist/durable/projection.d.ts.map +1 -0
  131. package/dist/durable/projection.js +328 -0
  132. package/dist/durable/prune-check.d.ts +34 -0
  133. package/dist/durable/prune-check.d.ts.map +1 -0
  134. package/dist/durable/prune-check.js +72 -0
  135. package/dist/durable/register.d.ts +100 -0
  136. package/dist/durable/register.d.ts.map +1 -0
  137. package/dist/durable/register.js +532 -0
  138. package/dist/durable/retention.d.ts +49 -0
  139. package/dist/durable/retention.d.ts.map +1 -0
  140. package/dist/durable/retention.js +59 -0
  141. package/dist/durable/runtime.d.ts +499 -0
  142. package/dist/durable/runtime.d.ts.map +1 -0
  143. package/dist/durable/runtime.js +1288 -0
  144. package/dist/durable/seams.d.ts +44 -0
  145. package/dist/durable/seams.d.ts.map +1 -0
  146. package/dist/durable/seams.js +21 -0
  147. package/dist/durable/testing.d.ts +11 -0
  148. package/dist/durable/testing.d.ts.map +1 -0
  149. package/dist/durable/testing.js +25 -0
  150. package/dist/durable/types.d.ts +442 -0
  151. package/dist/durable/types.d.ts.map +1 -0
  152. package/dist/durable/types.js +75 -0
  153. package/dist/graph/builder.d.ts +11 -0
  154. package/dist/graph/builder.d.ts.map +1 -0
  155. package/dist/graph/builder.js +153 -0
  156. package/dist/graph/identity.d.ts +37 -0
  157. package/dist/graph/identity.d.ts.map +1 -0
  158. package/dist/graph/identity.js +46 -0
  159. package/dist/graph/index.d.ts +7 -0
  160. package/dist/graph/index.d.ts.map +1 -0
  161. package/dist/graph/index.js +3 -0
  162. package/dist/graph/resolver.d.ts +41 -0
  163. package/dist/graph/resolver.d.ts.map +1 -0
  164. package/dist/graph/resolver.js +36 -0
  165. package/dist/graph/types.d.ts +42 -0
  166. package/dist/graph/types.d.ts.map +1 -0
  167. package/dist/graph/types.js +1 -0
  168. package/dist/http/index.d.ts +5 -0
  169. package/dist/http/index.d.ts.map +1 -0
  170. package/dist/http/index.js +2 -0
  171. package/dist/http/server.d.ts +76 -0
  172. package/dist/http/server.d.ts.map +1 -0
  173. package/dist/http/server.js +195 -0
  174. package/dist/http/ui-assets.d.ts +22 -0
  175. package/dist/http/ui-assets.d.ts.map +1 -0
  176. package/dist/http/ui-assets.js +100 -0
  177. package/dist/ideas/cli.d.ts +33 -0
  178. package/dist/ideas/cli.d.ts.map +1 -0
  179. package/dist/ideas/cli.js +158 -0
  180. package/dist/ideas/format.d.ts +38 -0
  181. package/dist/ideas/format.d.ts.map +1 -0
  182. package/dist/ideas/format.js +194 -0
  183. package/dist/ideas/index.d.ts +6 -0
  184. package/dist/ideas/index.d.ts.map +1 -0
  185. package/dist/ideas/index.js +3 -0
  186. package/dist/ideas/store.d.ts +55 -0
  187. package/dist/ideas/store.d.ts.map +1 -0
  188. package/dist/ideas/store.js +262 -0
  189. package/dist/ideas/types.d.ts +54 -0
  190. package/dist/ideas/types.d.ts.map +1 -0
  191. package/dist/ideas/types.js +26 -0
  192. package/dist/index.d.ts +32 -0
  193. package/dist/index.d.ts.map +1 -0
  194. package/dist/index.js +28 -0
  195. package/dist/init/index.d.ts +12 -0
  196. package/dist/init/index.d.ts.map +1 -0
  197. package/dist/init/index.js +90 -0
  198. package/dist/init/prompts.d.ts +3 -0
  199. package/dist/init/prompts.d.ts.map +1 -0
  200. package/dist/init/prompts.js +47 -0
  201. package/dist/init/scaffold.d.ts +29 -0
  202. package/dist/init/scaffold.d.ts.map +1 -0
  203. package/dist/init/scaffold.js +208 -0
  204. package/dist/init/types.d.ts +14 -0
  205. package/dist/init/types.d.ts.map +1 -0
  206. package/dist/init/types.js +1 -0
  207. package/dist/managed/index.d.ts +73 -0
  208. package/dist/managed/index.d.ts.map +1 -0
  209. package/dist/managed/index.js +222 -0
  210. package/dist/readmodel/dashboard.d.ts +40 -0
  211. package/dist/readmodel/dashboard.d.ts.map +1 -0
  212. package/dist/readmodel/dashboard.js +236 -0
  213. package/dist/readmodel/index.d.ts +3 -0
  214. package/dist/readmodel/index.d.ts.map +1 -0
  215. package/dist/readmodel/index.js +1 -0
  216. package/dist/readmodel/types.d.ts +104 -0
  217. package/dist/readmodel/types.d.ts.map +1 -0
  218. package/dist/readmodel/types.js +15 -0
  219. package/dist/root/index.d.ts +186 -0
  220. package/dist/root/index.d.ts.map +1 -0
  221. package/dist/root/index.js +430 -0
  222. package/dist/root/safe-write.d.ts +268 -0
  223. package/dist/root/safe-write.d.ts.map +1 -0
  224. package/dist/root/safe-write.js +966 -0
  225. package/dist/root/write-artifacts.d.ts +20 -0
  226. package/dist/root/write-artifacts.d.ts.map +1 -0
  227. package/dist/root/write-artifacts.js +42 -0
  228. package/dist/scriptorium/compile.d.ts +4 -0
  229. package/dist/scriptorium/compile.d.ts.map +1 -0
  230. package/dist/scriptorium/compile.js +185 -0
  231. package/dist/scriptorium/frontmatter.d.ts +13 -0
  232. package/dist/scriptorium/frontmatter.d.ts.map +1 -0
  233. package/dist/scriptorium/frontmatter.js +90 -0
  234. package/dist/scriptorium/index.d.ts +3 -0
  235. package/dist/scriptorium/index.d.ts.map +1 -0
  236. package/dist/scriptorium/index.js +1 -0
  237. package/dist/scriptorium/markdown.d.ts +31 -0
  238. package/dist/scriptorium/markdown.d.ts.map +1 -0
  239. package/dist/scriptorium/markdown.js +88 -0
  240. package/dist/scriptorium/types.d.ts +78 -0
  241. package/dist/scriptorium/types.d.ts.map +1 -0
  242. package/dist/scriptorium/types.js +1 -0
  243. package/dist/types/enforcement.d.ts +11 -0
  244. package/dist/types/enforcement.d.ts.map +1 -0
  245. package/dist/types/enforcement.js +1 -0
  246. package/dist/types/integrity.d.ts +33 -0
  247. package/dist/types/integrity.d.ts.map +1 -0
  248. package/dist/types/integrity.js +1 -0
  249. package/dist/types/project-definition.d.ts +30 -0
  250. package/dist/types/project-definition.d.ts.map +1 -0
  251. package/dist/types/project-definition.js +1 -0
  252. package/dist/ui/assets/cosmic-glass-bg-Dwx47zPu.webp +0 -0
  253. package/dist/ui/assets/index-C4NhMaq7.js +28 -0
  254. package/dist/ui/assets/index-DMZ7ZvZY.css +1 -0
  255. package/dist/ui/index.html +13 -0
  256. package/dist/update/cli.d.ts +8 -0
  257. package/dist/update/cli.d.ts.map +1 -0
  258. package/dist/update/cli.js +115 -0
  259. package/dist/update/index.d.ts +78 -0
  260. package/dist/update/index.d.ts.map +1 -0
  261. package/dist/update/index.js +285 -0
  262. package/dist/validate/index.d.ts +6 -0
  263. package/dist/validate/index.d.ts.map +1 -0
  264. package/dist/validate/index.js +214 -0
  265. package/dist/version.d.ts +2 -0
  266. package/dist/version.d.ts.map +1 -0
  267. package/dist/version.js +1 -0
  268. package/package.json +67 -4
  269. package/templates/.gitkeep +0 -0
  270. package/templates/default-instance/instruction/.cursorrules +59 -0
  271. package/templates/default-instance/instruction/.windsurfrules +59 -0
  272. package/templates/default-instance/instruction/CLAUDE.md +57 -0
  273. package/templates/default-instance/managed/agent.md +20 -0
  274. package/templates/default-instance/managed/bootstrap.md +7 -0
  275. package/templates/default-instance/managed/index-idea.md +5 -0
  276. package/templates/default-instance/managed/routing-idea.md +14 -0
  277. package/templates/default-instance/monolith/AGENT.md +1 -0
  278. package/templates/default-instance/monolith/context/boot/HANDOFF.md +49 -0
  279. package/templates/default-instance/monolith/context/boot/WORK_SESSION.md +30 -0
  280. package/templates/default-instance/monolith/context/execution/current-state.md +30 -0
  281. package/templates/default-instance/monolith/context/maps/dependency-map.md +38 -0
  282. package/templates/default-instance/monolith/context/maps/project-map.md +85 -0
  283. package/templates/default-instance/monolith/context/maps/roadmap-index.md +41 -0
  284. package/templates/default-instance/monolith/context/maps/routing-map.md +151 -0
  285. package/templates/default-instance/monolith/context/maps/semantic-map.md +75 -0
  286. package/templates/default-instance/monolith/context/models/model-registry.md +37 -0
  287. package/templates/default-instance/monolith/context/models/privacy-classification.md +35 -0
  288. package/templates/default-instance/monolith/context/templates/plan-template.md +56 -0
  289. package/templates/default-instance/monolith/governance/README.md +29 -0
  290. package/templates/default-instance/monolith/governance/RULE_SCHEMA.md +58 -0
  291. package/templates/default-instance/monolith/learning/INDEX.md +48 -0
  292. package/templates/default-instance/monolith/learning/injection-map.md +49 -0
  293. package/templates/default-instance/monolith/learning/taxonomy.md +30 -0
  294. package/templates/default-instance/monolith/memory/errors/README.md +26 -0
  295. package/templates/default-instance/monolith/memory/errors/SCHEMA.md +47 -0
  296. package/templates/default-instance/monolith/memory/errors/TAXONOMY.md +27 -0
  297. package/templates/default-instance/monolith/memory/long-term/architecture-principles.md +31 -0
  298. package/templates/default-instance/monolith/memory/long-term/decisions.md +36 -0
  299. package/templates/default-instance/monolith/memory/long-term/execution-history.md +20 -0
  300. package/templates/default-instance/monolith/memory/long-term/tech-debt.md +25 -0
  301. package/templates/default-instance/monolith/memory/working/active.md +41 -0
  302. package/templates/default-instance/monolith/memory/working/governance-result.md +0 -0
  303. package/templates/default-instance/monolith/memory/working/integrity-result.md +0 -0
  304. package/templates/default-instance/monolith/memory/working/story-index.md +28 -0
  305. package/templates/default-instance/monolith/protocols/boot/boot-sequence.md +59 -0
  306. package/templates/default-instance/monolith/protocols/cognition/cognitive-modes.md +90 -0
  307. package/templates/default-instance/monolith/protocols/cognition/dry-run.md +56 -0
  308. package/templates/default-instance/monolith/protocols/cognition/execution-trace-format.md +52 -0
  309. package/templates/default-instance/monolith/protocols/cognition/semantic-planning.md +51 -0
  310. package/templates/default-instance/monolith/protocols/execution/hybrid-execution.md +37 -0
  311. package/templates/default-instance/monolith/protocols/execution/local-llm-governance.md +42 -0
  312. package/templates/default-instance/monolith/protocols/governance/command-policy.md +51 -0
  313. package/templates/default-instance/monolith/protocols/governance/core-invariants.md +40 -0
  314. package/templates/default-instance/monolith/protocols/governance/memory-governance.md +54 -0
  315. package/templates/default-instance/monolith/protocols/governance/metadata-schema.md +62 -0
  316. package/templates/default-instance/monolith/protocols/governance/skills.md +53 -0
  317. package/templates/default-instance/monolith/protocols/governance/work-session.md +46 -0
  318. package/templates/default-instance/monolith/skills/INDEX.md +32 -0
  319. package/templates/default-instance/monolith/skills/current-status/SKILL.md +47 -0
  320. package/templates/default-instance/monolith/skills/finalize-story/SKILL.md +86 -0
  321. package/templates/default-instance/monolith/skills/global-status/SKILL.md +56 -0
  322. package/templates/default-instance/monolith/skills/idea-capture/SKILL.md +157 -0
  323. package/templates/default-instance/monolith/skills/self-improvement/SKILL.md +77 -0
  324. package/templates/default-instance/monolith/state.json +11 -0
  325. package/templates/default-instance/monolith/tests/INDEX.md +57 -0
  326. package/templates/default-instance/monolith/tests/scenarios/architecture-review.md +32 -0
  327. package/templates/default-instance/monolith/tests/scenarios/debug-failure.md +32 -0
  328. package/templates/default-instance/monolith/tests/scenarios/dry-run-activation.md +33 -0
  329. package/templates/default-instance/monolith/tests/scenarios/execution-trace-verification.md +31 -0
  330. package/templates/default-instance/monolith/tests/scenarios/finalize-story.md +35 -0
  331. package/templates/default-instance/monolith/tests/scenarios/implement-feature.md +36 -0
  332. package/templates/default-instance/monolith/tests/scenarios/local-llm-delegation.md +31 -0
  333. package/templates/default-instance/monolith/tests/scenarios/planning-request.md +32 -0
  334. package/templates/default-instance/monolith/tests/scenarios/sensitive-context-task.md +30 -0
  335. package/templates/default-instance/monolith/tests/scenarios/validator-verification.md +33 -0
  336. package/templates/default-instance/monolith/tests/validation/expected-behavior.md +70 -0
  337. package/templates/default-instance/monolith/tests/validators/INDEX.md +17 -0
  338. package/templates/default-instance/monolith/tests/validators/delegation-semantics.md +22 -0
  339. package/templates/default-instance/monolith/tests/validators/mode-consistency.md +25 -0
  340. package/templates/default-instance/monolith/tests/validators/privacy-routing.md +23 -0
  341. package/templates/default-instance/monolith/tests/validators/protocol-references.md +27 -0
  342. package/templates/default-instance/monolith/tests/validators/routing-integrity.md +30 -0
  343. package/templates/default-instance/projectroot/AGENTS.md +111 -0
  344. package/templates/default-instance/projectroot/monolith.config.json +32 -0
  345. package/templates/default-instance/projectroot/project.definition.mjs.template +41 -0
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Managed blocks (DX-UPDATE-003): the one mechanism by which `monolith init`
3
+ * and `monolith update` put MONOLITH-owned text into a file the user also owns
4
+ * (CLAUDE.md, AGENTS.md, routing-map, skills index) or into a MONOLITH-owned
5
+ * file the user may extend (`AGENT.md`).
6
+ *
7
+ * <!-- monolith:begin <id> sha256=<16 hex of the body> -->
8
+ * ...body...
9
+ * <!-- monolith:end <id> -->
10
+ *
11
+ * The checksum is what makes a refresh safe without a stored template version:
12
+ * a block whose body still matches its own checksum was never edited, so it is
13
+ * ours to refresh; a block that does not match carries a user edit and is left
14
+ * alone (CONFLICT). Everything outside the markers is never read for meaning
15
+ * and never changed. Markers inside fenced code blocks are examples, not blocks.
16
+ * This is not a Markdown parser: it knows lines, fences and these markers.
17
+ */
18
+ import { createHash } from 'crypto';
19
+ import { readFileSync } from 'fs';
20
+ import { dirname, join, resolve } from 'path';
21
+ import { fileURLToPath } from 'url';
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ // Works from src/managed/ and dist/managed/ alike.
24
+ const MANAGED_TEMPLATES = resolve(__dirname, '..', '..', 'templates', 'default-instance', 'managed');
25
+ export const BLOCK_IDS = ['bootstrap', 'agent', 'routing-idea', 'index-idea'];
26
+ /** `{{MANAGED_<ID>}}` placeholder name used by the init templates. */
27
+ export function placeholderFor(id) {
28
+ return `MANAGED_${id.toUpperCase().replace(/-/g, '_')}`;
29
+ }
30
+ export function bodyHash(body) {
31
+ return createHash('sha256').update(normalize(body)).digest('hex').slice(0, 16);
32
+ }
33
+ function normalize(s) {
34
+ return s.replace(/\r\n/g, '\n').replace(/\s+$/, '');
35
+ }
36
+ /** The body of a block for a given instance root. */
37
+ export function blockBody(id, monolithRoot) {
38
+ const raw = readFileSync(join(MANAGED_TEMPLATES, `${id}.md`), 'utf8');
39
+ return normalize(raw.replace(/\{\{MONOLITH_ROOT\}\}/g, monolithRoot));
40
+ }
41
+ /** The block as it is written to a file (markers + body), ending in a newline. */
42
+ export function renderBlock(id, body, eol = '\n') {
43
+ const lines = [`<!-- monolith:begin ${id} sha256=${bodyHash(body)} -->`, ...normalize(body).split('\n'), `<!-- monolith:end ${id} -->`];
44
+ return lines.join(eol) + eol;
45
+ }
46
+ const MARKER = /^<!-- monolith:(begin|end) ([a-z-]+)(?: sha256=([0-9a-f]{16}))? -->$/;
47
+ const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
48
+ const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
49
+ function splitLines(text) {
50
+ return text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
51
+ }
52
+ /** A leading UTF-8 BOM is not part of the first line's Markdown. */
53
+ const stripBom = (l, i) => (i === 0 ? l.replace(/^\uFEFF/, '') : l);
54
+ /**
55
+ * Per line: is it a fence line or inside a fenced code block? Same rules as
56
+ * CommonMark for the cases that matter here: a fence is 3+ backticks or tildes
57
+ * (at most 3 spaces of indent), it closes on the same character with at least
58
+ * the same length and nothing after it, and a block left open runs to the end
59
+ * of the file. `open` tells whether the text ends inside such a block.
60
+ */
61
+ function scanFences(lines) {
62
+ const hidden = [];
63
+ let fence = null;
64
+ for (const [i, l] of lines.entries()) {
65
+ const t = stripBom(l, i).replace(/\r?\n$/, '');
66
+ if (fence === null) {
67
+ const o = FENCE_OPEN.exec(t);
68
+ if (o && !(o[1][0] === '`' && o[2].includes('`'))) {
69
+ fence = { ch: o[1][0], len: o[1].length };
70
+ hidden.push(true);
71
+ }
72
+ else
73
+ hidden.push(false);
74
+ }
75
+ else {
76
+ const c = FENCE_CLOSE.exec(t);
77
+ if (c && c[1][0] === fence.ch && c[1].length >= fence.len)
78
+ fence = null;
79
+ hidden.push(true);
80
+ }
81
+ }
82
+ return { hidden, open: fence !== null };
83
+ }
84
+ /**
85
+ * Per line (outside fences): does it start inside an HTML comment? `open` tells
86
+ * whether the text ends inside one. A line that is exactly a marker opens and
87
+ * closes its own comment and does not change the state. Deliberately naive: a
88
+ * `<!--` in inline code counts as a comment, which can only make us refuse.
89
+ */
90
+ function scanComments(lines, hidden) {
91
+ const inside = [];
92
+ let open = false;
93
+ lines.forEach((l, i) => {
94
+ inside.push(open);
95
+ if (hidden[i])
96
+ return;
97
+ const t = stripBom(l, i).replace(/\r?\n$/, '');
98
+ if (!open && MARKER.test(t.trim()))
99
+ return;
100
+ let at = 0;
101
+ for (;;) {
102
+ if (open) {
103
+ const end = t.indexOf('-->', at);
104
+ if (end < 0)
105
+ break;
106
+ open = false;
107
+ at = end + 3;
108
+ }
109
+ else {
110
+ const start = t.indexOf('<!--', at);
111
+ if (start < 0)
112
+ break;
113
+ open = true;
114
+ at = start + 4;
115
+ }
116
+ }
117
+ });
118
+ return { inside, open };
119
+ }
120
+ function markers(lines) {
121
+ const out = [];
122
+ const { hidden } = scanFences(lines);
123
+ const { inside } = scanComments(lines, hidden);
124
+ lines.forEach((l, i) => {
125
+ if (hidden[i])
126
+ return;
127
+ const t = l.replace(/\r?\n$/, '');
128
+ const m = MARKER.exec(t.trim());
129
+ if (m)
130
+ out.push({ kind: m[1], id: m[2], hash: m[3], line: i, commented: inside[i] });
131
+ });
132
+ return out;
133
+ }
134
+ /** Classify `id` in `text` against the template body `desired`. */
135
+ export function inspectBlock(text, id, desired) {
136
+ const lines = splitLines(text);
137
+ const all = markers(lines);
138
+ const mine = all.filter((m) => m.id === id);
139
+ if (mine.length === 0)
140
+ return { state: 'absent' };
141
+ if (mine.some((m) => m.commented)) {
142
+ return { state: 'malformed', detail: `a "${id}" marker sits inside an HTML comment, so the block is not active` };
143
+ }
144
+ const begins = mine.filter((m) => m.kind === 'begin');
145
+ const ends = mine.filter((m) => m.kind === 'end');
146
+ if (begins.length !== 1 || ends.length !== 1) {
147
+ return { state: 'malformed', detail: `${begins.length} begin and ${ends.length} end marker(s) for "${id}"; exactly one of each is required` };
148
+ }
149
+ const [b] = begins;
150
+ const [e] = ends;
151
+ if (e.line < b.line)
152
+ return { state: 'malformed', detail: `the "${id}" end marker precedes its begin marker` };
153
+ if (all.some((m) => m !== b && m !== e && m.line > b.line && m.line < e.line)) {
154
+ return { state: 'malformed', detail: `another managed marker sits inside the "${id}" block` };
155
+ }
156
+ if (b.hash === undefined)
157
+ return { state: 'malformed', detail: `the "${id}" begin marker has no checksum` };
158
+ const body = lines.slice(b.line + 1, e.line).join('');
159
+ if (bodyHash(body) !== b.hash) {
160
+ return { state: 'edited', detail: `the text inside the "${id}" block was edited after MONOLITH wrote it` };
161
+ }
162
+ return { state: 'pristine', current: normalize(body) === normalize(desired), from: b.line, to: e.line, lines };
163
+ }
164
+ function eolOf(text) {
165
+ return text.includes('\r\n') ? '\r\n' : '\n';
166
+ }
167
+ /** `text` plus the block, appended; every existing byte stays in place. */
168
+ export function appendBlock(text, id, body) {
169
+ const eol = eolOf(text);
170
+ const sep = text.length === 0 ? '' : text.endsWith('\n') ? eol : eol + eol;
171
+ return text + sep + renderBlock(id, body, eol);
172
+ }
173
+ /** `text` with only the lines of the block replaced. */
174
+ export function refreshBlock(text, state, id, body) {
175
+ const eol = eolOf(text);
176
+ const rendered = renderBlock(id, body, eol);
177
+ // The end-marker line may be the unterminated last line of the file: keep it that way.
178
+ const unterminated = !/\n$/.test(state.lines[state.to]);
179
+ const block = unterminated ? rendered.slice(0, -eol.length) : rendered;
180
+ return state.lines.slice(0, state.from).join('') + block + state.lines.slice(state.to + 1).join('');
181
+ }
182
+ /**
183
+ * `text` with the block appended, or the reason that is not safe. Safe means the
184
+ * result, read back by `inspectBlock`, shows exactly one well-formed, unedited
185
+ * block that equals the template, and every original byte is still in place.
186
+ * An unclosed code fence or HTML comment in `text` swallows the new markers (the next run would
187
+ * not see the block and append another), so it is refused up front.
188
+ */
189
+ export function planAppend(text, id, body) {
190
+ if (scanFences(splitLines(text)).open) {
191
+ return { ok: false, detail: 'the file ends inside an unclosed fenced code block (``` or ~~~), which would hide the new block' };
192
+ }
193
+ const lines = splitLines(text);
194
+ if (scanComments(lines, scanFences(lines).hidden).open) {
195
+ return { ok: false, detail: 'the file ends inside an unclosed HTML comment (<!--), which would hide the new block' };
196
+ }
197
+ const content = appendBlock(text, id, body);
198
+ if (!content.startsWith(text))
199
+ return { ok: false, detail: 'the append would not keep the existing bytes in place' };
200
+ return verified(content, id, body);
201
+ }
202
+ /** `text` with the block refreshed in place, or the reason that is not safe (see `planAppend`). */
203
+ export function planRefresh(text, state, id, body) {
204
+ const content = refreshBlock(text, state, id, body);
205
+ const head = state.lines.slice(0, state.from).join('');
206
+ const tail = state.lines.slice(state.to + 1).join('');
207
+ if (!content.startsWith(head) || !content.endsWith(tail))
208
+ return { ok: false, detail: 'the refresh would change text outside the block' };
209
+ return verified(content, id, body);
210
+ }
211
+ function verified(content, id, body) {
212
+ const st = inspectBlock(content, id, body);
213
+ if (st.state === 'pristine' && st.current)
214
+ return { ok: true, content };
215
+ return { ok: false, detail: `the written block would not be recognised as current (${st.state === 'pristine' ? 'stale' : st.state})` };
216
+ }
217
+ /** Text with fenced code blocks and HTML comments removed: what an assistant reads as instructions. */
218
+ export function prose(text) {
219
+ const lines = splitLines(text);
220
+ const { hidden } = scanFences(lines);
221
+ return lines.filter((_, i) => !hidden[i]).join('').replace(/<!--[\s\S]*?-->/g, '');
222
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `readDashboardModel(projectRoot)` — builds the DashboardReadModel.
3
+ *
4
+ * Contract:
5
+ * - `projectRoot` is supplied by the embedding process (fixed at startup),
6
+ * never by a request. The set of files read is closed (see SourceId).
7
+ * - Strictly read-only and synchronous: no write, no lock, no `mutateState`,
8
+ * no `validate`, no `import()` of project code, no network. Nothing is
9
+ * cached; every call re-reads.
10
+ * - Never throws. Failures become SourceReport / ReadNote data.
11
+ * - Deterministic: same files ⇒ deep-equal result; no timestamps.
12
+ *
13
+ * Containment (G1): the instance root is resolved with `resolveMonolithRoot`
14
+ * (ADR-009) and every file is read with `readProjectFile`, which realpath-checks
15
+ * each path component against the real project root and then performs the
16
+ * bounded, non-blocking read of SEC-001B.1. `readState` is deliberately NOT
17
+ * used: it does not check containment. Check-then-use races are NOT closed by
18
+ * Core (SEC-001B) and are therefore not closed here.
19
+ *
20
+ * Every source goes through `readProjectFile` (ADR-014): no symlink in any
21
+ * component, one hard link, regular file, absolute boundary, never the cwd.
22
+ * This module adds no filesystem checks of its own; it only interprets Core's
23
+ * typed result (`FILE_CODE_MAP`). Threat model (ADR-013): TM-A supported with
24
+ * limitations, TM-B partially supported, TM-C (concurrent mutation) NOT supported.
25
+ */
26
+ import { type ProjectFileErrorCode } from '../root/index.js';
27
+ import type { DashboardReadModel, SourceErrorCode, SourceStatus } from './types.js';
28
+ export declare const ROADMAP_REL = "context/maps/roadmap-index.md";
29
+ /** Protective ceiling, like the other SEC-001B.1 limits — not a content policy. */
30
+ export declare const MAX_ROADMAP_BYTES: number;
31
+ /**
32
+ * Exhaustive over Core's `ProjectFileErrorCode`: a new Core code is a compile
33
+ * error here until it is mapped. The status of a security violation is NOT
34
+ * decided here — `sourceFailure` takes it from Core's `isReadSecurityViolation`,
35
+ * so the UI keeps no second classification of filesystem security. The
36
+ * 'forbidden' entries only document that and are checked against Core in tests.
37
+ */
38
+ export declare const FILE_CODE_MAP: Readonly<Record<ProjectFileErrorCode, readonly [SourceStatus, SourceErrorCode]>>;
39
+ export declare function readDashboardModel(projectRoot: string): DashboardReadModel;
40
+ //# sourceMappingURL=dashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/readmodel/dashboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,EAQL,KAAK,oBAAoB,EAE1B,MAAM,kBAAkB,CAAC;AAS1B,OAAO,KAAK,EAGV,kBAAkB,EAKlB,eAAe,EAGf,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,WAAW,kCAAkC,CAAC;AAC3D,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,QAAa,CAAC;AAS5C;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,oBAAoB,EAAE,SAAS,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC,CAa1G,CAAC;AA+LF,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,CAQ1E"}
@@ -0,0 +1,236 @@
1
+ /**
2
+ * `readDashboardModel(projectRoot)` — builds the DashboardReadModel.
3
+ *
4
+ * Contract:
5
+ * - `projectRoot` is supplied by the embedding process (fixed at startup),
6
+ * never by a request. The set of files read is closed (see SourceId).
7
+ * - Strictly read-only and synchronous: no write, no lock, no `mutateState`,
8
+ * no `validate`, no `import()` of project code, no network. Nothing is
9
+ * cached; every call re-reads.
10
+ * - Never throws. Failures become SourceReport / ReadNote data.
11
+ * - Deterministic: same files ⇒ deep-equal result; no timestamps.
12
+ *
13
+ * Containment (G1): the instance root is resolved with `resolveMonolithRoot`
14
+ * (ADR-009) and every file is read with `readProjectFile`, which realpath-checks
15
+ * each path component against the real project root and then performs the
16
+ * bounded, non-blocking read of SEC-001B.1. `readState` is deliberately NOT
17
+ * used: it does not check containment. Check-then-use races are NOT closed by
18
+ * Core (SEC-001B) and are therefore not closed here.
19
+ *
20
+ * Every source goes through `readProjectFile` (ADR-014): no symlink in any
21
+ * component, one hard link, regular file, absolute boundary, never the cwd.
22
+ * This module adds no filesystem checks of its own; it only interprets Core's
23
+ * typed result (`FILE_CODE_MAP`). Threat model (ADR-013): TM-A supported with
24
+ * limitations, TM-B partially supported, TM-C (concurrent mutation) NOT supported.
25
+ */
26
+ import { MAX_STATE_BYTES, validateState } from '../coordinatorium/index.js';
27
+ import { CONFIG_FILE_NAME, MAX_PROJECT_FILE_BYTES, isReadSecurityViolation, RootContainmentError, readProjectFile, resolveMonolithRoot, } from '../root/index.js';
28
+ import { MAX_ACTIVE_CONTEXT_BYTES } from '../brief/collect.js';
29
+ import { ACTIVE_MD_REL } from '../brief/constants.js';
30
+ import { extractCurrentStory } from '../brief/story.js';
31
+ import { hasCheckpointOrderMismatch, selectActiveMissions, selectLatestCheckpoint, } from '../brief/selectors.js';
32
+ export const ROADMAP_REL = 'context/maps/roadmap-index.md';
33
+ /** Protective ceiling, like the other SEC-001B.1 limits — not a content policy. */
34
+ export const MAX_ROADMAP_BYTES = 256 * 1024;
35
+ const CHANGED_RETRIES = 3;
36
+ /**
37
+ * Exhaustive over Core's `ProjectFileErrorCode`: a new Core code is a compile
38
+ * error here until it is mapped. The status of a security violation is NOT
39
+ * decided here — `sourceFailure` takes it from Core's `isReadSecurityViolation`,
40
+ * so the UI keeps no second classification of filesystem security. The
41
+ * 'forbidden' entries only document that and are checked against Core in tests.
42
+ */
43
+ export const FILE_CODE_MAP = {
44
+ FILE_MISSING: ['absent', 'SOURCE_ABSENT'],
45
+ FILE_OUTSIDE_PROJECT: ['forbidden', 'SOURCE_OUTSIDE_PROJECT'],
46
+ FILE_UNRESOLVABLE: ['unreadable', 'SOURCE_UNRESOLVABLE'],
47
+ FILE_NOT_REGULAR: ['forbidden', 'SOURCE_NOT_REGULAR'],
48
+ FILE_EMPTY: ['corrupt', 'SOURCE_EMPTY'],
49
+ FILE_TOO_LARGE: ['unreadable', 'SOURCE_TOO_LARGE'],
50
+ FILE_CHANGED: ['unreadable', 'SOURCE_CHANGED'],
51
+ FILE_UNREADABLE: ['unreadable', 'SOURCE_UNREADABLE'],
52
+ FILE_SYMLINK: ['forbidden', 'SOURCE_SYMLINK'],
53
+ FILE_HARDLINKED: ['forbidden', 'SOURCE_HARDLINKED'],
54
+ READ_BOUNDARY_INVALID: ['forbidden', 'SOURCE_BOUNDARY_INVALID'],
55
+ READ_UNSUPPORTED_PLATFORM: ['forbidden', 'SOURCE_UNSUPPORTED_PLATFORM'],
56
+ };
57
+ /** Never maps a security violation to anything but 'forbidden'; no default for unknown codes. */
58
+ function sourceFailure(code) {
59
+ const [status, sourceCode] = FILE_CODE_MAP[code];
60
+ return [isReadSecurityViolation(code) ? 'forbidden' : status, sourceCode];
61
+ }
62
+ function report(id, path, status, code) {
63
+ return { id, path, status, code };
64
+ }
65
+ function withStatus(r, status, code) {
66
+ return { ...r, status, code };
67
+ }
68
+ /** One contained, bounded read of a fixed project-relative path. */
69
+ function load(projectRoot, id, relPath, maxBytes) {
70
+ try {
71
+ let read = readProjectFile(projectRoot, relPath, { maxBytes, allowEmpty: true });
72
+ // A rename-based writer replacing the file under us is normal, not a fault.
73
+ for (let i = 1; i < CHANGED_RETRIES && !read.ok && read.code === 'FILE_CHANGED'; i++) {
74
+ read = readProjectFile(projectRoot, relPath, { maxBytes, allowEmpty: true });
75
+ }
76
+ if (read.ok)
77
+ return { report: report(id, relPath, 'available', null), content: read.content };
78
+ const [status, code] = sourceFailure(read.code);
79
+ return { report: report(id, relPath, status, code), content: null };
80
+ }
81
+ catch {
82
+ return { report: report(id, relPath, 'unreadable', 'INTERNAL'), content: null };
83
+ }
84
+ }
85
+ function isPlainObject(v) {
86
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
87
+ }
88
+ function loadConfig(projectRoot) {
89
+ const loaded = load(projectRoot, 'config', CONFIG_FILE_NAME, MAX_PROJECT_FILE_BYTES);
90
+ if (loaded.report.status === 'absent')
91
+ return { report: loaded.report, read: { status: 'absent' }, name: null };
92
+ if (loaded.content === null) {
93
+ return { report: loaded.report, read: null, name: null };
94
+ }
95
+ let parsed;
96
+ try {
97
+ parsed = JSON.parse(loaded.content);
98
+ }
99
+ catch {
100
+ return { report: withStatus(loaded.report, 'corrupt', 'CONFIG_INVALID'), read: null, name: null };
101
+ }
102
+ if (!isPlainObject(parsed)) {
103
+ return { report: withStatus(loaded.report, 'corrupt', 'CONFIG_INVALID'), read: null, name: null };
104
+ }
105
+ const project = parsed.project;
106
+ const rawName = isPlainObject(project) ? project.name : undefined;
107
+ const name = typeof rawName === 'string' && rawName.length > 0 ? rawName : null;
108
+ return { report: loaded.report, read: { status: 'ok', cfg: parsed }, name };
109
+ }
110
+ function loadState(projectRoot, root) {
111
+ const loaded = load(projectRoot, 'state', `${root.monolithRoot}/state.json`, MAX_STATE_BYTES);
112
+ if (loaded.content === null)
113
+ return { report: loaded.report, state: null, schemaVersionOnDisk: null };
114
+ const corrupt = (code) => ({
115
+ report: withStatus(loaded.report, 'corrupt', code),
116
+ state: null,
117
+ schemaVersionOnDisk: null,
118
+ });
119
+ if (loaded.content.trim() === '')
120
+ return corrupt('SOURCE_EMPTY');
121
+ let parsed;
122
+ try {
123
+ parsed = JSON.parse(loaded.content);
124
+ }
125
+ catch {
126
+ return corrupt('PARSE_ERROR');
127
+ }
128
+ try {
129
+ const state = validateState(parsed);
130
+ const onDisk = isPlainObject(parsed) && typeof parsed.schemaVersion === 'number' ? parsed.schemaVersion : null;
131
+ return { report: loaded.report, state, schemaVersionOnDisk: onDisk };
132
+ }
133
+ catch {
134
+ return corrupt('SCHEMA_INVALID');
135
+ }
136
+ }
137
+ function section(data, sources) {
138
+ return { data, sources };
139
+ }
140
+ /** Root could not be trusted: nothing below it is read (fail closed, never a default fallback). */
141
+ function unreadableModel(config, rootCode = 'ROOT_INVALID') {
142
+ const refused = (id) => report(id, null, 'forbidden', rootCode);
143
+ const state = refused('state');
144
+ const story = refused('activeStory');
145
+ const roadmap = refused('roadmap');
146
+ return {
147
+ projectOverview: section(null, [config, state, story]),
148
+ currentMissions: section(null, [state]),
149
+ checkpoints: section(null, [state]),
150
+ roadmap: section(null, [roadmap]),
151
+ diagnostics: { validation: 'NOT_PERFORMED', sources: [config, state, story, roadmap], notes: [] },
152
+ };
153
+ }
154
+ function buildModel(projectRoot) {
155
+ const config = loadConfig(projectRoot);
156
+ if (config.read === null)
157
+ return unreadableModel(config.report);
158
+ let root;
159
+ try {
160
+ root = resolveMonolithRoot(projectRoot, config.read);
161
+ }
162
+ catch (err) {
163
+ if (err instanceof RootContainmentError)
164
+ return unreadableModel(config.report);
165
+ throw err;
166
+ }
167
+ const notes = [];
168
+ const stateResult = loadState(projectRoot, root);
169
+ const storyLoaded = load(projectRoot, 'activeStory', `${root.monolithRoot}/${ACTIVE_MD_REL}`, MAX_ACTIVE_CONTEXT_BYTES);
170
+ const roadmapLoaded = load(projectRoot, 'roadmap', `${root.monolithRoot}/${ROADMAP_REL}`, MAX_ROADMAP_BYTES);
171
+ // An absent state.json means "no missions ever recorded", exactly like `brief`.
172
+ const stateUsable = stateResult.report.status === 'available' || stateResult.report.status === 'absent';
173
+ const state = stateResult.state;
174
+ const missions = state ? Object.values(state.tactical.missions) : [];
175
+ const active = state ? selectActiveMissions(state) : [];
176
+ if (stateResult.schemaVersionOnDisk === 1)
177
+ notes.push({ code: 'STATE_LEGACY_SCHEMA_MIGRATED', severity: 'info' });
178
+ if (active.length > 1)
179
+ notes.push({ code: 'MULTIPLE_ACTIVE_MISSIONS', severity: 'info' });
180
+ let currentStory = null;
181
+ if (storyLoaded.content !== null) {
182
+ const extraction = extractCurrentStory(storyLoaded.content);
183
+ if (extraction.hasCurrentStorySection && extraction.excerpt !== null && extraction.lineRange !== null) {
184
+ currentStory = { excerpt: extraction.excerpt, lineRange: extraction.lineRange };
185
+ }
186
+ else {
187
+ notes.push({ code: 'ACTIVE_MD_NO_CURRENT_STORY', severity: 'info' });
188
+ }
189
+ }
190
+ const overview = {
191
+ name: config.name,
192
+ monolithRoot: root.monolithRoot,
193
+ monolithRootSource: root.source,
194
+ stateSchemaVersionOnDisk: stateResult.schemaVersionOnDisk,
195
+ stateRevision: state && stateResult.report.status === 'available' ? state.revision : null,
196
+ activeMissionCount: stateUsable ? active.length : null,
197
+ finishedMissionCount: stateUsable ? missions.length - active.length : null,
198
+ currentStory,
199
+ };
200
+ const currentMissions = stateUsable ? { active } : null;
201
+ let checkpoints = null;
202
+ if (state !== null || stateResult.report.status === 'absent') {
203
+ const all = state ? Object.values(state.tactical.checkpoints) : [];
204
+ checkpoints = {
205
+ byActiveMission: active.map((m) => {
206
+ const latest = state ? selectLatestCheckpoint(state, m.id) : null;
207
+ if (state && latest && hasCheckpointOrderMismatch(state, m.id, latest)) {
208
+ notes.push({ code: 'CHECKPOINT_ORDER_MISMATCH', severity: 'info' });
209
+ }
210
+ return { missionId: m.id, count: all.filter((c) => c.missionId === m.id).length, latest };
211
+ }),
212
+ };
213
+ }
214
+ const roadmap = roadmapLoaded.content === null ? null : { text: roadmapLoaded.content };
215
+ return {
216
+ projectOverview: section(overview, [config.report, stateResult.report, storyLoaded.report]),
217
+ currentMissions: section(currentMissions, [stateResult.report]),
218
+ checkpoints: section(checkpoints, [stateResult.report]),
219
+ roadmap: section(roadmap, [roadmapLoaded.report]),
220
+ diagnostics: {
221
+ validation: 'NOT_PERFORMED',
222
+ sources: [config.report, stateResult.report, storyLoaded.report, roadmapLoaded.report],
223
+ notes,
224
+ },
225
+ };
226
+ }
227
+ export function readDashboardModel(projectRoot) {
228
+ try {
229
+ return buildModel(projectRoot);
230
+ }
231
+ catch {
232
+ // Unexpected fault: report it as data, without any message or path.
233
+ const internal = report('config', null, 'unreadable', 'INTERNAL');
234
+ return unreadableModel(internal, 'INTERNAL');
235
+ }
236
+ }
@@ -0,0 +1,3 @@
1
+ export { readDashboardModel, ROADMAP_REL, MAX_ROADMAP_BYTES } from './dashboard.js';
2
+ export type { CheckpointsData, CurrentMissions, DashboardReadModel, MissionCheckpoints, NoteCode, ProjectOverview, ReadDiagnostics, ReadNote, RoadmapData, Section, SourceErrorCode, SourceId, SourceReport, SourceStatus, } from './types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/readmodel/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACpF,YAAY,EACV,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,QAAQ,EACR,eAAe,EACf,eAAe,EACf,QAAQ,EACR,WAAW,EACX,OAAO,EACP,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC"}
@@ -0,0 +1 @@
1
+ export { readDashboardModel, ROADMAP_REL, MAX_ROADMAP_BYTES } from './dashboard.js';
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Typed Read Model for Mission Control (UI-001B.1, ADR-010).
3
+ *
4
+ * A pure, read-only projection of data MONOLITH Core already owns. It adds no
5
+ * persistent schema and no source of truth: every field is copied from, or
6
+ * counted over, an existing artifact (`monolith.config.json`, `state.json`,
7
+ * `active.md`, `roadmap-index.md`).
8
+ *
9
+ * Errors are DATA, not text: only fixed enum codes and project-relative paths
10
+ * cross this boundary. No absolute path, no exception message and no file
11
+ * excerpt other than the explicit content fields below ever appears in a
12
+ * diagnostic, so the DTO is safe to serialize over HTTP later (G2 still gates
13
+ * that).
14
+ */
15
+ import type { Checkpoint, Mission } from '../coordinatorium/types.js';
16
+ /** The fixed, closed set of files this model reads. */
17
+ export type SourceId = 'config' | 'state' | 'activeStory' | 'roadmap';
18
+ /**
19
+ * - available: read and (where applicable) parsed successfully
20
+ * - absent: the file does not exist (a legitimate state for a fresh project)
21
+ * - corrupt: the file was read but its content is unusable
22
+ * - forbidden: reading was refused by a security boundary
23
+ * - unreadable: the file exists inside the boundary but could not be read
24
+ */
25
+ export type SourceStatus = 'available' | 'absent' | 'corrupt' | 'forbidden' | 'unreadable';
26
+ export type SourceErrorCode = 'SOURCE_ABSENT' | 'SOURCE_OUTSIDE_PROJECT' | 'SOURCE_UNRESOLVABLE' | 'SOURCE_NOT_REGULAR' | 'SOURCE_EMPTY' | 'SOURCE_TOO_LARGE' | 'SOURCE_CHANGED' | 'SOURCE_UNREADABLE' | 'SOURCE_SYMLINK' | 'SOURCE_HARDLINKED' | 'SOURCE_BOUNDARY_INVALID' | 'SOURCE_UNSUPPORTED_PLATFORM' | 'PARSE_ERROR' | 'SCHEMA_INVALID' | 'CONFIG_INVALID' | 'ROOT_INVALID' | 'INTERNAL';
27
+ export interface SourceReport {
28
+ id: SourceId;
29
+ /** Project-relative, `/`-separated; null when the instance root could not be trusted. */
30
+ path: string | null;
31
+ status: SourceStatus;
32
+ /** null iff status === 'available'. */
33
+ code: SourceErrorCode | null;
34
+ }
35
+ /** `data` is null only when the sections' sources could not supply it. */
36
+ export interface Section<T> {
37
+ data: T | null;
38
+ /** The sources this section depends on, in fixed order. */
39
+ sources: SourceReport[];
40
+ }
41
+ export interface ProjectOverview {
42
+ /** `project.name` from `monolith.config.json`; null when absent or not a non-empty string. */
43
+ name: string | null;
44
+ /** Validated relative instance root (ADR-009), `/`-separated. */
45
+ monolithRoot: string;
46
+ monolithRootSource: 'config' | 'default';
47
+ /** `schemaVersion` as found on disk (1 is migrated in memory); null unless state is available. */
48
+ stateSchemaVersionOnDisk: number | null;
49
+ stateRevision: number | null;
50
+ /** Counts over `tactical.missions`; null unless state is available or absent (absent ⇒ 0). */
51
+ activeMissionCount: number | null;
52
+ finishedMissionCount: number | null;
53
+ /** Verbatim `## Current Story` section of `active.md`; null when the section cannot be shown. */
54
+ currentStory: {
55
+ excerpt: string;
56
+ lineRange: string;
57
+ } | null;
58
+ }
59
+ export interface CurrentMissions {
60
+ /** Every `in_progress` mission, ordered by (startedAt, id). More than one is reported, never collapsed. */
61
+ active: Mission[];
62
+ }
63
+ export interface MissionCheckpoints {
64
+ missionId: string;
65
+ /** Number of checkpoints recorded for the mission. */
66
+ count: number;
67
+ /**
68
+ * Highest-ordinal checkpoint (ID order is authoritative, as in `brief`).
69
+ * Its `remainingSteps` are a HISTORICAL record at that checkpoint, not the
70
+ * project's current plan.
71
+ */
72
+ latest: Checkpoint | null;
73
+ }
74
+ export interface CheckpointsData {
75
+ /** One entry per active mission, same order as `CurrentMissions.active`. */
76
+ byActiveMission: MissionCheckpoints[];
77
+ }
78
+ export interface RoadmapData {
79
+ /** Raw, unparsed text of `roadmap-index.md`. Untrusted: must be shown escaped, never rendered or executed. */
80
+ text: string;
81
+ }
82
+ export type NoteCode = 'STATE_LEGACY_SCHEMA_MIGRATED' | 'MULTIPLE_ACTIVE_MISSIONS' | 'CHECKPOINT_ORDER_MISMATCH' | 'ACTIVE_MD_NO_CURRENT_STORY';
83
+ export interface ReadNote {
84
+ code: NoteCode;
85
+ severity: 'info' | 'warn';
86
+ }
87
+ export interface ReadDiagnostics {
88
+ /**
89
+ * This reports availability of the fixed sources only. `monolith validate`
90
+ * is never run, so a clean report is NOT a statement that the project is valid.
91
+ */
92
+ validation: 'NOT_PERFORMED';
93
+ /** Every source, in fixed order: config, state, activeStory, roadmap. */
94
+ sources: SourceReport[];
95
+ notes: ReadNote[];
96
+ }
97
+ export interface DashboardReadModel {
98
+ projectOverview: Section<ProjectOverview>;
99
+ currentMissions: Section<CurrentMissions>;
100
+ checkpoints: Section<CheckpointsData>;
101
+ roadmap: Section<RoadmapData>;
102
+ diagnostics: ReadDiagnostics;
103
+ }
104
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/readmodel/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAEtE,uDAAuD;AACvD,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,aAAa,GAAG,SAAS,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,YAAY,CAAC;AAE3F,MAAM,MAAM,eAAe,GACvB,eAAe,GACf,wBAAwB,GACxB,qBAAqB,GACrB,oBAAoB,GACpB,cAAc,GACd,kBAAkB,GAClB,gBAAgB,GAChB,mBAAmB,GACnB,gBAAgB,GAChB,mBAAmB,GACnB,yBAAyB,GACzB,6BAA6B,GAC7B,aAAa,GACb,gBAAgB,GAChB,gBAAgB,GAChB,cAAc,GACd,UAAU,CAAC;AAEf,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,QAAQ,CAAC;IACb,yFAAyF;IACzF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC;IACrB,uCAAuC;IACvC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;CAC9B;AAED,0EAA0E;AAC1E,MAAM,WAAW,OAAO,CAAC,CAAC;IACxB,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,2DAA2D;IAC3D,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,8FAA8F;IAC9F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,QAAQ,GAAG,SAAS,CAAC;IACzC,kGAAkG;IAClG,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,8FAA8F;IAC9F,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,iGAAiG;IACjG,YAAY,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC7D;AAED,MAAM,WAAW,eAAe;IAC9B,2GAA2G;IAC3G,MAAM,EAAE,OAAO,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,eAAe,EAAE,kBAAkB,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,WAAW;IAC1B,8GAA8G;IAC9G,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,QAAQ,GAChB,8BAA8B,GAC9B,0BAA0B,GAC1B,2BAA2B,GAC3B,4BAA4B,CAAC;AAEjC,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,UAAU,EAAE,eAAe,CAAC;IAC5B,yEAAyE;IACzE,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,eAAe,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,eAAe,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,WAAW,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACtC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9B,WAAW,EAAE,eAAe,CAAC;CAC9B"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Typed Read Model for Mission Control (UI-001B.1, ADR-010).
3
+ *
4
+ * A pure, read-only projection of data MONOLITH Core already owns. It adds no
5
+ * persistent schema and no source of truth: every field is copied from, or
6
+ * counted over, an existing artifact (`monolith.config.json`, `state.json`,
7
+ * `active.md`, `roadmap-index.md`).
8
+ *
9
+ * Errors are DATA, not text: only fixed enum codes and project-relative paths
10
+ * cross this boundary. No absolute path, no exception message and no file
11
+ * excerpt other than the explicit content fields below ever appears in a
12
+ * diagnostic, so the DTO is safe to serialize over HTTP later (G2 still gates
13
+ * that).
14
+ */
15
+ export {};