@kungfu-tech/buildchain 2.8.7-alpha.1 → 2.8.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.
@@ -38,6 +38,7 @@ const requiredPaths = [
38
38
  "scripts/release-candidate-resolver.mjs",
39
39
  "scripts/buildchain-patrol.mjs",
40
40
  "scripts/workflow-friction-report.mjs",
41
+ "scripts/web-surface-production-release-pr.mjs",
41
42
  "docs/migration-inventory.md",
42
43
  "docs/lifecycle-protocol.md",
43
44
  "docs/ownership.md",
@@ -79,6 +80,22 @@ if (rootPackage.name !== "@kungfu-tech/buildchain") {
79
80
  if (rootPackage.private !== false) {
80
81
  throw new Error("root package must be publishable with private=false");
81
82
  }
83
+ const packageDependencySections = ["dependencies", "optionalDependencies", "peerDependencies"];
84
+ const exoticDependencyPattern =
85
+ /^(?:git(?:\+ssh|\+https|\+http|\+file)?:|github:|gitlab:|bitbucket:|https?:|file:|link:|workspace:)/i;
86
+ for (const sectionName of packageDependencySections) {
87
+ const section = rootPackage[sectionName] || {};
88
+ for (const [dependencyName, specifier] of Object.entries(section)) {
89
+ if (typeof specifier !== "string") {
90
+ throw new Error(`package ${sectionName}.${dependencyName} must use a string version specifier`);
91
+ }
92
+ if (exoticDependencyPattern.test(specifier.trim())) {
93
+ throw new Error(
94
+ `package ${sectionName}.${dependencyName} must use an auditable npm registry version, not exotic specifier: ${specifier}`,
95
+ );
96
+ }
97
+ }
98
+ }
82
99
  if (rootPackage.bin?.buildchain !== "./bin/buildchain.mjs") {
83
100
  throw new Error("root package must expose the buildchain CLI");
84
101
  }
@@ -109,6 +126,15 @@ if (rootPackage.exports?.["./release-propagation"] !== "./packages/core/release-
109
126
  if (rootPackage.exports?.["./buildchain-kfd-claims"] !== "./packages/core/buildchain-kfd-claims.js") {
110
127
  throw new Error("root package must export @kungfu-tech/buildchain/buildchain-kfd-claims");
111
128
  }
129
+ if (rootPackage.exports?.["./site/buildchain-site.json"] !== "./dist/site/buildchain-site.json") {
130
+ throw new Error("root package must export @kungfu-tech/buildchain/site/buildchain-site.json");
131
+ }
132
+ if (rootPackage.exports?.["./site/site-manifest.json"] !== "./dist/site/site-manifest.json") {
133
+ throw new Error("root package must export @kungfu-tech/buildchain/site/site-manifest.json");
134
+ }
135
+ if (rootPackage.exports?.["./site/page-registry.json"] !== "./dist/site/page-registry.json") {
136
+ throw new Error("root package must export @kungfu-tech/buildchain/site/page-registry.json");
137
+ }
112
138
  if (rootPackage.exports?.["./site/manual-registry.json"] !== "./dist/site/manual-registry.json") {
113
139
  throw new Error("root package must export @kungfu-tech/buildchain/site/manual-registry.json");
114
140
  }
@@ -124,25 +150,25 @@ if (rootPackage.publishConfig?.access !== "public") {
124
150
  if (rootPackage.publishConfig?.registry !== "https://registry.npmjs.org/") {
125
151
  throw new Error("root package publishConfig.registry must be npmjs");
126
152
  }
127
- for (const expectedFile of ["bin/", "scripts/*.mjs", "packages/core/", "docs/MAP.md", "docs/cli.md"]) {
153
+ for (const expectedFile of ["bin/", "scripts/*.mjs", "packages/core/", "docs/*.md"]) {
128
154
  if (!rootPackage.files?.includes(expectedFile)) {
129
155
  throw new Error(`root package files must include ${expectedFile}`);
130
156
  }
131
157
  }
132
- for (const expectedFile of ["dist/site/", "docs/install.md", "docs/binary-distribution.md", "docs/site-bundle-contract.md"]) {
158
+ for (const expectedFile of ["dist/site/", "actions/*/README.md", "fixtures/*/README.md"]) {
133
159
  if (!rootPackage.files?.includes(expectedFile)) {
134
160
  throw new Error(`root package files must include ${expectedFile}`);
135
161
  }
136
162
  }
137
- if (!rootPackage.files?.includes("docs/release-propagation.md")) {
138
- throw new Error("root package files must include docs/release-propagation.md");
139
- }
140
163
  const cliSource = fs.readFileSync(path.join(root, "bin/buildchain.mjs"), "utf8");
141
164
  const coreIndexSource = fs.readFileSync(path.join(root, "packages/core/index.js"), "utf8");
142
165
  const versioningDoc = fs.readFileSync(path.join(root, "docs/versioning.md"), "utf8");
143
166
  const cliDoc = fs.readFileSync(path.join(root, "docs/cli.md"), "utf8");
144
167
  const docsMap = fs.readFileSync(path.join(root, "docs/MAP.md"), "utf8");
145
168
  const installDoc = fs.readFileSync(path.join(root, "docs/install.md"), "utf8");
169
+ const siteBundle = JSON.parse(fs.readFileSync(path.join(root, "dist/site/buildchain-site.json"), "utf8"));
170
+ const siteManifest = JSON.parse(fs.readFileSync(path.join(root, "dist/site/site-manifest.json"), "utf8"));
171
+ const pageRegistry = JSON.parse(fs.readFileSync(path.join(root, "dist/site/page-registry.json"), "utf8"));
146
172
  if (!cliSource.startsWith("#!/usr/bin/env node")) {
147
173
  throw new Error("bin/buildchain.mjs must be executable with a node shebang");
148
174
  }
@@ -167,6 +193,89 @@ if (!coreIndexSource.includes("KFD2_RELEASE_TRUST_PASSPORT_CONTRACT")) {
167
193
  if (!coreIndexSource.includes("KFD2_TRUST_PROOF_CONTRACT")) {
168
194
  throw new Error("packages/core/index.js must export KFD-2 trust proof contract");
169
195
  }
196
+ if (siteBundle.contract !== "kungfu-buildchain-site-bundle") {
197
+ throw new Error("buildchain-site.json must expose the Buildchain site bundle contract");
198
+ }
199
+ if (siteBundle.source?.homepageTextSource !== "README.md") {
200
+ throw new Error("buildchain-site.json source.homepageTextSource must be README.md");
201
+ }
202
+ if (siteManifest.source?.homepageTextSource !== "README.md") {
203
+ throw new Error("site-manifest.json source.homepageTextSource must be README.md");
204
+ }
205
+ if (siteBundle.homepage?.title !== "Buildchain") {
206
+ throw new Error("buildchain-site.json homepage.title must match README H1");
207
+ }
208
+ if (!Array.isArray(siteBundle.homepage?.sections) || siteBundle.homepage.sections.length < 5) {
209
+ throw new Error("buildchain-site.json homepage.sections must expose README-derived sections");
210
+ }
211
+ for (const requiredSection of ["install-and-verify", "use-buildchain", "release-model", "toolkit-observability", "site-fact-source"]) {
212
+ if (!siteBundle.homepage.sections.some((entry) => entry.id === requiredSection && entry.sourcePath === "README.md" && entry.markdown)) {
213
+ throw new Error(`buildchain-site.json homepage.sections must include README projection ${requiredSection}`);
214
+ }
215
+ }
216
+ if (siteBundle.homepage.sections.some((entry) => entry.id === "homepage-content-contract")) {
217
+ throw new Error("buildchain-site.json homepage.sections must not render the renderer contract as homepage content");
218
+ }
219
+ if (
220
+ siteBundle.homepage?.rendererContract?.id !== "homepage-content-contract" ||
221
+ siteBundle.homepage?.rendererContract?.renderAsHomepageContent !== false ||
222
+ !siteBundle.homepage?.rendererContract?.markdown
223
+ ) {
224
+ throw new Error("buildchain-site.json homepage.rendererContract must expose README renderer contract outside homepage.sections");
225
+ }
226
+ if (!siteBundle.homepage?.displayPlan?.firstScreen?.include?.includes("install-and-verify")) {
227
+ throw new Error("buildchain-site.json homepage.displayPlan firstScreen must include install-and-verify");
228
+ }
229
+ if (!Array.isArray(siteBundle.renderingBoundary?.ownedByBuildchain) || !siteBundle.renderingBoundary.ownedByBuildchain.includes("homepage section projection from README.md")) {
230
+ throw new Error("buildchain-site.json renderingBoundary.ownedByBuildchain must include README projection ownership");
231
+ }
232
+ if (!Array.isArray(siteBundle.renderingBoundary?.ownedBySite) || !siteBundle.renderingBoundary.ownedBySite.includes("markdown-to-HTML renderer")) {
233
+ throw new Error("buildchain-site.json renderingBoundary.ownedBySite must include markdown-to-HTML renderer");
234
+ }
235
+ if (pageRegistry.contract !== "kungfu-buildchain-site-page-registry") {
236
+ throw new Error("page-registry.json must expose the site page registry contract");
237
+ }
238
+ if (siteBundle.pageRegistry?.path !== "page-registry.json" || siteBundle.pageRegistry?.pageCount !== pageRegistry.pageCount) {
239
+ throw new Error("buildchain-site.json must link to page-registry.json with matching page count");
240
+ }
241
+ if (!siteManifest.facts?.includes("page-registry.json") || !siteBundle.entrypoints?.includes("page-registry.json")) {
242
+ throw new Error("site bundle entrypoints must include page-registry.json");
243
+ }
244
+ function immediateReadmes(dir) {
245
+ const absoluteDir = path.join(root, dir);
246
+ if (!fs.existsSync(absoluteDir)) return [];
247
+ return fs.readdirSync(absoluteDir, { withFileTypes: true })
248
+ .filter((entry) => entry.isDirectory())
249
+ .map((entry) => `${dir}/${entry.name}/README.md`)
250
+ .filter((relPath) => fs.existsSync(path.join(root, relPath)))
251
+ .sort();
252
+ }
253
+ const expectedPageSources = [
254
+ "README.md",
255
+ ...fs.readdirSync(path.join(root, "docs")).filter((name) => name.endsWith(".md")).sort().map((name) => `docs/${name}`),
256
+ ...immediateReadmes("actions"),
257
+ "packages/core/README.md",
258
+ ...immediateReadmes("fixtures"),
259
+ ].sort();
260
+ const registryPageSources = [...new Set((pageRegistry.pages || []).map((page) => page.sourcePath))].sort();
261
+ const bundlePageSources = [...new Set((siteBundle.pages || []).map((page) => page.sourcePath))].sort();
262
+ if (JSON.stringify(registryPageSources) !== JSON.stringify(expectedPageSources)) {
263
+ throw new Error(`page-registry.json must include every public markdown page: expected ${expectedPageSources.length}, got ${registryPageSources.length}`);
264
+ }
265
+ if (JSON.stringify(bundlePageSources) !== JSON.stringify(expectedPageSources)) {
266
+ throw new Error(`buildchain-site.json pages must include every public markdown page: expected ${expectedPageSources.length}, got ${bundlePageSources.length}`);
267
+ }
268
+ for (const page of pageRegistry.pages || []) {
269
+ if (!page.id || !page.route || !page.title || !page.category || !page.sourcePath || !page.digest || !page.markdown) {
270
+ throw new Error(`page-registry.json page is incomplete: ${page.sourcePath || page.id || "<unknown>"}`);
271
+ }
272
+ }
273
+ if (!pageRegistry.pages?.some((page) => page.sourcePath === "actions/promote-buildchain-ref/README.md")) {
274
+ throw new Error("page-registry.json must include action manuals");
275
+ }
276
+ if (!pageRegistry.pages?.some((page) => page.sourcePath === "packages/core/README.md" && page.category === "api")) {
277
+ throw new Error("page-registry.json must include Node API overview");
278
+ }
170
279
  for (const requiredSnippet of [
171
280
  "createBuildchainKfdClaimRegistry",
172
281
  "createBuildchainKfd1Witness",
@@ -10,11 +10,16 @@ import {
10
10
  } from "../packages/core/buildchain-kfd-claims.js";
11
11
 
12
12
  const SITE_BUNDLE_CONTRACT = "kungfu-buildchain-site-bundle";
13
+ const README_PATH = "README.md";
13
14
  const root = path.resolve(import.meta.dirname, "..");
14
15
  const outputDir = path.join(root, "dist", "site");
15
16
 
17
+ function readText(rel) {
18
+ return fs.readFileSync(path.join(root, rel), "utf8");
19
+ }
20
+
16
21
  function readJson(rel) {
17
- return JSON.parse(fs.readFileSync(path.join(root, rel), "utf8"));
22
+ return JSON.parse(readText(rel));
18
23
  }
19
24
 
20
25
  function writeJson(filePath, value) {
@@ -37,6 +42,72 @@ function existingJson(filePath) {
37
42
  return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
38
43
  }
39
44
 
45
+ function normalizeMarkdown(value) {
46
+ return String(value || "").replace(/\r\n/g, "\n").trim();
47
+ }
48
+
49
+ function paragraphBlocks(markdown) {
50
+ return normalizeMarkdown(markdown)
51
+ .split(/\n{2,}/)
52
+ .map((block) => block.replace(/\n/g, " ").trim())
53
+ .filter(Boolean);
54
+ }
55
+
56
+ function parseReadme(markdown) {
57
+ const content = normalizeMarkdown(markdown);
58
+ const titleMatch = content.match(/^#\s+(.+)$/m);
59
+ if (!titleMatch) {
60
+ throw new Error(`${README_PATH} must start with an H1 title`);
61
+ }
62
+
63
+ const headingPattern = /^##\s+(.+)$/gm;
64
+ const headings = [];
65
+ let match;
66
+ while ((match = headingPattern.exec(content))) {
67
+ headings.push({
68
+ title: match[1].trim(),
69
+ index: match.index,
70
+ bodyStart: headingPattern.lastIndex,
71
+ });
72
+ }
73
+
74
+ const sections = {};
75
+ for (let index = 0; index < headings.length; index += 1) {
76
+ const current = headings[index];
77
+ const next = headings[index + 1];
78
+ sections[current.title] = content.slice(current.bodyStart, next ? next.index : content.length).trim();
79
+ }
80
+
81
+ const intro = content.slice(titleMatch[0].length, headings[0]?.index ?? content.length).trim();
82
+ return {
83
+ title: titleMatch[1].trim(),
84
+ intro,
85
+ sections,
86
+ };
87
+ }
88
+
89
+ function readmeSection(readme, heading) {
90
+ const body = readme.sections[heading];
91
+ if (!body) {
92
+ throw new Error(`${README_PATH} missing required section: ${heading}`);
93
+ }
94
+ return body;
95
+ }
96
+
97
+ function homepageSection({ readme, id, heading, title = heading, role, priority, presentation, firstScreen = false }) {
98
+ return {
99
+ id,
100
+ sourcePath: README_PATH,
101
+ sourceHeading: heading,
102
+ title,
103
+ renderRole: role,
104
+ homepagePriority: priority,
105
+ defaultPresentation: presentation,
106
+ includeInFirstScreen: firstScreen,
107
+ markdown: normalizeMarkdown(readmeSection(readme, heading)),
108
+ };
109
+ }
110
+
40
111
  function docEntry(id, title, pathName, plane) {
41
112
  return {
42
113
  id,
@@ -48,10 +119,102 @@ function docEntry(id, title, pathName, plane) {
48
119
  };
49
120
  }
50
121
 
122
+ function slugify(value) {
123
+ return String(value || "")
124
+ .toLowerCase()
125
+ .replace(/[^a-z0-9]+/g, "-")
126
+ .replace(/^-+|-+$/g, "") || "page";
127
+ }
128
+
129
+ function markdownTitle(markdown, fallback) {
130
+ const match = normalizeMarkdown(markdown).match(/^#\s+(.+)$/m);
131
+ return match ? match[1].trim() : fallback;
132
+ }
133
+
134
+ function markdownHeadings(markdown) {
135
+ const headings = [];
136
+ const pattern = /^(#{1,4})\s+(.+)$/gm;
137
+ let match;
138
+ while ((match = pattern.exec(normalizeMarkdown(markdown)))) {
139
+ headings.push({
140
+ level: match[1].length,
141
+ title: match[2].trim(),
142
+ anchor: slugify(match[2]),
143
+ });
144
+ }
145
+ return headings;
146
+ }
147
+
148
+ function listMarkdownFiles(dir) {
149
+ const absoluteDir = path.join(root, dir);
150
+ if (!fs.existsSync(absoluteDir)) return [];
151
+ return fs.readdirSync(absoluteDir)
152
+ .filter((name) => name.endsWith(".md"))
153
+ .sort()
154
+ .map((name) => `${dir}/${name}`);
155
+ }
156
+
157
+ function immediateReadmes(dir) {
158
+ const absoluteDir = path.join(root, dir);
159
+ if (!fs.existsSync(absoluteDir)) return [];
160
+ return fs.readdirSync(absoluteDir, { withFileTypes: true })
161
+ .filter((entry) => entry.isDirectory())
162
+ .map((entry) => `${dir}/${entry.name}/README.md`)
163
+ .filter((relPath) => fs.existsSync(path.join(root, relPath)))
164
+ .sort();
165
+ }
166
+
167
+ function sitePage(relPath, category, routePrefix, extra = {}) {
168
+ const markdown = normalizeMarkdown(readText(relPath));
169
+ const baseName = path.basename(relPath, path.extname(relPath));
170
+ const parentName = path.basename(path.dirname(relPath));
171
+ const slug = extra.slug || (baseName.toLowerCase() === "readme" ? slugify(parentName) : slugify(baseName));
172
+ const route = extra.route || `${routePrefix}/${slug}`;
173
+ const title = markdownTitle(markdown, extra.title || slug);
174
+ return {
175
+ id: extra.id || `${category}:${slug}`,
176
+ title,
177
+ route,
178
+ category,
179
+ sourcePath: relPath,
180
+ digest: sha256File(relPath),
181
+ headings: markdownHeadings(markdown),
182
+ markdown,
183
+ ...extra,
184
+ };
185
+ }
186
+
187
+ function buildSitePages() {
188
+ const docsPages = listMarkdownFiles("docs").map((relPath) => sitePage(relPath, "manual", "/docs"));
189
+ const actionPages = immediateReadmes("actions").map((relPath) => sitePage(relPath, "action", "/actions"));
190
+ const fixturePages = immediateReadmes("fixtures").map((relPath) => sitePage(relPath, "fixture", "/fixtures"));
191
+ const apiPages = [
192
+ sitePage("packages/core/README.md", "api", "/api", {
193
+ id: "api:node-package",
194
+ slug: "node-package",
195
+ route: "/api/node-package",
196
+ }),
197
+ ];
198
+ return [
199
+ sitePage(README_PATH, "overview", "/", {
200
+ id: "overview:home",
201
+ slug: "home",
202
+ route: "/",
203
+ title: "Buildchain",
204
+ }),
205
+ ...docsPages,
206
+ ...actionPages,
207
+ ...apiPages,
208
+ ...fixturePages,
209
+ ].sort((a, b) => a.route.localeCompare(b.route));
210
+ }
211
+
51
212
  function buildSiteBundle() {
52
213
  const packageJson = readJson("package.json");
53
214
  const inventory = readJson("tests/buildchain-inventory.json");
215
+ const readme = parseReadme(readText(README_PATH));
54
216
  const docs = BUILDCHAIN_AGENT_MANUALS.map((manual) => docEntry(manual.id, manual.title, manual.path, manual.plane));
217
+ const pages = buildSitePages();
55
218
 
56
219
  const cliRegistry = {
57
220
  schemaVersion: 1,
@@ -98,10 +261,29 @@ function buildSiteBundle() {
98
261
  "docs/release-passport.md",
99
262
  "docs/publish-transaction.md",
100
263
  "docs/site-bundle-contract.md",
264
+ "docs/runtime-train-validation.md",
265
+ "docs/consumer-issue-reporting.md",
266
+ "docs/ownership.md",
267
+ "docs/migration-inventory.md",
101
268
  ],
102
269
  guidance: "Agent consumers should use this registry to find packaged Buildchain manuals before inferring behavior from workflow snippets or release notes.",
103
270
  };
104
271
 
272
+ const pageRegistry = {
273
+ schemaVersion: 1,
274
+ contract: "kungfu-buildchain-site-page-registry",
275
+ package: packageJson.name,
276
+ sourceOfTruth: "npm package @kungfu-tech/buildchain/dist/site/page-registry.json",
277
+ rendererBoundary: {
278
+ contentOwner: "Buildchain",
279
+ rendererOwner: "site repository",
280
+ rule: "Site repositories render these markdown pages and metadata; they do not maintain separate Buildchain product copy.",
281
+ },
282
+ pageCount: pages.length,
283
+ categories: [...new Set(pages.map((page) => page.category))].sort(),
284
+ pages,
285
+ };
286
+
105
287
  const nodeApiRegistry = {
106
288
  schemaVersion: 1,
107
289
  contract: "kungfu-buildchain-node-api-registry",
@@ -137,6 +319,7 @@ function buildSiteBundle() {
137
319
  { id: "validate-config", path: "actions/validate-config", status: "active" },
138
320
  { id: "run-lifecycle", path: "actions/run-lifecycle", status: "active" },
139
321
  { id: "promote-buildchain-ref", path: "actions/promote-buildchain-ref", status: "active" },
322
+ { id: "report-buildchain-issue", path: "actions/report-buildchain-issue", status: "active" },
140
323
  ],
141
324
  };
142
325
 
@@ -196,6 +379,7 @@ function buildSiteBundle() {
196
379
  site: [
197
380
  "buildchain-site.json",
198
381
  "site-manifest.json",
382
+ "page-registry.json",
199
383
  "cli-registry.json",
200
384
  "manual-registry.json",
201
385
  "node-api-registry.json",
@@ -264,8 +448,14 @@ function buildSiteBundle() {
264
448
  versionSource: "package.json#version",
265
449
  },
266
450
  entrypoint: "buildchain-site.json",
451
+ source: {
452
+ homepageTextSource: README_PATH,
453
+ docsMap: "docs/MAP.md",
454
+ siteFactsDir: "dist/site",
455
+ },
267
456
  docs,
268
457
  facts: [
458
+ "page-registry.json",
269
459
  "cli-registry.json",
270
460
  "manual-registry.json",
271
461
  "node-api-registry.json",
@@ -284,6 +474,7 @@ function buildSiteBundle() {
284
474
  contract: "kungfu-buildchain-site-agent-index",
285
475
  readOrder: [
286
476
  "site-manifest.json",
477
+ "page-registry.json",
287
478
  "product-mechanism.json",
288
479
  "release-model.json",
289
480
  "cli-registry.json",
@@ -297,22 +488,141 @@ function buildSiteBundle() {
297
488
  instruction: "Use this bundle as the package-owned fact source for Buildchain pages. Do not infer current release mechanics from prose alone.",
298
489
  };
299
490
 
491
+ const homepageSections = [
492
+ homepageSection({
493
+ readme,
494
+ id: "install-and-verify",
495
+ heading: "Install and Verify",
496
+ role: "primary",
497
+ priority: 10,
498
+ presentation: "release-passport-install",
499
+ firstScreen: true,
500
+ }),
501
+ homepageSection({
502
+ readme,
503
+ id: "use-buildchain",
504
+ heading: "Use Buildchain",
505
+ role: "primary",
506
+ priority: 20,
507
+ presentation: "workflow-surface-list",
508
+ firstScreen: true,
509
+ }),
510
+ homepageSection({
511
+ readme,
512
+ id: "release-model",
513
+ heading: "Release Model",
514
+ role: "primary",
515
+ priority: 30,
516
+ presentation: "release-model-table",
517
+ }),
518
+ homepageSection({
519
+ readme,
520
+ id: "toolkit-observability",
521
+ heading: "Toolkit Observability",
522
+ role: "support",
523
+ priority: 40,
524
+ presentation: "toolkit-example",
525
+ }),
526
+ homepageSection({
527
+ readme,
528
+ id: "site-fact-source",
529
+ heading: "Site Fact Source",
530
+ role: "support",
531
+ priority: 50,
532
+ presentation: "site-fact-source",
533
+ }),
534
+ ];
535
+
536
+ const rendererContract = {
537
+ ...homepageSection({
538
+ readme,
539
+ id: "homepage-content-contract",
540
+ heading: "Homepage Content Contract",
541
+ role: "renderer-contract",
542
+ priority: 90,
543
+ presentation: "developer-note",
544
+ }),
545
+ renderAsHomepageContent: false,
546
+ note: "This is a machine/renderer contract for site implementers. It should not be rendered as ordinary homepage content.",
547
+ };
548
+
300
549
  const siteBundle = {
301
550
  schemaVersion: 1,
302
551
  contract: SITE_BUNDLE_CONTRACT,
303
552
  product: siteManifest.product,
304
553
  package: siteManifest.package,
554
+ source: {
555
+ package: packageJson.name,
556
+ homepageTextSource: README_PATH,
557
+ docsMap: "docs/MAP.md",
558
+ manualRegistry: "manual-registry.json",
559
+ siteFactsDir: "dist/site",
560
+ },
561
+ routes: {
562
+ home: "/",
563
+ docsPattern: "/docs/{id}",
564
+ llms: "/llms.txt",
565
+ manifest: "/manifest.json",
566
+ },
305
567
  sourceOfTruth: "npm package @kungfu-tech/buildchain/dist/site",
306
568
  humanFirst: true,
307
569
  agentFirst: true,
308
570
  entrypoints: siteManifest.facts.concat(["site-manifest.json"]),
571
+ pages,
572
+ pageRegistry: {
573
+ path: "page-registry.json",
574
+ contract: pageRegistry.contract,
575
+ pageCount: pageRegistry.pageCount,
576
+ categories: pageRegistry.categories,
577
+ },
578
+ homepage: {
579
+ title: readme.title,
580
+ lead: paragraphBlocks(readme.intro)[0] || "",
581
+ mechanismSummary: paragraphBlocks(readme.intro).slice(1),
582
+ sections: homepageSections,
583
+ displayPlan: {
584
+ firstScreen: {
585
+ include: ["title", "lead", "install-and-verify", "use-buildchain"],
586
+ maxPrimarySections: 2,
587
+ note: "The first viewport should establish Buildchain identity, release-passport trust, and the primary reusable workflow entrypoint without rendering implementation notes.",
588
+ },
589
+ primary: ["install-and-verify", "use-buildchain", "release-model"],
590
+ support: ["toolkit-observability", "site-fact-source"],
591
+ rendererContract: ["homepage-content-contract"],
592
+ },
593
+ rendererContract,
594
+ },
309
595
  docs,
310
596
  releaseModel,
597
+ renderingBoundary: {
598
+ ownedByBuildchain: [
599
+ "homepage title and text",
600
+ "homepage section projection from README.md",
601
+ "complete markdown page registry for Buildchain public docs, action manuals, Node API overview, and fixtures",
602
+ "release model facts",
603
+ "workflow and action registries",
604
+ "CLI command registry",
605
+ "manual and Node API registries",
606
+ "KFD claim registry",
607
+ "release-passport evidence vocabulary",
608
+ ],
609
+ ownedBySite: [
610
+ "HTML structure",
611
+ "CSS",
612
+ "responsive layout",
613
+ "navigation layout",
614
+ "visual assets",
615
+ "decorative images",
616
+ "markdown-to-HTML renderer",
617
+ "section presentation and progressive disclosure within Buildchain displayPlan constraints",
618
+ ],
619
+ },
311
620
  };
312
621
 
313
622
  return {
314
623
  "buildchain-site.json": siteBundle,
315
624
  "site-manifest.json": siteManifest,
625
+ "page-registry.json": pageRegistry,
316
626
  "cli-registry.json": cliRegistry,
317
627
  "manual-registry.json": manualRegistry,
318
628
  "node-api-registry.json": nodeApiRegistry,