@cairn-tool/cairn 2.0.1 → 3.0.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 (46) hide show
  1. package/README.md +57 -1367
  2. package/dist/agent/conditionals.d.ts +43 -0
  3. package/dist/agent/conditionals.js +314 -0
  4. package/dist/agent/conditionals.js.map +1 -0
  5. package/dist/agent/install/config.d.ts +47 -0
  6. package/dist/agent/install/config.js +156 -0
  7. package/dist/agent/install/config.js.map +1 -0
  8. package/dist/agent/install/index.d.ts +95 -14
  9. package/dist/agent/install/index.js +460 -188
  10. package/dist/agent/install/index.js.map +1 -1
  11. package/dist/agent/parser.js +15 -41
  12. package/dist/agent/parser.js.map +1 -1
  13. package/dist/agent/render.d.ts +9 -1
  14. package/dist/agent/render.js +12 -9
  15. package/dist/agent/render.js.map +1 -1
  16. package/dist/agent/types.d.ts +4 -1
  17. package/dist/agent/types.js.map +1 -1
  18. package/dist/agent/verify/compare.d.ts +58 -0
  19. package/dist/agent/verify/compare.js +159 -0
  20. package/dist/agent/verify/compare.js.map +1 -0
  21. package/dist/agent/verify/config.d.ts +54 -0
  22. package/dist/agent/verify/config.js +196 -0
  23. package/dist/agent/verify/config.js.map +1 -0
  24. package/dist/agent/verify/index.d.ts +87 -0
  25. package/dist/agent/verify/index.js +331 -0
  26. package/dist/agent/verify/index.js.map +1 -0
  27. package/dist/agent/verify/resolve.d.ts +9 -0
  28. package/dist/agent/verify/resolve.js +97 -0
  29. package/dist/agent/verify/resolve.js.map +1 -0
  30. package/dist/cli.js +18 -5
  31. package/dist/cli.js.map +1 -1
  32. package/dist/commands/agent-install.d.ts +15 -1
  33. package/dist/commands/agent-install.js +121 -24
  34. package/dist/commands/agent-install.js.map +1 -1
  35. package/dist/commands/agent-verify.d.ts +16 -0
  36. package/dist/commands/agent-verify.js +42 -0
  37. package/dist/commands/agent-verify.js.map +1 -0
  38. package/dist/commands/agent.js +23 -0
  39. package/dist/commands/agent.js.map +1 -1
  40. package/dist/config.js +9 -0
  41. package/dist/config.js.map +1 -1
  42. package/dist/contract/registry.js +11 -2
  43. package/dist/contract/registry.js.map +1 -1
  44. package/dist/contract/schemas/agent.js +141 -0
  45. package/dist/contract/schemas/agent.js.map +1 -1
  46. package/package.json +10 -10
