@smartmemory/compose 0.3.5-beta → 0.3.7
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.
- package/.claude/skills/compose/SKILL.md +42 -88
- package/bin/compose.js +288 -0
- package/bin/git-hooks/pre-push.template +29 -0
- package/bin/judgment-import.js +572 -0
- package/contracts/feature-json.schema.json +5 -0
- package/contracts/judgment-record.schema.json +821 -0
- package/dist/assets/App-PkZzHeMj.js +894 -0
- package/dist/assets/_baseUniq-Bo837sRJ.js +1 -0
- package/dist/assets/arc-BafGpyqE.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-BOBfUsqL.js +36 -0
- package/dist/assets/blockDiagram-DXYQGD6D-Dwodev1a.js +132 -0
- package/dist/assets/{browser-BSM23If2.js → browser-1ntj1-x_.js} +6 -6
- package/dist/assets/{c4Diagram-LMCZKHZV-CUmTCrMW.js → c4Diagram-AHTNJAMY-CU_bhYag.js} +1 -1
- package/dist/assets/channel-qVK_qn4E.js +1 -0
- package/dist/assets/{chunk-JWPE2WC7-DQFs7gjy.js → chunk-4BX2VUAB-p8WsDwnO.js} +1 -1
- package/dist/assets/chunk-4TB4RGXK-B8h7-eR0.js +206 -0
- package/dist/assets/{chunk-XXDRQBXY-CSdt06Hp.js → chunk-55IACEB6-DxeEr98s.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-Dvhgnm7E.js → chunk-EDXVE4YY-BYt8F151.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-Qw-L7OoJ.js → chunk-FMBD7UC4-DGSOVeie.js} +1 -1
- package/dist/assets/chunk-OYMX7WX6-B-QdgYR2.js +231 -0
- package/dist/assets/{chunk-2Q5K7J3B-Cyvi1AFH.js → chunk-QZHKN3VN-Du5UAZLs.js} +1 -1
- package/dist/assets/{chunk-32BRIVSS-B1y5FpOd.js → chunk-YZCP3GAM-C8JbNBSk.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-B8UcfC1q.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-B8UcfC1q.js +1 -0
- package/dist/assets/clone-Pu3RyLUh.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-G8l05tXy.js → cose-bilkent-S5V4N54A-O1ESaqge.js} +1 -1
- package/dist/assets/dagre-KV5264BT-CPTmFPHw.js +4 -0
- package/dist/assets/diagram-5BDNPKRD-B3PNrWs5.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-Cscfr6vc.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-CSfqZ-TM.js +43 -0
- package/dist/assets/diagram-TYMM5635-Cg4aYS7W.js +24 -0
- package/dist/assets/erDiagram-SMLLAGMA-_ZqwG5pl.js +85 -0
- package/dist/assets/flowDiagram-DWJPFMVM-C83boxFT.js +162 -0
- package/dist/assets/ganttDiagram-T4ZO3ILL-CWnIjuEi.js +292 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-DrMdxZfH.js +106 -0
- package/dist/assets/graph-Bi99_6Yf.js +331 -0
- package/dist/assets/graph-RE4I7Ty7.js +1 -0
- package/dist/assets/index-Rm2RE-c0.js +123 -0
- package/dist/assets/infoDiagram-42DDH7IO-BLmP4Epr.js +2 -0
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-Hm4zSDlM.js → ishikawaDiagram-UXIWVN3A-yuWWshKN.js} +5 -5
- package/dist/assets/{journeyDiagram-5HDEW3XC-CIqcTFuO.js → journeyDiagram-VCZTEJTY-BOfhaJov.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-CB9jCXj_.js → kanban-definition-6JOO6SKY-Bbolde15.js} +7 -7
- package/dist/assets/katex-DkKDou_j.js +257 -0
- package/dist/assets/layout-BSf33zm8.js +1 -0
- package/dist/assets/{linear-CsXqv70E.js → linear-AvSTWMqx.js} +1 -1
- package/dist/assets/min-QBM8H4xN.js +1 -0
- package/dist/assets/{mindmap-definition-LN4V7U3C-CpqH5F_P.js → mindmap-definition-QFDTVHPH-BuvgtqIc.js} +7 -7
- package/dist/assets/{mobile-CaoXUwAr.js → mobile-BnXEOE3U.js} +2 -2
- package/dist/assets/pieDiagram-DEJITSTG-DIzF16vh.js +30 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-D-mbUIjS.js +7 -0
- package/dist/assets/{requirementDiagram-TGXJPOKE-D1lG1P7h.js → requirementDiagram-MS252O5E-CEs4kCLd.js} +3 -3
- package/dist/assets/sankeyDiagram-XADWPNL6-DFsnCr9n.js +10 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-BEJYdTjQ.js +157 -0
- package/dist/assets/stateDiagram-FHFEXIEX-BBXs57uY.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-BqKuX4rj.js +1 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-CzqqUDvt.js → timeline-definition-GMOUNBTQ-BGvLoVAY.js} +3 -3
- package/dist/assets/vennDiagram-DHZGUBPP-9LaBTMe0.js +34 -0
- package/dist/assets/wardley-RL74JXVD-P4MEqMTP.js +162 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-o-tmxnlC.js +20 -0
- package/dist/assets/xychartDiagram-5P7HB3ND-Dpn7V6qk.js +7 -0
- package/dist/index.html +2 -2
- package/lib/bug-escalation.js +30 -4
- package/lib/build.js +777 -52
- package/lib/canon-guard.js +223 -0
- package/lib/canon-registry.js +187 -0
- package/lib/codex-preflight.js +26 -4
- package/lib/dispatch-ledger.js +301 -0
- package/lib/dispatch-metrics.js +236 -0
- package/lib/experiment-judge.js +6 -1
- package/lib/feature-writer.js +9 -0
- package/lib/gsd.js +11 -2
- package/lib/hooks-status.js +32 -3
- package/lib/idempotency.js +33 -8
- package/lib/judgment/schema.js +23 -0
- package/lib/judgment/store/index.js +207 -0
- package/lib/judgment/store/records.js +405 -0
- package/lib/judgment-attest.js +259 -0
- package/lib/judgment-gen.js +691 -0
- package/lib/judgment-verify.js +153 -0
- package/lib/judgment-write-guard.js +230 -0
- package/lib/judgment-writer.js +3403 -0
- package/lib/lane-gate.js +2 -0
- package/lib/lifecycle-modes.js +42 -0
- package/lib/local-claude-connector.js +199 -54
- package/lib/mcp-enforcement.js +21 -35
- package/lib/result-normalizer.js +93 -15
- package/lib/review-normalize.js +4 -0
- package/lib/stratum-mcp-client.js +131 -6
- package/package.json +2 -2
- package/server/compose-mcp-tools.js +49 -1
- package/server/compose-mcp.js +220 -0
- package/server/mcp-tool-policy.js +1 -0
- package/dist/assets/App-BG92y_CY.js +0 -896
- package/dist/assets/abnfDiagram-VRR7QNED-Bip6cMjP.js +0 -1
- package/dist/assets/arc-CyMTM07c.js +0 -1
- package/dist/assets/architectureDiagram-ZJ3FMSHR-BukV0CJw.js +0 -36
- package/dist/assets/blockDiagram-677ZJIJ3-Bjb_MNB4.js +0 -132
- package/dist/assets/channel-BzkmtaLy.js +0 -1
- package/dist/assets/chunk-EX3LRPZG-G_MZEji_.js +0 -231
- package/dist/assets/chunk-MOJQB5TN-CQZQYMcz.js +0 -88
- package/dist/assets/chunk-RYQCIY6F-CmAda4sZ.js +0 -1
- package/dist/assets/chunk-V7JOEXUC-CbjBz-Ek.js +0 -206
- package/dist/assets/classDiagram-OUVF2IWQ-BIvLcbex.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-BIvLcbex.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-C3n1PNJC.js +0 -178
- package/dist/assets/cynefinDiagram-TSTJHNR4-C8N_CvKJ.js +0 -62
- package/dist/assets/dagre-VKFMJZFB-B2pkwDhN.js +0 -4
- package/dist/assets/diagram-FQU43EPY-DPHD_t-1.js +0 -3
- package/dist/assets/diagram-G47NLZAW-DGmHIZRr.js +0 -24
- package/dist/assets/diagram-NH7WQ7WH-BlLWwKVt.js +0 -24
- package/dist/assets/diagram-OA4YK3LP-DEv704Re.js +0 -30
- package/dist/assets/diagram-WEI45ONY-Bj6PGUCe.js +0 -41
- package/dist/assets/ebnfDiagram-CCIWWBDH-Ch-icksT.js +0 -1
- package/dist/assets/erDiagram-Q63AITRT-CI3lwEFA.js +0 -85
- package/dist/assets/flowDiagram-23GEKE2U-DcUANpAz.js +0 -156
- package/dist/assets/ganttDiagram-NO4QXBWP-Cy0Ie8KS.js +0 -292
- package/dist/assets/gitGraphDiagram-IHSO6WYX-BswI52az.js +0 -106
- package/dist/assets/graph-BXPQrYYB.js +0 -331
- package/dist/assets/graph-C9eacEi8.js +0 -1
- package/dist/assets/index-B1H7-G2y.js +0 -119
- package/dist/assets/infoDiagram-FWYZ7A6U-BBunqvr-.js +0 -2
- package/dist/assets/katex-C5jXJg4s.js +0 -257
- package/dist/assets/layout-DEXfKzaS.js +0 -1
- package/dist/assets/map-Czzmt4hB.js +0 -1
- package/dist/assets/pegDiagram-2B236MQR-HpQyrDcm.js +0 -1
- package/dist/assets/pieDiagram-ENE6RG2P-xkcpHwnY.js +0 -39
- package/dist/assets/quadrantDiagram-ABIIQ3AL-kebZIOmu.js +0 -7
- package/dist/assets/railroadDiagram-RFXS5EU6-DNvGwWm9.js +0 -1
- package/dist/assets/sankeyDiagram-HTMAVEWB-DOEEcn52.js +0 -40
- package/dist/assets/sequenceDiagram-DBY2YBRQ-DynIVCk_.js +0 -162
- package/dist/assets/sizeCapture-X5ZJPWSS-DJXFjUUL.js +0 -1
- package/dist/assets/stateDiagram-2N3HPSRC-OZ7trFh5.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-GcPRHAJT.js +0 -1
- package/dist/assets/swimlanes-5IMT3BWC-DULYAFmB.js +0 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-BB0gCy01.js +0 -8
- package/dist/assets/vennDiagram-L72KCM5P-CdKmtcFL.js +0 -34
- package/dist/assets/wardleyDiagram-EHGQE667-DknGwjCA.js +0 -78
- package/dist/assets/xychartDiagram-FW5EYKEG-DdYyKfQ3.js +0 -7
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/judgment-gen.js — judgment projections (S04).
|
|
3
|
+
*
|
|
4
|
+
* records → REGISTER.md, LEDGER.md, OBJECTIVE.md, SITUATION.md,
|
|
5
|
+
* people/<slug>.md, positions/<slug>.md, and the OKF bundle root index.md.
|
|
6
|
+
* Projections are PURE OUTPUT: generation
|
|
7
|
+
* reads only records — never an existing projection — so any hand-edit is
|
|
8
|
+
* overwritten by the next regeneration and there are no preserved sections
|
|
9
|
+
* (curated prose is `note` records rendered at their anchors).
|
|
10
|
+
*
|
|
11
|
+
* Output is a deterministic function of the records (no wall-clock in the
|
|
12
|
+
* bytes), which is what makes the fixed-point roundtrip guard meaningful:
|
|
13
|
+
* regen of the generator's own output is byte-identical.
|
|
14
|
+
*
|
|
15
|
+
* OKF (design Decision 8): per-item files carry frontmatter type/title/
|
|
16
|
+
* timestamp plus the smartmemory { reference: true, origin } extension;
|
|
17
|
+
* `resource` is emitted ONLY when a record carries a real provider id AND
|
|
18
|
+
* an enrichment workspace (team_id) is configured — a made-up resource
|
|
19
|
+
* would be a dangling identity claim. The bundle root carries
|
|
20
|
+
* okf_version "0.1" and no type (okf.ts renderOkf rule).
|
|
21
|
+
*/
|
|
22
|
+
import {
|
|
23
|
+
readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, existsSync, readdirSync,
|
|
24
|
+
} from 'node:fs';
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
import {
|
|
27
|
+
createJudgmentStore,
|
|
28
|
+
effectiveStore,
|
|
29
|
+
goalCutoverComplete,
|
|
30
|
+
} from './judgment/store/index.js';
|
|
31
|
+
|
|
32
|
+
function atomicWrite(path, content) {
|
|
33
|
+
const tmp = `${path}.tmp.${process.pid}`;
|
|
34
|
+
try {
|
|
35
|
+
writeFileSync(tmp, content);
|
|
36
|
+
renameSync(tmp, path);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
try { unlinkSync(tmp); } catch { /* tmp may not exist */ }
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function smartmemoryTeamId(cwd) {
|
|
44
|
+
try {
|
|
45
|
+
const config = JSON.parse(readFileSync(join(cwd, '.compose', 'compose.json'), 'utf8'));
|
|
46
|
+
return config?.judgment?.enrichment?.smartmemory?.team_id ?? null;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── snapshot ────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Load everything projection-relevant from the store into a plain snapshot,
|
|
56
|
+
* so the generator core stays pure.
|
|
57
|
+
*/
|
|
58
|
+
export function loadSnapshot(cwd) {
|
|
59
|
+
const rawStore = createJudgmentStore(cwd);
|
|
60
|
+
const store = effectiveStore(rawStore);
|
|
61
|
+
const positions = store.listPositionSlugs().map((slug) => ({
|
|
62
|
+
slug,
|
|
63
|
+
chain: store.readPositionChain(slug),
|
|
64
|
+
status: store.derivePositionStatus(slug),
|
|
65
|
+
}));
|
|
66
|
+
return {
|
|
67
|
+
positions,
|
|
68
|
+
joints: store.listJoints(),
|
|
69
|
+
ledger: store.readLedgerEvents(),
|
|
70
|
+
people: store.listPeople(),
|
|
71
|
+
situationEntities: store.listSituationEntities(),
|
|
72
|
+
goalChain: store.readGoalChain(),
|
|
73
|
+
goalState: store.readGoalState(),
|
|
74
|
+
goalCutoverComplete: goalCutoverComplete(store),
|
|
75
|
+
teamId: smartmemoryTeamId(cwd),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── rendering helpers ───────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
function anchoredNotes(ledger, anchor) {
|
|
82
|
+
return ledger.filter((e) => e.kind === 'note' && e.anchor === anchor);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function renderNotes(notes) {
|
|
86
|
+
return notes.map((n) => `> **${n.title}** — ${n.body ?? ''}`.trimEnd()).join('\n\n');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Plain YAML scalars that need no quoting: no leading/trailing space, none
|
|
90
|
+
// of the indicator/structure characters that would change the parse. Risky
|
|
91
|
+
// titles (e.g. "Jane: CEO") are emitted JSON-quoted, which is valid YAML
|
|
92
|
+
// double-quote style.
|
|
93
|
+
const YAML_PLAIN_SAFE_RE = /^[A-Za-z0-9](?:[A-Za-z0-9 _./()-]*[A-Za-z0-9_./()-])?$/;
|
|
94
|
+
|
|
95
|
+
function yamlScalar(value) {
|
|
96
|
+
const s = String(value);
|
|
97
|
+
return YAML_PLAIN_SAFE_RE.test(s) ? s : JSON.stringify(s);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function okfFrontmatter({ type, title, timestamp, resource }) {
|
|
101
|
+
const lines = ['---'];
|
|
102
|
+
if (type) lines.push(`type: ${type}`);
|
|
103
|
+
else lines.push('okf_version: "0.1"');
|
|
104
|
+
lines.push(`title: ${yamlScalar(title)}`);
|
|
105
|
+
if (timestamp) lines.push(`timestamp: "${timestamp}"`);
|
|
106
|
+
if (resource) lines.push(`resource: "${resource}"`);
|
|
107
|
+
lines.push('smartmemory:');
|
|
108
|
+
lines.push(' reference: true');
|
|
109
|
+
lines.push(' origin: compose-projection');
|
|
110
|
+
lines.push('---');
|
|
111
|
+
return lines.join('\n');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function buildResource(teamId, providerId) {
|
|
115
|
+
if (!teamId || !providerId) return null;
|
|
116
|
+
return `smartmemory://${encodeURIComponent(teamId)}/${encodeURIComponent(providerId)}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function auditValue(value) {
|
|
120
|
+
const encoded = JSON.stringify(value);
|
|
121
|
+
return encoded === undefined ? String(value) : encoded;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderTraceLines(trace = [], { prefix = ' - ' } = {}) {
|
|
125
|
+
return trace.map((entry) => {
|
|
126
|
+
const prior = Object.entries(entry.prior ?? {})
|
|
127
|
+
.map(([key, value]) => `${key}=${auditValue(value)}`)
|
|
128
|
+
.join(', ');
|
|
129
|
+
return `${prefix}corrected from ${prior} at ${entry.corrected_at ?? 'unknown'}`;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function renderRecordedAndRemoval(entry, { prefix = ' - ' } = {}) {
|
|
134
|
+
const lines = [];
|
|
135
|
+
lines.push(`${prefix}recorded at ${entry.provenance?.written_at ?? 'unknown'}`);
|
|
136
|
+
if (entry.removed) {
|
|
137
|
+
lines.push(`${prefix}removed at ${entry.removed.at ?? 'unknown'} — ${entry.removed.reason}`);
|
|
138
|
+
}
|
|
139
|
+
return lines;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function renderAuditedFact(fact) {
|
|
143
|
+
const lines = [`- **${fact.id}** ${fact.text}`];
|
|
144
|
+
lines.push(` - channel: \`${fact.channel}\``);
|
|
145
|
+
if (fact.via !== undefined) lines.push(` - via: ${fact.via}`);
|
|
146
|
+
lines.push(` - at: ${fact.at}`);
|
|
147
|
+
lines.push(...renderTraceLines(fact.trace));
|
|
148
|
+
return lines.join('\n');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const PERSON_SECTIONS = ['role', 'life', 'stated', 'revealed'];
|
|
152
|
+
|
|
153
|
+
function sectionTitle(section) {
|
|
154
|
+
return section[0].toUpperCase() + section.slice(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function orderedPairLabel(left, right) {
|
|
158
|
+
const suffix = (id) => Number.parseInt(String(id).replace(/^[^0-9]+/, ''), 10);
|
|
159
|
+
const ids = [left, right].sort((a, b) => suffix(a) - suffix(b) || a.localeCompare(b));
|
|
160
|
+
return `${ids[0]} ↔ ${ids[1]}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function renderPerson(person) {
|
|
164
|
+
const lifecycle = person.facts.some((fact) => fact.channel === 'said') ? 'spoken' : 'stub';
|
|
165
|
+
const parts = [okfFrontmatter({
|
|
166
|
+
type: 'person',
|
|
167
|
+
title: person.display_name,
|
|
168
|
+
timestamp: person.provenance?.written_at,
|
|
169
|
+
})];
|
|
170
|
+
parts.push('');
|
|
171
|
+
parts.push(`# ${person.display_name}`);
|
|
172
|
+
parts.push('');
|
|
173
|
+
parts.push(`**Lifecycle:** ${lifecycle}`);
|
|
174
|
+
parts.push(`**Record:** \`${person.slug}\``);
|
|
175
|
+
|
|
176
|
+
const factsById = new Map(person.facts.map((fact) => [fact.id, fact]));
|
|
177
|
+
const rendered = new Set();
|
|
178
|
+
for (const section of PERSON_SECTIONS) {
|
|
179
|
+
parts.push('');
|
|
180
|
+
parts.push(`## ${sectionTitle(section)}`);
|
|
181
|
+
parts.push('');
|
|
182
|
+
const sectionLines = [];
|
|
183
|
+
for (const fact of person.facts.filter((entry) => entry.section === section)) {
|
|
184
|
+
if (rendered.has(fact.id)) continue;
|
|
185
|
+
const paired = fact.diverges_with ? factsById.get(fact.diverges_with) : null;
|
|
186
|
+
const isStatedRevealedPair = (
|
|
187
|
+
section === 'stated'
|
|
188
|
+
&& paired?.section === 'revealed'
|
|
189
|
+
&& paired.diverges_with === fact.id
|
|
190
|
+
);
|
|
191
|
+
sectionLines.push(renderAuditedFact(fact));
|
|
192
|
+
rendered.add(fact.id);
|
|
193
|
+
if (isStatedRevealedPair) {
|
|
194
|
+
sectionLines.push(renderAuditedFact(paired));
|
|
195
|
+
sectionLines.push(` - divergence pair: ${orderedPairLabel(fact.id, paired.id)}`);
|
|
196
|
+
rendered.add(paired.id);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
parts.push(sectionLines.length > 0 ? sectionLines.join('\n') : 'No facts recorded.');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
parts.push('');
|
|
203
|
+
parts.push('## Edges');
|
|
204
|
+
parts.push('');
|
|
205
|
+
if (person.edges.length === 0) {
|
|
206
|
+
parts.push('None.');
|
|
207
|
+
} else {
|
|
208
|
+
for (const edge of person.edges) {
|
|
209
|
+
parts.push(`- **${edge.id}** \`${edge.removed ? 'removed' : 'active'}\` — ${edge.kind} → [${edge.to}](${edge.to}.md)`);
|
|
210
|
+
parts.push(...renderRecordedAndRemoval(edge));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
parts.push('');
|
|
215
|
+
parts.push('## Open fields');
|
|
216
|
+
parts.push('');
|
|
217
|
+
if (person.open_fields.length === 0) {
|
|
218
|
+
parts.push('None.');
|
|
219
|
+
} else {
|
|
220
|
+
for (const field of person.open_fields) {
|
|
221
|
+
parts.push(`- **${field.id}** \`${field.status}\` — ${field.name}`);
|
|
222
|
+
parts.push(` - filled_by: ${field.filled_by ?? '—'}`);
|
|
223
|
+
parts.push(` - recorded at ${field.provenance?.written_at ?? 'unknown'}`);
|
|
224
|
+
parts.push(...renderTraceLines(field.trace));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
parts.push('');
|
|
229
|
+
parts.push('## Load links');
|
|
230
|
+
parts.push('');
|
|
231
|
+
if (person.load_links.length === 0) {
|
|
232
|
+
parts.push('None.');
|
|
233
|
+
} else {
|
|
234
|
+
for (const link of person.load_links) {
|
|
235
|
+
parts.push(`- **${link.id}** \`${link.removed ? 'removed' : 'active'}\` — fact ${link.fact} carries ${link.carries}`);
|
|
236
|
+
parts.push(...renderRecordedAndRemoval(link));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return parts.join('\n') + '\n';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function latestWrittenAt(records) {
|
|
243
|
+
return records
|
|
244
|
+
.map((record) => record.provenance?.written_at)
|
|
245
|
+
.filter(Boolean)
|
|
246
|
+
.sort()
|
|
247
|
+
.at(-1);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function renderSituation(snapshot) {
|
|
251
|
+
const entities = [...(snapshot.situationEntities ?? [])].sort((a, b) => (
|
|
252
|
+
a.display_name.localeCompare(b.display_name) || a.slug.localeCompare(b.slug)
|
|
253
|
+
));
|
|
254
|
+
const parts = [okfFrontmatter({
|
|
255
|
+
type: 'situation',
|
|
256
|
+
title: 'Situation',
|
|
257
|
+
timestamp: latestWrittenAt(entities),
|
|
258
|
+
})];
|
|
259
|
+
parts.push('');
|
|
260
|
+
parts.push('# Situation');
|
|
261
|
+
if (entities.length === 0) {
|
|
262
|
+
parts.push('');
|
|
263
|
+
parts.push('No situation entities recorded.');
|
|
264
|
+
}
|
|
265
|
+
for (const entity of entities) {
|
|
266
|
+
parts.push('');
|
|
267
|
+
parts.push(`## ${entity.display_name} (\`${entity.slug}\`)`);
|
|
268
|
+
parts.push('');
|
|
269
|
+
parts.push('### Facts');
|
|
270
|
+
parts.push('');
|
|
271
|
+
if (entity.facts.length === 0) {
|
|
272
|
+
parts.push('None.');
|
|
273
|
+
} else {
|
|
274
|
+
for (const fact of entity.facts) parts.push(renderAuditedFact(fact));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
parts.push('');
|
|
278
|
+
parts.push('### Owed');
|
|
279
|
+
parts.push('');
|
|
280
|
+
if (entity.owed.length === 0) {
|
|
281
|
+
parts.push('None.');
|
|
282
|
+
} else {
|
|
283
|
+
for (const owed of entity.owed) {
|
|
284
|
+
parts.push(`> **OWED ${owed.id} · ${owed.status}** ${owed.name}`);
|
|
285
|
+
parts.push(`> - why load-bearing: ${owed.why_load_bearing}`);
|
|
286
|
+
parts.push(`> - filled_by: ${owed.filled_by ?? '—'}`);
|
|
287
|
+
parts.push(`> - recorded at ${owed.provenance?.written_at ?? 'unknown'}`);
|
|
288
|
+
parts.push(...renderTraceLines(owed.trace, { prefix: '> - ' }));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
parts.push('');
|
|
293
|
+
parts.push('### Load links');
|
|
294
|
+
parts.push('');
|
|
295
|
+
if (entity.load_links.length === 0) {
|
|
296
|
+
parts.push('None.');
|
|
297
|
+
} else {
|
|
298
|
+
for (const link of entity.load_links) {
|
|
299
|
+
parts.push(`- **${link.id}** \`${link.removed ? 'removed' : 'active'}\` — fact ${link.fact} carries ${link.carries}`);
|
|
300
|
+
parts.push(...renderRecordedAndRemoval(link));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return parts.join('\n') + '\n';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function renderClaim(claim) {
|
|
308
|
+
const lines = [`- **${claim.id}** \`[${claim.grounding}]\`${claim.owner_locked ? ' `[owner-locked]`' : ''} ${claim.text}`];
|
|
309
|
+
if (Array.isArray(claim.supports) && claim.supports.length > 0) {
|
|
310
|
+
lines.push(` - supports: ${claim.supports.join(', ')}`);
|
|
311
|
+
}
|
|
312
|
+
if (claim.elicitation) {
|
|
313
|
+
lines.push(` - elicitation: asked "${claim.elicitation.asked}" (answered ${claim.elicitation.answered_at}, ref ${claim.elicitation.answer_ref})`);
|
|
314
|
+
}
|
|
315
|
+
return lines.join('\n');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function renderPosition(position, snapshot) {
|
|
319
|
+
const { slug, chain, status } = position;
|
|
320
|
+
const current = chain[chain.length - 1];
|
|
321
|
+
const resource = buildResource(snapshot.teamId, current?.provider_ids?.smartmemory);
|
|
322
|
+
const parts = [okfFrontmatter({
|
|
323
|
+
type: 'position',
|
|
324
|
+
title: slug,
|
|
325
|
+
timestamp: current?.provenance?.written_at,
|
|
326
|
+
resource,
|
|
327
|
+
})];
|
|
328
|
+
parts.push('');
|
|
329
|
+
parts.push(`# ${slug}`);
|
|
330
|
+
parts.push('');
|
|
331
|
+
parts.push(`**Status:** ${status} · **Conviction:** ${current.conviction?.level ?? '—'} (${current.conviction?.source ?? '—'})`);
|
|
332
|
+
if (current.retracted) {
|
|
333
|
+
parts.push('');
|
|
334
|
+
parts.push('**Retracted.** The latest revision is a tombstone.');
|
|
335
|
+
}
|
|
336
|
+
if (current.claims?.length) {
|
|
337
|
+
parts.push('');
|
|
338
|
+
parts.push(`## Claims (r${current.rev})`);
|
|
339
|
+
parts.push('');
|
|
340
|
+
for (const claim of current.claims) parts.push(renderClaim(claim));
|
|
341
|
+
}
|
|
342
|
+
if (current.rejected_alternatives?.length) {
|
|
343
|
+
parts.push('');
|
|
344
|
+
parts.push('## Rejected alternatives');
|
|
345
|
+
parts.push('');
|
|
346
|
+
for (const alt of current.rejected_alternatives) parts.push(`- ${alt.what} — ${alt.why}`);
|
|
347
|
+
}
|
|
348
|
+
parts.push('');
|
|
349
|
+
parts.push('## History');
|
|
350
|
+
parts.push('');
|
|
351
|
+
for (const rev of chain) {
|
|
352
|
+
const marks = [];
|
|
353
|
+
if (rev.supersedes) marks.push(`supersedes ${rev.supersedes}`);
|
|
354
|
+
if (rev.retracted) marks.push('tombstone');
|
|
355
|
+
parts.push(`- r${rev.rev} — ${rev.provenance?.written_at ?? 'unknown'}${marks.length ? ` (${marks.join(', ')})` : ''}`);
|
|
356
|
+
}
|
|
357
|
+
const notes = anchoredNotes(snapshot.ledger, `position:${slug}`);
|
|
358
|
+
if (notes.length) {
|
|
359
|
+
parts.push('');
|
|
360
|
+
parts.push(renderNotes(notes));
|
|
361
|
+
}
|
|
362
|
+
return parts.join('\n') + '\n';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function renderJointSection(joint, snapshot) {
|
|
366
|
+
const parts = [`## ${joint.slug}`];
|
|
367
|
+
parts.push('');
|
|
368
|
+
parts.push(`- **Question:** ${joint.question}`);
|
|
369
|
+
parts.push(`- **If true:** ${joint.branch_true}`);
|
|
370
|
+
parts.push(`- **If false:** ${joint.branch_false}`);
|
|
371
|
+
parts.push(`- **Method:** ${joint.resolve_by} · cost ${joint.cost} · rank ${joint.rank}`);
|
|
372
|
+
parts.push(`- **State:** ${joint.state}`);
|
|
373
|
+
if (joint.flags?.length) parts.push(`- **Flags:** ${joint.flags.join(', ')}`);
|
|
374
|
+
if (joint.ext) {
|
|
375
|
+
parts.push(joint.ext.judgment_dispatch
|
|
376
|
+
? `- **Ext:** judgment-dispatch — ${joint.ext.reason}`
|
|
377
|
+
: `- **Ext:** sharpened "${joint.ext.sharpened_question}" · bar: ${joint.ext.bar} · falsifier: ${joint.ext.falsifier}`);
|
|
378
|
+
}
|
|
379
|
+
if (joint.straddle) {
|
|
380
|
+
parts.push(`- **Straddle:** signal ${joint.straddle.discriminating_signal} · kill: ${joint.straddle.kill_criteria}`);
|
|
381
|
+
}
|
|
382
|
+
if (joint.resolution) {
|
|
383
|
+
const r = joint.resolution;
|
|
384
|
+
const detail = r.evidence ?? r.learned ?? r.reason ?? r.why ?? '';
|
|
385
|
+
parts.push(`- **Resolution:** ${r.outcome}${detail ? ` — ${typeof detail === 'string' ? detail : JSON.stringify(detail)}` : ''}`);
|
|
386
|
+
if (r.outcome === 'inconclusive' && r.would_have_settled) {
|
|
387
|
+
parts.push(` - would have settled: ${r.would_have_settled}`);
|
|
388
|
+
}
|
|
389
|
+
if (r.ext_result) {
|
|
390
|
+
parts.push(` - ext result: ${r.ext_result.outcome} (${r.ext_result.found_or_provoked}) — ${r.ext_result.search_record}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (joint.dissolution) {
|
|
394
|
+
parts.push(`- **Dissolved into:** ${joint.dissolution.decomposed_into.join(', ')}`);
|
|
395
|
+
}
|
|
396
|
+
const notes = anchoredNotes(snapshot.ledger, `joint:${joint.slug}`);
|
|
397
|
+
if (notes.length) {
|
|
398
|
+
parts.push('');
|
|
399
|
+
parts.push(renderNotes(notes));
|
|
400
|
+
}
|
|
401
|
+
return parts.join('\n');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const RANK_ORDER = { high: 0, medium: 1 };
|
|
405
|
+
|
|
406
|
+
function renderRegister(snapshot) {
|
|
407
|
+
const parts = ['# Judgment Register'];
|
|
408
|
+
const headerNotes = anchoredNotes(snapshot.ledger, 'register-header');
|
|
409
|
+
if (headerNotes.length) {
|
|
410
|
+
parts.push('');
|
|
411
|
+
parts.push(renderNotes(headerNotes));
|
|
412
|
+
}
|
|
413
|
+
const joints = [...snapshot.joints].sort((a, b) =>
|
|
414
|
+
(RANK_ORDER[a.rank] - RANK_ORDER[b.rank]) || a.slug.localeCompare(b.slug));
|
|
415
|
+
for (const joint of joints) {
|
|
416
|
+
parts.push('');
|
|
417
|
+
parts.push(renderJointSection(joint, snapshot));
|
|
418
|
+
}
|
|
419
|
+
const footerNotes = anchoredNotes(snapshot.ledger, 'register-footer');
|
|
420
|
+
if (footerNotes.length) {
|
|
421
|
+
parts.push('');
|
|
422
|
+
parts.push(renderNotes(footerNotes));
|
|
423
|
+
}
|
|
424
|
+
return parts.join('\n') + '\n';
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const EVENT_DETAIL_KEYS = [
|
|
428
|
+
'refs', 'rejected', 'conviction', 'trigger', 'open_joints', 'prediction',
|
|
429
|
+
'disposition', 'recall_verdict', 'attribution', 'prediction_ref',
|
|
430
|
+
'prediction_grade', 'reason', 'rank_change', 'elicitation',
|
|
431
|
+
'intent_id', 'tool', 'op', 'rests_on',
|
|
432
|
+
];
|
|
433
|
+
|
|
434
|
+
function renderLedger(snapshot) {
|
|
435
|
+
const parts = ['# Judgment Ledger'];
|
|
436
|
+
const headerNotes = anchoredNotes(snapshot.ledger, 'ledger-header');
|
|
437
|
+
if (headerNotes.length) {
|
|
438
|
+
parts.push('');
|
|
439
|
+
parts.push(renderNotes(headerNotes));
|
|
440
|
+
}
|
|
441
|
+
snapshot.ledger.forEach((event, i) => {
|
|
442
|
+
parts.push('');
|
|
443
|
+
parts.push(`## ${i + 1}. ${event.kind}: ${event.title}`);
|
|
444
|
+
if (event.body) {
|
|
445
|
+
parts.push('');
|
|
446
|
+
parts.push(event.body);
|
|
447
|
+
}
|
|
448
|
+
const details = EVENT_DETAIL_KEYS
|
|
449
|
+
.filter((k) => event[k] !== undefined && !(Array.isArray(event[k]) && event[k].length === 0))
|
|
450
|
+
.map((k) => `- ${k}: ${typeof event[k] === 'string' ? event[k] : JSON.stringify(event[k])}`);
|
|
451
|
+
if (event.anchor) details.push(`- anchor: ${event.anchor}`);
|
|
452
|
+
if (details.length) {
|
|
453
|
+
parts.push('');
|
|
454
|
+
parts.push(details.join('\n'));
|
|
455
|
+
}
|
|
456
|
+
parts.push('');
|
|
457
|
+
parts.push(`*${event.provenance?.written_at ?? ''}${event.provenance?.via ? ` · via ${event.provenance.via}` : ''}*`);
|
|
458
|
+
});
|
|
459
|
+
return parts.join('\n') + '\n';
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function renderGoalClause(clause) {
|
|
463
|
+
const lines = [`- **${clause.id}** ${clause.text}`];
|
|
464
|
+
lines.push(` - channel: \`${clause.channel}\``);
|
|
465
|
+
if (clause.via !== undefined) lines.push(` - via: ${clause.via}`);
|
|
466
|
+
const citation = clause.elicitation;
|
|
467
|
+
lines.push(
|
|
468
|
+
` - elicitation: asked "${citation.asked}" (answered ${citation.answered_at}, ref ${citation.answer_ref})`,
|
|
469
|
+
);
|
|
470
|
+
lines.push(...renderTraceLines(clause.trace));
|
|
471
|
+
return lines.join('\n');
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function tableCell(value) {
|
|
475
|
+
return String(value).replaceAll('|', '\\|').replaceAll('\n', ' ');
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function renderGoalObjective(snapshot) {
|
|
479
|
+
const chain = snapshot.goalChain ?? [];
|
|
480
|
+
const current = chain.at(-1);
|
|
481
|
+
if (!current) {
|
|
482
|
+
return [
|
|
483
|
+
okfFrontmatter({ type: 'goal', title: 'Objective' }),
|
|
484
|
+
'',
|
|
485
|
+
'# Objective',
|
|
486
|
+
'',
|
|
487
|
+
'No goal recorded.',
|
|
488
|
+
].join('\n') + '\n';
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const state = snapshot.goalState ?? { joints: [], load_links: [] };
|
|
492
|
+
const parts = [okfFrontmatter({
|
|
493
|
+
type: 'goal',
|
|
494
|
+
title: 'Objective',
|
|
495
|
+
timestamp: current.provenance?.written_at,
|
|
496
|
+
})];
|
|
497
|
+
parts.push('');
|
|
498
|
+
parts.push('# Objective');
|
|
499
|
+
parts.push('');
|
|
500
|
+
parts.push(`**Current version:** v${current.version}`);
|
|
501
|
+
const draft = (
|
|
502
|
+
['import', 'migration'].includes(current.provenance?.via)
|
|
503
|
+
&& !current.ratification
|
|
504
|
+
);
|
|
505
|
+
if (draft) {
|
|
506
|
+
parts.push('');
|
|
507
|
+
parts.push('**DRAFT HEALTH WARNING:** This imported/migrated goal is not owner-ratified.');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
parts.push('');
|
|
511
|
+
parts.push(`## Current clauses (v${current.version})`);
|
|
512
|
+
parts.push('');
|
|
513
|
+
for (const clause of current.clauses) parts.push(renderGoalClause(clause));
|
|
514
|
+
|
|
515
|
+
parts.push('');
|
|
516
|
+
parts.push('## Ratification citation');
|
|
517
|
+
parts.push('');
|
|
518
|
+
if (current.ratification) {
|
|
519
|
+
parts.push(`- asked "${current.ratification.asked}"`);
|
|
520
|
+
parts.push(`- quote: "${current.ratification.quote}"`);
|
|
521
|
+
parts.push(
|
|
522
|
+
`- answered ${current.ratification.answered_at}, ref ${current.ratification.answer_ref}`,
|
|
523
|
+
);
|
|
524
|
+
} else {
|
|
525
|
+
parts.push('None — imported/migrated draft.');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
parts.push('');
|
|
529
|
+
parts.push('## Goal joints');
|
|
530
|
+
parts.push('');
|
|
531
|
+
if ((state.joints ?? []).length === 0) {
|
|
532
|
+
parts.push('None.');
|
|
533
|
+
} else {
|
|
534
|
+
for (const link of state.joints) {
|
|
535
|
+
parts.push(`- **${link.id}** \`${link.removed ? 'removed' : 'active'}\` — [${link.joint}](REGISTER.md#${link.joint})`);
|
|
536
|
+
parts.push(...renderRecordedAndRemoval(link));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
parts.push('');
|
|
541
|
+
parts.push('## Load-link bill');
|
|
542
|
+
parts.push('');
|
|
543
|
+
if ((state.load_links ?? []).length === 0) {
|
|
544
|
+
parts.push('None.');
|
|
545
|
+
} else {
|
|
546
|
+
for (const link of state.load_links) {
|
|
547
|
+
const version = Number(/^v([1-9][0-9]*)#/.exec(link.clause)?.[1]);
|
|
548
|
+
const versionMark = version === current.version ? 'current version' : 'superseded version';
|
|
549
|
+
parts.push(`- **${link.id}** \`${link.removed ? 'removed' : 'active'}\` — ${link.clause} \`${versionMark}\` carries ${link.carries}`);
|
|
550
|
+
parts.push(...renderRecordedAndRemoval(link));
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
parts.push('');
|
|
555
|
+
parts.push('## Trajectory');
|
|
556
|
+
parts.push('');
|
|
557
|
+
parts.push('| Version | Date | Provocation | Diff note | Wording fix |');
|
|
558
|
+
parts.push('|---|---|---|---|---|');
|
|
559
|
+
for (const version of chain) {
|
|
560
|
+
const provocation = version.provocation?.quote ?? 'unknown (migrated)';
|
|
561
|
+
const wordingFix = version.clauses.some((clause) => (clause.trace?.length ?? 0) > 0)
|
|
562
|
+
? 'yes'
|
|
563
|
+
: 'no';
|
|
564
|
+
parts.push(
|
|
565
|
+
`| v${version.version} | ${tableCell(version.provenance?.written_at ?? 'unknown')} | ${tableCell(provocation)} | ${tableCell(version.diff_note)} | ${wordingFix} |`,
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
return parts.join('\n') + '\n';
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function renderObjective(snapshot) {
|
|
572
|
+
if (snapshot.goalCutoverComplete) return renderGoalObjective(snapshot);
|
|
573
|
+
const objective = (snapshot.positions ?? []).find((p) => p.slug === 'objective');
|
|
574
|
+
if (objective) return renderPosition(objective, snapshot);
|
|
575
|
+
return [
|
|
576
|
+
okfFrontmatter({ type: 'position', title: 'objective' }),
|
|
577
|
+
'',
|
|
578
|
+
'# objective',
|
|
579
|
+
'',
|
|
580
|
+
'No objective recorded.',
|
|
581
|
+
].join('\n') + '\n';
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function renderIndex(snapshot) {
|
|
585
|
+
const positions = (snapshot.positions ?? []).filter(
|
|
586
|
+
(position) => !(snapshot.goalCutoverComplete && position.slug === 'objective'),
|
|
587
|
+
);
|
|
588
|
+
const parts = [okfFrontmatter({ type: null, title: 'Judgment canon' })];
|
|
589
|
+
parts.push('');
|
|
590
|
+
parts.push('# Judgment canon (generated)');
|
|
591
|
+
parts.push('');
|
|
592
|
+
parts.push('All files in this bundle are projections of `records/` — regenerated, never hand-edited.');
|
|
593
|
+
parts.push('');
|
|
594
|
+
parts.push('- [Register](REGISTER.md)');
|
|
595
|
+
parts.push('- [Ledger](LEDGER.md)');
|
|
596
|
+
parts.push('- [Objective](OBJECTIVE.md)');
|
|
597
|
+
if (positions.length) {
|
|
598
|
+
parts.push('');
|
|
599
|
+
parts.push('## Positions');
|
|
600
|
+
parts.push('');
|
|
601
|
+
for (const p of positions) {
|
|
602
|
+
parts.push(`- [${p.slug}](positions/${p.slug}.md) — ${p.status}`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return parts.join('\n') + '\n';
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// ── public surface ──────────────────────────────────────────────────────────
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Pure generator core: snapshot → { relPath: content }. Exported for tests;
|
|
612
|
+
* `now` is accepted for interface stability but the output deliberately
|
|
613
|
+
* contains no wall-clock bytes (fixed-point requirement).
|
|
614
|
+
*/
|
|
615
|
+
export function generateFromRecords(snapshot, { now } = {}) { // eslint-disable-line no-unused-vars
|
|
616
|
+
const files = {
|
|
617
|
+
'docs/judgment/REGISTER.md': renderRegister(snapshot),
|
|
618
|
+
'docs/judgment/LEDGER.md': renderLedger(snapshot),
|
|
619
|
+
'docs/judgment/OBJECTIVE.md': renderObjective(snapshot),
|
|
620
|
+
'docs/judgment/SITUATION.md': renderSituation(snapshot),
|
|
621
|
+
'docs/judgment/index.md': renderIndex(snapshot),
|
|
622
|
+
};
|
|
623
|
+
for (const person of [...(snapshot.people ?? [])].sort((a, b) => a.slug.localeCompare(b.slug))) {
|
|
624
|
+
files[`docs/judgment/people/${person.slug}.md`] = renderPerson(person);
|
|
625
|
+
}
|
|
626
|
+
for (const position of (snapshot.positions ?? [])) {
|
|
627
|
+
if (snapshot.goalCutoverComplete && position.slug === 'objective') continue;
|
|
628
|
+
files[`docs/judgment/positions/${position.slug}.md`] = renderPosition(position, snapshot);
|
|
629
|
+
}
|
|
630
|
+
return files;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const MANAGED_MARKDOWN_DIRS = [
|
|
634
|
+
'docs/judgment/people',
|
|
635
|
+
'docs/judgment/positions',
|
|
636
|
+
];
|
|
637
|
+
|
|
638
|
+
function managedStaleProjections(cwd, files) {
|
|
639
|
+
const stale = [];
|
|
640
|
+
for (const relDir of MANAGED_MARKDOWN_DIRS) {
|
|
641
|
+
const dir = join(cwd, relDir);
|
|
642
|
+
if (!existsSync(dir)) continue;
|
|
643
|
+
const entries = readdirSync(dir, { withFileTypes: true })
|
|
644
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
|
645
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
646
|
+
for (const entry of entries) {
|
|
647
|
+
const rel = `${relDir}/${entry.name}`;
|
|
648
|
+
if (!(rel in files)) stale.push(rel);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return stale;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Regenerate every projection from records on disk. Atomic per file.
|
|
656
|
+
* @returns {{ files: string[] }} relative paths written
|
|
657
|
+
*/
|
|
658
|
+
export function regenerateProjections(cwd) {
|
|
659
|
+
const files = generateFromRecords(loadSnapshot(cwd));
|
|
660
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
661
|
+
const path = join(cwd, rel);
|
|
662
|
+
mkdirSync(join(path, '..'), { recursive: true });
|
|
663
|
+
atomicWrite(path, content);
|
|
664
|
+
}
|
|
665
|
+
for (const rel of managedStaleProjections(cwd, files)) {
|
|
666
|
+
unlinkSync(join(cwd, rel));
|
|
667
|
+
}
|
|
668
|
+
return { files: Object.keys(files) };
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Fixed-point roundtrip guard: regenerate in memory and compare byte-for-byte
|
|
673
|
+
* against what is on disk. Any drift (hand-edit, stale projection) fails.
|
|
674
|
+
* @returns {{ fixedPoint: boolean, diffs: string[] }}
|
|
675
|
+
*/
|
|
676
|
+
export function checkProjectionRoundtrip(cwd) {
|
|
677
|
+
const files = generateFromRecords(loadSnapshot(cwd));
|
|
678
|
+
const diffs = [];
|
|
679
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
680
|
+
const path = join(cwd, rel);
|
|
681
|
+
if (!existsSync(path)) {
|
|
682
|
+
diffs.push(`${rel} (missing)`);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if (readFileSync(path, 'utf8') !== content) diffs.push(`${rel} (drift)`);
|
|
686
|
+
}
|
|
687
|
+
for (const rel of managedStaleProjections(cwd, files)) {
|
|
688
|
+
diffs.push(`${rel} (orphan — not emitted by the generator)`);
|
|
689
|
+
}
|
|
690
|
+
return { fixedPoint: diffs.length === 0, diffs };
|
|
691
|
+
}
|