@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 +1 -1
- package/src/abstract-rule.js +1 -1
- package/src/abstract-rule.js.map +1 -1
- package/src/default-rules.js +1 -1
- package/src/default-rules.js.map +1 -1
- package/src/field-def.d.ts +4 -1
- package/src/field-def.js +13 -1
- package/src/field-def.js.map +1 -1
- package/src/load-config.d.ts +0 -8
- package/src/load-config.js +3 -29
- package/src/load-config.js.map +1 -1
- package/src/match-rules-config.d.ts +1 -1
- package/src/match-rules-config.js +4 -3
- package/src/match-rules-config.js.map +1 -1
- package/src/no-client-creation-config.js +1 -1
- package/src/no-client-creation-config.js.map +1 -1
- package/src/rule-configs.d.ts +1 -5
- package/src/rule-configs.js +19 -28
- package/src/rule-configs.js.map +1 -1
- package/src/skip-rule.d.ts +1 -1
- package/src/skip-rule.js +5 -2
- package/src/skip-rule.js.map +1 -1
- package/src/types.d.ts +1 -1
- package/src/types.js +1 -1
- package/src/types.js.map +1 -1
- package/src/validate-config.js +42 -20
- package/src/validate-config.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/rules-config",
|
|
3
|
-
"version": "0.4.
|
|
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",
|
package/src/abstract-rule.js
CHANGED
|
@@ -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.
|
|
25
|
+
const skip = (0, skip_rule_1.shouldSkipRule)(this.config.turnOffRuleUntilEpoch, this.config.turnOffRuleWhileOnBranch);
|
|
26
26
|
return !skip.skip;
|
|
27
27
|
}
|
|
28
28
|
}
|
package/src/abstract-rule.js.map
CHANGED
|
@@ -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,
|
|
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"]}
|
package/src/default-rules.js
CHANGED
|
@@ -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
|
-
//
|
|
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' },
|
package/src/default-rules.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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 // 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"]}
|
package/src/field-def.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
package/src/field-def.js.map
CHANGED
|
@@ -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;
|
|
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"]}
|
package/src/load-config.d.ts
CHANGED
|
@@ -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;
|
package/src/load-config.js
CHANGED
|
@@ -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[].
|
|
162
|
-
//
|
|
163
|
-
//
|
|
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) {
|
package/src/load-config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-config.ts"],"names":[],"mappings":";;;AAqPA,0CAEC;;AAvPD,mDAA6B;AAC7B,yCAA2D;AAE3D,uDAAwE;AACxE,+CAA2C;AAC3C,mDAA+C;AAC/C,6DAAoD;AACpD,uDAAkD;AAElD,mCAA0E;AAC1E,uDAAgK;AAEhK,iEAA8D;AAE9D;;;GAGG;AACH,MAAa,YAAY;IAGR;IACA;IACA;IACA;IACA;IACA;IACA;IARb,yDAAyD;IACzD,YACa,QAAwB,EACxB,WAAiC,EACjC,QAAwB,EACxB,MAAoB,EACpB,YAA0B,EAC1B,UAAsC,EACtC,UAAyB;QANzB,aAAQ,GAAR,QAAQ,CAAgB;QACxB,gBAAW,GAAX,WAAW,CAAsB;QACjC,aAAQ,GAAR,QAAQ,CAAgB;QACxB,WAAM,GAAN,MAAM,CAAc;QACpB,iBAAY,GAAZ,YAAY,CAAc;QAC1B,eAAU,GAAV,UAAU,CAA4B;QACtC,eAAU,GAAV,UAAU,CAAe;IACnC,CAAC;CACP;AAXD,oCAWC;AAED,qGAAqG;AACrG,oGAAoG;AACpG,qGAAqG;AACrG,MAAM,uBAAuB,GAAqC;IAC9D,kBAAkB,EAAE,gBAAgB;IACpC,mBAAmB,EAAE,2BAA2B;IAChD,+FAA+F;IAC/F,qEAAqE;IACrE,kBAAkB,EAAE,kBAAkB;CACzC,CAAC;AAKF;;;;GAIG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAY;IACQ;IAA7B,YAA6B,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAG,CAAC;IAEvD;;;;OAIG;IACH,kGAAkG;IAClG,eAAe,CAAC,GAAW;QACvB,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QACvD,IAAI,CAAC,UAAU,EAAE,CAAC;YACd,MAAM,aAAa,GAAG,IAAA,qCAAmB,EAAC,SAAS,CAAC,CAAC;YACrD,OAAO,IAAI,YAAY,CACnB,IAAI,sBAAc,CAAC,IAAI,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAClD,IAAI,2CAAoB,EAAE,EAC1B,aAAa,EACb,aAAa,CAAC,MAAM,EACpB,IAAI,iCAAY,CAAC,EAAE,EAAE,EAAE,CAAC,EACxB,EAAE,EACF,IAAI,CACP,CAAC;QACN,CAAC;QAED,MAAM,cAAc,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QACjE,MAAM,YAAY,GAAG,IAAI,CAAC,uBAAuB,CAAC,cAAc,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QAC9E,MAAM,iBAAiB,GAAG,IAAI,CAAC,uBAAuB,CAAC,cAAc,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;QACxF,MAAM,YAAY,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;QAE/C,8FAA8F;QAC9F,iEAAiE;QACjE,MAAM,aAAa,GAAG,EAAE,GAAG,YAAY,EAAE,GAAG,iBAAiB,EAAE,CAAC;QAEhE,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,IAAI,EAAE,CAAC;QAE/C,iGAAiG;QACjG,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG;YACX,GAAG,IAAA,yCAAuB,EAAC,aAAa,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YAC9D,GAAG,IAAA,0CAAwB,EAAC,YAAY,EAAE,iBAAiB,CAAC;YAC5D,GAAG,IAAA,yCAAuB,EAAC,cAAc,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,CAAC;YAC3E,GAAG,IAAA,sCAAoB,EAAC,cAAc,CAAC,YAAY,CAAC;YACpD,GAAG,IAAA,2CAAyB,EAAC,cAAc,CAAC,aAAa,CAAC,CAAC;SAC9D,CAAC;QACF,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,+BAAa,CAAC,IAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC,CAAC;QACnE,CAAC;QACD,yEAAyE;QACzE,2FAA2F;QAC3F,0FAA0F;QAC1F,2FAA2F;QAC3F,kGAAkG;QAClG,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC;YAAE,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC;QAElF,MAAM,QAAQ,GAAG,IAAA,qCAAmB,EAAC,cAAc,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC5E,IAAI,CAAC,oBAAoB,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QAEnD,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;QACpE,MAAM,WAAW,GAAG,IAAI,GAAG,EAA8B,CAAC;QAC1D,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC;YACzB,GAAG,MAAM,CAAC,IAAI,CAAC,4BAAY,CAAC;YAC5B,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC;SAChC,CAAC,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YAC9B,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,4BAAY,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnF,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,sBAAc,CAAC,WAAW,EAAE,uBAAuB,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QAEhG,MAAM,WAAW,GAAG,IAAI,CAAC,yBAAyB,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QAC5E,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;QACzE,MAAM,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;QAEvE,OAAO,IAAI,YAAY,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IACpH,CAAC;IAED,mGAAmG;IACnG,gGAAgG;IACxF,oBAAoB;IACxB,mEAAmE;IACnE,KAA8C,EAC9C,QAAwB;QAExB,MAAM,UAAU,GAAG,KAAK,CAAC,2BAA2B,CAAC,CAAC;QACtD,IAAI,UAAU,IAAI,UAAU,CAAC,iBAAiB,CAAC,KAAK,SAAS,EAAE,CAAC;YAC5D,UAAU,CAAC,iBAAiB,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC;QACtD,CAAC;QACD,MAAM,eAAe,GAAG,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACzD,IAAI,eAAe,IAAI,eAAe,CAAC,sBAAsB,CAAC,KAAK,SAAS,EAAE,CAAC;YAC3E,eAAe,CAAC,sBAAsB,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC;QACrE,CAAC;IACL,CAAC;IAED,kFAAkF;IAC1E,SAAS;IACb,wDAAwD;IACxD,QAA6C;IAC7C,wDAAwD;IACxD,YAAiD;QAEjD,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,0BAAkB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/E,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,0BAAkB,CAAC,YAA4B,CAAC,CAAC;QAC3E,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,0BAAkB,CAAC,QAAuB,CAAC,CAAC;QAE1E,iEAAiE;QACjE,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QACrE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAC7E,OAAO,IAAI,0BAAkB,CAAC,MAAqB,CAAC,CAAC;IACzD,CAAC;IAED,oFAAoF;IACpF,wFAAwF;IAChF,iBAAiB,CAAC,GAAY;QAClC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,iCAAY,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACnG,4FAA4F;QAC5F,MAAM,CAAC,GAAG,GAA+B,CAAC;QAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7F,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAChG,OAAO,IAAI,iCAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED,iGAAiG;IACjG,gGAAgG;IAChG,kGAAkG;IAClG,oGAAoG;IAC5F,eAAe,CAAC,GAAY;QAChC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO,EAAE,CAAC;QACnC,gGAAgG;QAChG,KAAK,MAAM,KAAK,IAAI,GAAgC;YAAE,IAAI,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC;QAC1F,OAAO,GAAwB,CAAC;IACpC,CAAC;IAED;;;;;;OAMG;IACH,oFAAoF;IAC5E,uBAAuB,CAAC,OAAgC;QAC5D,IAAI,OAAO,CAAC,uBAAuB,CAAC,KAAK,SAAS,EAAE,CAAC;YACjD,OAAO,CAAC,0BAA0B,CAAC,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;QAC3E,CAAC;QACD,IAAI,OAAO,CAAC,0BAA0B,CAAC,KAAK,SAAS,EAAE,CAAC;YACpD,OAAO,CAAC,yBAAyB,CAAC,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;QAC7E,CAAC;IACL,CAAC;IAEO,yBAAyB;IAC7B,4FAA4F;IAC5F,QAAiD,EACjD,QAAkB;QAElB,MAAM,KAAK,GAAG,IAAI,2CAAoB,EAAE,CAAC;QACzC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACtC,4EAA4E;YAC3E,KAAiC,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC5D,CAAC;QACD,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC1B,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,uBAAuB,CAAC,OAAuB;QACnD,MAAM,GAAG,GAAmB,EAAE,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACrC,GAAG,CAAC,uBAAuB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED,iGAAiG;IACzF,wBAAwB,CAAC,MAAgB;QAC7C,OAAO,CACH,6BAA6B,MAAM,CAAC,MAAM,iDAAiD;YAC3F,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YACtC,yFAAyF;YACzF,6FAA6F;YAC7F,2FAA2F;YAC3F,sFAAsF;YACtF,6FAA6F;YAC7F,qFAAqF;YACrF,sGAAsG;YACtG,gGAAgG;YAChG,uFAAuF,CAC1F,CAAC;IACN,CAAC;CACJ,CAAA;AA3LY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAEI,wBAAU;GAD1C,YAAY,CA2LxB;AAED,kGAAkG;AAClG,8FAA8F;AAC9F,MAAM,eAAe,GAAG,IAAI,YAAY,CAAC,IAAI,wBAAU,EAAE,CAAC,CAAC;AAE3D,2IAA2I;AAC3I,SAAgB,eAAe,CAAC,GAAW;IACvC,OAAO,eAAe,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;AAChD,CAAC","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,
|
|
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`, `
|
|
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,
|
|
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.
|
|
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),
|
|
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,
|
|
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"]}
|
package/src/rule-configs.d.ts
CHANGED
|
@@ -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
|
};
|
package/src/rule-configs.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
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
|
-
//
|
|
62
|
-
//
|
|
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
|
-
|
|
72
|
-
|
|
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),
|
|
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),
|
|
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),
|
|
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 `
|
|
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.
|