@@ -0,0 +1,331 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { loadBundle } from "../parser.js";
4
+ import { renderBundle } from "../render.js";
5
+ import { installKey, readInstallDocument, planInstall } from "../install/index.js";
6
+ import { CONVERSION_REPORT, diffOutput } from "../output.js";
7
+ import { PROFILE_SCHEMA_VERSION, compareSemver } from "../targets/schema.js";
8
+ import { profileFor } from "../targets/index.js";
9
+ import { packageName, packageVersion } from "../../version.js";
10
+ import { diffTree, treeMatches, walkRootsFor } from "./compare.js";
11
+ function error(code, message, extra = {}) {
12
+ return { code, severity: "error", message, quality: "exact", ...extra };
13
+ }
14
+ function notice(code, message, extra = {}) {
15
+ return { code, severity: "notice", message, quality: "exact", ...extra };
16
+ }
17
+ /**
18
+ * Evaluates one bound with a caller-supplied ordering.
19
+ *
20
+ * Documentation revisions are ISO dates compared lexicographically, exactly as
21
+ * `agent doctor` already compares them; CLI versions go through
22
+ * {@link compareSemver}.
23
+ */
24
+ function satisfies(actual, declared, compare) {
25
+ if (declared.exact !== undefined)
26
+ return compare(actual, declared.exact) === 0;
27
+ if (declared.min !== undefined && compare(actual, declared.min) < 0)
28
+ return false;
29
+ if (declared.max !== undefined && compare(actual, declared.max) > 0)
30
+ return false;
31
+ return true;
32
+ }
33
+ function lexical(a, b) {
34
+ return a < b ? -1 : a > b ? 1 : 0;
35
+ }
36
+ function describe(bound) {
37
+ if (bound.exact !== undefined)
38
+ return `exactly ${bound.exact}`;
39
+ return [
40
+ bound.min !== undefined ? `>= ${bound.min}` : undefined,
41
+ bound.max !== undefined ? `<= ${bound.max}` : undefined,
42
+ ]
43
+ .filter(Boolean)
44
+ .join(" and ");
45
+ }
46
+ export function evaluatePins(config, targets) {
47
+ const diagnostics = [];
48
+ const { pins } = config;
49
+ const cli = {
50
+ declared: pins.cli ?? null,
51
+ actual: packageVersion,
52
+ status: !pins.cli
53
+ ? "unpinned"
54
+ : satisfies(packageVersion, pins.cli, compareSemver)
55
+ ? "satisfied"
56
+ : "violated",
57
+ };
58
+ if (cli.status === "violated" && pins.cli)
59
+ diagnostics.push(error("AB420", `Running ${packageName} ${packageVersion} does not satisfy the pinned range (${describe(pins.cli)})`, {
60
+ remediation: "Install a cairn matching the pin, or update the pin and regenerate the trees in the same commit.",
61
+ }));
62
+ const schema = {
63
+ declared: pins.profileSchemaVersion ?? null,
64
+ actual: PROFILE_SCHEMA_VERSION,
65
+ status: !pins.profileSchemaVersion
66
+ ? "unpinned"
67
+ : pins.profileSchemaVersion === PROFILE_SCHEMA_VERSION
68
+ ? "satisfied"
69
+ : "violated",
70
+ };
71
+ if (schema.status === "violated")
72
+ diagnostics.push(error("AB421", `Target profile schema version is ${PROFILE_SCHEMA_VERSION}, but ${pins.profileSchemaVersion} is pinned`, { remediation: "Update the pin and regenerate the trees in the same commit." }));
73
+ const targetReports = [];
74
+ for (const target of targets) {
75
+ const declared = pins.targets[target];
76
+ const actual = profileFor(target).host.documentationRevision;
77
+ const status = !declared
78
+ ? "unpinned"
79
+ : satisfies(actual, declared, lexical)
80
+ ? "satisfied"
81
+ : "violated";
82
+ targetReports.push({ target, declared: declared ?? null, actual, status });
83
+ if (status === "violated" && declared)
84
+ diagnostics.push(error("AB422", `The ${target} target profile revision is ${actual}, which does not satisfy the pinned range (${describe(declared)})`, {
85
+ target,
86
+ remediation: "Update the pin and regenerate the trees in the same commit.",
87
+ }));
88
+ }
89
+ return { report: { cli, profileSchemaVersion: schema, targets: targetReports }, diagnostics };
90
+ }
91
+ function readConversionProvenance(destination) {
92
+ const file = path.join(destination, CONVERSION_REPORT);
93
+ if (!fs.existsSync(file))
94
+ return undefined;
95
+ try {
96
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
97
+ return {
98
+ source: CONVERSION_REPORT,
99
+ generator: parsed.generator ?? null,
100
+ profileSchemaVersion: parsed.profileSchemaVersion ?? null,
101
+ status: "matching",
102
+ };
103
+ }
104
+ catch {
105
+ return {
106
+ source: CONVERSION_REPORT,
107
+ generator: null,
108
+ profileSchemaVersion: null,
109
+ status: "malformed",
110
+ };
111
+ }
112
+ }
113
+ function provenanceOf(manifest) {
114
+ return {
115
+ source: ".cairn-install.json",
116
+ generator: manifest.generator,
117
+ // An install manifest records no profile schema version. Reported as null
118
+ // rather than invented, so a consumer can tell "not recorded" from "2".
119
+ profileSchemaVersion: null,
120
+ status: "matching",
121
+ };
122
+ }
123
+ /**
124
+ * A recorded version this build cannot parse counts as older rather than
125
+ * throwing: provenance is corroboration, and never the verdict.
126
+ */
127
+ function isNewer(recorded) {
128
+ try {
129
+ return compareSemver(recorded, packageVersion) > 0;
130
+ }
131
+ catch {
132
+ return false;
133
+ }
134
+ }
135
+ function gradeProvenance(provenance) {
136
+ if (provenance.status === "malformed" || !provenance.generator)
137
+ return provenance;
138
+ const recorded = provenance.generator.version;
139
+ if (recorded === packageVersion)
140
+ return provenance;
141
+ return { ...provenance, status: isNewer(recorded) ? "newer" : "older" };
142
+ }
143
+ function verifyEntry(entry) {
144
+ const diagnostics = [];
145
+ const base = {
146
+ name: entry.name,
147
+ bundle: entry.bundle,
148
+ target: entry.target,
149
+ profile: entry.profile,
150
+ scope: entry.scope,
151
+ layout: entry.layout,
152
+ destination: entry.destination,
153
+ mode: entry.unmanaged,
154
+ };
155
+ if (!fs.existsSync(entry.destination) || !fs.statSync(entry.destination).isDirectory()) {
156
+ diagnostics.push(error("AB423", `Destination does not exist or is not a directory: ${entry.destination}`, {
157
+ target: entry.target,
158
+ profile: entry.profile,
159
+ path: entry.destination,
160
+ remediation: "Generate the tree, or remove the entry from the verify configuration.",
161
+ }));
162
+ return {
163
+ report: {
164
+ ...base,
165
+ expected: 0,
166
+ missing: [],
167
+ changed: [],
168
+ orphaned: [],
169
+ unmanaged: [],
170
+ ok: false,
171
+ },
172
+ diagnostics,
173
+ };
174
+ }
175
+ const bundle = loadBundle(entry.bundle);
176
+ // A conversion root keeps the `<target>/<profile>` prefix the renderer emits,
177
+ // so it is the one layout `diffOutput` describes and is reused unchanged.
178
+ if (entry.layout === "conversion") {
179
+ const rendered = renderBundle(bundle, [entry.target], [entry.profile]);
180
+ diagnostics.push(...rendered.diagnostics);
181
+ const diff = diffOutput(entry.destination, rendered.artifacts, [entry.target], [entry.profile]);
182
+ const provenance = readConversionProvenance(entry.destination);
183
+ const report = {
184
+ ...base,
185
+ expected: rendered.artifacts.length,
186
+ missing: diff.missing,
187
+ changed: diff.changed,
188
+ orphaned: [],
189
+ unmanaged: entry.unmanaged === "strict" ? diff.unmanaged : [],
190
+ ...(provenance ? { provenance: gradeProvenance(provenance) } : {}),
191
+ ok: false,
192
+ };
193
+ return { report: finish(report, diagnostics, entry), diagnostics };
194
+ }
195
+ const plan = planInstall(bundle, entry.target, {
196
+ scope: entry.scope,
197
+ into: entry.destination,
198
+ profile: entry.profile,
199
+ // The destination is expected to be occupied — that is the whole point —
200
+ // so the occupancy check must not turn into an AB801 error here.
201
+ force: true,
202
+ });
203
+ diagnostics.push(...plan.diagnostics.filter((item) => item.code !== "AB802"));
204
+ const prior = readInstallDocument(plan.destination || entry.destination);
205
+ if (prior === "malformed")
206
+ diagnostics.push(error("AB806", `Install manifest at ${entry.destination} is malformed`, {
207
+ target: entry.target,
208
+ path: entry.destination,
209
+ remediation: "Remove it and reinstall, or repair it by hand.",
210
+ }));
211
+ // A destination may record several installs. The orphan source is *this*
212
+ // entry's record alone — a sibling's files are not this bundle's orphans —
213
+ // while the strict walk's allowlist is the union of every record, or a
214
+ // sibling's files would all report as unmanaged.
215
+ const records = prior === "missing" || prior === "malformed" ? [] : prior.installs;
216
+ const record = records.find((item) => installKey(item) ===
217
+ installKey({
218
+ bundle: { name: bundle.name },
219
+ target: entry.target,
220
+ profile: plan.profile,
221
+ scope: entry.scope,
222
+ }));
223
+ const inventory = record?.files.map((file) => file.path);
224
+ const managedPaths = records.length
225
+ ? [...new Set(records.flatMap((item) => item.files.map((file) => file.path)))]
226
+ : undefined;
227
+ if (!inventory && entry.unmanaged !== "off")
228
+ diagnostics.push(notice("AB426", `No install of '${bundle.name}' recorded at ${entry.destination}; files this bundle no longer renders cannot be detected`, {
229
+ target: entry.target,
230
+ path: entry.destination,
231
+ remediation: "Reinstall so the inventory is recorded.",
232
+ }));
233
+ const expectedPaths = plan.artifacts.map((artifact) => artifact.path);
234
+ const diff = diffTree(plan.destination || entry.destination, plan.artifacts, {
235
+ unmanaged: entry.unmanaged,
236
+ priorInventory: inventory,
237
+ ...(managedPaths ? { managedPaths } : {}),
238
+ walkRoots: entry.unmanaged === "strict"
239
+ ? walkRootsFor(entry.target, plan.profile, expectedPaths)
240
+ : undefined,
241
+ });
242
+ const provenance = prior !== "missing" && prior !== "malformed" ? gradeProvenance(provenanceOf(prior)) : undefined;
243
+ const report = {
244
+ ...base,
245
+ profile: plan.profile,
246
+ layout: entry.layout,
247
+ expected: plan.artifacts.length,
248
+ missing: diff.missing,
249
+ changed: diff.changed,
250
+ orphaned: diff.orphaned,
251
+ unmanaged: diff.unmanaged,
252
+ ...(provenance ? { provenance } : {}),
253
+ ok: false,
254
+ };
255
+ return { report: finish(report, diagnostics, entry), diagnostics };
256
+ }
257
+ /** Maps a comparison into findings and decides the entry's own verdict. */
258
+ function finish(report, diagnostics, entry) {
259
+ const where = { target: entry.target, profile: report.profile };
260
+ for (const missing of report.missing)
261
+ diagnostics.push(error("AB402", `Generated output is missing '${missing}'`, {
262
+ ...where,
263
+ path: missing,
264
+ remediation: "Regenerate the tree from the bundle.",
265
+ }));
266
+ for (const changed of report.changed)
267
+ diagnostics.push(error("AB402", `Generated output differs from the bundle at '${changed}'`, {
268
+ ...where,
269
+ path: changed,
270
+ remediation: "Regenerate the tree; edit the bundle rather than the generated tree.",
271
+ }));
272
+ for (const orphaned of report.orphaned)
273
+ diagnostics.push(error("AB424", `'${orphaned}' was generated previously but is no longer rendered`, {
274
+ ...where,
275
+ path: orphaned,
276
+ remediation: "Delete the file, or restore the component to the bundle.",
277
+ }));
278
+ for (const unmanaged of report.unmanaged)
279
+ diagnostics.push({
280
+ code: "AB403",
281
+ severity: "warning",
282
+ quality: "exact",
283
+ message: `Unmanaged file in the generated tree: '${unmanaged}'`,
284
+ ...where,
285
+ path: unmanaged,
286
+ });
287
+ if (report.provenance && report.provenance.status !== "matching" && report.provenance.generator)
288
+ diagnostics.push(notice("AB425", `The tree records generator ${report.provenance.generator.version}, but ${packageVersion} is verifying it`, { ...where, path: report.destination }));
289
+ return {
290
+ ...report,
291
+ ok: !report.missing.length &&
292
+ !report.changed.length &&
293
+ !report.orphaned.length &&
294
+ !report.unmanaged.length,
295
+ };
296
+ }
297
+ export function runVerify(config) {
298
+ const diagnostics = [];
299
+ const targets = [...new Set(config.entries.map((entry) => entry.target))];
300
+ const pins = evaluatePins(config, targets);
301
+ diagnostics.push(...pins.diagnostics);
302
+ const entries = [];
303
+ for (const entry of config.entries) {
304
+ const result = verifyEntry(entry);
305
+ entries.push(result.report);
306
+ diagnostics.push(...result.diagnostics);
307
+ }
308
+ const counts = {
309
+ entries: entries.length,
310
+ ok: entries.filter((entry) => entry.ok).length,
311
+ missing: entries.reduce((total, entry) => total + entry.missing.length, 0),
312
+ changed: entries.reduce((total, entry) => total + entry.changed.length, 0),
313
+ orphaned: entries.reduce((total, entry) => total + entry.orphaned.length, 0),
314
+ unmanaged: entries.reduce((total, entry) => total + entry.unmanaged.length, 0),
315
+ };
316
+ return {
317
+ report: {
318
+ config: { path: config.file, entries: config.entries.length },
319
+ generator: { name: packageName, version: packageVersion },
320
+ profileSchemaVersion: PROFILE_SCHEMA_VERSION,
321
+ pins: pins.report,
322
+ entries,
323
+ counts,
324
+ },
325
+ diagnostics,
326
+ targets,
327
+ profiles: [...new Set(entries.map((entry) => entry.profile))],
328
+ };
329
+ }
330
+ export { treeMatches };
331
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/agent/verify/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEnF,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAG/D,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAuEnE,SAAS,KAAK,CAAC,IAAY,EAAE,OAAe,EAAE,QAAkC,EAAE;IAChF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,EAAqB,CAAC;AAC7F,CAAC;AAED,SAAS,MAAM,CAAC,IAAY,EAAE,OAAe,EAAE,QAAkC,EAAE;IACjF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,EAAqB,CAAC;AAC9F,CAAC;AAED;;;;;;GAMG;AACH,SAAS,SAAS,CAChB,MAAc,EACd,QAAsB,EACtB,OAAyC;IAEzC,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC/E,IAAI,QAAQ,CAAC,GAAG,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAClF,IAAI,QAAQ,CAAC,GAAG,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAClF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,OAAO,CAAC,CAAS,EAAE,CAAS;IACnC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,QAAQ,CAAC,KAAmB;IACnC,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,WAAW,KAAK,CAAC,KAAK,EAAE,CAAC;IAC/D,OAAO;QACL,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;QACvD,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;KACxD;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,MAAoB,EACpB,OAA+B;IAE/B,MAAM,WAAW,GAAsB,EAAE,CAAC;IAC1C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC;IAExB,MAAM,GAAG,GAA4B;QACnC,QAAQ,EAAE,IAAI,CAAC,GAAG,IAAI,IAAI;QAC1B,MAAM,EAAE,cAAc;QACtB,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG;YACf,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC;gBAClD,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,UAAU;KACjB,CAAC;IACF,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,IAAI,IAAI,CAAC,GAAG;QACvC,WAAW,CAAC,IAAI,CACd,KAAK,CACH,OAAO,EACP,WAAW,WAAW,IAAI,cAAc,uCAAuC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EACpG;YACE,WAAW,EACT,kGAAkG;SACrG,CACF,CACF,CAAC;IAEJ,MAAM,MAAM,GAAsB;QAChC,QAAQ,EAAE,IAAI,CAAC,oBAAoB,IAAI,IAAI;QAC3C,MAAM,EAAE,sBAAsB;QAC9B,MAAM,EAAE,CAAC,IAAI,CAAC,oBAAoB;YAChC,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,IAAI,CAAC,oBAAoB,KAAK,sBAAsB;gBACpD,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,UAAU;KACjB,CAAC;IACF,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU;QAC9B,WAAW,CAAC,IAAI,CACd,KAAK,CACH,OAAO,EACP,oCAAoC,sBAAsB,SAAS,IAAI,CAAC,oBAAoB,YAAY,EACxG,EAAE,WAAW,EAAE,6DAA6D,EAAE,CAC/E,CACF,CAAC;IAEJ,MAAM,aAAa,GAAgC,EAAE,CAAC;IACtD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC;QAC7D,MAAM,MAAM,GAAc,CAAC,QAAQ;YACjC,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC;gBACpC,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,UAAU,CAAC;QACjB,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,IAAI,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3E,IAAI,MAAM,KAAK,UAAU,IAAI,QAAQ;YACnC,WAAW,CAAC,IAAI,CACd,KAAK,CACH,OAAO,EACP,OAAO,MAAM,+BAA+B,MAAM,8CAA8C,QAAQ,CAAC,QAAQ,CAAC,GAAG,EACrH;gBACE,MAAM;gBACN,WAAW,EAAE,6DAA6D;aAC3E,CACF,CACF,CAAC;IACN,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,oBAAoB,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,WAAW,EAAE,CAAC;AAChG,CAAC;AAED,SAAS,wBAAwB,CAAC,WAAmB;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;IACvD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAkC,CAAC;QAC1F,OAAO;YACL,MAAM,EAAE,iBAAiB;YACzB,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,IAAI;YACnC,oBAAoB,EAAE,MAAM,CAAC,oBAAoB,IAAI,IAAI;YACzD,MAAM,EAAE,UAAU;SACnB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,MAAM,EAAE,iBAAiB;YACzB,SAAS,EAAE,IAAI;YACf,oBAAoB,EAAE,IAAI;YAC1B,MAAM,EAAE,WAAW;SACpB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,QAAyB;IAC7C,OAAO;QACL,MAAM,EAAE,qBAAqB;QAC7B,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,0EAA0E;QAC1E,wEAAwE;QACxE,oBAAoB,EAAE,IAAI;QAC1B,MAAM,EAAE,UAAU;KACnB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,OAAO,CAAC,QAAgB;IAC/B,IAAI,CAAC;QACH,OAAO,aAAa,CAAC,QAAQ,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,UAA4B;IACnD,IAAI,UAAU,CAAC,MAAM,KAAK,WAAW,IAAI,CAAC,UAAU,CAAC,SAAS;QAAE,OAAO,UAAU,CAAC;IAClF,MAAM,QAAQ,GAAG,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC;IAC9C,IAAI,QAAQ,KAAK,cAAc;QAAE,OAAO,UAAU,CAAC;IACnD,OAAO,EAAE,GAAG,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;AAC1E,CAAC;AAED,SAAS,WAAW,CAAC,KAAkB;IAIrC,MAAM,WAAW,GAAsB,EAAE,CAAC;IAC1C,MAAM,IAAI,GAAG;QACX,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,IAAI,EAAE,KAAK,CAAC,SAAS;KACtB,CAAC;IAEF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QACvF,WAAW,CAAC,IAAI,CACd,KAAK,CAAC,OAAO,EAAE,qDAAqD,KAAK,CAAC,WAAW,EAAE,EAAE;YACvF,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,IAAI,EAAE,KAAK,CAAC,WAAW;YACvB,WAAW,EAAE,uEAAuE;SACrF,CAAC,CACH,CAAC;QACF,OAAO;YACL,MAAM,EAAE;gBACN,GAAG,IAAI;gBACP,QAAQ,EAAE,CAAC;gBACX,OAAO,EAAE,EAAE;gBACX,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,EAAE;gBACZ,SAAS,EAAE,EAAE;gBACb,EAAE,EAAE,KAAK;aACV;YACD,WAAW;SACZ,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAExC,8EAA8E;IAC9E,0EAA0E;IAC1E,IAAI,KAAK,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QACvE,WAAW,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAChG,MAAM,UAAU,GAAG,wBAAwB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAsB;YAChC,GAAG,IAAI;YACP,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM;YACnC,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,KAAK,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;YAC7D,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,eAAe,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClE,EAAE,EAAE,KAAK;SACV,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACrE,CAAC;IAED,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;QAC7C,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,IAAI,EAAE,KAAK,CAAC,WAAW;QACvB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,yEAAyE;QACzE,iEAAiE;QACjE,KAAK,EAAE,IAAI;KACZ,CAAC,CAAC;IACH,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC;IAE9E,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;IACzE,IAAI,KAAK,KAAK,WAAW;QACvB,WAAW,CAAC,IAAI,CACd,KAAK,CAAC,OAAO,EAAE,uBAAuB,KAAK,CAAC,WAAW,eAAe,EAAE;YACtE,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,KAAK,CAAC,WAAW;YACvB,WAAW,EAAE,gDAAgD;SAC9D,CAAC,CACH,CAAC;IAEJ,yEAAyE;IACzE,2EAA2E;IAC3E,uEAAuE;IACvE,iDAAiD;IACjD,MAAM,OAAO,GACX,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC;IACrE,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CACzB,CAAC,IAAI,EAAE,EAAE,CACP,UAAU,CAAC,IAAI,CAAC;QAChB,UAAU,CAAC;YACT,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE;YAC7B,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,KAAK,EAAE,KAAK,CAAC,KAAK;SACnB,CAAC,CACL,CAAC;IACF,MAAM,SAAS,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM;QACjC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC9E,CAAC,CAAC,SAAS,CAAC;IACd,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK;QACzC,WAAW,CAAC,IAAI,CACd,MAAM,CACJ,OAAO,EACP,kBAAkB,MAAM,CAAC,IAAI,iBAAiB,KAAK,CAAC,WAAW,0DAA0D,EACzH;YACE,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,KAAK,CAAC,WAAW;YACvB,WAAW,EAAE,yCAAyC;SACvD,CACF,CACF,CAAC;IAEJ,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,EAAE;QAC3E,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,cAAc,EAAE,SAAS;QACzB,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,SAAS,EACP,KAAK,CAAC,SAAS,KAAK,QAAQ;YAC1B,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC;YACzD,CAAC,CAAC,SAAS;KAChB,CAAC,CAAC;IAEH,MAAM,UAAU,GACd,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAElG,MAAM,MAAM,GAAsB;QAChC,GAAG,IAAI;QACP,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM;QAC/B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,EAAE,EAAE,KAAK;KACV,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;AACrE,CAAC;AAED,2EAA2E;AAC3E,SAAS,MAAM,CACb,MAAyB,EACzB,WAA8B,EAC9B,KAAkB;IAElB,MAAM,KAAK,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;IAChE,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,OAAO;QAClC,WAAW,CAAC,IAAI,CACd,KAAK,CAAC,OAAO,EAAE,gCAAgC,OAAO,GAAG,EAAE;YACzD,GAAG,KAAK;YACR,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,sCAAsC;SACpD,CAAC,CACH,CAAC;IACJ,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,OAAO;QAClC,WAAW,CAAC,IAAI,CACd,KAAK,CAAC,OAAO,EAAE,gDAAgD,OAAO,GAAG,EAAE;YACzE,GAAG,KAAK;YACR,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,sEAAsE;SACpF,CAAC,CACH,CAAC;IACJ,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,QAAQ;QACpC,WAAW,CAAC,IAAI,CACd,KAAK,CAAC,OAAO,EAAE,IAAI,QAAQ,sDAAsD,EAAE;YACjF,GAAG,KAAK;YACR,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,0DAA0D;SACxE,CAAC,CACH,CAAC;IACJ,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,SAAS;QACtC,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,OAAO;YACb,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,OAAO;YAChB,OAAO,EAAE,0CAA0C,SAAS,GAAG;YAC/D,GAAG,KAAK;YACR,IAAI,EAAE,SAAS;SAChB,CAAC,CAAC;IACL,IAAI,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC,SAAS;QAC7F,WAAW,CAAC,IAAI,CACd,MAAM,CACJ,OAAO,EACP,8BAA8B,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,OAAO,SAAS,cAAc,kBAAkB,EAC1G,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,CACvC,CACF,CAAC;IACJ,OAAO;QACL,GAAG,MAAM;QACT,EAAE,EACA,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM;YACtB,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM;YACtB,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM;YACvB,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM;KAC3B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAoB;IAM5C,MAAM,WAAW,GAAsB,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1E,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;IAEtC,MAAM,OAAO,GAAwB,EAAE,CAAC;IACxC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC5B,WAAW,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,MAAM,GAAG;QACb,OAAO,EAAE,OAAO,CAAC,MAAM;QACvB,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM;QAC9C,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1E,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1E,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5E,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;KAC/E,CAAC;IAEF,OAAO;QACL,MAAM,EAAE;YACN,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE;YAC7D,SAAS,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE;YACzD,oBAAoB,EAAE,sBAAsB;YAC5C,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,OAAO;YACP,MAAM;SACP;QACD,WAAW;QACX,OAAO;QACP,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;KAC9D,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { VerifyConfig } from "./config.js";
2
+ export interface VerifySelection {
3
+ /** `--config <file>`, resolved against the working directory. */
4
+ explicitPath?: string;
5
+ /** Invocation directory; defaults to the process working directory. */
6
+ cwd?: string;
7
+ }
8
+ export declare function readAgentDocument(file: string): Record<string, unknown>;
9
+ export declare function resolveVerifyConfig(selection?: VerifySelection): VerifyConfig;
@@ -0,0 +1,97 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { parse as parseYaml } from "yaml";
4
+ import { configIn } from "../../config.js";
5
+ import { object } from "../../config-schema.js";
6
+ import { parseVerifyBlock } from "./config.js";
7
+ /**
8
+ * Finds the document declaring `agent.verify`.
9
+ *
10
+ * Unlike the `scripts` walk this stops at the *nearest* document that declares
11
+ * the block. Entries are a set describing one repository, not a name lookup, so
12
+ * there is nothing for a farther ancestor to contribute — and merging two
13
+ * repositories' entry lists would verify trees the nearer document never
14
+ * mentioned.
15
+ */
16
+ /** A configuration file larger than this is not one anybody wrote by hand. */
17
+ const MAX_CONFIG_BYTES = 1024 * 1024;
18
+ /** Enough of the head to catch a binary file mistakenly named `.cairn.yml`. */
19
+ const NUL_PROBE_BYTES = 8 * 1024;
20
+ /**
21
+ * Opens for reading without ever blocking.
22
+ *
23
+ * Opening a FIFO `O_RDONLY` blocks until a writer appears, which would wedge
24
+ * the process with no output — so a plain `open` cannot be the first thing
25
+ * that touches the path. `O_NONBLOCK` makes the open return immediately for
26
+ * every file type, leaving `fstat` to reject what is not a regular file.
27
+ * Checking the type by path first would be a check-then-use race; this way
28
+ * every guard is made against the descriptor that is actually read.
29
+ *
30
+ * `O_NONBLOCK` is undefined on Windows, which has no FIFOs in this sense.
31
+ */
32
+ function openForRead(file) {
33
+ const { O_RDONLY, O_NONBLOCK } = fs.constants;
34
+ return fs.openSync(file, O_RDONLY | (O_NONBLOCK ?? 0));
35
+ }
36
+ export function readAgentDocument(file) {
37
+ // The guards mirror `readRegistry` in `src/scripts/resolve.ts`: a
38
+ // regular-file check so a FIFO cannot wedge the process, a size cap, and a
39
+ // NUL probe. Unlike that one, all three are made against an open descriptor,
40
+ // so the file they describe is the file that is read.
41
+ const handle = openForRead(file);
42
+ try {
43
+ const stat = fs.fstatSync(handle);
44
+ if (!stat.isFile())
45
+ throw new Error(`Not a regular file: ${file}`);
46
+ if (stat.size > MAX_CONFIG_BYTES)
47
+ throw new Error(`Configuration file is larger than ${MAX_CONFIG_BYTES} bytes: ${file}`);
48
+ const buffer = Buffer.alloc(stat.size);
49
+ let read = 0;
50
+ while (read < stat.size) {
51
+ const chunk = fs.readSync(handle, buffer, read, stat.size - read, read);
52
+ if (chunk === 0)
53
+ break;
54
+ read += chunk;
55
+ }
56
+ const contents = buffer.subarray(0, read);
57
+ if (contents.subarray(0, NUL_PROBE_BYTES).includes(0))
58
+ throw new Error(`Not a text file: ${file}`);
59
+ return object(parseYaml(contents.toString("utf-8")), "configuration");
60
+ }
61
+ finally {
62
+ fs.closeSync(handle);
63
+ }
64
+ }
65
+ function parseAt(file) {
66
+ const directory = path.dirname(path.resolve(file));
67
+ const document = readAgentDocument(file);
68
+ if (document.agent === undefined)
69
+ return undefined;
70
+ return parseVerifyBlock(document.agent, { file: path.resolve(file), directory });
71
+ }
72
+ export function resolveVerifyConfig(selection = {}) {
73
+ if (selection.explicitPath) {
74
+ const file = path.resolve(selection.explicitPath);
75
+ if (!fs.existsSync(file))
76
+ throw new Error(`Configuration file not found: ${file}`);
77
+ const config = parseAt(file);
78
+ if (!config)
79
+ throw new Error(`No 'agent.verify' block in ${file}`);
80
+ return config;
81
+ }
82
+ let current = path.resolve(selection.cwd ?? process.cwd());
83
+ while (true) {
84
+ const candidate = configIn(current);
85
+ if (candidate) {
86
+ const config = parseAt(candidate);
87
+ if (config)
88
+ return config;
89
+ }
90
+ const parent = path.dirname(current);
91
+ if (parent === current)
92
+ break;
93
+ current = parent;
94
+ }
95
+ throw new Error("No 'agent.verify' block found. Declare one in .cairn.yml, or pass --config <file>.");
96
+ }
97
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/agent/verify/resolve.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -51,6 +51,7 @@ import { agentDoctorAction } from "./commands/agent-doctor.js";
51
51
  import { agentInstallAction } from "./commands/agent-install.js";
