@latysh-development/monolith 0.0.0-stage → 0.1.0-alpha.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 (291) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -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 +302 -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 +10 -0
  13. package/dist/brief/constants.d.ts.map +1 -0
  14. package/dist/brief/constants.js +9 -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 +60 -0
  31. package/dist/brief/render.d.ts.map +1 -0
  32. package/dist/brief/render.js +407 -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 +110 -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 +166 -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/durable/assess.d.ts +105 -0
  82. package/dist/durable/assess.d.ts.map +1 -0
  83. package/dist/durable/assess.js +239 -0
  84. package/dist/durable/canonical.d.ts +25 -0
  85. package/dist/durable/canonical.d.ts.map +1 -0
  86. package/dist/durable/canonical.js +78 -0
  87. package/dist/durable/cli.d.ts +37 -0
  88. package/dist/durable/cli.d.ts.map +1 -0
  89. package/dist/durable/cli.js +141 -0
  90. package/dist/durable/errors.d.ts +188 -0
  91. package/dist/durable/errors.d.ts.map +1 -0
  92. package/dist/durable/errors.js +317 -0
  93. package/dist/durable/fs-store.d.ts +233 -0
  94. package/dist/durable/fs-store.d.ts.map +1 -0
  95. package/dist/durable/fs-store.js +1393 -0
  96. package/dist/durable/index.d.ts +20 -0
  97. package/dist/durable/index.d.ts.map +1 -0
  98. package/dist/durable/index.js +13 -0
  99. package/dist/durable/log.d.ts +33 -0
  100. package/dist/durable/log.d.ts.map +1 -0
  101. package/dist/durable/log.js +157 -0
  102. package/dist/durable/memory-store.d.ts +51 -0
  103. package/dist/durable/memory-store.d.ts.map +1 -0
  104. package/dist/durable/memory-store.js +302 -0
  105. package/dist/durable/projection.d.ts +35 -0
  106. package/dist/durable/projection.d.ts.map +1 -0
  107. package/dist/durable/projection.js +328 -0
  108. package/dist/durable/prune-check.d.ts +17 -0
  109. package/dist/durable/prune-check.d.ts.map +1 -0
  110. package/dist/durable/prune-check.js +45 -0
  111. package/dist/durable/register.d.ts +100 -0
  112. package/dist/durable/register.d.ts.map +1 -0
  113. package/dist/durable/register.js +532 -0
  114. package/dist/durable/retention.d.ts +49 -0
  115. package/dist/durable/retention.d.ts.map +1 -0
  116. package/dist/durable/retention.js +59 -0
  117. package/dist/durable/runtime.d.ts +497 -0
  118. package/dist/durable/runtime.d.ts.map +1 -0
  119. package/dist/durable/runtime.js +1271 -0
  120. package/dist/durable/seams.d.ts +40 -0
  121. package/dist/durable/seams.d.ts.map +1 -0
  122. package/dist/durable/seams.js +21 -0
  123. package/dist/durable/testing.d.ts +11 -0
  124. package/dist/durable/testing.d.ts.map +1 -0
  125. package/dist/durable/testing.js +25 -0
  126. package/dist/durable/types.d.ts +442 -0
  127. package/dist/durable/types.d.ts.map +1 -0
  128. package/dist/durable/types.js +75 -0
  129. package/dist/graph/builder.d.ts +11 -0
  130. package/dist/graph/builder.d.ts.map +1 -0
  131. package/dist/graph/builder.js +153 -0
  132. package/dist/graph/identity.d.ts +37 -0
  133. package/dist/graph/identity.d.ts.map +1 -0
  134. package/dist/graph/identity.js +46 -0
  135. package/dist/graph/index.d.ts +7 -0
  136. package/dist/graph/index.d.ts.map +1 -0
  137. package/dist/graph/index.js +3 -0
  138. package/dist/graph/resolver.d.ts +41 -0
  139. package/dist/graph/resolver.d.ts.map +1 -0
  140. package/dist/graph/resolver.js +36 -0
  141. package/dist/graph/types.d.ts +42 -0
  142. package/dist/graph/types.d.ts.map +1 -0
  143. package/dist/graph/types.js +1 -0
  144. package/dist/http/index.d.ts +5 -0
  145. package/dist/http/index.d.ts.map +1 -0
  146. package/dist/http/index.js +2 -0
  147. package/dist/http/server.d.ts +76 -0
  148. package/dist/http/server.d.ts.map +1 -0
  149. package/dist/http/server.js +195 -0
  150. package/dist/http/ui-assets.d.ts +22 -0
  151. package/dist/http/ui-assets.d.ts.map +1 -0
  152. package/dist/http/ui-assets.js +100 -0
  153. package/dist/index.d.ts +31 -0
  154. package/dist/index.d.ts.map +1 -0
  155. package/dist/index.js +26 -0
  156. package/dist/init/index.d.ts +12 -0
  157. package/dist/init/index.d.ts.map +1 -0
  158. package/dist/init/index.js +90 -0
  159. package/dist/init/prompts.d.ts +3 -0
  160. package/dist/init/prompts.d.ts.map +1 -0
  161. package/dist/init/prompts.js +47 -0
  162. package/dist/init/scaffold.d.ts +25 -0
  163. package/dist/init/scaffold.d.ts.map +1 -0
  164. package/dist/init/scaffold.js +204 -0
  165. package/dist/init/types.d.ts +14 -0
  166. package/dist/init/types.d.ts.map +1 -0
  167. package/dist/init/types.js +1 -0
  168. package/dist/readmodel/dashboard.d.ts +40 -0
  169. package/dist/readmodel/dashboard.d.ts.map +1 -0
  170. package/dist/readmodel/dashboard.js +236 -0
  171. package/dist/readmodel/index.d.ts +3 -0
  172. package/dist/readmodel/index.d.ts.map +1 -0
  173. package/dist/readmodel/index.js +1 -0
  174. package/dist/readmodel/types.d.ts +104 -0
  175. package/dist/readmodel/types.d.ts.map +1 -0
  176. package/dist/readmodel/types.js +15 -0
  177. package/dist/root/index.d.ts +179 -0
  178. package/dist/root/index.d.ts.map +1 -0
  179. package/dist/root/index.js +418 -0
  180. package/dist/root/safe-write.d.ts +268 -0
  181. package/dist/root/safe-write.d.ts.map +1 -0
  182. package/dist/root/safe-write.js +966 -0
  183. package/dist/root/write-artifacts.d.ts +20 -0
  184. package/dist/root/write-artifacts.d.ts.map +1 -0
  185. package/dist/root/write-artifacts.js +42 -0
  186. package/dist/scriptorium/compile.d.ts +4 -0
  187. package/dist/scriptorium/compile.d.ts.map +1 -0
  188. package/dist/scriptorium/compile.js +185 -0
  189. package/dist/scriptorium/frontmatter.d.ts +13 -0
  190. package/dist/scriptorium/frontmatter.d.ts.map +1 -0
  191. package/dist/scriptorium/frontmatter.js +90 -0
  192. package/dist/scriptorium/index.d.ts +3 -0
  193. package/dist/scriptorium/index.d.ts.map +1 -0
  194. package/dist/scriptorium/index.js +1 -0
  195. package/dist/scriptorium/markdown.d.ts +31 -0
  196. package/dist/scriptorium/markdown.d.ts.map +1 -0
  197. package/dist/scriptorium/markdown.js +88 -0
  198. package/dist/scriptorium/types.d.ts +78 -0
  199. package/dist/scriptorium/types.d.ts.map +1 -0
  200. package/dist/scriptorium/types.js +1 -0
  201. package/dist/types/enforcement.d.ts +11 -0
  202. package/dist/types/enforcement.d.ts.map +1 -0
  203. package/dist/types/enforcement.js +1 -0
  204. package/dist/types/integrity.d.ts +33 -0
  205. package/dist/types/integrity.d.ts.map +1 -0
  206. package/dist/types/integrity.js +1 -0
  207. package/dist/types/project-definition.d.ts +30 -0
  208. package/dist/types/project-definition.d.ts.map +1 -0
  209. package/dist/types/project-definition.js +1 -0
  210. package/dist/ui/assets/cosmic-glass-bg-Dwx47zPu.webp +0 -0
  211. package/dist/ui/assets/index-C4NhMaq7.js +28 -0
  212. package/dist/ui/assets/index-DMZ7ZvZY.css +1 -0
  213. package/dist/ui/index.html +13 -0
  214. package/dist/validate/index.d.ts +6 -0
  215. package/dist/validate/index.d.ts.map +1 -0
  216. package/dist/validate/index.js +214 -0
  217. package/dist/version.d.ts +2 -0
  218. package/dist/version.d.ts.map +1 -0
  219. package/dist/version.js +1 -0
  220. package/package.json +67 -4
  221. package/templates/.gitkeep +0 -0
  222. package/templates/default-instance/instruction/.cursorrules +53 -0
  223. package/templates/default-instance/instruction/.windsurfrules +53 -0
  224. package/templates/default-instance/instruction/CLAUDE.md +53 -0
  225. package/templates/default-instance/monolith/context/boot/HANDOFF.md +49 -0
  226. package/templates/default-instance/monolith/context/boot/WORK_SESSION.md +30 -0
  227. package/templates/default-instance/monolith/context/execution/current-state.md +30 -0
  228. package/templates/default-instance/monolith/context/maps/dependency-map.md +38 -0
  229. package/templates/default-instance/monolith/context/maps/project-map.md +85 -0
  230. package/templates/default-instance/monolith/context/maps/roadmap-index.md +41 -0
  231. package/templates/default-instance/monolith/context/maps/routing-map.md +147 -0
  232. package/templates/default-instance/monolith/context/maps/semantic-map.md +73 -0
  233. package/templates/default-instance/monolith/context/models/model-registry.md +37 -0
  234. package/templates/default-instance/monolith/context/models/privacy-classification.md +35 -0
  235. package/templates/default-instance/monolith/context/templates/plan-template.md +56 -0
  236. package/templates/default-instance/monolith/governance/README.md +29 -0
  237. package/templates/default-instance/monolith/governance/RULE_SCHEMA.md +58 -0
  238. package/templates/default-instance/monolith/learning/INDEX.md +48 -0
  239. package/templates/default-instance/monolith/learning/injection-map.md +49 -0
  240. package/templates/default-instance/monolith/learning/taxonomy.md +30 -0
  241. package/templates/default-instance/monolith/memory/errors/README.md +26 -0
  242. package/templates/default-instance/monolith/memory/errors/SCHEMA.md +47 -0
  243. package/templates/default-instance/monolith/memory/errors/TAXONOMY.md +27 -0
  244. package/templates/default-instance/monolith/memory/long-term/architecture-principles.md +31 -0
  245. package/templates/default-instance/monolith/memory/long-term/decisions.md +36 -0
  246. package/templates/default-instance/monolith/memory/long-term/execution-history.md +20 -0
  247. package/templates/default-instance/monolith/memory/long-term/tech-debt.md +25 -0
  248. package/templates/default-instance/monolith/memory/working/active.md +41 -0
  249. package/templates/default-instance/monolith/memory/working/governance-result.md +0 -0
  250. package/templates/default-instance/monolith/memory/working/integrity-result.md +0 -0
  251. package/templates/default-instance/monolith/memory/working/story-index.md +28 -0
  252. package/templates/default-instance/monolith/protocols/boot/boot-sequence.md +59 -0
  253. package/templates/default-instance/monolith/protocols/cognition/cognitive-modes.md +90 -0
  254. package/templates/default-instance/monolith/protocols/cognition/dry-run.md +56 -0
  255. package/templates/default-instance/monolith/protocols/cognition/execution-trace-format.md +52 -0
  256. package/templates/default-instance/monolith/protocols/cognition/semantic-planning.md +51 -0
  257. package/templates/default-instance/monolith/protocols/execution/hybrid-execution.md +37 -0
  258. package/templates/default-instance/monolith/protocols/execution/local-llm-governance.md +42 -0
  259. package/templates/default-instance/monolith/protocols/governance/command-policy.md +51 -0
  260. package/templates/default-instance/monolith/protocols/governance/core-invariants.md +40 -0
  261. package/templates/default-instance/monolith/protocols/governance/memory-governance.md +54 -0
  262. package/templates/default-instance/monolith/protocols/governance/metadata-schema.md +62 -0
  263. package/templates/default-instance/monolith/protocols/governance/skills.md +53 -0
  264. package/templates/default-instance/monolith/protocols/governance/work-session.md +46 -0
  265. package/templates/default-instance/monolith/skills/INDEX.md +28 -0
  266. package/templates/default-instance/monolith/skills/current-status/SKILL.md +47 -0
  267. package/templates/default-instance/monolith/skills/finalize-story/SKILL.md +86 -0
  268. package/templates/default-instance/monolith/skills/global-status/SKILL.md +56 -0
  269. package/templates/default-instance/monolith/skills/self-improvement/SKILL.md +77 -0
  270. package/templates/default-instance/monolith/state.json +11 -0
  271. package/templates/default-instance/monolith/tests/INDEX.md +57 -0
  272. package/templates/default-instance/monolith/tests/scenarios/architecture-review.md +32 -0
  273. package/templates/default-instance/monolith/tests/scenarios/debug-failure.md +32 -0
  274. package/templates/default-instance/monolith/tests/scenarios/dry-run-activation.md +33 -0
  275. package/templates/default-instance/monolith/tests/scenarios/execution-trace-verification.md +31 -0
  276. package/templates/default-instance/monolith/tests/scenarios/finalize-story.md +35 -0
  277. package/templates/default-instance/monolith/tests/scenarios/implement-feature.md +36 -0
  278. package/templates/default-instance/monolith/tests/scenarios/local-llm-delegation.md +31 -0
  279. package/templates/default-instance/monolith/tests/scenarios/planning-request.md +32 -0
  280. package/templates/default-instance/monolith/tests/scenarios/sensitive-context-task.md +30 -0
  281. package/templates/default-instance/monolith/tests/scenarios/validator-verification.md +33 -0
  282. package/templates/default-instance/monolith/tests/validation/expected-behavior.md +70 -0
  283. package/templates/default-instance/monolith/tests/validators/INDEX.md +17 -0
  284. package/templates/default-instance/monolith/tests/validators/delegation-semantics.md +22 -0
  285. package/templates/default-instance/monolith/tests/validators/mode-consistency.md +25 -0
  286. package/templates/default-instance/monolith/tests/validators/privacy-routing.md +23 -0
  287. package/templates/default-instance/monolith/tests/validators/protocol-references.md +27 -0
  288. package/templates/default-instance/monolith/tests/validators/routing-integrity.md +30 -0
  289. package/templates/default-instance/projectroot/AGENTS.md +107 -0
  290. package/templates/default-instance/projectroot/monolith.config.json +32 -0
  291. package/templates/default-instance/projectroot/project.definition.mjs.template +41 -0
