@kungfu-tech/buildchain 2.8.15 → 2.8.16-alpha.1

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.
@@ -0,0 +1,450 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { spawnSync } from "node:child_process";
4
+ import crypto from "node:crypto";
5
+ import { loadBuildchainConfig } from "./buildchain-config.js";
6
+ import {
7
+ readJsonFromLocation,
8
+ verifyReleasePassport,
9
+ } from "./release-passport.js";
10
+
11
+ export const README_BADGE_FACTS_CONTRACT = "kungfu-buildchain-readme-badge-facts";
12
+ export const README_BADGE_BLOCK_START = "<!-- buildchain:badges:start -->";
13
+ export const README_BADGE_BLOCK_END = "<!-- buildchain:badges:end -->";
14
+
15
+ const KFD_KEYS = [
16
+ { key: "kfd-1", id: "kfd1", label: "KFD-1", text: "contract world" },
17
+ { key: "kfd-2", id: "kfd2", label: "KFD-2", text: "trust passport" },
18
+ { key: "kfd-3", id: "kfd3", label: "KFD-3", text: "collaboration interface" },
19
+ ];
20
+
21
+ const STATE_COLORS = {
22
+ passed: "2ea44f",
23
+ aligned: "0969da",
24
+ declared: "6e7781",
25
+ planned: "bf8700",
26
+ draft: "8250df",
27
+ downgraded: "bf8700",
28
+ failed: "cf222e",
29
+ missing: "6e7781",
30
+ unknown: "6e7781",
31
+ };
32
+
33
+ function readJsonIfExists(filePath) {
34
+ if (!filePath || !fs.existsSync(filePath)) {
35
+ return undefined;
36
+ }
37
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
38
+ }
39
+
40
+ function readTextIfExists(filePath) {
41
+ return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
42
+ }
43
+
44
+ function sha256File(filePath) {
45
+ return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
46
+ }
47
+
48
+ function posixPath(value) {
49
+ return String(value || "").split(path.sep).join("/");
50
+ }
51
+
52
+ function encodeBadge(value) {
53
+ return encodeURIComponent(String(value || ""))
54
+ .replace(/-/g, "--")
55
+ .replace(/_/g, "__");
56
+ }
57
+
58
+ function badgeUrl({ label, message, color }) {
59
+ return `https://img.shields.io/badge/${encodeBadge(label)}-${encodeBadge(message)}-${color || "6e7781"}.svg`;
60
+ }
61
+
62
+ function normalizeState(value, fallback = "planned") {
63
+ const normalized = String(value || fallback).trim().toLowerCase();
64
+ if (["passed", "aligned", "declared", "planned", "draft", "downgraded", "failed", "missing"].includes(normalized)) {
65
+ return normalized;
66
+ }
67
+ return fallback;
68
+ }
69
+
70
+ function normalizeDeclarationState(value, fallback = "planned") {
71
+ const normalized = normalizeState(value, fallback);
72
+ return normalized === "passed" ? "declared" : normalized;
73
+ }
74
+
75
+ function normalizeStringArray(value) {
76
+ if (!Array.isArray(value)) {
77
+ return [];
78
+ }
79
+ return value
80
+ .map((entry) => String(entry || "").trim())
81
+ .filter(Boolean);
82
+ }
83
+
84
+ function readRepositoryFromGit(cwd) {
85
+ const remote = spawnSync("git", ["config", "--get", "remote.origin.url"], {
86
+ cwd,
87
+ encoding: "utf8",
88
+ });
89
+ const value = remote.status === 0 ? remote.stdout.trim() : "";
90
+ const match = value.match(/github\.com[:/]([^/\s]+)\/([^/\s.]+)(?:\.git)?$/);
91
+ if (!match) {
92
+ return { fullName: "", owner: "", name: "", url: "" };
93
+ }
94
+ const fullName = `${match[1]}/${match[2]}`;
95
+ return {
96
+ fullName,
97
+ owner: match[1],
98
+ name: match[2],
99
+ url: `https://github.com/${fullName}`,
100
+ };
101
+ }
102
+
103
+ function normalizePackageRepository(repository) {
104
+ const value = typeof repository === "string" ? repository : repository?.url || "";
105
+ const match = String(value).match(/github\.com[:/]([^/\s]+)\/([^/\s.]+)(?:\.git)?/);
106
+ if (!match) {
107
+ return undefined;
108
+ }
109
+ const fullName = `${match[1]}/${match[2]}`;
110
+ return {
111
+ fullName,
112
+ owner: match[1],
113
+ name: match[2],
114
+ url: `https://github.com/${fullName}`,
115
+ };
116
+ }
117
+
118
+ function discoverWorkflowFacts({ cwd, repository, badgeConfig = {} }) {
119
+ const workflowDir = path.join(cwd, ".github", "workflows");
120
+ const configured = normalizeStringArray(badgeConfig.workflows);
121
+ const defaults = ["verify.yml", "build.yml", "buildchain-ref-promotion.yml"];
122
+ const names = configured.length > 0 ? configured : defaults;
123
+ if (!fs.existsSync(workflowDir)) {
124
+ return [];
125
+ }
126
+ return names
127
+ .map((file) => {
128
+ const workflowPath = path.join(workflowDir, file);
129
+ if (!fs.existsSync(workflowPath)) {
130
+ return undefined;
131
+ }
132
+ const source = fs.readFileSync(workflowPath, "utf8");
133
+ const nameMatch = source.match(/^name:\s*["']?(.+?)["']?\s*$/m);
134
+ const name = String(nameMatch?.[1] || path.basename(file, path.extname(file))).trim();
135
+ return {
136
+ file,
137
+ name,
138
+ badgeUrl: repository.fullName
139
+ ? `https://github.com/${repository.fullName}/actions/workflows/${file}/badge.svg`
140
+ : "",
141
+ url: repository.fullName
142
+ ? `https://github.com/${repository.fullName}/actions/workflows/${file}`
143
+ : "",
144
+ };
145
+ })
146
+ .filter(Boolean);
147
+ }
148
+
149
+ function defaultReleasePassportLocation({ cwd, repository, badgeConfig = {} }) {
150
+ const configured = badgeConfig.release_passport_url || badgeConfig.releasePassportUrl || badgeConfig.release_passport || "";
151
+ if (configured) {
152
+ return String(configured);
153
+ }
154
+ const localCandidates = [
155
+ "buildchain.release.json",
156
+ ".buildchain/release-passport/buildchain.release.json",
157
+ ];
158
+ for (const candidate of localCandidates) {
159
+ const filePath = path.join(cwd, candidate);
160
+ if (fs.existsSync(filePath)) {
161
+ return candidate;
162
+ }
163
+ }
164
+ return repository.fullName
165
+ ? `https://github.com/${repository.fullName}/releases/latest/download/buildchain.release.json`
166
+ : "";
167
+ }
168
+
169
+ function siblingUrl(location, filename) {
170
+ if (!/^https?:\/\//.test(location)) {
171
+ return "";
172
+ }
173
+ return `${location.replace(/\/[^/]*$/, "")}/${filename}`;
174
+ }
175
+
176
+ async function readPassportAndReport({ cwd, location }) {
177
+ if (!location) {
178
+ return { passport: undefined, report: undefined, error: "" };
179
+ }
180
+ const resolvedLocation = /^https?:\/\//.test(location) ? location : path.resolve(cwd, location);
181
+ try {
182
+ const passport = await readJsonFromLocation(resolvedLocation);
183
+ const report = await verifyReleasePassport({
184
+ passportLocation: resolvedLocation,
185
+ artifactEvidenceLocation: siblingUrl(resolvedLocation, "artifact-evidence.json"),
186
+ impactLocation: siblingUrl(resolvedLocation, "impact.json"),
187
+ agentIndexLocation: siblingUrl(resolvedLocation, "agent-index.json"),
188
+ productMechanismLocation: siblingUrl(resolvedLocation, "product-mechanism.json"),
189
+ });
190
+ return { passport, report, error: "" };
191
+ } catch (error) {
192
+ return { passport: undefined, report: undefined, error: error.message };
193
+ }
194
+ }
195
+
196
+ function kfdSectionPassed({ passport, report, key }) {
197
+ if (!passport || !report?.ok) {
198
+ return false;
199
+ }
200
+ const section = passport[key] || (key === "kfd-1" ? passport.kfd1 : undefined) || (key === "kfd-3" ? passport.kfd3 : undefined);
201
+ return section?.status === "passed";
202
+ }
203
+
204
+ function declaredKfdState({ badgeConfig = {}, key, id }) {
205
+ const kfdConfig = badgeConfig.kfd && typeof badgeConfig.kfd === "object" ? badgeConfig.kfd : {};
206
+ return normalizeDeclarationState(
207
+ badgeConfig[key] ||
208
+ badgeConfig[id] ||
209
+ badgeConfig[key.replace("-", "_")] ||
210
+ kfdConfig[key] ||
211
+ kfdConfig[id] ||
212
+ kfdConfig[key.replace("kfd-", "")],
213
+ "planned",
214
+ );
215
+ }
216
+
217
+ function releasePassportState({ report, error, badgeConfig = {} }) {
218
+ if (report?.ok) {
219
+ return "passed";
220
+ }
221
+ if (error) {
222
+ return normalizeDeclarationState(badgeConfig.release_passport_state || badgeConfig.releasePassportState, "declared");
223
+ }
224
+ return normalizeDeclarationState(badgeConfig.release_passport_state || badgeConfig.releasePassportState, "planned");
225
+ }
226
+
227
+ function licenseFromFiles(cwd, packageJson) {
228
+ if (packageJson.license) {
229
+ return String(packageJson.license);
230
+ }
231
+ const licenseFile = fs.readdirSync(cwd).find((entry) => /^licen[sc]e($|\.)/i.test(entry));
232
+ return licenseFile ? licenseFile.replace(/^LICENSE[.-]?/i, "") || "present" : "";
233
+ }
234
+
235
+ function collectPlatformFacts({ badgeConfig = {}, passport = undefined }) {
236
+ const configured = normalizeStringArray(badgeConfig.platforms);
237
+ if (configured.length > 0) {
238
+ return configured;
239
+ }
240
+ const assets = Array.isArray(passport?.artifacts) ? passport.artifacts : [];
241
+ const platforms = new Set();
242
+ for (const asset of assets) {
243
+ const platform = String(asset.platform || "").trim();
244
+ if (platform) {
245
+ platforms.add(platform);
246
+ }
247
+ }
248
+ return [...platforms].sort();
249
+ }
250
+
251
+ function localFactFileSummary(cwd, candidates = []) {
252
+ for (const relPath of candidates) {
253
+ const filePath = path.join(cwd, relPath);
254
+ if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
255
+ continue;
256
+ }
257
+ try {
258
+ const value = JSON.parse(fs.readFileSync(filePath, "utf8"));
259
+ return {
260
+ location: relPath,
261
+ sha256: sha256File(filePath),
262
+ contract: value.contract || "",
263
+ claimCount: Array.isArray(value.publicClaims) ? value.publicClaims.length : undefined,
264
+ category: value.category || "",
265
+ };
266
+ } catch (error) {
267
+ return {
268
+ location: relPath,
269
+ error: error.message,
270
+ };
271
+ }
272
+ }
273
+ return undefined;
274
+ }
275
+
276
+ function buildBadgeEntries(facts) {
277
+ const entries = [];
278
+ for (const kfd of facts.kfd) {
279
+ entries.push({
280
+ id: kfd.key,
281
+ alt: `${kfd.label}: ${kfd.state}`,
282
+ image: badgeUrl({
283
+ label: kfd.label,
284
+ message: `${kfd.text} ${kfd.state}`,
285
+ color: STATE_COLORS[kfd.state] || STATE_COLORS.unknown,
286
+ }),
287
+ link: kfd.url,
288
+ });
289
+ }
290
+ entries.push({
291
+ id: "release-passport",
292
+ alt: `Release Passport: ${facts.releasePassport.state}`,
293
+ image: badgeUrl({
294
+ label: "release passport",
295
+ message: facts.releasePassport.state,
296
+ color: STATE_COLORS[facts.releasePassport.state] || STATE_COLORS.unknown,
297
+ }),
298
+ link: facts.releasePassport.url,
299
+ });
300
+ if (facts.package.license) {
301
+ entries.push({
302
+ id: "license",
303
+ alt: `License: ${facts.package.license}`,
304
+ image: badgeUrl({ label: "license", message: facts.package.license, color: "0969da" }),
305
+ link: facts.repository.url ? `${facts.repository.url}/blob/HEAD/LICENSE` : "",
306
+ });
307
+ }
308
+ if (facts.platforms.length > 0) {
309
+ entries.push({
310
+ id: "platform",
311
+ alt: `Platform: ${facts.platforms.join(" | ")}`,
312
+ image: badgeUrl({ label: "platform", message: facts.platforms.join(" | "), color: "6e7781" }),
313
+ link: facts.releasePassport.url,
314
+ });
315
+ }
316
+ for (const workflow of facts.workflows) {
317
+ entries.push({
318
+ id: `workflow:${workflow.file}`,
319
+ alt: workflow.name,
320
+ image: workflow.badgeUrl,
321
+ link: workflow.url,
322
+ });
323
+ }
324
+ return entries;
325
+ }
326
+
327
+ export async function collectReadmeBadgeFacts({ cwd = process.cwd() } = {}) {
328
+ const resolvedCwd = path.resolve(cwd);
329
+ const loadedConfig = loadBuildchainConfig(resolvedCwd);
330
+ const badgeConfig = loadedConfig?.config?.badges && typeof loadedConfig.config.badges === "object"
331
+ ? loadedConfig.config.badges
332
+ : {};
333
+ const packageJson = readJsonIfExists(path.join(resolvedCwd, "package.json")) || {};
334
+ const packageRepository = normalizePackageRepository(packageJson.repository);
335
+ const repository = packageRepository || readRepositoryFromGit(resolvedCwd);
336
+ const releasePassportLocation = defaultReleasePassportLocation({ cwd: resolvedCwd, repository, badgeConfig });
337
+ const { passport, report, error } = await readPassportAndReport({
338
+ cwd: resolvedCwd,
339
+ location: releasePassportLocation,
340
+ });
341
+ const releasePassportUrl = /^https?:\/\//.test(releasePassportLocation)
342
+ ? releasePassportLocation
343
+ : repository.fullName && releasePassportLocation
344
+ ? `${repository.url}/blob/HEAD/${posixPath(releasePassportLocation)}`
345
+ : releasePassportLocation;
346
+ const releaseState = releasePassportState({ report, error, badgeConfig });
347
+ const kfd = KFD_KEYS.map((entry) => {
348
+ const passed = kfdSectionPassed({ passport, report, key: entry.key });
349
+ const state = passed ? "passed" : declaredKfdState({ badgeConfig, key: entry.key, id: entry.id });
350
+ return {
351
+ key: entry.key,
352
+ label: entry.label,
353
+ text: entry.text,
354
+ state,
355
+ source: passed ? "release-passport" : "declaration",
356
+ url: releasePassportUrl,
357
+ };
358
+ });
359
+ const facts = {
360
+ schemaVersion: 1,
361
+ contract: README_BADGE_FACTS_CONTRACT,
362
+ cwd: resolvedCwd,
363
+ repository,
364
+ package: {
365
+ name: packageJson.name || "",
366
+ version: packageJson.version || "",
367
+ license: licenseFromFiles(resolvedCwd, packageJson),
368
+ },
369
+ releasePassport: {
370
+ location: releasePassportLocation,
371
+ url: releasePassportUrl,
372
+ state: releaseState,
373
+ verified: Boolean(report?.ok),
374
+ error,
375
+ reportSummary: report ? {
376
+ ok: report.ok,
377
+ trust: report.trust,
378
+ issueCount: Array.isArray(report.issues) ? report.issues.length : 0,
379
+ } : undefined,
380
+ },
381
+ kfdClaimRegistry: localFactFileSummary(resolvedCwd, [
382
+ "dist/site/kfd-claims.json",
383
+ ".buildchain/kfd-claims.json",
384
+ ]),
385
+ productMechanism: localFactFileSummary(resolvedCwd, [
386
+ "dist/site/product-mechanism.json",
387
+ "product-mechanism.json",
388
+ ".buildchain/product-mechanism.json",
389
+ ]),
390
+ kfd,
391
+ platforms: collectPlatformFacts({ badgeConfig, passport }),
392
+ workflows: discoverWorkflowFacts({ cwd: resolvedCwd, repository, badgeConfig }),
393
+ badges: [],
394
+ };
395
+ facts.badges = buildBadgeEntries(facts);
396
+ return facts;
397
+ }
398
+
399
+ export function renderReadmeBadgeBlock(facts) {
400
+ const lines = [
401
+ README_BADGE_BLOCK_START,
402
+ ...facts.badges.map((badge) => (
403
+ badge.link
404
+ ? `[![${badge.alt}](${badge.image})](${badge.link})`
405
+ : `![${badge.alt}](${badge.image})`
406
+ )),
407
+ README_BADGE_BLOCK_END,
408
+ ];
409
+ return `${lines.join("\n")}\n`;
410
+ }
411
+
412
+ function badgeBlockRegex() {
413
+ return new RegExp(`${README_BADGE_BLOCK_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${README_BADGE_BLOCK_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`);
414
+ }
415
+
416
+ export function checkReadmeBadgeBlock({ readmeText, facts } = {}) {
417
+ const expected = renderReadmeBadgeBlock(facts);
418
+ const match = String(readmeText || "").match(badgeBlockRegex());
419
+ const actual = match ? match[0] : "";
420
+ const normalizedActual = actual.endsWith("\n") ? actual : `${actual}\n`;
421
+ const ok = normalizedActual === expected;
422
+ return {
423
+ schemaVersion: 1,
424
+ contract: "kungfu-buildchain-readme-badge-check",
425
+ ok,
426
+ missing: !match,
427
+ stale: Boolean(match) && !ok,
428
+ expected,
429
+ actual,
430
+ facts,
431
+ message: ok ? "README badge block is current" : (match ? "README badge block is stale" : "README badge block is missing"),
432
+ };
433
+ }
434
+
435
+ export function updateReadmeBadgeBlock({ readmeText, facts } = {}) {
436
+ const source = String(readmeText || "");
437
+ const block = renderReadmeBadgeBlock(facts);
438
+ if (badgeBlockRegex().test(source)) {
439
+ return source.replace(badgeBlockRegex(), block);
440
+ }
441
+ const h1 = source.match(/^# .+\n+/);
442
+ if (h1) {
443
+ return `${h1[0]}${block}\n${source.slice(h1[0].length)}`;
444
+ }
445
+ return `${block}\n${source}`;
446
+ }
447
+
448
+ export function readReadme({ cwd = process.cwd(), readmePath = "README.md" } = {}) {
449
+ return readTextIfExists(path.resolve(cwd, readmePath));
450
+ }
@@ -1789,13 +1789,22 @@ export function createReleaseCheckReport({
1789
1789
  };
1790
1790
  }
1791
1791
 
1792
- export async function readJsonFromLocation(location) {
1792
+ export async function readJsonFromLocation(location, redirectCount = 0) {
1793
1793
  const input = nonEmptyString(location, "location");
1794
+ if (redirectCount > 5) {
1795
+ throw new Error(`too many redirects while reading ${input}`);
1796
+ }
1794
1797
  if (/^https?:\/\//.test(input)) {
1795
1798
  const client = input.startsWith("https:") ? https : http;
1796
1799
  return new Promise((resolve, reject) => {
1797
1800
  client
1798
1801
  .get(input, (response) => {
1802
+ if ([301, 302, 303, 307, 308].includes(response.statusCode) && response.headers.location) {
1803
+ const nextLocation = new URL(response.headers.location, input).toString();
1804
+ response.resume();
1805
+ readJsonFromLocation(nextLocation, redirectCount + 1).then(resolve, reject);
1806
+ return;
1807
+ }
1799
1808
  if (response.statusCode < 200 || response.statusCode >= 300) {
1800
1809
  reject(new Error(`HTTP ${response.statusCode} while reading ${input}`));
1801
1810
  response.resume();
@@ -20,6 +20,7 @@ const requiredPaths = [
20
20
  "docs/consumer-issue-reporting.md",
21
21
  "docs/install.md",
22
22
  "docs/product-mechanism.md",
23
+ "docs/readme-badges.md",
23
24
  "docs/release-passport.md",
24
25
  "docs/release-propagation.md",
25
26
  "docs/site-bundle-contract.md",
@@ -111,6 +112,9 @@ if (rootPackage.exports?.["./buildchain-contract"] !== "./packages/core/buildcha
111
112
  if (rootPackage.exports?.["./issue-reporting"] !== "./packages/core/issue-reporting.js") {
112
113
  throw new Error("root package must export @kungfu-tech/buildchain/issue-reporting");
113
114
  }
115
+ if (rootPackage.exports?.["./readme-badges"] !== "./packages/core/readme-badges.js") {
116
+ throw new Error("root package must export @kungfu-tech/buildchain/readme-badges");
117
+ }
114
118
  if (rootPackage.exports?.["./logging"] !== "./packages/core/logging.js") {
115
119
  throw new Error("root package must export @kungfu-tech/buildchain/logging");
116
120
  }
@@ -199,6 +203,16 @@ if (!coreIndexSource.includes("KFD2_TRUST_PROOF_CONTRACT")) {
199
203
  if (!coreIndexSource.includes("createSurfaceTimestampPolicy")) {
200
204
  throw new Error("packages/core/index.js must export surface manifest timestamp policy APIs");
201
205
  }
206
+ for (const requiredSnippet of [
207
+ "collectReadmeBadgeFacts",
208
+ "renderReadmeBadgeBlock",
209
+ "checkReadmeBadgeBlock",
210
+ "updateReadmeBadgeBlock",
211
+ ]) {
212
+ if (!coreIndexSource.includes(requiredSnippet)) {
213
+ throw new Error(`packages/core/index.js must export README badge API: ${requiredSnippet}`);
214
+ }
215
+ }
202
216
  if (siteBundle.contract !== "kungfu-buildchain-site-bundle") {
203
217
  throw new Error("buildchain-site.json must expose the Buildchain site bundle contract");
204
218
  }
@@ -376,11 +390,25 @@ for (const requiredSnippet of [
376
390
  "release propagation",
377
391
  "manual-registry.json",
378
392
  "node-api-registry.json",
393
+ "README badge",
379
394
  ]) {
380
395
  if (!docsMap.includes(requiredSnippet)) {
381
396
  throw new Error(`documentation map missing capability coverage snippet: ${requiredSnippet}`);
382
397
  }
383
398
  }
399
+ const readmeBadgesDoc = fs.readFileSync(path.join(root, "docs/readme-badges.md"), "utf8");
400
+ for (const requiredSnippet of [
401
+ "buildchain badges readme --check",
402
+ "collectReadmeBadgeFacts",
403
+ "kungfu-buildchain-readme-badge-facts",
404
+ "KFD passed",
405
+ "release passport",
406
+ "<!-- buildchain:badges:start -->",
407
+ ]) {
408
+ if (!readmeBadgesDoc.includes(requiredSnippet)) {
409
+ throw new Error(`README badges doc missing required snippet: ${requiredSnippet}`);
410
+ }
411
+ }
384
412
  const reusableBuildSurfaceDoc = fs.readFileSync(path.join(root, "docs/reusable-build-surface.md"), "utf8");
385
413
  for (const requiredSnippet of [
386
414
  "Floating Ref Contract Lock",
@@ -403,6 +431,15 @@ for (const [docName, docSource] of Object.entries({ "docs/cli.md": cliDoc, "docs
403
431
  }
404
432
  }
405
433
  }
434
+ for (const requiredSnippet of [
435
+ "buildchain badges readme --check",
436
+ "buildchain badges readme --write",
437
+ "@kungfu-tech/buildchain/readme-badges",
438
+ ]) {
439
+ if (!cliDoc.includes(requiredSnippet)) {
440
+ throw new Error(`CLI doc missing README badge command snippet: ${requiredSnippet}`);
441
+ }
442
+ }
406
443
  const releaseLineDryRunScript = fs.readFileSync(path.join(root, "scripts/release-line-dry-run.mjs"), "utf8");
407
444
  const standaloneBinaryScript = fs.readFileSync(path.join(root, "scripts/build-standalone-binary.mjs"), "utf8");
408
445
  for (const requiredSnippet of [
@@ -356,6 +356,7 @@ function buildSiteBundle() {
356
356
  })),
357
357
  docs: [
358
358
  { id: "cli-and-node-package", path: "docs/cli.md", digest: sha256File("docs/cli.md") },
359
+ { id: "readme-badges", path: "docs/readme-badges.md", digest: sha256File("docs/readme-badges.md") },
359
360
  { id: "site-bundle-contract", path: "docs/site-bundle-contract.md", digest: sha256File("docs/site-bundle-contract.md") },
360
361
  ],
361
362
  guidance: "These are the public Node import surfaces shipped by the npm package. Agents should prefer these exports over internal file paths.",