52
52
  import { agentUninstallAction } from "./commands/agent-uninstall.js";
53
53
  import { agentInstalledAction } from "./commands/agent-installed.js";
54
+ import { agentVerifyAction } from "./commands/agent-verify.js";
54
55
  import { describeAction } from "./commands/describe.js";
55
56
  import { schemaAction } from "./commands/schema.js";
56
57
  import { completionAction } from "./commands/completion.js";
@@ -327,10 +328,12 @@ agent
327
328
  .action((source, opts) => agentActionBoundary("test", opts, () => agentTestAction(source, opts)));
328
329
  agent
329
330
  .command("install")
330
- .description("Install a bundle into a host plugin or project directory")
331
- .argument("<source>", "Bundle root")
332
- .requiredOption("--target <target>", `Target: ${TARGETS.join(", ")}`, collect)
333
- .option("--scope <scope>", "Install scope: user or project", "user")
331
+ .description("Install one or more bundles into a host plugin or project directory")
332
+ .argument("[source]", "Bundle root; omit when using --config")
333
+ .option("--target <target>", `Target (repeatable, or all): ${TARGETS.join(", ")}`, collect)
334
+ .option("--config <file>", "Install the agent.install block declared in a config file")
335
+ .option("--name <name>", "With --config, install only the named bundle (repeatable)", collect)
336
+ .option("--scope <scope>", "Install scope: user or project")
334
337
  .option("--into <dir>", "Override the install root declared by the target profile")
