@webpieces/rules-config 0.3.190 → 0.3.192
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.
- package/package.json +1 -1
- package/src/commands-config.d.ts +16 -0
- package/src/commands-config.js +43 -0
- package/src/commands-config.js.map +1 -0
- package/src/config-file.d.ts +2 -0
- package/src/config-file.js.map +1 -1
- package/src/constants.js +1 -1
- package/src/constants.js.map +1 -1
- package/src/index.d.ts +5 -1
- package/src/index.js +19 -2
- package/src/index.js.map +1 -1
- package/src/load-config.d.ts +6 -3
- package/src/load-config.js +35 -9
- package/src/load-config.js.map +1 -1
- package/src/pr-gate-config.d.ts +3 -2
- package/src/pr-gate-config.js +16 -8
- package/src/pr-gate-config.js.map +1 -1
- package/src/review-json.d.ts +18 -0
- package/src/review-json.js +115 -0
- package/src/review-json.js.map +1 -0
- package/src/rule-configs.d.ts +1 -0
- package/src/rule-configs.js +5 -1
- package/src/rule-configs.js.map +1 -1
- package/src/sections.d.ts +4 -0
- package/src/sections.js +24 -0
- package/src/sections.js.map +1 -0
- package/src/validate-config.d.ts +15 -0
- package/src/validate-config.js +75 -10
- package/src/validate-config.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/rules-config",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.192",
|
|
4
4
|
"description": "Shared webpieces.config.json loader. Single source of truth for validation rule configuration consumed by @webpieces/ai-hook-rules, @webpieces/code-rules, and @webpieces/nx-webpieces-rules.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PrGateConfig } from './pr-gate-config';
|
|
2
|
+
export declare const DEFAULT_UPSERT_PR_COMMAND = "pnpm wp-start-upsert-pr";
|
|
3
|
+
export declare const DEFAULT_MERGE_COMPLETE_COMMAND = "pnpm wp-finish-upsert-pr";
|
|
4
|
+
export declare class CommandsConfig {
|
|
5
|
+
prGate: PrGateConfig;
|
|
6
|
+
upsertPr: string;
|
|
7
|
+
mergeComplete: string;
|
|
8
|
+
constructor(prGate: PrGateConfig, upsertPr: string, mergeComplete: string);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Build a CommandsConfig from the already-parsed `commands` section, falling back to defaults for any
|
|
12
|
+
* field the consumer omits. `legacyPrGate` is the top-level `pr-gate` block (pre-migration layout);
|
|
13
|
+
* it is used only as a fallback so an un-migrated file still loads its gate config. Pure transform —
|
|
14
|
+
* the structural validation happens in loadAndValidate.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildCommandsConfig(section: unknown, legacyPrGate?: unknown): CommandsConfig;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The "commands" section of webpieces.config.json. It configures the gated command endpoints that
|
|
3
|
+
// the bash guards point agents toward (instead of running raw `gh pr create` / finishing a merge by
|
|
4
|
+
// hand), plus the pr-gate build dashboard. pr-gate lives here (not at the top level) because it
|
|
5
|
+
// configures the wp-upsert-pr / wp-git-merge-complete commands — the guards only POINT at them.
|
|
6
|
+
//
|
|
7
|
+
// Data-only (per CLAUDE.md, classes for data). Built + validated by loadAndValidate (load-config.ts).
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.CommandsConfig = exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = void 0;
|
|
10
|
+
exports.buildCommandsConfig = buildCommandsConfig;
|
|
11
|
+
const pr_gate_config_1 = require("./pr-gate-config");
|
|
12
|
+
// Canonical gated commands. Guards default their command hints to these so a project that renames a
|
|
13
|
+
// command edits it in ONE place (the commands section) and every guard message follows.
|
|
14
|
+
exports.DEFAULT_UPSERT_PR_COMMAND = 'pnpm wp-start-upsert-pr';
|
|
15
|
+
exports.DEFAULT_MERGE_COMPLETE_COMMAND = 'pnpm wp-finish-upsert-pr';
|
|
16
|
+
class CommandsConfig {
|
|
17
|
+
prGate;
|
|
18
|
+
// Command the pr-creation-guard tells agents to run instead of `gh pr create`.
|
|
19
|
+
upsertPr;
|
|
20
|
+
// Command the merge-in-progress-guard tells agents to run to finish a 3-point merge.
|
|
21
|
+
mergeComplete;
|
|
22
|
+
constructor(prGate, upsertPr, mergeComplete) {
|
|
23
|
+
this.prGate = prGate;
|
|
24
|
+
this.upsertPr = upsertPr;
|
|
25
|
+
this.mergeComplete = mergeComplete;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.CommandsConfig = CommandsConfig;
|
|
29
|
+
/**
|
|
30
|
+
* Build a CommandsConfig from the already-parsed `commands` section, falling back to defaults for any
|
|
31
|
+
* field the consumer omits. `legacyPrGate` is the top-level `pr-gate` block (pre-migration layout);
|
|
32
|
+
* it is used only as a fallback so an un-migrated file still loads its gate config. Pure transform —
|
|
33
|
+
* the structural validation happens in loadAndValidate.
|
|
34
|
+
*/
|
|
35
|
+
// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed here
|
|
36
|
+
function buildCommandsConfig(section, legacyPrGate) {
|
|
37
|
+
const raw = (typeof section === 'object' && section !== null)
|
|
38
|
+
? section
|
|
39
|
+
: {};
|
|
40
|
+
const prGateRaw = raw['pr-gate'] ?? legacyPrGate;
|
|
41
|
+
return new CommandsConfig((0, pr_gate_config_1.buildPrGateConfig)(prGateRaw), typeof raw.upsertPr === 'string' && raw.upsertPr.trim() !== '' ? raw.upsertPr : exports.DEFAULT_UPSERT_PR_COMMAND, typeof raw.mergeComplete === 'string' && raw.mergeComplete.trim() !== '' ? raw.mergeComplete : exports.DEFAULT_MERGE_COMPLETE_COMMAND);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=commands-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/commands-config.ts"],"names":[],"mappings":";AAAA,kGAAkG;AAClG,oGAAoG;AACpG,gGAAgG;AAChG,gGAAgG;AAChG,EAAE;AACF,sGAAsG;;;AAqCtG,kDAUC;AA7CD,qDAAmE;AAEnE,oGAAoG;AACpG,wFAAwF;AAC3E,QAAA,yBAAyB,GAAG,yBAAyB,CAAC;AACtD,QAAA,8BAA8B,GAAG,0BAA0B,CAAC;AAEzE,MAAa,cAAc;IACvB,MAAM,CAAe;IACrB,+EAA+E;IAC/E,QAAQ,CAAS;IACjB,qFAAqF;IACrF,aAAa,CAAS;IAEtB,YAAY,MAAoB,EAAE,QAAgB,EAAE,aAAqB;QACrE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IACvC,CAAC;CACJ;AAZD,wCAYC;AASD;;;;;GAKG;AACH,4FAA4F;AAC5F,SAAgB,mBAAmB,CAAC,OAAgB,EAAE,YAAsB;IACxE,MAAM,GAAG,GAAuB,CAAC,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,CAAC;QAC7E,CAAC,CAAE,OAA8B;QACjC,CAAC,CAAC,EAAE,CAAC;IACT,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC,IAAI,YAAY,CAAC;IACjD,OAAO,IAAI,cAAc,CACrB,IAAA,kCAAiB,EAAC,SAAS,CAAC,EAC5B,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,iCAAyB,EACzG,OAAO,GAAG,CAAC,aAAa,KAAK,QAAQ,IAAI,GAAG,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,sCAA8B,CAChI,CAAC;AACN,CAAC","sourcesContent":["// The \"commands\" section of webpieces.config.json. It configures the gated command endpoints that\n// the bash guards point agents toward (instead of running raw `gh pr create` / finishing a merge by\n// hand), plus the pr-gate build dashboard. pr-gate lives here (not at the top level) because it\n// configures the wp-upsert-pr / wp-git-merge-complete commands — the guards only POINT at them.\n//\n// Data-only (per CLAUDE.md, classes for data). Built + validated by loadAndValidate (load-config.ts).\n\nimport { PrGateConfig, buildPrGateConfig } from './pr-gate-config';\n\n// Canonical gated commands. Guards default their command hints to these so a project that renames a\n// command edits it in ONE place (the commands section) and every guard message follows.\nexport const DEFAULT_UPSERT_PR_COMMAND = 'pnpm wp-start-upsert-pr';\nexport const DEFAULT_MERGE_COMPLETE_COMMAND = 'pnpm wp-finish-upsert-pr';\n\nexport class CommandsConfig {\n prGate: PrGateConfig;\n // Command the pr-creation-guard tells agents to run instead of `gh pr create`.\n upsertPr: string;\n // Command the merge-in-progress-guard tells agents to run to finish a 3-point merge.\n mergeComplete: string;\n\n constructor(prGate: PrGateConfig, upsertPr: string, mergeComplete: string) {\n this.prGate = prGate;\n this.upsertPr = upsertPr;\n this.mergeComplete = mergeComplete;\n }\n}\n\ninterface RawCommandsSection {\n // webpieces-disable no-any-unknown -- opaque pr-gate JSON, validated by validatePrGateSection\n 'pr-gate'?: unknown;\n upsertPr?: string;\n mergeComplete?: string;\n}\n\n/**\n * Build a CommandsConfig from the already-parsed `commands` section, falling back to defaults for any\n * field the consumer omits. `legacyPrGate` is the top-level `pr-gate` block (pre-migration layout);\n * it is used only as a fallback so an un-migrated file still loads its gate config. Pure transform —\n * the structural validation happens in loadAndValidate.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed here\nexport function buildCommandsConfig(section: unknown, legacyPrGate?: unknown): CommandsConfig {\n const raw: RawCommandsSection = (typeof section === 'object' && section !== null)\n ? (section as RawCommandsSection)\n : {};\n const prGateRaw = raw['pr-gate'] ?? legacyPrGate;\n return new CommandsConfig(\n buildPrGateConfig(prGateRaw),\n typeof raw.upsertPr === 'string' && raw.upsertPr.trim() !== '' ? raw.upsertPr : DEFAULT_UPSERT_PR_COMMAND,\n typeof raw.mergeComplete === 'string' && raw.mergeComplete.trim() !== '' ? raw.mergeComplete : DEFAULT_MERGE_COMPLETE_COMMAND,\n );\n}\n"]}
|
package/src/config-file.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export declare const CONFIG_FILENAME = "webpieces.config.json";
|
|
|
2
2
|
export interface RawConfigFile {
|
|
3
3
|
extends?: string;
|
|
4
4
|
rules?: Record<string, Record<string, unknown>>;
|
|
5
|
+
hookGuards?: Record<string, Record<string, unknown>>;
|
|
6
|
+
commands?: unknown;
|
|
5
7
|
rulesDir?: string[];
|
|
6
8
|
'pr-gate'?: unknown;
|
|
7
9
|
}
|
package/src/config-file.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-file.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/config-file.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"config-file.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/config-file.ts"],"names":[],"mappings":";;;AA+BA,wCASC;AAKD,sCAaC;;AA1DD,+CAAyB;AACzB,mDAA6B;AAE7B,uDAAkD;AAClD,yCAAqC;AAExB,QAAA,eAAe,GAAG,uBAAuB,CAAC;AAsBvD;;GAEG;AACH,SAAgB,cAAc,CAAC,QAAgB;IAC3C,IAAI,GAAG,GAAG,QAAQ,CAAC;IACnB,OAAO,IAAI,EAAE,CAAC;QACV,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,uBAAe,CAAC,CAAC;QAChD,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAChC,GAAG,GAAG,MAAM,CAAC;IACjB,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAgB,aAAa,CAAC,UAAkB;IAC5C,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAChD,8DAA8D;IAC9D,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;IAC5C,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,IAAI,+BAAa,CACnB,sEAAsE;YACtE,gBAAgB,KAAK,CAAC,OAAO,IAAI;YACjC,SAAS,UAAU,EAAE,CACxB,CAAC;IACN,CAAC;AACL,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\nexport const CONFIG_FILENAME = 'webpieces.config.json';\n\n// Raw shape of webpieces.config.json as parsed from JSON, before validation/typing.\n// - `rules` — code-style validators (scope edit/file).\n// - `hookGuards` — git/PR/branch protection guards (scope bash).\n// - `commands` — gated command config the guards point at; `pr-gate` lives inside it. Carried as\n// opaque JSON because its nested `gates` array can't be expressed in the FieldDef\n// schema; validated structurally by validateCommandsSection.\n// - `pr-gate` — DEPRECATED top-level block (pre-migration layout). Read only as a back-compat\n// fallback / to emit a \"move it under commands\" migration error.\n// webpieces-disable no-any-unknown -- consumer JSON config has opaque rule option values\nexport interface RawConfigFile {\n extends?: string;\n rules?: Record<string, Record<string, unknown>>;\n hookGuards?: Record<string, Record<string, unknown>>;\n // webpieces-disable no-any-unknown -- opaque commands JSON, validated by validateCommandsSection\n commands?: unknown;\n rulesDir?: string[];\n // webpieces-disable no-any-unknown -- DEPRECATED top-level pr-gate, migrated under `commands`\n 'pr-gate'?: unknown;\n}\n\n/**\n * Walk up from `startDir` looking for webpieces.config.json. Returns its absolute path or null.\n */\nexport function findConfigFile(startDir: string): string | null {\n let dir = startDir;\n while (true) {\n const primary = path.join(dir, CONFIG_FILENAME);\n if (fs.existsSync(primary)) return primary;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * Read + JSON.parse webpieces.config.json, surfacing parse failures as a readable InformAiError.\n */\nexport function readRawConfig(configPath: string): RawConfigFile {\n const raw = fs.readFileSync(configPath, 'utf8');\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(raw) as RawConfigFile;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(\n `webpieces.config.json has invalid JSON — fix the file, then retry.\\n` +\n `Parse error: ${error.message}\\n` +\n `File: ${configPath}`,\n );\n }\n}\n"]}
|
package/src/constants.js
CHANGED
|
@@ -43,7 +43,7 @@ exports.MERGE_IN_PROGRESS_FILE = 'merge-in-progress.json';
|
|
|
43
43
|
// Proof-of-work the AI must produce for every conflicted file it resolves during a 3-point
|
|
44
44
|
// merge: a short explanation written NEXT TO that file's 3-point context (the same
|
|
45
45
|
// `updatemain-<safe_path>/` dir that holds A-forkpoint.txt / B-A.diff / C-A.diff). The
|
|
46
|
-
//
|
|
46
|
+
// wp-finish-upsert-pr gate requires a non-empty file of this name per conflicted file before passing —
|
|
47
47
|
// it is the only check on the part of the process the AI actually owns (resolving files). Using a
|
|
48
48
|
// sidecar file (rather than an in-source comment) works for any file type, including comment-less
|
|
49
49
|
// ones like JSON and files resolved by deletion.
|
package/src/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/constants.ts"],"names":[],"mappings":";AAAA,iFAAiF;AACjF,sEAAsE;AACtE,EAAE;AACF,yFAAyF;AACzF,qFAAqF;AACrF,kFAAkF;;;AAoDlF,gCAEC;AApDY,QAAA,iBAAiB,GAAG,mBAAmB,CAAC;AAErD,kFAAkF;AAClF,uFAAuF;AACvF,uEAAuE;AACvE,sFAAsF;AACzE,QAAA,UAAU,GAAG;IACtB,cAAc,EAAE,gBAAgB;IAChC,eAAe,EAAE,iBAAiB;IAClC,cAAc,EAAE,gBAAgB;IAChC,uBAAuB,EAAE,yBAAyB;IAClD,mBAAmB,EAAE,qBAAqB;IAC1C,oBAAoB,EAAE,sBAAsB;IAC5C,mBAAmB,EAAE,qBAAqB;IAC1C,mBAAmB,EAAE,qBAAqB;IAC1C,eAAe,EAAE,iBAAiB;IAClC,sBAAsB,EAAE,wBAAwB;IAChD,gBAAgB,EAAE,kBAAkB;IACpC,qBAAqB,EAAE,uBAAuB;IAC9C,wBAAwB,EAAE,0BAA0B;IACpD,kBAAkB,EAAE,oBAAoB;CAClC,CAAC;AAEX,wFAAwF;AACxF,0FAA0F;AAC1F,sFAAsF;AACtF,0DAA0D;AAC1D,EAAE;AACF,mFAAmF;AACnF,yFAAyF;AACzF,yFAAyF;AAC5E,QAAA,iBAAiB,GAAG,YAAY,CAAC;AACjC,QAAA,gBAAgB,GAAG,QAAQ,CAAC;AAC5B,QAAA,sBAAsB,GAAG,wBAAwB,CAAC;AAE/D,2FAA2F;AAC3F,mFAAmF;AACnF,uFAAuF;AACvF,
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/constants.ts"],"names":[],"mappings":";AAAA,iFAAiF;AACjF,sEAAsE;AACtE,EAAE;AACF,yFAAyF;AACzF,qFAAqF;AACrF,kFAAkF;;;AAoDlF,gCAEC;AApDY,QAAA,iBAAiB,GAAG,mBAAmB,CAAC;AAErD,kFAAkF;AAClF,uFAAuF;AACvF,uEAAuE;AACvE,sFAAsF;AACzE,QAAA,UAAU,GAAG;IACtB,cAAc,EAAE,gBAAgB;IAChC,eAAe,EAAE,iBAAiB;IAClC,cAAc,EAAE,gBAAgB;IAChC,uBAAuB,EAAE,yBAAyB;IAClD,mBAAmB,EAAE,qBAAqB;IAC1C,oBAAoB,EAAE,sBAAsB;IAC5C,mBAAmB,EAAE,qBAAqB;IAC1C,mBAAmB,EAAE,qBAAqB;IAC1C,eAAe,EAAE,iBAAiB;IAClC,sBAAsB,EAAE,wBAAwB;IAChD,gBAAgB,EAAE,kBAAkB;IACpC,qBAAqB,EAAE,uBAAuB;IAC9C,wBAAwB,EAAE,0BAA0B;IACpD,kBAAkB,EAAE,oBAAoB;CAClC,CAAC;AAEX,wFAAwF;AACxF,0FAA0F;AAC1F,sFAAsF;AACtF,0DAA0D;AAC1D,EAAE;AACF,mFAAmF;AACnF,yFAAyF;AACzF,yFAAyF;AAC5E,QAAA,iBAAiB,GAAG,YAAY,CAAC;AACjC,QAAA,gBAAgB,GAAG,QAAQ,CAAC;AAC5B,QAAA,sBAAsB,GAAG,wBAAwB,CAAC;AAE/D,2FAA2F;AAC3F,mFAAmF;AACnF,uFAAuF;AACvF,uGAAuG;AACvG,kGAAkG;AAClG,kGAAkG;AAClG,iDAAiD;AACpC,QAAA,sBAAsB,GAAG,sBAAsB,CAAC;AAE7D;;;;;GAKG;AACH,SAAgB,UAAU,CAAC,IAAY,EAAE,QAAgB;IACrD,OAAO,IAAI,CAAC,QAAQ,CAAC,yBAAiB,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACvE,CAAC","sourcesContent":["// Single source of truth for the disable-comment token and rule-name identifiers\n// shared across rules-config, ai-hook-rules, code-rules, and pr-gate.\n//\n// There is exactly ONE disable form: `// webpieces-disable <rule>[, <rule2>] -- reason`.\n// The legacy `ai-hook-disable` alias and the `-file`/`-next`/`-all` variants and the\n// `*`/bare (no-rule) wildcard have been removed — every disable MUST name a rule.\n\nexport const WEBPIECES_DISABLE = 'webpieces-disable';\n\n// Rule-name tokens as they appear AFTER `webpieces-disable` in a disable comment.\n// Values must match existing comments exactly — changing a value silently breaks every\n// disable that names that rule. Note MAX_LINES_MODIFIED is a prefix of\n// MAX_LINES_MODIFIED_FILES (a historical substring-match quirk preserved on purpose).\nexport const RULE_NAMES = {\n NO_ANY_UNKNOWN: 'no-any-unknown',\n NO_IMPLICIT_ANY: 'no-implicit-any',\n NO_DESTRUCTURE: 'no-destructure',\n NO_UNMANAGED_EXCEPTIONS: 'no-unmanaged-exceptions',\n CATCH_ERROR_PATTERN: 'catch-error-pattern',\n THROW_CAUSE_REQUIRED: 'throw-cause-required',\n REQUIRE_RETURN_TYPE: 'require-return-type',\n NO_SYMBOL_DI_TOKENS: 'no-symbol-di-tokens',\n NO_INLINE_TYPES: 'no-inline-types',\n NO_DIRECT_API_RESOLVER: 'no-direct-api-resolver',\n PRISMA_CONVERTER: 'prisma-converter',\n MAX_LINES_NEW_METHODS: 'max-lines-new-methods',\n MAX_LINES_MODIFIED_FILES: 'max-lines-modified-files',\n MAX_LINES_MODIFIED: 'max-lines-modified',\n} as const;\n\n// Merge-state convention shared by the pr-gate scripts (which WRITE the marker during a\n// conflicted 3-point merge) and the ai-hook-rules merge-in-progress-guard (which READS it\n// to block commit/push/PR until the merge is validated). Kept here so neither package\n// depends on the other — they only share this vocabulary.\n//\n// `.webpieces/` is the single working dir for all webpieces tooling: ai-hook-rules\n// bootstrap/cache, the instruct-ai docs, and the per-feature merge-/review-/pr- workflow\n// dirs. It is gitignored. Only the prefixed workflow dirs are subject to 30-day cleanup.\nexport const WEBPIECES_TMP_DIR = '.webpieces';\nexport const MERGE_DIR_PREFIX = 'merge-';\nexport const MERGE_IN_PROGRESS_FILE = 'merge-in-progress.json';\n\n// Proof-of-work the AI must produce for every conflicted file it resolves during a 3-point\n// merge: a short explanation written NEXT TO that file's 3-point context (the same\n// `updatemain-<safe_path>/` dir that holds A-forkpoint.txt / B-A.diff / C-A.diff). The\n// wp-finish-upsert-pr gate requires a non-empty file of this name per conflicted file before passing —\n// it is the only check on the part of the process the AI actually owns (resolving files). Using a\n// sidecar file (rather than an in-source comment) works for any file type, including comment-less\n// ones like JSON and files resolved by deletion.\nexport const MERGE_EXPLANATION_FILE = 'merge-explanation.md';\n\n/**\n * Fast predicate: does this text carry a webpieces-disable for the given rule?\n * Line-agnostic — the caller decides which line(s) or block of text to feed it.\n * This is the cheap substring form used by code-rules detection and pr-gate's\n * dashboard grep/count. (ai-hook-rules uses a richer line-mapping parser.)\n */\nexport function hasDisable(text: string, ruleName: string): boolean {\n return text.includes(WEBPIECES_DISABLE) && text.includes(ruleName);\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -6,7 +6,9 @@ export { findConfigFile, CONFIG_FILENAME } from './config-file';
|
|
|
6
6
|
export { isPathExcluded } from './exclude-paths';
|
|
7
7
|
export { defaultRules, defaultRulesDir } from './default-rules';
|
|
8
8
|
export { loadTemplate, writeTemplateIfMissing, writeTemplate } from './load-template';
|
|
9
|
-
export { validateWebpiecesConfig, validatePrGateSection } from './validate-config';
|
|
9
|
+
export { validateWebpiecesConfig, validatePrGateSection, validateSectionPlacement, validateCommandsSection, allRuleNames } from './validate-config';
|
|
10
|
+
export type { ConfigSection } from './sections';
|
|
11
|
+
export { HOOK_GUARD_NAMES, isHookGuard, sectionForRule } from './sections';
|
|
10
12
|
export { FieldDef } from './field-def';
|
|
11
13
|
export type { SchemaShape } from './field-def';
|
|
12
14
|
export { shouldSkipRule, getCurrentBranch } from './skip-rule';
|
|
@@ -18,3 +20,5 @@ export { MaxMethodLinesConfig, MaxFileLinesConfig, RequireReturnTypeConfig, NoIn
|
|
|
18
20
|
export { METHOD_LIMIT_MODES, FILE_LIMIT_MODES, RETURN_TYPE_MODES, INLINE_TYPE_MODES, MODIFIED_CODE_MODES, PRISMA_DTOS_MODES, PRISMA_CONVERTER_MODES, DIRECT_API_RESOLVER_MODES, THROW_CAUSE_MODES, ON_OFF_MODES, VALIDATE_TS_MODES, } from './rule-configs';
|
|
19
21
|
export type { MethodLimitMode, FileLimitMode, ReturnTypeMode, InlineTypeMode, ModifiedCodeMode, PrismaValidateDtosMode, PrismaConverterMode, DirectApiResolverMode, ThrowCauseMode, OnOffMode, ValidateTsMode, } from './rule-configs';
|
|
20
22
|
export { GateDefinition, PrGateConfig, defaultGates, defaultPrGateConfig, buildPrGateConfig, } from './pr-gate-config';
|
|
23
|
+
export { ReviewJson, loadReviewJson, reviewJsonPath, reviewJsonSchemaHint, } from './review-json';
|
|
24
|
+
export { CommandsConfig, buildCommandsConfig, DEFAULT_UPSERT_PR_COMMAND, DEFAULT_MERGE_COMPLETE_COMMAND, } from './commands-config';
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.buildPrGateConfig = exports.defaultPrGateConfig = exports.defaultGates = exports.PrGateConfig = exports.GateDefinition = exports.VALIDATE_TS_MODES = exports.ON_OFF_MODES = exports.THROW_CAUSE_MODES = exports.DIRECT_API_RESOLVER_MODES = exports.PRISMA_CONVERTER_MODES = exports.PRISMA_DTOS_MODES = exports.MODIFIED_CODE_MODES = exports.INLINE_TYPE_MODES = exports.RETURN_TYPE_MODES = exports.FILE_LIMIT_MODES = exports.METHOD_LIMIT_MODES = exports.BaseRuleConfig = exports.ValidateTsInSrcConfig = exports.NoJsFilesConfig = exports.RuntimeArchitectureConfig = void 0;
|
|
3
|
+
exports.BranchCreationGuardConfig = exports.NoShellSubstitutionConfig = exports.NoSymbolDiTokensConfig = exports.AngularNoDirectApiInResolverConfig = exports.ThrowCauseRequiredConfig = exports.CatchErrorPatternConfig = exports.NoUnmanagedExceptionsConfig = exports.NoDestructureConfig = exports.PrismaConverterConfig = exports.PrismaValidateDtosConfig = exports.NoImplicitAnyConfig = exports.NoAnyUnknownConfig = exports.NoInlineTypeLiteralsConfig = exports.RequireReturnTypeConfig = exports.MaxFileLinesConfig = exports.MaxMethodLinesConfig = exports.WebpiecesRulesConfig = exports.MERGE_EXPLANATION_FILE = exports.MERGE_IN_PROGRESS_FILE = exports.MERGE_DIR_PREFIX = exports.WEBPIECES_TMP_DIR = exports.hasDisable = exports.RULE_NAMES = exports.WEBPIECES_DISABLE = exports.AbstractRule = exports.getCurrentBranch = exports.shouldSkipRule = exports.FieldDef = exports.sectionForRule = exports.isHookGuard = exports.HOOK_GUARD_NAMES = exports.allRuleNames = exports.validateCommandsSection = exports.validateSectionPlacement = exports.validatePrGateSection = exports.validateWebpiecesConfig = exports.writeTemplate = exports.writeTemplateIfMissing = exports.loadTemplate = exports.defaultRulesDir = exports.defaultRules = exports.isPathExcluded = exports.CONFIG_FILENAME = exports.findConfigFile = exports.LoadedConfig = exports.loadAndValidate = exports.toError = exports.InformAiError = exports.ResolvedRuleConfig = exports.ResolvedConfig = void 0;
|
|
4
|
+
exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = exports.buildCommandsConfig = exports.CommandsConfig = exports.reviewJsonSchemaHint = exports.reviewJsonPath = exports.loadReviewJson = exports.ReviewJson = exports.buildPrGateConfig = exports.defaultPrGateConfig = exports.defaultGates = exports.PrGateConfig = exports.GateDefinition = exports.VALIDATE_TS_MODES = exports.ON_OFF_MODES = exports.THROW_CAUSE_MODES = exports.DIRECT_API_RESOLVER_MODES = exports.PRISMA_CONVERTER_MODES = exports.PRISMA_DTOS_MODES = exports.MODIFIED_CODE_MODES = exports.INLINE_TYPE_MODES = exports.RETURN_TYPE_MODES = exports.FILE_LIMIT_MODES = exports.METHOD_LIMIT_MODES = exports.BaseRuleConfig = exports.ValidateTsInSrcConfig = exports.NoJsFilesConfig = exports.RuntimeArchitectureConfig = exports.NoFileImportCyclesConfig = exports.NoEditOnMainConfig = exports.NoDirectMainUpdateConfig = exports.PrMergeCleanupConfig = exports.MergeInProgressGuardConfig = exports.PrCreationGuardConfig = void 0;
|
|
5
5
|
var types_1 = require("./types");
|
|
6
6
|
Object.defineProperty(exports, "ResolvedConfig", { enumerable: true, get: function () { return types_1.ResolvedConfig; } });
|
|
7
7
|
Object.defineProperty(exports, "ResolvedRuleConfig", { enumerable: true, get: function () { return types_1.ResolvedRuleConfig; } });
|
|
@@ -27,6 +27,13 @@ Object.defineProperty(exports, "writeTemplate", { enumerable: true, get: functio
|
|
|
27
27
|
var validate_config_1 = require("./validate-config");
|
|
28
28
|
Object.defineProperty(exports, "validateWebpiecesConfig", { enumerable: true, get: function () { return validate_config_1.validateWebpiecesConfig; } });
|
|
29
29
|
Object.defineProperty(exports, "validatePrGateSection", { enumerable: true, get: function () { return validate_config_1.validatePrGateSection; } });
|
|
30
|
+
Object.defineProperty(exports, "validateSectionPlacement", { enumerable: true, get: function () { return validate_config_1.validateSectionPlacement; } });
|
|
31
|
+
Object.defineProperty(exports, "validateCommandsSection", { enumerable: true, get: function () { return validate_config_1.validateCommandsSection; } });
|
|
32
|
+
Object.defineProperty(exports, "allRuleNames", { enumerable: true, get: function () { return validate_config_1.allRuleNames; } });
|
|
33
|
+
var sections_1 = require("./sections");
|
|
34
|
+
Object.defineProperty(exports, "HOOK_GUARD_NAMES", { enumerable: true, get: function () { return sections_1.HOOK_GUARD_NAMES; } });
|
|
35
|
+
Object.defineProperty(exports, "isHookGuard", { enumerable: true, get: function () { return sections_1.isHookGuard; } });
|
|
36
|
+
Object.defineProperty(exports, "sectionForRule", { enumerable: true, get: function () { return sections_1.sectionForRule; } });
|
|
30
37
|
var field_def_1 = require("./field-def");
|
|
31
38
|
Object.defineProperty(exports, "FieldDef", { enumerable: true, get: function () { return field_def_1.FieldDef; } });
|
|
32
39
|
var skip_rule_1 = require("./skip-rule");
|
|
@@ -90,4 +97,14 @@ Object.defineProperty(exports, "PrGateConfig", { enumerable: true, get: function
|
|
|
90
97
|
Object.defineProperty(exports, "defaultGates", { enumerable: true, get: function () { return pr_gate_config_1.defaultGates; } });
|
|
91
98
|
Object.defineProperty(exports, "defaultPrGateConfig", { enumerable: true, get: function () { return pr_gate_config_1.defaultPrGateConfig; } });
|
|
92
99
|
Object.defineProperty(exports, "buildPrGateConfig", { enumerable: true, get: function () { return pr_gate_config_1.buildPrGateConfig; } });
|
|
100
|
+
var review_json_1 = require("./review-json");
|
|
101
|
+
Object.defineProperty(exports, "ReviewJson", { enumerable: true, get: function () { return review_json_1.ReviewJson; } });
|
|
102
|
+
Object.defineProperty(exports, "loadReviewJson", { enumerable: true, get: function () { return review_json_1.loadReviewJson; } });
|
|
103
|
+
Object.defineProperty(exports, "reviewJsonPath", { enumerable: true, get: function () { return review_json_1.reviewJsonPath; } });
|
|
104
|
+
Object.defineProperty(exports, "reviewJsonSchemaHint", { enumerable: true, get: function () { return review_json_1.reviewJsonSchemaHint; } });
|
|
105
|
+
var commands_config_1 = require("./commands-config");
|
|
106
|
+
Object.defineProperty(exports, "CommandsConfig", { enumerable: true, get: function () { return commands_config_1.CommandsConfig; } });
|
|
107
|
+
Object.defineProperty(exports, "buildCommandsConfig", { enumerable: true, get: function () { return commands_config_1.buildCommandsConfig; } });
|
|
108
|
+
Object.defineProperty(exports, "DEFAULT_UPSERT_PR_COMMAND", { enumerable: true, get: function () { return commands_config_1.DEFAULT_UPSERT_PR_COMMAND; } });
|
|
109
|
+
Object.defineProperty(exports, "DEFAULT_MERGE_COMPLETE_COMMAND", { enumerable: true, get: function () { return commands_config_1.DEFAULT_MERGE_COMPLETE_COMMAND; } });
|
|
93
110
|
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/index.ts"],"names":[],"mappings":";;;;AAAA,iCAA0E;AAAjE,uGAAA,cAAc,OAAA;AAAE,2GAAA,kBAAkB,OAAA;AAC3C,qDAAkD;AAAzC,gHAAA,aAAa,OAAA;AACtB,uCAAqC;AAA5B,mGAAA,OAAO,OAAA;AAChB,6CAA8D;AAArD,8GAAA,eAAe,OAAA;AAAE,2GAAA,YAAY,OAAA;AACtC,6CAAgE;AAAvD,6GAAA,cAAc,OAAA;AAAE,8GAAA,eAAe,OAAA;AACxC,iDAAiD;AAAxC,+GAAA,cAAc,OAAA;AACvB,iDAAgE;AAAvD,6GAAA,YAAY,OAAA;AAAE,gHAAA,eAAe,OAAA;AACtC,iDAAsF;AAA7E,6GAAA,YAAY,OAAA;AAAE,uHAAA,sBAAsB,OAAA;AAAE,8GAAA,aAAa,OAAA;AAC5D,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["export { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nexport { InformAiError } from './inform-ai-error';\nexport { toError } from './to-error';\nexport { loadAndValidate, LoadedConfig } from './load-config';\nexport { findConfigFile, CONFIG_FILENAME } from './config-file';\nexport { isPathExcluded } from './exclude-paths';\nexport { defaultRules, defaultRulesDir } from './default-rules';\nexport { loadTemplate, writeTemplateIfMissing, writeTemplate } from './load-template';\nexport { validateWebpiecesConfig, validatePrGateSection, validateSectionPlacement, validateCommandsSection, allRuleNames } from './validate-config';\nexport type { ConfigSection } from './sections';\nexport { HOOK_GUARD_NAMES, isHookGuard, sectionForRule } from './sections';\nexport { FieldDef } from './field-def';\nexport type { SchemaShape } from './field-def';\nexport { shouldSkipRule, getCurrentBranch } from './skip-rule';\nexport type { SkipRuleResult } from './skip-rule';\nexport { AbstractRule } from './abstract-rule';\nexport {\n WEBPIECES_DISABLE,\n RULE_NAMES,\n hasDisable,\n WEBPIECES_TMP_DIR,\n MERGE_DIR_PREFIX,\n MERGE_IN_PROGRESS_FILE,\n MERGE_EXPLANATION_FILE,\n} from './constants';\nexport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\nexport {\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 NoShellSubstitutionConfig,\n BranchCreationGuardConfig,\n PrCreationGuardConfig,\n MergeInProgressGuardConfig,\n PrMergeCleanupConfig,\n NoDirectMainUpdateConfig,\n NoEditOnMainConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n BaseRuleConfig,\n} from './rule-configs';\n// Mode unions + their value arrays — the single source of truth shared with code-rules.\nexport {\n METHOD_LIMIT_MODES,\n FILE_LIMIT_MODES,\n RETURN_TYPE_MODES,\n INLINE_TYPE_MODES,\n MODIFIED_CODE_MODES,\n PRISMA_DTOS_MODES,\n PRISMA_CONVERTER_MODES,\n DIRECT_API_RESOLVER_MODES,\n THROW_CAUSE_MODES,\n ON_OFF_MODES,\n VALIDATE_TS_MODES,\n} from './rule-configs';\nexport type {\n MethodLimitMode,\n FileLimitMode,\n ReturnTypeMode,\n InlineTypeMode,\n ModifiedCodeMode,\n PrismaValidateDtosMode,\n PrismaConverterMode,\n DirectApiResolverMode,\n ThrowCauseMode,\n OnOffMode,\n ValidateTsMode,\n} from './rule-configs';\nexport {\n GateDefinition,\n PrGateConfig,\n defaultGates,\n defaultPrGateConfig,\n buildPrGateConfig,\n} from './pr-gate-config';\nexport {\n ReviewJson,\n loadReviewJson,\n reviewJsonPath,\n reviewJsonSchemaHint,\n} from './review-json';\nexport {\n CommandsConfig,\n buildCommandsConfig,\n DEFAULT_UPSERT_PR_COMMAND,\n DEFAULT_MERGE_COMPLETE_COMMAND,\n} from './commands-config';\n"]}
|
package/src/load-config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CommandsConfig } from './commands-config';
|
|
1
2
|
import { PrGateConfig } from './pr-gate-config';
|
|
2
3
|
import { ResolvedConfig } from './types';
|
|
3
4
|
import { WebpiecesRulesConfig } from './WebpiecesRulesConfig';
|
|
@@ -5,16 +6,18 @@ import { WebpiecesRulesConfig } from './WebpiecesRulesConfig';
|
|
|
5
6
|
* Everything a consumer might need from webpieces.config.json, produced from ONE parse + ONE
|
|
6
7
|
* validation pass. Data-only (per CLAUDE.md, classes for data):
|
|
7
8
|
* - `resolved` — Map-based view merged with defaultRules (nx executors).
|
|
8
|
-
* - `rulesConfig` — typed WebpiecesRulesConfig (ai-hook-rules, code-rules).
|
|
9
|
-
* - `
|
|
9
|
+
* - `rulesConfig` — typed WebpiecesRulesConfig (ai-hook-rules, code-rules); rules + hookGuards merged.
|
|
10
|
+
* - `commands` — the `commands` section (gated commands + pr-gate).
|
|
11
|
+
* - `prGate` — convenience alias of `commands.prGate` (pr-gate scripts).
|
|
10
12
|
* - `configPath` — absolute path, or null when no config file was found.
|
|
11
13
|
*/
|
|
12
14
|
export declare class LoadedConfig {
|
|
13
15
|
readonly resolved: ResolvedConfig;
|
|
14
16
|
readonly rulesConfig: WebpiecesRulesConfig;
|
|
17
|
+
readonly commands: CommandsConfig;
|
|
15
18
|
readonly prGate: PrGateConfig;
|
|
16
19
|
readonly configPath: string | null;
|
|
17
|
-
constructor(resolved: ResolvedConfig, rulesConfig: WebpiecesRulesConfig, prGate: PrGateConfig, configPath: string | null);
|
|
20
|
+
constructor(resolved: ResolvedConfig, rulesConfig: WebpiecesRulesConfig, commands: CommandsConfig, prGate: PrGateConfig, configPath: string | null);
|
|
18
21
|
}
|
|
19
22
|
/**
|
|
20
23
|
* The single load+validate entry point for ALL consumers (ai-hook-rules, code-rules,
|
package/src/load-config.js
CHANGED
|
@@ -2,13 +2,28 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LoadedConfig = void 0;
|
|
4
4
|
exports.loadAndValidate = loadAndValidate;
|
|
5
|
+
const commands_config_1 = require("./commands-config");
|
|
5
6
|
const config_file_1 = require("./config-file");
|
|
6
7
|
const default_rules_1 = require("./default-rules");
|
|
7
8
|
const inform_ai_error_1 = require("./inform-ai-error");
|
|
8
|
-
const pr_gate_config_1 = require("./pr-gate-config");
|
|
9
9
|
const types_1 = require("./types");
|
|
10
10
|
const validate_config_1 = require("./validate-config");
|
|
11
11
|
const WebpiecesRulesConfig_1 = require("./WebpiecesRulesConfig");
|
|
12
|
+
// Inject the canonical command strings (from the `commands` section) as the DEFAULT for the guards
|
|
13
|
+
// that surface them in their fix hints, so a project renames a command in one place. Only fills a
|
|
14
|
+
// gap — an explicit per-guard override wins. Mutates the merged guard entries in place.
|
|
15
|
+
function applyCommandDefaults(
|
|
16
|
+
// webpieces-disable no-any-unknown -- opaque merged rule/guard map
|
|
17
|
+
rules, commands) {
|
|
18
|
+
const prCreation = rules['pr-creation-guard'];
|
|
19
|
+
if (prCreation && prCreation['upsertPrCommand'] === undefined) {
|
|
20
|
+
prCreation['upsertPrCommand'] = commands.upsertPr;
|
|
21
|
+
}
|
|
22
|
+
const mergeInProgress = rules['merge-in-progress-guard'];
|
|
23
|
+
if (mergeInProgress && mergeInProgress['mergeCompleteCommand'] === undefined) {
|
|
24
|
+
mergeInProgress['mergeCompleteCommand'] = commands.mergeComplete;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
12
27
|
// webpieces-disable no-any-unknown -- merging opaque option bags from config JSON
|
|
13
28
|
function mergeRule(
|
|
14
29
|
// webpieces-disable no-any-unknown -- opaque option bag
|
|
@@ -44,18 +59,21 @@ rawRules, rulesDir) {
|
|
|
44
59
|
* Everything a consumer might need from webpieces.config.json, produced from ONE parse + ONE
|
|
45
60
|
* validation pass. Data-only (per CLAUDE.md, classes for data):
|
|
46
61
|
* - `resolved` — Map-based view merged with defaultRules (nx executors).
|
|
47
|
-
* - `rulesConfig` — typed WebpiecesRulesConfig (ai-hook-rules, code-rules).
|
|
48
|
-
* - `
|
|
62
|
+
* - `rulesConfig` — typed WebpiecesRulesConfig (ai-hook-rules, code-rules); rules + hookGuards merged.
|
|
63
|
+
* - `commands` — the `commands` section (gated commands + pr-gate).
|
|
64
|
+
* - `prGate` — convenience alias of `commands.prGate` (pr-gate scripts).
|
|
49
65
|
* - `configPath` — absolute path, or null when no config file was found.
|
|
50
66
|
*/
|
|
51
67
|
class LoadedConfig {
|
|
52
68
|
resolved;
|
|
53
69
|
rulesConfig;
|
|
70
|
+
commands;
|
|
54
71
|
prGate;
|
|
55
72
|
configPath;
|
|
56
|
-
constructor(resolved, rulesConfig, prGate, configPath) {
|
|
73
|
+
constructor(resolved, rulesConfig, commands, prGate, configPath) {
|
|
57
74
|
this.resolved = resolved;
|
|
58
75
|
this.rulesConfig = rulesConfig;
|
|
76
|
+
this.commands = commands;
|
|
59
77
|
this.prGate = prGate;
|
|
60
78
|
this.configPath = configPath;
|
|
61
79
|
}
|
|
@@ -70,19 +88,28 @@ exports.LoadedConfig = LoadedConfig;
|
|
|
70
88
|
function loadAndValidate(cwd) {
|
|
71
89
|
const configPath = (0, config_file_1.findConfigFile)(cwd);
|
|
72
90
|
if (!configPath) {
|
|
73
|
-
|
|
91
|
+
const emptyCommands = (0, commands_config_1.buildCommandsConfig)(undefined);
|
|
92
|
+
return new LoadedConfig(new types_1.ResolvedConfig(new Map(), new Set(), [], null), new WebpiecesRulesConfig_1.WebpiecesRulesConfig(), emptyCommands, emptyCommands.prGate, null);
|
|
74
93
|
}
|
|
75
94
|
const consumerConfig = (0, config_file_1.readRawConfig)(configPath);
|
|
76
|
-
const
|
|
95
|
+
const rulesSection = consumerConfig.rules || {};
|
|
96
|
+
const hookGuardsSection = consumerConfig.hookGuards || {};
|
|
97
|
+
const legacyPrGate = consumerConfig['pr-gate'];
|
|
98
|
+
// rules + hookGuards are validated/loaded as one flat name→config map (the runtime dispatches by
|
|
99
|
+
// each rule's own `scope`, so it needs no section knowledge). Placement is enforced separately.
|
|
100
|
+
const overrideRules = { ...rulesSection, ...hookGuardsSection };
|
|
77
101
|
const errors = [
|
|
78
102
|
...(0, validate_config_1.validateWebpiecesConfig)(overrideRules),
|
|
79
|
-
...(0, validate_config_1.
|
|
103
|
+
...(0, validate_config_1.validateSectionPlacement)(rulesSection, hookGuardsSection),
|
|
104
|
+
...(0, validate_config_1.validateCommandsSection)(consumerConfig.commands, legacyPrGate),
|
|
80
105
|
];
|
|
81
106
|
if (errors.length > 0) {
|
|
82
107
|
throw new inform_ai_error_1.InformAiError(`webpieces.config.json has ${errors.length} validation error(s) — fix ALL, then retry:\n\n` +
|
|
83
108
|
errors.map(e => ` • ${e}`).join('\n'));
|
|
84
109
|
}
|
|
85
110
|
const rulesDir = consumerConfig.rulesDir ?? [];
|
|
111
|
+
const commands = (0, commands_config_1.buildCommandsConfig)(consumerConfig.commands, legacyPrGate);
|
|
112
|
+
applyCommandDefaults(overrideRules, commands);
|
|
86
113
|
const userConfiguredRuleNames = new Set(Object.keys(overrideRules));
|
|
87
114
|
const mergedRules = new Map();
|
|
88
115
|
const allRuleNames = new Set([
|
|
@@ -94,7 +121,6 @@ function loadAndValidate(cwd) {
|
|
|
94
121
|
}
|
|
95
122
|
const resolved = new types_1.ResolvedConfig(mergedRules, userConfiguredRuleNames, rulesDir, configPath);
|
|
96
123
|
const rulesConfig = buildWebpiecesRulesConfig(overrideRules, rulesDir);
|
|
97
|
-
|
|
98
|
-
return new LoadedConfig(resolved, rulesConfig, prGate, configPath);
|
|
124
|
+
return new LoadedConfig(resolved, rulesConfig, commands, commands.prGate, configPath);
|
|
99
125
|
}
|
|
100
126
|
//# sourceMappingURL=load-config.js.map
|
package/src/load-config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { findConfigFile, readRawConfig } from './config-file';\nimport { defaultRules } from './default-rules';\nimport { InformAiError } from './inform-ai-error';\nimport { buildPrGateConfig, PrGateConfig } from './pr-gate-config';\nimport { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nimport { validatePrGateSection, validateWebpiecesConfig } from './validate-config';\nimport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\n\n// webpieces-disable no-any-unknown -- merging opaque option bags from config JSON\nfunction mergeRule(\n // webpieces-disable no-any-unknown -- opaque option bag\n baseRule: Record<string, unknown> | undefined,\n // webpieces-disable no-any-unknown -- opaque option bag\n overrideRule: Record<string, unknown> | undefined,\n): ResolvedRuleConfig {\n if (!baseRule && !overrideRule) return new ResolvedRuleConfig({ mode: 'OFF' });\n if (!baseRule) return new ResolvedRuleConfig(overrideRule! as RuleOptions);\n if (!overrideRule) return new ResolvedRuleConfig(baseRule as RuleOptions);\n\n // webpieces-disable no-any-unknown -- building merged option bag\n const merged: Record<string, unknown> = {};\n for (const key of Object.keys(baseRule)) merged[key] = baseRule[key];\n for (const key of Object.keys(overrideRule)) merged[key] = overrideRule[key];\n return new ResolvedRuleConfig(merged as RuleOptions);\n}\n\nfunction buildWebpiecesRulesConfig(\n // webpieces-disable no-any-unknown -- JSON values are opaque until assigned to typed fields\n rawRules: Record<string, Record<string, unknown>>,\n rulesDir: string[],\n): WebpiecesRulesConfig {\n const typed = new WebpiecesRulesConfig();\n for (const key of Object.keys(rawRules)) {\n // webpieces-disable no-any-unknown -- dynamic key assignment to typed class\n (typed as Record<string, unknown>)[key] = rawRules[key];\n }\n typed.rulesDir = rulesDir;\n return typed;\n}\n\n/**\n * Everything a consumer might need from webpieces.config.json, produced from ONE parse + ONE\n * validation pass. Data-only (per CLAUDE.md, classes for data):\n * - `resolved` — Map-based view merged with defaultRules (nx executors).\n * - `rulesConfig` — typed WebpiecesRulesConfig (ai-hook-rules, code-rules).\n * - `prGate` — the pr-gate section (pr-gate scripts).\n * - `configPath` — absolute path, or null when no config file was found.\n */\nexport class LoadedConfig {\n constructor(\n readonly resolved: ResolvedConfig,\n readonly rulesConfig: WebpiecesRulesConfig,\n readonly prGate: PrGateConfig,\n readonly configPath: string | null,\n ) {}\n}\n\n/**\n * The single load+validate entry point for ALL consumers (ai-hook-rules, code-rules,\n * nx-webpieces-rules, pr-gate scripts). Reads webpieces.config.json once, validates BOTH the `rules`\n * map and the top-level `pr-gate` block, and throws one InformAiError listing every error. When no\n * config file is found it returns lenient empties/defaults (matching prior no-file behavior).\n */\nexport function loadAndValidate(cwd: string): LoadedConfig {\n const configPath = findConfigFile(cwd);\n if (!configPath) {\n return new LoadedConfig(\n new ResolvedConfig(new Map(), new Set(), [], null),\n new WebpiecesRulesConfig(),\n buildPrGateConfig(undefined),\n null,\n );\n }\n\n const consumerConfig = readRawConfig(configPath);\n const overrideRules = consumerConfig.rules || {};\n\n const errors = [\n ...validateWebpiecesConfig(overrideRules),\n ...validatePrGateSection(consumerConfig['pr-gate']),\n ];\n if (errors.length > 0) {\n throw new InformAiError(\n `webpieces.config.json has ${errors.length} validation error(s) — fix ALL, then retry:\\n\\n` +\n errors.map(e => ` • ${e}`).join('\\n'),\n );\n }\n\n const rulesDir = consumerConfig.rulesDir ?? [];\n\n const userConfiguredRuleNames = new Set(Object.keys(overrideRules));\n const mergedRules = new Map<string, ResolvedRuleConfig>();\n const allRuleNames = new Set([\n ...Object.keys(defaultRules),\n ...Object.keys(overrideRules),\n ]);\n for (const name of allRuleNames) {\n mergedRules.set(name, mergeRule(defaultRules[name], overrideRules[name]));\n }\n const resolved = new ResolvedConfig(mergedRules, userConfiguredRuleNames, rulesDir, configPath);\n\n const rulesConfig = buildWebpiecesRulesConfig(overrideRules, rulesDir);\n const prGate = buildPrGateConfig(consumerConfig['pr-gate']);\n\n return new LoadedConfig(resolved, rulesConfig, prGate, configPath);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"load-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { buildCommandsConfig, CommandsConfig } from './commands-config';\nimport { findConfigFile, readRawConfig } from './config-file';\nimport { defaultRules } from './default-rules';\nimport { InformAiError } from './inform-ai-error';\nimport { PrGateConfig } from './pr-gate-config';\nimport { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nimport { validateCommandsSection, validateSectionPlacement, validateWebpiecesConfig } from './validate-config';\nimport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\n\n// Inject the canonical command strings (from the `commands` section) as the DEFAULT for the guards\n// that surface them in their fix hints, so a project renames a command in one place. Only fills a\n// gap — an explicit per-guard override wins. Mutates the merged guard entries in place.\nfunction applyCommandDefaults(\n // webpieces-disable no-any-unknown -- opaque merged rule/guard map\n rules: Record<string, Record<string, unknown>>,\n commands: CommandsConfig,\n): void {\n const prCreation = rules['pr-creation-guard'];\n if (prCreation && prCreation['upsertPrCommand'] === undefined) {\n prCreation['upsertPrCommand'] = commands.upsertPr;\n }\n const mergeInProgress = rules['merge-in-progress-guard'];\n if (mergeInProgress && mergeInProgress['mergeCompleteCommand'] === undefined) {\n mergeInProgress['mergeCompleteCommand'] = commands.mergeComplete;\n }\n}\n\n// webpieces-disable no-any-unknown -- merging opaque option bags from config JSON\nfunction mergeRule(\n // webpieces-disable no-any-unknown -- opaque option bag\n baseRule: Record<string, unknown> | undefined,\n // webpieces-disable no-any-unknown -- opaque option bag\n overrideRule: Record<string, unknown> | undefined,\n): ResolvedRuleConfig {\n if (!baseRule && !overrideRule) return new ResolvedRuleConfig({ mode: 'OFF' });\n if (!baseRule) return new ResolvedRuleConfig(overrideRule! as RuleOptions);\n if (!overrideRule) return new ResolvedRuleConfig(baseRule as RuleOptions);\n\n // webpieces-disable no-any-unknown -- building merged option bag\n const merged: Record<string, unknown> = {};\n for (const key of Object.keys(baseRule)) merged[key] = baseRule[key];\n for (const key of Object.keys(overrideRule)) merged[key] = overrideRule[key];\n return new ResolvedRuleConfig(merged as RuleOptions);\n}\n\nfunction buildWebpiecesRulesConfig(\n // webpieces-disable no-any-unknown -- JSON values are opaque until assigned to typed fields\n rawRules: Record<string, Record<string, unknown>>,\n rulesDir: string[],\n): WebpiecesRulesConfig {\n const typed = new WebpiecesRulesConfig();\n for (const key of Object.keys(rawRules)) {\n // webpieces-disable no-any-unknown -- dynamic key assignment to typed class\n (typed as Record<string, unknown>)[key] = rawRules[key];\n }\n typed.rulesDir = rulesDir;\n return typed;\n}\n\n/**\n * Everything a consumer might need from webpieces.config.json, produced from ONE parse + ONE\n * validation pass. Data-only (per CLAUDE.md, classes for data):\n * - `resolved` — Map-based view merged with defaultRules (nx executors).\n * - `rulesConfig` — typed WebpiecesRulesConfig (ai-hook-rules, code-rules); rules + hookGuards merged.\n * - `commands` — the `commands` section (gated commands + pr-gate).\n * - `prGate` — convenience alias of `commands.prGate` (pr-gate scripts).\n * - `configPath` — absolute path, or null when no config file was found.\n */\nexport class LoadedConfig {\n constructor(\n readonly resolved: ResolvedConfig,\n readonly rulesConfig: WebpiecesRulesConfig,\n readonly commands: CommandsConfig,\n readonly prGate: PrGateConfig,\n readonly configPath: string | null,\n ) {}\n}\n\n/**\n * The single load+validate entry point for ALL consumers (ai-hook-rules, code-rules,\n * nx-webpieces-rules, pr-gate scripts). Reads webpieces.config.json once, validates BOTH the `rules`\n * map and the top-level `pr-gate` block, and throws one InformAiError listing every error. When no\n * config file is found it returns lenient empties/defaults (matching prior no-file behavior).\n */\nexport function loadAndValidate(cwd: string): LoadedConfig {\n const configPath = findConfigFile(cwd);\n if (!configPath) {\n const emptyCommands = buildCommandsConfig(undefined);\n return new LoadedConfig(\n new ResolvedConfig(new Map(), new Set(), [], null),\n new WebpiecesRulesConfig(),\n emptyCommands,\n emptyCommands.prGate,\n null,\n );\n }\n\n const consumerConfig = readRawConfig(configPath);\n const rulesSection = consumerConfig.rules || {};\n const hookGuardsSection = consumerConfig.hookGuards || {};\n const legacyPrGate = consumerConfig['pr-gate'];\n\n // rules + hookGuards are validated/loaded as one flat name→config map (the runtime dispatches by\n // each rule's own `scope`, so it needs no section knowledge). Placement is enforced separately.\n const overrideRules = { ...rulesSection, ...hookGuardsSection };\n\n const errors = [\n ...validateWebpiecesConfig(overrideRules),\n ...validateSectionPlacement(rulesSection, hookGuardsSection),\n ...validateCommandsSection(consumerConfig.commands, legacyPrGate),\n ];\n if (errors.length > 0) {\n throw new InformAiError(\n `webpieces.config.json has ${errors.length} validation error(s) — fix ALL, then retry:\\n\\n` +\n errors.map(e => ` • ${e}`).join('\\n'),\n );\n }\n\n const rulesDir = consumerConfig.rulesDir ?? [];\n const commands = buildCommandsConfig(consumerConfig.commands, legacyPrGate);\n applyCommandDefaults(overrideRules, commands);\n\n const userConfiguredRuleNames = new Set(Object.keys(overrideRules));\n const mergedRules = new Map<string, ResolvedRuleConfig>();\n const allRuleNames = new Set([\n ...Object.keys(defaultRules),\n ...Object.keys(overrideRules),\n ]);\n for (const name of allRuleNames) {\n mergedRules.set(name, mergeRule(defaultRules[name], overrideRules[name]));\n }\n const resolved = new ResolvedConfig(mergedRules, userConfiguredRuleNames, rulesDir, configPath);\n\n const rulesConfig = buildWebpiecesRulesConfig(overrideRules, rulesDir);\n\n return new LoadedConfig(resolved, rulesConfig, commands, commands.prGate, configPath);\n}\n"]}
|
package/src/pr-gate-config.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export declare class GateDefinition {
|
|
2
2
|
name: string;
|
|
3
3
|
patterns: string[];
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
color: string;
|
|
5
|
+
disabled: boolean;
|
|
6
|
+
constructor(name: string, patterns: string[], color: string, disabled?: boolean);
|
|
6
7
|
}
|
|
7
8
|
export declare class PrGateConfig {
|
|
8
9
|
mode: string;
|
package/src/pr-gate-config.js
CHANGED
|
@@ -11,11 +11,19 @@ exports.buildPrGateConfig = buildPrGateConfig;
|
|
|
11
11
|
class GateDefinition {
|
|
12
12
|
name;
|
|
13
13
|
patterns;
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// The color shown on the dashboard WHEN this gate's patterns match a changed file. Green is
|
|
15
|
+
// implicit (shown when nothing matched), so it is never configured. Color is purely visual —
|
|
16
|
+
// even 'red' never fails/blocks the PR (only the build gate can). 'yellow' = caution,
|
|
17
|
+
// 'red' = louder "look here" flag (e.g. DB schema / migration changes).
|
|
18
|
+
color; // 'yellow' | 'red'
|
|
19
|
+
// Example/inactive gate: parsed and kept in the file (JSON has no comments) but skipped at
|
|
20
|
+
// compute/render time. Other projects flip this to false and tune patterns/color.
|
|
21
|
+
disabled;
|
|
22
|
+
constructor(name, patterns, color, disabled = false) {
|
|
16
23
|
this.name = name;
|
|
17
24
|
this.patterns = patterns;
|
|
18
|
-
this.
|
|
25
|
+
this.color = color;
|
|
26
|
+
this.disabled = disabled;
|
|
19
27
|
}
|
|
20
28
|
}
|
|
21
29
|
exports.GateDefinition = GateDefinition;
|
|
@@ -34,17 +42,17 @@ exports.PrGateConfig = PrGateConfig;
|
|
|
34
42
|
// whole list via the `pr-gate.gates` array in webpieces.config.json.
|
|
35
43
|
function defaultGates() {
|
|
36
44
|
return [
|
|
37
|
-
new GateDefinition('API Changed', ['libraries/apis/**', '**/*Api.ts'], '
|
|
38
|
-
new GateDefinition('Config Files Changed', ['**/package.json', '**/tsconfig*.json', 'nx.json', '**/*.config.*'], '
|
|
39
|
-
new GateDefinition('Dependency Graph Changed', ['architecture/dependencies.json'], '
|
|
40
|
-
new GateDefinition('Claude / Rules Changed', ['**/CLAUDE.md', '**/claude.*.md', '.claude/**', 'webpieces.config.json'], '
|
|
45
|
+
new GateDefinition('API Changed', ['libraries/apis/**', '**/*Api.ts'], 'yellow'),
|
|
46
|
+
new GateDefinition('Config Files Changed', ['**/package.json', '**/tsconfig*.json', 'nx.json', '**/*.config.*'], 'yellow'),
|
|
47
|
+
new GateDefinition('Dependency Graph Changed', ['architecture/dependencies.json'], 'yellow'),
|
|
48
|
+
new GateDefinition('Claude / Rules Changed', ['**/CLAUDE.md', '**/claude.*.md', '.claude/**', 'webpieces.config.json'], 'yellow'),
|
|
41
49
|
];
|
|
42
50
|
}
|
|
43
51
|
function defaultPrGateConfig() {
|
|
44
52
|
return new PrGateConfig('ON', '', defaultGates());
|
|
45
53
|
}
|
|
46
54
|
function toGate(raw) {
|
|
47
|
-
return new GateDefinition(raw.name ?? '', raw.patterns ?? [], raw.
|
|
55
|
+
return new GateDefinition(raw.name ?? '', raw.patterns ?? [], raw.color ?? 'yellow', raw.disabled ?? false);
|
|
48
56
|
}
|
|
49
57
|
/**
|
|
50
58
|
* Build a PrGateConfig from the already-parsed top-level `pr-gate` section, falling back to defaults
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr-gate-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/pr-gate-config.ts"],"names":[],"mappings":";AAAA,2FAA2F;AAC3F,2FAA2F;AAC3F,iFAAiF;AACjF,iGAAiG;;;
|
|
1
|
+
{"version":3,"file":"pr-gate-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/pr-gate-config.ts"],"names":[],"mappings":";AAAA,2FAA2F;AAC3F,2FAA2F;AAC3F,iFAAiF;AACjF,iGAAiG;;;AAoCjG,oCAOC;AAED,kDAEC;AA0BD,8CASC;AAhFD,MAAa,cAAc;IACvB,IAAI,CAAS;IACb,QAAQ,CAAW;IACnB,4FAA4F;IAC5F,6FAA6F;IAC7F,sFAAsF;IACtF,wEAAwE;IACxE,KAAK,CAAS,CAAC,mBAAmB;IAClC,2FAA2F;IAC3F,kFAAkF;IAClF,QAAQ,CAAU;IAElB,YAAY,IAAY,EAAE,QAAkB,EAAE,KAAa,EAAE,QAAQ,GAAG,KAAK;QACzE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;CACJ;AAlBD,wCAkBC;AAED,MAAa,YAAY;IACrB,IAAI,CAAS;IACb,YAAY,CAAS;IACrB,KAAK,CAAmB;IAExB,YAAY,IAAY,EAAE,YAAoB,EAAE,KAAuB;QACnE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;CACJ;AAVD,oCAUC;AAED,0FAA0F;AAC1F,qEAAqE;AACrE,SAAgB,YAAY;IACxB,OAAO;QACH,IAAI,cAAc,CAAC,aAAa,EAAE,CAAC,mBAAmB,EAAE,YAAY,CAAC,EAAE,QAAQ,CAAC;QAChF,IAAI,cAAc,CAAC,sBAAsB,EAAE,CAAC,iBAAiB,EAAE,mBAAmB,EAAE,SAAS,EAAE,eAAe,CAAC,EAAE,QAAQ,CAAC;QAC1H,IAAI,cAAc,CAAC,0BAA0B,EAAE,CAAC,gCAAgC,CAAC,EAAE,QAAQ,CAAC;QAC5F,IAAI,cAAc,CAAC,wBAAwB,EAAE,CAAC,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,uBAAuB,CAAC,EAAE,QAAQ,CAAC;KACpI,CAAC;AACN,CAAC;AAED,SAAgB,mBAAmB;IAC/B,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC;AACtD,CAAC;AAeD,SAAS,MAAM,CAAC,GAAY;IACxB,OAAO,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,GAAG,CAAC,KAAK,IAAI,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC;AAChH,CAAC;AAED;;;;;GAKG;AACH,4FAA4F;AAC5F,SAAgB,iBAAiB,CAAC,OAAgB;IAC9C,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAC;IACvC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9F,MAAM,GAAG,GAAG,OAA2B,CAAC;IACxC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC;IACvC,MAAM,YAAY,GAAG,GAAG,CAAC,YAAY,IAAI,QAAQ,CAAC,YAAY,CAAC;IAC/D,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;IAC/E,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;AACvD,CAAC","sourcesContent":["// PrGateConfig is the \"special section\" for the pr-gate dashboard. It does NOT live in the\n// validated `rules` map (the FieldDef schema can't express nested object arrays), but as a\n// top-level `pr-gate` key in webpieces.config.json. It is built and validated by\n// loadAndValidate (load-config.ts); this module holds only the data classes + defaults + toGate.\n\nexport class GateDefinition {\n name: string;\n patterns: string[];\n // The color shown on the dashboard WHEN this gate's patterns match a changed file. Green is\n // implicit (shown when nothing matched), so it is never configured. Color is purely visual —\n // even 'red' never fails/blocks the PR (only the build gate can). 'yellow' = caution,\n // 'red' = louder \"look here\" flag (e.g. DB schema / migration changes).\n color: string; // 'yellow' | 'red'\n // Example/inactive gate: parsed and kept in the file (JSON has no comments) but skipped at\n // compute/render time. Other projects flip this to false and tune patterns/color.\n disabled: boolean;\n\n constructor(name: string, patterns: string[], color: string, disabled = false) {\n this.name = name;\n this.patterns = patterns;\n this.color = color;\n this.disabled = disabled;\n }\n}\n\nexport class PrGateConfig {\n mode: string;\n buildCommand: string;\n gates: GateDefinition[];\n\n constructor(mode: string, buildCommand: string, gates: GateDefinition[]) {\n this.mode = mode;\n this.buildCommand = buildCommand;\n this.gates = gates;\n }\n}\n\n// Default infra gates — path-pattern based, tuned for this monorepo. Clients override the\n// whole list via the `pr-gate.gates` array in webpieces.config.json.\nexport function defaultGates(): GateDefinition[] {\n return [\n new GateDefinition('API Changed', ['libraries/apis/**', '**/*Api.ts'], 'yellow'),\n new GateDefinition('Config Files Changed', ['**/package.json', '**/tsconfig*.json', 'nx.json', '**/*.config.*'], 'yellow'),\n new GateDefinition('Dependency Graph Changed', ['architecture/dependencies.json'], 'yellow'),\n new GateDefinition('Claude / Rules Changed', ['**/CLAUDE.md', '**/claude.*.md', '.claude/**', 'webpieces.config.json'], 'yellow'),\n ];\n}\n\nexport function defaultPrGateConfig(): PrGateConfig {\n return new PrGateConfig('ON', '', defaultGates());\n}\n\ninterface RawGate {\n name?: string;\n patterns?: string[];\n color?: string;\n disabled?: boolean;\n}\n\ninterface RawPrGateSection {\n mode?: string;\n buildCommand?: string;\n gates?: RawGate[];\n}\n\nfunction toGate(raw: RawGate): GateDefinition {\n return new GateDefinition(raw.name ?? '', raw.patterns ?? [], raw.color ?? 'yellow', raw.disabled ?? false);\n}\n\n/**\n * Build a PrGateConfig from the already-parsed top-level `pr-gate` section, falling back to defaults\n * for any field the consumer omits. Pure transform — the file read + structural validation happen in\n * loadAndValidate (load-config.ts) so every consumer goes through one validated path. Pass undefined\n * (no `pr-gate` key / no config file) to get full defaults.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed here\nexport function buildPrGateConfig(section: unknown): PrGateConfig {\n const defaults = defaultPrGateConfig();\n if (section === undefined || section === null || typeof section !== 'object') return defaults;\n\n const raw = section as RawPrGateSection;\n const mode = raw.mode ?? defaults.mode;\n const buildCommand = raw.buildCommand ?? defaults.buildCommand;\n const gates = raw.gates !== undefined ? raw.gates.map(toGate) : defaults.gates;\n return new PrGateConfig(mode, buildCommand, gates);\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class ReviewJson {
|
|
2
|
+
riskScore: number;
|
|
3
|
+
riskLevel: string;
|
|
4
|
+
riskEmoji: string;
|
|
5
|
+
summary: string;
|
|
6
|
+
violations: string[];
|
|
7
|
+
risks: string[];
|
|
8
|
+
filesToReview: string[];
|
|
9
|
+
constructor(riskScore: number, riskLevel: string, riskEmoji: string, summary: string, violations: string[], risks: string[], filesToReview: string[]);
|
|
10
|
+
}
|
|
11
|
+
export declare function reviewJsonPath(repoRoot: string, featureName: string): string;
|
|
12
|
+
export declare function reviewJsonSchemaHint(filePath: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Load + validate the AI-authored review.json. Throws InformAiError (with the schema) when the file
|
|
15
|
+
* is missing, unparseable, or structurally wrong — the message is written straight back to the AI so
|
|
16
|
+
* it can fix the file and re-run. Returns a fully-populated ReviewJson on success.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadReviewJson(filePath: string): ReviewJson;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReviewJson = void 0;
|
|
4
|
+
exports.reviewJsonPath = reviewJsonPath;
|
|
5
|
+
exports.reviewJsonSchemaHint = reviewJsonSchemaHint;
|
|
6
|
+
exports.loadReviewJson = loadReviewJson;
|
|
7
|
+
const tslib_1 = require("tslib");
|
|
8
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
9
|
+
const path = tslib_1.__importStar(require("path"));
|
|
10
|
+
const constants_1 = require("./constants");
|
|
11
|
+
const inform_ai_error_1 = require("./inform-ai-error");
|
|
12
|
+
const to_error_1 = require("./to-error");
|
|
13
|
+
// The AI-authored review for a PR. webpieces is AI-first, so unlike trytami (where a human command
|
|
14
|
+
// calls Claude), the AI writes this file itself between `wp-start-upsert-pr` (which prints the
|
|
15
|
+
// schema + instructions) and `wp-finish-upsert-pr` (which reads it to render the RISK section and
|
|
16
|
+
// post the PR). Data-only (per CLAUDE.md, classes for data).
|
|
17
|
+
class ReviewJson {
|
|
18
|
+
riskScore; // 0–100, drives the risk bar
|
|
19
|
+
riskLevel; // 'green' | 'yellow' | 'red'
|
|
20
|
+
riskEmoji; // '🟢' | '🟡' | '🔴' — derived from riskLevel when omitted
|
|
21
|
+
summary; // rendered in the dashboard Summary section
|
|
22
|
+
violations; // pattern/architecture violations; length = the Pattern Violations count
|
|
23
|
+
risks;
|
|
24
|
+
filesToReview;
|
|
25
|
+
constructor(riskScore, riskLevel, riskEmoji, summary, violations, risks, filesToReview) {
|
|
26
|
+
this.riskScore = riskScore;
|
|
27
|
+
this.riskLevel = riskLevel;
|
|
28
|
+
this.riskEmoji = riskEmoji;
|
|
29
|
+
this.summary = summary;
|
|
30
|
+
this.violations = violations;
|
|
31
|
+
this.risks = risks;
|
|
32
|
+
this.filesToReview = filesToReview;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.ReviewJson = ReviewJson;
|
|
36
|
+
const RISK_LEVELS = ['green', 'yellow', 'red'];
|
|
37
|
+
const EMOJI_FOR_LEVEL = { green: '🟢', yellow: '🟡', red: '🔴' };
|
|
38
|
+
// Absolute path of the review.json for a feature — beside pr-body.md, keyed by branch name so the
|
|
39
|
+
// AI and the finish command agree on the location without passing it around.
|
|
40
|
+
function reviewJsonPath(repoRoot, featureName) {
|
|
41
|
+
return path.join(repoRoot, constants_1.WEBPIECES_TMP_DIR, `pr-${featureName}`, 'review.json');
|
|
42
|
+
}
|
|
43
|
+
// Copy-paste schema both commands print: wp-start-upsert-pr to instruct the AI to WRITE it,
|
|
44
|
+
// wp-finish-upsert-pr to instruct the AI to FIX it when missing/invalid.
|
|
45
|
+
function reviewJsonSchemaHint(filePath) {
|
|
46
|
+
return (`Write your PR review to:\n ${filePath}\n\n` +
|
|
47
|
+
`with this exact JSON shape (riskEmoji optional — derived from riskLevel):\n\n` +
|
|
48
|
+
`{\n` +
|
|
49
|
+
` "riskScore": 0, // integer 0–100 (higher = riskier)\n` +
|
|
50
|
+
` "riskLevel": "green | yellow | red",\n` +
|
|
51
|
+
` "summary": "5–10 sentence review summary",\n` +
|
|
52
|
+
` "violations": ["pattern/architecture violations you found (empty array if none)"],\n` +
|
|
53
|
+
` "risks": ["notable risks (empty array if none)"],\n` +
|
|
54
|
+
` "filesToReview": ["paths a human should look at (empty array if none)"]\n` +
|
|
55
|
+
`}`);
|
|
56
|
+
}
|
|
57
|
+
// webpieces-disable no-any-unknown -- opaque parsed JSON value, narrowed to string[] here
|
|
58
|
+
function asStringArray(value) {
|
|
59
|
+
if (!Array.isArray(value))
|
|
60
|
+
return [];
|
|
61
|
+
// webpieces-disable no-any-unknown -- element of an opaque JSON array, narrowed by the type guard
|
|
62
|
+
return value.filter((v) => typeof v === 'string');
|
|
63
|
+
}
|
|
64
|
+
// Parse opaque AI-authored JSON, converting a SyntaxError into a readable InformAiError the AI can
|
|
65
|
+
// act on (mirrors readRawConfig in config-file.ts — the established JSON-parse chokepoint).
|
|
66
|
+
// webpieces-disable no-any-unknown -- returns the opaque parsed object; the caller narrows each field
|
|
67
|
+
function parseReviewJson(raw, filePath) {
|
|
68
|
+
// webpieces-disable no-unmanaged-exceptions -- chokepoint: convert JSON.parse SyntaxError to an InformAiError for the AI
|
|
69
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
70
|
+
try {
|
|
71
|
+
// webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed by the caller
|
|
72
|
+
return JSON.parse(raw);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
const error = (0, to_error_1.toError)(err);
|
|
76
|
+
throw new inform_ai_error_1.InformAiError(`review.json is not valid JSON (${error.message}).\n\n${reviewJsonSchemaHint(filePath)}\n\n` +
|
|
77
|
+
`Then re-run: pnpm wp-finish-upsert-pr`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Load + validate the AI-authored review.json. Throws InformAiError (with the schema) when the file
|
|
82
|
+
* is missing, unparseable, or structurally wrong — the message is written straight back to the AI so
|
|
83
|
+
* it can fix the file and re-run. Returns a fully-populated ReviewJson on success.
|
|
84
|
+
*/
|
|
85
|
+
function loadReviewJson(filePath) {
|
|
86
|
+
if (!fs.existsSync(filePath)) {
|
|
87
|
+
throw new inform_ai_error_1.InformAiError(`Required review.json not found.\n\n${reviewJsonSchemaHint(filePath)}\n\n` +
|
|
88
|
+
`Then re-run: pnpm wp-finish-upsert-pr`);
|
|
89
|
+
}
|
|
90
|
+
const raw = parseReviewJson(fs.readFileSync(filePath, 'utf8'), filePath);
|
|
91
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
92
|
+
throw new inform_ai_error_1.InformAiError(`review.json must be a JSON object.\n\n${reviewJsonSchemaHint(filePath)}`);
|
|
93
|
+
}
|
|
94
|
+
const errors = [];
|
|
95
|
+
const riskScore = raw['riskScore'];
|
|
96
|
+
if (typeof riskScore !== 'number' || !Number.isFinite(riskScore) || riskScore < 0 || riskScore > 100) {
|
|
97
|
+
errors.push(`"riskScore" must be a number 0–100, got ${JSON.stringify(riskScore)}.`);
|
|
98
|
+
}
|
|
99
|
+
const riskLevel = raw['riskLevel'];
|
|
100
|
+
if (typeof riskLevel !== 'string' || !RISK_LEVELS.includes(riskLevel)) {
|
|
101
|
+
errors.push(`"riskLevel" must be one of: ${RISK_LEVELS.join(', ')}.`);
|
|
102
|
+
}
|
|
103
|
+
if (errors.length > 0) {
|
|
104
|
+
throw new inform_ai_error_1.InformAiError(`review.json has ${errors.length} error(s) — fix ALL, then re-run pnpm wp-finish-upsert-pr:\n\n` +
|
|
105
|
+
errors.map((e) => ` • ${e}`).join('\n') +
|
|
106
|
+
`\n\n${reviewJsonSchemaHint(filePath)}`);
|
|
107
|
+
}
|
|
108
|
+
const level = riskLevel;
|
|
109
|
+
const emoji = typeof raw['riskEmoji'] === 'string' && raw['riskEmoji'] !== ''
|
|
110
|
+
? raw['riskEmoji']
|
|
111
|
+
: (EMOJI_FOR_LEVEL[level] ?? '🟡');
|
|
112
|
+
const summary = typeof raw['summary'] === 'string' ? raw['summary'] : '';
|
|
113
|
+
return new ReviewJson(riskScore, level, emoji, summary, asStringArray(raw['violations']), asStringArray(raw['risks']), asStringArray(raw['filesToReview']));
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=review-json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-json.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/review-json.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\n// The AI-authored review for a PR. webpieces is AI-first, so unlike trytami (where a human command\n// calls Claude), the AI writes this file itself between `wp-start-upsert-pr` (which prints the\n// schema + instructions) and `wp-finish-upsert-pr` (which reads it to render the RISK section and\n// post the PR). Data-only (per CLAUDE.md, classes for data).\nexport class ReviewJson {\n riskScore: number; // 0–100, drives the risk bar\n riskLevel: string; // 'green' | 'yellow' | 'red'\n riskEmoji: string; // '🟢' | '🟡' | '🔴' — derived from riskLevel when omitted\n summary: string; // rendered in the dashboard Summary section\n violations: string[]; // pattern/architecture violations; length = the Pattern Violations count\n risks: string[];\n filesToReview: string[];\n\n constructor(\n riskScore: number,\n riskLevel: string,\n riskEmoji: string,\n summary: string,\n violations: string[],\n risks: string[],\n filesToReview: string[],\n ) {\n this.riskScore = riskScore;\n this.riskLevel = riskLevel;\n this.riskEmoji = riskEmoji;\n this.summary = summary;\n this.violations = violations;\n this.risks = risks;\n this.filesToReview = filesToReview;\n }\n}\n\nconst RISK_LEVELS = ['green', 'yellow', 'red'] as const;\nconst EMOJI_FOR_LEVEL: Record<string, string> = { green: '🟢', yellow: '🟡', red: '🔴' };\n\n// Absolute path of the review.json for a feature — beside pr-body.md, keyed by branch name so the\n// AI and the finish command agree on the location without passing it around.\nexport function reviewJsonPath(repoRoot: string, featureName: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, `pr-${featureName}`, 'review.json');\n}\n\n// Copy-paste schema both commands print: wp-start-upsert-pr to instruct the AI to WRITE it,\n// wp-finish-upsert-pr to instruct the AI to FIX it when missing/invalid.\nexport function reviewJsonSchemaHint(filePath: string): string {\n return (\n `Write your PR review to:\\n ${filePath}\\n\\n` +\n `with this exact JSON shape (riskEmoji optional — derived from riskLevel):\\n\\n` +\n `{\\n` +\n ` \"riskScore\": 0, // integer 0–100 (higher = riskier)\\n` +\n ` \"riskLevel\": \"green | yellow | red\",\\n` +\n ` \"summary\": \"5–10 sentence review summary\",\\n` +\n ` \"violations\": [\"pattern/architecture violations you found (empty array if none)\"],\\n` +\n ` \"risks\": [\"notable risks (empty array if none)\"],\\n` +\n ` \"filesToReview\": [\"paths a human should look at (empty array if none)\"]\\n` +\n `}`\n );\n}\n\n// webpieces-disable no-any-unknown -- opaque parsed JSON value, narrowed to string[] here\nfunction asStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n // webpieces-disable no-any-unknown -- element of an opaque JSON array, narrowed by the type guard\n return value.filter((v: unknown): v is string => typeof v === 'string');\n}\n\n// Parse opaque AI-authored JSON, converting a SyntaxError into a readable InformAiError the AI can\n// act on (mirrors readRawConfig in config-file.ts — the established JSON-parse chokepoint).\n// webpieces-disable no-any-unknown -- returns the opaque parsed object; the caller narrows each field\nfunction parseReviewJson(raw: string, filePath: string): Record<string, unknown> {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: convert JSON.parse SyntaxError to an InformAiError for the AI\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed by the caller\n return JSON.parse(raw) as Record<string, unknown>;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(\n `review.json is not valid JSON (${error.message}).\\n\\n${reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n}\n\n/**\n * Load + validate the AI-authored review.json. Throws InformAiError (with the schema) when the file\n * is missing, unparseable, or structurally wrong — the message is written straight back to the AI so\n * it can fix the file and re-run. Returns a fully-populated ReviewJson on success.\n */\nexport function loadReviewJson(filePath: string): ReviewJson {\n if (!fs.existsSync(filePath)) {\n throw new InformAiError(\n `Required review.json not found.\\n\\n${reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n\n const raw = parseReviewJson(fs.readFileSync(filePath, 'utf8'), filePath);\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new InformAiError(`review.json must be a JSON object.\\n\\n${reviewJsonSchemaHint(filePath)}`);\n }\n\n const errors: string[] = [];\n\n const riskScore = raw['riskScore'];\n if (typeof riskScore !== 'number' || !Number.isFinite(riskScore) || riskScore < 0 || riskScore > 100) {\n errors.push(`\"riskScore\" must be a number 0–100, got ${JSON.stringify(riskScore)}.`);\n }\n\n const riskLevel = raw['riskLevel'];\n if (typeof riskLevel !== 'string' || !RISK_LEVELS.includes(riskLevel as typeof RISK_LEVELS[number])) {\n errors.push(`\"riskLevel\" must be one of: ${RISK_LEVELS.join(', ')}.`);\n }\n\n if (errors.length > 0) {\n throw new InformAiError(\n `review.json has ${errors.length} error(s) — fix ALL, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\n${reviewJsonSchemaHint(filePath)}`,\n );\n }\n\n const level = riskLevel as string;\n const emoji = typeof raw['riskEmoji'] === 'string' && raw['riskEmoji'] !== ''\n ? (raw['riskEmoji'] as string)\n : (EMOJI_FOR_LEVEL[level] ?? '🟡');\n const summary = typeof raw['summary'] === 'string' ? (raw['summary'] as string) : '';\n\n return new ReviewJson(\n riskScore as number,\n level,\n emoji,\n summary,\n asStringArray(raw['violations']),\n asStringArray(raw['risks']),\n asStringArray(raw['filesToReview']),\n );\n}\n"]}
|
package/src/rule-configs.d.ts
CHANGED
|
@@ -129,6 +129,7 @@ export declare class PrCreationGuardConfig extends BaseRuleConfig {
|
|
|
129
129
|
}
|
|
130
130
|
export declare class MergeInProgressGuardConfig extends BaseRuleConfig {
|
|
131
131
|
mode?: OnOffMode;
|
|
132
|
+
mergeCompleteCommand?: string;
|
|
132
133
|
static readonly SCHEMA: SchemaShape<MergeInProgressGuardConfig>;
|
|
133
134
|
}
|
|
134
135
|
export declare class PrMergeCleanupConfig extends BaseRuleConfig {
|
package/src/rule-configs.js
CHANGED
|
@@ -222,7 +222,7 @@ class BranchCreationGuardConfig extends BaseRuleConfig {
|
|
|
222
222
|
exports.BranchCreationGuardConfig = BranchCreationGuardConfig;
|
|
223
223
|
class PrCreationGuardConfig extends BaseRuleConfig {
|
|
224
224
|
// The gated command the guard points agents to instead of direct PR creation. Per-project
|
|
225
|
-
// override; defaults to `pnpm wp-upsert-pr` at the point of use.
|
|
225
|
+
// override; defaults to `pnpm wp-start-upsert-pr` at the point of use.
|
|
226
226
|
upsertPrCommand;
|
|
227
227
|
static SCHEMA = {
|
|
228
228
|
mode: new field_def_1.FieldDef('string', exports.ON_OFF_MODES),
|
|
@@ -232,8 +232,12 @@ class PrCreationGuardConfig extends BaseRuleConfig {
|
|
|
232
232
|
}
|
|
233
233
|
exports.PrCreationGuardConfig = PrCreationGuardConfig;
|
|
234
234
|
class MergeInProgressGuardConfig extends BaseRuleConfig {
|
|
235
|
+
// The gated command the guard points agents to in order to finish a 3-point merge. Per-project
|
|
236
|
+
// override; defaults to `commands.mergeComplete` (pnpm wp-git-merge-complete) at load time.
|
|
237
|
+
mergeCompleteCommand;
|
|
235
238
|
static SCHEMA = {
|
|
236
239
|
mode: new field_def_1.FieldDef('string', exports.ON_OFF_MODES),
|
|
240
|
+
mergeCompleteCommand: field_def_1.FieldDef.optional('string'),
|
|
237
241
|
...exports.BASE_RULE_SCHEMA,
|
|
238
242
|
};
|
|
239
243
|
}
|
package/src/rule-configs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-configs.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/rule-configs.ts"],"names":[],"mappings":"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","sourcesContent":["import { FieldDef, SchemaShape } from './field-def';\n\n// Mode const arrays — TypeScript union types derived from them, and FieldDef enum\n// values reference the same array. Impossible for the type and runtime check to diverge.\n\n// Single source of truth for rule \"mode\" values. Exported so code-rules (and any other\n// consumer) imports these instead of re-declaring the same unions — a rename here ripples\n// everywhere at compile time. The FieldDef SCHEMA below references the same arrays, so the\n// type and the runtime validation can never diverge.\nexport const METHOD_LIMIT_MODES = ['OFF', 'NEW_METHODS', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'] as const;\nexport type MethodLimitMode = typeof METHOD_LIMIT_MODES[number];\n\nexport const FILE_LIMIT_MODES = ['OFF', 'MODIFIED_FILES'] as const;\nexport type FileLimitMode = typeof FILE_LIMIT_MODES[number];\n\nexport const RETURN_TYPE_MODES = ['OFF', 'NEW_METHODS', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'] as const;\nexport type ReturnTypeMode = typeof RETURN_TYPE_MODES[number];\n\nexport const INLINE_TYPE_MODES = ['OFF', 'NEW_METHODS', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'] as const;\nexport type InlineTypeMode = typeof INLINE_TYPE_MODES[number];\n\nexport const MODIFIED_CODE_MODES = ['OFF', 'MODIFIED_CODE', 'MODIFIED_FILES'] as const;\nexport type ModifiedCodeMode = typeof MODIFIED_CODE_MODES[number];\n\nexport const PRISMA_DTOS_MODES = ['OFF', 'MODIFIED_CLASS', 'MODIFIED_FILES'] as const;\nexport type PrismaValidateDtosMode = typeof PRISMA_DTOS_MODES[number];\n\nexport const PRISMA_CONVERTER_MODES = ['OFF', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'] as const;\nexport type PrismaConverterMode = typeof PRISMA_CONVERTER_MODES[number];\n\nexport const DIRECT_API_RESOLVER_MODES = ['OFF', 'MODIFIED_CODE', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'] as const;\nexport type DirectApiResolverMode = typeof DIRECT_API_RESOLVER_MODES[number];\n\nexport const THROW_CAUSE_MODES = ['ON', 'OFF', 'MODIFIED_CODE'] as const;\nexport type ThrowCauseMode = typeof THROW_CAUSE_MODES[number];\n\nexport const ON_OFF_MODES = ['ON', 'OFF'] as const;\nexport type OnOffMode = typeof ON_OFF_MODES[number];\n\n// branch-creation-guard modes. ON_NO_SUBBRANCHES is the strict variant: it hard-blocks\n// creating a branch off any non-main branch (no sub-branch affordance), pointing the agent\n// back to `git checkout main && git pull && git checkout -b <branch>`. Temporarily overridable\n// via the universal ignoreModifiedUntilEpoch escape hatch.\nexport const BRANCH_GUARD_MODES = ['ON', 'OFF', 'ON_NO_SUBBRANCHES'] as const;\nexport type BranchGuardMode = typeof BRANCH_GUARD_MODES[number];\n\nexport const VALIDATE_TS_MODES = ['ON', 'OFF', 'MODIFIED_FILES'] as const;\nexport type ValidateTsMode = typeof VALIDATE_TS_MODES[number];\n\n// ---------------------------------------------------------------------------\n// Universal escape hatches — EVERY rule supports temporarily disabling itself\n// either while on a named git branch (ignoreRuleWhileOnBranch) or until an\n// epoch passes (ignoreModifiedUntilEpoch). They live on a shared base class so\n// the two fields (and their schema entries) are declared once instead of\n// repeated per rule. `mode` stays per-rule because its allowed values vary\n// (ON/OFF vs MODIFIED_CODE vs NEW_AND_MODIFIED_METHODS, etc).\n//\n// `ignoreModifiedUntilEpoch` is REQUIRED on every rule so the time-box escape\n// hatch is always present and a rule can be turned off with a one-value edit.\n// Convention: 0 = rule active (epoch is in the past, never skipped); a future\n// unix epoch IN SECONDS = rule temporarily disabled until that moment.\n// `ignoreRuleWhileOnBranch` stays optional.\n// ---------------------------------------------------------------------------\nexport abstract class BaseRuleConfig {\n // `mode` is declared here (loosely typed) so the shared AbstractRule base can read it for\n // on/off. Each concrete *Config narrows it to its own union (e.g. `mode?: ModifiedCodeMode`),\n // which is an assignable (covariant) override.\n mode?: string;\n // TS-optional, but schema-REQUIRED (see BASE_RULE_SCHEMA) — same split as `mode`.\n ignoreModifiedUntilEpoch?: number;\n ignoreRuleWhileOnBranch?: string;\n}\n\nexport const BASE_RULE_SCHEMA = {\n ignoreModifiedUntilEpoch: new FieldDef('number'),\n ignoreRuleWhileOnBranch: FieldDef.optional('string'),\n};\n\nexport class MaxMethodLinesConfig extends BaseRuleConfig {\n declare mode?: MethodLimitMode;\n limit?: number;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<MaxMethodLinesConfig> = {\n mode: new FieldDef('string', METHOD_LIMIT_MODES),\n limit: FieldDef.optional('number'),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class MaxFileLinesConfig extends BaseRuleConfig {\n declare mode?: FileLimitMode;\n limit?: number;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<MaxFileLinesConfig> = {\n mode: new FieldDef('string', FILE_LIMIT_MODES),\n limit: FieldDef.optional('number'),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class RequireReturnTypeConfig extends BaseRuleConfig {\n declare mode?: ReturnTypeMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<RequireReturnTypeConfig> = {\n mode: new FieldDef('string', RETURN_TYPE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoInlineTypeLiteralsConfig extends BaseRuleConfig {\n declare mode?: InlineTypeMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<NoInlineTypeLiteralsConfig> = {\n mode: new FieldDef('string', INLINE_TYPE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoAnyUnknownConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<NoAnyUnknownConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoImplicitAnyConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<NoImplicitAnyConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class PrismaValidateDtosConfig extends BaseRuleConfig {\n declare mode?: PrismaValidateDtosMode;\n disableAllowed?: boolean;\n prismaSchemaPath?: string;\n dtoSourcePaths?: string[];\n\n static readonly SCHEMA: SchemaShape<PrismaValidateDtosConfig> = {\n mode: new FieldDef('string', PRISMA_DTOS_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n prismaSchemaPath: FieldDef.optional('string'),\n dtoSourcePaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class PrismaConverterConfig extends BaseRuleConfig {\n declare mode?: PrismaConverterMode;\n disableAllowed?: boolean;\n schemaPath?: string;\n convertersPaths?: string[];\n enforcePaths?: string[];\n\n static readonly SCHEMA: SchemaShape<PrismaConverterConfig> = {\n mode: new FieldDef('string', PRISMA_CONVERTER_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n schemaPath: FieldDef.optional('string'),\n convertersPaths: FieldDef.optional('string[]'),\n enforcePaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoDestructureConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n allowTopLevel?: boolean;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<NoDestructureConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n allowTopLevel: FieldDef.optional('boolean'),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoUnmanagedExceptionsConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<NoUnmanagedExceptionsConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class CatchErrorPatternConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<CatchErrorPatternConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class ThrowCauseRequiredConfig extends BaseRuleConfig {\n declare mode?: ThrowCauseMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<ThrowCauseRequiredConfig> = {\n mode: new FieldDef('string', THROW_CAUSE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class AngularNoDirectApiInResolverConfig extends BaseRuleConfig {\n declare mode?: DirectApiResolverMode;\n disableAllowed?: boolean;\n enforcePaths?: string[];\n\n static readonly SCHEMA: SchemaShape<AngularNoDirectApiInResolverConfig> = {\n mode: new FieldDef('string', DIRECT_API_RESOLVER_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n enforcePaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoSymbolDiTokensConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n disableAllowed?: boolean;\n allowedPaths?: string[];\n\n static readonly SCHEMA: SchemaShape<NoSymbolDiTokensConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n allowedPaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoShellSubstitutionConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n\n static readonly SCHEMA: SchemaShape<NoShellSubstitutionConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class BranchCreationGuardConfig extends BaseRuleConfig {\n declare mode?: BranchGuardMode;\n // Naming pattern for stacked SUB-branches only (branches created off another feature branch,\n // which require human approval). Never applied to branches created off main.\n subBranchNaming?: string;\n // Human-sentence instruction telling the AI how to name a NEW branch off main. Surfaced back\n // to the agent in the guard's fix hints. May mirror no-edit-on-main.branchNamingConvention.\n branchFormat?: string;\n\n static readonly SCHEMA: SchemaShape<BranchCreationGuardConfig> = {\n mode: new FieldDef('string', BRANCH_GUARD_MODES),\n subBranchNaming: FieldDef.optional('string'),\n branchFormat: FieldDef.optional('string'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class PrCreationGuardConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n // The gated command the guard points agents to instead of direct PR creation. Per-project\n // override; defaults to `pnpm wp-upsert-pr` at the point of use.\n upsertPrCommand?: string;\n\n static readonly SCHEMA: SchemaShape<PrCreationGuardConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n upsertPrCommand: FieldDef.optional('string'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class MergeInProgressGuardConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n\n static readonly SCHEMA: SchemaShape<MergeInProgressGuardConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class PrMergeCleanupConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n\n static readonly SCHEMA: SchemaShape<PrMergeCleanupConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoDirectMainUpdateConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n\n static readonly SCHEMA: SchemaShape<NoDirectMainUpdateConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoEditOnMainConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n branchNamingConvention?: string;\n\n static readonly SCHEMA: SchemaShape<NoEditOnMainConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n branchNamingConvention: FieldDef.optional('string'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoFileImportCyclesConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n ignoreTypeOnly?: boolean;\n excludePackages?: string[];\n\n static readonly SCHEMA: SchemaShape<NoFileImportCyclesConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n ignoreTypeOnly: FieldDef.optional('boolean'),\n excludePackages: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class RuntimeArchitectureConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n servicePaths?: string[];\n apiProjectPaths?: string[];\n allowedCycles?: string[];\n\n static readonly SCHEMA: SchemaShape<RuntimeArchitectureConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n servicePaths: FieldDef.optional('string[]'),\n apiProjectPaths: FieldDef.optional('string[]'),\n allowedCycles: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoJsFilesConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n allowedPaths?: string[];\n\n static readonly SCHEMA: SchemaShape<NoJsFilesConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n allowedPaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class ValidateTsInSrcConfig extends BaseRuleConfig {\n declare mode?: ValidateTsMode;\n allowedRootFiles?: string[];\n excludePaths?: string[];\n\n static readonly SCHEMA: SchemaShape<ValidateTsInSrcConfig> = {\n mode: new FieldDef('string', VALIDATE_TS_MODES),\n allowedRootFiles: FieldDef.optional('string[]'),\n excludePaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rule-configs.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/rule-configs.ts"],"names":[],"mappings":"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","sourcesContent":["import { FieldDef, SchemaShape } from './field-def';\n\n// Mode const arrays — TypeScript union types derived from them, and FieldDef enum\n// values reference the same array. Impossible for the type and runtime check to diverge.\n\n// Single source of truth for rule \"mode\" values. Exported so code-rules (and any other\n// consumer) imports these instead of re-declaring the same unions — a rename here ripples\n// everywhere at compile time. The FieldDef SCHEMA below references the same arrays, so the\n// type and the runtime validation can never diverge.\nexport const METHOD_LIMIT_MODES = ['OFF', 'NEW_METHODS', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'] as const;\nexport type MethodLimitMode = typeof METHOD_LIMIT_MODES[number];\n\nexport const FILE_LIMIT_MODES = ['OFF', 'MODIFIED_FILES'] as const;\nexport type FileLimitMode = typeof FILE_LIMIT_MODES[number];\n\nexport const RETURN_TYPE_MODES = ['OFF', 'NEW_METHODS', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'] as const;\nexport type ReturnTypeMode = typeof RETURN_TYPE_MODES[number];\n\nexport const INLINE_TYPE_MODES = ['OFF', 'NEW_METHODS', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'] as const;\nexport type InlineTypeMode = typeof INLINE_TYPE_MODES[number];\n\nexport const MODIFIED_CODE_MODES = ['OFF', 'MODIFIED_CODE', 'MODIFIED_FILES'] as const;\nexport type ModifiedCodeMode = typeof MODIFIED_CODE_MODES[number];\n\nexport const PRISMA_DTOS_MODES = ['OFF', 'MODIFIED_CLASS', 'MODIFIED_FILES'] as const;\nexport type PrismaValidateDtosMode = typeof PRISMA_DTOS_MODES[number];\n\nexport const PRISMA_CONVERTER_MODES = ['OFF', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'] as const;\nexport type PrismaConverterMode = typeof PRISMA_CONVERTER_MODES[number];\n\nexport const DIRECT_API_RESOLVER_MODES = ['OFF', 'MODIFIED_CODE', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'] as const;\nexport type DirectApiResolverMode = typeof DIRECT_API_RESOLVER_MODES[number];\n\nexport const THROW_CAUSE_MODES = ['ON', 'OFF', 'MODIFIED_CODE'] as const;\nexport type ThrowCauseMode = typeof THROW_CAUSE_MODES[number];\n\nexport const ON_OFF_MODES = ['ON', 'OFF'] as const;\nexport type OnOffMode = typeof ON_OFF_MODES[number];\n\n// branch-creation-guard modes. ON_NO_SUBBRANCHES is the strict variant: it hard-blocks\n// creating a branch off any non-main branch (no sub-branch affordance), pointing the agent\n// back to `git checkout main && git pull && git checkout -b <branch>`. Temporarily overridable\n// via the universal ignoreModifiedUntilEpoch escape hatch.\nexport const BRANCH_GUARD_MODES = ['ON', 'OFF', 'ON_NO_SUBBRANCHES'] as const;\nexport type BranchGuardMode = typeof BRANCH_GUARD_MODES[number];\n\nexport const VALIDATE_TS_MODES = ['ON', 'OFF', 'MODIFIED_FILES'] as const;\nexport type ValidateTsMode = typeof VALIDATE_TS_MODES[number];\n\n// ---------------------------------------------------------------------------\n// Universal escape hatches — EVERY rule supports temporarily disabling itself\n// either while on a named git branch (ignoreRuleWhileOnBranch) or until an\n// epoch passes (ignoreModifiedUntilEpoch). They live on a shared base class so\n// the two fields (and their schema entries) are declared once instead of\n// repeated per rule. `mode` stays per-rule because its allowed values vary\n// (ON/OFF vs MODIFIED_CODE vs NEW_AND_MODIFIED_METHODS, etc).\n//\n// `ignoreModifiedUntilEpoch` is REQUIRED on every rule so the time-box escape\n// hatch is always present and a rule can be turned off with a one-value edit.\n// Convention: 0 = rule active (epoch is in the past, never skipped); a future\n// unix epoch IN SECONDS = rule temporarily disabled until that moment.\n// `ignoreRuleWhileOnBranch` stays optional.\n// ---------------------------------------------------------------------------\nexport abstract class BaseRuleConfig {\n // `mode` is declared here (loosely typed) so the shared AbstractRule base can read it for\n // on/off. Each concrete *Config narrows it to its own union (e.g. `mode?: ModifiedCodeMode`),\n // which is an assignable (covariant) override.\n mode?: string;\n // TS-optional, but schema-REQUIRED (see BASE_RULE_SCHEMA) — same split as `mode`.\n ignoreModifiedUntilEpoch?: number;\n ignoreRuleWhileOnBranch?: string;\n}\n\nexport const BASE_RULE_SCHEMA = {\n ignoreModifiedUntilEpoch: new FieldDef('number'),\n ignoreRuleWhileOnBranch: FieldDef.optional('string'),\n};\n\nexport class MaxMethodLinesConfig extends BaseRuleConfig {\n declare mode?: MethodLimitMode;\n limit?: number;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<MaxMethodLinesConfig> = {\n mode: new FieldDef('string', METHOD_LIMIT_MODES),\n limit: FieldDef.optional('number'),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class MaxFileLinesConfig extends BaseRuleConfig {\n declare mode?: FileLimitMode;\n limit?: number;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<MaxFileLinesConfig> = {\n mode: new FieldDef('string', FILE_LIMIT_MODES),\n limit: FieldDef.optional('number'),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class RequireReturnTypeConfig extends BaseRuleConfig {\n declare mode?: ReturnTypeMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<RequireReturnTypeConfig> = {\n mode: new FieldDef('string', RETURN_TYPE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoInlineTypeLiteralsConfig extends BaseRuleConfig {\n declare mode?: InlineTypeMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<NoInlineTypeLiteralsConfig> = {\n mode: new FieldDef('string', INLINE_TYPE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoAnyUnknownConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<NoAnyUnknownConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoImplicitAnyConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<NoImplicitAnyConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class PrismaValidateDtosConfig extends BaseRuleConfig {\n declare mode?: PrismaValidateDtosMode;\n disableAllowed?: boolean;\n prismaSchemaPath?: string;\n dtoSourcePaths?: string[];\n\n static readonly SCHEMA: SchemaShape<PrismaValidateDtosConfig> = {\n mode: new FieldDef('string', PRISMA_DTOS_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n prismaSchemaPath: FieldDef.optional('string'),\n dtoSourcePaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class PrismaConverterConfig extends BaseRuleConfig {\n declare mode?: PrismaConverterMode;\n disableAllowed?: boolean;\n schemaPath?: string;\n convertersPaths?: string[];\n enforcePaths?: string[];\n\n static readonly SCHEMA: SchemaShape<PrismaConverterConfig> = {\n mode: new FieldDef('string', PRISMA_CONVERTER_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n schemaPath: FieldDef.optional('string'),\n convertersPaths: FieldDef.optional('string[]'),\n enforcePaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoDestructureConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n allowTopLevel?: boolean;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<NoDestructureConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n allowTopLevel: FieldDef.optional('boolean'),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoUnmanagedExceptionsConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<NoUnmanagedExceptionsConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class CatchErrorPatternConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<CatchErrorPatternConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class ThrowCauseRequiredConfig extends BaseRuleConfig {\n declare mode?: ThrowCauseMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<ThrowCauseRequiredConfig> = {\n mode: new FieldDef('string', THROW_CAUSE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class AngularNoDirectApiInResolverConfig extends BaseRuleConfig {\n declare mode?: DirectApiResolverMode;\n disableAllowed?: boolean;\n enforcePaths?: string[];\n\n static readonly SCHEMA: SchemaShape<AngularNoDirectApiInResolverConfig> = {\n mode: new FieldDef('string', DIRECT_API_RESOLVER_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n enforcePaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoSymbolDiTokensConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n disableAllowed?: boolean;\n allowedPaths?: string[];\n\n static readonly SCHEMA: SchemaShape<NoSymbolDiTokensConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n allowedPaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoShellSubstitutionConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n\n static readonly SCHEMA: SchemaShape<NoShellSubstitutionConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class BranchCreationGuardConfig extends BaseRuleConfig {\n declare mode?: BranchGuardMode;\n // Naming pattern for stacked SUB-branches only (branches created off another feature branch,\n // which require human approval). Never applied to branches created off main.\n subBranchNaming?: string;\n // Human-sentence instruction telling the AI how to name a NEW branch off main. Surfaced back\n // to the agent in the guard's fix hints. May mirror no-edit-on-main.branchNamingConvention.\n branchFormat?: string;\n\n static readonly SCHEMA: SchemaShape<BranchCreationGuardConfig> = {\n mode: new FieldDef('string', BRANCH_GUARD_MODES),\n subBranchNaming: FieldDef.optional('string'),\n branchFormat: FieldDef.optional('string'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class PrCreationGuardConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n // The gated command the guard points agents to instead of direct PR creation. Per-project\n // override; defaults to `pnpm wp-start-upsert-pr` at the point of use.\n upsertPrCommand?: string;\n\n static readonly SCHEMA: SchemaShape<PrCreationGuardConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n upsertPrCommand: FieldDef.optional('string'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class MergeInProgressGuardConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n // The gated command the guard points agents to in order to finish a 3-point merge. Per-project\n // override; defaults to `commands.mergeComplete` (pnpm wp-git-merge-complete) at load time.\n mergeCompleteCommand?: string;\n\n static readonly SCHEMA: SchemaShape<MergeInProgressGuardConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n mergeCompleteCommand: FieldDef.optional('string'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class PrMergeCleanupConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n\n static readonly SCHEMA: SchemaShape<PrMergeCleanupConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoDirectMainUpdateConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n\n static readonly SCHEMA: SchemaShape<NoDirectMainUpdateConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoEditOnMainConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n branchNamingConvention?: string;\n\n static readonly SCHEMA: SchemaShape<NoEditOnMainConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n branchNamingConvention: FieldDef.optional('string'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoFileImportCyclesConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n ignoreTypeOnly?: boolean;\n excludePackages?: string[];\n\n static readonly SCHEMA: SchemaShape<NoFileImportCyclesConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n ignoreTypeOnly: FieldDef.optional('boolean'),\n excludePackages: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class RuntimeArchitectureConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n servicePaths?: string[];\n apiProjectPaths?: string[];\n allowedCycles?: string[];\n\n static readonly SCHEMA: SchemaShape<RuntimeArchitectureConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n servicePaths: FieldDef.optional('string[]'),\n apiProjectPaths: FieldDef.optional('string[]'),\n allowedCycles: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoJsFilesConfig extends BaseRuleConfig {\n declare mode?: OnOffMode;\n allowedPaths?: string[];\n\n static readonly SCHEMA: SchemaShape<NoJsFilesConfig> = {\n mode: new FieldDef('string', ON_OFF_MODES),\n allowedPaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class ValidateTsInSrcConfig extends BaseRuleConfig {\n declare mode?: ValidateTsMode;\n allowedRootFiles?: string[];\n excludePaths?: string[];\n\n static readonly SCHEMA: SchemaShape<ValidateTsInSrcConfig> = {\n mode: new FieldDef('string', VALIDATE_TS_MODES),\n allowedRootFiles: FieldDef.optional('string[]'),\n excludePaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n"]}
|
package/src/sections.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HOOK_GUARD_NAMES = void 0;
|
|
4
|
+
exports.isHookGuard = isHookGuard;
|
|
5
|
+
exports.sectionForRule = sectionForRule;
|
|
6
|
+
// The bash-scope guards. Single source of truth for the rule/guard split, imported by the config
|
|
7
|
+
// validator (placement checks), the loader (section merge), and the installer (config seeding).
|
|
8
|
+
exports.HOOK_GUARD_NAMES = [
|
|
9
|
+
'branch-creation-guard',
|
|
10
|
+
'pr-creation-guard',
|
|
11
|
+
'merge-in-progress-guard',
|
|
12
|
+
'pr-merge-cleanup',
|
|
13
|
+
'no-direct-main-update',
|
|
14
|
+
'no-edit-on-main',
|
|
15
|
+
'no-shell-substitution',
|
|
16
|
+
];
|
|
17
|
+
const HOOK_GUARD_SET = new Set(exports.HOOK_GUARD_NAMES);
|
|
18
|
+
function isHookGuard(name) {
|
|
19
|
+
return HOOK_GUARD_SET.has(name);
|
|
20
|
+
}
|
|
21
|
+
function sectionForRule(name) {
|
|
22
|
+
return HOOK_GUARD_SET.has(name) ? 'hookGuards' : 'rules';
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=sections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sections.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/sections.ts"],"names":[],"mappings":";;;AAwBA,kCAEC;AAED,wCAEC;AApBD,iGAAiG;AACjG,gGAAgG;AACnF,QAAA,gBAAgB,GAAsB;IAC/C,uBAAuB;IACvB,mBAAmB;IACnB,yBAAyB;IACzB,kBAAkB;IAClB,uBAAuB;IACvB,iBAAiB;IACjB,uBAAuB;CAC1B,CAAC;AAEF,MAAM,cAAc,GAAwB,IAAI,GAAG,CAAC,wBAAgB,CAAC,CAAC;AAEtE,SAAgB,WAAW,CAAC,IAAY;IACpC,OAAO,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,SAAgB,cAAc,CAAC,IAAY;IACvC,OAAO,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC;AAC7D,CAAC","sourcesContent":["// Which top-level section of webpieces.config.json a built-in belongs to.\n//\n// - \"rules\" — code-style validators (scope edit/file). They inspect file contents/diffs.\n// - \"hookGuards\" — git/PR/branch protection (scope bash). They intercept the shell command an\n// agent is about to run (git/gh) rather than validate file contents.\n//\n// These are conceptually different and are installed differently (guards typically for the whole\n// team, code rules often per-developer while iterating), so they live in separate config sections.\nexport type ConfigSection = 'rules' | 'hookGuards';\n\n// The bash-scope guards. Single source of truth for the rule/guard split, imported by the config\n// validator (placement checks), the loader (section merge), and the installer (config seeding).\nexport const HOOK_GUARD_NAMES: readonly string[] = [\n 'branch-creation-guard',\n 'pr-creation-guard',\n 'merge-in-progress-guard',\n 'pr-merge-cleanup',\n 'no-direct-main-update',\n 'no-edit-on-main',\n 'no-shell-substitution',\n];\n\nconst HOOK_GUARD_SET: ReadonlySet<string> = new Set(HOOK_GUARD_NAMES);\n\nexport function isHookGuard(name: string): boolean {\n return HOOK_GUARD_SET.has(name);\n}\n\nexport function sectionForRule(name: string): ConfigSection {\n return HOOK_GUARD_SET.has(name) ? 'hookGuards' : 'rules';\n}\n"]}
|
package/src/validate-config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare function allRuleNames(): readonly string[];
|
|
1
2
|
export declare function validateWebpiecesConfig(rawRules: Record<string, Record<string, unknown>>): string[];
|
|
2
3
|
/**
|
|
3
4
|
* Validate the top-level `pr-gate` section. It is REQUIRED (a client that opts out sets mode "OFF").
|
|
@@ -6,3 +7,17 @@ export declare function validateWebpiecesConfig(rawRules: Record<string, Record<
|
|
|
6
7
|
* nested `gates` array can't be expressed there, so it gets its own structural validation here.
|
|
7
8
|
*/
|
|
8
9
|
export declare function validatePrGateSection(section: unknown): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Enforce that each built-in lives in its correct section: code rules under `rules`, bash guards
|
|
12
|
+
* under `hookGuards`. A guard left in `rules` (or a rule placed in `hookGuards`) is reported with a
|
|
13
|
+
* "move it" message so the split stays clean. Unknown/custom names are ignored (they may be custom
|
|
14
|
+
* rules from rulesDir). Presence ("every built-in must be configured") is checked separately by
|
|
15
|
+
* validateWebpiecesConfig against the merged map.
|
|
16
|
+
*/
|
|
17
|
+
export declare function validateSectionPlacement(rulesSection: Record<string, Record<string, unknown>>, hookGuardsSection: Record<string, Record<string, unknown>>): string[];
|
|
18
|
+
/**
|
|
19
|
+
* Validate the `commands` section: its `pr-gate` block (delegated to validatePrGateSection) plus the
|
|
20
|
+
* optional command-string fields. Also surfaces a migration error if a DEPRECATED top-level `pr-gate`
|
|
21
|
+
* block is still present, telling the consumer to move it under `commands`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function validateCommandsSection(commands: unknown, legacyPrGate: unknown): string[];
|
package/src/validate-config.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.allRuleNames = allRuleNames;
|
|
3
4
|
exports.validateWebpiecesConfig = validateWebpiecesConfig;
|
|
4
5
|
exports.validatePrGateSection = validatePrGateSection;
|
|
6
|
+
exports.validateSectionPlacement = validateSectionPlacement;
|
|
7
|
+
exports.validateCommandsSection = validateCommandsSection;
|
|
8
|
+
const sections_1 = require("./sections");
|
|
5
9
|
const rule_configs_1 = require("./rule-configs");
|
|
6
10
|
// Thin lookup table — each entry delegates to the class's own SCHEMA.
|
|
7
11
|
// No field lists here; all schemas live with their config class.
|
|
@@ -32,6 +36,11 @@ const RULE_SCHEMAS = {
|
|
|
32
36
|
'no-js-files': rule_configs_1.NoJsFilesConfig.SCHEMA,
|
|
33
37
|
'validate-ts-in-src': rule_configs_1.ValidateTsInSrcConfig.SCHEMA,
|
|
34
38
|
};
|
|
39
|
+
// Every built-in rule name that has a typed schema (code rules + bash guards). The installer uses
|
|
40
|
+
// this (with sectionForRule) to seed a fresh webpieces.config.json with every rule in its section.
|
|
41
|
+
function allRuleNames() {
|
|
42
|
+
return Object.keys(RULE_SCHEMAS);
|
|
43
|
+
}
|
|
35
44
|
function valueHint(def, key) {
|
|
36
45
|
// ignoreModifiedUntilEpoch is required on every rule; 0 keeps the rule active (epoch in the
|
|
37
46
|
// past), a future unix epoch (seconds) temporarily disables it. Spell that out for the AI.
|
|
@@ -52,7 +61,8 @@ function missingRuleSnippet(ruleName, schema) {
|
|
|
52
61
|
const required = fields.filter(f => !schema[f].optional);
|
|
53
62
|
const optional = fields.filter(f => schema[f].optional);
|
|
54
63
|
const requiredLines = required.map(f => ` "${f}": ${valueHint(schema[f], f)}`);
|
|
55
|
-
|
|
64
|
+
const section = (0, sections_1.sectionForRule)(ruleName);
|
|
65
|
+
let out = `[${ruleName}] Not configured in webpieces.config.json. Add this entry to the "${section}" section\n` +
|
|
56
66
|
`(choose values appropriate for your project):\n\n` +
|
|
57
67
|
` "${ruleName}": {\n${requiredLines.join(',\n')}\n }`;
|
|
58
68
|
if (optional.length > 0) {
|
|
@@ -113,16 +123,16 @@ const PR_GATE_MODES = ['ON', 'OFF'];
|
|
|
113
123
|
function prGateExample() {
|
|
114
124
|
return (` "pr-gate": {\n` +
|
|
115
125
|
` "mode": "ON",\n` +
|
|
116
|
-
` "buildCommand": "<command CI runs to validate a PR, e.g. pnpm nx affected --target=ci --base
|
|
126
|
+
` "buildCommand": "<command CI runs to validate a PR, e.g. pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)>",\n` +
|
|
117
127
|
` "gates": [\n` +
|
|
118
|
-
` { "name": "API Changed", "patterns": ["libraries/apis/**", "**/*Api.ts"], "
|
|
128
|
+
` { "name": "API Changed", "patterns": ["libraries/apis/**", "**/*Api.ts"], "color": "yellow" }\n` +
|
|
119
129
|
` ]\n` +
|
|
120
130
|
` }`);
|
|
121
131
|
}
|
|
122
132
|
// webpieces-disable no-any-unknown -- one gate entry from opaque consumer JSON, validated field-by-field
|
|
123
133
|
function validateGate(gate, index) {
|
|
124
134
|
if (typeof gate !== 'object' || gate === null) {
|
|
125
|
-
return [`[pr-gate] gates[${index}] must be an object { name, patterns,
|
|
135
|
+
return [`[pr-gate] gates[${index}] must be an object { name, patterns, color, disabled? }.`];
|
|
126
136
|
}
|
|
127
137
|
// webpieces-disable no-any-unknown -- narrowing one opaque gate object from consumer JSON
|
|
128
138
|
const g = gate;
|
|
@@ -131,8 +141,10 @@ function validateGate(gate, index) {
|
|
|
131
141
|
errors.push(`[pr-gate] gates[${index}].name must be a string.`);
|
|
132
142
|
if (!Array.isArray(g['patterns']) || !g['patterns'].every(p => typeof p === 'string'))
|
|
133
143
|
errors.push(`[pr-gate] gates[${index}].patterns must be string[].`);
|
|
134
|
-
if (g['
|
|
135
|
-
errors.push(`[pr-gate] gates[${index}].
|
|
144
|
+
if (g['color'] !== undefined && g['color'] !== 'yellow' && g['color'] !== 'red')
|
|
145
|
+
errors.push(`[pr-gate] gates[${index}].color must be "yellow" or "red" (green is implicit when nothing matches).`);
|
|
146
|
+
if (g['disabled'] !== undefined && typeof g['disabled'] !== 'boolean')
|
|
147
|
+
errors.push(`[pr-gate] gates[${index}].disabled must be a boolean (example/inactive gate kept in the file).`);
|
|
136
148
|
return errors;
|
|
137
149
|
}
|
|
138
150
|
/**
|
|
@@ -145,8 +157,8 @@ function validateGate(gate, index) {
|
|
|
145
157
|
function validatePrGateSection(section) {
|
|
146
158
|
if (section === undefined || section === null) {
|
|
147
159
|
return [
|
|
148
|
-
`[pr-gate] Not configured in webpieces.config.json. Add this
|
|
149
|
-
`(
|
|
160
|
+
`[pr-gate] Not configured in webpieces.config.json. Add this block under the "commands" ` +
|
|
161
|
+
`section (set "mode": "OFF" to opt out):\n\n${prGateExample()}`,
|
|
150
162
|
];
|
|
151
163
|
}
|
|
152
164
|
if (typeof section !== 'object' || Array.isArray(section)) {
|
|
@@ -166,13 +178,13 @@ function validatePrGateSection(section) {
|
|
|
166
178
|
const cmd = s['buildCommand'];
|
|
167
179
|
if (typeof cmd !== 'string' || cmd.trim() === '') {
|
|
168
180
|
errors.push(`[pr-gate] Missing required field "buildCommand" — the command CI runs to validate a PR. ` +
|
|
169
|
-
`Add e.g. "buildCommand": "pnpm nx affected --target=ci --base
|
|
181
|
+
`Add e.g. "buildCommand": "pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)".`);
|
|
170
182
|
}
|
|
171
183
|
}
|
|
172
184
|
if ('gates' in s) {
|
|
173
185
|
const gates = s['gates'];
|
|
174
186
|
if (!Array.isArray(gates)) {
|
|
175
|
-
errors.push(`[pr-gate] "gates" must be an array of { name, patterns,
|
|
187
|
+
errors.push(`[pr-gate] "gates" must be an array of { name, patterns, color, disabled? }.`);
|
|
176
188
|
}
|
|
177
189
|
else {
|
|
178
190
|
for (let i = 0; i < gates.length; i += 1) {
|
|
@@ -182,4 +194,57 @@ function validatePrGateSection(section) {
|
|
|
182
194
|
}
|
|
183
195
|
return errors;
|
|
184
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Enforce that each built-in lives in its correct section: code rules under `rules`, bash guards
|
|
199
|
+
* under `hookGuards`. A guard left in `rules` (or a rule placed in `hookGuards`) is reported with a
|
|
200
|
+
* "move it" message so the split stays clean. Unknown/custom names are ignored (they may be custom
|
|
201
|
+
* rules from rulesDir). Presence ("every built-in must be configured") is checked separately by
|
|
202
|
+
* validateWebpiecesConfig against the merged map.
|
|
203
|
+
*/
|
|
204
|
+
// webpieces-disable no-any-unknown -- section maps are opaque consumer JSON
|
|
205
|
+
function validateSectionPlacement(rulesSection, hookGuardsSection) {
|
|
206
|
+
const errors = [];
|
|
207
|
+
for (const name of Object.keys(rulesSection)) {
|
|
208
|
+
if ((0, sections_1.isHookGuard)(name)) {
|
|
209
|
+
errors.push(`[${name}] is a hook guard and belongs in the "hookGuards" section, not "rules". ` +
|
|
210
|
+
`Move it (or run \`wp-setup-ai-hooks --sync\` to migrate automatically).`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
for (const name of Object.keys(hookGuardsSection)) {
|
|
214
|
+
// Only flag KNOWN code rules misplaced into hookGuards; unknown names may be custom rules.
|
|
215
|
+
if (!(0, sections_1.isHookGuard)(name) && RULE_SCHEMAS[name]) {
|
|
216
|
+
errors.push(`[${name}] is a code rule and belongs in the "rules" section, not "hookGuards". ` +
|
|
217
|
+
`Move it (or run \`wp-setup-ai-hooks --sync\` to migrate automatically).`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return errors;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Validate the `commands` section: its `pr-gate` block (delegated to validatePrGateSection) plus the
|
|
224
|
+
* optional command-string fields. Also surfaces a migration error if a DEPRECATED top-level `pr-gate`
|
|
225
|
+
* block is still present, telling the consumer to move it under `commands`.
|
|
226
|
+
*/
|
|
227
|
+
// webpieces-disable no-any-unknown -- `commands`/`legacyPrGate` are opaque consumer JSON
|
|
228
|
+
function validateCommandsSection(commands, legacyPrGate) {
|
|
229
|
+
const errors = [];
|
|
230
|
+
if (legacyPrGate !== undefined) {
|
|
231
|
+
errors.push(`[pr-gate] The top-level "pr-gate" block is deprecated. Move it under the "commands" ` +
|
|
232
|
+
`section as commands["pr-gate"] (run \`wp-setup-ai-hooks --sync\` to migrate automatically).`);
|
|
233
|
+
}
|
|
234
|
+
if (commands !== undefined && (typeof commands !== 'object' || commands === null || Array.isArray(commands))) {
|
|
235
|
+
errors.push(`[commands] Must be an object { "pr-gate": {...}, "upsertPr": "...", "mergeComplete": "..." }.`);
|
|
236
|
+
return errors;
|
|
237
|
+
}
|
|
238
|
+
// webpieces-disable no-any-unknown -- narrowing the opaque commands section from consumer JSON
|
|
239
|
+
const c = (commands ?? {});
|
|
240
|
+
// pr-gate is required (set mode OFF to opt out). Prefer commands["pr-gate"]; fall back to the
|
|
241
|
+
// legacy top-level block so an un-migrated file still validates its gate config.
|
|
242
|
+
errors.push(...validatePrGateSection(c['pr-gate'] ?? legacyPrGate));
|
|
243
|
+
for (const field of ['upsertPr', 'mergeComplete']) {
|
|
244
|
+
if (field in c && typeof c[field] !== 'string') {
|
|
245
|
+
errors.push(`[commands] "${field}" must be a string (the gated command to run).`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return errors;
|
|
249
|
+
}
|
|
185
250
|
//# sourceMappingURL=validate-config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/validate-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { FieldDef } from './field-def';\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 NoShellSubstitutionConfig,\n BranchCreationGuardConfig,\n PrCreationGuardConfig,\n MergeInProgressGuardConfig,\n PrMergeCleanupConfig,\n NoDirectMainUpdateConfig,\n NoEditOnMainConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n} from './rule-configs';\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.\nconst 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-shell-substitution': NoShellSubstitutionConfig.SCHEMA,\n 'branch-creation-guard': BranchCreationGuardConfig.SCHEMA,\n 'pr-creation-guard': PrCreationGuardConfig.SCHEMA,\n 'merge-in-progress-guard': MergeInProgressGuardConfig.SCHEMA,\n 'pr-merge-cleanup': PrMergeCleanupConfig.SCHEMA,\n 'no-direct-main-update': NoDirectMainUpdateConfig.SCHEMA,\n 'no-edit-on-main': NoEditOnMainConfig.SCHEMA,\n 'no-file-import-cycles': NoFileImportCyclesConfig.SCHEMA,\n 'runtime-architecture': RuntimeArchitectureConfig.SCHEMA,\n 'no-js-files': NoJsFilesConfig.SCHEMA,\n 'validate-ts-in-src': ValidateTsInSrcConfig.SCHEMA,\n};\n\nfunction valueHint(def: FieldDef, key?: string): string {\n // ignoreModifiedUntilEpoch is required on every rule; 0 keeps the rule active (epoch in the\n // past), a future unix epoch (seconds) temporarily disables it. Spell that out for the AI.\n if (key === 'ignoreModifiedUntilEpoch') return '0 (0 = active; future unix-epoch seconds = temporarily off)';\n return def.enumValues\n ? `\"${def.enumValues.join(' | ')}\"`\n : def.type === 'string[]' ? '[\"<string>\", ...]'\n : def.type === 'number' ? '<number>'\n : def.type === 'boolean' ? '<boolean>'\n : '\"<string>\"';\n}\n\nfunction missingRuleSnippet(ruleName: string, schema: Record<string, FieldDef>): string {\n // Only required fields go in the copy-paste entry. Optional fields (e.g. the\n // universal escape hatches ignoreRuleWhileOnBranch / ignoreModifiedUntilEpoch)\n // are listed separately so the snippet doesn't over-state what's mandatory.\n const fields = Object.keys(schema);\n const required = fields.filter(f => !schema[f].optional);\n const optional = fields.filter(f => schema[f].optional);\n\n const requiredLines = required.map(f => ` \"${f}\": ${valueHint(schema[f], f)}`);\n let out =\n `[${ruleName}] Not configured in webpieces.config.json. Add this entry to the \"rules\" section\\n` +\n `(choose values appropriate for your project):\\n\\n` +\n ` \"${ruleName}\": {\\n${requiredLines.join(',\\n')}\\n }`;\n\n if (optional.length > 0) {\n const optionalLines = optional.map(f => ` \"${f}\": ${valueHint(schema[f], f)}`);\n out +=\n `\\n\\nOptional fields you may add to this rule (omit if not needed):\\n` +\n `${optionalLines.join(',\\n')}`;\n }\n return out;\n}\n\n// webpieces-disable no-any-unknown -- rawRules values are opaque JSON; each field is validated individually\nexport function validateWebpiecesConfig(\n rawRules: Record<string, Record<string, unknown>>,\n): string[] {\n const errors: string[] = [];\n\n // Check field-level correctness for rules that are present\n for (const [ruleName, entry] of Object.entries(rawRules)) {\n const schema = RULE_SCHEMAS[ruleName];\n if (!schema) continue; // custom/unknown rule — no schema to validate against\n for (const [key, value] of Object.entries(entry)) {\n const fieldDef = schema[key];\n if (!fieldDef) {\n errors.push(`[${ruleName}] Unknown field \"${key}\". Valid fields: [${Object.keys(schema).join(', ')}]`);\n continue;\n }\n if (fieldDef.type === 'string[]') {\n if (!Array.isArray(value) || !value.every(v => typeof v === 'string'))\n errors.push(`[${ruleName}] \"${key}\" must be string[], got ${typeof value}.`);\n } else if (typeof value !== fieldDef.type) {\n errors.push(`[${ruleName}] \"${key}\" must be ${fieldDef.type}, got ${typeof value}.`);\n } else if (fieldDef.enumValues && !fieldDef.enumValues.includes(value as string)) {\n errors.push(`[${ruleName}] \"${key}\" = \"${value}\" is not valid. Must be one of: ${fieldDef.enumValues.join(', ')}.`);\n }\n }\n // Required fields must actually be present. Until now the loop above only checked\n // fields that WERE present, so an entry like `{}` (or one missing `mode` /\n // `ignoreModifiedUntilEpoch`) slipped through. Every non-optional schema field is mandatory.\n for (const [key, fieldDef] of Object.entries(schema)) {\n if (!fieldDef.optional && !(key in entry)) {\n errors.push(`[${ruleName}] Missing required field \"${key}\". Add ${key}: ${valueHint(fieldDef, key)}.`);\n }\n }\n }\n\n // Every built-in rule must be explicitly configured — no silent defaults.\n // When a new rule is added to the framework, this check surfaces it immediately\n // with a ready-to-copy snippet so AI can configure it in one pass.\n for (const [ruleName, schema] of Object.entries(RULE_SCHEMAS)) {\n if (!(ruleName in rawRules)) {\n errors.push(missingRuleSnippet(ruleName, schema));\n }\n }\n\n return errors;\n}\n\nconst PR_GATE_MODES = ['ON', 'OFF'] as const;\n\n// Copy-paste example for the top-level `pr-gate` block (sibling of `rules`). Kept inline rather\n// than imported from pr-gate-config.ts to avoid a load-config ↔ pr-gate-config import cycle.\nfunction prGateExample(): string {\n return (\n ` \"pr-gate\": {\\n` +\n ` \"mode\": \"ON\",\\n` +\n ` \"buildCommand\": \"<command CI runs to validate a PR, e.g. pnpm nx affected --target=ci --base=origin/main>\",\\n` +\n ` \"gates\": [\\n` +\n ` { \"name\": \"API Changed\", \"patterns\": [\"libraries/apis/**\", \"**/*Api.ts\"], \"severity\": \"warn\" }\\n` +\n ` ]\\n` +\n ` }`\n );\n}\n\n// webpieces-disable no-any-unknown -- one gate entry from opaque consumer JSON, validated field-by-field\nfunction validateGate(gate: unknown, index: number): string[] {\n if (typeof gate !== 'object' || gate === null) {\n return [`[pr-gate] gates[${index}] must be an object { name, patterns, severity }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque gate object from consumer JSON\n const g = gate as Record<string, unknown>;\n const errors: string[] = [];\n if (typeof g['name'] !== 'string') errors.push(`[pr-gate] gates[${index}].name must be a string.`);\n if (!Array.isArray(g['patterns']) || !g['patterns'].every(p => typeof p === 'string'))\n errors.push(`[pr-gate] gates[${index}].patterns must be string[].`);\n if (g['severity'] !== undefined && typeof g['severity'] !== 'string')\n errors.push(`[pr-gate] gates[${index}].severity must be a string (\"warn\" | \"block\").`);\n return errors;\n}\n\n/**\n * Validate the top-level `pr-gate` section. It is REQUIRED (a client that opts out sets mode \"OFF\").\n * `buildCommand` is required unless mode is \"OFF\". Returns human-readable, copy-paste-friendly errors\n * — never throws. The pr-gate block lives outside the FieldDef-driven `rules` schema because its\n * nested `gates` array can't be expressed there, so it gets its own structural validation here.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed below\nexport function validatePrGateSection(section: unknown): string[] {\n if (section === undefined || section === null) {\n return [\n `[pr-gate] Not configured in webpieces.config.json. Add this top-level block ` +\n `(sibling of \"rules\"; set \"mode\": \"OFF\" to opt out):\\n\\n${prGateExample()}`,\n ];\n }\n if (typeof section !== 'object' || Array.isArray(section)) {\n return [`[pr-gate] Must be an object. Example:\\n\\n${prGateExample()}`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque pr-gate section from consumer JSON\n const s = section as Record<string, unknown>;\n const errors: string[] = [];\n\n if (!('mode' in s)) {\n errors.push(`[pr-gate] Missing required field \"mode\". Must be one of: ${PR_GATE_MODES.join(', ')}.`);\n } else if (typeof s['mode'] !== 'string' || !PR_GATE_MODES.includes(s['mode'] as typeof PR_GATE_MODES[number])) {\n errors.push(`[pr-gate] \"mode\" = \"${String(s['mode'])}\" is not valid. Must be one of: ${PR_GATE_MODES.join(', ')}.`);\n }\n\n // buildCommand is required whenever the gate is active (mode !== OFF).\n if (s['mode'] !== 'OFF') {\n const cmd = s['buildCommand'];\n if (typeof cmd !== 'string' || cmd.trim() === '') {\n errors.push(\n `[pr-gate] Missing required field \"buildCommand\" — the command CI runs to validate a PR. ` +\n `Add e.g. \"buildCommand\": \"pnpm nx affected --target=ci --base=origin/main\".`,\n );\n }\n }\n\n if ('gates' in s) {\n const gates = s['gates'];\n if (!Array.isArray(gates)) {\n errors.push(`[pr-gate] \"gates\" must be an array of { name, patterns, severity }.`);\n } else {\n for (let i = 0; i < gates.length; i += 1) {\n errors.push(...validateGate(gates[i], i));\n }\n }\n }\n\n return errors;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"validate-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/validate-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { FieldDef } from './field-def';\nimport { sectionForRule, isHookGuard } from './sections';\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 NoShellSubstitutionConfig,\n BranchCreationGuardConfig,\n PrCreationGuardConfig,\n MergeInProgressGuardConfig,\n PrMergeCleanupConfig,\n NoDirectMainUpdateConfig,\n NoEditOnMainConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n} from './rule-configs';\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.\nconst 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-shell-substitution': NoShellSubstitutionConfig.SCHEMA,\n 'branch-creation-guard': BranchCreationGuardConfig.SCHEMA,\n 'pr-creation-guard': PrCreationGuardConfig.SCHEMA,\n 'merge-in-progress-guard': MergeInProgressGuardConfig.SCHEMA,\n 'pr-merge-cleanup': PrMergeCleanupConfig.SCHEMA,\n 'no-direct-main-update': NoDirectMainUpdateConfig.SCHEMA,\n 'no-edit-on-main': NoEditOnMainConfig.SCHEMA,\n 'no-file-import-cycles': NoFileImportCyclesConfig.SCHEMA,\n 'runtime-architecture': RuntimeArchitectureConfig.SCHEMA,\n 'no-js-files': NoJsFilesConfig.SCHEMA,\n 'validate-ts-in-src': ValidateTsInSrcConfig.SCHEMA,\n};\n\n// Every built-in rule name 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 rule in its section.\nexport function allRuleNames(): readonly string[] {\n return Object.keys(RULE_SCHEMAS);\n}\n\nfunction valueHint(def: FieldDef, key?: string): string {\n // ignoreModifiedUntilEpoch is required on every rule; 0 keeps the rule active (epoch in the\n // past), a future unix epoch (seconds) temporarily disables it. Spell that out for the AI.\n if (key === 'ignoreModifiedUntilEpoch') return '0 (0 = active; future unix-epoch seconds = temporarily off)';\n return def.enumValues\n ? `\"${def.enumValues.join(' | ')}\"`\n : def.type === 'string[]' ? '[\"<string>\", ...]'\n : def.type === 'number' ? '<number>'\n : def.type === 'boolean' ? '<boolean>'\n : '\"<string>\"';\n}\n\nfunction missingRuleSnippet(ruleName: string, schema: Record<string, FieldDef>): string {\n // Only required fields go in the copy-paste entry. Optional fields (e.g. the\n // universal escape hatches ignoreRuleWhileOnBranch / ignoreModifiedUntilEpoch)\n // are listed separately so the snippet doesn't over-state what's mandatory.\n const fields = Object.keys(schema);\n const required = fields.filter(f => !schema[f].optional);\n const optional = fields.filter(f => schema[f].optional);\n\n const requiredLines = required.map(f => ` \"${f}\": ${valueHint(schema[f], f)}`);\n const section = sectionForRule(ruleName);\n let out =\n `[${ruleName}] Not configured in webpieces.config.json. Add this entry to the \"${section}\" section\\n` +\n `(choose values appropriate for your project):\\n\\n` +\n ` \"${ruleName}\": {\\n${requiredLines.join(',\\n')}\\n }`;\n\n if (optional.length > 0) {\n const optionalLines = optional.map(f => ` \"${f}\": ${valueHint(schema[f], f)}`);\n out +=\n `\\n\\nOptional fields you may add to this rule (omit if not needed):\\n` +\n `${optionalLines.join(',\\n')}`;\n }\n return out;\n}\n\n// webpieces-disable no-any-unknown -- rawRules values are opaque JSON; each field is validated individually\nexport function validateWebpiecesConfig(\n rawRules: Record<string, Record<string, unknown>>,\n): string[] {\n const errors: string[] = [];\n\n // Check field-level correctness for rules that are present\n for (const [ruleName, entry] of Object.entries(rawRules)) {\n const schema = RULE_SCHEMAS[ruleName];\n if (!schema) continue; // custom/unknown rule — no schema to validate against\n for (const [key, value] of Object.entries(entry)) {\n const fieldDef = schema[key];\n if (!fieldDef) {\n errors.push(`[${ruleName}] Unknown field \"${key}\". Valid fields: [${Object.keys(schema).join(', ')}]`);\n continue;\n }\n if (fieldDef.type === 'string[]') {\n if (!Array.isArray(value) || !value.every(v => typeof v === 'string'))\n errors.push(`[${ruleName}] \"${key}\" must be string[], got ${typeof value}.`);\n } else if (typeof value !== fieldDef.type) {\n errors.push(`[${ruleName}] \"${key}\" must be ${fieldDef.type}, got ${typeof value}.`);\n } else if (fieldDef.enumValues && !fieldDef.enumValues.includes(value as string)) {\n errors.push(`[${ruleName}] \"${key}\" = \"${value}\" is not valid. Must be one of: ${fieldDef.enumValues.join(', ')}.`);\n }\n }\n // Required fields must actually be present. Until now the loop above only checked\n // fields that WERE present, so an entry like `{}` (or one missing `mode` /\n // `ignoreModifiedUntilEpoch`) slipped through. Every non-optional schema field is mandatory.\n for (const [key, fieldDef] of Object.entries(schema)) {\n if (!fieldDef.optional && !(key in entry)) {\n errors.push(`[${ruleName}] Missing required field \"${key}\". Add ${key}: ${valueHint(fieldDef, key)}.`);\n }\n }\n }\n\n // Every built-in rule must be explicitly configured — no silent defaults.\n // When a new rule is added to the framework, this check surfaces it immediately\n // with a ready-to-copy snippet so AI can configure it in one pass.\n for (const [ruleName, schema] of Object.entries(RULE_SCHEMAS)) {\n if (!(ruleName in rawRules)) {\n errors.push(missingRuleSnippet(ruleName, schema));\n }\n }\n\n return errors;\n}\n\nconst PR_GATE_MODES = ['ON', 'OFF'] as const;\n\n// Copy-paste example for the top-level `pr-gate` block (sibling of `rules`). Kept inline rather\n// than imported from pr-gate-config.ts to avoid a load-config ↔ pr-gate-config import cycle.\nfunction prGateExample(): string {\n return (\n ` \"pr-gate\": {\\n` +\n ` \"mode\": \"ON\",\\n` +\n ` \"buildCommand\": \"<command CI runs to validate a PR, e.g. pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)>\",\\n` +\n ` \"gates\": [\\n` +\n ` { \"name\": \"API Changed\", \"patterns\": [\"libraries/apis/**\", \"**/*Api.ts\"], \"color\": \"yellow\" }\\n` +\n ` ]\\n` +\n ` }`\n );\n}\n\n// webpieces-disable no-any-unknown -- one gate entry from opaque consumer JSON, validated field-by-field\nfunction validateGate(gate: unknown, index: number): string[] {\n if (typeof gate !== 'object' || gate === null) {\n return [`[pr-gate] gates[${index}] must be an object { name, patterns, color, disabled? }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque gate object from consumer JSON\n const g = gate as Record<string, unknown>;\n const errors: string[] = [];\n if (typeof g['name'] !== 'string') errors.push(`[pr-gate] gates[${index}].name must be a string.`);\n if (!Array.isArray(g['patterns']) || !g['patterns'].every(p => typeof p === 'string'))\n errors.push(`[pr-gate] gates[${index}].patterns must be string[].`);\n if (g['color'] !== undefined && g['color'] !== 'yellow' && g['color'] !== 'red')\n errors.push(`[pr-gate] gates[${index}].color must be \"yellow\" or \"red\" (green is implicit when nothing matches).`);\n if (g['disabled'] !== undefined && typeof g['disabled'] !== 'boolean')\n errors.push(`[pr-gate] gates[${index}].disabled must be a boolean (example/inactive gate kept in the file).`);\n return errors;\n}\n\n/**\n * Validate the top-level `pr-gate` section. It is REQUIRED (a client that opts out sets mode \"OFF\").\n * `buildCommand` is required unless mode is \"OFF\". Returns human-readable, copy-paste-friendly errors\n * — never throws. The pr-gate block lives outside the FieldDef-driven `rules` schema because its\n * nested `gates` array can't be expressed there, so it gets its own structural validation here.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed below\nexport function validatePrGateSection(section: unknown): string[] {\n if (section === undefined || section === null) {\n return [\n `[pr-gate] Not configured in webpieces.config.json. Add this block under the \"commands\" ` +\n `section (set \"mode\": \"OFF\" to opt out):\\n\\n${prGateExample()}`,\n ];\n }\n if (typeof section !== 'object' || Array.isArray(section)) {\n return [`[pr-gate] Must be an object. Example:\\n\\n${prGateExample()}`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque pr-gate section from consumer JSON\n const s = section as Record<string, unknown>;\n const errors: string[] = [];\n\n if (!('mode' in s)) {\n errors.push(`[pr-gate] Missing required field \"mode\". Must be one of: ${PR_GATE_MODES.join(', ')}.`);\n } else if (typeof s['mode'] !== 'string' || !PR_GATE_MODES.includes(s['mode'] as typeof PR_GATE_MODES[number])) {\n errors.push(`[pr-gate] \"mode\" = \"${String(s['mode'])}\" is not valid. Must be one of: ${PR_GATE_MODES.join(', ')}.`);\n }\n\n // buildCommand is required whenever the gate is active (mode !== OFF).\n if (s['mode'] !== 'OFF') {\n const cmd = s['buildCommand'];\n if (typeof cmd !== 'string' || cmd.trim() === '') {\n errors.push(\n `[pr-gate] Missing required field \"buildCommand\" — the command CI runs to validate a PR. ` +\n `Add e.g. \"buildCommand\": \"pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)\".`,\n );\n }\n }\n\n if ('gates' in s) {\n const gates = s['gates'];\n if (!Array.isArray(gates)) {\n errors.push(`[pr-gate] \"gates\" must be an array of { name, patterns, color, disabled? }.`);\n } else {\n for (let i = 0; i < gates.length; i += 1) {\n errors.push(...validateGate(gates[i], i));\n }\n }\n }\n\n return errors;\n}\n\n/**\n * Enforce that each built-in lives in its correct section: code rules under `rules`, bash guards\n * under `hookGuards`. A guard left in `rules` (or a rule placed in `hookGuards`) is reported with a\n * \"move it\" message so the split stays clean. Unknown/custom names are ignored (they may be custom\n * rules from rulesDir). Presence (\"every built-in must be configured\") is checked separately by\n * validateWebpiecesConfig against the merged map.\n */\n// webpieces-disable no-any-unknown -- section maps are opaque consumer JSON\nexport function validateSectionPlacement(\n rulesSection: Record<string, Record<string, unknown>>,\n hookGuardsSection: Record<string, Record<string, unknown>>,\n): string[] {\n const errors: string[] = [];\n for (const name of Object.keys(rulesSection)) {\n if (isHookGuard(name)) {\n errors.push(\n `[${name}] is a hook guard and belongs in the \"hookGuards\" section, not \"rules\". ` +\n `Move it (or run \\`wp-setup-ai-hooks --sync\\` to migrate automatically).`,\n );\n }\n }\n for (const name of Object.keys(hookGuardsSection)) {\n // Only flag KNOWN code rules misplaced into hookGuards; unknown names may be custom rules.\n if (!isHookGuard(name) && RULE_SCHEMAS[name]) {\n errors.push(\n `[${name}] is a code rule and belongs in the \"rules\" section, not \"hookGuards\". ` +\n `Move it (or run \\`wp-setup-ai-hooks --sync\\` to migrate automatically).`,\n );\n }\n }\n return errors;\n}\n\n/**\n * Validate the `commands` section: its `pr-gate` block (delegated to validatePrGateSection) plus the\n * optional command-string fields. Also surfaces a migration error if a DEPRECATED top-level `pr-gate`\n * block is still present, telling the consumer to move it under `commands`.\n */\n// webpieces-disable no-any-unknown -- `commands`/`legacyPrGate` are opaque consumer JSON\nexport function validateCommandsSection(commands: unknown, legacyPrGate: unknown): string[] {\n const errors: string[] = [];\n\n if (legacyPrGate !== undefined) {\n errors.push(\n `[pr-gate] The top-level \"pr-gate\" block is deprecated. Move it under the \"commands\" ` +\n `section as commands[\"pr-gate\"] (run \\`wp-setup-ai-hooks --sync\\` to migrate automatically).`,\n );\n }\n\n if (commands !== undefined && (typeof commands !== 'object' || commands === null || Array.isArray(commands))) {\n errors.push(`[commands] Must be an object { \"pr-gate\": {...}, \"upsertPr\": \"...\", \"mergeComplete\": \"...\" }.`);\n return errors;\n }\n\n // webpieces-disable no-any-unknown -- narrowing the opaque commands section from consumer JSON\n const c = (commands ?? {}) as Record<string, unknown>;\n\n // pr-gate is required (set mode OFF to opt out). Prefer commands[\"pr-gate\"]; fall back to the\n // legacy top-level block so an un-migrated file still validates its gate config.\n errors.push(...validatePrGateSection(c['pr-gate'] ?? legacyPrGate));\n\n for (const field of ['upsertPr', 'mergeComplete']) {\n if (field in c && typeof c[field] !== 'string') {\n errors.push(`[commands] \"${field}\" must be a string (the gated command to run).`);\n }\n }\n\n return errors;\n}\n"]}
|