@webpieces/rules-config 0.4.490 → 0.4.492

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/rules-config",
3
- "version": "0.4.490",
3
+ "version": "0.4.492",
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",
@@ -22,7 +22,7 @@ class AbstractRule {
22
22
  shouldRun() {
23
23
  if (this.config.mode === 'OFF')
24
24
  return false;
25
- const skip = (0, skip_rule_1.shouldSkipRule)(this.config.ignoreModifiedUntilEpoch, this.config.ignoreRuleWhileOnBranch);
25
+ const skip = (0, skip_rule_1.shouldSkipRule)(this.config.turnOffRuleUntilEpoch, this.config.turnOffRuleWhileOnBranch);
26
26
  return !skip.skip;
27
27
  }
28
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"abstract-rule.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/abstract-rule.ts"],"names":[],"mappings":";;;AACA,2CAA6C;AAE7C;;;;;;;;GAQG;AACH,MAAsB,YAAY;IACC;IAAoB;IAAnD,YAA+B,MAAS,EAAW,IAAY;QAAhC,WAAM,GAAN,MAAM,CAAG;QAAW,SAAI,GAAJ,IAAI,CAAQ;IAAG,CAAC;IAEnE,uFAAuF;IACvF,SAAS;QACL,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QAC7C,MAAM,IAAI,GAAG,IAAA,0BAAc,EAAC,IAAI,CAAC,MAAM,CAAC,wBAAwB,EAAE,IAAI,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC;QACvG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;IACtB,CAAC;CACJ;AATD,oCASC","sourcesContent":["import { BaseRuleConfig } from './rule-configs';\nimport { shouldSkipRule } from './skip-rule';\n\n/**\n * Shared base for every rule in BOTH packages (ai-hook-rules and code-rules). A rule is\n * constructed with its typed config (`new NoAnyUnknownRule(config['no-any-unknown'])`), so the\n * config class is genuinely consumed — find-usages/rename work across packages.\n *\n * It is execution-agnostic: it owns only `name` + the on/off + escape-hatch decision\n * (`shouldRun`). Each package's base adds its own execution surface (ai-hook `check(ctx)`,\n * code-rules `run(workspaceRoot)`), so rules-config stays free of package-specific types.\n */\nexport abstract class AbstractRule<C extends BaseRuleConfig> {\n constructor(protected readonly config: C, readonly name: string) {}\n\n /** True unless the rule is `mode: \"OFF\"` or skipped by a branch/epoch escape hatch. */\n shouldRun(): boolean {\n if (this.config.mode === 'OFF') return false;\n const skip = shouldSkipRule(this.config.ignoreModifiedUntilEpoch, this.config.ignoreRuleWhileOnBranch);\n return !skip.skip;\n }\n}\n"]}
1
+ {"version":3,"file":"abstract-rule.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/abstract-rule.ts"],"names":[],"mappings":";;;AACA,2CAA6C;AAE7C;;;;;;;;GAQG;AACH,MAAsB,YAAY;IACC;IAAoB;IAAnD,YAA+B,MAAS,EAAW,IAAY;QAAhC,WAAM,GAAN,MAAM,CAAG;QAAW,SAAI,GAAJ,IAAI,CAAQ;IAAG,CAAC;IAEnE,uFAAuF;IACvF,SAAS;QACL,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QAC7C,MAAM,IAAI,GAAG,IAAA,0BAAc,EAAC,IAAI,CAAC,MAAM,CAAC,qBAAqB,EAAE,IAAI,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC;QACrG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;IACtB,CAAC;CACJ;AATD,oCASC","sourcesContent":["import { BaseRuleConfig } from './rule-configs';\nimport { shouldSkipRule } from './skip-rule';\n\n/**\n * Shared base for every rule in BOTH packages (ai-hook-rules and code-rules). A rule is\n * constructed with its typed config (`new NoAnyUnknownRule(config['no-any-unknown'])`), so the\n * config class is genuinely consumed — find-usages/rename work across packages.\n *\n * It is execution-agnostic: it owns only `name` + the on/off + escape-hatch decision\n * (`shouldRun`). Each package's base adds its own execution surface (ai-hook `check(ctx)`,\n * code-rules `run(workspaceRoot)`), so rules-config stays free of package-specific types.\n */\nexport abstract class AbstractRule<C extends BaseRuleConfig> {\n constructor(protected readonly config: C, readonly name: string) {}\n\n /** True unless the rule is `mode: \"OFF\"` or skipped by a branch/epoch escape hatch. */\n shouldRun(): boolean {\n if (this.config.mode === 'OFF') return false;\n const skip = shouldSkipRule(this.config.turnOffRuleUntilEpoch, this.config.turnOffRuleWhileOnBranch);\n return !skip.skip;\n }\n}\n"]}
@@ -51,7 +51,7 @@ exports.defaultRules = {
51
51
  // config, so RUN_EVERY_TIME is the only default that keeps existing repos behaving identically on
52
52
  // upgrade. Set "mode": "OFF" to disable one; the two graph-baseline rules
53
53
  // (validate-architecture-unchanged / validate-no-architecture-cycles) additionally honor
54
- // ignoreModifiedUntilEpoch — the other three are all-or-nothing (see rule-configs.ts).
54
+ // turnOffRuleUntilEpoch — the other three are all-or-nothing (see rule-configs.ts).
55
55
  'validate-architecture-unchanged': { mode: 'RUN_EVERY_TIME' },
56
56
  'validate-no-architecture-cycles': { mode: 'RUN_EVERY_TIME' },
57
57
  'validate-packagejson': { mode: 'RUN_EVERY_TIME' },
@@ -1 +1 @@
1
- {"version":3,"file":"default-rules.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/default-rules.ts"],"names":[],"mappings":"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","sourcesContent":["// Default holistic exclude list for the validate-ts-in-src\n// rules. Bare names match a directory segment at any depth; globs match the\n// workspace-relative path. `**/*.d.ts` (ambient declarations) and\n// `**/jest.config.ts` legitimately live outside src/ and are exempt here.\nconst DEFAULT_EXCLUDE_PATHS: readonly string[] = [\n 'node_modules', 'dist', '.nx', '.git',\n '**/*.d.ts', '**/jest.config.ts',\n];\n\n// On/off is driven by `mode` (\"OFF\" disables; an absent mode leaves a rule\n// on). Code-rules entries omit `mode` so each executor keeps its own default\n// scope; structural rules declare `mode: 'RUN_EVERY_TIME'`, bash guards `mode: 'ON'`.\n// webpieces-disable no-any-unknown -- rule options are opaque at framework level\nexport const defaultRules: Record<string, Record<string, unknown>> = {\n 'no-any-unknown': {},\n 'no-implicit-any': {},\n 'max-file-lines': { limit: 900 },\n 'max-method-lines': { limit: 80 },\n 'require-return-type': {},\n 'no-inline-type-literals': {},\n 'no-destructure': { allowTopLevel: true },\n 'catch-error-pattern': {},\n 'no-unmanaged-exceptions': {},\n 'no-file-import-cycles': { mode: 'RUN_EVERY_TIME' },\n 'runtime-architecture': { mode: 'RUN_EVERY_TIME', allowedCycles: [] },\n 'prisma-validate-dtos': {},\n 'prisma-converter': {},\n 'angular-no-direct-api-in-resolver': {},\n 'no-symbol-di-tokens': {},\n // Ships OFF: a repo opts in per webpieces.config.json once it is ready to migrate any\n // client-in-a-lib sites (severity defaults to \"warn\" so even when enabled it reports without\n // failing until a repo flips it to \"error\").\n 'no-client-creation-outside-server-or-client': { mode: 'OFF' },\n 'no-custom-css': { allowGlobs: [] },\n 'no-process-exit-outside-main': {},\n 'inject-annotation-not-needed-for-concrete-class': {},\n 'framework-tag': { mode: 'MODIFIED_PROJECTS', knownTypes: ['browser', 'react', 'angular', 'node', 'express'] },\n 'role-tag': { mode: 'MODIFIED_PROJECTS', knownTypes: ['server', 'app', 'designed-lib', 'lib', 'client', 'api-lib'] },\n 'nx-wiring': { mode: 'RUN_EVERY_TIME' },\n 'di-graph': { mode: 'RUN_EVERY_TIME' },\n 'missing-design-annotation': { mode: 'RUN_EVERY_TIME' },\n 'validate-ts-in-src': {\n mode: 'NEW_AND_MODIFIED_FILES',\n allowedRootFiles: ['jest.setup.ts'],\n excludePaths: [...DEFAULT_EXCLUDE_PATHS],\n },\n 'no-js-files': { mode: 'OFF' },\n // The five Nx infrastructure validators. They enforced unconditionally before they were wired to\n // config, so RUN_EVERY_TIME is the only default that keeps existing repos behaving identically on\n // upgrade. Set \"mode\": \"OFF\" to disable one; the two graph-baseline rules\n // (validate-architecture-unchanged / validate-no-architecture-cycles) additionally honor\n // ignoreModifiedUntilEpoch — the other three are all-or-nothing (see rule-configs.ts).\n 'validate-architecture-unchanged': { mode: 'RUN_EVERY_TIME' },\n 'validate-no-architecture-cycles': { mode: 'RUN_EVERY_TIME' },\n 'validate-packagejson': { mode: 'RUN_EVERY_TIME' },\n 'validate-versions-locked': { mode: 'RUN_EVERY_TIME' },\n 'validate-eslint-sync': { mode: 'RUN_EVERY_TIME' },\n // autoReapMergedBranches ships FALSE. It is schema-required so every consumer must state a value,\n // and the framework default must be the conservative one: an upgrade should never start deleting\n // a project's branches unattended before a human has opted in. Set it true to let the background\n // refresher reap dead branches on its own; `pnpm wp-cleanup` works either way.\n 'branch-creation-guard': {\n mode: 'ON',\n subBranchNaming: 'feature/<ticket>/<short-description>',\n autoReapMergedBranches: false,\n },\n 'pr-creation-or-push-guard': { mode: 'ON' },\n 'merge-in-progress-guard': { mode: 'ON' },\n 'pr-merge-guard': { mode: 'ON' },\n 'redirect-how-to-merge-main': { mode: 'ON' },\n // Phase 1 ships OFF on purpose. This guard blocks Read, the highest-blast-radius tool there is,\n // so it is opted into per-repo (webpieces.config.json → hookGuards) only AFTER the release\n // carrying it is published and installed. Flipping it ON here would arm it for every consumer\n // on upgrade, before anyone has verified the fail-open paths against their own git layout.\n 'read-stale-guard': { mode: 'OFF' },\n};\n\nexport const defaultRulesDir: readonly string[] = [];\n"]}
1
+ {"version":3,"file":"default-rules.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/default-rules.ts"],"names":[],"mappings":";;;AAAA,2DAA2D;AAC3D,4EAA4E;AAC5E,kEAAkE;AAClE,0EAA0E;AAC1E,MAAM,qBAAqB,GAAsB;IAC7C,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IACrC,WAAW,EAAE,mBAAmB;CACnC,CAAC;AAEF,2EAA2E;AAC3E,6EAA6E;AAC7E,sFAAsF;AACtF,iFAAiF;AACpE,QAAA,YAAY,GAA4C;IACjE,gBAAgB,EAAE,EAAE;IACpB,iBAAiB,EAAE,EAAE;IACrB,gBAAgB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;IAChC,kBAAkB,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;IACjC,qBAAqB,EAAE,EAAE;IACzB,yBAAyB,EAAE,EAAE;IAC7B,gBAAgB,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;IACzC,qBAAqB,EAAE,EAAE;IACzB,yBAAyB,EAAE,EAAE;IAC7B,uBAAuB,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACnD,sBAAsB,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,aAAa,EAAE,EAAE,EAAE;IACrE,sBAAsB,EAAE,EAAE;IAC1B,kBAAkB,EAAE,EAAE;IACtB,mCAAmC,EAAE,EAAE;IACvC,qBAAqB,EAAE,EAAE;IACzB,sFAAsF;IACtF,6FAA6F;IAC7F,6CAA6C;IAC7C,6CAA6C,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;IAC9D,eAAe,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;IACnC,8BAA8B,EAAE,EAAE;IAClC,iDAAiD,EAAE,EAAE;IACrD,eAAe,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,UAAU,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE;IAC9G,UAAU,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,UAAU,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE;IACpH,WAAW,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACvC,UAAU,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACtC,2BAA2B,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACvD,oBAAoB,EAAE;QAClB,IAAI,EAAE,wBAAwB;QAC9B,gBAAgB,EAAE,CAAC,eAAe,CAAC;QACnC,YAAY,EAAE,CAAC,GAAG,qBAAqB,CAAC;KAC3C;IACD,aAAa,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;IAC9B,iGAAiG;IACjG,kGAAkG;IAClG,0EAA0E;IAC1E,yFAAyF;IACzF,oFAAoF;IACpF,iCAAiC,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IAC7D,iCAAiC,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IAC7D,sBAAsB,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IAClD,0BAA0B,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACtD,sBAAsB,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE;IAClD,kGAAkG;IAClG,iGAAiG;IACjG,iGAAiG;IACjG,+EAA+E;IAC/E,uBAAuB,EAAE;QACrB,IAAI,EAAE,IAAI;QACV,eAAe,EAAE,sCAAsC;QACvD,sBAAsB,EAAE,KAAK;KAChC;IACD,2BAA2B,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IAC3C,yBAAyB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IACzC,gBAAgB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IAChC,4BAA4B,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IAC5C,gGAAgG;IAChG,2FAA2F;IAC3F,8FAA8F;IAC9F,2FAA2F;IAC3F,kBAAkB,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;CACtC,CAAC;AAEW,QAAA,eAAe,GAAsB,EAAE,CAAC","sourcesContent":["// Default holistic exclude list for the validate-ts-in-src\n// rules. Bare names match a directory segment at any depth; globs match the\n// workspace-relative path. `**/*.d.ts` (ambient declarations) and\n// `**/jest.config.ts` legitimately live outside src/ and are exempt here.\nconst DEFAULT_EXCLUDE_PATHS: readonly string[] = [\n 'node_modules', 'dist', '.nx', '.git',\n '**/*.d.ts', '**/jest.config.ts',\n];\n\n// On/off is driven by `mode` (\"OFF\" disables; an absent mode leaves a rule\n// on). Code-rules entries omit `mode` so each executor keeps its own default\n// scope; structural rules declare `mode: 'RUN_EVERY_TIME'`, bash guards `mode: 'ON'`.\n// webpieces-disable no-any-unknown -- rule options are opaque at framework level\nexport const defaultRules: Record<string, Record<string, unknown>> = {\n 'no-any-unknown': {},\n 'no-implicit-any': {},\n 'max-file-lines': { limit: 900 },\n 'max-method-lines': { limit: 80 },\n 'require-return-type': {},\n 'no-inline-type-literals': {},\n 'no-destructure': { allowTopLevel: true },\n 'catch-error-pattern': {},\n 'no-unmanaged-exceptions': {},\n 'no-file-import-cycles': { mode: 'RUN_EVERY_TIME' },\n 'runtime-architecture': { mode: 'RUN_EVERY_TIME', allowedCycles: [] },\n 'prisma-validate-dtos': {},\n 'prisma-converter': {},\n 'angular-no-direct-api-in-resolver': {},\n 'no-symbol-di-tokens': {},\n // Ships OFF: a repo opts in per webpieces.config.json once it is ready to migrate any\n // client-in-a-lib sites (severity defaults to \"warn\" so even when enabled it reports without\n // failing until a repo flips it to \"error\").\n 'no-client-creation-outside-server-or-client': { mode: 'OFF' },\n 'no-custom-css': { allowGlobs: [] },\n 'no-process-exit-outside-main': {},\n 'inject-annotation-not-needed-for-concrete-class': {},\n 'framework-tag': { mode: 'MODIFIED_PROJECTS', knownTypes: ['browser', 'react', 'angular', 'node', 'express'] },\n 'role-tag': { mode: 'MODIFIED_PROJECTS', knownTypes: ['server', 'app', 'designed-lib', 'lib', 'client', 'api-lib'] },\n 'nx-wiring': { mode: 'RUN_EVERY_TIME' },\n 'di-graph': { mode: 'RUN_EVERY_TIME' },\n 'missing-design-annotation': { mode: 'RUN_EVERY_TIME' },\n 'validate-ts-in-src': {\n mode: 'NEW_AND_MODIFIED_FILES',\n allowedRootFiles: ['jest.setup.ts'],\n excludePaths: [...DEFAULT_EXCLUDE_PATHS],\n },\n 'no-js-files': { mode: 'OFF' },\n // The five Nx infrastructure validators. They enforced unconditionally before they were wired to\n // config, so RUN_EVERY_TIME is the only default that keeps existing repos behaving identically on\n // upgrade. Set \"mode\": \"OFF\" to disable one; the two graph-baseline rules\n // (validate-architecture-unchanged / validate-no-architecture-cycles) additionally honor\n // turnOffRuleUntilEpoch — the other three are all-or-nothing (see rule-configs.ts).\n 'validate-architecture-unchanged': { mode: 'RUN_EVERY_TIME' },\n 'validate-no-architecture-cycles': { mode: 'RUN_EVERY_TIME' },\n 'validate-packagejson': { mode: 'RUN_EVERY_TIME' },\n 'validate-versions-locked': { mode: 'RUN_EVERY_TIME' },\n 'validate-eslint-sync': { mode: 'RUN_EVERY_TIME' },\n // autoReapMergedBranches ships FALSE. It is schema-required so every consumer must state a value,\n // and the framework default must be the conservative one: an upgrade should never start deleting\n // a project's branches unattended before a human has opted in. Set it true to let the background\n // refresher reap dead branches on its own; `pnpm wp-cleanup` works either way.\n 'branch-creation-guard': {\n mode: 'ON',\n subBranchNaming: 'feature/<ticket>/<short-description>',\n autoReapMergedBranches: false,\n },\n 'pr-creation-or-push-guard': { mode: 'ON' },\n 'merge-in-progress-guard': { mode: 'ON' },\n 'pr-merge-guard': { mode: 'ON' },\n 'redirect-how-to-merge-main': { mode: 'ON' },\n // Phase 1 ships OFF on purpose. This guard blocks Read, the highest-blast-radius tool there is,\n // so it is opted into per-repo (webpieces.config.json → hookGuards) only AFTER the release\n // carrying it is published and installed. Flipping it ON here would arm it for every consumer\n // on upgrade, before anyone has verified the fail-open paths against their own git layout.\n 'read-stale-guard': { mode: 'OFF' },\n};\n\nexport const defaultRulesDir: readonly string[] = [];\n"]}
@@ -3,9 +3,12 @@ export declare class FieldDef {
3
3
  readonly type: FieldType;
4
4
  readonly enumValues?: readonly string[] | undefined;
5
5
  readonly optional: boolean;
6
- constructor(type: FieldType, enumValues?: readonly string[] | undefined, optional?: boolean);
6
+ readonly nullable: boolean;
7
+ constructor(type: FieldType, enumValues?: readonly string[] | undefined, optional?: boolean, nullable?: boolean);
7
8
  /** Marks a field as optional (omittable) in the config schema. */
8
9
  static optional(type: FieldType, enumValues?: readonly string[]): FieldDef;
10
+ /** A REQUIRED string field that also accepts `null` (present-but-unset). */
11
+ static nullableString(): FieldDef;
9
12
  }