335
338
  .option("--profile <profile>", "Must match the location's profile when given")
336
339
  .option("--link", "Symlink the rendered tree instead of copying")
@@ -341,7 +344,7 @@ agent
341
344
  .option("--check", "Compare against an existing install without writing")
342
345
  .option("--format <fmt>", "Output format: llm, human, json", "llm")
343
346
  .option("--envelope", "Wrap --format json output in the versioned result envelope")
344
- .addHelpText("after", "\nRenders and packages in memory, so an install is always derived from the\nbundle rather than from a possibly-drifted dist tree. Destinations come from\nthe target profiles. --register is the only flag that edits host config.\n\nExit codes:\n 0 Installed, or checks passed\n 1 Invocation or I/O error\n 2 Install finding, or --check found drift")
347
+ .addHelpText("after", "\nRenders and packages in memory, so an install is always derived from the\nbundle rather than from a possibly-drifted dist tree. Destinations come from\nthe target profiles. --register is the only flag that edits host config.\n\n--target is repeatable, and one destination may hold several installs: they are\ntold apart by bundle, target, profile and scope. A run is planned in full before\nanything is written, so a blocked plan writes nothing at all. --target all covers\nevery target declaring a location for the scope.\n\n--config installs the agent.install block a repository declares, and --target\nthere narrows that block rather than adding to it.\n\nExit codes:\n 0 Installed, or checks passed\n 1 Invocation or I/O error\n 2 Install finding, or --check found drift")
345
348
  .action((source, opts) => agentActionBoundary("install", opts, () => agentInstallAction(source, opts)));