@@ -0,0 +1,407 @@
1
+ /**
2
+ * `render(model, maxChars)` — the Rendering stage of the brief pipeline.
3
+ *
4
+ * Pure and synchronous: same BriefModel + same maxChars always produces the
5
+ * byte-identical string (section 11 — determinism). No Date.now(), no
6
+ * random IDs, no locale-dependent sorting; every list collect() hands in is
7
+ * already in a fixed, stably-sorted order.
8
+ *
9
+ * Budget accounting (section 10) in two phases to avoid a circular
10
+ * dependency between "does P0 fit" and "what does the Completeness line
11
+ * say" (rule: no diagnostic/marker line may be added without being counted
12
+ * in the budget that decided what else fits):
13
+ *
14
+ * Phase 1 — build all P0 content (identity, State Keeper status,
15
+ * collect-time diagnostics, every active mission identifier, every
16
+ * mandatory protocol pointer) with NO budget limit — P0 is never
17
+ * truncated or hidden (rule 5/7). If P0 alone (plus the reserved,
18
+ * fixed-size Completeness line) exceeds maxChars, that is reported via
19
+ * BUDGET_EXCEEDED_BY_REQUIRED (which also states explicitly that P1–P3
20
+ * are skipped entirely as a result — review finding F-5) and nothing
21
+ * else is attempted.
22
+ *
23
+ * Phase 2 — whatever chars remain after P0 + the reserved Completeness
24
+ * line are handed to P1 (Checkpoints, then Story — Checkpoints first so
25
+ * a long Story can never silently starve "Next steps" out of the budget;
26
+ * review finding F-5), then P2, then P3, in that fixed order. Each
27
+ * section either fits whole, or (list sections) includes as many whole
28
+ * items as fit plus an explicit "N more omitted" marker, or (the Story
29
+ * excerpt) is field-truncated with an explicit `[truncated N chars]`
30
+ * marker, or — if not even that fits — falls back to a short, fixed-size
31
+ * local omission marker so a section can never disappear from the output
32
+ * without some local trace of its omission (review finding F-5; the only
33
+ * residual case where a section can still vanish with zero local marker
34
+ * is when `remaining` is too small even for that minimal marker, which
35
+ * is itself already covered by the global Completeness: INCOMPLETE
36
+ * signal). Every one of these outcomes is itself measured and subtracted
37
+ * from the remaining budget before the next section is attempted —
38
+ * nothing is added "for free" after the fact.
39
+ *
40
+ * Completeness contract (N-3): `Completeness: COMPLETE` means no
41
+ * *substantive data* was lost, not that every section heading is present.
42
+ * An optional section that has nothing to show (an empty list, rendered
43
+ * only as "(none)") and the static P3 pointer block may be dropped under a
44
+ * tight budget without flipping the result to INCOMPLETE — dropping them
45
+ * loses no information. A section that holds data is never dropped
46
+ * silently: it is shown whole, shown partially with an explicit marker, or
47
+ * replaced by a pointer-bearing omission marker, and either of the latter
48
+ * two forces INCOMPLETE. P0 is never dropped. All project-controlled
49
+ * strings, provenance pointers included, pass through
50
+ * `sanitizeSingleLine` (N-1).
51
+ */
52
+ import { ACTIVE_MD_REL } from './constants.js';
53
+ import { sanitizeSingleLine, sanitizeMultiLine } from './sanitize.js';
54
+ // Shorthand for the existing single-line sanitizer — applied to every string
55
+ // that originates in project files or configuration (N-1), including
56
+ // provenance pointers, which embed config-controlled paths.
57
+ const san = sanitizeSingleLine;
58
+ export const DEFAULT_MAX_CHARS = 6000;
59
+ const RECENT_FINISHED_MISSIONS_RENDER_LIMIT = 5;
60
+ const COMPLETE_LINE = 'Completeness: COMPLETE';
61
+ const INCOMPLETE_LINE = 'Completeness: INCOMPLETE';
62
+ const RESERVED_COMPLETENESS_LEN = Math.max(COMPLETE_LINE.length, INCOMPLETE_LINE.length) + 1;
63
+ const BUDGET_EXCEEDED_LINE = 'BUDGET_EXCEEDED_BY_REQUIRED: required (P0) content exceeds --max-chars; shown in full regardless ' +
64
+ '(--max-chars is not honored in this case). Optional sections (Current Story, Active Mission ' +
65
+ 'Checkpoints, Accepted ADR Index, Recently Finished Missions) are skipped entirely as a result.';
66
+ function formatDiagnosticsBlock(diagnostics, extraLine) {
67
+ const lines = diagnostics.map((d) => ` [${d.severity.toUpperCase()}] ${d.code}: ${sanitizeSingleLine(d.message)}`);
68
+ if (extraLine)
69
+ lines.push(` [ERROR] ${extraLine}`);
70
+ const body = lines.length > 0 ? lines.join('\n') + '\n' : ' (none)\n';
71
+ return `\n## Diagnostics\n${body}`;
72
+ }
73
+ function formatStateKeeperBlock(sk) {
74
+ const versionNote = sk.schemaVersionOnDisk !== undefined ? ` schemaVersionOnDisk=${sk.schemaVersionOnDisk}` : '';
75
+ return `\n## State Keeper\n status: ${sk.status}${versionNote}\n (source: ${san(sk.provenance.source)})\n`;
76
+ }
77
+ function formatMission(m) {
78
+ return ` - ${san(m.id)} [${m.status}] ${san(m.title)} (${san(m.system)}) — started ${san(m.startedAt)}\n`;
79
+ }
80
+ function formatActiveMissionsBlock(missions, provenance) {
81
+ const body = missions.length > 0 ? missions.map(formatMission).join('') : ' (none)\n';
82
+ return `\n## Active Missions (${missions.length})\n${body} (source: ${san(provenance.source)})\n`;
83
+ }
84
+ function formatDocRef(d) {
85
+ return ` - ${san(d.id)}: ${san(d.title)} (${san(d.path)})\n`;
86
+ }
87
+ function formatProtocolsBlock(items, provenance) {
88
+ const body = items.length > 0 ? items.map(formatDocRef).join('') : ' (none)\n';
89
+ return `\n## Mandatory Protocols (${items.length})\n${body} (source: ${san(provenance.source)})\n`;
90
+ }
91
+ function formatIdentityTitle(model) {
92
+ return `\n# MONOLITH Brief — ${san(model.identity.name)}\n(monolithRoot: ${san(model.identity.monolithRoot)}, source: ${san(model.identity.provenance.source)})\n`;
93
+ }
94
+ const ACTIVE_STORY_LABEL = {
95
+ 'none-recorded': 'none recorded (this does not mean the project is complete)',
96
+ recorded: 'recorded in active.md',
97
+ unknown: 'unknown',
98
+ };
99
+ /**
100
+ * SELF-AWARENESS-001: the Freshness block. Part of P0 — it is never truncated,
101
+ * and it never influences `Completeness`. Every string that originates in
102
+ * project files or git history passes through the sanitizer.
103
+ */
104
+ function formatFreshnessBlock(f) {
105
+ const lines = ['', '## Freshness', ` Status: ${f.status}`];
106
+ if (f.lastStateUpdate) {
107
+ lines.push(` Last state update: ${san(f.lastStateUpdate.sha.slice(0, 7))} (${san(f.lastStateUpdate.date.slice(0, 10))}, git)`);
108
+ }
109
+ lines.push(` Active Story: ${ACTIVE_STORY_LABEL[f.activeStory]}`);
110
+ if (f.reasons.length > 0) {
111
+ lines.push(' Evidence:');
112
+ for (const r of f.reasons)
113
+ lines.push(` - [${r.code}] ${san(r.message)}`);
114
+ }
115
+ if (f.warnings.length > 0) {
116
+ lines.push(' Warnings:');
117
+ for (const w of f.warnings)
118
+ lines.push(` - ${san(w)}`);
119
+ }
120
+ lines.push(` Recommendation: ${san(f.recommendation)}`);
121
+ return lines.join('\n') + '\n';
122
+ }
123
+ function buildCoreP0(model, includeBudgetExceeded) {
124
+ return (formatIdentityTitle(model) +
125
+ (model.freshness ? formatFreshnessBlock(model.freshness) : '') +
126
+ formatStateKeeperBlock(model.stateKeeper) +
127
+ formatDiagnosticsBlock(model.diagnostics, includeBudgetExceeded ? BUDGET_EXCEEDED_LINE : null) +
128
+ formatActiveMissionsBlock(model.missions.active, model.missions.provenance) +
129
+ formatProtocolsBlock(model.mandatoryProtocols.items, model.mandatoryProtocols.provenance));
130
+ }
131
+ /**
132
+ * Truncates `text` to fit within `maxBodyLen`, appending an explicit,
133
+ * pointer-bearing `[truncated N chars — see <pointer>]` marker where N is
134
+ * the actual number of characters removed.
135
+ *
136
+ * Review finding F-4: N depends on the marker's own length (removing more
137
+ * chars to make room for a longer "N" can itself change how many digits N
138
+ * has), so a single-pass `text.length - maxBodyLen` estimate under-reports
139
+ * N by exactly the marker's length. This resolves it as a small fixed-point
140
+ * computation: rebuild the marker from the current estimate, re-derive how
141
+ * much is actually kept, and repeat until N stops changing (converges in at
142
+ * most a couple of iterations — the only way it can change at all is a
143
+ * digit-count crossing in N, which an 8-iteration cap comfortably covers).
144
+ *
145
+ * The cut point is also nudged left by one character if it would otherwise
146
+ * split a UTF-16 surrogate pair, and N is recomputed once more for that
147
+ * final cut point so it stays exact even in that case.
148
+ */
149
+ function truncateToBudget(text, maxBodyLen, pointer) {
150
+ if (text.length <= maxBodyLen)
151
+ return text;
152
+ const buildMarker = (removed) => `\n[truncated ${removed} chars — see ${pointer}]`;
153
+ let removed = text.length - maxBodyLen;
154
+ let keepLen = 0;
155
+ for (let i = 0; i < 8; i++) {
156
+ const marker = buildMarker(removed);
157
+ keepLen = Math.max(0, maxBodyLen - marker.length);
158
+ const nextRemoved = text.length - keepLen;
159
+ if (nextRemoved === removed)
160
+ break;
161
+ removed = nextRemoved;
162
+ }
163
+ if (keepLen > 0 && keepLen < text.length) {
164
+ const code = text.charCodeAt(keepLen - 1);
165
+ if (code >= 0xd800 && code <= 0xdbff)
166
+ keepLen -= 1; // don't split a surrogate pair
167
+ }
168
+ const finalRemoved = text.length - keepLen;
169
+ return text.slice(0, keepLen) + buildMarker(finalRemoved);
170
+ }
171
+ /**
172
+ * Absolute last-resort fallback (review finding F-5): a label- and
173
+ * pointer-free marker so short it fits almost any nonzero `remaining`
174
+ * budget. Each section tries its own more informative (labeled,
175
+ * pointer-bearing) fallback first; this is only reached when even that
176
+ * doesn't fit. The only way a section can still end up with zero local
177
+ * trace is `remaining` being smaller than this marker itself — by that
178
+ * point the budget is so exhausted that the global `Completeness:
179
+ * INCOMPLETE` line is the only thing that could fit anyway.
180
+ */
181
+ const BARE_OMIT_MARKER = '\n[omitted — budget]\n';
182
+ function buildStorySection(model, remaining) {
183
+ const header = '\n## Current Story\n';
184
+ const pointer = san(model.story.provenance?.source ?? ACTIVE_MD_REL);
185
+ const minimalFallback = `\n[Story omitted — budget; see ${pointer}]\n`;
186
+ let body;
187
+ if (!model.story.fileExists) {
188
+ body = ' (active.md not found)\n';
189
+ }
190
+ else if (!model.story.hasCurrentStorySection) {
191
+ body = ' (no "Current Story" section found)\n';
192
+ }
193
+ else if (model.story.excerpt === '') {
194
+ body = ' (section present but empty)\n';
195
+ }
196
+ else {
197
+ body = `${sanitizeMultiLine(model.story.excerpt ?? '')}\n`;
198
+ }
199
+ const provenanceLine = model.story.provenance ? ` (source: ${san(model.story.provenance.source)})\n` : '';
200
+ const full = header + body + provenanceLine;
201
+ if (full.length <= remaining) {
202
+ return { text: full, consumed: full.length, lossFlag: false };
203
+ }
204
+ if (model.story.excerpt) {
205
+ const sanitizedExcerpt = sanitizeMultiLine(model.story.excerpt);
206
+ const fixedOverhead = header.length + 1 + provenanceLine.length;
207
+ const budgetForBody = remaining - fixedOverhead;
208
+ if (budgetForBody > 0) {
209
+ const truncatedBody = truncateToBudget(sanitizedExcerpt, budgetForBody, pointer);
210
+ const truncatedFull = header + truncatedBody + '\n' + provenanceLine;
211
+ if (truncatedFull.length <= remaining) {
212
+ return { text: truncatedFull, consumed: truncatedFull.length, lossFlag: true };
213
+ }
214
+ }
215
+ }
216
+ const omitLine = `\n## Current Story\n [omitted — budget] see ${pointer}\n`;
217
+ if (omitLine.length <= remaining) {
218
+ return { text: omitLine, consumed: omitLine.length, lossFlag: true };
219
+ }
220
+ if (minimalFallback.length <= remaining) {
221
+ return { text: minimalFallback, consumed: minimalFallback.length, lossFlag: true };
222
+ }
223
+ if (BARE_OMIT_MARKER.length <= remaining) {
224
+ return { text: BARE_OMIT_MARKER, consumed: BARE_OMIT_MARKER.length, lossFlag: true };
225
+ }
226
+ return { text: '', consumed: 0, lossFlag: true };
227
+ }
228
+ function formatCheckpointItem(cp) {
229
+ const lines = [` - Mission ${san(cp.missionId)}:`];
230
+ if (!cp.latest) {
231
+ lines.push(' Next steps: UNKNOWN (no checkpoints)');
232
+ }
233
+ else {
234
+ lines.push(` checkpoint: ${san(cp.latest.id)} (createdAt ${san(cp.latest.createdAt)})`);
235
+ lines.push(` summary: ${san(cp.latest.summary)}`);
236
+ if (cp.latest.remainingSteps.length === 0) {
237
+ lines.push(' Next steps: none recorded');
238
+ }
239
+ else {
240
+ lines.push(' Next steps:');
241
+ for (const step of cp.latest.remainingSteps)
242
+ lines.push(` - ${san(step)}`);
243
+ }
244
+ }
245
+ lines.push(` (source: ${san(cp.provenance.source)})`);
246
+ return lines.join('\n') + '\n';
247
+ }
248
+ function buildCheckpointsSection(model, remaining) {
249
+ if (model.checkpoints.length === 0)
250
+ return { text: '', consumed: 0, lossFlag: false };
251
+ const header = '\n## Active Mission Checkpoints\n';
252
+ const items = model.checkpoints.map(formatCheckpointItem);
253
+ const minimalFallback = '\n[Active Mission Checkpoints omitted — budget; run `monolith mission checkpoints <id>`]\n';
254
+ const makeMarker = (n) => ` ...and ${n} more mission(s)' checkpoint details omitted (budget) — run \`monolith mission checkpoints <id>\`.\n`;
255
+ let used = header.length;
256
+ const included = [];
257
+ for (const item of items) {
258
+ if (used + item.length <= remaining) {
259
+ included.push(item);
260
+ used += item.length;
261
+ }
262
+ else {
263
+ break;
264
+ }
265
+ }
266
+ let omitted = items.length - included.length;
267
+ let marker = '';
268
+ if (omitted > 0) {
269
+ marker = makeMarker(omitted);
270
+ while (included.length > 0 && used + marker.length > remaining) {
271
+ const removedItem = included.pop();
272
+ used -= removedItem.length;
273
+ omitted = items.length - included.length;
274
+ marker = makeMarker(omitted);
275
+ }
276
+ }
277
+ const fits = omitted === 0 || used + marker.length <= remaining;
278
+ if (fits) {
279
+ if (included.length === 0 && !marker)
280
+ return { text: '', consumed: 0, lossFlag: false };
281
+ const text = header + included.join('') + marker;
282
+ return { text, consumed: text.length, lossFlag: omitted > 0 };
283
+ }
284
+ if (minimalFallback.length <= remaining) {
285
+ return { text: minimalFallback, consumed: minimalFallback.length, lossFlag: true };
286
+ }
287
+ if (BARE_OMIT_MARKER.length <= remaining) {
288
+ return { text: BARE_OMIT_MARKER, consumed: BARE_OMIT_MARKER.length, lossFlag: true };
289
+ }
290
+ return { text: '', consumed: 0, lossFlag: true };
291
+ }
292
+ /**
293
+ * Generic fitter for a deterministically-ordered list of small, atomic text
294
+ * items: include as many whole items as fit, in order, then an explicit
295
+ * "N more omitted" marker for the rest, then a provenance footer (section 9
296
+ * — every content section names its source). Items are never cut mid-item;
297
+ * the footer is reserved up front so it is never silently dropped while
298
+ * items still fit. If not even the whole-items-plus-marker form fits, falls
299
+ * back to a short, fixed-size `minimalFallback` marker rather than
300
+ * disappearing without any local trace (review finding F-5).
301
+ */
302
+ function fitItemList(header, items, footer, remaining, omittedMarkerFn, minimalFallback) {
303
+ const noneBody = ' (none)\n';
304
+ if (items.length === 0) {
305
+ const full = header + noneBody + footer;
306
+ if (full.length <= remaining)
307
+ return { text: full, consumed: full.length, lossFlag: false };
308
+ const withoutFooter = header + noneBody;
309
+ if (withoutFooter.length <= remaining)
310
+ return { text: withoutFooter, consumed: withoutFooter.length, lossFlag: false };
311
+ return { text: '', consumed: 0, lossFlag: false };
312
+ }
313
+ const itemBudget = Math.max(0, remaining - footer.length);
314
+ let used = header.length;
315
+ const included = [];
316
+ for (const item of items) {
317
+ if (used + item.length <= itemBudget) {
318
+ included.push(item);
319
+ used += item.length;
320
+ }
321
+ else {
322
+ break;
323
+ }
324
+ }
325
+ let omitted = items.length - included.length;
326
+ let marker = '';
327
+ if (omitted > 0) {
328
+ marker = omittedMarkerFn(omitted);
329
+ while (included.length > 0 && used + marker.length > itemBudget) {
330
+ const removedItem = included.pop();
331
+ used -= removedItem.length;
332
+ omitted = items.length - included.length;
333
+ marker = omittedMarkerFn(omitted);
334
+ }
335
+ }
336
+ const fits = omitted === 0 || used + marker.length <= itemBudget;
337
+ if (fits) {
338
+ const withFooter = header + included.join('') + marker + footer;
339
+ if (withFooter.length <= remaining) {
340
+ return { text: withFooter, consumed: withFooter.length, lossFlag: omitted > 0 };
341
+ }
342
+ const withoutFooter = header + included.join('') + marker;
343
+ if (withoutFooter.length <= remaining) {
344
+ return { text: withoutFooter, consumed: withoutFooter.length, lossFlag: true };
345
+ }
346
+ }
347
+ if (minimalFallback.length <= remaining) {
348
+ return { text: minimalFallback, consumed: minimalFallback.length, lossFlag: true };
349
+ }
350
+ if (BARE_OMIT_MARKER.length <= remaining) {
351
+ return { text: BARE_OMIT_MARKER, consumed: BARE_OMIT_MARKER.length, lossFlag: true };
352
+ }
353
+ return { text: '', consumed: 0, lossFlag: true };
354
+ }
355
+ const P3_TEXT = '\n## More\n' +
356
+ ' Full checkpoint history: `monolith mission checkpoints <id>`\n' +
357
+ ' Mission detail: `monolith mission show <id>`\n' +
358
+ ' Full ADR/protocol listings: see their directories above.\n';
359
+ export function render(model, maxChars = DEFAULT_MAX_CHARS) {
360
+ const coreText0 = buildCoreP0(model, false);
361
+ // The Freshness headline sits right under Completeness and is counted in the budget like it.
362
+ const freshnessLine = model.freshness ? `Freshness: ${model.freshness.status}\n` : '';
363
+ const requiredMinimum = coreText0.length + RESERVED_COMPLETENESS_LEN + freshnessLine.length;
364
+ const budgetExceededByRequired = requiredMinimum > maxChars;
365
+ const coreText = budgetExceededByRequired ? buildCoreP0(model, true) : coreText0;
366
+ let remaining = budgetExceededByRequired ? 0 : Math.max(0, maxChars - coreText.length - RESERVED_COMPLETENESS_LEN - freshnessLine.length);
367
+ let budgetLoss = false;
368
+ let p1 = '';
369
+ let p2 = '';
370
+ let p3 = '';
371
+ if (!budgetExceededByRequired) {
372
+ // Checkpoints (carries "Next steps") is given first claim on the P1
373
+ // budget, ahead of Story — a long Story excerpt must not be able to
374
+ // silently starve Next-steps information out of the brief (review
375
+ // finding F-5).
376
+ const ckpt = buildCheckpointsSection(model, remaining);
377
+ p1 += ckpt.text;
378
+ remaining -= ckpt.consumed;
379
+ budgetLoss = budgetLoss || ckpt.lossFlag;
380
+ const story = buildStorySection(model, remaining);
381
+ p1 += story.text;
382
+ remaining -= story.consumed;
383
+ budgetLoss = budgetLoss || story.lossFlag;
384
+ const adrSource = san(model.adrs.provenance.source);
385
+ const missionsSource = san(model.missions.provenance.source);
386
+ const adrItems = model.adrs.items.map((a) => formatDocRef(a));
387
+ const adrSection = fitItemList(`\n## Accepted ADR Index (${model.adrs.items.length})\n`, adrItems, ` (source: ${adrSource})\n`, remaining, (n) => ` ...and ${n} more ADR(s) omitted (budget) — see ${adrSource}.\n`, `\n[Accepted ADR Index omitted — budget; see ${adrSource}]\n`);
388
+ p2 += adrSection.text;
389
+ remaining -= adrSection.consumed;
390
+ budgetLoss = budgetLoss || adrSection.lossFlag;
391
+ const recentFinished = model.missions.recentFinished.slice(0, RECENT_FINISHED_MISSIONS_RENDER_LIMIT);
392
+ const finishedItems = recentFinished.map(formatMission);
393
+ const finishedSection = fitItemList(`\n## Recently Finished Missions (${recentFinished.length})\n`, finishedItems, ` (source: ${missionsSource})\n`, remaining, (n) => ` ...and ${n} more omitted (budget) — see ${missionsSource}.\n`, `\n[Recently Finished Missions omitted — budget; see ${missionsSource}]\n`);
394
+ p2 += finishedSection.text;
395
+ remaining -= finishedSection.consumed;
396
+ budgetLoss = budgetLoss || finishedSection.lossFlag;
397
+ if (P3_TEXT.length <= remaining) {
398
+ p3 = P3_TEXT;
399
+ remaining -= p3.length;
400
+ }
401
+ }
402
+ const collectTimeError = model.diagnostics.some((d) => d.severity === 'error');
403
+ const completeness = !collectTimeError && !budgetExceededByRequired && !budgetLoss ? 'COMPLETE' : 'INCOMPLETE';
404
+ const completenessLine = completeness === 'COMPLETE' ? COMPLETE_LINE : INCOMPLETE_LINE;
405
+ const text = `${completenessLine}\n${freshnessLine}${coreText}${p1}${p2}${p3}`;
406
+ return { text: `${text.replace(/\n+$/, '')}\n`, completeness };
407
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Review finding F-7 (data/instruction boundary): `monolith brief` output
3
+ * is AI-facing. Project data — a Mission title, a checkpoint summary, a
4
+ * Story excerpt, a document title from frontmatter — is free text that a
5
+ * contributor (or an attacker with ordinary write access to the project)
6
+ * fully controls. Without escaping, a crafted value containing real
7
+ * newlines could fake a brief-native line such as `Completeness: COMPLETE`
8
+ * or a `## Section` heading, making project data visually indistinguishable
9
+ * from the brief's own structure to a reader (human or AI) skimming the
10
+ * raw text.
11
+ *
12
+ * This is deterministic, non-LLM hygiene only — it does not attempt to
13
+ * detect or block prompt injection as a concept, and makes no claim that
14
+ * it does. It guarantees two narrower, checkable properties:
15
+ * - a field rendered as a single line can never introduce a newline
16
+ * (so it can never start what looks like a new brief line/section);
17
+ * - control characters (escape codes, NUL, etc.) are never written to
18
+ * stdout raw, in single-line or multi-line fields alike.
19
+ * The Story excerpt is the one legitimately multi-line field (it is a
20
+ * verbatim block from active.md, already bounded by its own header and
21
+ * `(source: ...)` footer) — its internal newlines are preserved, only
22
+ * other control characters are escaped.
23
+ */
24
+ /**
25
+ * For fields rendered as a single logical line (mission title, checkpoint
26
+ * summary, a remaining-step entry, a doc title): collapses any real
27
+ * newline to a visible `\n` escape sequence and escapes other control
28
+ * characters, so the field can never break out of its line.
29
+ */
30
+ export declare function sanitizeSingleLine(s: string): string;
31
+ /**
32
+ * For fields allowed to span multiple lines (the Story excerpt): keeps
33
+ * real newlines (that is the field's legitimate internal structure) but
34
+ * still escapes other control characters so nothing unprintable reaches
35
+ * stdout raw.
36
+ */
37
+ export declare function sanitizeMultiLine(s: string): string;
38
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/brief/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAWH;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnD"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Review finding F-7 (data/instruction boundary): `monolith brief` output
3
+ * is AI-facing. Project data — a Mission title, a checkpoint summary, a
4
+ * Story excerpt, a document title from frontmatter — is free text that a
5
+ * contributor (or an attacker with ordinary write access to the project)
6
+ * fully controls. Without escaping, a crafted value containing real
7
+ * newlines could fake a brief-native line such as `Completeness: COMPLETE`
8
+ * or a `## Section` heading, making project data visually indistinguishable
9
+ * from the brief's own structure to a reader (human or AI) skimming the
10
+ * raw text.
11
+ *
12
+ * This is deterministic, non-LLM hygiene only — it does not attempt to
13
+ * detect or block prompt injection as a concept, and makes no claim that
14
+ * it does. It guarantees two narrower, checkable properties:
15
+ * - a field rendered as a single line can never introduce a newline
16
+ * (so it can never start what looks like a new brief line/section);
17
+ * - control characters (escape codes, NUL, etc.) are never written to
18
+ * stdout raw, in single-line or multi-line fields alike.
19
+ * The Story excerpt is the one legitimately multi-line field (it is a
20
+ * verbatim block from active.md, already bounded by its own header and
21
+ * `(source: ...)` footer) — its internal newlines are preserved, only
22
+ * other control characters are escaped.
23
+ */
24
+ // C0 controls other than \t (0x09), \n (0x0A), \r (0x0D), plus DEL (0x7F) and
25
+ // the C1 range (0x80-0x9F — includes 0x9B, a single-byte CSI that some
26
+ // terminals honor as an escape-sequence introducer, and 0x85, NEL).
27
+ const OTHER_CONTROL_CHARS_RE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g;
28
+ function escapeChar(ch) {
29
+ return `\\x${ch.charCodeAt(0).toString(16).padStart(2, '0')}`;
30
+ }
31
+ /**
32
+ * For fields rendered as a single logical line (mission title, checkpoint
33
+ * summary, a remaining-step entry, a doc title): collapses any real
34
+ * newline to a visible `\n` escape sequence and escapes other control
35
+ * characters, so the field can never break out of its line.
36
+ */
37
+ export function sanitizeSingleLine(s) {
38
+ return s.replace(/\r\n|\r|\n/g, '\\n').replace(OTHER_CONTROL_CHARS_RE, escapeChar);
39
+ }
40
+ /**
41
+ * For fields allowed to span multiple lines (the Story excerpt): keeps
42
+ * real newlines (that is the field's legitimate internal structure) but
43
+ * still escapes other control characters so nothing unprintable reaches
44
+ * stdout raw.
45
+ */
46
+ export function sanitizeMultiLine(s) {
47
+ return s.replace(/\r\n/g, '\n').replace(OTHER_CONTROL_CHARS_RE, escapeChar);
48
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Pure, read-only selectors over an already-loaded CoordinatoriumState.
3
+ *
4
+ * `listMissions()`/`listCheckpoints()` in mission.ts/checkpoint.ts each call
5
+ * `readState()` independently, which would mean re-reading state.json once
6
+ * per section of a brief. Collect-brief reads state.json exactly once (see
7
+ * collect.ts) and derives everything else from that single snapshot through
8
+ * these selectors instead.
9
+ */
10
+ import type { CoordinatoriumState, Mission, Checkpoint } from '../coordinatorium/types.js';
11
+ /** Stable, locale-independent string comparator (never localeCompare — ADR determinism rule). */
12
+ export declare function compareStrings(a: string, b: string): number;
13
+ export declare function selectActiveMissions(state: CoordinatoriumState): Mission[];
14
+ /**
15
+ * Finished missions (completed/failed/cancelled), most recently updated
16
+ * first, capped to `limit`. Used for the P2 "last finished missions" slice.
17
+ */
18
+ export declare function selectRecentFinishedMissions(state: CoordinatoriumState, limit: number): Mission[];
19
+ /**
20
+ * The checkpoint with the highest numeric ordinal in its id
21
+ * (`<missionId>-CKPT-NNNN`) — the monotonic per-mission sequence number,
22
+ * exactly as nextCheckpointId() in checkpoint.ts derives it — NOT the one
23
+ * with the latest `createdAt` (section 6 of the brief contract: ID order is
24
+ * authoritative, timestamps are not). Returns null if the mission has no
25
+ * checkpoints.
26
+ */
27
+ export declare function selectLatestCheckpoint(state: CoordinatoriumState, missionId: string): Checkpoint | null;
28
+ /**
29
+ * True if some other checkpoint for this mission has a strictly later
30
+ * `createdAt` than the one selectLatestCheckpoint() picked by ID ordinal —
31
+ * i.e. timestamps and ID order disagree about which checkpoint is "latest".
32
+ * Purely informational (CHECKPOINT_ORDER_MISMATCH), never changes which
33
+ * checkpoint is reported.
34
+ */
35
+ export declare function hasCheckpointOrderMismatch(state: CoordinatoriumState, missionId: string, latest: Checkpoint): boolean;
36
+ //# sourceMappingURL=selectors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectors.d.ts","sourceRoot":"","sources":["../../src/brief/selectors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE3F,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAI3D;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,EAAE,CAI1E;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,CAKjG;AAWD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAWvG;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAMrH"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Pure, read-only selectors over an already-loaded CoordinatoriumState.
3
+ *
4
+ * `listMissions()`/`listCheckpoints()` in mission.ts/checkpoint.ts each call
5
+ * `readState()` independently, which would mean re-reading state.json once
6
+ * per section of a brief. Collect-brief reads state.json exactly once (see
7
+ * collect.ts) and derives everything else from that single snapshot through
8
+ * these selectors instead.
9
+ */
10
+ /** Stable, locale-independent string comparator (never localeCompare — ADR determinism rule). */
11
+ export function compareStrings(a, b) {
12
+ if (a < b)
13
+ return -1;
14
+ if (a > b)
15
+ return 1;
16
+ return 0;
17
+ }
18
+ export function selectActiveMissions(state) {
19
+ return Object.values(state.tactical.missions)
20
+ .filter((m) => m.status === 'in_progress')
21
+ .sort((a, b) => compareStrings(a.startedAt, b.startedAt) || compareStrings(a.id, b.id));
22
+ }
23
+ /**
24
+ * Finished missions (completed/failed/cancelled), most recently updated
25
+ * first, capped to `limit`. Used for the P2 "last finished missions" slice.
26
+ */
27
+ export function selectRecentFinishedMissions(state, limit) {
28
+ return Object.values(state.tactical.missions)
29
+ .filter((m) => m.status !== 'in_progress')
30
+ .sort((a, b) => compareStrings(b.updatedAt, a.updatedAt) || compareStrings(b.id, a.id))
31
+ .slice(0, limit);
32
+ }
33
+ function escapeRegExp(s) {
34
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
35
+ }
36
+ /** All checkpoints belonging to one mission, in no particular order. */
37
+ function checkpointsForMission(state, missionId) {
38
+ return Object.values(state.tactical.checkpoints).filter((c) => c.missionId === missionId);
39
+ }
40
+ /**
41
+ * The checkpoint with the highest numeric ordinal in its id
42
+ * (`<missionId>-CKPT-NNNN`) — the monotonic per-mission sequence number,
43
+ * exactly as nextCheckpointId() in checkpoint.ts derives it — NOT the one
44
+ * with the latest `createdAt` (section 6 of the brief contract: ID order is
45
+ * authoritative, timestamps are not). Returns null if the mission has no
46
+ * checkpoints.
47
+ */
48
+ export function selectLatestCheckpoint(state, missionId) {
49
+ const re = new RegExp(`^${escapeRegExp(missionId)}-CKPT-(\\d+)$`);
50
+ let best = null;
51
+ for (const c of checkpointsForMission(state, missionId)) {
52
+ const match = re.exec(c.id);
53
+ const ordinal = match ? Number(match[1]) : -1;
54
+ if (!best || ordinal > best.ordinal || (ordinal === best.ordinal && compareStrings(c.id, best.checkpoint.id) < 0)) {
55
+ best = { checkpoint: c, ordinal };
56
+ }
57
+ }
58
+ return best?.checkpoint ?? null;
59
+ }
60
+ /**
61
+ * True if some other checkpoint for this mission has a strictly later
62
+ * `createdAt` than the one selectLatestCheckpoint() picked by ID ordinal —
63
+ * i.e. timestamps and ID order disagree about which checkpoint is "latest".
64
+ * Purely informational (CHECKPOINT_ORDER_MISMATCH), never changes which
65
+ * checkpoint is reported.
66
+ */
67
+ export function hasCheckpointOrderMismatch(state, missionId, latest) {
68
+ for (const c of checkpointsForMission(state, missionId)) {
69
+ if (c.id === latest.id)
70
+ continue;
71
+ if (compareStrings(c.createdAt, latest.createdAt) > 0)
72
+ return true;
73
+ }
74
+ return false;
75
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Extracts the "## Current Story" section from
3
+ * `.monolith/memory/working/active.md` — confirmed structure only, no
4
+ * heuristic Markdown interpretation (section 5 of the brief contract).
5
+ *
6
+ * Only two things are recognized: a line that is exactly a level-1..6
7
+ * heading, and a line that is exactly `---` (used in active.md as a visual
8
+ * section separator). The "Current Story" section's content is everything
9
+ * between its own heading line and the next such boundary.
10
+ */
11
+ export interface StoryExtraction {
12
+ hasCurrentStorySection: boolean;
13
+ excerpt: string | null;
14
+ /** 1-indexed inclusive line range, e.g. "24-29". */
15
+ lineRange: string | null;
16
+ }
17
+ export declare function extractCurrentStory(content: string): StoryExtraction;
18
+ //# sourceMappingURL=story.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"story.d.ts","sourceRoot":"","sources":["../../src/brief/story.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,MAAM,WAAW,eAAe;IAC9B,sBAAsB,EAAE,OAAO,CAAC;IAChC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,oDAAoD;IACpD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAoDpE"}