10
13
  export type SchemaShape<T> = {
11
14
  [K in keyof Required<T>]: FieldDef;
package/src/field-def.js CHANGED
@@ -5,18 +5,30 @@ class FieldDef {
5
5
  type;
6
6
  enumValues;
7
7
  optional;
8
+ nullable;
8
9
  constructor(type, enumValues,
9
10
  // When true, the field is omittable: the missing-rule snippet lists it
10
11
  // as optional rather than as a required copy-paste field.
11
- optional = false) {
12
+ optional = false,
13
+ // When true, JSON `null` is an accepted value in addition to `type`. Used for a REQUIRED
14
+ // field whose "unset" state must still be visible in the config (e.g. turnOffRuleWhileOnBranch:
15
+ // null means "no branch / always on") — required so it is always present, nullable so it can be
16
+ // present-but-unset without inventing a sentinel string.
17
+ nullable = false) {
12
18
  this.type = type;
13
19
  this.enumValues = enumValues;
14
20
  this.optional = optional;
21
+ this.nullable = nullable;
15
22
  }
16
23
  /** Marks a field as optional (omittable) in the config schema. */
17
24
  static optional(type, enumValues) {
18
25
  return new FieldDef(type, enumValues, true);
19
26
  }
27
+ /** A REQUIRED string field that also accepts `null` (present-but-unset). */
28
+ // webpieces-disable no-function-outside-class -- static factory, matches sibling FieldDef.optional
29
+ static nullableString() {
30
+ return new FieldDef('string', undefined, false, true);
31
+ }
20
32
  }
21
33
  exports.FieldDef = FieldDef;
22
34
  //# sourceMappingURL=field-def.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"field-def.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/field-def.ts"],"names":[],"mappings":";;;AAEA,MAAa,QAAQ;IAEJ;IACA;IAGA;IALb,YACa,IAAe,EACf,UAA8B;IACvC,uEAAuE;IACvE,0DAA0D;IACjD,WAAoB,KAAK;QAJzB,SAAI,GAAJ,IAAI,CAAW;QACf,eAAU,GAAV,UAAU,CAAoB;QAG9B,aAAQ,GAAR,QAAQ,CAAiB;IACnC,CAAC;IAEJ,kEAAkE;IAClE,MAAM,CAAC,QAAQ,CAAC,IAAe,EAAE,UAA8B;QAC3D,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC;CACJ;AAbD,4BAaC","sourcesContent":["type FieldType = 'string' | 'number' | 'boolean' | 'string[]';\n\nexport class FieldDef {\n constructor(\n readonly type: FieldType,\n readonly enumValues?: readonly string[],\n // When true, the field is omittable: the missing-rule snippet lists it\n // as optional rather than as a required copy-paste field.\n readonly optional: boolean = false,\n ) {}\n\n /** Marks a field as optional (omittable) in the config schema. */\n static optional(type: FieldType, enumValues?: readonly string[]): FieldDef {\n return new FieldDef(type, enumValues, true);\n }\n}\n\n// Enforces that a static SCHEMA has exactly the same keys as the config class.\n// Add a field to the class → TS errors until SCHEMA is updated.\n// Add to SCHEMA without adding to class → TS errors (extra property).\nexport type SchemaShape<T> = { [K in keyof Required<T>]: FieldDef };\n"]}
1
+ {"version":3,"file":"field-def.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/field-def.ts"],"names":[],"mappings":";;;AAEA,MAAa,QAAQ;IAEJ;IACA;IAGA;IAKA;IAVb,YACa,IAAe,EACf,UAA8B;IACvC,uEAAuE;IACvE,0DAA0D;IACjD,WAAoB,KAAK;IAClC,yFAAyF;IACzF,gGAAgG;IAChG,gGAAgG;IAChG,yDAAyD;IAChD,WAAoB,KAAK;QATzB,SAAI,GAAJ,IAAI,CAAW;QACf,eAAU,GAAV,UAAU,CAAoB;QAG9B,aAAQ,GAAR,QAAQ,CAAiB;QAKzB,aAAQ,GAAR,QAAQ,CAAiB;IACnC,CAAC;IAEJ,kEAAkE;IAClE,MAAM,CAAC,QAAQ,CAAC,IAAe,EAAE,UAA8B;QAC3D,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC;IAED,4EAA4E;IAC5E,mGAAmG;IACnG,MAAM,CAAC,cAAc;QACjB,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1D,CAAC;CACJ;AAxBD,4BAwBC","sourcesContent":["type FieldType = 'string' | 'number' | 'boolean' | 'string[]';\n\nexport class FieldDef {\n constructor(\n readonly type: FieldType,\n readonly enumValues?: readonly string[],\n // When true, the field is omittable: the missing-rule snippet lists it\n // as optional rather than as a required copy-paste field.\n readonly optional: boolean = false,\n // When true, JSON `null` is an accepted value in addition to `type`. Used for a REQUIRED\n // field whose \"unset\" state must still be visible in the config (e.g. turnOffRuleWhileOnBranch:\n // null means \"no branch / always on\") — required so it is always present, nullable so it can be\n // present-but-unset without inventing a sentinel string.\n readonly nullable: boolean = false,\n ) {}\n\n /** Marks a field as optional (omittable) in the config schema. */\n static optional(type: FieldType, enumValues?: readonly string[]): FieldDef {\n return new FieldDef(type, enumValues, true);\n }\n\n /** A REQUIRED string field that also accepts `null` (present-but-unset). */\n // webpieces-disable no-function-outside-class -- static factory, matches sibling FieldDef.optional\n static nullableString(): FieldDef {\n return new FieldDef('string', undefined, false, true);\n }\n}\n\n// Enforces that a static SCHEMA has exactly the same keys as the config class.\n// Add a field to the class → TS errors until SCHEMA is updated.\n// Add to SCHEMA without adding to class → TS errors (extra property).\nexport type SchemaShape<T> = { [K in keyof Required<T>]: FieldDef };\n"]}
@@ -37,14 +37,6 @@ export declare class ConfigLoader {
37
37
  private mergeRule;
38
38
  private parseExcludePaths;
39
39
  private parseMatchRules;
40
- /**
41
- * Canonicalize the new self-describing escape-hatch names onto the original ones, in place, on one
42
- * rule/guard/match-rule option bag: turnOffRuleUntilEpoch → ignoreModifiedUntilEpoch and
43
- * turnOffRuleWhileOnBranch → ignoreRuleWhileOnBranch. The new name wins when both are present. So
44
- * every downstream reader (AbstractRule.shouldRun, RuleGate, the match-rules engine) keeps reading
45
- * the single original pair. Sibling to normalizeDeprecatedKeys, which does this for renamed RULE names.
46
- */
47
- private normalizeTurnOffAliases;
48
40
  private buildWebpiecesRulesConfig;
49
41
  private normalizeDeprecatedKeys;
50
42
  private formatConfigErrorsBanner;
@@ -89,13 +89,6 @@ let ConfigLoader = class ConfigLoader {
89
89
  if (errors.length > 0) {
90
90
  throw new inform_ai_error_1.InformAiError(this.formatConfigErrorsBanner(errors));
91
91
  }
92
- // Canonicalize the new escape-hatch field names (turnOffRuleUntilEpoch /
93
- // turnOffRuleWhileOnBranch) onto their originals on every rule/guard bag, AFTER validation
94
- // (so errors name the field the user actually wrote) and BEFORE any typed config / merged
95
- // rule is built. overrideRules' per-rule objects are the SAME references as rulesSection /
96
- // hookGuardsSection, so this normalizes those too. Match-rules are normalized in parseMatchRules.
97
- for (const bag of Object.values(overrideRules))
98
- this.normalizeTurnOffAliases(bag);
99
92
  const commands = (0, commands_config_1.buildCommandsConfig)(consumerConfig.commands, legacyPrGate);
100
93
  this.applyCommandDefaults(overrideRules, commands);
101
94
  const userConfiguredRuleNames = new Set(Object.keys(overrideRules));
@@ -158,34 +151,15 @@ let ConfigLoader = class ConfigLoader {
158
151
  const guards = Array.isArray(s['guards']) ? s['guards'].filter(p => typeof p === 'string') : [];
159
152
  return new exclude_hook_paths_1.ExcludePaths(rules, guards);
160
153
  }
161
- // Parse the (already-validated) raw match-rules array into typed MatchRuleConfig[]. Each entry's
162
- // new escape-hatch field names are canonicalized onto their originals so the match-rules engine
163
- // (which reads config.ignoreModifiedUntilEpoch / config.ignoreRuleWhileOnBranch) needs no change.
154
+ // Parse the (already-validated) raw match-rules array into typed MatchRuleConfig[]. The entries use
155
+ // the canonical field names (turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch) that the match-rules
156
+ // engine reads directly, so no normalization is needed.
164
157
  // webpieces-disable no-any-unknown -- validated array; each entry cast to the typed MatchRuleConfig
165
158
  parseMatchRules(raw) {
166
159
  if (!Array.isArray(raw))
167
160
  return [];
168
- // webpieces-disable no-any-unknown -- opaque validated match-rule entry bags from consumer JSON
169
- for (const entry of raw)
170
- this.normalizeTurnOffAliases(entry);
171
161
  return raw;
172
162
  }
173
- /**
174
- * Canonicalize the new self-describing escape-hatch names onto the original ones, in place, on one
175
- * rule/guard/match-rule option bag: turnOffRuleUntilEpoch → ignoreModifiedUntilEpoch and
176
- * turnOffRuleWhileOnBranch → ignoreRuleWhileOnBranch. The new name wins when both are present. So
177
- * every downstream reader (AbstractRule.shouldRun, RuleGate, the match-rules engine) keeps reading
178
- * the single original pair. Sibling to normalizeDeprecatedKeys, which does this for renamed RULE names.
179
- */
180
- // webpieces-disable no-any-unknown -- opaque per-rule option bag from consumer JSON
181
- normalizeTurnOffAliases(options) {
182
- if (options['turnOffRuleUntilEpoch'] !== undefined) {
183
- options['ignoreModifiedUntilEpoch'] = options['turnOffRuleUntilEpoch'];
184
- }
185
- if (options['turnOffRuleWhileOnBranch'] !== undefined) {
186
- options['ignoreRuleWhileOnBranch'] = options['turnOffRuleWhileOnBranch'];
187
- }
188
- }
189
163
  buildWebpiecesRulesConfig(
190
164
  // webpieces-disable no-any-unknown -- JSON values are opaque until assigned to typed fields
191
165
  rawRules, rulesDir) {
@@ -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 * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { buildCommandsConfig, CommandsConfig } from './commands-config';\nimport { ConfigFile } from './config-file';\nimport { defaultRules } from './default-rules';\nimport { ExcludePaths } from './exclude-hook-paths';\nimport { InformAiError } from './inform-ai-error';\nimport { PrGateConfig } from './pr-gate-config';\nimport { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nimport { validateCommandsSection, validateExcludePaths, validateMatchRulesSection, validateSectionPlacement, validateWebpiecesConfig } from './validate-config';\nimport { MatchRuleConfig } from './match-rules-config';\nimport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\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 */\nexport class LoadedConfig {\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n readonly resolved: ResolvedConfig,\n readonly rulesConfig: WebpiecesRulesConfig,\n readonly commands: CommandsConfig,\n readonly prGate: PrGateConfig,\n readonly excludePaths: ExcludePaths,\n readonly matchRules: readonly MatchRuleConfig[],\n readonly configPath: string | null,\n ) {}\n}\n\n// Config keys that were renamed. A project's webpieces.config.json may still use the OLD key (it can\n// legitimately lag the published rules-config by a release), so normalize any deprecated key to its\n// canonical name BEFORE validation/placement/loading — every downstream consumer then sees one name.\nconst DEPRECATED_RULE_ALIASES: Readonly<Record<string, string>> = {\n 'pr-merge-cleanup': 'pr-merge-guard',\n 'pr-creation-guard': 'pr-creation-or-push-guard',\n // Renamed once the guard grew a second blocked state (already-merged feature branch): it is no\n // longer about `main` at all, it is THE guard that can block a Read.\n 'main-stale-guard': 'read-stale-guard',\n};\n\n// webpieces-disable no-any-unknown -- opaque per-rule option bags from consumer JSON, validated later\ntype RuleSectionMap = Record<string, Record<string, unknown>>;\n\n/**\n * The single load+validate entry point for ALL consumers (ai-hook-rules, code-rules,\n * nx-webpieces-rules, pr-gate scripts). `@injectable(bindingScopeValues.Singleton)` + injects {@link ConfigFile} so it appears\n * in the rules-config DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ConfigLoader {\n constructor(private readonly configFile: ConfigFile) {}\n\n /**\n * Reads webpieces.config.json once, validates BOTH the `rules` map and the top-level `pr-gate`\n * block, and throws one InformAiError listing every error. When no config file is found it returns\n * lenient empties/defaults (matching prior no-file behavior).\n */\n // webpieces-disable max-lines-new-methods -- the single load+validate pass is one cohesive method\n loadAndValidate(cwd: string): LoadedConfig {\n const configPath = this.configFile.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 new ExcludePaths([], []),\n [],\n null,\n );\n }\n\n const consumerConfig = this.configFile.readRawConfig(configPath);\n const rulesSection = this.normalizeDeprecatedKeys(consumerConfig.rules || {});\n const hookGuardsSection = this.normalizeDeprecatedKeys(consumerConfig.hookGuards || {});\n const legacyPrGate = consumerConfig['pr-gate'];\n\n // rules + hookGuards are validated/loaded as one flat name→config map (the runtime dispatches\n // by each rule's own `scope`). Placement is enforced separately.\n const overrideRules = { ...rulesSection, ...hookGuardsSection };\n\n const rulesDir = consumerConfig.rulesDir ?? [];\n\n // The repo root (dir holding webpieces.config.json) lets checklists[].docs existence be checked.\n const repoRoot = path.dirname(configPath);\n const errors = [\n ...validateWebpiecesConfig(overrideRules, rulesDir.length > 0),\n ...validateSectionPlacement(rulesSection, hookGuardsSection),\n ...validateCommandsSection(consumerConfig.commands, legacyPrGate, repoRoot),\n ...validateExcludePaths(consumerConfig.excludePaths),\n ...validateMatchRulesSection(consumerConfig['match-rules']),\n ];\n if (errors.length > 0) {\n throw new InformAiError(this.formatConfigErrorsBanner(errors));\n }\n // Canonicalize the new escape-hatch field names (turnOffRuleUntilEpoch /\n // turnOffRuleWhileOnBranch) onto their originals on every rule/guard bag, AFTER validation\n // (so errors name the field the user actually wrote) and BEFORE any typed config / merged\n // rule is built. overrideRules' per-rule objects are the SAME references as rulesSection /\n // hookGuardsSection, so this normalizes those too. Match-rules are normalized in parseMatchRules.\n for (const bag of Object.values(overrideRules)) this.normalizeTurnOffAliases(bag);\n\n const commands = buildCommandsConfig(consumerConfig.commands, legacyPrGate);\n this.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, this.mergeRule(defaultRules[name], overrideRules[name]));\n }\n const resolved = new ResolvedConfig(mergedRules, userConfiguredRuleNames, rulesDir, configPath);\n\n const rulesConfig = this.buildWebpiecesRulesConfig(overrideRules, rulesDir);\n const excludePaths = this.parseExcludePaths(consumerConfig.excludePaths);\n const matchRules = this.parseMatchRules(consumerConfig['match-rules']);\n\n return new LoadedConfig(resolved, rulesConfig, commands, commands.prGate, excludePaths, matchRules, configPath);\n }\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. Only fills a gap — an explicit per-guard override wins.\n private 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-or-push-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\n private 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\n // Parse the (already-validated) raw excludePaths block into the typed ExcludePaths.\n // webpieces-disable no-any-unknown -- `raw` is opaque consumer JSON until narrowed here\n private parseExcludePaths(raw: unknown): ExcludePaths {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return new ExcludePaths([], []);\n // webpieces-disable no-any-unknown -- validateExcludePaths already proved both are string[]\n const s = raw as Record<string, string[]>;\n const rules = Array.isArray(s['rules']) ? s['rules'].filter(p => typeof p === 'string') : [];\n const guards = Array.isArray(s['guards']) ? s['guards'].filter(p => typeof p === 'string') : [];\n return new ExcludePaths(rules, guards);\n }\n\n // Parse the (already-validated) raw match-rules array into typed MatchRuleConfig[]. Each entry's\n // new escape-hatch field names are canonicalized onto their originals so the match-rules engine\n // (which reads config.ignoreModifiedUntilEpoch / config.ignoreRuleWhileOnBranch) needs no change.\n // webpieces-disable no-any-unknown -- validated array; each entry cast to the typed MatchRuleConfig\n private parseMatchRules(raw: unknown): MatchRuleConfig[] {\n if (!Array.isArray(raw)) return [];\n // webpieces-disable no-any-unknown -- opaque validated match-rule entry bags from consumer JSON\n for (const entry of raw as Record<string, unknown>[]) this.normalizeTurnOffAliases(entry);\n return raw as MatchRuleConfig[];\n }\n\n /**\n * Canonicalize the new self-describing escape-hatch names onto the original ones, in place, on one\n * rule/guard/match-rule option bag: turnOffRuleUntilEpoch → ignoreModifiedUntilEpoch and\n * turnOffRuleWhileOnBranch → ignoreRuleWhileOnBranch. The new name wins when both are present. So\n * every downstream reader (AbstractRule.shouldRun, RuleGate, the match-rules engine) keeps reading\n * the single original pair. Sibling to normalizeDeprecatedKeys, which does this for renamed RULE names.\n */\n // webpieces-disable no-any-unknown -- opaque per-rule option bag from consumer JSON\n private normalizeTurnOffAliases(options: Record<string, unknown>): void {\n if (options['turnOffRuleUntilEpoch'] !== undefined) {\n options['ignoreModifiedUntilEpoch'] = options['turnOffRuleUntilEpoch'];\n }\n if (options['turnOffRuleWhileOnBranch'] !== undefined) {\n options['ignoreRuleWhileOnBranch'] = options['turnOffRuleWhileOnBranch'];\n }\n }\n\n private 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 private normalizeDeprecatedKeys(section: RuleSectionMap): RuleSectionMap {\n const out: RuleSectionMap = {};\n for (const key of Object.keys(section)) {\n out[DEPRECATED_RULE_ALIASES[key] ?? key] = section[key];\n }\n return out;\n }\n\n // Assemble the validation-failure banner. Most of these errors are version skew, not bad config.\n private formatConfigErrorsBanner(errors: string[]): string {\n return (\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👉 FIX ORDER (do NOT start by deleting keys — that usually deletes VALID config):\\n` +\n ` 1. Run \\`pnpm install\\`. It is ALWAYS allowed through the guard (installer bypass), even ` +\n `while this config is invalid. This is the #1 cause: your installed @webpieces guard is a ` +\n `release BEHIND webpieces.config.json (a dep bump updated the config + lockfile, but ` +\n `node_modules here was never re-installed), so the running validator doesn't know the newer ` +\n `rule names/values yet. \\`pnpm install\\` syncs node_modules to the pinned version.\\n` +\n ` 2. Retry your command. If the errors are gone, you're DONE — do not touch webpieces.config.json.\\n` +\n ` 3. ONLY if an error survives a fresh install is it a genuine typo / removed / renamed rule. ` +\n `Then edit webpieces.config.json (edits to it are ALWAYS allowed) to fix each • above.`\n );\n }\n}\n\n// Temporary migration delegator — consumers migrate to injecting ConfigLoader over follow-up PRs,\n// then this free function is removed. The logic now lives in the injected ConfigLoader class.\nconst configLoaderSvc = new ConfigLoader(new ConfigFile());\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ConfigLoader; removed once all 118 consumers inject it\nexport function loadAndValidate(cwd: string): LoadedConfig {\n return configLoaderSvc.loadAndValidate(cwd);\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 * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { buildCommandsConfig, CommandsConfig } from './commands-config';\nimport { ConfigFile } from './config-file';\nimport { defaultRules } from './default-rules';\nimport { ExcludePaths } from './exclude-hook-paths';\nimport { InformAiError } from './inform-ai-error';\nimport { PrGateConfig } from './pr-gate-config';\nimport { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nimport { validateCommandsSection, validateExcludePaths, validateMatchRulesSection, validateSectionPlacement, validateWebpiecesConfig } from './validate-config';\nimport { MatchRuleConfig } from './match-rules-config';\nimport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\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 */\nexport class LoadedConfig {\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n readonly resolved: ResolvedConfig,\n readonly rulesConfig: WebpiecesRulesConfig,\n readonly commands: CommandsConfig,\n readonly prGate: PrGateConfig,\n readonly excludePaths: ExcludePaths,\n readonly matchRules: readonly MatchRuleConfig[],\n readonly configPath: string | null,\n ) {}\n}\n\n// Config keys that were renamed. A project's webpieces.config.json may still use the OLD key (it can\n// legitimately lag the published rules-config by a release), so normalize any deprecated key to its\n// canonical name BEFORE validation/placement/loading — every downstream consumer then sees one name.\nconst DEPRECATED_RULE_ALIASES: Readonly<Record<string, string>> = {\n 'pr-merge-cleanup': 'pr-merge-guard',\n 'pr-creation-guard': 'pr-creation-or-push-guard',\n // Renamed once the guard grew a second blocked state (already-merged feature branch): it is no\n // longer about `main` at all, it is THE guard that can block a Read.\n 'main-stale-guard': 'read-stale-guard',\n};\n\n// webpieces-disable no-any-unknown -- opaque per-rule option bags from consumer JSON, validated later\ntype RuleSectionMap = Record<string, Record<string, unknown>>;\n\n/**\n * The single load+validate entry point for ALL consumers (ai-hook-rules, code-rules,\n * nx-webpieces-rules, pr-gate scripts). `@injectable(bindingScopeValues.Singleton)` + injects {@link ConfigFile} so it appears\n * in the rules-config DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ConfigLoader {\n constructor(private readonly configFile: ConfigFile) {}\n\n /**\n * Reads webpieces.config.json once, validates BOTH the `rules` map and the top-level `pr-gate`\n * block, and throws one InformAiError listing every error. When no config file is found it returns\n * lenient empties/defaults (matching prior no-file behavior).\n */\n // webpieces-disable max-lines-new-methods -- the single load+validate pass is one cohesive method\n loadAndValidate(cwd: string): LoadedConfig {\n const configPath = this.configFile.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 new ExcludePaths([], []),\n [],\n null,\n );\n }\n\n const consumerConfig = this.configFile.readRawConfig(configPath);\n const rulesSection = this.normalizeDeprecatedKeys(consumerConfig.rules || {});\n const hookGuardsSection = this.normalizeDeprecatedKeys(consumerConfig.hookGuards || {});\n const legacyPrGate = consumerConfig['pr-gate'];\n\n // rules + hookGuards are validated/loaded as one flat name→config map (the runtime dispatches\n // by each rule's own `scope`). Placement is enforced separately.\n const overrideRules = { ...rulesSection, ...hookGuardsSection };\n\n const rulesDir = consumerConfig.rulesDir ?? [];\n\n // The repo root (dir holding webpieces.config.json) lets checklists[].docs existence be checked.\n const repoRoot = path.dirname(configPath);\n const errors = [\n ...validateWebpiecesConfig(overrideRules, rulesDir.length > 0),\n ...validateSectionPlacement(rulesSection, hookGuardsSection),\n ...validateCommandsSection(consumerConfig.commands, legacyPrGate, repoRoot),\n ...validateExcludePaths(consumerConfig.excludePaths),\n ...validateMatchRulesSection(consumerConfig['match-rules']),\n ];\n if (errors.length > 0) {\n throw new InformAiError(this.formatConfigErrorsBanner(errors));\n }\n\n const commands = buildCommandsConfig(consumerConfig.commands, legacyPrGate);\n this.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, this.mergeRule(defaultRules[name], overrideRules[name]));\n }\n const resolved = new ResolvedConfig(mergedRules, userConfiguredRuleNames, rulesDir, configPath);\n\n const rulesConfig = this.buildWebpiecesRulesConfig(overrideRules, rulesDir);\n const excludePaths = this.parseExcludePaths(consumerConfig.excludePaths);\n const matchRules = this.parseMatchRules(consumerConfig['match-rules']);\n\n return new LoadedConfig(resolved, rulesConfig, commands, commands.prGate, excludePaths, matchRules, configPath);\n }\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. Only fills a gap — an explicit per-guard override wins.\n private 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-or-push-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\n private 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\n // Parse the (already-validated) raw excludePaths block into the typed ExcludePaths.\n // webpieces-disable no-any-unknown -- `raw` is opaque consumer JSON until narrowed here\n private parseExcludePaths(raw: unknown): ExcludePaths {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return new ExcludePaths([], []);\n // webpieces-disable no-any-unknown -- validateExcludePaths already proved both are string[]\n const s = raw as Record<string, string[]>;\n const rules = Array.isArray(s['rules']) ? s['rules'].filter(p => typeof p === 'string') : [];\n const guards = Array.isArray(s['guards']) ? s['guards'].filter(p => typeof p === 'string') : [];\n return new ExcludePaths(rules, guards);\n }\n\n // Parse the (already-validated) raw match-rules array into typed MatchRuleConfig[]. The entries use\n // the canonical field names (turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch) that the match-rules\n // engine reads directly, so no normalization is needed.\n // webpieces-disable no-any-unknown -- validated array; each entry cast to the typed MatchRuleConfig\n private parseMatchRules(raw: unknown): MatchRuleConfig[] {\n if (!Array.isArray(raw)) return [];\n return raw as MatchRuleConfig[];\n }\n\n private 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 private normalizeDeprecatedKeys(section: RuleSectionMap): RuleSectionMap {\n const out: RuleSectionMap = {};\n for (const key of Object.keys(section)) {\n out[DEPRECATED_RULE_ALIASES[key] ?? key] = section[key];\n }\n return out;\n }\n\n // Assemble the validation-failure banner. Most of these errors are version skew, not bad config.\n private formatConfigErrorsBanner(errors: string[]): string {\n return (\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👉 FIX ORDER (do NOT start by deleting keys — that usually deletes VALID config):\\n` +\n ` 1. Run \\`pnpm install\\`. It is ALWAYS allowed through the guard (installer bypass), even ` +\n `while this config is invalid. This is the #1 cause: your installed @webpieces guard is a ` +\n `release BEHIND webpieces.config.json (a dep bump updated the config + lockfile, but ` +\n `node_modules here was never re-installed), so the running validator doesn't know the newer ` +\n `rule names/values yet. \\`pnpm install\\` syncs node_modules to the pinned version.\\n` +\n ` 2. Retry your command. If the errors are gone, you're DONE — do not touch webpieces.config.json.\\n` +\n ` 3. ONLY if an error survives a fresh install is it a genuine typo / removed / renamed rule. ` +\n `Then edit webpieces.config.json (edits to it are ALWAYS allowed) to fix each • above.`\n );\n }\n}\n\n// Temporary migration delegator — consumers migrate to injecting ConfigLoader over follow-up PRs,\n// then this free function is removed. The logic now lives in the injected ConfigLoader class.\nconst configLoaderSvc = new ConfigLoader(new ConfigFile());\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ConfigLoader; removed once all 118 consumers inject it\nexport function loadAndValidate(cwd: string): LoadedConfig {\n return configLoaderSvc.loadAndValidate(cwd);\n}\n"]}
@@ -12,7 +12,7 @@ export declare class MatchRuleConfig extends BaseRuleConfig {
12
12
  options: string[];
13
13
  disableAllowed?: boolean;
14
14
  allowedPaths?: string[];
15
- constructor(name: string, patterns: string[], mainMessage: string, mode: ModifiedCodeMode, ignoreModifiedUntilEpoch: number, options?: string[], disableAllowed?: boolean, allowedPaths?: string[]);
15
+ constructor(name: string, patterns: string[], mainMessage: string, mode: ModifiedCodeMode, turnOffRuleUntilEpoch: number, options?: string[], disableAllowed?: boolean, allowedPaths?: string[], turnOffRuleWhileOnBranch?: string | null);
16
16
  }
17
17
  /** One flagged line. Raw hit — callers apply their own disable filtering. */
18
18
  export declare class MatchRuleViolation {
@@ -13,7 +13,7 @@ const constants_1 = require("./constants");
13
13
  // Unlike the keyed `rules` (each a framework class with a fixed regex + message),
14
14
  // a match-rule is authored ENTIRELY in webpieces.config.json: a `name`, a list of
15
15
  // raw-regex `patterns` to flag, a `mainMessage` + `options[]` shown to the AI, and
16
- // per-entry scoping (`mode`, `allowedPaths`, `disableAllowed`, `ignoreModifiedUntilEpoch`).
16
+ // per-entry scoping (`mode`, `allowedPaths`, `disableAllowed`, `turnOffRuleUntilEpoch`).
17
17
  // The framework ships ONE default example — the `no-fetch` guard (see DEFAULT_MATCH_RULES)
18
18
  // — and clients add more (no-moment, no-lodash-chain, …) without a framework release.
19
19
  //
@@ -34,13 +34,14 @@ class MatchRuleConfig extends rule_configs_1.BaseRuleConfig {
34
34
  disableAllowed;
35
35
  allowedPaths;
36
36
  // eslint-disable-next-line @typescript-eslint/max-params
37
- constructor(name, patterns, mainMessage, mode, ignoreModifiedUntilEpoch, options = [], disableAllowed = true, allowedPaths = []) {
37
+ constructor(name, patterns, mainMessage, mode, turnOffRuleUntilEpoch, options = [], disableAllowed = true, allowedPaths = [], turnOffRuleWhileOnBranch = null) {
38
38
  super();
39
39
  this.name = name;
40
40
  this.patterns = patterns;
41
41
  this.mainMessage = mainMessage;
42
42
  this.mode = mode;
43
- this.ignoreModifiedUntilEpoch = ignoreModifiedUntilEpoch;
43
+ this.turnOffRuleUntilEpoch = turnOffRuleUntilEpoch;
44
+ this.turnOffRuleWhileOnBranch = turnOffRuleWhileOnBranch;
44
45
  this.options = options;
45
46
  this.disableAllowed = disableAllowed;
46
47
  this.allowedPaths = allowedPaths;
@@ -1 +1 @@
1
- {"version":3,"file":"match-rules-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/match-rules-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseRuleConfig, ModifiedCodeMode } from './rule-configs';\nimport { WEBPIECES_DISABLE } from './constants';\n\n// ---------------------------------------------------------------------------\n// match-rules — a generic, client-configurable content-guard engine.\n//\n// Unlike the keyed `rules` (each a framework class with a fixed regex + message),\n// a match-rule is authored ENTIRELY in webpieces.config.json: a `name`, a list of\n// raw-regex `patterns` to flag, a `mainMessage` + `options[]` shown to the AI, and\n// per-entry scoping (`mode`, `allowedPaths`, `disableAllowed`, `ignoreModifiedUntilEpoch`).\n// The framework ships ONE default example — the `no-fetch` guard (see DEFAULT_MATCH_RULES)\n// — and clients add more (no-moment, no-lodash-chain, …) without a framework release.\n//\n// Lives in a NEW top-level `match-rules` ARRAY section (shaped like `pr-gate`/`excludePaths`),\n// NOT a keyed entry under `rules`. Both engines (ai-hook-rules edit-time, code-rules build-time)\n// instantiate one guard per array entry and share the pure matching engine below.\n// ---------------------------------------------------------------------------\n\n/**\n * One entry of the `match-rules` array. Extends BaseRuleConfig so AbstractRule.shouldRun()\n * (OFF / epoch / branch escape hatches) works per entry. `name` doubles as the\n * `// webpieces-disable <name> -- <reason>` token and the report label.\n */\nexport class MatchRuleConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n name: string;\n patterns: string[];\n mainMessage: string;\n options: string[];\n disableAllowed?: boolean;\n allowedPaths?: string[];\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n name: string,\n patterns: string[],\n mainMessage: string,\n mode: ModifiedCodeMode,\n ignoreModifiedUntilEpoch: number,\n options: string[] = [],\n disableAllowed: boolean = true,\n allowedPaths: string[] = [],\n ) {\n super();\n this.name = name;\n this.patterns = patterns;\n this.mainMessage = mainMessage;\n this.mode = mode;\n this.ignoreModifiedUntilEpoch = ignoreModifiedUntilEpoch;\n this.options = options;\n this.disableAllowed = disableAllowed;\n this.allowedPaths = allowedPaths;\n }\n}\n\n/** One flagged line. Raw hit — callers apply their own disable filtering. */\nexport class MatchRuleViolation {\n readonly line: number;\n readonly context: string;\n readonly patternIndex: number;\n\n constructor(line: number, context: string, patternIndex: number) {\n this.line = line;\n this.context = context;\n this.patternIndex = patternIndex;\n }\n}\n\nconst TEST_PATHS: readonly RegExp[] = [/\\.test\\.ts$/, /\\.spec\\.ts$/, /__tests__\\//];\n\n// Glob → RegExp, matching the convention used by no-symbol-di-tokens (`**` spans path\n// separators, `*` stays within a segment). Kept local so rules-config owns no shared glob util.\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n\n/** A file is exempt if it's a test file or matches one of the rule's allowedPaths globs. */\nexport function isMatchRuleAllowedPath(relativePath: string, allowedPaths: readonly string[]): boolean {\n if (TEST_PATHS.some((re: RegExp) => re.test(relativePath))) return true;\n return allowedPaths.some((pattern: string) => globToRegex(pattern).test(relativePath));\n}\n\nfunction stripLineComment(line: string): string {\n const idx = line.indexOf('//');\n if (idx === -1) return line;\n return line.substring(0, idx);\n}\n\n/**\n * Compile a match-rule's raw-regex patterns. Patterns are trusted here — validateMatchRulesSection\n * (run in loadAndValidate, before any engine consumes a match-rule) compile-checks every pattern and\n * rejects the config on the first bad one, so an invalid pattern can never reach this function.\n */\nexport function compileMatchRulePatterns(config: MatchRuleConfig): RegExp[] {\n return (config.patterns ?? []).map((pattern: string) => new RegExp(pattern));\n}\n\n/**\n * Pure matching engine shared by BOTH engines. Takes the ORIGINAL lines (each is stripped of its\n * `//` comment before matching, mirroring the code-rules validators), returns raw hits — NO disable\n * filtering (ai-hook applies its line-mapped isLineDisabled; code-rules applies hasDisable). Returns\n * [] for exempt paths (test files / allowedPaths). At most one violation per line (first pattern wins).\n */\nexport function findMatchRuleViolations(\n lines: readonly string[],\n relativePath: string,\n config: MatchRuleConfig,\n): MatchRuleViolation[] {\n if (isMatchRuleAllowedPath(relativePath, config.allowedPaths ?? [])) return [];\n\n const compiled = compileMatchRulePatterns(config);\n if (compiled.length === 0) return [];\n\n const violations: MatchRuleViolation[] = [];\n for (let i = 0; i < lines.length; i += 1) {\n const stripped = stripLineComment(lines[i] ?? '');\n for (let p = 0; p < compiled.length; p += 1) {\n if (compiled[p].test(stripped)) {\n violations.push(new MatchRuleViolation(i + 1, (lines[i] ?? '').trim(), p));\n break;\n }\n }\n }\n return violations;\n}\n\n/**\n * Render a match-rule's message as a single string for the code-rules console report. Mirrors the\n * ai-hook FixHint layout (mainMessage, \"Fix Option N:\", disable escape) so both engines read alike.\n * ai-hook builds a real FixHint instead (see MatchRule) to reuse report.ts numbering.\n */\nexport function renderMatchRuleMessage(config: MatchRuleConfig): string {\n const lines: string[] = [config.mainMessage];\n (config.options ?? []).forEach((opt: string, i: number) => {\n lines.push(` Fix Option ${String(i + 1)}: ${opt}`);\n });\n if (config.disableAllowed ?? true) {\n lines.push(` Escape (if truly needed): // ${WEBPIECES_DISABLE} ${config.name} -- <reason>`);\n }\n return lines.join('\\n');\n}\n\n// The ONE guard the framework seeds. Printed verbatim (as JSON) by validateMatchRulesSection when the\n// `match-rules` section is missing, and written into a fresh config by the installer. Clients edit it\n// and add more entries. `packages/http/http-client/**` MUST stay allowlisted — ClientFactory.ts is the\n// single sanctioned fetch (the generated proxy); the apis-external dirs host external-service impls.\nexport const DEFAULT_MATCH_RULES: readonly MatchRuleConfig[] = [\n new MatchRuleConfig(\n 'no-fetch',\n [\n '(?<![.\\\\w])fetch\\\\s*\\\\(',\n '\\\\baxios\\\\b',\n '\\\\bXMLHttpRequest\\\\b',\n '\\\\bnew\\\\s+Request\\\\s*\\\\(',\n \"from\\\\s+['\\\"](node-fetch|got|undici)['\\\"]\",\n ],\n 'Raw HTTP (fetch/axios/XMLHttpRequest/…) bypasses contract-first development — the client and server stop sharing the same API contract/types. Generate a type-safe client from the contract instead:',\n 'NEW_AND_MODIFIED_CODE',\n 0,\n [\n \"PREFERRED: generate a client from the API you want to call — import { ClientHttpFactory, ClientConfig } from '@webpieces/http-client'; const client = factory.createRpcClient(SomeApi, new ClientConfig('https://host')); await client.someMethod(req);\",\n 'Reuse an existing API contract already defined in your repo (under libraries/apis/**) and generate its client the same way.',\n 'For a truly external service, create a NEW API contract (a decorated abstract class) AND a NEW implementation that calls fetch behind that contract, under an allowlisted dir (libraries/apis-external/**). The contract stays the shared surface; fetch is an impl detail.',\n ],\n true,\n ['packages/http/http-client/**', 'libraries/apis-external/**', 'libraries/apis/external/**'],\n ),\n];\n"]}
1
+ {"version":3,"file":"match-rules-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/match-rules-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseRuleConfig, ModifiedCodeMode } from './rule-configs';\nimport { WEBPIECES_DISABLE } from './constants';\n\n// ---------------------------------------------------------------------------\n// match-rules — a generic, client-configurable content-guard engine.\n//\n// Unlike the keyed `rules` (each a framework class with a fixed regex + message),\n// a match-rule is authored ENTIRELY in webpieces.config.json: a `name`, a list of\n// raw-regex `patterns` to flag, a `mainMessage` + `options[]` shown to the AI, and\n// per-entry scoping (`mode`, `allowedPaths`, `disableAllowed`, `turnOffRuleUntilEpoch`).\n// The framework ships ONE default example — the `no-fetch` guard (see DEFAULT_MATCH_RULES)\n// — and clients add more (no-moment, no-lodash-chain, …) without a framework release.\n//\n// Lives in a NEW top-level `match-rules` ARRAY section (shaped like `pr-gate`/`excludePaths`),\n// NOT a keyed entry under `rules`. Both engines (ai-hook-rules edit-time, code-rules build-time)\n// instantiate one guard per array entry and share the pure matching engine below.\n// ---------------------------------------------------------------------------\n\n/**\n * One entry of the `match-rules` array. Extends BaseRuleConfig so AbstractRule.shouldRun()\n * (OFF / epoch / branch escape hatches) works per entry. `name` doubles as the\n * `// webpieces-disable <name> -- <reason>` token and the report label.\n */\nexport class MatchRuleConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n name: string;\n patterns: string[];\n mainMessage: string;\n options: string[];\n disableAllowed?: boolean;\n allowedPaths?: string[];\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n name: string,\n patterns: string[],\n mainMessage: string,\n mode: ModifiedCodeMode,\n turnOffRuleUntilEpoch: number,\n options: string[] = [],\n disableAllowed: boolean = true,\n allowedPaths: string[] = [],\n turnOffRuleWhileOnBranch: string | null = null,\n ) {\n super();\n this.name = name;\n this.patterns = patterns;\n this.mainMessage = mainMessage;\n this.mode = mode;\n this.turnOffRuleUntilEpoch = turnOffRuleUntilEpoch;\n this.turnOffRuleWhileOnBranch = turnOffRuleWhileOnBranch;\n this.options = options;\n this.disableAllowed = disableAllowed;\n this.allowedPaths = allowedPaths;\n }\n}\n\n/** One flagged line. Raw hit — callers apply their own disable filtering. */\nexport class MatchRuleViolation {\n readonly line: number;\n readonly context: string;\n readonly patternIndex: number;\n\n constructor(line: number, context: string, patternIndex: number) {\n this.line = line;\n this.context = context;\n this.patternIndex = patternIndex;\n }\n}\n\nconst TEST_PATHS: readonly RegExp[] = [/\\.test\\.ts$/, /\\.spec\\.ts$/, /__tests__\\//];\n\n// Glob → RegExp, matching the convention used by no-symbol-di-tokens (`**` spans path\n// separators, `*` stays within a segment). Kept local so rules-config owns no shared glob util.\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n\n/** A file is exempt if it's a test file or matches one of the rule's allowedPaths globs. */\nexport function isMatchRuleAllowedPath(relativePath: string, allowedPaths: readonly string[]): boolean {\n if (TEST_PATHS.some((re: RegExp) => re.test(relativePath))) return true;\n return allowedPaths.some((pattern: string) => globToRegex(pattern).test(relativePath));\n}\n\nfunction stripLineComment(line: string): string {\n const idx = line.indexOf('//');\n if (idx === -1) return line;\n return line.substring(0, idx);\n}\n\n/**\n * Compile a match-rule's raw-regex patterns. Patterns are trusted here — validateMatchRulesSection\n * (run in loadAndValidate, before any engine consumes a match-rule) compile-checks every pattern and\n * rejects the config on the first bad one, so an invalid pattern can never reach this function.\n */\nexport function compileMatchRulePatterns(config: MatchRuleConfig): RegExp[] {\n return (config.patterns ?? []).map((pattern: string) => new RegExp(pattern));\n}\n\n/**\n * Pure matching engine shared by BOTH engines. Takes the ORIGINAL lines (each is stripped of its\n * `//` comment before matching, mirroring the code-rules validators), returns raw hits — NO disable\n * filtering (ai-hook applies its line-mapped isLineDisabled; code-rules applies hasDisable). Returns\n * [] for exempt paths (test files / allowedPaths). At most one violation per line (first pattern wins).\n */\nexport function findMatchRuleViolations(\n lines: readonly string[],\n relativePath: string,\n config: MatchRuleConfig,\n): MatchRuleViolation[] {\n if (isMatchRuleAllowedPath(relativePath, config.allowedPaths ?? [])) return [];\n\n const compiled = compileMatchRulePatterns(config);\n if (compiled.length === 0) return [];\n\n const violations: MatchRuleViolation[] = [];\n for (let i = 0; i < lines.length; i += 1) {\n const stripped = stripLineComment(lines[i] ?? '');\n for (let p = 0; p < compiled.length; p += 1) {\n if (compiled[p].test(stripped)) {\n violations.push(new MatchRuleViolation(i + 1, (lines[i] ?? '').trim(), p));\n break;\n }\n }\n }\n return violations;\n}\n\n/**\n * Render a match-rule's message as a single string for the code-rules console report. Mirrors the\n * ai-hook FixHint layout (mainMessage, \"Fix Option N:\", disable escape) so both engines read alike.\n * ai-hook builds a real FixHint instead (see MatchRule) to reuse report.ts numbering.\n */\nexport function renderMatchRuleMessage(config: MatchRuleConfig): string {\n const lines: string[] = [config.mainMessage];\n (config.options ?? []).forEach((opt: string, i: number) => {\n lines.push(` Fix Option ${String(i + 1)}: ${opt}`);\n });\n if (config.disableAllowed ?? true) {\n lines.push(` Escape (if truly needed): // ${WEBPIECES_DISABLE} ${config.name} -- <reason>`);\n }\n return lines.join('\\n');\n}\n\n// The ONE guard the framework seeds. Printed verbatim (as JSON) by validateMatchRulesSection when the\n// `match-rules` section is missing, and written into a fresh config by the installer. Clients edit it\n// and add more entries. `packages/http/http-client/**` MUST stay allowlisted — ClientFactory.ts is the\n// single sanctioned fetch (the generated proxy); the apis-external dirs host external-service impls.\nexport const DEFAULT_MATCH_RULES: readonly MatchRuleConfig[] = [\n new MatchRuleConfig(\n 'no-fetch',\n [\n '(?<![.\\\\w])fetch\\\\s*\\\\(',\n '\\\\baxios\\\\b',\n '\\\\bXMLHttpRequest\\\\b',\n '\\\\bnew\\\\s+Request\\\\s*\\\\(',\n \"from\\\\s+['\\\"](node-fetch|got|undici)['\\\"]\",\n ],\n 'Raw HTTP (fetch/axios/XMLHttpRequest/…) bypasses contract-first development — the client and server stop sharing the same API contract/types. Generate a type-safe client from the contract instead:',\n 'NEW_AND_MODIFIED_CODE',\n 0,\n [\n \"PREFERRED: generate a client from the API you want to call — import { ClientHttpFactory, ClientConfig } from '@webpieces/http-client'; const client = factory.createRpcClient(SomeApi, new ClientConfig('https://host')); await client.someMethod(req);\",\n 'Reuse an existing API contract already defined in your repo (under libraries/apis/**) and generate its client the same way.',\n 'For a truly external service, create a NEW API contract (a decorated abstract class) AND a NEW implementation that calls fetch behind that contract, under an allowlisted dir (libraries/apis-external/**). The contract stays the shared surface; fetch is an impl detail.',\n ],\n true,\n ['packages/http/http-client/**', 'libraries/apis-external/**', 'libraries/apis/external/**'],\n ),\n];\n"]}
@@ -20,7 +20,7 @@ exports.CLIENT_CREATION_SEVERITIES = ['warn', 'error'];
20
20
  // `severity` ships "warn" (report + migration, build passes) so an upgrade can't break an un-migrated
21
21
  // Angular repo; flip to "error" once libraries are migrated. `allowedRoles` defaults to the runnable
22
22
  // roles; `allowedPaths` exempts whole file trees (shared glob semantics). Standard rollout knobs via
23
- // the base: mode (OFF | NEW_AND_MODIFIED_CODE | NEW_AND_MODIFIED_FILES), ignoreModifiedUntilEpoch,
23
+ // the base: mode (OFF | NEW_AND_MODIFIED_CODE | NEW_AND_MODIFIED_FILES), turnOffRuleUntilEpoch,
24
24
  // branch, and disableAllowed for the inline `// webpieces-disable` escape.
25
25
  class NoClientCreationOutsideServerOrClientConfig extends rule_configs_1.BaseRuleConfig {
26
26
  severity;
@@ -1 +1 @@
1
- {"version":3,"file":"no-client-creation-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/no-client-creation-config.ts"],"names":[],"mappings":";;;AAAA,2CAAoD;AACpD,iDAAyG;AAEzG,iGAAiG;AACjG,kGAAkG;AAClG,kGAAkG;AAClG,6EAA6E;AAChE,QAAA,0BAA0B,GAAG,CAAC,MAAM,EAAE,OAAO,CAAU,CAAC;AAGrE,qGAAqG;AACrG,sGAAsG;AACtG,sGAAsG;AACtG,sGAAsG;AACtG,qGAAqG;AACrG,qGAAqG;AACrG,mGAAmG;AACnG,8DAA8D;AAC9D,EAAE;AACF,sGAAsG;AACtG,qGAAqG;AACrG,qGAAqG;AACrG,mGAAmG;AACnG,2EAA2E;AAC3E,MAAa,2CAA4C,SAAQ,6BAAc;IAE3E,QAAQ,CAA0B;IAClC,cAAc,CAAW;IACzB,YAAY,CAAY;IACxB,YAAY,CAAY;IAExB,MAAM,CAAU,MAAM,GAA6D;QAC/E,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,kCAAmB,CAAC;QACjD,QAAQ,EAAE,oBAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,kCAA0B,CAAC;QACjE,cAAc,EAAE,oBAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC5C,YAAY,EAAE,oBAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC3C,YAAY,EAAE,oBAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC3C,GAAG,+BAAgB;KACtB,CAAC;;AAdN,kGAeC","sourcesContent":["import { FieldDef, SchemaShape } from './field-def';\nimport { BaseRuleConfig, BASE_RULE_SCHEMA, ModifiedCodeMode, MODIFIED_CODE_MODES } from './rule-configs';\n\n// no-client-creation-outside-server-or-client severity. Landed as a hard failure this rule would\n// break every existing Angular repo on upgrade (provideCoreClient-style helpers create the client\n// inside a role:lib on purpose), so it ships WARN — reports + prints the migration but passes the\n// build — and a repo flips it to `error` once it has migrated its libraries.\nexport const CLIENT_CREATION_SEVERITIES = ['warn', 'error'] as const;\nexport type ClientCreationSeverity = typeof CLIENT_CREATION_SEVERITIES[number];\n\n// no-client-creation-outside-server-or-client — flags a project that CONSTRUCTS an rpc/pubsub client\n// (`factory.createRpcClient(...)` / `factory.createPubSubClient(...)`) when the project's `role:` tag\n// is not one of `allowedRoles`. Only a runnable entrypoint (server / client app / app) has a declared\n// identity (`serviceName`) or target (`callsService`), so a client built there is attributable in the\n// runtime graph; a client built inside a `role:lib` reaches the fan-out fallback (one `uses` edge to\n// EVERY implementer of the api) and draws calls that cannot happen. A reusable library takes the api\n// INJECTED — the server/app module binds it to a client. Importing the api type or its DI token is\n// FINE and never flagged; only constructing the transport is.\n//\n// `severity` ships \"warn\" (report + migration, build passes) so an upgrade can't break an un-migrated\n// Angular repo; flip to \"error\" once libraries are migrated. `allowedRoles` defaults to the runnable\n// roles; `allowedPaths` exempts whole file trees (shared glob semantics). Standard rollout knobs via\n// the base: mode (OFF | NEW_AND_MODIFIED_CODE | NEW_AND_MODIFIED_FILES), ignoreModifiedUntilEpoch,\n// branch, and disableAllowed for the inline `// webpieces-disable` escape.\nexport class NoClientCreationOutsideServerOrClientConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n severity?: ClientCreationSeverity;\n disableAllowed?: boolean;\n allowedRoles?: string[];\n allowedPaths?: string[];\n\n static readonly SCHEMA: SchemaShape<NoClientCreationOutsideServerOrClientConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n severity: FieldDef.optional('string', CLIENT_CREATION_SEVERITIES),\n disableAllowed: FieldDef.optional('boolean'),\n allowedRoles: FieldDef.optional('string[]'),\n allowedPaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n"]}
1
+ {"version":3,"file":"no-client-creation-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/no-client-creation-config.ts"],"names":[],"mappings":";;;AAAA,2CAAoD;AACpD,iDAAyG;AAEzG,iGAAiG;AACjG,kGAAkG;AAClG,kGAAkG;AAClG,6EAA6E;AAChE,QAAA,0BAA0B,GAAG,CAAC,MAAM,EAAE,OAAO,CAAU,CAAC;AAGrE,qGAAqG;AACrG,sGAAsG;AACtG,sGAAsG;AACtG,sGAAsG;AACtG,qGAAqG;AACrG,qGAAqG;AACrG,mGAAmG;AACnG,8DAA8D;AAC9D,EAAE;AACF,sGAAsG;AACtG,qGAAqG;AACrG,qGAAqG;AACrG,gGAAgG;AAChG,2EAA2E;AAC3E,MAAa,2CAA4C,SAAQ,6BAAc;IAE3E,QAAQ,CAA0B;IAClC,cAAc,CAAW;IACzB,YAAY,CAAY;IACxB,YAAY,CAAY;IAExB,MAAM,CAAU,MAAM,GAA6D;QAC/E,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,kCAAmB,CAAC;QACjD,QAAQ,EAAE,oBAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,kCAA0B,CAAC;QACjE,cAAc,EAAE,oBAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC5C,YAAY,EAAE,oBAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC3C,YAAY,EAAE,oBAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC3C,GAAG,+BAAgB;KACtB,CAAC;;AAdN,kGAeC","sourcesContent":["import { FieldDef, SchemaShape } from './field-def';\nimport { BaseRuleConfig, BASE_RULE_SCHEMA, ModifiedCodeMode, MODIFIED_CODE_MODES } from './rule-configs';\n\n// no-client-creation-outside-server-or-client severity. Landed as a hard failure this rule would\n// break every existing Angular repo on upgrade (provideCoreClient-style helpers create the client\n// inside a role:lib on purpose), so it ships WARN — reports + prints the migration but passes the\n// build — and a repo flips it to `error` once it has migrated its libraries.\nexport const CLIENT_CREATION_SEVERITIES = ['warn', 'error'] as const;\nexport type ClientCreationSeverity = typeof CLIENT_CREATION_SEVERITIES[number];\n\n// no-client-creation-outside-server-or-client — flags a project that CONSTRUCTS an rpc/pubsub client\n// (`factory.createRpcClient(...)` / `factory.createPubSubClient(...)`) when the project's `role:` tag\n// is not one of `allowedRoles`. Only a runnable entrypoint (server / client app / app) has a declared\n// identity (`serviceName`) or target (`callsService`), so a client built there is attributable in the\n// runtime graph; a client built inside a `role:lib` reaches the fan-out fallback (one `uses` edge to\n// EVERY implementer of the api) and draws calls that cannot happen. A reusable library takes the api\n// INJECTED — the server/app module binds it to a client. Importing the api type or its DI token is\n// FINE and never flagged; only constructing the transport is.\n//\n// `severity` ships \"warn\" (report + migration, build passes) so an upgrade can't break an un-migrated\n// Angular repo; flip to \"error\" once libraries are migrated. `allowedRoles` defaults to the runnable\n// roles; `allowedPaths` exempts whole file trees (shared glob semantics). Standard rollout knobs via\n// the base: mode (OFF | NEW_AND_MODIFIED_CODE | NEW_AND_MODIFIED_FILES), turnOffRuleUntilEpoch,\n// branch, and disableAllowed for the inline `// webpieces-disable` escape.\nexport class NoClientCreationOutsideServerOrClientConfig extends BaseRuleConfig {\n declare mode?: ModifiedCodeMode;\n severity?: ClientCreationSeverity;\n disableAllowed?: boolean;\n allowedRoles?: string[];\n allowedPaths?: string[];\n\n static readonly SCHEMA: SchemaShape<NoClientCreationOutsideServerOrClientConfig> = {\n mode: new FieldDef('string', MODIFIED_CODE_MODES),\n severity: FieldDef.optional('string', CLIENT_CREATION_SEVERITIES),\n disableAllowed: FieldDef.optional('boolean'),\n allowedRoles: FieldDef.optional('string[]'),\n allowedPaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n"]}
@@ -29,14 +29,10 @@ export declare const STRUCTURAL_MODES: readonly ["OFF", "RUN_EVERY_TIME"];
29
29
  export type StructuralMode = typeof STRUCTURAL_MODES[number];
30
30
  export declare abstract class BaseRuleConfig {
31
31
  mode?: string;
32
- ignoreModifiedUntilEpoch?: number;
33
- ignoreRuleWhileOnBranch?: string;
34
32
  turnOffRuleUntilEpoch?: number;
35
- turnOffRuleWhileOnBranch?: string;
33
+ turnOffRuleWhileOnBranch?: string | null;
36
34
  }
37
35
  export declare const BASE_RULE_SCHEMA: {
38
- ignoreModifiedUntilEpoch: FieldDef;
39
- ignoreRuleWhileOnBranch: FieldDef;
40
36
  turnOffRuleUntilEpoch: FieldDef;
41
37
  turnOffRuleWhileOnBranch: FieldDef;
42
38
  };
@@ -26,7 +26,7 @@ exports.ON_OFF_MODES = ['ON', 'OFF'];
26
26
  // branch-creation-guard modes. ON_NO_SUBBRANCHES is the strict variant: it hard-blocks
27
27
  // creating a branch off any non-main branch (no sub-branch affordance), pointing the agent
28
28
  // back to `git checkout main && git pull && git checkout -b <branch>`. Temporarily overridable
29
- // via the universal ignoreModifiedUntilEpoch escape hatch.
29
+ // via the universal turnOffRuleUntilEpoch escape hatch.
30
30
  exports.BRANCH_GUARD_MODES = ['ON', 'OFF', 'ON_NO_SUBBRANCHES'];
31
31
  exports.VALIDATE_TS_MODES = ['OFF', 'NEW_AND_MODIFIED_FILES'];
32
32
  // Structural / whole-graph rules (import-cycle, runtime-architecture, nx-wiring). They can't be
@@ -42,41 +42,32 @@ exports.STRUCTURAL_MODES = ['OFF', 'RUN_EVERY_TIME'];
42
42
  // rule. `mode` stays per-rule because its allowed values vary (ON/OFF vs
43
43
  // NEW_AND_MODIFIED_CODE vs NEW_AND_MODIFIED_METHODS, etc).
44
44
  //
45
- // TWO NAMES per hatch, and BOTH are accepted (see normalizeTurnOffAliases):
46
- // turnOffRuleUntilEpoch — new, self-describing name. Supersedes ignoreModifiedUntilEpoch.
47
- // turnOffRuleWhileOnBranch new, self-describing name. Supersedes ignoreRuleWhileOnBranch.
48
- // The original `ignoreModifiedUntilEpoch` / `ignoreRuleWhileOnBranch` keep working so a config can
49
- // migrate a rule at a time. When both names are present on one rule, the new name wins.
50
- //
51
- // Convention (unchanged): 0 = rule active (epoch is in the past, never skipped); a future unix epoch
52
- // IN SECONDS = rule temporarily disabled until that moment. All four fields are OPTIONAL — a rule may
53
- // carry the branch hatch alone (turnOffRuleWhileOnBranch) with no epoch, which is the intended
54
- // end-state once callers stop writing turnOffRuleUntilEpoch.
45
+ // BOTH fields are REQUIRED on every rule so both hatches are ALWAYS VISIBLE in the config — an AI
46
+ // editing webpieces.config.json sees them on every rule and cannot miss that a rule can be time-boxed
47
+ // or branch-scoped off. Convention:
48
+ // turnOffRuleUntilEpoch: 0 = rule active (epoch in the past); a future unix epoch IN SECONDS =
49
+ // temporarily disabled until that moment.
50
+ // turnOffRuleWhileOnBranch: null = always on; a branch name = disabled while that branch is checked out.
51
+ // Required-but-nullable so its "unset" state is present-and-visible (null)
52
+ // rather than omitted.
53
+ // The earlier names `ignoreModifiedUntilEpoch` / `ignoreRuleWhileOnBranch` were RENAMED to these and are
54
+ // no longer accepted the validator flags them with a "renamed to X" hint (validate-config.ts).
55
55
  // ---------------------------------------------------------------------------
56
56
  class BaseRuleConfig {
57
57
  // `mode` is declared here (loosely typed) so the shared AbstractRule base can read it for
58
58
  // on/off. Each concrete *Config narrows it to its own union (e.g. `mode?: ModifiedCodeMode`),
59
59
  // which is an assignable (covariant) override.
60
60
  mode;
61
- // Original names still accepted. normalizeTurnOffAliases canonicalizes the new names ONTO these,
62
- // so every reader in both packages keeps reading exactly this pair.
63
- ignoreModifiedUntilEpoch;
64
- ignoreRuleWhileOnBranch;
65
- // New, self-describing names. A config using these is normalized onto the pair above at load time.
61
+ // TS-optional, but schema-REQUIRED (see BASE_RULE_SCHEMA) same split as `mode`. Read directly by
62
+ // AbstractRule.shouldRun, RuleGate, and the code-rules validators.
66
63
  turnOffRuleUntilEpoch;
67
64
  turnOffRuleWhileOnBranch;
68
65
  }
69
66
  exports.BaseRuleConfig = BaseRuleConfig;
70
67
  exports.BASE_RULE_SCHEMA = {
71
- ignoreModifiedUntilEpoch: field_def_1.FieldDef.optional('number'),
72
- ignoreRuleWhileOnBranch: field_def_1.FieldDef.optional('string'),
73
- turnOffRuleUntilEpoch: field_def_1.FieldDef.optional('number'),
74
- turnOffRuleWhileOnBranch: field_def_1.FieldDef.optional('string'),
68
+ turnOffRuleUntilEpoch: new field_def_1.FieldDef('number'),
69
+ turnOffRuleWhileOnBranch: field_def_1.FieldDef.nullableString(),
75
70
  };
76
- // The new names are canonicalized onto the original pair at the load boundary by
77
- // ConfigLoader.normalizeTurnOffAliases (load-config.ts), sibling to normalizeDeprecatedKeys — so every
78
- // downstream reader (AbstractRule.shouldRun, RuleGate, the match-rules engine) reads one name and
79
- // needs no change. new name wins when both are present.
80
71
  class MaxMethodLinesConfig extends BaseRuleConfig {
81
72
  limit;
82
73
  disableAllowed;
@@ -245,7 +236,7 @@ class NoSymbolDiTokensConfig extends BaseRuleConfig {
245
236
  exports.NoSymbolDiTokensConfig = NoSymbolDiTokensConfig;
246
237
  // Flags `process.exit(...)` outside a main()/runMain wrapper (and `import { main }`) so a deep exit
247
238
  // can't silently kill a reused server/command. Gradual-rollout knobs via the standard base: mode
248
- // (OFF | NEW_AND_MODIFIED_CODE | NEW_AND_MODIFIED_FILES), ignoreModifiedUntilEpoch, branch, and
239
+ // (OFF | NEW_AND_MODIFIED_CODE | NEW_AND_MODIFIED_FILES), turnOffRuleUntilEpoch, branch, and
249
240
  // disableAllowed for the inline `// webpieces-disable` escape at genuine terminal boundaries.
250
241
  class NoProcessExitOutsideMainConfig extends BaseRuleConfig {
251
242
  disableAllowed;
@@ -261,7 +252,7 @@ exports.NoProcessExitOutsideMainConfig = NoProcessExitOutsideMainConfig;
261
252
  // @DocumentDesign only work when behavior lives in injectable classes — a module-scope function is a
262
253
  // dead-end the DI graph can't reach. Inline callbacks, nested functions inside methods, and non-function
263
254
  // top-level consts (objects, zod schemas, primitives) are NOT flagged. Standard rollout knobs via the
264
- // base: mode (OFF | NEW_AND_MODIFIED_CODE | NEW_AND_MODIFIED_FILES), ignoreModifiedUntilEpoch, branch,
255
+ // base: mode (OFF | NEW_AND_MODIFIED_CODE | NEW_AND_MODIFIED_FILES), turnOffRuleUntilEpoch, branch,
265
256
  // and disableAllowed for the inline `// webpieces-disable` escape. `allowedPaths` exempts whole file
266
257
  // trees that legitimately live outside the class-per-behavior model (e.g. React component/hook files,
267
258
  // framework glue), matched with the shared glob/prefix/segment semantics of `isPathExcluded`.
@@ -283,7 +274,7 @@ exports.NoFunctionOutsideClassConfig = NoFunctionOutsideClassConfig;
283
274
  // its own (see CLAUDE.md, and the no-symbol-di-tokens rule that pushes the same way). Symbol/interface
284
275
  // tokens are NOT flagged because they never equal the type (`@inject(FOO_TOKEN) x: Provider<Foo>`).
285
276
  // AI keeps carpet-bombing `@inject`; this fails the build on the redundant form. Standard rollout knobs
286
- // via the base: mode (OFF | NEW_AND_MODIFIED_CODE | NEW_AND_MODIFIED_FILES), ignoreModifiedUntilEpoch,
277
+ // via the base: mode (OFF | NEW_AND_MODIFIED_CODE | NEW_AND_MODIFIED_FILES), turnOffRuleUntilEpoch,
287
278
  // branch, and disableAllowed for the inline `// webpieces-disable` escape. `allowedPaths` exempts whole
288
279
  // file trees, matched with the shared glob/prefix/segment semantics.
289
280
  class InjectAnnotationNotNeededForConcreteClassConfig extends BaseRuleConfig {
@@ -347,7 +338,7 @@ class BranchCreationGuardConfig extends BaseRuleConfig {
347
338
  // Let the detached background refresher DELETE dead branches on its own, instead of only
348
339
  // reporting them. Every candidate is provably dead (merged PR / squash backup / no commits) and
349
340
  // recoverable by the SHA logged to branch-mutations.log — but it is still UNATTENDED deletion,
350
- // so this is schema-REQUIRED like `mode` and `ignoreModifiedUntilEpoch`. "Every built-in rule
341
+ // so this is schema-REQUIRED like `mode` and `turnOffRuleUntilEpoch`. "Every built-in rule
351
342
  // must be explicitly configured — no silent defaults" (validate-config.ts) applies with extra
352
343
  // force here: branches disappearing on a preference nobody ever stated is precisely the kind of
353
344
  // default that must not exist. Validation makes each consumer answer the question once.