@smartmemory/compose 0.3.6-beta → 0.3.8

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 (269) hide show
  1. package/.claude/skills/compose/SKILL.md +42 -88
  2. package/.compose-deps.json +1 -13
  3. package/README.md +72 -5
  4. package/bin/compose.js +754 -347
  5. package/bin/git-hooks/pre-push.template +29 -0
  6. package/bin/judgment-import.js +7 -0
  7. package/bin/judgment-migrate.js +387 -0
  8. package/contracts/comp-obs-contract.schema.json +9 -3
  9. package/contracts/feature-json.schema.json +5 -0
  10. package/contracts/fluid-record.schema.json +209 -0
  11. package/contracts/judgment-record.schema.json +425 -4
  12. package/contracts/lifecycle-backfill.schema.json +322 -0
  13. package/dist/assets/App-Z4MU-H_F.js +916 -0
  14. package/dist/assets/_baseUniq-ClWoCPFl.js +1 -0
  15. package/dist/assets/arc-DY26UIVo.js +1 -0
  16. package/dist/assets/architectureDiagram-Q4EWVU46-6Ggq4DqJ.js +36 -0
  17. package/dist/assets/blockDiagram-DXYQGD6D-CH3Ked0l.js +132 -0
  18. package/dist/assets/{browser-BSM23If2.js → browser-BWkrenen.js} +6 -6
  19. package/dist/assets/{c4Diagram-LMCZKHZV-DZf45Fbz.js → c4Diagram-AHTNJAMY-Bk8dYilu.js} +1 -1
  20. package/dist/assets/channel-SnZzzh7k.js +1 -0
  21. package/dist/assets/{chunk-JWPE2WC7-_7ujgd_Q.js → chunk-4BX2VUAB-BMR0XaAQ.js} +1 -1
  22. package/dist/assets/chunk-4TB4RGXK-JytR14a9.js +206 -0
  23. package/dist/assets/{chunk-XXDRQBXY-DfdVhbmA.js → chunk-55IACEB6-B4Q97BCP.js} +1 -1
  24. package/dist/assets/{chunk-VR4S4FIN-Dt9NZ67m.js → chunk-EDXVE4YY-R_qarkSf.js} +1 -1
  25. package/dist/assets/{chunk-5VM5RSS4-BY4_PV5H.js → chunk-FMBD7UC4-C9s7KR9m.js} +1 -1
  26. package/dist/assets/chunk-OYMX7WX6-BySQzVxc.js +231 -0
  27. package/dist/assets/{chunk-2Q5K7J3B-Dn1spZYu.js → chunk-QZHKN3VN-DdpSYZsW.js} +1 -1
  28. package/dist/assets/{chunk-32BRIVSS-pURGrJDk.js → chunk-YZCP3GAM-iE_tzriw.js} +1 -1
  29. package/dist/assets/classDiagram-6PBFFD2Q-CBu92dSH.js +1 -0
  30. package/dist/assets/classDiagram-v2-HSJHXN6E-CBu92dSH.js +1 -0
  31. package/dist/assets/clone-DgklGjHm.js +1 -0
  32. package/dist/assets/{cose-bilkent-JH36ORCC-BieYif4o.js → cose-bilkent-S5V4N54A-BdlU6ZX_.js} +1 -1
  33. package/dist/assets/dagre-KV5264BT-Cp3F5KTn.js +4 -0
  34. package/dist/assets/diagram-5BDNPKRD-DiR6_2q_.js +10 -0
  35. package/dist/assets/diagram-G4DWMVQ6-w0i-p5HX.js +24 -0
  36. package/dist/assets/diagram-MMDJMWI5-tIHhwUv3.js +43 -0
  37. package/dist/assets/diagram-TYMM5635-BAeY3B19.js +24 -0
  38. package/dist/assets/erDiagram-SMLLAGMA-Ckx_Knko.js +85 -0
  39. package/dist/assets/flowDiagram-DWJPFMVM-DeoNka6J.js +162 -0
  40. package/dist/assets/ganttDiagram-T4ZO3ILL-BmGnFbEg.js +292 -0
  41. package/dist/assets/gitGraphDiagram-UUTBAWPF-Dk48IHsx.js +106 -0
  42. package/dist/assets/graph-BNzKGvoy.js +1 -0
  43. package/dist/assets/graph-CI_1htl0.js +331 -0
  44. package/dist/assets/index-BEfrNBp8.js +123 -0
  45. package/dist/assets/index-yyrA5OZd.css +1 -0
  46. package/dist/assets/infoDiagram-42DDH7IO-BRf827i0.js +2 -0
  47. package/dist/assets/{ishikawaDiagram-FXEZZL3T-CzEB9fQS.js → ishikawaDiagram-UXIWVN3A-0kCZaeCM.js} +5 -5
  48. package/dist/assets/{journeyDiagram-5HDEW3XC-Bz8TCdz2.js → journeyDiagram-VCZTEJTY-rvU7ayRt.js} +1 -1
  49. package/dist/assets/{kanban-definition-HUTT4EX6-tozrMoV_.js → kanban-definition-6JOO6SKY-DpQwX1C5.js} +7 -7
  50. package/dist/assets/katex-DkKDou_j.js +257 -0
  51. package/dist/assets/layout-BI8cXFPI.js +1 -0
  52. package/dist/assets/{linear-Ck7gpa5N.js → linear-a0glcDiw.js} +1 -1
  53. package/dist/assets/min-vPHfnXcC.js +1 -0
  54. package/dist/assets/{mindmap-definition-LN4V7U3C-DTcHO0DJ.js → mindmap-definition-QFDTVHPH-D14eF-7C.js} +7 -7
  55. package/dist/assets/mobile-B7m9EO9D.js +17 -0
  56. package/dist/assets/pieDiagram-DEJITSTG-Cno-gETh.js +30 -0
  57. package/dist/assets/quadrantDiagram-34T5L4WZ-BUQM1Hfm.js +7 -0
  58. package/dist/assets/{requirementDiagram-TGXJPOKE-bnI2zJeT.js → requirementDiagram-MS252O5E-pOXlN2-q.js} +3 -3
  59. package/dist/assets/sankeyDiagram-XADWPNL6-Crynd3_b.js +10 -0
  60. package/dist/assets/sequenceDiagram-FGHM5R23-D9fZdCM8.js +157 -0
  61. package/dist/assets/stateDiagram-FHFEXIEX-CW9qVec8.js +1 -0
  62. package/dist/assets/stateDiagram-v2-QKLJ7IA2-DkVLzHbY.js +1 -0
  63. package/dist/assets/{timeline-definition-FHXFAJF6-D267GQFF.js → timeline-definition-GMOUNBTQ-BcHzhm_8.js} +3 -3
  64. package/dist/assets/vennDiagram-DHZGUBPP-BfytJcWk.js +34 -0
  65. package/dist/assets/wardley-RL74JXVD-DLj-IjyB.js +162 -0
  66. package/dist/assets/wardleyDiagram-NUSXRM2D-Ds0Ue68c.js +20 -0
  67. package/dist/assets/xychartDiagram-5P7HB3ND-vjWDXFL6.js +7 -0
  68. package/dist/index.html +3 -3
  69. package/lib/agent-string.js +7 -5
  70. package/lib/append-integrity.js +81 -0
  71. package/lib/backfill-evidence.js +109 -0
  72. package/lib/bug-escalation.js +39 -4
  73. package/lib/build-stream-schema.js +3 -1
  74. package/lib/build-stream-writer.js +25 -0
  75. package/lib/build.js +1624 -195
  76. package/lib/canon-guard.js +245 -0
  77. package/lib/canon-override.js +196 -0
  78. package/lib/canon-registry.js +291 -0
  79. package/lib/cli-commands.js +144 -0
  80. package/lib/codex-preflight.js +50 -15
  81. package/lib/colleague/context.js +215 -0
  82. package/lib/colleague/writeback.js +95 -0
  83. package/lib/completion-gate.js +1421 -0
  84. package/lib/completion-writer.js +47 -47
  85. package/lib/consumer-fanout.js +105 -11
  86. package/lib/coverage-gate.js +200 -0
  87. package/lib/dir-lock.js +170 -0
  88. package/lib/dispatch-ledger.js +301 -0
  89. package/lib/dispatch-metrics.js +236 -0
  90. package/lib/experiment-judge.js +6 -1
  91. package/lib/feature-json.js +1 -1
  92. package/lib/feature-reconciler.js +8 -0
  93. package/lib/feature-validator.js +64 -1
  94. package/lib/feature-writer.js +66 -2
  95. package/lib/fluid/factory.js +167 -0
  96. package/lib/fluid/ideabox-dates.js +73 -0
  97. package/lib/fluid/ideabox-migrate.js +154 -0
  98. package/lib/fluid/ideabox-ops.js +585 -0
  99. package/lib/fluid/ideabox-view.js +146 -0
  100. package/lib/fluid/import-ideabox.js +186 -0
  101. package/lib/fluid/local-provider.js +606 -0
  102. package/lib/fluid/provider.js +684 -0
  103. package/lib/fluid/record-shape.js +214 -0
  104. package/lib/fluid/record-store.js +328 -0
  105. package/lib/fluid/render-ideabox.js +261 -0
  106. package/lib/fluid/schema.js +40 -0
  107. package/lib/fluid/smartmemory-provider.js +1695 -0
  108. package/lib/gsd.js +63 -14
  109. package/lib/guard-cli.js +175 -0
  110. package/lib/guard-custody.js +141 -0
  111. package/lib/guard-descriptors.js +530 -0
  112. package/lib/guard-enrol.js +254 -0
  113. package/lib/health-score.js +1 -1
  114. package/lib/hooks-status.js +32 -3
  115. package/lib/ideabox-cli.js +315 -0
  116. package/lib/ideabox.js +121 -21
  117. package/lib/judgment/store/index.js +166 -0
  118. package/lib/judgment/store/records.js +184 -25
  119. package/lib/judgment/trace.js +380 -0
  120. package/lib/judgment-attest.js +259 -0
  121. package/lib/judgment-decision-write.js +277 -0
  122. package/lib/judgment-decisions.js +466 -0
  123. package/lib/judgment-gen.js +375 -22
  124. package/lib/judgment-verify.js +153 -0
  125. package/lib/judgment-writer.js +2842 -262
  126. package/lib/lane-gate.js +2 -0
  127. package/lib/lifecycle-modes.js +4 -4
  128. package/lib/lineage.js +400 -0
  129. package/lib/local-claude-connector.js +250 -54
  130. package/lib/maya-client.js +302 -0
  131. package/lib/maya-config.js +53 -0
  132. package/lib/maya-identity.js +283 -0
  133. package/lib/mcp-enforcement.js +21 -35
  134. package/lib/migrate-anon.js +5 -0
  135. package/lib/migrate-roadmap.js +15 -0
  136. package/lib/new.js +13 -1
  137. package/lib/pipeline-compat.js +104 -0
  138. package/lib/policy-catalog.js +295 -0
  139. package/lib/policy-check.js +0 -0
  140. package/lib/process-termination.js +98 -0
  141. package/lib/resolve-workspace.js +5 -1
  142. package/lib/result-normalizer.js +428 -153
  143. package/lib/review-normalize.js +4 -0
  144. package/lib/roadmap-errors.js +65 -0
  145. package/lib/roadmap-preservers.js +24 -4
  146. package/lib/roadmap-residue.js +299 -0
  147. package/lib/smartmemory-client.js +614 -78
  148. package/lib/smartmemory-config.js +54 -0
  149. package/lib/smartmemory-ingest.js +19 -2
  150. package/lib/step-prompt.js +7 -6
  151. package/lib/stratum-engine.js +53 -4
  152. package/lib/stratum-mcp-client.js +391 -31
  153. package/lib/test-bootstrap.js +31 -0
  154. package/lib/tool-inventory.js +122 -0
  155. package/lib/version-check.js +91 -19
  156. package/lib/vision-writer.js +88 -1
  157. package/package.json +7 -6
  158. package/pipelines/bug-fix.stratum.yaml +205 -211
  159. package/pipelines/build-quick.profiles.json +12 -0
  160. package/pipelines/build-quick.stratum.yaml +263 -350
  161. package/pipelines/content.stratum.yaml +81 -77
  162. package/pipelines/coverage-sweep.stratum.yaml +49 -30
  163. package/pipelines/plan.stratum.yaml +76 -86
  164. package/pipelines/refactor.stratum.yaml +125 -125
  165. package/pipelines/research.stratum.yaml +56 -58
  166. package/pipelines/review-fix.profiles.json +6 -0
  167. package/pipelines/review-fix.stratum.yaml +110 -83
  168. package/presets/team-feature.profiles.json +6 -0
  169. package/presets/team-feature.stratum.yaml +93 -66
  170. package/presets/team-research.profiles.json +6 -0
  171. package/presets/team-research.stratum.yaml +89 -80
  172. package/presets/team-review.profiles.json +8 -0
  173. package/presets/team-review.stratum.yaml +98 -80
  174. package/scripts/cost-census.mjs +70 -0
  175. package/scripts/guard-sign/compose-guard-sign.sh +62 -0
  176. package/server/agent-health.js +22 -0
  177. package/server/agent-hooks.js +14 -1
  178. package/server/agent-server.js +5 -248
  179. package/server/agent-spawn.js +3 -4
  180. package/server/agent-workspace.js +294 -0
  181. package/server/build-routes.js +6 -5
  182. package/server/build-stream-bridge.js +53 -0
  183. package/server/cc-session-watcher.js +4 -1
  184. package/server/coalescing-buffer.js +7 -1
  185. package/server/completion-projection.js +228 -0
  186. package/server/compose-mcp-tools.js +124 -24
  187. package/server/compose-mcp.js +91 -790
  188. package/server/decision-event-emit.js +41 -2
  189. package/server/decision-event-id.js +17 -0
  190. package/server/decision-events-snapshot.js +3 -0
  191. package/server/design-routes.js +14 -8
  192. package/server/feature-scan.js +76 -2
  193. package/server/file-watcher.js +170 -21
  194. package/server/ideabox-routes.js +166 -224
  195. package/server/index.js +70 -100
  196. package/server/lifecycle-guard.js +240 -10
  197. package/server/lifecycle-phase-history.js +276 -0
  198. package/server/maya-routes.js +507 -0
  199. package/server/mcp-tool-defs.js +940 -0
  200. package/server/mcp-tool-policy.js +35 -3
  201. package/server/model-tiers.js +22 -5
  202. package/server/pipeline-routes.js +21 -11
  203. package/server/project-root.js +58 -19
  204. package/server/remote-utils.js +3 -1
  205. package/server/schema-validator.js +7 -1
  206. package/server/session-manager.js +5 -6
  207. package/server/session-routes.js +3 -1
  208. package/server/stratum-client.js +57 -10
  209. package/server/stratum-sync.js +6 -3
  210. package/server/summarizer.js +3 -4
  211. package/server/supervisor.js +0 -1
  212. package/server/vision-routes.js +208 -98
  213. package/server/vision-server.js +86 -23
  214. package/server/vision-store.js +60 -6
  215. package/server/vision-utils.js +3 -4
  216. package/server/workspace-activity.js +18 -0
  217. package/server/workspace-middleware.js +2 -2
  218. package/server/workspace-runtime.js +243 -0
  219. package/server/worktree-gc.js +1 -0
  220. package/dist/assets/App-BG3ngu8H.js +0 -896
  221. package/dist/assets/abnfDiagram-VRR7QNED-CjB_sD3D.js +0 -1
  222. package/dist/assets/arc-_v4hR_uD.js +0 -1
  223. package/dist/assets/architectureDiagram-ZJ3FMSHR-DreJmzXQ.js +0 -36
  224. package/dist/assets/blockDiagram-677ZJIJ3-BG9-c0O1.js +0 -132
  225. package/dist/assets/channel-B3U5wFAT.js +0 -1
  226. package/dist/assets/chunk-EX3LRPZG-DdELs1qP.js +0 -231
  227. package/dist/assets/chunk-MOJQB5TN-D-ky35G-.js +0 -88
  228. package/dist/assets/chunk-RYQCIY6F-Dag_kVlO.js +0 -1
  229. package/dist/assets/chunk-V7JOEXUC-BtewURat.js +0 -206
  230. package/dist/assets/classDiagram-OUVF2IWQ-B6fCN-ht.js +0 -1
  231. package/dist/assets/classDiagram-v2-EOCWNBFH-B6fCN-ht.js +0 -1
  232. package/dist/assets/cynefin-VYW2F7L2-CT2BA6KE.js +0 -178
  233. package/dist/assets/cynefinDiagram-TSTJHNR4-Bh6exbyg.js +0 -62
  234. package/dist/assets/dagre-VKFMJZFB-aXMLSmQL.js +0 -4
  235. package/dist/assets/diagram-FQU43EPY-Dr7JAOuQ.js +0 -3
  236. package/dist/assets/diagram-G47NLZAW-DUvA3FQK.js +0 -24
  237. package/dist/assets/diagram-NH7WQ7WH-BQUARqcu.js +0 -24
  238. package/dist/assets/diagram-OA4YK3LP-dDUc1zHi.js +0 -30
  239. package/dist/assets/diagram-WEI45ONY-B2h5Qlb1.js +0 -41
  240. package/dist/assets/ebnfDiagram-CCIWWBDH-DThRGupB.js +0 -1
  241. package/dist/assets/erDiagram-Q63AITRT-BUCsprO2.js +0 -85
  242. package/dist/assets/flowDiagram-23GEKE2U-DXtNNi6r.js +0 -156
  243. package/dist/assets/ganttDiagram-NO4QXBWP-D4zbBHh_.js +0 -292
  244. package/dist/assets/gitGraphDiagram-IHSO6WYX-DpoQws0W.js +0 -106
  245. package/dist/assets/graph-BXPQrYYB.js +0 -331
  246. package/dist/assets/graph-C9eacEi8.js +0 -1
  247. package/dist/assets/index-3ZH5eMcZ.js +0 -119
  248. package/dist/assets/index-LIwREYgH.css +0 -1
  249. package/dist/assets/infoDiagram-FWYZ7A6U-Bbas2GAo.js +0 -2
  250. package/dist/assets/katex-C5jXJg4s.js +0 -257
  251. package/dist/assets/layout-DEXfKzaS.js +0 -1
  252. package/dist/assets/map-Czzmt4hB.js +0 -1
  253. package/dist/assets/mobile-CaoXUwAr.js +0 -17
  254. package/dist/assets/pegDiagram-2B236MQR-CHiINrNy.js +0 -1
  255. package/dist/assets/pieDiagram-ENE6RG2P-CfS4YFlR.js +0 -39
  256. package/dist/assets/quadrantDiagram-ABIIQ3AL-CadesS9w.js +0 -7
  257. package/dist/assets/railroadDiagram-RFXS5EU6-CgWEspBN.js +0 -1
  258. package/dist/assets/sankeyDiagram-HTMAVEWB-YWKFgOGw.js +0 -40
  259. package/dist/assets/sequenceDiagram-DBY2YBRQ-BvkNOyF9.js +0 -162
  260. package/dist/assets/sizeCapture-X5ZJPWSS-DlFPA2yO.js +0 -1
  261. package/dist/assets/stateDiagram-2N3HPSRC-h8NIx0kQ.js +0 -1
  262. package/dist/assets/stateDiagram-v2-6OUMAXLB-DjPgZtJ9.js +0 -1
  263. package/dist/assets/swimlanes-5IMT3BWC-CT5n22kG.js +0 -2
  264. package/dist/assets/swimlanesDiagram-G3AALYLV-Dn318Bhq.js +0 -8
  265. package/dist/assets/vennDiagram-L72KCM5P-Dj-wWLYG.js +0 -34
  266. package/dist/assets/wardleyDiagram-EHGQE667-BxCeYxkG.js +0 -78
  267. package/dist/assets/xychartDiagram-FW5EYKEG-DMFqWn7z.js +0 -7
  268. package/lib/staleness.js +0 -87
  269. package/server/ideabox-cache.js +0 -77
