@smartmemory/compose 0.3.6-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 +7 -0
- package/contracts/feature-json.schema.json +5 -0
- package/contracts/judgment-record.schema.json +425 -4
- 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-DZf45Fbz.js → c4Diagram-AHTNJAMY-CU_bhYag.js} +1 -1
- package/dist/assets/channel-qVK_qn4E.js +1 -0
- package/dist/assets/{chunk-JWPE2WC7-_7ujgd_Q.js → chunk-4BX2VUAB-p8WsDwnO.js} +1 -1
- package/dist/assets/chunk-4TB4RGXK-B8h7-eR0.js +206 -0
- package/dist/assets/{chunk-XXDRQBXY-DfdVhbmA.js → chunk-55IACEB6-DxeEr98s.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-Dt9NZ67m.js → chunk-EDXVE4YY-BYt8F151.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-BY4_PV5H.js → chunk-FMBD7UC4-DGSOVeie.js} +1 -1
- package/dist/assets/chunk-OYMX7WX6-B-QdgYR2.js +231 -0
- package/dist/assets/{chunk-2Q5K7J3B-Dn1spZYu.js → chunk-QZHKN3VN-Du5UAZLs.js} +1 -1
- package/dist/assets/{chunk-32BRIVSS-pURGrJDk.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-BieYif4o.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-CzEB9fQS.js → ishikawaDiagram-UXIWVN3A-yuWWshKN.js} +5 -5
- package/dist/assets/{journeyDiagram-5HDEW3XC-Bz8TCdz2.js → journeyDiagram-VCZTEJTY-BOfhaJov.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-tozrMoV_.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-Ck7gpa5N.js → linear-AvSTWMqx.js} +1 -1
- package/dist/assets/min-QBM8H4xN.js +1 -0
- package/dist/assets/{mindmap-definition-LN4V7U3C-DTcHO0DJ.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-bnI2zJeT.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-D267GQFF.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/judgment/store/index.js +158 -0
- package/lib/judgment/store/records.js +183 -24
- package/lib/judgment-attest.js +259 -0
- package/lib/judgment-gen.js +370 -21
- package/lib/judgment-verify.js +153 -0
- package/lib/judgment-writer.js +2803 -277
- package/lib/lane-gate.js +2 -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 +15 -1
- package/server/compose-mcp.js +96 -1
- package/server/mcp-tool-policy.js +1 -1
- package/dist/assets/App-BG3ngu8H.js +0 -896
- package/dist/assets/abnfDiagram-VRR7QNED-CjB_sD3D.js +0 -1
- package/dist/assets/arc-_v4hR_uD.js +0 -1
- package/dist/assets/architectureDiagram-ZJ3FMSHR-DreJmzXQ.js +0 -36
- package/dist/assets/blockDiagram-677ZJIJ3-BG9-c0O1.js +0 -132
- package/dist/assets/channel-B3U5wFAT.js +0 -1
- package/dist/assets/chunk-EX3LRPZG-DdELs1qP.js +0 -231
- package/dist/assets/chunk-MOJQB5TN-D-ky35G-.js +0 -88
- package/dist/assets/chunk-RYQCIY6F-Dag_kVlO.js +0 -1
- package/dist/assets/chunk-V7JOEXUC-BtewURat.js +0 -206
- package/dist/assets/classDiagram-OUVF2IWQ-B6fCN-ht.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-B6fCN-ht.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-CT2BA6KE.js +0 -178
- package/dist/assets/cynefinDiagram-TSTJHNR4-Bh6exbyg.js +0 -62
- package/dist/assets/dagre-VKFMJZFB-aXMLSmQL.js +0 -4
- package/dist/assets/diagram-FQU43EPY-Dr7JAOuQ.js +0 -3
- package/dist/assets/diagram-G47NLZAW-DUvA3FQK.js +0 -24
- package/dist/assets/diagram-NH7WQ7WH-BQUARqcu.js +0 -24
- package/dist/assets/diagram-OA4YK3LP-dDUc1zHi.js +0 -30
- package/dist/assets/diagram-WEI45ONY-B2h5Qlb1.js +0 -41
- package/dist/assets/ebnfDiagram-CCIWWBDH-DThRGupB.js +0 -1
- package/dist/assets/erDiagram-Q63AITRT-BUCsprO2.js +0 -85
- package/dist/assets/flowDiagram-23GEKE2U-DXtNNi6r.js +0 -156
- package/dist/assets/ganttDiagram-NO4QXBWP-D4zbBHh_.js +0 -292
- package/dist/assets/gitGraphDiagram-IHSO6WYX-DpoQws0W.js +0 -106
- package/dist/assets/graph-BXPQrYYB.js +0 -331
- package/dist/assets/graph-C9eacEi8.js +0 -1
- package/dist/assets/index-3ZH5eMcZ.js +0 -119
- package/dist/assets/infoDiagram-FWYZ7A6U-Bbas2GAo.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-CHiINrNy.js +0 -1
- package/dist/assets/pieDiagram-ENE6RG2P-CfS4YFlR.js +0 -39
- package/dist/assets/quadrantDiagram-ABIIQ3AL-CadesS9w.js +0 -7
- package/dist/assets/railroadDiagram-RFXS5EU6-CgWEspBN.js +0 -1
- package/dist/assets/sankeyDiagram-HTMAVEWB-YWKFgOGw.js +0 -40
- package/dist/assets/sequenceDiagram-DBY2YBRQ-BvkNOyF9.js +0 -162
- package/dist/assets/sizeCapture-X5ZJPWSS-DlFPA2yO.js +0 -1
- package/dist/assets/stateDiagram-2N3HPSRC-h8NIx0kQ.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DjPgZtJ9.js +0 -1
- package/dist/assets/swimlanes-5IMT3BWC-CT5n22kG.js +0 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-Dn318Bhq.js +0 -8
- package/dist/assets/vennDiagram-L72KCM5P-Dj-wWLYG.js +0 -34
- package/dist/assets/wardleyDiagram-EHGQE667-BxCeYxkG.js +0 -78
- package/dist/assets/xychartDiagram-FW5EYKEG-DMFqWn7z.js +0 -7
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
renameSync,
|
|
7
|
+
unlinkSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from 'node:fs';
|
|
10
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
11
|
+
import {
|
|
12
|
+
basename,
|
|
13
|
+
dirname,
|
|
14
|
+
isAbsolute,
|
|
15
|
+
join,
|
|
16
|
+
relative,
|
|
17
|
+
resolve,
|
|
18
|
+
sep,
|
|
19
|
+
} from 'node:path';
|
|
20
|
+
|
|
21
|
+
const RECORDS_REL = 'docs/judgment/records';
|
|
22
|
+
// The manifest lives OUTSIDE `docs/judgment/**` entirely, so no deletion of the
|
|
23
|
+
// canon can also delete the baseline that would expose the deletion (R4
|
|
24
|
+
// fail-closed). Two review findings and one earlier fix converge here: a
|
|
25
|
+
// baseline stored inside the tree it attests dies with that tree and reads as a
|
|
26
|
+
// false GREEN. `.compose/` is git-tracked (only `.compose/data/` is ignored), so
|
|
27
|
+
// this location is BOTH committed — a fresh clone can verify — and immune to
|
|
28
|
+
// `rm -rf docs/judgment`. A wiped canon now surfaces every record as `removed`
|
|
29
|
+
// drift; a genuinely fresh repo (no canon, no manifest) stays green.
|
|
30
|
+
const MANIFEST_REL = '.compose/judgment-attest.json';
|
|
31
|
+
|
|
32
|
+
function absolutePath(cwd, relPath) {
|
|
33
|
+
return join(cwd, ...relPath.split('/'));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function sha256(bytes) {
|
|
37
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function inspectRecord(cwd, relPath) {
|
|
41
|
+
const bytes = readFileSync(absolutePath(cwd, relPath));
|
|
42
|
+
let malformed = false;
|
|
43
|
+
|
|
44
|
+
if (relPath.endsWith('.json')) {
|
|
45
|
+
try {
|
|
46
|
+
JSON.parse(bytes.toString('utf8'));
|
|
47
|
+
} catch {
|
|
48
|
+
malformed = true;
|
|
49
|
+
}
|
|
50
|
+
} else if (relPath.endsWith('.jsonl')) {
|
|
51
|
+
// `ledger.jsonl` is a record too, and R4's fail-closed rule covers it. Only
|
|
52
|
+
// hashing it was a hole: the store's reader silently DROPS unparseable
|
|
53
|
+
// ledger lines, so a corrupted ledger could be baselined and then verify
|
|
54
|
+
// green while its bad lines quietly vanished from every projection.
|
|
55
|
+
// Validate line-by-line instead.
|
|
56
|
+
const text = bytes.toString('utf8');
|
|
57
|
+
for (const line of text.split('\n')) {
|
|
58
|
+
if (line.trim() === '') continue;
|
|
59
|
+
try {
|
|
60
|
+
JSON.parse(line);
|
|
61
|
+
} catch {
|
|
62
|
+
malformed = true;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { hash: sha256(bytes), malformed };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function malformedRecordError(relPath) {
|
|
72
|
+
const error = new SyntaxError(`Malformed JSON judgment record: ${relPath}`);
|
|
73
|
+
error.code = 'JUDGMENT_RECORD_MALFORMED';
|
|
74
|
+
error.path = relPath;
|
|
75
|
+
error.kind = 'malformed';
|
|
76
|
+
return error;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function recordFileSet(cwd) {
|
|
80
|
+
const recordsRoot = absolutePath(cwd, RECORDS_REL);
|
|
81
|
+
if (!existsSync(recordsRoot)) return [];
|
|
82
|
+
|
|
83
|
+
const paths = [];
|
|
84
|
+
|
|
85
|
+
function walk(absDir, relDir) {
|
|
86
|
+
const entries = readdirSync(absDir, { withFileTypes: true })
|
|
87
|
+
.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
88
|
+
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
const absPath = join(absDir, entry.name);
|
|
91
|
+
const relPath = `${relDir}/${entry.name}`;
|
|
92
|
+
if (entry.isDirectory()) {
|
|
93
|
+
walk(absPath, relPath);
|
|
94
|
+
} else if (entry.isFile() && basename(relPath) !== '.attest.json') {
|
|
95
|
+
paths.push(relPath);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
walk(recordsRoot, RECORDS_REL);
|
|
101
|
+
return paths.sort();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function computeRecordHashes(cwd) {
|
|
105
|
+
const hashes = {};
|
|
106
|
+
|
|
107
|
+
for (const relPath of recordFileSet(cwd)) {
|
|
108
|
+
const record = inspectRecord(cwd, relPath);
|
|
109
|
+
if (record.malformed) throw malformedRecordError(relPath);
|
|
110
|
+
hashes[relPath] = record.hash;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return hashes;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function readManifest(cwd) {
|
|
117
|
+
const manifestPath = absolutePath(cwd, MANIFEST_REL);
|
|
118
|
+
try {
|
|
119
|
+
return JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (error?.code === 'ENOENT') return null;
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function writeManifest(cwd, hashes) {
|
|
127
|
+
const manifestPath = absolutePath(cwd, MANIFEST_REL);
|
|
128
|
+
mkdirSync(dirname(manifestPath), { recursive: true });
|
|
129
|
+
|
|
130
|
+
const sortedHashes = {};
|
|
131
|
+
for (const relPath of Object.keys(hashes).sort()) {
|
|
132
|
+
sortedHashes[relPath] = hashes[relPath];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const tempPath = `${manifestPath}.tmp.${process.pid}.${randomUUID()}`;
|
|
136
|
+
try {
|
|
137
|
+
writeFileSync(tempPath, `${JSON.stringify(sortedHashes, null, 2)}\n`);
|
|
138
|
+
renameSync(tempPath, manifestPath);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
try {
|
|
141
|
+
unlinkSync(tempPath);
|
|
142
|
+
} catch {
|
|
143
|
+
// The temp file may not exist or may already have been renamed.
|
|
144
|
+
}
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Absolute path of the manifest for `cwd` — for user-facing messages. */
|
|
150
|
+
export function manifestPathFor(cwd) {
|
|
151
|
+
return absolutePath(cwd, MANIFEST_REL);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Write the FIRST baseline, failing with EEXIST if one is already there.
|
|
156
|
+
* Exclusive (`wx`) on purpose: overwriting an existing manifest is the
|
|
157
|
+
* laundering step, so the refusal has to be enforced by the write itself
|
|
158
|
+
* rather than by a check that a race — or a manifest whose contents are
|
|
159
|
+
* literally `null` — can slip past.
|
|
160
|
+
*/
|
|
161
|
+
export function initManifestExclusive(cwd, hashes) {
|
|
162
|
+
const manifestPath = absolutePath(cwd, MANIFEST_REL);
|
|
163
|
+
mkdirSync(dirname(manifestPath), { recursive: true });
|
|
164
|
+
|
|
165
|
+
const sortedHashes = {};
|
|
166
|
+
for (const relPath of Object.keys(hashes).sort()) {
|
|
167
|
+
sortedHashes[relPath] = hashes[relPath];
|
|
168
|
+
}
|
|
169
|
+
// 'wx' → EEXIST if the path already exists. No temp+rename here: atomicity
|
|
170
|
+
// against a concurrent initializer IS the exclusivity.
|
|
171
|
+
writeFileSync(manifestPath, `${JSON.stringify(sortedHashes, null, 2)}\n`, { flag: 'wx' });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function stampRecord(cwd, relPath) {
|
|
175
|
+
const record = inspectRecord(cwd, relPath);
|
|
176
|
+
if (record.malformed) throw malformedRecordError(relPath);
|
|
177
|
+
|
|
178
|
+
const hashes = readManifest(cwd) ?? {};
|
|
179
|
+
hashes[relPath] = record.hash;
|
|
180
|
+
writeManifest(cwd, hashes);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function removeRecord(cwd, relPath) {
|
|
184
|
+
const hashes = readManifest(cwd) ?? {};
|
|
185
|
+
delete hashes[relPath];
|
|
186
|
+
writeManifest(cwd, hashes);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Synchronize the manifest for touched records under an absolute workspace
|
|
191
|
+
* root. Path entries may be absolute filesystem paths or repo-relative POSIX
|
|
192
|
+
* paths; non-record paths are ignored.
|
|
193
|
+
*/
|
|
194
|
+
export function syncManifest(cwd, paths) {
|
|
195
|
+
if (typeof cwd !== 'string' || !isAbsolute(cwd)) {
|
|
196
|
+
const error = new TypeError(
|
|
197
|
+
'syncManifest contract violation: cwd must be an absolute workspace path',
|
|
198
|
+
);
|
|
199
|
+
error.code = 'JUDGMENT_ATTEST_CWD_ABSOLUTE';
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const cwdAbs = resolve(cwd);
|
|
204
|
+
const recordPaths = new Map();
|
|
205
|
+
|
|
206
|
+
for (const path of paths) {
|
|
207
|
+
if (typeof path !== 'string') continue;
|
|
208
|
+
const absPath = resolve(cwdAbs, path);
|
|
209
|
+
const nativeRelPath = relative(cwdAbs, absPath);
|
|
210
|
+
if (
|
|
211
|
+
nativeRelPath === ''
|
|
212
|
+
|| nativeRelPath === '..'
|
|
213
|
+
|| nativeRelPath.startsWith(`..${sep}`)
|
|
214
|
+
|| isAbsolute(nativeRelPath)
|
|
215
|
+
) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const relPath = nativeRelPath.split(sep).join('/');
|
|
220
|
+
if (!relPath.startsWith(`${RECORDS_REL}/`)) continue;
|
|
221
|
+
recordPaths.set(relPath, absPath);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
for (const [relPath, absPath] of recordPaths) {
|
|
225
|
+
if (existsSync(absPath)) stampRecord(cwdAbs, relPath);
|
|
226
|
+
else removeRecord(cwdAbs, relPath);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function verifyRecords(cwd) {
|
|
231
|
+
const manifest = readManifest(cwd) ?? {};
|
|
232
|
+
const currentPaths = recordFileSet(cwd);
|
|
233
|
+
const currentPathSet = new Set(currentPaths);
|
|
234
|
+
const drift = [];
|
|
235
|
+
|
|
236
|
+
for (const relPath of Object.keys(manifest)) {
|
|
237
|
+
if (!currentPathSet.has(relPath)) {
|
|
238
|
+
drift.push({ path: relPath, kind: 'removed' });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
for (const relPath of currentPaths) {
|
|
243
|
+
const record = inspectRecord(cwd, relPath);
|
|
244
|
+
if (record.malformed) {
|
|
245
|
+
drift.push({ path: relPath, kind: 'malformed' });
|
|
246
|
+
} else if (!Object.hasOwn(manifest, relPath)) {
|
|
247
|
+
drift.push({ path: relPath, kind: 'added' });
|
|
248
|
+
} else if (manifest[relPath] !== record.hash) {
|
|
249
|
+
drift.push({ path: relPath, kind: 'modified' });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
drift.sort((a, b) => {
|
|
254
|
+
if (a.path !== b.path) return a.path < b.path ? -1 : 1;
|
|
255
|
+
return a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0;
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
return { ok: drift.length === 0, drift };
|
|
259
|
+
}
|