346
349
  agent
347
350
  .command("uninstall")
@@ -366,6 +369,16 @@ agent
366
369
  .option("--envelope", "Wrap --format json output in the versioned result envelope")
367
370
  .addHelpText("after", "\nScans the install roots declared on the target profiles and lists every\n.cairn-install.json it finds.\n\nExit codes:\n 0 Listing written to stdout\n 1 Invocation error")
368
371
  .action((opts) => agentActionBoundary("installed", opts, () => agentInstalledAction(opts)));
372
+ agent
373
+ .command("verify")
374
+ .description("Check committed agent trees against the bundles they were generated from")
375
+ .option("--config <file>", "Configuration document declaring the agent.verify block")
376
+ .option("--name <name>", "Verify only this entry (repeatable)", collect)
377
+ .option("--strict", "Treat warnings as blocking findings")
378
+ .option("--format <fmt>", "Output format: llm, human, json", "llm")
379
+ .option("--envelope", "Wrap --format json output in the versioned result envelope")
380
+ .addHelpText("after", "\nReads what to verify from the agent.verify block of a cairn configuration\ndocument, so a CI pipeline can run it with no arguments. Each declared bundle\nis rendered in memory and compared against the committed tree, and the pinned\nCLI and target profile versions are asserted against the running build.\n\nExit codes:\n 0 Every entry matches and every pin is satisfied\n 1 Invocation, configuration, or I/O error\n 2 Drift, an orphaned file, or a violated pin")
381
+ .action((opts) => agentActionBoundary("verify", opts, () => agentVerifyAction(opts)));
369
382
  agent
370
383
  .command("specs")
371
384
  .description("Print the versioned target conformance profiles")