feat(tooling): pre-ship approved library traces in optional core generator templates

Each of the five optional core package generators (events, realtime, audit,
trpc, ui) now copies pre-written decision: approved trace files into
docs/library-decisions/ at scaffold time, covering every direct runtime
dependency of that core package.

This prevents a pre-commit gate failure the first time a developer runs
pnpm turbo gen core-package <name> — the generator is the policy-compliant
path, so the traces land by construction.

- Added docs/library-decisions/*.md.hbs trace files under each of the five
  core-package template directories (15 files total)
- Updated generator config to emit traces into workspace docs/library-decisions/
  via a second emitTemplateTree call per core package
- Updated all five __snapshots__/core-package/*.snapshot.json to include the
  new trace file entries
- Added verify-doc-shas.test.ts to pin SHA256 hashes of all 15 trace templates
  so snapshot and file content cannot drift independently

ADR refs: events→ADR-015, realtime→ADR-016, audit→ADR-018;
trpc and ui cite closest ADR or null where no specific ADR exists.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 09:34:17 +00:00
parent 090410e9c3
commit b1e2e8a788
23 changed files with 1248 additions and 10 deletions

View File

@@ -1,4 +1,10 @@
import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
import {
existsSync,
readFileSync,
writeFileSync,
readdirSync,
statSync,
} from "node:fs";
import { join, relative } from "node:path";
import type { PlopTypes } from "@turbo/gen";
@@ -61,8 +67,7 @@ export function splicePluginRulesAt(
}
const after = source.slice(idx + anchor.length);
if (after.trimStart().startsWith(block.trim())) return; // idempotent
const updated =
source.slice(0, idx + anchor.length) + "\n" + block + after;
const updated = source.slice(0, idx + anchor.length) + "\n" + block + after;
writeFileSync(filePath, updated);
}
@@ -97,7 +102,10 @@ export function addBoundariesEntry(
}
const modeFragment = opts.mode ? `, mode: "${opts.mode}"` : "";
const newEntry = ` { type: "core", pattern: "${packagePath}"${modeFragment} },\n`;
const updated = source.replace(wildcardLine[0], `\n${newEntry}${wildcardLine[1]}`);
const updated = source.replace(
wildcardLine[0],
`\n${newEntry}${wildcardLine[1]}`,
);
writeFileSync(baseJsPath, updated);
}
@@ -106,11 +114,13 @@ export function addBoundariesEntry(
* file, returns a plop `add` action that emits the file (without .hbs
* extension) at <destPrefix>/<relative-path>. The actions are sorted so
* directory creation is deterministic.
*
* Set `opts.force` to overwrite existing files (idempotent re-runs).
*/
export function emitTemplateTree(
srcPrefix: string,
destPrefix: string,
opts: { templatesRoot?: string } = {},
opts: { templatesRoot?: string; force?: boolean } = {},
): PlopTypes.AddActionConfig[] {
// The templates directory is resolved in priority order:
// 1. opts.templatesRoot — test injection (temp directory)
@@ -120,13 +130,18 @@ export function emitTemplateTree(
if (opts.templatesRoot) {
root = opts.templatesRoot;
} else {
const fromRepoRoot = join(process.cwd(), "turbo", "generators", "templates");
const fromRepoRoot = join(
process.cwd(),
"turbo",
"generators",
"templates",
);
const fromGeneratorsDir = join(process.cwd(), "templates");
root = existsSync(fromRepoRoot) ? fromRepoRoot : fromGeneratorsDir;
}
const srcRoot = join(root, srcPrefix);
const out: PlopTypes.AddActionConfig[] = [];
walkHbs(srcRoot, srcRoot, srcPrefix, destPrefix, out);
walkHbs(srcRoot, srcRoot, srcPrefix, destPrefix, out, opts.force ?? false);
out.sort((a, b) => (a.path ?? "").localeCompare(b.path ?? ""));
return out;
}
@@ -137,11 +152,12 @@ function walkHbs(
srcPrefix: string,
destPrefix: string,
out: PlopTypes.AddActionConfig[],
force: boolean,
): void {
for (const name of readdirSync(dir)) {
const full = join(dir, name);
if (statSync(full).isDirectory()) {
walkHbs(topRoot, full, srcPrefix, destPrefix, out);
walkHbs(topRoot, full, srcPrefix, destPrefix, out, force);
continue;
}
if (!name.endsWith(".hbs")) continue;
@@ -149,8 +165,12 @@ function walkHbs(
out.push({
type: "add",
path: join(destPrefix, rel).replace(/\\/g, "/"),
templateFile: join("templates", srcPrefix, relative(topRoot, full)).replace(/\\/g, "/"),
templateFile: join(
"templates",
srcPrefix,
relative(topRoot, full),
).replace(/\\/g, "/"),
force,
} as PlopTypes.AddActionConfig);
}
}