/*
RFC-0221: `handoff.pack` — produce a thin, version-stamped site bundle for internal
transfer. Carries the authored site only (content + whitelisted config) plus provenance, lock,
and an authored/derived manifest. No packages/, node_modules/, dist/, or *.generated.* files.
- Do not copy packages/, node_modules/, dist/, or any *.generated.* file.
- Do not mutate the source workspace — pack is read-only on the source.
- RFC-0221: initial thin-bundle packer.
- RFC-0221: Compass-complete authored partition (consumed-subset capability narrowing is follow-up).
- RFC-1249: wire werkstatt.commands.validate into packages.check + retire CMD-OUTPUT debt
*/
import path from "node:path";
import {
handoffLockSchema,
handoffManifestSchema,
type HandoffCapability,
type HandoffManifestEntry,
} from "@warpgogol/werkstatt-engine/schemas";
import type {
KernelCommandInput,
KernelCommandResult,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-engine/kernel";
import {
hashFile,
resolveCurrentEcosystem,
resolvePackagesHash,
resolvePlatformSemanticHash,
} from "./bundle-io.ts";
import { allMigratorIds } from "../migrators/registry.ts";
import { resolveAuthoredFiles } from "./authored-set.ts";
import { buildValidationPack } from "./validation-pack.ts";
import { createDefaultIO, type WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
const LOCK_SCHEMA_VERSION = "1.0.0";
const ZERO_HASH = `sha256:${"0".repeat(64)}`;
async function walkFiles(root: string): Promise {
const out: string[] = [];
async function rec(dir: string, io: WorkspaceIO = createDefaultIO().io): Promise {
const entries = await io.readdir(dir);
for (const e of entries) {
const abs = path.join(dir, e.name);
if (e.isDirectory) await rec(abs);
else out.push(abs);
}
}
await rec(root);
return out;
}
async function highestRfcId(
workspaceRoot: string,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
try {
const files = await io.readdir(path.join(workspaceRoot, "docs", "rfcs"));
let max = 0;
for (const f of files) {
const m = /^RFC-(\d{4})/.exec(f.name);
if (m) max = Math.max(max, Number(m[1]));
}
return `RFC-${String(max).padStart(4, "0")}`;
} catch {
return "RFC-0000";
}
}
/**
* Snapshot consumed capabilities. Foundation slice: conservatively snapshots the whole
* uni.registry component surface (over-inclusion is safe for the diff — unused entries
* stay green). Narrowing to the app's resolved component graph is a follow-up.
*/
async function snapshotCapabilities(
workspaceRoot: string,
io: WorkspaceIO = createDefaultIO().io,
): Promise {
try {
const raw = await io.readFile(path.join(workspaceRoot, "uni.registry.yaml"));
const parsed = JSON.parse(raw) as {
entries?: { id: string; semanticId?: string; version: string; intent?: string[] }[];
};
return (parsed.entries ?? [])
.filter((e) => e.id)
.map((e) => ({
id: e.id,
semanticId: e.semanticId ?? e.id,
version: e.version ?? "0.0.0",
intent: e.intent ?? [],
}));
} catch {
return [];
}
}
export interface HandoffPackData {
bundleDir: string;
site: string;
fileCount: number;
}
function resolveSiteName(input: KernelCommandInput): string {
const flag = input.flags["site"];
const raw = typeof flag === "string" ? flag : undefined;
if (!raw) {
throw new Error("[handoff.pack] requires the site name — use --site ");
}
return raw;
}
export async function runHandoffPack(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise> {
const { workspaceRoot, logger, dryRun } = context;
const siteName = resolveSiteName(input);
const sourceSiteDir = path.resolve(workspaceRoot, "apps", siteName);
try {
await context.io.stat(sourceSiteDir);
} catch {
throw new Error(`[handoff.pack] no site at apps/${siteName}`);
}
const bundleDir = path.resolve(workspaceRoot, "..", "handoff", siteName);
const siteDir = path.join(bundleDir, "site");
const [{ version, commit }, platformSemanticHash, packagesHash, rfcHead, capabilities] =
await Promise.all([
resolveCurrentEcosystem(workspaceRoot),
resolvePlatformSemanticHash(workspaceRoot),
resolvePackagesHash(workspaceRoot),
highestRfcId(workspaceRoot),
snapshotCapabilities(workspaceRoot),
]);
// Build provenance facts from the passport when available.
let systemHash = ZERO_HASH;
let buildCommit = commit === "unknown" ? "0000000" : commit;
try {
const passportRaw = await context.io.readFile(
path.join(sourceSiteDir, "dist", ".well-known", "cosmic-passport.json"),
);
const passport = JSON.parse(passportRaw) as {
composition?: { systemHash?: string };
provenance?: { commitSha?: string };
};
if (passport.composition?.systemHash) systemHash = passport.composition.systemHash;
if (passport.provenance?.commitSha) buildCommit = passport.provenance.commitSha;
} catch {
// no passport built — provenance falls back to placeholders
}
const lock = handoffLockSchema.parse({
schemaVersion: LOCK_SCHEMA_VERSION,
site: siteName,
packedAt: new Date().toISOString(),
ecosystem: {
version,
commit: commit === "unknown" ? "0000000" : commit,
rfcHead,
platformSemanticHash,
packagesHash,
},
migratorCursor: allMigratorIds(),
capabilities,
build: { systemHash, commitSha: buildCommit },
});
// Compass-complete authored partition: what travels (source of truth) vs what regen re-derives.
const partition = await resolveAuthoredFiles(workspaceRoot, siteName, input);
logger.section("[handoff.pack] plan");
logger.info(`App: ${siteName}`);
logger.info(`Target: ${bundleDir}`);
logger.info(`Stamp: ecosystem ${version} | ${rfcHead} | ${capabilities.length} capabilities`);
logger.info(
`Partition: ${partition.authored.length} authored, ${partition.excluded.length} excluded (regenerated)`,
);
if (dryRun) {
return {
data: { bundleDir, site: siteName, fileCount: partition.authored.length },
exitCode: 0,
summary: `[handoff.pack] dry-run — would pack ${partition.authored.length} authored file(s) for ${siteName} → ${bundleDir}`,
};
}
// Clear and rebuild the bundle (preserve .git so a per-client repo survives).
try {
for (const entry of await context.io.readdir(bundleDir)) {
if (entry.name === ".git") continue;
await context.io.rm(path.join(bundleDir, entry.name), { recursive: true });
}
} catch {
await context.io.mkdir(bundleDir);
}
await context.io.mkdir(siteDir);
// Copy the Compass-complete authored partition (one file at a time).
for (const rel of partition.authored) {
const src = path.join(sourceSiteDir, rel);
const dest = path.join(siteDir, rel);
await context.io.mkdir(path.dirname(dest));
await context.io.copyFile(src, dest);
}
// Snapshot provenance.
const provDir = path.join(bundleDir, "provenance");
await context.io.mkdir(provDir);
for (const [from, to] of [
[
path.join(sourceSiteDir, "dist", ".well-known", "cosmic-passport.json"),
"cosmic-passport.json",
],
[path.join(sourceSiteDir, "provenance", "coverage-ledger.yaml"), "coverage-ledger.yaml"],
] as const) {
try {
await context.io.copyFile(from, path.join(provDir, to));
} catch {
/* absent — skip */
}
}
// Golden validation pack — significant properties of the last build (for absorb to diff against).
const validationDir = path.join(bundleDir, "validation");
await context.io.mkdir(validationDir);
const golden = await buildValidationPack(sourceSiteDir);
await context.io.writeFile(
path.join(validationDir, "pack.json"),
`${JSON.stringify(golden, null, 2)}\n`,
);
try {
const outDir = await context.io
.stat(path.join(sourceSiteDir, "dist", "client"))
.then(() => "dist/client")
.catch(() => "dist");
await context.io.copyFile(
path.join(sourceSiteDir, outDir, "sitemap.xml"),
path.join(validationDir, "sitemap.snapshot.xml"),
);
} catch {
/* no sitemap — skip snapshot */
}
if (golden.empty) {
logger.warn("[handoff.pack] no build output (dist/) — golden validation pack is empty");
} else {
logger.info(
`Validation: ${golden.routes.length} routes, scores ${golden.scores ? "captured" : "absent"}`,
);
}
// Build the manifest over everything actually placed in site/.
const siteFiles = await walkFiles(siteDir);
const entries: HandoffManifestEntry[] = [];
for (const abs of siteFiles) {
const rel = path.relative(bundleDir, abs).replace(/\\/g, "/");
entries.push({ path: rel, kind: "authored", hash: await hashFile(abs) });
}
const manifest = handoffManifestSchema.parse({
schemaVersion: LOCK_SCHEMA_VERSION,
site: siteName,
entries,
});
await context.io.writeFile(
path.join(bundleDir, "handoff-lock.json"),
`${JSON.stringify(lock, null, 2)}\n`,
);
await context.io.writeFile(
path.join(bundleDir, "handoff-manifest.json"),
`${JSON.stringify(manifest, null, 2)}\n`,
);
logger.success(`[handoff.pack] packed ${entries.length} authored file(s) → ${bundleDir}`);
return {
data: { bundleDir, site: siteName, fileCount: entries.length },
exitCode: 0,
summary: `[handoff.pack] ${siteName}: ${entries.length} authored files → ${bundleDir}`,
nextSteps: [
{
action: `Validate the bundle: pnpm exec werkstatt run handoff.validate --bundle ${bundleDir}`,
kind: "optional",
},
],
};
}