@webpieces/rules-config 0.4.546 → 0.4.548
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/checklist-docs-validator.js +4 -2
- package/src/checklist-docs-validator.js.map +1 -1
- package/src/commands-config.d.ts +7 -7
- package/src/commands-config.js +17 -17
- package/src/commands-config.js.map +1 -1
- package/src/commands-section-validators.d.ts +10 -0
- package/src/commands-section-validators.js +67 -0
- package/src/commands-section-validators.js.map +1 -0
- package/src/config-key-rules.d.ts +26 -0
- package/src/config-key-rules.js +71 -0
- package/src/config-key-rules.js.map +1 -0
- package/src/exclude-hook-paths.js +3 -3
- package/src/exclude-hook-paths.js.map +1 -1
- package/src/index.d.ts +4 -1
- package/src/index.js +25 -7
- package/src/index.js.map +1 -1
- package/src/load-config.d.ts +0 -1
- package/src/load-config.js +24 -35
- package/src/load-config.js.map +1 -1
- package/src/retired-config-keys.d.ts +75 -0
- package/src/retired-config-keys.js +145 -0
- package/src/retired-config-keys.js.map +1 -0
- package/src/validate-config.d.ts +4 -10
- package/src/validate-config.js +19 -63
- package/src/validate-config.js.map +1 -1
- package/templates/webpieces.config-policy.md +66 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/rules-config",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.548",
|
|
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",
|
|
@@ -33,11 +33,13 @@ function validateChecklistDocs(cwd) {
|
|
|
33
33
|
return [`[pr-gate] ${path.basename(configPath)} is not valid JSON: ${error.message}`];
|
|
34
34
|
}
|
|
35
35
|
const repoRoot = path.dirname(configPath);
|
|
36
|
-
//
|
|
36
|
+
// ONE location: commands["pr-gate"]. This function parses the config itself rather than going through
|
|
37
|
+
// loadAndValidate, so it used to also read the retired top-level `pr-gate` — which meant a config on the
|
|
38
|
+
// dead layout validated its checklists silently here while loadAndValidate rejected it everywhere else.
|
|
37
39
|
// webpieces-disable no-any-unknown -- narrowing the opaque command section
|
|
38
40
|
const commands = raw['commands'];
|
|
39
41
|
// webpieces-disable no-any-unknown -- narrowing the opaque pr-gate section
|
|
40
|
-
const prGate =
|
|
42
|
+
const prGate = commands?.['pr-gate'];
|
|
41
43
|
if (!prGate || !('checklists' in prGate))
|
|
42
44
|
return [];
|
|
43
45
|
return (0, validate_config_1.validateChecklistsSection)(prGate['checklists'], repoRoot);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checklist-docs-validator.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/checklist-docs-validator.ts"],"names":[],"mappings":";;AAeA,
|
|
1
|
+
{"version":3,"file":"checklist-docs-validator.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/checklist-docs-validator.ts"],"names":[],"mappings":";;AAeA,sDAwBC;;AAvCD,+CAAyB;AACzB,mDAA6B;AAC7B,+CAA+C;AAC/C,uDAA8D;AAC9D,yCAAqC;AAErC;;;;;;;GAOG;AACH,2GAA2G;AAC3G,SAAgB,qBAAqB,CAAC,GAAW;IAC7C,MAAM,UAAU,GAAG,IAAA,4BAAc,EAAC,GAAG,CAAC,CAAC;IACvC,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,CAAC;IAC3B,wFAAwF;IACxF,IAAI,GAA4B,CAAC;IACjC,6GAA6G;IAC7G,8DAA8D;IAC9D,IAAI,CAAC;QACD,wFAAwF;QACxF,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAA4B,CAAC;IACrF,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,aAAa,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,uBAAuB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1F,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1C,sGAAsG;IACtG,yGAAyG;IACzG,wGAAwG;IACxG,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAwC,CAAC;IACxE,2EAA2E;IAC3E,MAAM,MAAM,GAAG,QAAQ,EAAE,CAAC,SAAS,CAAwC,CAAC;IAC5E,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,YAAY,IAAI,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IACpD,OAAO,IAAA,2CAAyB,EAAC,MAAM,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,CAAC;AACrE,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { findConfigFile } from './config-file';\nimport { validateChecklistsSection } from './validate-config';\nimport { toError } from './to-error';\n\n/**\n * Validate ONLY the pr-gate `checklists` array, in isolation — that it IS an array (the removed `{ doc }`\n * manifest shape is rejected with its migration steps), each entry's subagent is present, distinct, and\n * names a real `.claude/agents/<subagent>.md`, each entry's repo-relative doc exists, and patterns are\n * string[]. Same logic loadAndValidate runs, but callable directly so broken checklists fail as their OWN\n * `validate-checklist-docs` check (clear owner) instead of surfacing as an unrelated validator's banner.\n * Returns errors; never throws.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches validate-config.ts\nexport function validateChecklistDocs(cwd: string): string[] {\n const configPath = findConfigFile(cwd);\n if (!configPath) return [];\n // webpieces-disable no-any-unknown -- parsed config JSON is opaque until narrowed below\n let raw: Record<string, unknown>;\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: a malformed config surfaces as one readable error\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed config JSON is opaque until narrowed below\n raw = JSON.parse(fs.readFileSync(configPath, 'utf8')) as Record<string, unknown>;\n } catch (err: unknown) {\n const error = toError(err);\n return [`[pr-gate] ${path.basename(configPath)} is not valid JSON: ${error.message}`];\n }\n const repoRoot = path.dirname(configPath);\n // ONE location: commands[\"pr-gate\"]. This function parses the config itself rather than going through\n // loadAndValidate, so it used to also read the retired top-level `pr-gate` — which meant a config on the\n // dead layout validated its checklists silently here while loadAndValidate rejected it everywhere else.\n // webpieces-disable no-any-unknown -- narrowing the opaque command section\n const commands = raw['commands'] as Record<string, unknown> | undefined;\n // webpieces-disable no-any-unknown -- narrowing the opaque pr-gate section\n const prGate = commands?.['pr-gate'] as Record<string, unknown> | undefined;\n if (!prGate || !('checklists' in prGate)) return [];\n return validateChecklistsSection(prGate['checklists'], repoRoot);\n}\n"]}
|
package/src/commands-config.d.ts
CHANGED
|
@@ -8,12 +8,12 @@ export declare class CommandsConfig {
|
|
|
8
8
|
constructor(prGate: PrGateConfig, upsertPr: string, mergeComplete: string);
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* Build a CommandsConfig from the already-parsed `commands` section, falling back to
|
|
12
|
-
* field the consumer omits.
|
|
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.
|
|
11
|
+
* Build a CommandsConfig from the already-parsed `commands` section, falling back to the built-in default
|
|
12
|
+
* for any field the consumer omits. Pure transform — the structural validation happens in loadAndValidate.
|
|
15
13
|
*
|
|
16
|
-
*
|
|
17
|
-
* commands
|
|
14
|
+
* ONE accepted source per field: commands.guardHints.<name>, else the built-in default. The retired flat
|
|
15
|
+
* `commands.upsertPr` / `commands.mergeComplete` strings are NOT read — validateCommandsSection fails the
|
|
16
|
+
* load with the move instruction instead. A resolution chain that quietly accepts the old key is exactly
|
|
17
|
+
* what kept this repo's own config on the dead shape for releases; see retired-config-keys.ts.
|
|
18
18
|
*/
|
|
19
|
-
export declare function buildCommandsConfig(section: unknown
|
|
19
|
+
export declare function buildCommandsConfig(section: unknown): CommandsConfig;
|
package/src/commands-config.js
CHANGED
|
@@ -16,10 +16,10 @@ exports.DEFAULT_MERGE_COMPLETE_COMMAND = 'pnpm wp-finish-upsert-pr';
|
|
|
16
16
|
class CommandsConfig {
|
|
17
17
|
prGate;
|
|
18
18
|
// Command the pr-creation-or-push-guard tells agents to run instead of `gh pr create` or a manual push.
|
|
19
|
-
// Sourced from commands.guardHints.prCreationOrPush
|
|
19
|
+
// Sourced from commands.guardHints.prCreationOrPush.
|
|
20
20
|
upsertPr;
|
|
21
21
|
// Command the merge-in-progress-guard tells agents to run to finish a 3-point merge.
|
|
22
|
-
// Sourced from commands.guardHints.mergeInProgress
|
|
22
|
+
// Sourced from commands.guardHints.mergeInProgress.
|
|
23
23
|
mergeComplete;
|
|
24
24
|
constructor(prGate, upsertPr, mergeComplete) {
|
|
25
25
|
this.prGate = prGate;
|
|
@@ -29,29 +29,29 @@ class CommandsConfig {
|
|
|
29
29
|
}
|
|
30
30
|
exports.CommandsConfig = CommandsConfig;
|
|
31
31
|
/**
|
|
32
|
-
* Build a CommandsConfig from the already-parsed `commands` section, falling back to
|
|
33
|
-
* field the consumer omits.
|
|
34
|
-
* it is used only as a fallback so an un-migrated file still loads its gate config. Pure transform —
|
|
35
|
-
* the structural validation happens in loadAndValidate.
|
|
32
|
+
* Build a CommandsConfig from the already-parsed `commands` section, falling back to the built-in default
|
|
33
|
+
* for any field the consumer omits. Pure transform — the structural validation happens in loadAndValidate.
|
|
36
34
|
*
|
|
37
|
-
*
|
|
38
|
-
* commands
|
|
35
|
+
* ONE accepted source per field: commands.guardHints.<name>, else the built-in default. The retired flat
|
|
36
|
+
* `commands.upsertPr` / `commands.mergeComplete` strings are NOT read — validateCommandsSection fails the
|
|
37
|
+
* load with the move instruction instead. A resolution chain that quietly accepts the old key is exactly
|
|
38
|
+
* what kept this repo's own config on the dead shape for releases; see retired-config-keys.ts.
|
|
39
39
|
*/
|
|
40
40
|
// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed here
|
|
41
|
-
function
|
|
41
|
+
// webpieces-disable no-function-outside-class -- module-level config transform, matches buildPrGateConfig in pr-gate-config.ts
|
|
42
|
+
function buildCommandsConfig(section) {
|
|
42
43
|
const raw = (typeof section === 'object' && section !== null)
|
|
43
44
|
? section
|
|
44
45
|
: {};
|
|
45
|
-
const prGateRaw = raw['pr-gate'] ?? legacyPrGate;
|
|
46
46
|
const hints = (typeof raw.guardHints === 'object' && raw.guardHints !== null)
|
|
47
47
|
? raw.guardHints
|
|
48
48
|
: {};
|
|
49
|
-
const upsertPr = typeof hints.prCreationOrPush === 'string' && hints.prCreationOrPush.trim() !== ''
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const mergeComplete = typeof hints.mergeInProgress === 'string' && hints.mergeInProgress.trim() !== ''
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return new CommandsConfig((0, pr_gate_config_1.buildPrGateConfig)(
|
|
49
|
+
const upsertPr = typeof hints.prCreationOrPush === 'string' && hints.prCreationOrPush.trim() !== ''
|
|
50
|
+
? hints.prCreationOrPush
|
|
51
|
+
: exports.DEFAULT_UPSERT_PR_COMMAND;
|
|
52
|
+
const mergeComplete = typeof hints.mergeInProgress === 'string' && hints.mergeInProgress.trim() !== ''
|
|
53
|
+
? hints.mergeInProgress
|
|
54
|
+
: exports.DEFAULT_MERGE_COMPLETE_COMMAND;
|
|
55
|
+
return new CommandsConfig((0, pr_gate_config_1.buildPrGateConfig)(raw['pr-gate']), upsertPr, mergeComplete);
|
|
56
56
|
}
|
|
57
57
|
//# sourceMappingURL=commands-config.js.map
|
|
@@ -1 +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,oGAAoG;AACpG,EAAE;AACF,sGAAsG;;;
|
|
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,oGAAoG;AACpG,EAAE;AACF,sGAAsG;;;AAmDtG,kDAcC;AA/DD,qDAAmE;AAEnE,oGAAoG;AACpG,wFAAwF;AAC3E,QAAA,yBAAyB,GAAG,yBAAyB,CAAC;AACtD,QAAA,8BAA8B,GAAG,0BAA0B,CAAC;AAEzE,MAAa,cAAc;IACvB,MAAM,CAAe;IACrB,wGAAwG;IACxG,qDAAqD;IACrD,QAAQ,CAAS;IACjB,qFAAqF;IACrF,oDAAoD;IACpD,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;AAdD,wCAcC;AAiBD;;;;;;;;GAQG;AACH,4FAA4F;AAC5F,+HAA+H;AAC/H,SAAgB,mBAAmB,CAAC,OAAgB;IAChD,MAAM,GAAG,GAAuB,CAAC,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,CAAC;QAC7E,CAAC,CAAE,OAA8B;QACjC,CAAC,CAAC,EAAE,CAAC;IACT,MAAM,KAAK,GAAkB,CAAC,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC;QACxF,CAAC,CAAC,GAAG,CAAC,UAAU;QAChB,CAAC,CAAC,EAAE,CAAC;IACT,MAAM,QAAQ,GAAG,OAAO,KAAK,CAAC,gBAAgB,KAAK,QAAQ,IAAI,KAAK,CAAC,gBAAgB,CAAC,IAAI,EAAE,KAAK,EAAE;QAC/F,CAAC,CAAC,KAAK,CAAC,gBAAgB;QACxB,CAAC,CAAC,iCAAyB,CAAC;IAChC,MAAM,aAAa,GAAG,OAAO,KAAK,CAAC,eAAe,KAAK,QAAQ,IAAI,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE;QAClG,CAAC,CAAC,KAAK,CAAC,eAAe;QACvB,CAAC,CAAC,sCAA8B,CAAC;IACrC,OAAO,IAAI,cAAc,CAAC,IAAA,kCAAiB,EAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;AAC1F,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-start-upsert-pr / wp-finish-upsert-pr 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-or-push-guard tells agents to run instead of `gh pr create` or a manual push.\n // Sourced from commands.guardHints.prCreationOrPush.\n upsertPr: string;\n // Command the merge-in-progress-guard tells agents to run to finish a 3-point merge.\n // Sourced from commands.guardHints.mergeInProgress.\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\n// The `commands.guardHints` sub-object: the ONLY command strings the guards read (each maps to exactly\n// one guard). Grouped under `guardHints` so the file makes plain these are guard hints — not pr-gate\n// config, and not command behaviour. `prCreationOrPush` → pr-creation-or-push-guard;\n// `mergeInProgress` → merge-in-progress-guard.\ninterface RawGuardHints {\n prCreationOrPush?: string;\n mergeInProgress?: string;\n}\n\ninterface RawCommandsSection {\n // webpieces-disable no-any-unknown -- opaque pr-gate JSON, validated by validatePrGateSection\n 'pr-gate'?: unknown;\n guardHints?: RawGuardHints;\n}\n\n/**\n * Build a CommandsConfig from the already-parsed `commands` section, falling back to the built-in default\n * for any field the consumer omits. Pure transform — the structural validation happens in loadAndValidate.\n *\n * ONE accepted source per field: commands.guardHints.<name>, else the built-in default. The retired flat\n * `commands.upsertPr` / `commands.mergeComplete` strings are NOT read — validateCommandsSection fails the\n * load with the move instruction instead. A resolution chain that quietly accepts the old key is exactly\n * what kept this repo's own config on the dead shape for releases; see retired-config-keys.ts.\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON until narrowed here\n// webpieces-disable no-function-outside-class -- module-level config transform, matches buildPrGateConfig in pr-gate-config.ts\nexport function buildCommandsConfig(section: unknown): CommandsConfig {\n const raw: RawCommandsSection = (typeof section === 'object' && section !== null)\n ? (section as RawCommandsSection)\n : {};\n const hints: RawGuardHints = (typeof raw.guardHints === 'object' && raw.guardHints !== null)\n ? raw.guardHints\n : {};\n const upsertPr = typeof hints.prCreationOrPush === 'string' && hints.prCreationOrPush.trim() !== ''\n ? hints.prCreationOrPush\n : DEFAULT_UPSERT_PR_COMMAND;\n const mergeComplete = typeof hints.mergeInProgress === 'string' && hints.mergeInProgress.trim() !== ''\n ? hints.mergeInProgress\n : DEFAULT_MERGE_COMPLETE_COMMAND;\n return new CommandsConfig(buildPrGateConfig(raw['pr-gate']), upsertPr, mergeComplete);\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate the `commands` section: its `pr-gate` block (delegated to validatePrGateSection), the
|
|
3
|
+
* `guardHints` command strings, and strict rejection of everything else. Also surfaces a migration error if
|
|
4
|
+
* a DEPRECATED top-level `pr-gate` block is still present, telling the consumer to move it under `commands`.
|
|
5
|
+
*
|
|
6
|
+
* There is deliberately no fallback from `commands["pr-gate"]` to the top-level block: the top-level block
|
|
7
|
+
* is a hard error above, so a config carrying it never loads, and a fallback for a shape that cannot load is
|
|
8
|
+
* dead code that only makes the real shape ambiguous. See retired-config-keys.ts for the policy.
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateCommandsSection(commands: unknown, legacyPrGate: unknown, repoRoot?: string): string[];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateCommandsSection = validateCommandsSection;
|
|
4
|
+
const config_key_rules_1 = require("./config-key-rules");
|
|
5
|
+
const retired_config_keys_1 = require("./retired-config-keys");
|
|
6
|
+
const validate_config_1 = require("./validate-config");
|
|
7
|
+
// The `commands` section of webpieces.config.json, plus the key-level rules every section shares: strict
|
|
8
|
+
// unknown-key rejection and the `*Why` comment convention. Split out of validate-config.ts only for size,
|
|
9
|
+
// exactly as pr-gate-section-validators.ts was; loadAndValidate still reaches this through
|
|
10
|
+
// validateCommandsSection, which validate-config.ts re-exports.
|
|
11
|
+
// Every key the `commands` section defines. Anything else is rejected — see unknownKeyErrors for why a
|
|
12
|
+
// tolerated unknown key is the mechanism by which a stale config shape survives upgrades forever.
|
|
13
|
+
const COMMANDS_KEYS = ['pr-gate', 'guardHints'];
|
|
14
|
+
const GUARD_HINT_FIELDS = ['prCreationOrPush', 'mergeInProgress'];
|
|
15
|
+
/**
|
|
16
|
+
* Validate the `commands` section: its `pr-gate` block (delegated to validatePrGateSection), the
|
|
17
|
+
* `guardHints` command strings, and strict rejection of everything else. Also surfaces a migration error if
|
|
18
|
+
* a DEPRECATED top-level `pr-gate` block is still present, telling the consumer to move it under `commands`.
|
|
19
|
+
*
|
|
20
|
+
* There is deliberately no fallback from `commands["pr-gate"]` to the top-level block: the top-level block
|
|
21
|
+
* is a hard error above, so a config carrying it never loads, and a fallback for a shape that cannot load is
|
|
22
|
+
* dead code that only makes the real shape ambiguous. See retired-config-keys.ts for the policy.
|
|
23
|
+
*/
|
|
24
|
+
// webpieces-disable no-any-unknown -- `commands`/`legacyPrGate` are opaque consumer JSON
|
|
25
|
+
// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file
|
|
26
|
+
function validateCommandsSection(commands, legacyPrGate, repoRoot) {
|
|
27
|
+
const errors = [];
|
|
28
|
+
if (legacyPrGate !== undefined) {
|
|
29
|
+
errors.push(`[pr-gate] The top-level "pr-gate" block is RETIRED. Move it under the "commands" ` +
|
|
30
|
+
`section as commands["pr-gate"] (run \`pnpm wp-install-ai-hooks --sync\` to migrate automatically).`);
|
|
31
|
+
}
|
|
32
|
+
if (commands !== undefined && (typeof commands !== 'object' || commands === null || Array.isArray(commands))) {
|
|
33
|
+
errors.push(`[commands] Must be an object { "pr-gate": {...}, "guardHints": {...} }.`);
|
|
34
|
+
return errors;
|
|
35
|
+
}
|
|
36
|
+
// webpieces-disable no-any-unknown -- narrowing the opaque commands section from consumer JSON
|
|
37
|
+
const c = (commands ?? {});
|
|
38
|
+
errors.push(...(0, retired_config_keys_1.retiredKeyErrorsIn)(c, '[commands]'));
|
|
39
|
+
errors.push(...(0, config_key_rules_1.unknownKeyErrors)(c, COMMANDS_KEYS, '[commands]'));
|
|
40
|
+
// pr-gate is required (set mode OFF to opt out).
|
|
41
|
+
errors.push(...(0, validate_config_1.validatePrGateSection)(c['pr-gate'], repoRoot));
|
|
42
|
+
errors.push(...validateGuardHints(c['guardHints']));
|
|
43
|
+
return errors;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The `commands.guardHints` sub-object: the command strings the bash guards print in their fix hints. Each
|
|
47
|
+
* name binds to exactly one guard, so an unknown field here is a hint nothing will ever read.
|
|
48
|
+
*/
|
|
49
|
+
// webpieces-disable no-any-unknown -- `hints` is opaque consumer JSON until narrowed here
|
|
50
|
+
// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file
|
|
51
|
+
function validateGuardHints(hints) {
|
|
52
|
+
if (hints === undefined)
|
|
53
|
+
return [];
|
|
54
|
+
if (typeof hints !== 'object' || hints === null || Array.isArray(hints)) {
|
|
55
|
+
return [`[commands] "guardHints" must be an object { "prCreationOrPush": "...", "mergeInProgress": "..." }.`];
|
|
56
|
+
}
|
|
57
|
+
// webpieces-disable no-any-unknown -- narrowing the opaque guardHints object from consumer JSON
|
|
58
|
+
const h = hints;
|
|
59
|
+
const errors = (0, config_key_rules_1.unknownKeyErrors)(h, GUARD_HINT_FIELDS, '[commands.guardHints]');
|
|
60
|
+
for (const field of GUARD_HINT_FIELDS) {
|
|
61
|
+
if (field in h && (typeof h[field] !== 'string' || h[field].trim() === '')) {
|
|
62
|
+
errors.push(`[commands] "guardHints.${field}" must be a non-empty string (the gated command to run).`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return errors;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=commands-section-validators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands-section-validators.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/commands-section-validators.ts"],"names":[],"mappings":";;AAyBA,0DA0BC;AAnDD,yDAAsD;AACtD,+DAA2D;AAC3D,uDAA0D;AAE1D,yGAAyG;AACzG,0GAA0G;AAC1G,2FAA2F;AAC3F,gEAAgE;AAEhE,uGAAuG;AACvG,kGAAkG;AAClG,MAAM,aAAa,GAAsB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;AACnE,MAAM,iBAAiB,GAAsB,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,CAAC;AAErF;;;;;;;;GAQG;AACH,yFAAyF;AACzF,8GAA8G;AAC9G,SAAgB,uBAAuB,CAAC,QAAiB,EAAE,YAAqB,EAAE,QAAiB;IAC/F,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,CAAC,IAAI,CACP,mFAAmF;YACnF,oGAAoG,CACvG,CAAC;IACN,CAAC;IAED,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC3G,MAAM,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAC;QACvF,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,+FAA+F;IAC/F,MAAM,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAA4B,CAAC;IAEtD,MAAM,CAAC,IAAI,CAAC,GAAG,IAAA,wCAAkB,EAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;IACpD,MAAM,CAAC,IAAI,CAAC,GAAG,IAAA,mCAAgB,EAAC,CAAC,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;IAEjE,iDAAiD;IACjD,MAAM,CAAC,IAAI,CAAC,GAAG,IAAA,uCAAqB,EAAC,CAAC,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC9D,MAAM,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAEpD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,0FAA0F;AAC1F,8GAA8G;AAC9G,SAAS,kBAAkB,CAAC,KAAc;IACtC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACnC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACtE,OAAO,CAAC,oGAAoG,CAAC,CAAC;IAClH,CAAC;IACD,gGAAgG;IAChG,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,MAAM,MAAM,GAAa,IAAA,mCAAgB,EAAC,CAAC,EAAE,iBAAiB,EAAE,uBAAuB,CAAC,CAAC;IACzF,KAAK,MAAM,KAAK,IAAI,iBAAiB,EAAE,CAAC;QACpC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,QAAQ,IAAK,CAAC,CAAC,KAAK,CAAY,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;YACrF,MAAM,CAAC,IAAI,CAAC,0BAA0B,KAAK,0DAA0D,CAAC,CAAC;QAC3G,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC","sourcesContent":["import { unknownKeyErrors } from './config-key-rules';\nimport { retiredKeyErrorsIn } from './retired-config-keys';\nimport { validatePrGateSection } from './validate-config';\n\n// The `commands` section of webpieces.config.json, plus the key-level rules every section shares: strict\n// unknown-key rejection and the `*Why` comment convention. Split out of validate-config.ts only for size,\n// exactly as pr-gate-section-validators.ts was; loadAndValidate still reaches this through\n// validateCommandsSection, which validate-config.ts re-exports.\n\n// Every key the `commands` section defines. Anything else is rejected — see unknownKeyErrors for why a\n// tolerated unknown key is the mechanism by which a stale config shape survives upgrades forever.\nconst COMMANDS_KEYS: readonly string[] = ['pr-gate', 'guardHints'];\nconst GUARD_HINT_FIELDS: readonly string[] = ['prCreationOrPush', 'mergeInProgress'];\n\n/**\n * Validate the `commands` section: its `pr-gate` block (delegated to validatePrGateSection), the\n * `guardHints` command strings, and strict rejection of everything else. Also surfaces a migration error if\n * a DEPRECATED top-level `pr-gate` block is still present, telling the consumer to move it under `commands`.\n *\n * There is deliberately no fallback from `commands[\"pr-gate\"]` to the top-level block: the top-level block\n * is a hard error above, so a config carrying it never loads, and a fallback for a shape that cannot load is\n * dead code that only makes the real shape ambiguous. See retired-config-keys.ts for the policy.\n */\n// webpieces-disable no-any-unknown -- `commands`/`legacyPrGate` are opaque consumer JSON\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateCommandsSection(commands: unknown, legacyPrGate: unknown, repoRoot?: string): string[] {\n const errors: string[] = [];\n\n if (legacyPrGate !== undefined) {\n errors.push(\n `[pr-gate] The top-level \"pr-gate\" block is RETIRED. Move it under the \"commands\" ` +\n `section as commands[\"pr-gate\"] (run \\`pnpm wp-install-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\": {...}, \"guardHints\": {...} }.`);\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 errors.push(...retiredKeyErrorsIn(c, '[commands]'));\n errors.push(...unknownKeyErrors(c, COMMANDS_KEYS, '[commands]'));\n\n // pr-gate is required (set mode OFF to opt out).\n errors.push(...validatePrGateSection(c['pr-gate'], repoRoot));\n errors.push(...validateGuardHints(c['guardHints']));\n\n return errors;\n}\n\n/**\n * The `commands.guardHints` sub-object: the command strings the bash guards print in their fix hints. Each\n * name binds to exactly one guard, so an unknown field here is a hint nothing will ever read.\n */\n// webpieces-disable no-any-unknown -- `hints` is opaque consumer JSON until narrowed here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction validateGuardHints(hints: unknown): string[] {\n if (hints === undefined) return [];\n if (typeof hints !== 'object' || hints === null || Array.isArray(hints)) {\n return [`[commands] \"guardHints\" must be an object { \"prCreationOrPush\": \"...\", \"mergeInProgress\": \"...\" }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque guardHints object from consumer JSON\n const h = hints as Record<string, unknown>;\n const errors: string[] = unknownKeyErrors(h, GUARD_HINT_FIELDS, '[commands.guardHints]');\n for (const field of GUARD_HINT_FIELDS) {\n if (field in h && (typeof h[field] !== 'string' || (h[field] as string).trim() === '')) {\n errors.push(`[commands] \"guardHints.${field}\" must be a non-empty string (the gated command to run).`);\n }\n }\n return errors;\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reject unknown TOP-LEVEL keys. Without this, a key that was retired at the top level (or simply
|
|
3
|
+
* misspelled) is silently ignored — the config looks configured and behaves as if it were not, and the dead
|
|
4
|
+
* key survives every upgrade. `*Why` rationale keys are allowed, which is what lets the file document
|
|
5
|
+
* itself: this repo ships a top-level `upgradePolicyWhy` stating that config is validated on startup and
|
|
6
|
+
* migrated by the agent, so no backwards-compatible release is needed.
|
|
7
|
+
*/
|
|
8
|
+
export declare function validateTopLevelKeys(raw: Record<string, unknown>): string[];
|
|
9
|
+
/**
|
|
10
|
+
* The `*Why` convention: any key ending in "Why" is free-form rationale a consumer keeps beside the key it
|
|
11
|
+
* explains. JSON has no comments, so this is how this repo documents non-obvious config — and it is the only
|
|
12
|
+
* way the config file can carry the note that matters most to the agent reading it (see the top-level
|
|
13
|
+
* "upgradePolicyWhy" this repo ships). Comment keys are therefore allowed wherever unknown keys are
|
|
14
|
+
* otherwise rejected, and must be strings.
|
|
15
|
+
*
|
|
16
|
+
* ONE exception, enforced separately: `gateSaltWhy` is rejected outright, because rationale next to the gate
|
|
17
|
+
* salt is a bypass how-to in the most-read file in the repo. See validateNoGateSaltRationale.
|
|
18
|
+
*/
|
|
19
|
+
export declare const COMMENT_KEY_SUFFIX = "Why";
|
|
20
|
+
export declare function isCommentKey(key: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Reject keys a section does not define. Without this a retired or misspelled key is simply ignored, which is
|
|
23
|
+
* how a stale shape survives an upgrade forever — the whole failure mode RETIRED_CONFIG_KEYS exists to end.
|
|
24
|
+
* Retired keys are skipped here so they get their own migration message instead of a generic "unknown key".
|
|
25
|
+
*/
|
|
26
|
+
export declare function unknownKeyErrors(section: Record<string, unknown>, knownKeys: readonly string[], label: string): string[];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.COMMENT_KEY_SUFFIX = void 0;
|
|
4
|
+
exports.validateTopLevelKeys = validateTopLevelKeys;
|
|
5
|
+
exports.isCommentKey = isCommentKey;
|
|
6
|
+
exports.unknownKeyErrors = unknownKeyErrors;
|
|
7
|
+
const retired_config_keys_1 = require("./retired-config-keys");
|
|
8
|
+
// Key-level rules every webpieces.config.json section shares: strict unknown-key rejection and the `*Why`
|
|
9
|
+
// comment convention. Its own module because both validate-config.ts (top level) and
|
|
10
|
+
// commands-section-validators.ts (the commands section) need it, and either importing the other would
|
|
11
|
+
// close an import cycle.
|
|
12
|
+
/**
|
|
13
|
+
* Every key webpieces.config.json defines at the TOP level. `pr-gate` is deliberately absent — it is the
|
|
14
|
+
* retired top-level block, and validateCommandsSection already reports it with the move instruction, so
|
|
15
|
+
* listing it here would replace that precise message with a generic "unknown key".
|
|
16
|
+
*/
|
|
17
|
+
const TOP_LEVEL_KEYS = [
|
|
18
|
+
'extends', 'rules', 'hookGuards', 'commands', 'excludePaths', 'match-rules', 'rulesDir',
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Reject unknown TOP-LEVEL keys. Without this, a key that was retired at the top level (or simply
|
|
22
|
+
* misspelled) is silently ignored — the config looks configured and behaves as if it were not, and the dead
|
|
23
|
+
* key survives every upgrade. `*Why` rationale keys are allowed, which is what lets the file document
|
|
24
|
+
* itself: this repo ships a top-level `upgradePolicyWhy` stating that config is validated on startup and
|
|
25
|
+
* migrated by the agent, so no backwards-compatible release is needed.
|
|
26
|
+
*/
|
|
27
|
+
// webpieces-disable no-any-unknown -- the raw parsed config is opaque; only key names are read here
|
|
28
|
+
// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file
|
|
29
|
+
function validateTopLevelKeys(raw) {
|
|
30
|
+
return unknownKeyErrors(raw, [...TOP_LEVEL_KEYS, 'pr-gate'], '[webpieces.config.json]');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The `*Why` convention: any key ending in "Why" is free-form rationale a consumer keeps beside the key it
|
|
34
|
+
* explains. JSON has no comments, so this is how this repo documents non-obvious config — and it is the only
|
|
35
|
+
* way the config file can carry the note that matters most to the agent reading it (see the top-level
|
|
36
|
+
* "upgradePolicyWhy" this repo ships). Comment keys are therefore allowed wherever unknown keys are
|
|
37
|
+
* otherwise rejected, and must be strings.
|
|
38
|
+
*
|
|
39
|
+
* ONE exception, enforced separately: `gateSaltWhy` is rejected outright, because rationale next to the gate
|
|
40
|
+
* salt is a bypass how-to in the most-read file in the repo. See validateNoGateSaltRationale.
|
|
41
|
+
*/
|
|
42
|
+
exports.COMMENT_KEY_SUFFIX = 'Why';
|
|
43
|
+
// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file
|
|
44
|
+
function isCommentKey(key) {
|
|
45
|
+
return key.length > exports.COMMENT_KEY_SUFFIX.length && key.endsWith(exports.COMMENT_KEY_SUFFIX);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Reject keys a section does not define. Without this a retired or misspelled key is simply ignored, which is
|
|
49
|
+
* how a stale shape survives an upgrade forever — the whole failure mode RETIRED_CONFIG_KEYS exists to end.
|
|
50
|
+
* Retired keys are skipped here so they get their own migration message instead of a generic "unknown key".
|
|
51
|
+
*/
|
|
52
|
+
// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON; only key names are read
|
|
53
|
+
// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file
|
|
54
|
+
function unknownKeyErrors(section, knownKeys, label) {
|
|
55
|
+
const errors = [];
|
|
56
|
+
for (const key of Object.keys(section)) {
|
|
57
|
+
if (knownKeys.includes(key) || (0, retired_config_keys_1.isRetiredKey)(key, label))
|
|
58
|
+
continue;
|
|
59
|
+
if (isCommentKey(key)) {
|
|
60
|
+
if (typeof section[key] !== 'string') {
|
|
61
|
+
errors.push(`${label} "${key}" is a *Why rationale note and must be a string.`);
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
errors.push(`${label} Unknown key "${key}". Valid keys: [${knownKeys.join(', ')}]. ` +
|
|
66
|
+
`Delete it, or — if it is rationale you want to keep beside a key — rename it to "<key>Why" ` +
|
|
67
|
+
`with a string value (JSON has no comments, so "*Why" siblings are how this repo documents config).`);
|
|
68
|
+
}
|
|
69
|
+
return errors;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=config-key-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-key-rules.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/config-key-rules.ts"],"names":[],"mappings":";;;AAyBA,oDAEC;AAeD,oCAEC;AASD,4CAiBC;AAtED,+DAAqD;AAErD,0GAA0G;AAC1G,qFAAqF;AACrF,sGAAsG;AACtG,yBAAyB;AAEzB;;;;GAIG;AACH,MAAM,cAAc,GAAsB;IACtC,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU;CAC1F,CAAC;AAEF;;;;;;GAMG;AACH,oGAAoG;AACpG,8GAA8G;AAC9G,SAAgB,oBAAoB,CAAC,GAA4B;IAC7D,OAAO,gBAAgB,CAAC,GAAG,EAAE,CAAC,GAAG,cAAc,EAAE,SAAS,CAAC,EAAE,yBAAyB,CAAC,CAAC;AAC5F,CAAC;AAED;;;;;;;;;GASG;AACU,QAAA,kBAAkB,GAAG,KAAK,CAAC;AAExC,8GAA8G;AAC9G,SAAgB,YAAY,CAAC,GAAW;IACpC,OAAO,GAAG,CAAC,MAAM,GAAG,0BAAkB,CAAC,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,0BAAkB,CAAC,CAAC;AACtF,CAAC;AAED;;;;GAIG;AACH,iGAAiG;AACjG,8GAA8G;AAC9G,SAAgB,gBAAgB,CAAC,OAAgC,EAAE,SAA4B,EAAE,KAAa;IAC1G,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACrC,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAA,kCAAY,EAAC,GAAG,EAAE,KAAK,CAAC;YAAE,SAAS;QAClE,IAAI,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,IAAI,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACnC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,KAAK,GAAG,kDAAkD,CAAC,CAAC;YACpF,CAAC;YACD,SAAS;QACb,CAAC;QACD,MAAM,CAAC,IAAI,CACP,GAAG,KAAK,iBAAiB,GAAG,mBAAmB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;YACxE,6FAA6F;YAC7F,oGAAoG,CACvG,CAAC;IACN,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC","sourcesContent":["import { isRetiredKey } from './retired-config-keys';\n\n// Key-level rules every webpieces.config.json section shares: strict unknown-key rejection and the `*Why`\n// comment convention. Its own module because both validate-config.ts (top level) and\n// commands-section-validators.ts (the commands section) need it, and either importing the other would\n// close an import cycle.\n\n/**\n * Every key webpieces.config.json defines at the TOP level. `pr-gate` is deliberately absent — it is the\n * retired top-level block, and validateCommandsSection already reports it with the move instruction, so\n * listing it here would replace that precise message with a generic \"unknown key\".\n */\nconst TOP_LEVEL_KEYS: readonly string[] = [\n 'extends', 'rules', 'hookGuards', 'commands', 'excludePaths', 'match-rules', 'rulesDir',\n];\n\n/**\n * Reject unknown TOP-LEVEL keys. Without this, a key that was retired at the top level (or simply\n * misspelled) is silently ignored — the config looks configured and behaves as if it were not, and the dead\n * key survives every upgrade. `*Why` rationale keys are allowed, which is what lets the file document\n * itself: this repo ships a top-level `upgradePolicyWhy` stating that config is validated on startup and\n * migrated by the agent, so no backwards-compatible release is needed.\n */\n// webpieces-disable no-any-unknown -- the raw parsed config is opaque; only key names are read here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateTopLevelKeys(raw: Record<string, unknown>): string[] {\n return unknownKeyErrors(raw, [...TOP_LEVEL_KEYS, 'pr-gate'], '[webpieces.config.json]');\n}\n\n/**\n * The `*Why` convention: any key ending in \"Why\" is free-form rationale a consumer keeps beside the key it\n * explains. JSON has no comments, so this is how this repo documents non-obvious config — and it is the only\n * way the config file can carry the note that matters most to the agent reading it (see the top-level\n * \"upgradePolicyWhy\" this repo ships). Comment keys are therefore allowed wherever unknown keys are\n * otherwise rejected, and must be strings.\n *\n * ONE exception, enforced separately: `gateSaltWhy` is rejected outright, because rationale next to the gate\n * salt is a bypass how-to in the most-read file in the repo. See validateNoGateSaltRationale.\n */\nexport const COMMENT_KEY_SUFFIX = 'Why';\n\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function isCommentKey(key: string): boolean {\n return key.length > COMMENT_KEY_SUFFIX.length && key.endsWith(COMMENT_KEY_SUFFIX);\n}\n\n/**\n * Reject keys a section does not define. Without this a retired or misspelled key is simply ignored, which is\n * how a stale shape survives an upgrade forever — the whole failure mode RETIRED_CONFIG_KEYS exists to end.\n * Retired keys are skipped here so they get their own migration message instead of a generic \"unknown key\".\n */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON; only key names are read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function unknownKeyErrors(section: Record<string, unknown>, knownKeys: readonly string[], label: string): string[] {\n const errors: string[] = [];\n for (const key of Object.keys(section)) {\n if (knownKeys.includes(key) || isRetiredKey(key, label)) continue;\n if (isCommentKey(key)) {\n if (typeof section[key] !== 'string') {\n errors.push(`${label} \"${key}\" is a *Why rationale note and must be a string.`);\n }\n continue;\n }\n errors.push(\n `${label} Unknown key \"${key}\". Valid keys: [${knownKeys.join(', ')}]. ` +\n `Delete it, or — if it is rationale you want to keep beside a key — rename it to \"<key>Why\" ` +\n `with a string value (JSON has no comments, so \"*Why\" siblings are how this repo documents config).`,\n );\n }\n return errors;\n}\n\n\n"]}
|
|
@@ -11,9 +11,9 @@ exports.ExcludePaths = void 0;
|
|
|
11
11
|
// one level down, by a rule's OWN `excludePaths` inside its config block. A path is governed by
|
|
12
12
|
// webpieces or it is not, so the top-level block is a single list.
|
|
13
13
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
14
|
+
// The object form `{ "rules": [...], "guards": [...] }` is RETIRED, not accepted — validateExcludePaths
|
|
15
|
+
// rejects it with the union instruction. It was tolerated once, which is precisely why consumer configs
|
|
16
|
+
// (this repo's included) stayed on it: an accepted shape is never migrated. See retired-config-keys.ts.
|
|
17
17
|
// Data-only (per CLAUDE.md, classes for data). Built once by loadAndValidate after validation.
|
|
18
18
|
class ExcludePaths {
|
|
19
19
|
paths;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exclude-hook-paths.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/exclude-hook-paths.ts"],"names":[],"mappings":";;;AAAA,gGAAgG;AAChG,sGAAsG;AACtG,4EAA4E;AAC5E,EAAE;AACF,kGAAkG;AAClG,mGAAmG;AACnG,qGAAqG;AACrG,gGAAgG;AAChG,mEAAmE;AACnE,EAAE;AACF,
|
|
1
|
+
{"version":3,"file":"exclude-hook-paths.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/exclude-hook-paths.ts"],"names":[],"mappings":";;;AAAA,gGAAgG;AAChG,sGAAsG;AACtG,4EAA4E;AAC5E,EAAE;AACF,kGAAkG;AAClG,mGAAmG;AACnG,qGAAqG;AACrG,gGAAgG;AAChG,mEAAmE;AACnE,EAAE;AACF,wGAAwG;AACxG,wGAAwG;AACxG,wGAAwG;AACxG,+FAA+F;AAC/F,MAAa,YAAY;IACA;IAArB,YAAqB,KAAe;QAAf,UAAK,GAAL,KAAK,CAAU;IAAG,CAAC;CAC3C;AAFD,oCAEC","sourcesContent":["// Top-level `excludePaths` block from webpieces.config.json: ONE glob list that suppresses hook\n// enforcement for matching files (matched against the workspace-relative path). A path listed here is\n// hands-off for everything — code-style rules AND file-scoped guards alike.\n//\n// It used to be TWO independently-varying lists, `rules` and `guards`. The split never earned its\n// keep: every consumer set both to the same value, and the one case that would need them to differ\n// (exclude a generated tree from style rules but keep the git guards on it) is already served better\n// one level down, by a rule's OWN `excludePaths` inside its config block. A path is governed by\n// webpieces or it is not, so the top-level block is a single list.\n//\n// The object form `{ \"rules\": [...], \"guards\": [...] }` is RETIRED, not accepted — validateExcludePaths\n// rejects it with the union instruction. It was tolerated once, which is precisely why consumer configs\n// (this repo's included) stayed on it: an accepted shape is never migrated. See retired-config-keys.ts.\n// Data-only (per CLAUDE.md, classes for data). Built once by loadAndValidate after validation.\nexport class ExcludePaths {\n constructor(readonly paths: string[]) {}\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -17,7 +17,10 @@ export { ExcludePaths } from './exclude-hook-paths';
|
|
|
17
17
|
export { isPathExcluded, matchesAnyGlob } from './exclude-paths';
|
|
18
18
|
export { defaultRules, defaultRulesDir } from './default-rules';
|
|
19
19
|
export { loadTemplate, writeTemplateIfMissing, writeTemplate, TemplateWriter } from './load-template';
|
|
20
|
-
export { validateWebpiecesConfig, validatePrGateSection, validateChecklistsSection, validateSectionPlacement,
|
|
20
|
+
export { validateWebpiecesConfig, validatePrGateSection, validateChecklistsSection, validateSectionPlacement, validateExcludePaths, validateMatchRulesSection, allRuleNames } from './validate-config';
|
|
21
|
+
export { validateCommandsSection } from './commands-section-validators';
|
|
22
|
+
export { unknownKeyErrors, isCommentKey, validateTopLevelKeys, COMMENT_KEY_SUFFIX } from './config-key-rules';
|
|
23
|
+
export { RETIRED_CONFIG_KEYS, RETIRED_SCOPE_KEY, RETIRED_SCOPE_RULE, RetiredConfigKey, isRetiredKey, retiredEntry, retiredKeyError, retiredKeyErrorsIn, retiredRuleFor } from './retired-config-keys';
|
|
21
24
|
export { validateChecklistDocs } from './checklist-docs-validator';
|
|
22
25
|
export { MatchRuleConfig, MatchRuleViolation, findMatchRuleViolations, isMatchRuleAllowedPath, compileMatchRulePatterns, renderMatchRuleMessage, DEFAULT_MATCH_RULES, } from './match-rules-config';
|
|
23
26
|
export type { ConfigSection } from './sections';
|
package/src/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.
|
|
5
|
-
exports.
|
|
6
|
-
exports.
|
|
7
|
-
exports.
|
|
8
|
-
exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = exports.buildCommandsConfig = exports.CommandsConfig = exports.logBranchMutation = exports.branchMutationLogPath = exports.BranchMutationLog = exports.BranchMutationEvent = exports.WorktreeReaper = exports.WorktreeReapResult = exports.ReapedWorktree = exports.BranchReaper = exports.ReapResult = exports.ReapedBranch = exports.WorktreeService = exports.Worktree = exports.BRANCH_RETENTION_KEEP = exports.BRANCH_RETENTION_ARCHIVE_TAG = exports.BRANCH_RETENTION_DELETE = exports.BRANCH_RETENTIONS = exports.ARCHIVE_TAG_PREFIX = exports.ArchiveResult = exports.BranchArchiver = exports.PROMPTABLE_CLASSIFICATIONS = exports.CLASSIFICATION_DETACHED = exports.CLASSIFICATION_CURRENT = exports.CLASSIFICATION_LOCKED = exports.CLASSIFICATION_PRUNABLE = exports.CLASSIFICATION_IN_USE = void 0;
|
|
3
|
+
exports.validateCommandsSection = exports.allRuleNames = exports.validateMatchRulesSection = exports.validateExcludePaths = exports.validateSectionPlacement = exports.validateChecklistsSection = exports.validatePrGateSection = exports.validateWebpiecesConfig = exports.TemplateWriter = exports.writeTemplate = exports.writeTemplateIfMissing = exports.loadTemplate = exports.defaultRulesDir = exports.defaultRules = exports.matchesAnyGlob = exports.isPathExcluded = exports.ExcludePaths = exports.DESIGN_METADATA_KEYS = exports.isDocumentDesign = exports.DocumentDesign = exports.RulesConfigDesign = exports.AtomicFile = exports.StateMigrationReport = exports.StateDirMigrator = exports.WORKTREE_STATE_DIR = exports.GitDirs = exports.dotWebpieces = exports.DotWebpieces = exports.INSTRUCT_AI_LEAF = exports.INSTRUCT_AI_DIR = exports.RepoRootFinder = exports.CONFIG_PARSE_RETRY_MILLIS = exports.CONFIG_PARSE_ATTEMPTS = exports.ConfigParseAttempt = exports.ConfigFile = exports.CONFIG_FILENAME = exports.findConfigFile = exports.ConfigLoader = exports.LoadedConfig = exports.loadAndValidate = exports.toError = exports.runMain = exports.CliArgs = exports.CliArgsCheck = exports.CliUsage = exports.CliExitError = exports.RuleFailError = exports.InformAiError = exports.ResolvedRuleConfig = exports.ResolvedConfig = void 0;
|
|
4
|
+
exports.WP_START_UPDATE = exports.SyncFlowGuidance = exports.WebpiecesRulesConfig = exports.MERGE_EXPLANATION_FILE = exports.MERGE_IN_PROGRESS_FILE = exports.PR_REVIEW_DIR = exports.MERGE_INFO_DIR = exports.WEBPIECES_TMP_DIR = exports.hasDisable = exports.RULE_NAMES = exports.WEBPIECES_DISABLE = exports.AbstractRule = exports.ChangedFilesOptions = exports.DiffRange = exports.DiffScope = exports.isNewOrModified = exports.hasChangesInRange = exports.findNewMethodSignaturesInDiff = exports.getChangedLineNumbers = exports.getFileDiff = exports.getChangedFiles = exports.resolveBase = exports.detectBase = exports.getCurrentBranch = exports.shouldSkipRule = exports.FieldDef = exports.sectionForRule = exports.isHookGuard = exports.HOOK_GUARD_NAMES = exports.DEFAULT_MATCH_RULES = exports.renderMatchRuleMessage = exports.compileMatchRulePatterns = exports.isMatchRuleAllowedPath = exports.findMatchRuleViolations = exports.MatchRuleViolation = exports.MatchRuleConfig = exports.validateChecklistDocs = exports.retiredRuleFor = exports.retiredKeyErrorsIn = exports.retiredKeyError = exports.retiredEntry = exports.isRetiredKey = exports.RetiredConfigKey = exports.RETIRED_SCOPE_RULE = exports.RETIRED_SCOPE_KEY = exports.RETIRED_CONFIG_KEYS = exports.COMMENT_KEY_SUFFIX = exports.validateTopLevelKeys = exports.isCommentKey = exports.unknownKeyErrors = void 0;
|
|
5
|
+
exports.THROW_CAUSE_MODES = exports.DIRECT_API_RESOLVER_MODES = exports.PRISMA_CONVERTER_MODES = exports.PRISMA_DTOS_MODES = exports.PROJECT_MODES = exports.MODIFIED_CODE_MODES = exports.INLINE_TYPE_MODES = exports.RETURN_TYPE_MODES = exports.FILE_LIMIT_MODES = exports.METHOD_LIMIT_MODES = exports.BaseRuleConfig = exports.ValidateEslintSyncConfig = exports.ValidateVersionsLockedConfig = exports.ValidatePackageJsonConfig = exports.ValidateNoArchitectureCyclesConfig = exports.ValidateArchitectureUnchangedConfig = exports.ValidateTsInSrcConfig = exports.NoJsFilesConfig = exports.DiGraphConfig = exports.NxWiringConfig = exports.RuntimeArchitectureConfig = exports.NoFileImportCyclesConfig = exports.RedirectHowToMergeMainConfig = exports.PrMergeGuardConfig = exports.MergeInProgressGuardConfig = exports.PrCreationOrPushGuardConfig = exports.BranchCreationGuardConfig = exports.RoleTagConfig = exports.FrameworkTagConfig = exports.InjectAnnotationNotNeededForConcreteClassConfig = exports.NoFunctionOutsideClassConfig = exports.NoProcessExitOutsideMainConfig = exports.NoCustomCssConfig = 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.WP_FINISH_UPSERT_PR = exports.WP_START_UPSERT_PR = exports.WP_FINISH_UPDATE = void 0;
|
|
6
|
+
exports.ReviewerPaths = exports.ReviewerTranscript = exports.ReviewProvenance = exports.ReviewProvenanceService = exports.PROVENANCE_SKIPPED = exports.PROVENANCE_MISSING = exports.PROVENANCE_OK = exports.ProvenanceResult = exports.TranscriptScan = exports.EvidenceRequest = exports.ReviewerEvidence = exports.SubagentProvenanceService = exports.verifyGateToken = exports.extractGateToken = exports.gateTokenMarker = exports.computeGateToken = exports.GateTokenService = exports.ALL_DIFF_ONE_READ_LINES = exports.READ_TRUNCATION_LINES = exports.ContextEntry = exports.BriefedFile = exports.ReviewerBriefing = exports.ReviewerInstructionsService = exports.ChecklistInstructionsService = exports.ChecklistValidator = exports.formatFileList = exports.normalizeChecklistDoc = exports.toChecklist = exports.ChecklistDefinition = exports.MERGE_MODES = exports.MERGE_MODE_NONE = exports.MERGE_MODE_AUTO = exports.buildLandPrConfig = exports.buildPrGateConfig = exports.defaultLandPrConfig = exports.defaultPrGateConfig = exports.defaultGates = exports.ReviewContextEntry = exports.LandPrConfig = exports.PrGateConfig = exports.GateDefinition = exports.StaleMainBashGuardConfig = exports.MergedBranchBashGuardConfig = exports.ReadStaleGuardConfig = exports.FeatureBranchGuardConfig = exports.CLIENT_CREATION_SEVERITIES = exports.NoClientCreationOutsideServerOrClientConfig = exports.VALIDATE_TS_MODES = exports.STRUCTURAL_MODES = exports.ON_OFF_MODES = void 0;
|
|
7
|
+
exports.MergedBranch = exports.squashRecoverySteps = exports.stampCleanMainSyncStatus = exports.computeMainSyncStatus = exports.finishedLock = exports.inProcessLock = exports.tryAcquireMainSyncLock = exports.isRefreshInProgress = exports.isLockStale = exports.writeMainSyncLock = exports.readMainSyncLock = exports.computeAllMainSyncStatuses = exports.writeMainSyncStatusFile = exports.writeMainSyncStatus = exports.readMainSyncStatusFile = exports.readMainSyncStatus = exports.mainSyncLockPath = exports.mainSyncStatusPath = exports.DEFAULT_HANG_TIMEOUT_MINUTES = exports.MainSyncStatusService = exports.MainSyncLock = exports.MAIN_SYNC_STATUS_VERSION = exports.PullRequestIndex = exports.MainSyncFileStore = exports.MainSyncStatusFile = exports.MainSyncStatus = exports.reviewJsonSchemaHint = exports.reviewJsonPath = exports.prDirFor = exports.loadReviewJson = exports.ReviewJsonService = exports.ChecklistReviewContext = exports.RequiredChecklist = exports.VERDICT_STATUSES = exports.VERDICT_RED = exports.VERDICT_YELLOW = exports.VERDICT_GREEN = exports.CK_BAD_FORMAT = exports.CK_MISSING = exports.CK_FAIL = exports.CK_OVERRIDDEN = exports.CK_WARN = exports.CK_PASS = exports.ChecklistVerdict = exports.ChecklistResult = exports.PrContext = exports.ReviewJson = exports.DEFAULT_RETENTION_DAYS = exports.ProvenanceWriteRequest = exports.OfferedContext = void 0;
|
|
8
|
+
exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = exports.buildCommandsConfig = exports.CommandsConfig = exports.logBranchMutation = exports.branchMutationLogPath = exports.BranchMutationLog = exports.BranchMutationEvent = exports.WorktreeReaper = exports.WorktreeReapResult = exports.ReapedWorktree = exports.BranchReaper = exports.ReapResult = exports.ReapedBranch = exports.WorktreeService = exports.Worktree = exports.BRANCH_RETENTION_KEEP = exports.BRANCH_RETENTION_ARCHIVE_TAG = exports.BRANCH_RETENTION_DELETE = exports.BRANCH_RETENTIONS = exports.ARCHIVE_TAG_PREFIX = exports.ArchiveResult = exports.BranchArchiver = exports.PROMPTABLE_CLASSIFICATIONS = exports.CLASSIFICATION_DETACHED = exports.CLASSIFICATION_CURRENT = exports.CLASSIFICATION_LOCKED = exports.CLASSIFICATION_PRUNABLE = exports.CLASSIFICATION_IN_USE = exports.CLASSIFICATION_NEVER_PROPOSED = exports.CLASSIFICATION_CONTENT_IN_MAIN = exports.CLASSIFICATION_SUPERSEDED = exports.CLASSIFICATION_NO_COMMITS = exports.CLASSIFICATION_BACKUP_OF_LIVE = exports.CLASSIFICATION_BACKUP_OF_MERGED = exports.CLASSIFICATION_MERGED_PR = exports.CACHE_STALE_AFTER_MS = exports.CacheFreshness = exports.MergedBranchesService = exports.MergedBranchesCache = exports.DeletableWorktree = exports.DeletableBranch = void 0;
|
|
9
9
|
var types_1 = require("./types");
|
|
10
10
|
Object.defineProperty(exports, "ResolvedConfig", { enumerable: true, get: function () { return types_1.ResolvedConfig; } });
|
|
11
11
|
Object.defineProperty(exports, "ResolvedRuleConfig", { enumerable: true, get: function () { return types_1.ResolvedRuleConfig; } });
|
|
@@ -74,10 +74,28 @@ Object.defineProperty(exports, "validateWebpiecesConfig", { enumerable: true, ge
|
|
|
74
74
|
Object.defineProperty(exports, "validatePrGateSection", { enumerable: true, get: function () { return validate_config_1.validatePrGateSection; } });
|
|
75
75
|
Object.defineProperty(exports, "validateChecklistsSection", { enumerable: true, get: function () { return validate_config_1.validateChecklistsSection; } });
|
|
76
76
|
Object.defineProperty(exports, "validateSectionPlacement", { enumerable: true, get: function () { return validate_config_1.validateSectionPlacement; } });
|
|
77
|
-
Object.defineProperty(exports, "validateCommandsSection", { enumerable: true, get: function () { return validate_config_1.validateCommandsSection; } });
|
|
78
77
|
Object.defineProperty(exports, "validateExcludePaths", { enumerable: true, get: function () { return validate_config_1.validateExcludePaths; } });
|
|
79
78
|
Object.defineProperty(exports, "validateMatchRulesSection", { enumerable: true, get: function () { return validate_config_1.validateMatchRulesSection; } });
|
|
80
79
|
Object.defineProperty(exports, "allRuleNames", { enumerable: true, get: function () { return validate_config_1.allRuleNames; } });
|
|
80
|
+
var commands_section_validators_1 = require("./commands-section-validators");
|
|
81
|
+
Object.defineProperty(exports, "validateCommandsSection", { enumerable: true, get: function () { return commands_section_validators_1.validateCommandsSection; } });
|
|
82
|
+
var config_key_rules_1 = require("./config-key-rules");
|
|
83
|
+
Object.defineProperty(exports, "unknownKeyErrors", { enumerable: true, get: function () { return config_key_rules_1.unknownKeyErrors; } });
|
|
84
|
+
Object.defineProperty(exports, "isCommentKey", { enumerable: true, get: function () { return config_key_rules_1.isCommentKey; } });
|
|
85
|
+
Object.defineProperty(exports, "validateTopLevelKeys", { enumerable: true, get: function () { return config_key_rules_1.validateTopLevelKeys; } });
|
|
86
|
+
Object.defineProperty(exports, "COMMENT_KEY_SUFFIX", { enumerable: true, get: function () { return config_key_rules_1.COMMENT_KEY_SUFFIX; } });
|
|
87
|
+
// The retired-key table + the no-back-compat policy it enforces. Exported so the installer can migrate
|
|
88
|
+
// what the errors instruct, and so consumers can enumerate retirements.
|
|
89
|
+
var retired_config_keys_1 = require("./retired-config-keys");
|
|
90
|
+
Object.defineProperty(exports, "RETIRED_CONFIG_KEYS", { enumerable: true, get: function () { return retired_config_keys_1.RETIRED_CONFIG_KEYS; } });
|
|
91
|
+
Object.defineProperty(exports, "RETIRED_SCOPE_KEY", { enumerable: true, get: function () { return retired_config_keys_1.RETIRED_SCOPE_KEY; } });
|
|
92
|
+
Object.defineProperty(exports, "RETIRED_SCOPE_RULE", { enumerable: true, get: function () { return retired_config_keys_1.RETIRED_SCOPE_RULE; } });
|
|
93
|
+
Object.defineProperty(exports, "RetiredConfigKey", { enumerable: true, get: function () { return retired_config_keys_1.RetiredConfigKey; } });
|
|
94
|
+
Object.defineProperty(exports, "isRetiredKey", { enumerable: true, get: function () { return retired_config_keys_1.isRetiredKey; } });
|
|
95
|
+
Object.defineProperty(exports, "retiredEntry", { enumerable: true, get: function () { return retired_config_keys_1.retiredEntry; } });
|
|
96
|
+
Object.defineProperty(exports, "retiredKeyError", { enumerable: true, get: function () { return retired_config_keys_1.retiredKeyError; } });
|
|
97
|
+
Object.defineProperty(exports, "retiredKeyErrorsIn", { enumerable: true, get: function () { return retired_config_keys_1.retiredKeyErrorsIn; } });
|
|
98
|
+
Object.defineProperty(exports, "retiredRuleFor", { enumerable: true, get: function () { return retired_config_keys_1.retiredRuleFor; } });
|
|
81
99
|
var checklist_docs_validator_1 = require("./checklist-docs-validator");
|
|
82
100
|
Object.defineProperty(exports, "validateChecklistDocs", { enumerable: true, get: function () { return checklist_docs_validator_1.validateChecklistDocs; } });
|
|
83
101
|
var match_rules_config_1 = require("./match-rules-config");
|