@@ -14,6 +14,8 @@ import { readFileSync } from 'node:fs';
14
14
  import { join } from 'node:path';
15
15
  import { RecordsStore } from './records.js';
16
16
 
17
+ const EFFECTIVE_STORES = new WeakSet();
18
+
17
19
  function configuredProvider(cwd) {
18
20
  try {
19
21
  const config = JSON.parse(readFileSync(join(cwd, '.compose', 'compose.json'), 'utf8'));
@@ -47,3 +49,167 @@ export function createJudgmentStore(cwd) {
47
49
  throw new Error(`Unknown judgment provider '${provider}'. Valid ids: records, smartmemory.`);
48
50
  }
49
51
  }
52
+
53
+ /**
54
+ * Construct a read-only view that excludes whole artifacts attributed to an
55
+ * intent which was pending when the view was created.
56
+ *
57
+ * The intent set is deliberately snapshotted once. Record files and ledger
58
+ * lines are filtered only by top-level provenance; nested intent attribution
59
+ * belongs to the containing record and is not recursively hidden.
60
+ *
61
+ * @param {RecordsStore} rawStore
62
+ */
63
+ export function effectiveStore(rawStore) {
64
+ if (EFFECTIVE_STORES.has(rawStore)) return rawStore;
65
+
66
+ const pendingIntents = rawStore.readIntents();
67
+ const pendingIds = new Set(pendingIntents.map((intent) => intent.id));
68
+ const isVisible = (record) => (
69
+ record != null && !pendingIds.has(record.provenance?.intent_id)
70
+ );
71
+ const visibleOrNull = (record) => (isVisible(record) ? record : null);
72
+
73
+ const adapter = {
74
+ capabilities() {
75
+ return rawStore.capabilities();
76
+ },
77
+
78
+ readPositionRevision(slug, rev) {
79
+ return visibleOrNull(rawStore.readPositionRevision(slug, rev));
80
+ },
81
+
82
+ readPositionChain(slug) {
83
+ return rawStore.readPositionChain(slug)
84
+ .map((record) => adapter.readPositionRevision(slug, record.rev))
85
+ .filter(Boolean);
86
+ },
87
+
88
+ latestPositionRevision(slug) {
89
+ const chain = adapter.readPositionChain(slug);
90
+ return chain.length === 0 ? null : chain[chain.length - 1];
91
+ },
92
+
93
+ listPositionSlugs() {
94
+ return rawStore.listPositionSlugs()
95
+ .filter((slug) => adapter.readPositionChain(slug).length > 0);
96
+ },
97
+
98
+ /**
99
+ * @param {string} slug
100
+ * @param {Map} [index] optional supersession index (lib/judgment/trace.js
101
+ * buildSupersessionIndex). Callers looping over every slug should pass one:
102
+ * without it this rescans all other slugs' chains per call, so a whole-store
103
+ * status pass is O(n^2). Result is identical either way.
104
+ */
105
+ derivePositionStatus(slug, index) {
106
+ const latest = adapter.latestPositionRevision(slug);
107
+ if (!latest) return null;
108
+ if (latest.retracted === true) return 'retracted';
109
+ if (index) return index.get(slug)?.status ?? 'live';
110
+ for (const other of adapter.listPositionSlugs()) {
111
+ if (other === slug) continue;
112
+ const otherLatest = adapter.latestPositionRevision(other);
113
+ if (!otherLatest || otherLatest.retracted === true) continue;
114
+ const ref = otherLatest.supersedes;
115
+ if (typeof ref === 'string' && ref.startsWith(`${slug}#r`)) {
116
+ return 'superseded';
117
+ }
118
+ }
119
+ return 'live';
120
+ },
121
+
122
+ readJoint(slug) {
123
+ return visibleOrNull(rawStore.readJoint(slug));
124
+ },
125
+
126
+ listJoints() {
127
+ return rawStore.listJoints()
128
+ .map((record) => adapter.readJoint(record.slug))
129
+ .filter(Boolean);
130
+ },
131
+
132
+ readPrediction(id) {
133
+ return visibleOrNull(rawStore.readPrediction(id));
134
+ },
135
+
136
+ listPredictions({ status } = {}) {
137
+ return rawStore.listPredictions()
138
+ .map((record) => adapter.readPrediction(record.id))
139
+ .filter(Boolean)
140
+ .filter((record) => (status ? record.status === status : true));
141
+ },
142
+
143
+ readLedgerEvents() {
144
+ return rawStore.readLedgerEvents().filter(isVisible);
145
+ },
146
+
147
+ readPerson(slug) {
148
+ return visibleOrNull(rawStore.readPerson(slug));
149
+ },
150
+
151
+ listPeople() {
152
+ return rawStore.listPeople()
153
+ .map((record) => adapter.readPerson(record.slug))
154
+ .filter(Boolean);
155
+ },
156
+
157
+ readSituationEntity(slug) {
158
+ return visibleOrNull(rawStore.readSituationEntity(slug));
159
+ },
160
+
161
+ listSituationEntities() {
162
+ return rawStore.listSituationEntities()
163
+ .map((record) => adapter.readSituationEntity(record.slug))
164
+ .filter(Boolean);
165
+ },
166
+
167
+ readGoalVersion(version) {
168
+ return visibleOrNull(rawStore.readGoalVersion(version));
169
+ },
170
+
171
+ readGoalChain() {
172
+ return rawStore.readGoalChain()
173
+ .map((record) => adapter.readGoalVersion(record.version))
174
+ .filter(Boolean);
175
+ },
176
+
177
+ latestGoalVersion() {
178
+ const chain = adapter.readGoalChain();
179
+ return chain.length === 0 ? null : chain[chain.length - 1];
180
+ },
181
+
182
+ readGoalState() {
183
+ const raw = rawStore.readGoalState();
184
+ // Records-atomic guarantee (C3): while a goal migration is pending, a
185
+ // state.json the migration already wrote (attributed to that intent)
186
+ // must read back as the captured PREIMAGE, not as absence. Every other
187
+ // family hides an attributed record as null; goal state instead
188
+ // substitutes the pre-migration bytes the intent carries.
189
+ const migration = pendingIntents.find((intent) => intent.kind === 'goal_migration');
190
+ if (migration && raw?.provenance?.intent_id === migration.id) {
191
+ return migration.payload?.goal_state_preimage?.record ?? null;
192
+ }
193
+ return visibleOrNull(raw);
194
+ },
195
+
196
+ hasPendingIntentKind(kind) {
197
+ return pendingIntents.some((intent) => intent.kind === kind);
198
+ },
199
+ };
200
+
201
+ EFFECTIVE_STORES.add(adapter);
202
+ return Object.freeze(adapter);
203
+ }
204
+
205
+ /**
206
+ * True once a published goal chain exists and no goal migration is pending.
207
+ * Accepts either a raw RecordsStore or an already-effective view.
208
+ */
209
+ export function goalCutoverComplete(store) {
210
+ const effective = EFFECTIVE_STORES.has(store) ? store : effectiveStore(store);
211
+ return (
212
+ effective.readGoalChain().length > 0
213
+ && !effective.hasPendingIntentKind('goal_migration')
214
+ );
215
+ }
@@ -5,6 +5,10 @@
5
5
  * (the feature.json precedent — NOT the gitignored vision store):
6
6
  *
7
7
  * positions/<slug>/r<N>.json append-only revision chain; highest N is current
8
+ * people/<slug>.json mutable person aggregate
9
+ * situation/<slug>.json mutable situation aggregate
10
+ * goal/v<N>.json append-only goal meaning chain
11
+ * goal/state.json mutable goal associations sidecar
8
12
  * joints/<slug>.json one file per joint; state lives here
9
13
  * predictions/<id>.json spawned by CONSTRUCT/commit events
10
14
  * intents/<id>.json pending-intent records (intent-first transitions)
@@ -25,7 +29,7 @@ import {
25
29
  } from 'node:fs';
26
30
  import { join } from 'node:path';
27
31
 
28
- function atomicWrite(path, content) {
32
+ export function atomicWrite(path, content) {
29
33
  const tmp = `${path}.tmp.${process.pid}`;
30
34
  try {
31
35
  writeFileSync(tmp, content);
@@ -44,8 +48,6 @@ function readJson(path) {
44
48
  }
45
49
  }
46
50
 
47
- const REV_FILE_RE = /^r([1-9][0-9]*)\.json$/;
48
-
49
51
  export class RecordsStore {
50
52
  /** @param {string} cwd project root */
51
53
  constructor(cwd) {
@@ -60,18 +62,77 @@ export class RecordsStore {
60
62
 
61
63
  // ── positions ────────────────────────────────────────────────────────────
62
64
 
65
+ _chainNumbers({ dir, prefix }) {
66
+ if (!existsSync(dir)) return [];
67
+ const fileRe = new RegExp(`^${prefix}([1-9][0-9]*)\\.json$`);
68
+ return readdirSync(dir)
69
+ .map((file) => fileRe.exec(file))
70
+ .filter(Boolean)
71
+ .map((match) => Number(match[1]))
72
+ .sort((a, b) => a - b);
73
+ }
74
+
75
+ _appendChainRecord(config, record) {
76
+ mkdirSync(config.dir, { recursive: true });
77
+ const numbers = this._chainNumbers(config);
78
+ const number = (numbers.length ? numbers[numbers.length - 1] : 0) + 1;
79
+ const path = join(config.dir, `${config.prefix}${number}.json`);
80
+ atomicWrite(
81
+ path,
82
+ JSON.stringify({ ...record, [config.numberField]: number }, null, 2) + '\n',
83
+ );
84
+ return {
85
+ [config.numberField]: number,
86
+ ref: config.ref(number),
87
+ path,
88
+ };
89
+ }
90
+
91
+ _readChainRecord(config, number) {
92
+ return readJson(join(config.dir, `${config.prefix}${number}.json`));
93
+ }
94
+
95
+ _readChain(config) {
96
+ return this._chainNumbers(config)
97
+ .map((number) => this._readChainRecord(config, number))
98
+ .filter(Boolean);
99
+ }
100
+
101
+ _latestChainRecord(config) {
102
+ const numbers = this._chainNumbers(config);
103
+ if (numbers.length === 0) return null;
104
+ return this._readChainRecord(config, numbers[numbers.length - 1]);
105
+ }
106
+
107
+ _replaceChainRecord(config, number, record) {
108
+ mkdirSync(config.dir, { recursive: true });
109
+ const path = join(config.dir, `${config.prefix}${number}.json`);
110
+ atomicWrite(
111
+ path,
112
+ JSON.stringify({ ...record, [config.numberField]: number }, null, 2) + '\n',
113
+ );
114
+ return {
115
+ [config.numberField]: number,
116
+ ref: config.ref(number),
117
+ path,
118
+ };
119
+ }
120
+
63
121
  _positionDir(slug) {
64
122
  return join(this.root, 'positions', slug);
65
123
  }
66
124
 
125
+ _positionChain(slug) {
126
+ return {
127
+ dir: this._positionDir(slug),
128
+ prefix: 'r',
129
+ numberField: 'rev',
130
+ ref: (rev) => `${slug}#r${rev}`,
131
+ };
132
+ }
133
+
67
134
  _revNumbers(slug) {
68
- const dir = this._positionDir(slug);
69
- if (!existsSync(dir)) return [];
70
- return readdirSync(dir)
71
- .map((f) => REV_FILE_RE.exec(f))
72
- .filter(Boolean)
73
- .map((m) => Number(m[1]))
74
- .sort((a, b) => a - b);
135
+ return this._chainNumbers(this._positionChain(slug));
75
136
  }
76
137
 
77
138
  /**
@@ -80,24 +141,16 @@ export class RecordsStore {
80
141
  * @returns {{ rev: number, ref: string, path: string }}
81
142
  */
82
143
  writePositionRevision(record) {
83
- const dir = this._positionDir(record.slug);
84
- mkdirSync(dir, { recursive: true });
85
- const revs = this._revNumbers(record.slug);
86
- const rev = (revs.length ? revs[revs.length - 1] : 0) + 1;
87
- const path = join(dir, `r${rev}.json`);
88
- atomicWrite(path, JSON.stringify({ ...record, rev }, null, 2) + '\n');
89
- return { rev, ref: `${record.slug}#r${rev}`, path };
144
+ return this._appendChainRecord(this._positionChain(record.slug), record);
90
145
  }
91
146
 
92
147
  /** Full chain, ascending by rev. */
93
148
  readPositionChain(slug) {
94
- return this._revNumbers(slug)
95
- .map((n) => readJson(join(this._positionDir(slug), `r${n}.json`)))
96
- .filter(Boolean);
149
+ return this._readChain(this._positionChain(slug));
97
150
  }
98
151
 
99
152
  readPositionRevision(slug, rev) {
100
- return readJson(join(this._positionDir(slug), `r${rev}.json`));
153
+ return this._readChainRecord(this._positionChain(slug), rev);
101
154
  }
102
155
 
103
156
  listPositionSlugs() {
@@ -111,9 +164,7 @@ export class RecordsStore {
111
164
 
112
165
  /** Latest revision of a chain, or null. */
113
166
  latestPositionRevision(slug) {
114
- const revs = this._revNumbers(slug);
115
- if (revs.length === 0) return null;
116
- return this.readPositionRevision(slug, revs[revs.length - 1]);
167
+ return this._latestChainRecord(this._positionChain(slug));
117
168
  }
118
169
 
119
170
  /**
@@ -134,6 +185,110 @@ export class RecordsStore {
134
185
  return 'live';
135
186
  }
136
187
 
188
+ // ── goal ─────────────────────────────────────────────────────────────────
189
+
190
+ _goalDir() {
191
+ return join(this.root, 'goal');
192
+ }
193
+
194
+ _goalChain() {
195
+ return {
196
+ dir: this._goalDir(),
197
+ prefix: 'v',
198
+ numberField: 'version',
199
+ ref: (version) => `goal:v${version}`,
200
+ };
201
+ }
202
+
203
+ writeGoalVersion(record) {
204
+ return this._appendChainRecord(this._goalChain(), record);
205
+ }
206
+
207
+ readGoalVersion(version) {
208
+ return this._readChainRecord(this._goalChain(), version);
209
+ }
210
+
211
+ readGoalChain() {
212
+ return this._readChain(this._goalChain());
213
+ }
214
+
215
+ latestGoalVersion() {
216
+ return this._latestChainRecord(this._goalChain());
217
+ }
218
+
219
+ replaceGoalVersion(version, record) {
220
+ return this._replaceChainRecord(this._goalChain(), version, record);
221
+ }
222
+
223
+ _goalStatePath() {
224
+ return join(this._goalDir(), 'state.json');
225
+ }
226
+
227
+ writeGoalState(record) {
228
+ mkdirSync(this._goalDir(), { recursive: true });
229
+ atomicWrite(this._goalStatePath(), JSON.stringify(record, null, 2) + '\n');
230
+ return { path: this._goalStatePath() };
231
+ }
232
+
233
+ readGoalState() {
234
+ return readJson(this._goalStatePath());
235
+ }
236
+
237
+ // ── people ───────────────────────────────────────────────────────────────
238
+
239
+ _personPath(slug) {
240
+ return join(this.root, 'people', `${slug}.json`);
241
+ }
242
+
243
+ writePerson(record) {
244
+ mkdirSync(join(this.root, 'people'), { recursive: true });
245
+ atomicWrite(this._personPath(record.slug), JSON.stringify(record, null, 2) + '\n');
246
+ return { slug: record.slug };
247
+ }
248
+
249
+ readPerson(slug) {
250
+ return readJson(this._personPath(slug));
251
+ }
252
+
253
+ listPeople() {
254
+ const dir = join(this.root, 'people');
255
+ if (!existsSync(dir)) return [];
256
+ return readdirSync(dir)
257
+ .filter((file) => file.endsWith('.json'))
258
+ .sort()
259
+ .map((file) => readJson(join(dir, file)))
260
+ .filter(Boolean);
261
+ }
262
+
263
+ // ── situation ────────────────────────────────────────────────────────────
264
+
265
+ _situationEntityPath(slug) {
266
+ return join(this.root, 'situation', `${slug}.json`);
267
+ }
268
+
269
+ writeSituationEntity(record) {
270
+ mkdirSync(join(this.root, 'situation'), { recursive: true });
271
+ atomicWrite(
272
+ this._situationEntityPath(record.slug),
273
+ JSON.stringify(record, null, 2) + '\n',
274
+ );
275
+ return { slug: record.slug };
276
+ }
277
+
278
+ readSituationEntity(slug) {
279
+ return readJson(this._situationEntityPath(slug));
280
+ }
281
+
282
+ listSituationEntities() {
283
+ const dir = join(this.root, 'situation');
284
+ if (!existsSync(dir)) return [];
285
+ return readdirSync(dir)
286
+ .filter((file) => file.endsWith('.json'))
287
+ .sort()
288
+ .map((file) => readJson(join(dir, file)))
289
+ .filter(Boolean);
290
+ }
291
+
137
292
  // ── joints ───────────────────────────────────────────────────────────────
138
293
 
139
294
  _jointPath(slug) {
@@ -241,6 +396,10 @@ export class RecordsStore {
241
396
  }
242
397
 
243
398
  clearIntent(id) {
244
- try { unlinkSync(this._intentPath(id)); } catch { /* already cleared */ }
399
+ try {
400
+ unlinkSync(this._intentPath(id));
401
+ } catch (err) {
402
+ if (err?.code !== 'ENOENT') throw err;
403
+ }
245
404
  }
246
405
  }