/* Generates the app-local tools/kernel.config.ts wiring file — a thin delegation to the canonical site module map (RFC-1147/RFC-1148). Do not generate app src/ or public/ boilerplate. Do not discover behavior from arbitrary app code. Wiring is generated from system.md — never hand-edited. The module surface is uniform: kernel.config.ts delegates to siteModuleLoaders() (SITE_MODULE_MAP), so every site loads the same 18 modules unconditionally. RFC-1097: step 6 — compass.migrate codemod run Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order. RFC-1097: sweep — werkstatt-engine clean Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics. RFC-1147: uniform surface — wire emits only tools/kernel.config.ts delegating to siteModuleLoaders(); module templates, runtime re-export shims, and conditional module decisions deleted. RFC-1149: migrate ambient filesystem and process IO to the WorkspaceIO port across engine and shared */ import type { WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io"; import path from "node:path"; import { fileURLToPath } from "node:url"; import type { KernelCommandInput, KernelCommandResult, KernelRuntimeContext, } from "@warpgogol/werkstatt-shared/kernel"; import { discoverSiteWorkspaces } from "./discovery.ts"; import { loadSystemManifest } from "@warpgogol/werkstatt-shared/content"; import { buildGeneratedHeader, hasGeneratedMarker } from "./generated-marker.ts"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const TEMPLATES_DIR = path.join(__dirname, "templates", "wire"); type WireWarning = { file: string; message: string; }; type KernelWireResult = { command: "kernel.wire"; status: "ok" | "fail"; generated: string[]; warnings?: WireWarning[]; }; // RFC-0336: repo-relative root for wire template files, used to build the // "Edit instead:" line of the advisory header — never hand-roll the marker here. const WIRE_TEMPLATES_REPO_RELATIVE = "packages/werkstatt/src/kernel/templates/wire"; function wireContent(templateRelPath: string, siteName: string, content: string): string { const header = buildGeneratedHeader({ filePath: "tools/kernel-wire-output.ts", ownerCommand: "kernel.wire", templatePath: `${WIRE_TEMPLATES_REPO_RELATIVE}/${templateRelPath}`, site: siteName, }); return `${header}${content}`; } async function readFileIfExists(filePath: string, io: WorkspaceIO): Promise { try { return await io.readFile(filePath); } catch { return null; } } async function writeGeneratedFile( absolutePath: string, content: string, appDir: string, dryRun: boolean, warnings: WireWarning[], io: WorkspaceIO, ): Promise { const existing = await readFileIfExists(absolutePath, io); if (existing === content) { return false; } if (existing !== null && !hasGeneratedMarker(existing)) { warnings.push({ file: absolutePath, message: `"${rel(appDir, absolutePath)}" is project-specific (no GENERATED marker) — skipped.`, }); return false; } if (!dryRun) { await io.mkdir(path.dirname(absolutePath)); await io.writeFile(absolutePath, content); } return true; } function rel(appDirectory: string, absolutePath: string): string { return path.relative(appDirectory, absolutePath).replace(/\\/g, "/"); } async function readTemplate(templatePath: string, io: WorkspaceIO): Promise { return io.readFile(path.join(TEMPLATES_DIR, templatePath)); } function applyTokens(template: string, tokens: Record): string { return template.replace(/{{\s*(\w+)\s*}}/g, (_match, key) => tokens[key] ?? ""); } async function buildKernelConfigFile(appName: string, io: WorkspaceIO): Promise { return wireContent( "tools/kernel.config.template.ts", appName, applyTokens(await readTemplate("tools/kernel.config.template.ts", io), { APP_NAME: appName, }), ); } async function resolveWirePaths( input: KernelCommandInput, context: KernelRuntimeContext, ): Promise<{ appDirectory: string; srcDirectory: string; publicDirectory: string; contentDirectory: string; contentPagesDirectory: string; stylesDirectory: string; }> { if (context.site) { const appDirectory = context.site.directory; const srcDirectory = path.join(appDirectory, "src"); return { appDirectory, srcDirectory, publicDirectory: path.join(appDirectory, "public"), contentDirectory: path.join(srcDirectory, "content"), contentPagesDirectory: path.join(srcDirectory, "content", "pages"), stylesDirectory: path.join(srcDirectory, "styles"), }; } const requestedSite = typeof input.flags.site === "string" ? input.flags.site : Array.isArray(input.flags.site) ? input.flags.site[0] : undefined; const sites = await discoverSiteWorkspaces(context.workspaceRoot); const target = requestedSite ? sites.find((site) => site.name === requestedSite) : sites.length === 1 ? sites[0] : undefined; if (!target) { throw new Error( "kernel.wire requires --site when no app-scoped runtime context is available.", ); } const srcDirectory = path.join(target.directory, "src"); return { appDirectory: target.directory, srcDirectory, publicDirectory: path.join(target.directory, "public"), contentDirectory: path.join(srcDirectory, "content"), contentPagesDirectory: path.join(srcDirectory, "content", "pages"), stylesDirectory: path.join(srcDirectory, "styles"), }; } export async function runKernelWire( _input: KernelCommandInput, context: KernelRuntimeContext, ): Promise> { const paths = await resolveWirePaths(_input, context); const { manifest } = await loadSystemManifest(paths.contentDirectory, context.io); const warnings: WireWarning[] = []; const generated: string[] = []; // RFC-1147: the only emitted file — kernel.config.ts delegates to // siteModuleLoaders() (SITE_MODULE_MAP), so every site gets the same // 18-module surface unconditionally. tools/modules/ and tools/runtime/ // are no longer generated. const files: Array<{ absolutePath: string; content: string }> = [ { absolutePath: path.join(paths.appDirectory, "tools", "kernel.config.ts"), content: await buildKernelConfigFile(manifest.app, context.io), }, ]; for (const file of files) { const changed = await writeGeneratedFile( file.absolutePath, file.content, paths.appDirectory, context.dryRun ?? false, warnings, context.io, ); if (changed) { generated.push(rel(paths.appDirectory, file.absolutePath)); } } return { data: { command: "kernel.wire", status: "ok", generated, warnings: warnings.length > 0 ? warnings : undefined, }, exitCode: 0, summary: `[kernel.wire] ${generated.length} file(s) ${context.dryRun ? "would be written" : "written"}`, }; }