Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,370 @@
#!/usr/bin/env bun
/**
* cleanup-md-monorepo-refs — carefully scrub genesis/monorepo/stage metadata
* from library .md files so they read as standalone packages.
*
* Reads `.md-cleanup-todo.txt` (path|line1,line2,...), processes files whose
* matches are all in the first N lines, and removes them from the todo.
*
* Usage:
* bun scripts/cleanup-md-monorepo-refs.ts # process all
* bun scripts/cleanup-md-monorepo-refs.ts --first10 # only first-10-line files
* bun scripts/cleanup-md-monorepo-refs.ts --readme # only README.md files
* bun scripts/cleanup-md-monorepo-refs.ts --dry-run # preview
*/
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
const TODO_PATH = ".md-cleanup-todo.txt";
interface TodoItem {
path: string;
lines: number[];
}
function parseTodo(content: string): TodoItem[] {
const items: TodoItem[] = [];
for (const line of content.split("\n")) {
const pipe = line.indexOf("|");
if (pipe === -1) continue;
const path = line.slice(0, pipe).trim();
const lines = line
.slice(pipe + 1)
.split(",")
.map((s) => parseInt(s.trim(), 10))
.filter((n) => !isNaN(n));
if (path && lines.length > 0) items.push({ path, lines });
}
return items;
}
function serializeTodo(items: TodoItem[]): string {
return items.map((i) => `${i.path}|${i.lines.join(",")}`).join("\n") + "\n";
}
function maxLine(lines: number[]): number {
return Math.max(...lines);
}
/**
* Very conservative replacements. Each function receives the full text and
* returns the scrubbed text.
*/
function scrub(text: string, filePath: string): { next: string; edits: string[] } {
const edits: string[] = [];
let next = text;
// 1. Remove OpenSpec link blocks safely
if (next.includes("<!-- begin:auto-spec-link -->")) {
const before = next;
next = next.replace(
/\n?<!-- begin:auto-spec-link -->[\s\S]*?<!-- end:auto-spec-link -->\n?/g,
"\n"
);
if (next !== before) edits.push("removed OpenSpec block");
}
// 2. Remove bare "## OpenSpec" heading + following spec-link paragraph
if (/\n## OpenSpec\n/.test(next)) {
const before = next;
next = next.replace(
/\n## OpenSpec\n\nCanonical capability spec:[^\n]*\n/g,
"\n"
);
if (next !== before) edits.push("removed OpenSpec heading");
}
// 3. Remove Stage-N prefix from H1 title lines (only if line starts with # @suaveplan/... - Stage-N ...)
// Use a regex that only touches spaces/tabs, never newlines.
const h1StagePattern =
/^(# @suaveplan\/[\w-]+)[ \t]+-[ \t]+Stage-?\d+(?:\.\d+)?[ \t]+/gim;
if (h1StagePattern.test(next)) {
next = next.replace(h1StagePattern, "$1 — ");
edits.push("removed Stage from H1");
}
// 4. Remove "Stage N — " or "Stage-N " at the very start of a paragraph (after blank line or start of file)
const stagePrefixPattern =
/(^|\n\n)[ \t]*Stage-?\d+(?:\.\d+)?[ \t]*(?:—|-)[ \t]*/gi;
if (stagePrefixPattern.test(next)) {
next = next.replace(stagePrefixPattern, "$1");
edits.push("removed Stage prefix from paragraph");
}
// 5. Replace specific monorepo phrases
const phraseReplacements: { pattern: RegExp; replacement: string; label: string }[] = [
{ pattern: /\bfor the Suaveplan monorepo\b/gi, replacement: "for Suaveplan", label: "monorepo phrase" },
{ pattern: /\bthe Suaveplan monorepo\b/gi, replacement: "Suaveplan", label: "monorepo phrase" },
{ pattern: /\bthe genesis monorepo\b/gi, replacement: "Suaveplan", label: "genesis monorepo" },
{ pattern: /\bgenesis `@suaveplan\//gi, replacement: "`@suaveplan/", label: "genesis pkg prefix" },
{ pattern: /\bfor the `@suaveplan\/api` plugin foundation\b/gi, replacement: "for `@suaveplan/api`", label: "plugin foundation" },
{ pattern: /\bthe `@suaveplan\/api` plugin foundation\b/gi, replacement: "`@suaveplan/api`", label: "plugin foundation" },
{ pattern: /\bplugin foundation\b/gi, replacement: "plugin system", label: "plugin foundation" },
{ pattern: /\bGenesis Stage \d+(?:\.\d+)?[^.]*\.\s*/gi, replacement: "", label: "Genesis Stage sentence" },
{ pattern: /\bGenesis-wave package[^.]*\.\s*/gi, replacement: "", label: "Genesis-wave sentence" },
{ pattern: /\bPer design doc §\d+(?:\.\d+)*,\s+genesis\s+[^.]*\.\s*/gi, replacement: "", label: "design-doc genesis sentence" },
{ pattern: /\bgenesis (fix|package|design|crypto|wave|port|rewrite|drops?|error|monorepo)\b/gi, replacement: "$1", label: "genesis adjective" },
{ pattern: /\bStage \d+(?:\.\d+)?\s+of the umbrella plan\b/gi, replacement: "the design", label: "umbrella plan" },
{ pattern: /\bof the umbrella plan\s*\([^)]*openspec[^)]*\)\b/gi, replacement: "of the design", label: "umbrella plan" },
{ pattern: /\bOpenSpec change\b/gi, replacement: "spec change", label: "OpenSpec change" },
{ pattern: /\bOpenSpec-tracked\b/gi, replacement: "tracked", label: "OpenSpec-tracked" },
{ pattern: /\bgenesis-only\b/gi, replacement: "", label: "genesis-only" },
{ pattern: /\bgenesis original\b/gi, replacement: "original", label: "genesis original" },
{ pattern: /\bgenesis-side\b/gi, replacement: "", label: "genesis-side" },
{ pattern: /\bgenesis `EventBus/gi, replacement: "`EventBus", label: "genesis EventBus" },
{ pattern: /\bgenesis `tsconfig/gi, replacement: "`tsconfig", label: "genesis tsconfig" },
{ pattern: /\bgenesis `exactOptionalPropertyTypes/gi, replacement: "`exactOptionalPropertyTypes", label: "genesis exactOptionalPropertyTypes" },
{ pattern: /\bgenesis error ecosystem/gi, replacement: "error ecosystem", label: "genesis error ecosystem" },
{ pattern: /\bgenesis dependency graph/gi, replacement: "dependency graph", label: "genesis dependency graph" },
{ pattern: /\bgenesis stack/gi, replacement: "stack", label: "genesis stack" },
{ pattern: /\bgenesis hardening/gi, replacement: "hardening", label: "genesis hardening" },
{ pattern: /\bgenesis family/gi, replacement: "family", label: "genesis family" },
{ pattern: /\bgenesis cache family/gi, replacement: "cache family", label: "genesis cache family" },
{ pattern: /\bgenesis audio family/gi, replacement: "audio family", label: "genesis audio family" },
{ pattern: /\bgenesis mesh/gi, replacement: "mesh", label: "genesis mesh" },
{ pattern: /\bthe monorepo\b/gi, replacement: "the project", label: "the monorepo" },
{ pattern: /\bthe rest of the monorepo\b/gi, replacement: "the rest of the project", label: "rest of monorepo" },
{ pattern: /\bmonorepo-wide\b/gi, replacement: "project-wide", label: "monorepo-wide" },
{ pattern: /\bmonorepo manifest\b/gi, replacement: "project manifest", label: "monorepo manifest" },
{ pattern: /\bmonorepo Python-tier story\b/gi, replacement: "Python tooling story", label: "monorepo Python" },
{ pattern: /\bbroader monorepo\b/gi, replacement: "broader project", label: "broader monorepo" },
{ pattern: /\bextends the monorepo/gi, replacement: "extends the project", label: "extends monorepo" },
{ pattern: /\bExtending the monorepo/gi, replacement: "Extending the project", label: "extends monorepo" },
{ pattern: /\bthe genesis `audio-decode-\*\*` family/gi, replacement: "the `audio-decode-*` family", label: "genesis audio-decode family" },
{ pattern: /\bthe first downstream consumer is `@suaveplan\/audio-ir-loader`\./gi, replacement: "", label: "first downstream consumer" },
{ pattern: /\bgenesis surface\b/gi, replacement: "surface", label: "genesis surface" },
{ pattern: /\bgenesis-canonical\b/gi, replacement: "canonical", label: "genesis-canonical" },
{ pattern: /\bgenesis preserves\b/gi, replacement: "preserves", label: "genesis preserves" },
{ pattern: /\bgenesis cause-chain\b/gi, replacement: "cause-chain", label: "genesis cause-chain" },
{ pattern: /\bgenesis codec plugin\b/gi, replacement: "codec plugin", label: "genesis codec plugin" },
{ pattern: /\bgenesis voice domain conventions\b/gi, replacement: "voice domain conventions", label: "genesis voice domain conventions" },
{ pattern: /\bgenesis voice\b/gi, replacement: "voice", label: "genesis voice" },
{ pattern: /\bgenesis rule\b/gi, replacement: "rule", label: "genesis rule" },
{ pattern: /\bgenesis-added\b/gi, replacement: "added", label: "genesis-added" },
{ pattern: /\bgenesis exposes\b/gi, replacement: "exposes", label: "genesis exposes" },
{ pattern: /\bgenesis WebSocket\b/gi, replacement: "WebSocket", label: "genesis WebSocket" },
{ pattern: /\bgenesis OpenSpec\b/gi, replacement: "OpenSpec", label: "genesis OpenSpec" },
{ pattern: /\bgenesis-wide convention\b/gi, replacement: "convention", label: "genesis-wide convention" },
{ pattern: /\bgenesis packages\b/gi, replacement: "packages", label: "genesis packages" },
{ pattern: /\bgenesis packages? that\b/gi, replacement: "packages that", label: "genesis packages that" },
{ pattern: /\bgenesis (\w+ing)\b/gi, replacement: "$1", label: "genesis ...ing" },
{ pattern: /\bGenesis includes it\b/gi, replacement: "It is included", label: "Genesis includes it" },
{ pattern: /\bGenesis treats it\b/gi, replacement: "It is treated", label: "Genesis treats it" },
{ pattern: /\bGenesis treats\b/gi, replacement: "The package treats", label: "Genesis treats" },
{ pattern: /\bGenesis's preferred\b/gi, replacement: "Preferred", label: "Genesis's preferred" },
{ pattern: /\bGenesis preserves\b/gi, replacement: "Preserves", label: "Genesis preserves" },
{ pattern: /\bGenesis closes\b/gi, replacement: "This package closes", label: "Genesis closes" },
{ pattern: /\bmonorepo with dozens of packages\b/gi, replacement: "package ecosystem", label: "monorepo with dozens" },
{ pattern: /\bmonorepo's shared code space\b/gi, replacement: "shared code space", label: "monorepo's shared code" },
{ pattern: /\bmonorepo\. Wraps\b/gi, replacement: ". Wraps", label: "monorepo. Wraps" },
{ pattern: /\bthe wider monorepo\b/gi, replacement: "the wider ecosystem", label: "wider monorepo" },
{ pattern: /\bopenspec-conformance\b/gi, replacement: "conformance", label: "openspec-conformance" },
{ pattern: /\bthe other genesis\b/gi, replacement: "the other", label: "the other genesis" },
{ pattern: /\bthe genesis (lint|docs-check|storage|lint profile|cause-chain|WebRTC|WebSocket|codec|voice|surface)\b/gi, replacement: "the $1", label: "the genesis ..." },
{ pattern: /\bgenesis (lint profile|docs-check rule|storage family|storage backend|HTTP-stat parser|tier package)\b/gi, replacement: "$1", label: "genesis ..." },
{ pattern: /\bgenesis-app\b/gi, replacement: "app", label: "genesis-app" },
{ pattern: /\bgenesis bans\b/gi, replacement: "the test suite bans", label: "genesis bans" },
{ pattern: /\bgenesis-tier\b/gi, replacement: "", label: "genesis-tier" },
{ pattern: /\bgenesis-specific\b/gi, replacement: "specific", label: "genesis-specific" },
{ pattern: /\bGenesis-specific\b/gi, replacement: "Specific", label: "Genesis-specific" },
{ pattern: /\bGenesis sits\b/gi, replacement: "This package sits", label: "Genesis sits" },
{ pattern: /\bgithub\.com\/SuavePlan\/genesis\b/gi, replacement: "github.com/SuavePlan/suaveplan", label: "github.com/SuavePlan/genesis" },
{ pattern: /\bgenesis (\w+ing)\b/gi, replacement: "$1", label: "genesis ...ing" },
{ pattern: /\bgenesis project rules\b/gi, replacement: "project rules", label: "genesis project rules" },
{ pattern: /\bgenesis convention\b/gi, replacement: "convention", label: "genesis convention" },
{ pattern: /\bgenesis 300-line hard cap\b/gi, replacement: "300-line hard cap", label: "genesis 300-line" },
{ pattern: /\bgenesis wrapper\b/gi, replacement: "wrapper", label: "genesis wrapper" },
{ pattern: /\bgenesis pre-1\.0\b/gi, replacement: "pre-1.0", label: "genesis pre-1.0" },
{ pattern: /\bgenesis 5-files-per-feature convention\b/gi, replacement: "5-files-per-feature convention", label: "genesis 5-files" },
{ pattern: /\bPre-Genesis consumer\b/gi, replacement: "Pre-migration consumer", label: "Pre-Genesis consumer" },
{ pattern: /\bPre-genesis consumer\b/gi, replacement: "Pre-migration consumer", label: "Pre-genesis consumer" },
{ pattern: /\bgenesis Zod import rule\b/gi, replacement: "Zod import rule", label: "genesis Zod import" },
{ pattern: /\bgenesis boundary\b/gi, replacement: "boundary", label: "genesis boundary" },
{ pattern: /\bgenesis tsconfig\b/gi, replacement: "tsconfig", label: "genesis tsconfig" },
{ pattern: /\bthis monorepo\b/gi, replacement: "this project", label: "this monorepo" },
{ pattern: /\bPer the genesis\b/gi, replacement: "Per the", label: "Per the genesis" },
{ pattern: /\bgenesis (error|cause-chain|lint|docs-check|storage|codec|voice|surface|WebRTC|WebSocket)\b/gi, replacement: "$1", label: "genesis generic" },
{ pattern: /\bGenesis (error|cause-chain|lint|docs-check|storage|codec|voice|surface|WebRTC|WebSocket)\b/gi, replacement: "$1", label: "Genesis generic" },
{ pattern: /\bgenesis\b/gi, replacement: "", label: "genesis fallback" },
{ pattern: /\bGenesis\b/gi, replacement: "", label: "Genesis fallback" },
{ pattern: /\bmonorepo\b/gi, replacement: "project", label: "monorepo fallback" },
{ pattern: /\bMonorepo\b/gi, replacement: "Project", label: "Monorepo fallback" },
{ pattern: /\bstage[- ]?\d+\b/gi, replacement: "", label: "stage fallback" },
{ pattern: /\bStage[- ]?\d+\b/gi, replacement: "", label: "Stage fallback" },
{ pattern: /\bopenspec\b/gi, replacement: "specification", label: "openspec fallback" },
{ pattern: /\bOpenspec\b/gi, replacement: "Specification", label: "Openspec fallback" },
{ pattern: /\bOpenSpec\b/gi, replacement: "OpenSpec", label: "OpenSpec keep" },
{ pattern: /\bplugin foundation\b/gi, replacement: "plugin", label: "plugin foundation" },
{ pattern: /\bon genesis\b/gi, replacement: "", label: "on genesis" },
{ pattern: /\bin genesis\b/gi, replacement: "", label: "in genesis" },
{ pattern: /\bhas not yet landed on genesis\b/gi, replacement: "has not yet shipped", label: "not landed on genesis" },
{ pattern: /\bpackages not yet in genesis\b/gi, replacement: "packages not yet shipped", label: "not yet in genesis" },
{ pattern: /\bsuaveplan main, v\d+\.\d+\.\d+\) plus genesis hardening/gi, replacement: "harded", label: "suaveplan main plus genesis" },
{ pattern: /\bgenesis improvement\b/gi, replacement: "improvement", label: "genesis improvement" },
{ pattern: /\bgenesis game type/gi, replacement: "game type", label: "genesis game type" },
{ pattern: /\bgenesis `Middleware`/gi, replacement: "`Middleware`", label: "genesis Middleware" },
{ pattern: /\bgenesis `storage-uri-plugin-bundle`/gi, replacement: "`storage-uri-plugin-bundle`", label: "genesis storage-uri" },
{ pattern: /\bGenesis-original\b/gi, replacement: "Original", label: "Genesis-original" },
{ pattern: /\bgenesis-original\b/gi, replacement: "original", label: "genesis-original" },
{ pattern: /\bgenesis bootstrap landing/gi, replacement: "bootstrap landing", label: "genesis bootstrap" },
{ pattern: /\bgenesis JSX output/gi, replacement: "JSX output", label: "genesis JSX" },
{ pattern: /\bduring genesis\b/gi, replacement: "", label: "during genesis" },
{ pattern: /\bthe genesis port/gi, replacement: "the port", label: "genesis port" },
{ pattern: /\bthe genesis rewrite/gi, replacement: "the rewrite", label: "genesis rewrite" },
{ pattern: /\bthis migration/gi, replacement: "this release", label: "this migration" },
{ pattern: /\bgenesis deps\b/gi, replacement: "dependencies", label: "genesis deps" },
{ pattern: /\bgenesis `([^`]+)`/gi, replacement: "`$1`", label: "genesis backtick" },
{ pattern: /\bgenesis API-plugin/gi, replacement: "API-plugin", label: "genesis API-plugin" },
{ pattern: /\bgenesis ship\b/gi, replacement: "ship", label: "genesis ship" },
{ pattern: /\binitial genesis ship\b/gi, replacement: "initial ship", label: "initial genesis ship" },
{ pattern: /\blanding on `?genesis`?\b/gi, replacement: "shipping", label: "landing on genesis" },
{ pattern: /\bmigrated into genesis\b/gi, replacement: "being migrated", label: "migrated into genesis" },
{ pattern: /\bgenesis plugin\b/gi, replacement: "plugin", label: "genesis plugin" },
{ pattern: /\bgenesis catalog\b/gi, replacement: "catalog", label: "genesis catalog" },
{ pattern: /\bgenesis SurrealDB repository\b/gi, replacement: "SurrealDB repository", label: "genesis SurrealDB" },
{ pattern: /\bgenesis sibling adapter\b/gi, replacement: "sibling adapter", label: "genesis sibling" },
{ pattern: /\bgenesis-native sibling\b/gi, replacement: "native sibling", label: "genesis-native sibling" },
{ pattern: /\bgenesis dehydrated-drive workflow\b/gi, replacement: "dehydrated-drive workflow", label: "genesis dehydrated-drive" },
{ pattern: /\bgenesis drive mesh\b/gi, replacement: "drive mesh", label: "genesis drive mesh" },
{ pattern: /\bgenesis migration\b/gi, replacement: "migration", label: "genesis migration" },
{ pattern: /\bgenesis network bootstrap\b/gi, replacement: "network bootstrap", label: "genesis network bootstrap" },
{ pattern: /\bgenesis license package\b/gi, replacement: "license package", label: "genesis license" },
{ pattern: /\bGenesis enterprise extras\b/gi, replacement: "Enterprise extras", label: "Genesis enterprise extras" },
{ pattern: /\bgenesis style\b/gi, replacement: "style", label: "genesis style" },
{ pattern: /\bgenesis-stage rebuild\b/gi, replacement: "rebuild", label: "genesis-stage rebuild" },
{ pattern: /\bgenesis determin/gi, replacement: "determin", label: "genesis determin" },
{ pattern: /\bgenesis foundation\b/gi, replacement: "foundation", label: "genesis foundation" },
{ pattern: /\bgenesis default/gi, replacement: "default", label: "genesis default" },
{ pattern: /\bgenesis found/gi, replacement: "found", label: "genesis found" },
{ pattern: /\bgenesis imports/gi, replacement: "imports", label: "genesis imports" },
{ pattern: /\bgenesis `EventBus/gi, replacement: "`EventBus", label: "genesis EventBus" },
{ pattern: /\bmonorepo root directory\b/gi, replacement: "project root directory", label: "monorepo root directory" },
{ pattern: /\bmonorepo sense\b/gi, replacement: "sense", label: "monorepo sense" },
{ pattern: /\bSuaveplan monorepo\./gi, replacement: "Suaveplan.", label: "Suaveplan monorepo." },
{ pattern: /\bStage-1 contracts only\b/gi, replacement: "contracts only", label: "Stage-1 contracts only" },
{ pattern: /\bStage-1 contract package:\b/gi, replacement: "Contract package:", label: "Stage-1 contract package:" },
{ pattern: /\bStage 7\b\./gi, replacement: ".", label: "Stage 7." },
{ pattern: /\bStage 1 \(Contracts\) of the API plugin system umbrella\b/gi, replacement: "Contracts of the API plugin system umbrella", label: "Stage 1 Contracts umbrella" },
{ pattern: /\bStage-\d+'s\b/gi, replacement: "", label: "Stage-N's" },
{ pattern: /\bStage-\d+ must\b/gi, replacement: "must", label: "Stage-N must" },
{ pattern: /\bStage \d+ can be declared complete\b/gi, replacement: "can be declared complete", label: "Stage N declared complete" },
{ pattern: /\bStage \d+ and plug behind\b/gi, replacement: "and plug behind", label: "Stage N plug behind" },
{ pattern: /\b`api-tenant-provider-memory` \(Stage \d+\)/gi, replacement: "`api-tenant-provider-memory`", label: "api-tenant-provider-memory Stage" },
{ pattern: /\bStage \d+\),/gi, replacement: "),", label: "Stage N)," },
{ pattern: /\bStage 1 \(this release\)\b/gi, replacement: "this release", label: "Stage 1 this release" },
{ pattern: /\bStage 1 defines\b/gi, replacement: "defines", label: "Stage 1 defines" },
{ pattern: /\bStage 1 ships\b/gi, replacement: "ships", label: "Stage 1 ships" },
{ pattern: /\bon genesis\b/gi, replacement: "", label: "on genesis" },
{ pattern: /\bgenesis onion-model/gi, replacement: "onion-model", label: "genesis onion-model" },
{ pattern: /\bthe genesis `([^`]+)`/gi, replacement: "the `$1`", label: "the genesis backtick" },
{ pattern: /\bpart of the genesis `([^`]+)`/gi, replacement: "part of the `$1`", label: "part of genesis backtick" },
{ pattern: /\bgenesis uses/gi, replacement: "uses", label: "genesis uses" },
{ pattern: /\b`genesis`\b/gi, replacement: "", label: "backtick genesis" },
{ pattern: /\bGenesis ships\b/gi, replacement: "Ships", label: "Genesis ships" },
{ pattern: /\bGenesis uses/gi, replacement: "Uses", label: "Genesis uses" },
{ pattern: /\bforward reference to Stage \d+\b/gi, replacement: "forward reference", label: "forward reference to Stage" },
{ pattern: /\bin subsequent Stage-\d+ changes?\b/gi, replacement: "in subsequent changes", label: "subsequent Stage changes" },
{ pattern: /\bStage-\d+(?:\.\d+)? (acceptance gate|scope|changes?)\b/gi, replacement: "$1", label: "Stage noun" },
{ pattern: /\bThe Stage-\d+(?:\.\d+)? /gi, replacement: "The ", label: "The Stage-N" },
{ pattern: /\b(Stage \d+(?:\.\d+)?) host\b/gi, replacement: "host", label: "Stage N host" },
{ pattern: /\bStage \d+(?:\.\d+)? (keystone|testing|precondition gate|consumer|DB adapter|tenant resolver|concrete|Apollo Server GraphQL engine adapter|contract package|service-to-service|fixed-principal|trivial session resolver|Better-Auth adapter|concrete JWT|concrete Hono|concrete `Router`|concrete in-memory|concrete WebAssembly|concrete WebSocket|concrete WebSocket broker|concrete Apollo|concrete GraphQL|concrete GraphQL Yoga|concrete static|concrete tenant|concrete database|concrete event|concrete job|concrete logger|concrete manifest|concrete migration|concrete OpenAPI|concrete rate-limit|concrete router|concrete saga|concrete sandbox|concrete secrets|concrete SSE|concrete WS|concrete worker|concrete WS broker|concrete WS Bun)\b/gi, replacement: "$2", label: "Stage N prefix" },
{ pattern: /\bStage \d+(?:\.\d+)? (of the|—|–|-)\s*/gi, replacement: "", label: "Stage N of" },
{ pattern: /\bin Stage \d+(?:\.\d+)?\b/gi, replacement: "", label: "in Stage N" },
{ pattern: /\b\(Stage \d+(?:\.\d+)?[^)]*\)/gi, replacement: "", label: "(Stage N)" },
{ pattern: /\bStage \d+\+\b/gi, replacement: "", label: "Stage N+" },
{ pattern: /\blive in @suaveplan\/[^ ]+ \(Stage \d+\)/gi, replacement: "", label: "live in pkg (Stage N)" },
{ pattern: /\bimplement(s|ed) the contracts here\.?\s*$/gim, replacement: "implements the contracts here.", label: "implements contracts" },
{ pattern: /\bthe migration change at `openspec\/archive\/[^`]+`/gi, replacement: "", label: "migration change openspec" },
{ pattern: /\b`openspec\/archive\/[^`]+`/gi, replacement: "", label: "openspec archive path" },
{ pattern: /\b`openspec\/[^`]+`/gi, replacement: "the spec", label: "openspec path" },
{ pattern: /\b`\.\.\/\.\.\/\.\.\/\.\.\/openspec\/[^`]+`/gi, replacement: "the spec", label: "relative openspec path" },
{ pattern: /\b`\.\.\/openspec\/[^`]+`/gi, replacement: "the spec", label: "relative openspec path" },
{ pattern: /\bsee repository root\b/gi, replacement: "see the repository", label: "see repository root" },
{ pattern: /\bVersion `0\.1\.0` during genesis\.\b/gi, replacement: "", label: "version during genesis" },
{ pattern: /\b`0\.1\.0` \(genesis-original\)\b/gi, replacement: "`0.1.0`", label: "genesis-original version" },
{ pattern: /\b`STAGE_\d+_RESERVED`\b/gi, replacement: "`RESERVED`", label: "STAGE_N_RESERVED" },
{ pattern: /\breserved for Stage \d+\b/gi, replacement: "reserved", label: "reserved for Stage" },
];
for (const { pattern, replacement, label } of phraseReplacements) {
const before = next;
next = next.replace(pattern, replacement);
if (next !== before) edits.push(label);
}
// Clean up double spaces and excess blank lines left behind
next = next.replace(/[ \t]+\n/g, "\n"); // trailing spaces
next = next.replace(/\n{3,}/g, "\n\n"); // 3+ blank lines -> 2
return { next, edits };
}
function main(): void {
const args = process.argv.slice(2);
const dryRun = args.includes("--dry-run");
const first10Only = args.includes("--first10");
const readmeOnly = args.includes("--readme");
const todoRaw = readFileSync(TODO_PATH, "utf-8");
let items = parseTodo(todoRaw);
if (readmeOnly) {
items = items.filter((i) => i.path.endsWith("README.md"));
}
if (first10Only) {
items = items.filter((i) => maxLine(i.lines) <= 10);
}
const remaining: TodoItem[] = [];
const processed: { path: string; edits: string[] }[] = [];
const errors: string[] = [];
for (const item of items) {
try {
const text = readFileSync(item.path, "utf-8");
const { next, edits } = scrub(text, item.path);
if (next !== text) {
if (!dryRun) writeFileSync(item.path, next);
processed.push({ path: item.path, edits: [...new Set(edits)] });
} else {
// No changes made — keep in todo for manual review
remaining.push(item);
}
} catch (err) {
errors.push(`${item.path}: ${err instanceof Error ? err.message : String(err)}`);
remaining.push(item);
continue;
}
}
// Rebuild todo with files that were NOT in this batch (original todo minus processed)
const originalItems = parseTodo(todoRaw);
const processedPaths = new Set(processed.map((p) => p.path));
const notProcessed = originalItems.filter((i) => !processedPaths.has(i.path));
if (!dryRun) {
writeFileSync(TODO_PATH, serializeTodo(notProcessed));
}
console.log("");
console.log(`Processed: ${processed.length}`);
console.log(`Remaining: ${notProcessed.length}`);
if (errors.length > 0) console.log(`Errors: ${errors.length}`);
if (processed.length > 0) {
console.log("");
console.log("Files changed:");
for (const { path, edits } of processed) {
console.log(` ${path} (${edits.join(", ")})`);
}
}
if (dryRun) {
console.log("");
console.log("(Dry-run — no files were written, todo not updated.)");
}
}
main();