@webpieces/rules-config 0.4.691 → 0.4.693

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.
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InstructAiDocSet = exports.MergeProcessDoc = exports.InstructAiDoc = void 0;
3
+ exports.InstructAiDocSet = exports.GitWorkflowDoc = exports.GIT_WORKFLOW_DOC = exports.MergeProcessDoc = exports.InstructAiDoc = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const fs = tslib_1.__importStar(require("fs"));
6
6
  const path = tslib_1.__importStar(require("path"));
7
+ const branch_mutation_log_1 = require("./branch-mutation-log");
7
8
  const merge_process_doc_1 = require("./merge-process-doc");
8
9
  // ---------------------------------------------------------------------------
9
10
  // THE INSTRUCT-AI DOCS ARE WRITTEN AS A SET, NEVER ONE BY ONE.
@@ -42,8 +43,17 @@ class InstructAiDoc {
42
43
  constructor(name) {
43
44
  this.name = name;
44
45
  }
45
- /** The bytes to deliver, given the template's own bytes. */
46
- render(templateText) {
46
+ /**
47
+ * The bytes to deliver, given the template's own bytes and the ROOT of the tree they are being
48
+ * written into.
49
+ *
50
+ * `root` is not decoration: every webpieces state path is per-tree (a linked worktree's state lives
51
+ * under `<primary>/.webpieces/worktrees/<name>/`), so a doc that names one must RESOLVE it for the
52
+ * tree the reader is standing in. A template that restated a relative path instead shipped a path
53
+ * that does not exist in a worktree — see `GitWorkflowDoc`.
54
+ */
55
+ render(templateText, root) {
56
+ void root;
47
57
  return templateText;
48
58
  }
49
59
  /**
@@ -66,7 +76,8 @@ class MergeProcessDoc extends InstructAiDoc {
66
76
  constructor() {
67
77
  super(merge_process_doc_1.MERGE_PROCESS_DOC);
68
78
  }
69
- render(templateText) {
79
+ render(templateText, root) {
80
+ void root;
70
81
  return new merge_process_doc_1.MergeProcessText(templateText).render(new merge_process_doc_1.ReferenceMergeRun());
71
82
  }
72
83
  seedOnly() {
@@ -74,12 +85,41 @@ class MergeProcessDoc extends InstructAiDoc {
74
85
  }
75
86
  }
76
87
  exports.MergeProcessDoc = MergeProcessDoc;
88
+ /** The rules-config template name, and the file name it lands under in `.webpieces/instruct-ai/`. */
89
+ exports.GIT_WORKFLOW_DOC = 'webpieces.git-workflow.md';
90
+ /**
91
+ * `webpieces.git-workflow.md` — the one doc that names the branch-mutation log by path.
92
+ *
93
+ * It used to restate `.webpieces/logs/branch-mutations.log` as a literal. That log is deliberately
94
+ * PER-WORKTREE (one appender each), so in a linked worktree the literal names a file that does not
95
+ * exist: the reader greps nothing, and the silence reads as "no deletions were logged" — the exact
96
+ * opposite of the truth, from the one file whose job is to prove every deletion is recoverable.
97
+ *
98
+ * So the template carries `{{BRANCH_MUTATION_LOG}}` and this class fills it from the SAME resolver the
99
+ * writer uses, for the tree the doc is being written into. There is one answer to "where is that log",
100
+ * and it is `BranchMutationLog.branchMutationLogPath`.
101
+ */
102
+ class GitWorkflowDoc extends InstructAiDoc {
103
+ mutationLog;
104
+ constructor(mutationLog = new branch_mutation_log_1.BranchMutationLog()) {
105
+ super(exports.GIT_WORKFLOW_DOC);
106
+ this.mutationLog = mutationLog;
107
+ }
108
+ render(templateText, root) {
109
+ return templateText.replace(/\{\{BRANCH_MUTATION_LOG\}\}/g, this.mutationLog.branchMutationLogPath(root));
110
+ }
111
+ }
112
+ exports.GitWorkflowDoc = GitWorkflowDoc;
77
113
  /**
78
114
  * The set of instruct-ai docs, and the link graph over them.
79
115
  *
80
116
  * Constructed from the templates directory, so the set cannot disagree with what ships.
81
117
  */
82
118
  class InstructAiDocSet {
119
+ mutationLog;
120
+ constructor(mutationLog = new branch_mutation_log_1.BranchMutationLog()) {
121
+ this.mutationLog = mutationLog;
122
+ }
83
123
  // Resolved on FIRST USE, never in the constructor. `TemplateWriter` is constructed at module load
84
124
  // (its migration delegators), and a spec that mocks `fs` for its own subject would otherwise blow up
85
125
  // on an unrelated package's import — which is exactly what happened to read-stale-guard.spec.ts.
@@ -114,6 +154,7 @@ class InstructAiDocSet {
114
154
  return this.byName;
115
155
  const overrides = new Map();
116
156
  overrides.set(merge_process_doc_1.MERGE_PROCESS_DOC, new MergeProcessDoc());
157
+ overrides.set(exports.GIT_WORKFLOW_DOC, new GitWorkflowDoc(this.mutationLog));
117
158
  const built = new Map();
118
159
  for (const name of this.templateNames()) {
119
160
  built.set(name, overrides.get(name) ?? new InstructAiDoc(name));
@@ -1 +1 @@
1
- {"version":3,"file":"instruct-ai-docs.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/instruct-ai-docs.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { MERGE_PROCESS_DOC, MergeProcessText, ReferenceMergeRun } from './merge-process-doc';\n\n// ---------------------------------------------------------------------------\n// THE INSTRUCT-AI DOCS ARE WRITTEN AS A SET, NEVER ONE BY ONE.\n//\n// The bug this fixes: `webpieces.git-workflow.md` links to `webpieces.mergeprocess.md`, and six of the\n// nine governed repos had the first file and not the second — the link pointed at nothing. Nobody\n// forgot on purpose. Each `wp-*` command named the ONE doc it cared about\n// (`writeTemplate(root, 'webpieces.git-workflow.md')`), and a doc that only some other code path wrote\n// simply never arrived.\n//\n// Adding `webpieces.mergeprocess.md` to those six call sites would fix that one link and nothing else:\n// the NEXT doc to gain a sibling link would go missing the same way. So the fix is structural — a doc\n// is never written alone. Writing one writes the TRANSITIVE CLOSURE of the instruct-ai docs it links\n// to, so a doc and everything it points at always land together.\n//\n// TWO CONSEQUENCES WORTH SPELLING OUT:\n//\n// • THE SET IS THE TEMPLATES DIRECTORY. Membership is `readdirSync(templates/)`, not a hand-kept\n// array — dropping `webpieces.<something>.md` into that directory enrols it, and there is no\n// second place to remember. That is the same reason L1/L2 render their tables from the array the\n// guard dispatches on rather than from a description of it.\n// • `instruct-ai-docs.spec.ts` FAILS THE BUILD if any template references a `webpieces.*.md` sibling\n// that is not a member. Without that test this is one instance fixed, not the class.\n// ---------------------------------------------------------------------------\n\nconst TEMPLATES_DIR = path.join(__dirname, '..', 'templates');\n\n/** Every `webpieces.*.md` name mentioned anywhere in a doc's body — links, prose, code fences alike. */\nconst DOC_REFERENCE = /webpieces\\.[A-Za-z0-9._-]*\\.md/g;\n\n/**\n * One deliverable instruct-ai doc.\n *\n * The default is a byte-for-byte copy of its template, rewritten whenever the bytes change. A doc that\n * needs rendering, or that must not clobber live run state, overrides one of the two methods below.\n */\nexport class InstructAiDoc {\n constructor(readonly name: string) {}\n\n /** The bytes to deliver, given the template's own bytes. */\n render(templateText: string): string {\n return templateText;\n }\n\n /**\n * True when an EXISTING copy must be left alone.\n *\n * Only docs a `wp-*` command stamps with live run state say yes: overwriting one mid-run would\n * replace the handback an agent is reading with a generic reference copy.\n */\n seedOnly(): boolean {\n return false;\n }\n}\n\n/**\n * `webpieces.mergeprocess.md` — delivered as the reference rendering, never over a live handback.\n *\n * See merge-process-doc.ts for why one template with two inputs is the whole point.\n */\nexport class MergeProcessDoc extends InstructAiDoc {\n constructor() {\n super(MERGE_PROCESS_DOC);\n }\n\n override render(templateText: string): string {\n return new MergeProcessText(templateText).render(new ReferenceMergeRun());\n }\n\n override seedOnly(): boolean {\n return true;\n }\n}\n\n/**\n * The set of instruct-ai docs, and the link graph over them.\n *\n * Constructed from the templates directory, so the set cannot disagree with what ships.\n */\nexport class InstructAiDocSet {\n // Resolved on FIRST USE, never in the constructor. `TemplateWriter` is constructed at module load\n // (its migration delegators), and a spec that mocks `fs` for its own subject would otherwise blow up\n // on an unrelated package's import — which is exactly what happened to read-stale-guard.spec.ts.\n private byName: Map<string, InstructAiDoc> | null = null;\n\n /** Every `webpieces.*.md` template that ships with this package. */\n templateNames(): readonly string[] {\n return fs.readdirSync(TEMPLATES_DIR)\n .filter((name: string): boolean => name.startsWith('webpieces.') && name.endsWith('.md'))\n .sort();\n }\n\n /** Every member, in name order. */\n all(): readonly InstructAiDoc[] {\n return [...this.members().values()];\n }\n\n /** The member called `name`, or null when `name` is not an instruct-ai doc at all. */\n get(name: string): InstructAiDoc | null {\n return this.members().get(name) ?? null;\n }\n\n /** Every `webpieces.*.md` name `text` mentions, member or not, de-duplicated in first-seen order. */\n referencesIn(text: string): readonly string[] {\n return [...new Set(text.match(DOC_REFERENCE) ?? [])];\n }\n\n /** The references in `text` that are NOT members — what the link-integrity test refuses. */\n danglingIn(text: string): readonly string[] {\n const members = this.members();\n return this.referencesIn(text).filter((name: string): boolean => !members.has(name));\n }\n\n // The set, built once from the templates directory. The overrides are the docs that need rendering\n // or that must not clobber live run state; everything else is a plain copy of its template.\n private members(): Map<string, InstructAiDoc> {\n if (this.byName !== null) return this.byName;\n const overrides = new Map<string, InstructAiDoc>();\n overrides.set(MERGE_PROCESS_DOC, new MergeProcessDoc());\n const built = new Map<string, InstructAiDoc>();\n for (const name of this.templateNames()) {\n built.set(name, overrides.get(name) ?? new InstructAiDoc(name));\n }\n this.byName = built;\n return built;\n }\n\n /**\n * `name` plus every member reachable from it by links, breadth-first from `name`.\n *\n * A non-member is returned alone: a caller writing something that is not an instruct-ai doc (the\n * CI workflow yml) still gets exactly what it asked for.\n */\n closure(name: string, loadText: (docName: string) => string): readonly InstructAiDoc[] {\n const first = this.get(name);\n if (first === null) return [new InstructAiDoc(name)];\n const out: InstructAiDoc[] = [first];\n const seen = new Set<string>([name]);\n for (let i = 0; i < out.length; i++) {\n for (const reference of this.referencesIn(loadText(out[i].name))) {\n const doc = this.get(reference);\n if (doc === null || seen.has(reference)) continue;\n seen.add(reference);\n out.push(doc);\n }\n }\n return out;\n }\n}\n"]}
1
+ {"version":3,"file":"instruct-ai-docs.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/instruct-ai-docs.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { BranchMutationLog } from './branch-mutation-log';\nimport { MERGE_PROCESS_DOC, MergeProcessText, ReferenceMergeRun } from './merge-process-doc';\n\n// ---------------------------------------------------------------------------\n// THE INSTRUCT-AI DOCS ARE WRITTEN AS A SET, NEVER ONE BY ONE.\n//\n// The bug this fixes: `webpieces.git-workflow.md` links to `webpieces.mergeprocess.md`, and six of the\n// nine governed repos had the first file and not the second — the link pointed at nothing. Nobody\n// forgot on purpose. Each `wp-*` command named the ONE doc it cared about\n// (`writeTemplate(root, 'webpieces.git-workflow.md')`), and a doc that only some other code path wrote\n// simply never arrived.\n//\n// Adding `webpieces.mergeprocess.md` to those six call sites would fix that one link and nothing else:\n// the NEXT doc to gain a sibling link would go missing the same way. So the fix is structural — a doc\n// is never written alone. Writing one writes the TRANSITIVE CLOSURE of the instruct-ai docs it links\n// to, so a doc and everything it points at always land together.\n//\n// TWO CONSEQUENCES WORTH SPELLING OUT:\n//\n// • THE SET IS THE TEMPLATES DIRECTORY. Membership is `readdirSync(templates/)`, not a hand-kept\n// array — dropping `webpieces.<something>.md` into that directory enrols it, and there is no\n// second place to remember. That is the same reason L1/L2 render their tables from the array the\n// guard dispatches on rather than from a description of it.\n// • `instruct-ai-docs.spec.ts` FAILS THE BUILD if any template references a `webpieces.*.md` sibling\n// that is not a member. Without that test this is one instance fixed, not the class.\n// ---------------------------------------------------------------------------\n\nconst TEMPLATES_DIR = path.join(__dirname, '..', 'templates');\n\n/** Every `webpieces.*.md` name mentioned anywhere in a doc's body — links, prose, code fences alike. */\nconst DOC_REFERENCE = /webpieces\\.[A-Za-z0-9._-]*\\.md/g;\n\n/**\n * One deliverable instruct-ai doc.\n *\n * The default is a byte-for-byte copy of its template, rewritten whenever the bytes change. A doc that\n * needs rendering, or that must not clobber live run state, overrides one of the two methods below.\n */\nexport class InstructAiDoc {\n constructor(readonly name: string) {}\n\n /**\n * The bytes to deliver, given the template's own bytes and the ROOT of the tree they are being\n * written into.\n *\n * `root` is not decoration: every webpieces state path is per-tree (a linked worktree's state lives\n * under `<primary>/.webpieces/worktrees/<name>/`), so a doc that names one must RESOLVE it for the\n * tree the reader is standing in. A template that restated a relative path instead shipped a path\n * that does not exist in a worktree — see `GitWorkflowDoc`.\n */\n render(templateText: string, root: string): string {\n void root;\n return templateText;\n }\n\n /**\n * True when an EXISTING copy must be left alone.\n *\n * Only docs a `wp-*` command stamps with live run state say yes: overwriting one mid-run would\n * replace the handback an agent is reading with a generic reference copy.\n */\n seedOnly(): boolean {\n return false;\n }\n}\n\n/**\n * `webpieces.mergeprocess.md` — delivered as the reference rendering, never over a live handback.\n *\n * See merge-process-doc.ts for why one template with two inputs is the whole point.\n */\nexport class MergeProcessDoc extends InstructAiDoc {\n constructor() {\n super(MERGE_PROCESS_DOC);\n }\n\n override render(templateText: string, root: string): string {\n void root;\n return new MergeProcessText(templateText).render(new ReferenceMergeRun());\n }\n\n override seedOnly(): boolean {\n return true;\n }\n}\n\n/** The rules-config template name, and the file name it lands under in `.webpieces/instruct-ai/`. */\nexport const GIT_WORKFLOW_DOC = 'webpieces.git-workflow.md';\n\n/**\n * `webpieces.git-workflow.md` — the one doc that names the branch-mutation log by path.\n *\n * It used to restate `.webpieces/logs/branch-mutations.log` as a literal. That log is deliberately\n * PER-WORKTREE (one appender each), so in a linked worktree the literal names a file that does not\n * exist: the reader greps nothing, and the silence reads as \"no deletions were logged\" — the exact\n * opposite of the truth, from the one file whose job is to prove every deletion is recoverable.\n *\n * So the template carries `{{BRANCH_MUTATION_LOG}}` and this class fills it from the SAME resolver the\n * writer uses, for the tree the doc is being written into. There is one answer to \"where is that log\",\n * and it is `BranchMutationLog.branchMutationLogPath`.\n */\nexport class GitWorkflowDoc extends InstructAiDoc {\n constructor(private readonly mutationLog: BranchMutationLog = new BranchMutationLog()) {\n super(GIT_WORKFLOW_DOC);\n }\n\n override render(templateText: string, root: string): string {\n return templateText.replace(/\\{\\{BRANCH_MUTATION_LOG\\}\\}/g, this.mutationLog.branchMutationLogPath(root));\n }\n}\n\n/**\n * The set of instruct-ai docs, and the link graph over them.\n *\n * Constructed from the templates directory, so the set cannot disagree with what ships.\n */\nexport class InstructAiDocSet {\n constructor(private readonly mutationLog: BranchMutationLog = new BranchMutationLog()) {}\n\n // Resolved on FIRST USE, never in the constructor. `TemplateWriter` is constructed at module load\n // (its migration delegators), and a spec that mocks `fs` for its own subject would otherwise blow up\n // on an unrelated package's import — which is exactly what happened to read-stale-guard.spec.ts.\n private byName: Map<string, InstructAiDoc> | null = null;\n\n /** Every `webpieces.*.md` template that ships with this package. */\n templateNames(): readonly string[] {\n return fs.readdirSync(TEMPLATES_DIR)\n .filter((name: string): boolean => name.startsWith('webpieces.') && name.endsWith('.md'))\n .sort();\n }\n\n /** Every member, in name order. */\n all(): readonly InstructAiDoc[] {\n return [...this.members().values()];\n }\n\n /** The member called `name`, or null when `name` is not an instruct-ai doc at all. */\n get(name: string): InstructAiDoc | null {\n return this.members().get(name) ?? null;\n }\n\n /** Every `webpieces.*.md` name `text` mentions, member or not, de-duplicated in first-seen order. */\n referencesIn(text: string): readonly string[] {\n return [...new Set(text.match(DOC_REFERENCE) ?? [])];\n }\n\n /** The references in `text` that are NOT members — what the link-integrity test refuses. */\n danglingIn(text: string): readonly string[] {\n const members = this.members();\n return this.referencesIn(text).filter((name: string): boolean => !members.has(name));\n }\n\n // The set, built once from the templates directory. The overrides are the docs that need rendering\n // or that must not clobber live run state; everything else is a plain copy of its template.\n private members(): Map<string, InstructAiDoc> {\n if (this.byName !== null) return this.byName;\n const overrides = new Map<string, InstructAiDoc>();\n overrides.set(MERGE_PROCESS_DOC, new MergeProcessDoc());\n overrides.set(GIT_WORKFLOW_DOC, new GitWorkflowDoc(this.mutationLog));\n const built = new Map<string, InstructAiDoc>();\n for (const name of this.templateNames()) {\n built.set(name, overrides.get(name) ?? new InstructAiDoc(name));\n }\n this.byName = built;\n return built;\n }\n\n /**\n * `name` plus every member reachable from it by links, breadth-first from `name`.\n *\n * A non-member is returned alone: a caller writing something that is not an instruct-ai doc (the\n * CI workflow yml) still gets exactly what it asked for.\n */\n closure(name: string, loadText: (docName: string) => string): readonly InstructAiDoc[] {\n const first = this.get(name);\n if (first === null) return [new InstructAiDoc(name)];\n const out: InstructAiDoc[] = [first];\n const seen = new Set<string>([name]);\n for (let i = 0; i < out.length; i++) {\n for (const reference of this.referencesIn(loadText(out[i].name))) {\n const doc = this.get(reference);\n if (doc === null || seen.has(reference)) continue;\n seen.add(reference);\n out.push(doc);\n }\n }\n return out;\n }\n}\n"]}
@@ -47,7 +47,7 @@ let TemplateWriter = class TemplateWriter {
47
47
  const target = this.destination(workspaceRoot, doc.name, instructDir);
48
48
  if (fs.existsSync(target))
49
49
  continue;
50
- this.atomicFile.writeAtomic(target, doc.render(this.loadTemplate(doc.name)));
50
+ this.atomicFile.writeAtomic(target, doc.render(this.loadTemplate(doc.name), workspaceRoot));
51
51
  }
52
52
  }
53
53
  /**
@@ -70,7 +70,7 @@ let TemplateWriter = class TemplateWriter {
70
70
  // copy would delete the file list the agent is working from.
71
71
  if (doc.seedOnly() && fs.existsSync(target))
72
72
  continue;
73
- this.atomicFile.writeIfChanged(target, doc.render(this.loadTemplate(doc.name)));
73
+ this.atomicFile.writeIfChanged(target, doc.render(this.loadTemplate(doc.name), workspaceRoot));
74
74
  }
75
75
  return this.destination(workspaceRoot, name, instructDir);
76
76
  }
@@ -1 +1 @@
1
- {"version":3,"file":"load-template.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-template.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { AtomicFile } from './atomic-file';\nimport { InstructAiDocSet } from './instruct-ai-docs';\nimport { INSTRUCT_AI_LEAF } from './repo-root';\nimport { DotWebpieces, dotWebpieces } from './state-dir';\n\nconst TEMPLATES_DIR = path.join(__dirname, '..', 'templates');\n// Sentinel for \"use the resolved LOCAL instruct-ai dir\". Kept as the parameter default so the handful\n// of callers that pass an explicit relative dir (they join it onto workspaceRoot themselves) are\n// unaffected; anything passing the default gets DotWebpieces.local() resolution.\nconst DEFAULT_INSTRUCT_DIR = '';\n\n/**\n * Writes the AI-facing instruct-ai template docs under `<workspaceRoot>/.webpieces/instruct-ai/`.\n * `@injectable(bindingScopeValues.Singleton)` so it can be injected and appear in the rules-config DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class TemplateWriter {\n constructor(\n private readonly dotDir: DotWebpieces = dotWebpieces,\n private readonly atomicFile: AtomicFile = new AtomicFile(),\n private readonly docs: InstructAiDocSet = new InstructAiDocSet(),\n ) {}\n\n loadTemplate(name: string): string {\n return fs.readFileSync(path.join(TEMPLATES_DIR, name), 'utf-8');\n }\n\n /**\n * SEED `name` and everything it links to — writing only the ones that are not already on disk.\n *\n * Same closure as `writeTemplate`, for the same reason: a rule that drops\n * `webpieces.exceptions.md` next to its violation is delivering a doc a reader follows links out\n * of, and a seeded doc that later gains a sibling link would otherwise dangle exactly the way\n * git-workflow.md's link to the merge process did. The difference from `writeTemplate` is only\n * WHETHER an existing file is refreshed, never WHICH files are considered.\n */\n writeTemplateIfMissing(workspaceRoot: string, name: string, instructDir: string = DEFAULT_INSTRUCT_DIR): void {\n for (const doc of this.docs.closure(name, (docName: string): string => this.loadTemplate(docName))) {\n const target = this.destination(workspaceRoot, doc.name, instructDir);\n if (fs.existsSync(target)) continue;\n this.atomicFile.writeAtomic(target, doc.render(this.loadTemplate(doc.name)));\n }\n }\n\n /**\n * Write `name` AND every instruct-ai doc it links to, ATOMICALLY and only where bytes changed.\n * Returns the absolute path of `name` itself.\n *\n * THE CLOSURE IS THE POINT. Callers name the ONE doc their command is about; the docs a reader is\n * sent on to arrive with it, because a doc whose links dangle is worse than no doc — it teaches the\n * reader that the paths in these files cannot be trusted. See instruct-ai-docs.ts for the incident.\n *\n * Every `wp-*` command regenerates these, and the AI is routinely told to open one by absolute\n * path. A plain truncating write means a reader can catch it empty; skip-if-unchanged means the\n * overwhelmingly common case (same package version ⇒ identical content) does not write at all.\n */\n writeTemplate(workspaceRoot: string, name: string, instructDir: string = DEFAULT_INSTRUCT_DIR): string {\n for (const doc of this.docs.closure(name, (docName: string): string => this.loadTemplate(docName))) {\n const target = this.destination(workspaceRoot, doc.name, instructDir);\n // A doc stamped with live run state is SEEDED, never refreshed: `wp-finish-upsert-pr` runs\n // while a conflicted merge is still open, and clobbering that handback with the reference\n // copy would delete the file list the agent is working from.\n if (doc.seedOnly() && fs.existsSync(target)) continue;\n this.atomicFile.writeIfChanged(target, doc.render(this.loadTemplate(doc.name)));\n }\n return this.destination(workspaceRoot, name, instructDir);\n }\n\n // LOCAL `.webpieces/instruct-ai/<name>` by default; an explicitly-passed relative dir is still\n // joined onto workspaceRoot exactly as before.\n private destination(workspaceRoot: string, name: string, instructDir: string): string {\n if (instructDir === DEFAULT_INSTRUCT_DIR) {\n return this.dotDir.localFile(workspaceRoot, INSTRUCT_AI_LEAF, name);\n }\n return path.join(workspaceRoot, instructDir, name);\n }\n}\n\n// Temporary migration delegators — consumers migrate to injecting TemplateWriter over follow-up PRs.\nconst templateWriterSvc = new TemplateWriter();\n\nexport function loadTemplate(name: string): string {\n return templateWriterSvc.loadTemplate(name);\n}\n\nexport function writeTemplateIfMissing(\n workspaceRoot: string,\n name: string,\n instructDir: string = DEFAULT_INSTRUCT_DIR,\n): void {\n templateWriterSvc.writeTemplateIfMissing(workspaceRoot, name, instructDir);\n}\n\nexport function writeTemplate(\n workspaceRoot: string,\n name: string,\n instructDir: string = DEFAULT_INSTRUCT_DIR,\n): string {\n return templateWriterSvc.writeTemplate(workspaceRoot, name, instructDir);\n}\n"]}
1
+ {"version":3,"file":"load-template.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-template.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { AtomicFile } from './atomic-file';\nimport { InstructAiDocSet } from './instruct-ai-docs';\nimport { INSTRUCT_AI_LEAF } from './repo-root';\nimport { DotWebpieces, dotWebpieces } from './state-dir';\n\nconst TEMPLATES_DIR = path.join(__dirname, '..', 'templates');\n// Sentinel for \"use the resolved LOCAL instruct-ai dir\". Kept as the parameter default so the handful\n// of callers that pass an explicit relative dir (they join it onto workspaceRoot themselves) are\n// unaffected; anything passing the default gets DotWebpieces.local() resolution.\nconst DEFAULT_INSTRUCT_DIR = '';\n\n/**\n * Writes the AI-facing instruct-ai template docs under `<workspaceRoot>/.webpieces/instruct-ai/`.\n * `@injectable(bindingScopeValues.Singleton)` so it can be injected and appear in the rules-config DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class TemplateWriter {\n constructor(\n private readonly dotDir: DotWebpieces = dotWebpieces,\n private readonly atomicFile: AtomicFile = new AtomicFile(),\n private readonly docs: InstructAiDocSet = new InstructAiDocSet(),\n ) {}\n\n loadTemplate(name: string): string {\n return fs.readFileSync(path.join(TEMPLATES_DIR, name), 'utf-8');\n }\n\n /**\n * SEED `name` and everything it links to — writing only the ones that are not already on disk.\n *\n * Same closure as `writeTemplate`, for the same reason: a rule that drops\n * `webpieces.exceptions.md` next to its violation is delivering a doc a reader follows links out\n * of, and a seeded doc that later gains a sibling link would otherwise dangle exactly the way\n * git-workflow.md's link to the merge process did. The difference from `writeTemplate` is only\n * WHETHER an existing file is refreshed, never WHICH files are considered.\n */\n writeTemplateIfMissing(workspaceRoot: string, name: string, instructDir: string = DEFAULT_INSTRUCT_DIR): void {\n for (const doc of this.docs.closure(name, (docName: string): string => this.loadTemplate(docName))) {\n const target = this.destination(workspaceRoot, doc.name, instructDir);\n if (fs.existsSync(target)) continue;\n this.atomicFile.writeAtomic(target, doc.render(this.loadTemplate(doc.name), workspaceRoot));\n }\n }\n\n /**\n * Write `name` AND every instruct-ai doc it links to, ATOMICALLY and only where bytes changed.\n * Returns the absolute path of `name` itself.\n *\n * THE CLOSURE IS THE POINT. Callers name the ONE doc their command is about; the docs a reader is\n * sent on to arrive with it, because a doc whose links dangle is worse than no doc — it teaches the\n * reader that the paths in these files cannot be trusted. See instruct-ai-docs.ts for the incident.\n *\n * Every `wp-*` command regenerates these, and the AI is routinely told to open one by absolute\n * path. A plain truncating write means a reader can catch it empty; skip-if-unchanged means the\n * overwhelmingly common case (same package version ⇒ identical content) does not write at all.\n */\n writeTemplate(workspaceRoot: string, name: string, instructDir: string = DEFAULT_INSTRUCT_DIR): string {\n for (const doc of this.docs.closure(name, (docName: string): string => this.loadTemplate(docName))) {\n const target = this.destination(workspaceRoot, doc.name, instructDir);\n // A doc stamped with live run state is SEEDED, never refreshed: `wp-finish-upsert-pr` runs\n // while a conflicted merge is still open, and clobbering that handback with the reference\n // copy would delete the file list the agent is working from.\n if (doc.seedOnly() && fs.existsSync(target)) continue;\n this.atomicFile.writeIfChanged(target, doc.render(this.loadTemplate(doc.name), workspaceRoot));\n }\n return this.destination(workspaceRoot, name, instructDir);\n }\n\n // LOCAL `.webpieces/instruct-ai/<name>` by default; an explicitly-passed relative dir is still\n // joined onto workspaceRoot exactly as before.\n private destination(workspaceRoot: string, name: string, instructDir: string): string {\n if (instructDir === DEFAULT_INSTRUCT_DIR) {\n return this.dotDir.localFile(workspaceRoot, INSTRUCT_AI_LEAF, name);\n }\n return path.join(workspaceRoot, instructDir, name);\n }\n}\n\n// Temporary migration delegators — consumers migrate to injecting TemplateWriter over follow-up PRs.\nconst templateWriterSvc = new TemplateWriter();\n\nexport function loadTemplate(name: string): string {\n return templateWriterSvc.loadTemplate(name);\n}\n\nexport function writeTemplateIfMissing(\n workspaceRoot: string,\n name: string,\n instructDir: string = DEFAULT_INSTRUCT_DIR,\n): void {\n templateWriterSvc.writeTemplateIfMissing(workspaceRoot, name, instructDir);\n}\n\nexport function writeTemplate(\n workspaceRoot: string,\n name: string,\n instructDir: string = DEFAULT_INSTRUCT_DIR,\n): string {\n return templateWriterSvc.writeTemplate(workspaceRoot, name, instructDir);\n}\n"]}
@@ -0,0 +1,41 @@
1
+ import { SchemaShape } from './field-def';
2
+ import { BaseRuleConfig, ModifiedCodeMode } from './rule-configs';
3
+ /**
4
+ * The template dirs `no-state-paths-in-templates` looks at when a repo has not said otherwise:
5
+ * webpieces' own instruct-ai templates, which are the generated docs the rule was written for.
6
+ *
7
+ * A consumer repo that generates AI-facing docs from its own templates points `templateDirs` at them;
8
+ * a repo that generates none matches no files and the rule is a no-op. Seeded rather than left empty
9
+ * so the rule arrives doing the job it exists for instead of waiting to be discovered.
10
+ */
11
+ export declare const DEFAULT_TEMPLATE_DIRS: readonly string[];
12
+ /**
13
+ * The path prefixes that must never be RESTATED in a generated doc, only computed.
14
+ *
15
+ * `.webpieces/` is the whole webpieces state tree, and every path under it is per-tree: a linked
16
+ * worktree's state lives at `<primary>/.webpieces/worktrees/<name>/…`, so the relative spelling names
17
+ * a file that does not exist there.
18
+ */
19
+ export declare const DEFAULT_BANNED_STATE_PATH_PREFIXES: readonly string[];
20
+ /**
21
+ * Bans a hard-coded state path inside a GENERATED-doc template.
22
+ *
23
+ * The incident: `webpieces.git-workflow.md` restated `.webpieces/logs/branch-mutations.log`. That log
24
+ * is per-worktree, so the doc — regenerated into every governed repo and handed to an agent by
25
+ * absolute path as instruction — named a file that does not exist in half the trees that read it.
26
+ * Every one of these paths already has a resolver; the template engine already substitutes. The rule
27
+ * makes "restate it" the thing that fails and "render `{{PLACEHOLDER}}` from the resolver" the thing
28
+ * that passes.
29
+ *
30
+ * Diff-scoped like every other code rule, so the docs whose SUBJECT is the layout (they explain both
31
+ * rows on purpose) are not retroactively flooded — the rule bites when a template is next edited.
32
+ * A doc that genuinely has to print the literal says so out loud:
33
+ * `<!-- webpieces-disable no-state-paths-in-templates -- this table IS the layout -->`.
34
+ */
35
+ export declare class NoStatePathsInTemplatesConfig extends BaseRuleConfig {
36
+ mode?: ModifiedCodeMode;
37
+ disableAllowed?: boolean;
38
+ templateDirs?: string[];
39
+ bannedPathPrefixes?: string[];
40
+ static readonly SCHEMA: SchemaShape<NoStatePathsInTemplatesConfig>;
41
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NoStatePathsInTemplatesConfig = exports.DEFAULT_BANNED_STATE_PATH_PREFIXES = exports.DEFAULT_TEMPLATE_DIRS = void 0;
4
+ const field_def_1 = require("./field-def");
5
+ const rule_configs_1 = require("./rule-configs");
6
+ /**
7
+ * The template dirs `no-state-paths-in-templates` looks at when a repo has not said otherwise:
8
+ * webpieces' own instruct-ai templates, which are the generated docs the rule was written for.
9
+ *
10
+ * A consumer repo that generates AI-facing docs from its own templates points `templateDirs` at them;
11
+ * a repo that generates none matches no files and the rule is a no-op. Seeded rather than left empty
12
+ * so the rule arrives doing the job it exists for instead of waiting to be discovered.
13
+ */
14
+ exports.DEFAULT_TEMPLATE_DIRS = ['packages/tooling/rules-config/templates'];
15
+ /**
16
+ * The path prefixes that must never be RESTATED in a generated doc, only computed.
17
+ *
18
+ * `.webpieces/` is the whole webpieces state tree, and every path under it is per-tree: a linked
19
+ * worktree's state lives at `<primary>/.webpieces/worktrees/<name>/…`, so the relative spelling names
20
+ * a file that does not exist there.
21
+ */
22
+ exports.DEFAULT_BANNED_STATE_PATH_PREFIXES = ['.webpieces/'];
23
+ /**
24
+ * Bans a hard-coded state path inside a GENERATED-doc template.
25
+ *
26
+ * The incident: `webpieces.git-workflow.md` restated `.webpieces/logs/branch-mutations.log`. That log
27
+ * is per-worktree, so the doc — regenerated into every governed repo and handed to an agent by
28
+ * absolute path as instruction — named a file that does not exist in half the trees that read it.
29
+ * Every one of these paths already has a resolver; the template engine already substitutes. The rule
30
+ * makes "restate it" the thing that fails and "render `{{PLACEHOLDER}}` from the resolver" the thing
31
+ * that passes.
32
+ *
33
+ * Diff-scoped like every other code rule, so the docs whose SUBJECT is the layout (they explain both
34
+ * rows on purpose) are not retroactively flooded — the rule bites when a template is next edited.
35
+ * A doc that genuinely has to print the literal says so out loud:
36
+ * `<!-- webpieces-disable no-state-paths-in-templates -- this table IS the layout -->`.
37
+ */
38
+ class NoStatePathsInTemplatesConfig extends rule_configs_1.BaseRuleConfig {
39
+ disableAllowed;
40
+ templateDirs;
41
+ bannedPathPrefixes;
42
+ static SCHEMA = {
43
+ mode: new field_def_1.FieldDef('string', rule_configs_1.MODIFIED_CODE_MODES),
44
+ disableAllowed: field_def_1.FieldDef.optional('boolean'),
45
+ templateDirs: field_def_1.FieldDef.optional('string[]'),
46
+ bannedPathPrefixes: field_def_1.FieldDef.optional('string[]'),
47
+ ...rule_configs_1.BASE_RULE_SCHEMA,
48
+ };
49
+ }
50
+ exports.NoStatePathsInTemplatesConfig = NoStatePathsInTemplatesConfig;
51
+ //# sourceMappingURL=no-state-paths-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-state-paths-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/no-state-paths-config.ts"],"names":[],"mappings":";;;AAAA,2CAAoD;AACpD,iDAAyG;AAEzG;;;;;;;GAOG;AACU,QAAA,qBAAqB,GAAsB,CAAC,yCAAyC,CAAC,CAAC;AAEpG;;;;;;GAMG;AACU,QAAA,kCAAkC,GAAsB,CAAC,aAAa,CAAC,CAAC;AAErF;;;;;;;;;;;;;;GAcG;AACH,MAAa,6BAA8B,SAAQ,6BAAc;IAE7D,cAAc,CAAW;IACzB,YAAY,CAAY;IACxB,kBAAkB,CAAY;IAE9B,MAAM,CAAU,MAAM,GAA+C;QACjE,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,kCAAmB,CAAC;QACjD,cAAc,EAAE,oBAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC5C,YAAY,EAAE,oBAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC3C,kBAAkB,EAAE,oBAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;QACjD,GAAG,+BAAgB;KACtB,CAAC;;AAZN,sEAaC","sourcesContent":["import { FieldDef, SchemaShape } from './field-def';\nimport { BaseRuleConfig, BASE_RULE_SCHEMA, ModifiedCodeMode, MODIFIED_CODE_MODES } from './rule-configs';\n\n/**\n * The template dirs `no-state-paths-in-templates` looks at when a repo has not said otherwise:\n * webpieces' own instruct-ai templates, which are the generated docs the rule was written for.\n *\n * A consumer repo that generates AI-facing docs from its own templates points `templateDirs` at them;\n * a repo that generates none matches no files and the rule is a no-op. Seeded rather than left empty\n * so the rule arrives doing the job it exists for instead of waiting to be discovered.\n */\nexport const DEFAULT_TEMPLATE_DIRS: readonly string[] = ['packages/tooling/rules-config/templates'];\n\n/**\n * The path prefixes that must never be RESTATED in a generated doc, only computed.\n *\n * `.webpieces/` is the whole webpieces state tree, and every path under it is per-tree: a linked\n * worktree's state lives at `<primary>/.webpieces/worktrees/<name>/…`, so the relative spelling names\n * a file that does not exist there.\n */\nexport const DEFAULT_BANNED_STATE_PATH_PREFIXES: readonly string[] = ['.webpieces/'];\n\n/**\n * Bans a hard-coded state path inside a GENERATED-doc template.\n *\n * The incident: `webpieces.git-workflow.md` restated `.webpieces/logs/branch-mutations.log`. That log\n * is per-worktree, so the doc — regenerated into every governed repo and handed to an agent by\n * absolute path as instruction — named a file that does not exist in half the trees that read it.\n * Every one of these paths already has a resolver; the template engine already substitutes. The rule\n * makes \"restate it\" the thing that fails and \"render `{{PLACEHOLDER}}` from the resolver\" the thing\n * that passes.\n *\n * Diff-scoped like every other code rule, so the docs whose SUBJECT is the layout (they explain both\n * rows on purpose) are not retroactively flooded — the rule bites when a template is next edited.\n * A doc that genuinely has to print the literal says so out loud:\n * `<!-- webpieces-disable no-state-paths-in-templates -- this table IS the layout -->`.\n */\nexport class NoStatePathsInTemplatesConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n disableAllowed?: boolean;\n templateDirs?: string[];\n bannedPathPrefixes?: string[];\n\n static readonly SCHEMA: SchemaShape<NoStatePathsInTemplatesConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n templateDirs: FieldDef.optional('string[]'),\n bannedPathPrefixes: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n"]}
@@ -6,6 +6,7 @@ exports.allRuleNames = allRuleNames;
6
6
  const main_sync_guard_configs_1 = require("./main-sync-guard-configs");
7
7
  const rule_configs_1 = require("./rule-configs");
8
8
  const no_client_creation_config_1 = require("./no-client-creation-config");
9
+ const no_state_paths_config_1 = require("./no-state-paths-config");
9
10
  // Thin lookup table — each entry delegates to the class's own SCHEMA.
10
11
  // No field lists here; all schemas live with their config class.
11
12
  //
@@ -30,6 +31,7 @@ exports.RULE_SCHEMAS = {
30
31
  'no-symbol-di-tokens': rule_configs_1.NoSymbolDiTokensConfig.SCHEMA,
31
32
  'no-client-creation-outside-server-or-client': no_client_creation_config_1.NoClientCreationOutsideServerOrClientConfig.SCHEMA,
32
33
  'no-custom-css': rule_configs_1.NoCustomCssConfig.SCHEMA,
34
+ 'no-state-paths-in-templates': no_state_paths_config_1.NoStatePathsInTemplatesConfig.SCHEMA,
33
35
  'no-process-exit-outside-main': rule_configs_1.NoProcessExitOutsideMainConfig.SCHEMA,
34
36
  'no-function-outside-class': rule_configs_1.NoFunctionOutsideClassConfig.SCHEMA,
35
37
  'inject-annotation-not-needed-for-concrete-class': rule_configs_1.InjectAnnotationNotNeededForConcreteClassConfig.SCHEMA,
@@ -1 +1 @@
1
- {"version":3,"file":"rule-schemas.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/rule-schemas.ts"],"names":[],"mappings":";;;AAoGA,4CAGC;AAMD,oCAEC;AA3GD,uEAAmE;AACnE,iDAmCwB;AACxB,2EAA0F;AAE1F,sEAAsE;AACtE,iEAAiE;AACjE,EAAE;AACF,iGAAiG;AACjG,mGAAmG;AACnG,qGAAqG;AACrG,kGAAkG;AACrF,QAAA,YAAY,GAA6C;IAClE,kBAAkB,EAAE,mCAAoB,CAAC,MAAM;IAC/C,gBAAgB,EAAE,iCAAkB,CAAC,MAAM;IAC3C,qBAAqB,EAAE,sCAAuB,CAAC,MAAM;IACrD,yBAAyB,EAAE,yCAA0B,CAAC,MAAM;IAC5D,gBAAgB,EAAE,iCAAkB,CAAC,MAAM;IAC3C,iBAAiB,EAAE,kCAAmB,CAAC,MAAM;IAC7C,sBAAsB,EAAE,uCAAwB,CAAC,MAAM;IACvD,kBAAkB,EAAE,oCAAqB,CAAC,MAAM;IAChD,gBAAgB,EAAE,kCAAmB,CAAC,MAAM;IAC5C,yBAAyB,EAAE,0CAA2B,CAAC,MAAM;IAC7D,qBAAqB,EAAE,sCAAuB,CAAC,MAAM;IACrD,sBAAsB,EAAE,uCAAwB,CAAC,MAAM;IACvD,mCAAmC,EAAE,iDAAkC,CAAC,MAAM;IAC9E,qBAAqB,EAAE,qCAAsB,CAAC,MAAM;IACpD,6CAA6C,EAAE,uEAA2C,CAAC,MAAM;IACjG,eAAe,EAAE,gCAAiB,CAAC,MAAM;IACzC,8BAA8B,EAAE,6CAA8B,CAAC,MAAM;IACrE,2BAA2B,EAAE,2CAA4B,CAAC,MAAM;IAChE,iDAAiD,EAAE,8DAA+C,CAAC,MAAM;IACzG,eAAe,EAAE,iCAAkB,CAAC,MAAM;IAC1C,UAAU,EAAE,4BAAa,CAAC,MAAM;IAChC,uBAAuB,EAAE,wCAAyB,CAAC,MAAM;IACzD,oBAAoB,EAAE,qCAAsB,CAAC,MAAM;IACnD,oBAAoB,EAAE,gDAAsB,CAAC,MAAM;IACnD,uBAAuB,EAAE,uCAAwB,CAAC,MAAM;IACxD,sBAAsB,EAAE,wCAAyB,CAAC,MAAM;IACxD,WAAW,EAAE,6BAAc,CAAC,MAAM;IAClC,UAAU,EAAE,4BAAa,CAAC,MAAM;IAChC,2BAA2B,EAAE,4CAA6B,CAAC,MAAM;IACjE,aAAa,EAAE,8BAAe,CAAC,MAAM;IACrC,oBAAoB,EAAE,oCAAqB,CAAC,MAAM;IAClD,iCAAiC,EAAE,kDAAmC,CAAC,MAAM;IAC7E,iCAAiC,EAAE,iDAAkC,CAAC,MAAM;IAC5E,sBAAsB,EAAE,wCAAyB,CAAC,MAAM;IACxD,0BAA0B,EAAE,2CAA4B,CAAC,MAAM;IAC/D,sBAAsB,EAAE,uCAAwB,CAAC,MAAM;CAC1D,CAAC;AAEF;;;;;;;;;GASG;AACH,qHAAqH;AACrH,SAAgB,gBAAgB,CAAC,SAAiB;IAC9C,MAAM,MAAM,GAAG,oBAAY,CAAC,SAAS,CAAC,CAAC;IACvC,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC7D,CAAC;AAED,mGAAmG;AACnG,oGAAoG;AACpG,sGAAsG;AACtG,gGAAgG;AAChG,SAAgB,YAAY;IACxB,OAAO,MAAM,CAAC,IAAI,CAAC,oBAAY,CAAC,CAAC;AACrC,CAAC","sourcesContent":["// The rule-name -> schema lookup table, split out of validate-config.ts (which hit the 700-line cap).\n// Every consumer of \"what fields does rule X have\" reads it from here: the validator, the missing-rule\n// snippet, and the installer's seeding (seed-entry.ts) — one table, so they cannot disagree.\nimport { FieldDef } from './field-def';\nimport { BranchStateGuardConfig } from './main-sync-guard-configs';\nimport {\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig,\n ThrowCauseRequiredConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n NoCustomCssConfig,\n NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n InjectAnnotationNotNeededForConcreteClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n BranchCreationGuardConfig,\n PrLifecycleGuardConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NxWiringConfig,\n DiGraphConfig,\n MissingDesignAnnotationConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n ValidateArchitectureUnchangedConfig,\n ValidateNoArchitectureCyclesConfig,\n ValidatePackageJsonConfig,\n ValidateVersionsLockedConfig,\n ValidateEslintSyncConfig,\n} from './rule-configs';\nimport { NoClientCreationOutsideServerOrClientConfig } from './no-client-creation-config';\n\n// Thin lookup table — each entry delegates to the class's own SCHEMA.\n// No field lists here; all schemas live with their config class.\n//\n// KEYED BY CONFIG KEY, not by rule name. For most rules those are the same string, but the three\n// hookGuard entries below are POLICIES implemented by more than one class: `branch-state-guard` is\n// read by the four branch-state guards and `pr-lifecycle-guard` by the four PR-lifecycle guards (see\n// AbstractRule.configKey). A rule NAME that is not a config key has no row here and never should.\nexport const RULE_SCHEMAS: Record<string, Record<string, FieldDef>> = {\n 'max-method-lines': MaxMethodLinesConfig.SCHEMA,\n 'max-file-lines': MaxFileLinesConfig.SCHEMA,\n 'require-return-type': RequireReturnTypeConfig.SCHEMA,\n 'no-inline-type-literals': NoInlineTypeLiteralsConfig.SCHEMA,\n 'no-any-unknown': NoAnyUnknownConfig.SCHEMA,\n 'no-implicit-any': NoImplicitAnyConfig.SCHEMA,\n 'prisma-validate-dtos': PrismaValidateDtosConfig.SCHEMA,\n 'prisma-converter': PrismaConverterConfig.SCHEMA,\n 'no-destructure': NoDestructureConfig.SCHEMA,\n 'no-unmanaged-exceptions': NoUnmanagedExceptionsConfig.SCHEMA,\n 'catch-error-pattern': CatchErrorPatternConfig.SCHEMA,\n 'throw-cause-required': ThrowCauseRequiredConfig.SCHEMA,\n 'angular-no-direct-api-in-resolver': AngularNoDirectApiInResolverConfig.SCHEMA,\n 'no-symbol-di-tokens': NoSymbolDiTokensConfig.SCHEMA,\n 'no-client-creation-outside-server-or-client': NoClientCreationOutsideServerOrClientConfig.SCHEMA,\n 'no-custom-css': NoCustomCssConfig.SCHEMA,\n 'no-process-exit-outside-main': NoProcessExitOutsideMainConfig.SCHEMA,\n 'no-function-outside-class': NoFunctionOutsideClassConfig.SCHEMA,\n 'inject-annotation-not-needed-for-concrete-class': InjectAnnotationNotNeededForConcreteClassConfig.SCHEMA,\n 'framework-tag': FrameworkTagConfig.SCHEMA,\n 'role-tag': RoleTagConfig.SCHEMA,\n 'branch-creation-guard': BranchCreationGuardConfig.SCHEMA,\n 'pr-lifecycle-guard': PrLifecycleGuardConfig.SCHEMA,\n 'branch-state-guard': BranchStateGuardConfig.SCHEMA,\n 'no-file-import-cycles': NoFileImportCyclesConfig.SCHEMA,\n 'runtime-architecture': RuntimeArchitectureConfig.SCHEMA,\n 'nx-wiring': NxWiringConfig.SCHEMA,\n 'di-graph': DiGraphConfig.SCHEMA,\n 'missing-design-annotation': MissingDesignAnnotationConfig.SCHEMA,\n 'no-js-files': NoJsFilesConfig.SCHEMA,\n 'validate-ts-in-src': ValidateTsInSrcConfig.SCHEMA,\n 'validate-architecture-unchanged': ValidateArchitectureUnchangedConfig.SCHEMA,\n 'validate-no-architecture-cycles': ValidateNoArchitectureCyclesConfig.SCHEMA,\n 'validate-packagejson': ValidatePackageJsonConfig.SCHEMA,\n 'validate-versions-locked': ValidateVersionsLockedConfig.SCHEMA,\n 'validate-eslint-sync': ValidateEslintSyncConfig.SCHEMA,\n};\n\n/**\n * The field names `configKey`'s schema accepts, or null when there is no schema (a custom rule from\n * `rulesDir`, or a name that is not a config key at all).\n *\n * Exists so the installer's N→1 retirement merge can drop fields the DESTINATION schema does not know\n * — `upsertPrCommand` folded from a retired guard entry into `pr-lifecycle-guard` would otherwise\n * produce a config the validator rejects on the very next call. Returning the names rather than the\n * FieldDefs keeps the schema objects themselves unexported: there is one reader of a schema's shape,\n * and it is this package's own validator.\n */\n// webpieces-disable no-function-outside-class -- pure lookup over the module-scope schema table, beside allRuleNames\nexport function schemaFieldNames(configKey: string): readonly string[] | null {\n const schema = RULE_SCHEMAS[configKey];\n return schema === undefined ? null : Object.keys(schema);\n}\n\n// Every built-in CONFIG KEY that has a typed schema (code rules + bash guards). The installer uses\n// this (with sectionForRule) to seed a fresh webpieces.config.json with every entry in its section.\n// It is the key set, not the class set: four classes behind `branch-state-guard` contribute one name.\n// webpieces-disable no-function-outside-class -- pure lookup over the module-scope schema table\nexport function allRuleNames(): readonly string[] {\n return Object.keys(RULE_SCHEMAS);\n}\n"]}
1
+ {"version":3,"file":"rule-schemas.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/rule-schemas.ts"],"names":[],"mappings":";;;AAsGA,4CAGC;AAMD,oCAEC;AA7GD,uEAAmE;AACnE,iDAmCwB;AACxB,2EAA0F;AAC1F,mEAAwE;AAExE,sEAAsE;AACtE,iEAAiE;AACjE,EAAE;AACF,iGAAiG;AACjG,mGAAmG;AACnG,qGAAqG;AACrG,kGAAkG;AACrF,QAAA,YAAY,GAA6C;IAClE,kBAAkB,EAAE,mCAAoB,CAAC,MAAM;IAC/C,gBAAgB,EAAE,iCAAkB,CAAC,MAAM;IAC3C,qBAAqB,EAAE,sCAAuB,CAAC,MAAM;IACrD,yBAAyB,EAAE,yCAA0B,CAAC,MAAM;IAC5D,gBAAgB,EAAE,iCAAkB,CAAC,MAAM;IAC3C,iBAAiB,EAAE,kCAAmB,CAAC,MAAM;IAC7C,sBAAsB,EAAE,uCAAwB,CAAC,MAAM;IACvD,kBAAkB,EAAE,oCAAqB,CAAC,MAAM;IAChD,gBAAgB,EAAE,kCAAmB,CAAC,MAAM;IAC5C,yBAAyB,EAAE,0CAA2B,CAAC,MAAM;IAC7D,qBAAqB,EAAE,sCAAuB,CAAC,MAAM;IACrD,sBAAsB,EAAE,uCAAwB,CAAC,MAAM;IACvD,mCAAmC,EAAE,iDAAkC,CAAC,MAAM;IAC9E,qBAAqB,EAAE,qCAAsB,CAAC,MAAM;IACpD,6CAA6C,EAAE,uEAA2C,CAAC,MAAM;IACjG,eAAe,EAAE,gCAAiB,CAAC,MAAM;IACzC,6BAA6B,EAAE,qDAA6B,CAAC,MAAM;IACnE,8BAA8B,EAAE,6CAA8B,CAAC,MAAM;IACrE,2BAA2B,EAAE,2CAA4B,CAAC,MAAM;IAChE,iDAAiD,EAAE,8DAA+C,CAAC,MAAM;IACzG,eAAe,EAAE,iCAAkB,CAAC,MAAM;IAC1C,UAAU,EAAE,4BAAa,CAAC,MAAM;IAChC,uBAAuB,EAAE,wCAAyB,CAAC,MAAM;IACzD,oBAAoB,EAAE,qCAAsB,CAAC,MAAM;IACnD,oBAAoB,EAAE,gDAAsB,CAAC,MAAM;IACnD,uBAAuB,EAAE,uCAAwB,CAAC,MAAM;IACxD,sBAAsB,EAAE,wCAAyB,CAAC,MAAM;IACxD,WAAW,EAAE,6BAAc,CAAC,MAAM;IAClC,UAAU,EAAE,4BAAa,CAAC,MAAM;IAChC,2BAA2B,EAAE,4CAA6B,CAAC,MAAM;IACjE,aAAa,EAAE,8BAAe,CAAC,MAAM;IACrC,oBAAoB,EAAE,oCAAqB,CAAC,MAAM;IAClD,iCAAiC,EAAE,kDAAmC,CAAC,MAAM;IAC7E,iCAAiC,EAAE,iDAAkC,CAAC,MAAM;IAC5E,sBAAsB,EAAE,wCAAyB,CAAC,MAAM;IACxD,0BAA0B,EAAE,2CAA4B,CAAC,MAAM;IAC/D,sBAAsB,EAAE,uCAAwB,CAAC,MAAM;CAC1D,CAAC;AAEF;;;;;;;;;GASG;AACH,qHAAqH;AACrH,SAAgB,gBAAgB,CAAC,SAAiB;IAC9C,MAAM,MAAM,GAAG,oBAAY,CAAC,SAAS,CAAC,CAAC;IACvC,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC7D,CAAC;AAED,mGAAmG;AACnG,oGAAoG;AACpG,sGAAsG;AACtG,gGAAgG;AAChG,SAAgB,YAAY;IACxB,OAAO,MAAM,CAAC,IAAI,CAAC,oBAAY,CAAC,CAAC;AACrC,CAAC","sourcesContent":["// The rule-name -> schema lookup table, split out of validate-config.ts (which hit the 700-line cap).\n// Every consumer of \"what fields does rule X have\" reads it from here: the validator, the missing-rule\n// snippet, and the installer's seeding (seed-entry.ts) — one table, so they cannot disagree.\nimport { FieldDef } from './field-def';\nimport { BranchStateGuardConfig } from './main-sync-guard-configs';\nimport {\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig,\n ThrowCauseRequiredConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n NoCustomCssConfig,\n NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n InjectAnnotationNotNeededForConcreteClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n BranchCreationGuardConfig,\n PrLifecycleGuardConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NxWiringConfig,\n DiGraphConfig,\n MissingDesignAnnotationConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n ValidateArchitectureUnchangedConfig,\n ValidateNoArchitectureCyclesConfig,\n ValidatePackageJsonConfig,\n ValidateVersionsLockedConfig,\n ValidateEslintSyncConfig,\n} from './rule-configs';\nimport { NoClientCreationOutsideServerOrClientConfig } from './no-client-creation-config';\nimport { NoStatePathsInTemplatesConfig } from './no-state-paths-config';\n\n// Thin lookup table — each entry delegates to the class's own SCHEMA.\n// No field lists here; all schemas live with their config class.\n//\n// KEYED BY CONFIG KEY, not by rule name. For most rules those are the same string, but the three\n// hookGuard entries below are POLICIES implemented by more than one class: `branch-state-guard` is\n// read by the four branch-state guards and `pr-lifecycle-guard` by the four PR-lifecycle guards (see\n// AbstractRule.configKey). A rule NAME that is not a config key has no row here and never should.\nexport const RULE_SCHEMAS: Record<string, Record<string, FieldDef>> = {\n 'max-method-lines': MaxMethodLinesConfig.SCHEMA,\n 'max-file-lines': MaxFileLinesConfig.SCHEMA,\n 'require-return-type': RequireReturnTypeConfig.SCHEMA,\n 'no-inline-type-literals': NoInlineTypeLiteralsConfig.SCHEMA,\n 'no-any-unknown': NoAnyUnknownConfig.SCHEMA,\n 'no-implicit-any': NoImplicitAnyConfig.SCHEMA,\n 'prisma-validate-dtos': PrismaValidateDtosConfig.SCHEMA,\n 'prisma-converter': PrismaConverterConfig.SCHEMA,\n 'no-destructure': NoDestructureConfig.SCHEMA,\n 'no-unmanaged-exceptions': NoUnmanagedExceptionsConfig.SCHEMA,\n 'catch-error-pattern': CatchErrorPatternConfig.SCHEMA,\n 'throw-cause-required': ThrowCauseRequiredConfig.SCHEMA,\n 'angular-no-direct-api-in-resolver': AngularNoDirectApiInResolverConfig.SCHEMA,\n 'no-symbol-di-tokens': NoSymbolDiTokensConfig.SCHEMA,\n 'no-client-creation-outside-server-or-client': NoClientCreationOutsideServerOrClientConfig.SCHEMA,\n 'no-custom-css': NoCustomCssConfig.SCHEMA,\n 'no-state-paths-in-templates': NoStatePathsInTemplatesConfig.SCHEMA,\n 'no-process-exit-outside-main': NoProcessExitOutsideMainConfig.SCHEMA,\n 'no-function-outside-class': NoFunctionOutsideClassConfig.SCHEMA,\n 'inject-annotation-not-needed-for-concrete-class': InjectAnnotationNotNeededForConcreteClassConfig.SCHEMA,\n 'framework-tag': FrameworkTagConfig.SCHEMA,\n 'role-tag': RoleTagConfig.SCHEMA,\n 'branch-creation-guard': BranchCreationGuardConfig.SCHEMA,\n 'pr-lifecycle-guard': PrLifecycleGuardConfig.SCHEMA,\n 'branch-state-guard': BranchStateGuardConfig.SCHEMA,\n 'no-file-import-cycles': NoFileImportCyclesConfig.SCHEMA,\n 'runtime-architecture': RuntimeArchitectureConfig.SCHEMA,\n 'nx-wiring': NxWiringConfig.SCHEMA,\n 'di-graph': DiGraphConfig.SCHEMA,\n 'missing-design-annotation': MissingDesignAnnotationConfig.SCHEMA,\n 'no-js-files': NoJsFilesConfig.SCHEMA,\n 'validate-ts-in-src': ValidateTsInSrcConfig.SCHEMA,\n 'validate-architecture-unchanged': ValidateArchitectureUnchangedConfig.SCHEMA,\n 'validate-no-architecture-cycles': ValidateNoArchitectureCyclesConfig.SCHEMA,\n 'validate-packagejson': ValidatePackageJsonConfig.SCHEMA,\n 'validate-versions-locked': ValidateVersionsLockedConfig.SCHEMA,\n 'validate-eslint-sync': ValidateEslintSyncConfig.SCHEMA,\n};\n\n/**\n * The field names `configKey`'s schema accepts, or null when there is no schema (a custom rule from\n * `rulesDir`, or a name that is not a config key at all).\n *\n * Exists so the installer's N→1 retirement merge can drop fields the DESTINATION schema does not know\n * — `upsertPrCommand` folded from a retired guard entry into `pr-lifecycle-guard` would otherwise\n * produce a config the validator rejects on the very next call. Returning the names rather than the\n * FieldDefs keeps the schema objects themselves unexported: there is one reader of a schema's shape,\n * and it is this package's own validator.\n */\n// webpieces-disable no-function-outside-class -- pure lookup over the module-scope schema table, beside allRuleNames\nexport function schemaFieldNames(configKey: string): readonly string[] | null {\n const schema = RULE_SCHEMAS[configKey];\n return schema === undefined ? null : Object.keys(schema);\n}\n\n// Every built-in CONFIG KEY that has a typed schema (code rules + bash guards). The installer uses\n// this (with sectionForRule) to seed a fresh webpieces.config.json with every entry in its section.\n// It is the key set, not the class set: four classes behind `branch-state-guard` contribute one name.\n// webpieces-disable no-function-outside-class -- pure lookup over the module-scope schema table\nexport function allRuleNames(): readonly string[] {\n return Object.keys(RULE_SCHEMAS);\n}\n"]}
@@ -230,7 +230,9 @@ flow are not interchangeable.
230
230
  `origin/main`, which loses a NAME and not a commit. Run it after the merge lands, or any time the
231
231
  branch cap blocks you. It recomputes the verdicts itself, deletes one branch per command, and logs
232
232
  every deletion with its pre-delete SHA plus a `recover=` command in
233
- `.webpieces/logs/branch-mutations.log`. **Use this instead of `git branch -D`.**
233
+ `{{BRANCH_MUTATION_LOG}}` — the log for THIS tree, resolved when this doc was written (it is
234
+ per-worktree, so a relative spelling of it is wrong in half the trees that read this).
235
+ **Use this instead of `git branch -D`.**
234
236
 
235
237
  A husk is spared only when somebody is provably holding it: a worktree with uncommitted or
236
238
  untracked files, one locked by a live agent, the tree you are standing in, a detached HEAD — each