@webpieces/rules-config 0.3.355 → 0.3.357

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.3.355",
3
+ "version": "0.3.357",
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",
@@ -14,6 +14,11 @@
14
14
  "README.md"
15
15
  ],
16
16
  "dependencies": {
17
+ "@webpieces/core-context": "0.3.357",
18
+ "@webpieces/core-util": "0.3.357",
19
+ "@inversifyjs/binding-decorators": "1.1.5",
20
+ "inversify": "7.10.4",
21
+ "reflect-metadata": "0.2.2",
17
22
  "minimatch": "10.0.1"
18
23
  },
19
24
  "author": "Dean Hiller",
@@ -9,6 +9,16 @@ export interface RawConfigFile {
9
9
  rulesDir?: string[];
10
10
  'pr-gate'?: unknown;
11
11
  }
12
+ /**
13
+ * Locates + reads webpieces.config.json. `@provideSingleton` so it can be injected into the config
14
+ * loader and appear in the rules-config DI design.
15
+ */
16
+ export declare class ConfigFile {
17
+ /** Walk up from `startDir` looking for webpieces.config.json. Returns its absolute path or null. */
18
+ findConfigFile(startDir: string): string | null;
19
+ /** Read + JSON.parse webpieces.config.json, surfacing parse failures as a readable InformAiError. */
20
+ readRawConfig(configPath: string): RawConfigFile;
21
+ }
12
22
  /**
13
23
  * Walk up from `startDir` looking for webpieces.config.json. Returns its absolute path or null.
14
24
  */
@@ -1,43 +1,67 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CONFIG_FILENAME = void 0;
3
+ exports.ConfigFile = exports.CONFIG_FILENAME = void 0;
4
4
  exports.findConfigFile = findConfigFile;
5
5
  exports.readRawConfig = readRawConfig;
6
6
  const tslib_1 = require("tslib");
7
7
  const fs = tslib_1.__importStar(require("fs"));
8
8
  const path = tslib_1.__importStar(require("path"));
9
+ const core_context_1 = require("@webpieces/core-context");
10
+ const inversify_1 = require("inversify");
9
11
  const inform_ai_error_1 = require("./inform-ai-error");
10
12
  const to_error_1 = require("./to-error");
11
13
  exports.CONFIG_FILENAME = 'webpieces.config.json';
14
+ /**
15
+ * Locates + reads webpieces.config.json. `@provideSingleton` so it can be injected into the config
16
+ * loader and appear in the rules-config DI design.
17
+ */
18
+ let ConfigFile = class ConfigFile {
19
+ /** Walk up from `startDir` looking for webpieces.config.json. Returns its absolute path or null. */
20
+ findConfigFile(startDir) {
21
+ let dir = startDir;
22
+ while (true) {
23
+ const primary = path.join(dir, exports.CONFIG_FILENAME);
24
+ if (fs.existsSync(primary))
25
+ return primary;
26
+ const parent = path.dirname(dir);
27
+ if (parent === dir)
28
+ return null;
29
+ dir = parent;
30
+ }
31
+ }
32
+ /** Read + JSON.parse webpieces.config.json, surfacing parse failures as a readable InformAiError. */
33
+ readRawConfig(configPath) {
34
+ const raw = fs.readFileSync(configPath, 'utf8');
35
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
36
+ try {
37
+ return JSON.parse(raw);
38
+ }
39
+ catch (err) {
40
+ const error = (0, to_error_1.toError)(err);
41
+ throw new inform_ai_error_1.InformAiError(`webpieces.config.json has invalid JSON — fix the file, then retry.\n` +
42
+ `Parse error: ${error.message}\n` +
43
+ `File: ${configPath}`);
44
+ }
45
+ }
46
+ };
47
+ exports.ConfigFile = ConfigFile;
48
+ exports.ConfigFile = ConfigFile = tslib_1.__decorate([
49
+ (0, core_context_1.provideSingleton)(),
50
+ (0, inversify_1.injectable)()
51
+ ], ConfigFile);
52
+ // Temporary migration delegators — consumers migrate to injecting ConfigFile over follow-up PRs, then
53
+ // these free functions are removed. Declarations kept identical to the originals (unchanged lines).
54
+ const configFileSvc = new ConfigFile();
12
55
  /**
13
56
  * Walk up from `startDir` looking for webpieces.config.json. Returns its absolute path or null.
14
57
  */
15
58
  function findConfigFile(startDir) {
16
- let dir = startDir;
17
- while (true) {
18
- const primary = path.join(dir, exports.CONFIG_FILENAME);
19
- if (fs.existsSync(primary))
20
- return primary;
21
- const parent = path.dirname(dir);
22
- if (parent === dir)
23
- return null;
24
- dir = parent;
25
- }
59
+ return configFileSvc.findConfigFile(startDir);
26
60
  }
27
61
  /**
28
62
  * Read + JSON.parse webpieces.config.json, surfacing parse failures as a readable InformAiError.
29
63
  */
30
64
  function readRawConfig(configPath) {
31
- const raw = fs.readFileSync(configPath, 'utf8');
32
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
33
- try {
34
- return JSON.parse(raw);
35
- }
36
- catch (err) {
37
- const error = (0, to_error_1.toError)(err);
38
- throw new inform_ai_error_1.InformAiError(`webpieces.config.json has invalid JSON — fix the file, then retry.\n` +
39
- `Parse error: ${error.message}\n` +
40
- `File: ${configPath}`);
41
- }
65
+ return configFileSvc.readRawConfig(configPath);
42
66
  }
43
67
  //# sourceMappingURL=config-file.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-file.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/config-file.ts"],"names":[],"mappings":";;;AAuCA,wCASC;AAKD,sCAaC;;AAlED,+CAAyB;AACzB,mDAA6B;AAE7B,uDAAkD;AAClD,yCAAqC;AAExB,QAAA,eAAe,GAAG,uBAAuB,CAAC;AA8BvD;;GAEG;AACH,SAAgB,cAAc,CAAC,QAAgB;IAC3C,IAAI,GAAG,GAAG,QAAQ,CAAC;IACnB,OAAO,IAAI,EAAE,CAAC;QACV,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,uBAAe,CAAC,CAAC;QAChD,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAChC,GAAG,GAAG,MAAM,CAAC;IACjB,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAgB,aAAa,CAAC,UAAkB;IAC5C,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAChD,8DAA8D;IAC9D,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;IAC5C,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,IAAI,+BAAa,CACnB,sEAAsE;YACtE,gBAAgB,KAAK,CAAC,OAAO,IAAI;YACjC,SAAS,UAAU,EAAE,CACxB,CAAC;IACN,CAAC;AACL,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\nexport const CONFIG_FILENAME = 'webpieces.config.json';\n\n// Raw shape of webpieces.config.json as parsed from JSON, before validation/typing.\n// - `rules` — code-style validators (scope edit/file).\n// - `hookGuards` — git/PR/branch protection guards (scope bash).\n// - `commands` — gated command config the guards point at; `pr-gate` lives inside it. Carried as\n// opaque JSON because its nested `gates` array can't be expressed in the FieldDef\n// schema; validated structurally by validateCommandsSection.\n// - `pr-gate` — DEPRECATED top-level block (pre-migration layout). Read only as a back-compat\n// fallback / to emit a \"move it under commands\" migration error.\n// webpieces-disable no-any-unknown -- consumer JSON config has opaque rule option values\nexport interface RawConfigFile {\n extends?: string;\n rules?: Record<string, Record<string, unknown>>;\n hookGuards?: Record<string, Record<string, unknown>>;\n // webpieces-disable no-any-unknown -- opaque commands JSON, validated by validateCommandsSection\n commands?: unknown;\n // REQUIRED top-level block: two glob lists that suppress hook enforcement per file path.\n // Opaque here (validated structurally by validateExcludePaths, then parsed into ExcludePaths).\n // webpieces-disable no-any-unknown -- opaque excludePaths JSON, validated by validateExcludePaths\n excludePaths?: unknown;\n // REQUIRED top-level array of client-authored content guards (regex patterns + message + scoping).\n // Opaque here; validated structurally by validateMatchRulesSection, then parsed into MatchRuleConfig[].\n // webpieces-disable no-any-unknown -- opaque match-rules JSON, validated by validateMatchRulesSection\n 'match-rules'?: unknown;\n rulesDir?: string[];\n // webpieces-disable no-any-unknown -- DEPRECATED top-level pr-gate, migrated under `commands`\n 'pr-gate'?: unknown;\n}\n\n/**\n * Walk up from `startDir` looking for webpieces.config.json. Returns its absolute path or null.\n */\nexport function findConfigFile(startDir: string): string | null {\n let dir = startDir;\n while (true) {\n const primary = path.join(dir, CONFIG_FILENAME);\n if (fs.existsSync(primary)) return primary;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * Read + JSON.parse webpieces.config.json, surfacing parse failures as a readable InformAiError.\n */\nexport function readRawConfig(configPath: string): RawConfigFile {\n const raw = fs.readFileSync(configPath, 'utf8');\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(raw) as RawConfigFile;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(\n `webpieces.config.json has invalid JSON — fix the file, then retry.\\n` +\n `Parse error: ${error.message}\\n` +\n `File: ${configPath}`,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"config-file.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/config-file.ts"],"names":[],"mappings":";;;AAiFA,wCAEC;AAKD,sCAEC;;AA1FD,+CAAyB;AACzB,mDAA6B;AAC7B,0DAA2D;AAC3D,yCAAuC;AAEvC,uDAAkD;AAClD,yCAAqC;AAExB,QAAA,eAAe,GAAG,uBAAuB,CAAC;AA8BvD;;;GAGG;AAGI,IAAM,UAAU,GAAhB,MAAM,UAAU;IACnB,oGAAoG;IACpG,cAAc,CAAC,QAAgB;QAC3B,IAAI,GAAG,GAAG,QAAQ,CAAC;QACnB,OAAO,IAAI,EAAE,CAAC;YACV,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,uBAAe,CAAC,CAAC;YAChD,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;gBAAE,OAAO,OAAO,CAAC;YAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,MAAM,KAAK,GAAG;gBAAE,OAAO,IAAI,CAAC;YAChC,GAAG,GAAG,MAAM,CAAC;QACjB,CAAC;IACL,CAAC;IAED,qGAAqG;IACrG,aAAa,CAAC,UAAkB;QAC5B,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAChD,8DAA8D;QAC9D,IAAI,CAAC;YACD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;QAC5C,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,MAAM,IAAI,+BAAa,CACnB,sEAAsE;gBACtE,gBAAgB,KAAK,CAAC,OAAO,IAAI;gBACjC,SAAS,UAAU,EAAE,CACxB,CAAC;QACN,CAAC;IACL,CAAC;CACJ,CAAA;AA5BY,gCAAU;qBAAV,UAAU;IAFtB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;GACA,UAAU,CA4BtB;AAED,sGAAsG;AACtG,oGAAoG;AACpG,MAAM,aAAa,GAAG,IAAI,UAAU,EAAE,CAAC;AAEvC;;GAEG;AACH,SAAgB,cAAc,CAAC,QAAgB;IAC3C,OAAO,aAAa,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,SAAgB,aAAa,CAAC,UAAkB;IAC5C,OAAO,aAAa,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;AACnD,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { provideSingleton } from '@webpieces/core-context';\nimport { injectable } from 'inversify';\n\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\nexport const CONFIG_FILENAME = 'webpieces.config.json';\n\n// Raw shape of webpieces.config.json as parsed from JSON, before validation/typing.\n// - `rules` — code-style validators (scope edit/file).\n// - `hookGuards` — git/PR/branch protection guards (scope bash).\n// - `commands` — gated command config the guards point at; `pr-gate` lives inside it. Carried as\n// opaque JSON because its nested `gates` array can't be expressed in the FieldDef\n// schema; validated structurally by validateCommandsSection.\n// - `pr-gate` — DEPRECATED top-level block (pre-migration layout). Read only as a back-compat\n// fallback / to emit a \"move it under commands\" migration error.\n// webpieces-disable no-any-unknown -- consumer JSON config has opaque rule option values\nexport interface RawConfigFile {\n extends?: string;\n rules?: Record<string, Record<string, unknown>>;\n hookGuards?: Record<string, Record<string, unknown>>;\n // webpieces-disable no-any-unknown -- opaque commands JSON, validated by validateCommandsSection\n commands?: unknown;\n // REQUIRED top-level block: two glob lists that suppress hook enforcement per file path.\n // Opaque here (validated structurally by validateExcludePaths, then parsed into ExcludePaths).\n // webpieces-disable no-any-unknown -- opaque excludePaths JSON, validated by validateExcludePaths\n excludePaths?: unknown;\n // REQUIRED top-level array of client-authored content guards (regex patterns + message + scoping).\n // Opaque here; validated structurally by validateMatchRulesSection, then parsed into MatchRuleConfig[].\n // webpieces-disable no-any-unknown -- opaque match-rules JSON, validated by validateMatchRulesSection\n 'match-rules'?: unknown;\n rulesDir?: string[];\n // webpieces-disable no-any-unknown -- DEPRECATED top-level pr-gate, migrated under `commands`\n 'pr-gate'?: unknown;\n}\n\n/**\n * Locates + reads webpieces.config.json. `@provideSingleton` so it can be injected into the config\n * loader and appear in the rules-config DI design.\n */\n@provideSingleton()\n@injectable()\nexport class ConfigFile {\n /** Walk up from `startDir` looking for webpieces.config.json. Returns its absolute path or null. */\n findConfigFile(startDir: string): string | null {\n let dir = startDir;\n while (true) {\n const primary = path.join(dir, CONFIG_FILENAME);\n if (fs.existsSync(primary)) return primary;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n }\n\n /** Read + JSON.parse webpieces.config.json, surfacing parse failures as a readable InformAiError. */\n readRawConfig(configPath: string): RawConfigFile {\n const raw = fs.readFileSync(configPath, 'utf8');\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(raw) as RawConfigFile;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(\n `webpieces.config.json has invalid JSON — fix the file, then retry.\\n` +\n `Parse error: ${error.message}\\n` +\n `File: ${configPath}`,\n );\n }\n }\n}\n\n// Temporary migration delegators — consumers migrate to injecting ConfigFile over follow-up PRs, then\n// these free functions are removed. Declarations kept identical to the originals (unchanged lines).\nconst configFileSvc = new ConfigFile();\n\n/**\n * Walk up from `startDir` looking for webpieces.config.json. Returns its absolute path or null.\n */\nexport function findConfigFile(startDir: string): string | null {\n return configFileSvc.findConfigFile(startDir);\n}\n\n/**\n * Read + JSON.parse webpieces.config.json, surfacing parse failures as a readable InformAiError.\n */\nexport function readRawConfig(configPath: string): RawConfigFile {\n return configFileSvc.readRawConfig(configPath);\n}\n"]}
package/src/index.d.ts CHANGED
@@ -4,13 +4,14 @@ export { RuleFailError } from './rule-fail-error';
4
4
  export { CliExitError } from './cli-exit-error';
5
5
  export { runMain } from './run-main';
6
6
  export { toError } from './to-error';
7
- export { loadAndValidate, LoadedConfig } from './load-config';
8
- export { findConfigFile, CONFIG_FILENAME } from './config-file';
7
+ export { loadAndValidate, LoadedConfig, ConfigLoader } from './load-config';
8
+ export { findConfigFile, CONFIG_FILENAME, ConfigFile } from './config-file';
9
9
  export { RepoRootFinder, INSTRUCT_AI_DIR } from './repo-root';
10
+ export { RulesConfigDesign } from './rules-config-design';
10
11
  export { ExcludePaths } from './exclude-hook-paths';
11
12
  export { isPathExcluded } from './exclude-paths';
12
13
  export { defaultRules, defaultRulesDir } from './default-rules';
13
- export { loadTemplate, writeTemplateIfMissing, writeTemplate } from './load-template';
14
+ export { loadTemplate, writeTemplateIfMissing, writeTemplate, TemplateWriter } from './load-template';
14
15
  export { validateWebpiecesConfig, validatePrGateSection, validateSectionPlacement, validateCommandsSection, validateExcludePaths, validateMatchRulesSection, allRuleNames } from './validate-config';
15
16
  export { MatchRuleConfig, MatchRuleViolation, findMatchRuleViolations, isMatchRuleAllowedPath, compileMatchRulePatterns, renderMatchRuleMessage, DEFAULT_MATCH_RULES, } from './match-rules-config';
16
17
  export type { ConfigSection } from './sections';
package/src/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WEBPIECES_DISABLE = exports.AbstractRule = exports.isNewOrModified = exports.hasChangesInRange = exports.findNewMethodSignaturesInDiff = exports.getChangedLineNumbers = exports.getFileDiff = exports.getChangedFiles = exports.resolveBase = exports.detectBase = exports.getCurrentBranch = exports.shouldSkipRule = exports.FieldDef = exports.sectionForRule = exports.isHookGuard = exports.HOOK_GUARD_NAMES = exports.DEFAULT_MATCH_RULES = exports.renderMatchRuleMessage = exports.compileMatchRulePatterns = exports.isMatchRuleAllowedPath = exports.findMatchRuleViolations = exports.MatchRuleViolation = exports.MatchRuleConfig = exports.allRuleNames = exports.validateMatchRulesSection = exports.validateExcludePaths = exports.validateCommandsSection = exports.validateSectionPlacement = exports.validatePrGateSection = exports.validateWebpiecesConfig = exports.writeTemplate = exports.writeTemplateIfMissing = exports.loadTemplate = exports.defaultRulesDir = exports.defaultRules = exports.isPathExcluded = exports.ExcludePaths = exports.INSTRUCT_AI_DIR = exports.RepoRootFinder = exports.CONFIG_FILENAME = exports.findConfigFile = exports.LoadedConfig = exports.loadAndValidate = exports.toError = exports.runMain = exports.CliExitError = exports.RuleFailError = exports.InformAiError = exports.ResolvedRuleConfig = exports.ResolvedConfig = void 0;
4
- exports.THROW_CAUSE_MODES = exports.DIRECT_API_RESOLVER_MODES = exports.PRISMA_CONVERTER_MODES = exports.PRISMA_DTOS_MODES = exports.PROJECT_MODES = exports.MODIFIED_CODE_MODES = exports.INLINE_TYPE_MODES = exports.RETURN_TYPE_MODES = exports.FILE_LIMIT_MODES = exports.METHOD_LIMIT_MODES = exports.BaseRuleConfig = exports.ValidateTsInSrcConfig = exports.NoJsFilesConfig = exports.DiGraphConfig = exports.NxWiringConfig = exports.RuntimeArchitectureConfig = exports.NoFileImportCyclesConfig = exports.FeatureBranchGuardConfig = exports.RedirectHowToMergeMainConfig = exports.PrMergeGuardConfig = exports.MergeInProgressGuardConfig = exports.PrCreationOrPushGuardConfig = exports.BranchCreationGuardConfig = exports.RoleTagConfig = exports.FrameworkTagConfig = exports.InjectAnnotationNotNeededForConcreteClassConfig = exports.NoFunctionOutsideClassConfig = exports.NoProcessExitOutsideMainConfig = exports.NoSymbolDiTokensConfig = exports.AngularNoDirectApiInResolverConfig = exports.ThrowCauseRequiredConfig = exports.CatchErrorPatternConfig = exports.NoUnmanagedExceptionsConfig = exports.NoDestructureConfig = exports.PrismaConverterConfig = exports.PrismaValidateDtosConfig = exports.NoImplicitAnyConfig = exports.NoAnyUnknownConfig = exports.NoInlineTypeLiteralsConfig = exports.RequireReturnTypeConfig = exports.MaxFileLinesConfig = exports.MaxMethodLinesConfig = exports.WebpiecesRulesConfig = exports.MERGE_EXPLANATION_FILE = exports.MERGE_IN_PROGRESS_FILE = exports.PR_REVIEW_DIR = exports.MERGE_INFO_DIR = exports.WEBPIECES_TMP_DIR = exports.hasDisable = exports.RULE_NAMES = void 0;
5
- exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = exports.buildCommandsConfig = exports.CommandsConfig = exports.logBranchMutation = exports.branchMutationLogPath = exports.BranchMutationEvent = exports.squashRecoverySteps = exports.stampCleanMainSyncStatus = exports.computeMainSyncStatus = exports.finishedLock = exports.inProcessLock = exports.isRefreshInProgress = exports.isLockStale = exports.writeMainSyncLock = exports.readMainSyncLock = exports.writeMainSyncStatus = exports.readMainSyncStatus = exports.mainSyncLockPath = exports.mainSyncStatusPath = exports.DEFAULT_HANG_TIMEOUT_MINUTES = exports.MainSyncLock = exports.MainSyncStatus = exports.reviewJsonSchemaHint = exports.reviewJsonPath = exports.prDirFor = exports.loadReviewJson = exports.ReviewJson = exports.buildPrGateConfig = exports.defaultPrGateConfig = exports.defaultGates = exports.PrGateConfig = exports.GateDefinition = exports.VALIDATE_TS_MODES = exports.STRUCTURAL_MODES = exports.ON_OFF_MODES = void 0;
3
+ exports.findNewMethodSignaturesInDiff = exports.getChangedLineNumbers = exports.getFileDiff = exports.getChangedFiles = exports.resolveBase = exports.detectBase = exports.getCurrentBranch = exports.shouldSkipRule = exports.FieldDef = exports.sectionForRule = exports.isHookGuard = exports.HOOK_GUARD_NAMES = exports.DEFAULT_MATCH_RULES = exports.renderMatchRuleMessage = exports.compileMatchRulePatterns = exports.isMatchRuleAllowedPath = exports.findMatchRuleViolations = exports.MatchRuleViolation = exports.MatchRuleConfig = exports.allRuleNames = exports.validateMatchRulesSection = exports.validateExcludePaths = exports.validateCommandsSection = exports.validateSectionPlacement = exports.validatePrGateSection = exports.validateWebpiecesConfig = exports.TemplateWriter = exports.writeTemplate = exports.writeTemplateIfMissing = exports.loadTemplate = exports.defaultRulesDir = exports.defaultRules = exports.isPathExcluded = exports.ExcludePaths = exports.RulesConfigDesign = exports.INSTRUCT_AI_DIR = exports.RepoRootFinder = exports.ConfigFile = exports.CONFIG_FILENAME = exports.findConfigFile = exports.ConfigLoader = exports.LoadedConfig = exports.loadAndValidate = exports.toError = exports.runMain = exports.CliExitError = exports.RuleFailError = exports.InformAiError = exports.ResolvedRuleConfig = exports.ResolvedConfig = void 0;
4
+ exports.PROJECT_MODES = exports.MODIFIED_CODE_MODES = exports.INLINE_TYPE_MODES = exports.RETURN_TYPE_MODES = exports.FILE_LIMIT_MODES = exports.METHOD_LIMIT_MODES = exports.BaseRuleConfig = exports.ValidateTsInSrcConfig = exports.NoJsFilesConfig = exports.DiGraphConfig = exports.NxWiringConfig = exports.RuntimeArchitectureConfig = exports.NoFileImportCyclesConfig = exports.FeatureBranchGuardConfig = exports.RedirectHowToMergeMainConfig = exports.PrMergeGuardConfig = exports.MergeInProgressGuardConfig = exports.PrCreationOrPushGuardConfig = exports.BranchCreationGuardConfig = exports.RoleTagConfig = exports.FrameworkTagConfig = exports.InjectAnnotationNotNeededForConcreteClassConfig = exports.NoFunctionOutsideClassConfig = exports.NoProcessExitOutsideMainConfig = exports.NoSymbolDiTokensConfig = exports.AngularNoDirectApiInResolverConfig = exports.ThrowCauseRequiredConfig = exports.CatchErrorPatternConfig = exports.NoUnmanagedExceptionsConfig = exports.NoDestructureConfig = exports.PrismaConverterConfig = exports.PrismaValidateDtosConfig = exports.NoImplicitAnyConfig = exports.NoAnyUnknownConfig = exports.NoInlineTypeLiteralsConfig = exports.RequireReturnTypeConfig = exports.MaxFileLinesConfig = exports.MaxMethodLinesConfig = exports.WebpiecesRulesConfig = exports.MERGE_EXPLANATION_FILE = exports.MERGE_IN_PROGRESS_FILE = exports.PR_REVIEW_DIR = exports.MERGE_INFO_DIR = exports.WEBPIECES_TMP_DIR = exports.hasDisable = exports.RULE_NAMES = exports.WEBPIECES_DISABLE = exports.AbstractRule = exports.isNewOrModified = exports.hasChangesInRange = void 0;
5
+ exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = exports.buildCommandsConfig = exports.CommandsConfig = exports.logBranchMutation = exports.branchMutationLogPath = exports.BranchMutationEvent = exports.squashRecoverySteps = exports.stampCleanMainSyncStatus = exports.computeMainSyncStatus = exports.finishedLock = exports.inProcessLock = exports.isRefreshInProgress = exports.isLockStale = exports.writeMainSyncLock = exports.readMainSyncLock = exports.writeMainSyncStatus = exports.readMainSyncStatus = exports.mainSyncLockPath = exports.mainSyncStatusPath = exports.DEFAULT_HANG_TIMEOUT_MINUTES = exports.MainSyncLock = exports.MainSyncStatus = exports.reviewJsonSchemaHint = exports.reviewJsonPath = exports.prDirFor = exports.loadReviewJson = exports.ReviewJson = exports.buildPrGateConfig = exports.defaultPrGateConfig = exports.defaultGates = exports.PrGateConfig = exports.GateDefinition = exports.VALIDATE_TS_MODES = exports.STRUCTURAL_MODES = exports.ON_OFF_MODES = exports.THROW_CAUSE_MODES = exports.DIRECT_API_RESOLVER_MODES = exports.PRISMA_CONVERTER_MODES = exports.PRISMA_DTOS_MODES = void 0;
6
6
  var types_1 = require("./types");
7
7
  Object.defineProperty(exports, "ResolvedConfig", { enumerable: true, get: function () { return types_1.ResolvedConfig; } });
8
8
  Object.defineProperty(exports, "ResolvedRuleConfig", { enumerable: true, get: function () { return types_1.ResolvedRuleConfig; } });
@@ -19,12 +19,16 @@ Object.defineProperty(exports, "toError", { enumerable: true, get: function () {
19
19
  var load_config_1 = require("./load-config");
20
20
  Object.defineProperty(exports, "loadAndValidate", { enumerable: true, get: function () { return load_config_1.loadAndValidate; } });
21
21
  Object.defineProperty(exports, "LoadedConfig", { enumerable: true, get: function () { return load_config_1.LoadedConfig; } });
22
+ Object.defineProperty(exports, "ConfigLoader", { enumerable: true, get: function () { return load_config_1.ConfigLoader; } });
22
23
  var config_file_1 = require("./config-file");
23
24
  Object.defineProperty(exports, "findConfigFile", { enumerable: true, get: function () { return config_file_1.findConfigFile; } });
24
25
  Object.defineProperty(exports, "CONFIG_FILENAME", { enumerable: true, get: function () { return config_file_1.CONFIG_FILENAME; } });
26
+ Object.defineProperty(exports, "ConfigFile", { enumerable: true, get: function () { return config_file_1.ConfigFile; } });
25
27
  var repo_root_1 = require("./repo-root");
26
28
  Object.defineProperty(exports, "RepoRootFinder", { enumerable: true, get: function () { return repo_root_1.RepoRootFinder; } });
27
29
  Object.defineProperty(exports, "INSTRUCT_AI_DIR", { enumerable: true, get: function () { return repo_root_1.INSTRUCT_AI_DIR; } });
30
+ var rules_config_design_1 = require("./rules-config-design");
31
+ Object.defineProperty(exports, "RulesConfigDesign", { enumerable: true, get: function () { return rules_config_design_1.RulesConfigDesign; } });
28
32
  var exclude_hook_paths_1 = require("./exclude-hook-paths");
29
33
  Object.defineProperty(exports, "ExcludePaths", { enumerable: true, get: function () { return exclude_hook_paths_1.ExcludePaths; } });
30
34
  var exclude_paths_1 = require("./exclude-paths");
@@ -36,6 +40,7 @@ var load_template_1 = require("./load-template");
36
40
  Object.defineProperty(exports, "loadTemplate", { enumerable: true, get: function () { return load_template_1.loadTemplate; } });
37
41
  Object.defineProperty(exports, "writeTemplateIfMissing", { enumerable: true, get: function () { return load_template_1.writeTemplateIfMissing; } });
38
42
  Object.defineProperty(exports, "writeTemplate", { enumerable: true, get: function () { return load_template_1.writeTemplate; } });
43
+ Object.defineProperty(exports, "TemplateWriter", { enumerable: true, get: function () { return load_template_1.TemplateWriter; } });
39
44
  var validate_config_1 = require("./validate-config");
40
45
  Object.defineProperty(exports, "validateWebpiecesConfig", { enumerable: true, get: function () { return validate_config_1.validateWebpiecesConfig; } });
41
46
  Object.defineProperty(exports, "validatePrGateSection", { enumerable: true, get: function () { return validate_config_1.validatePrGateSection; } });
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/index.ts"],"names":[],"mappings":";;;;;AAAA,iCAA0E;AAAjE,uGAAA,cAAc,OAAA;AAAE,2GAAA,kBAAkB,OAAA;AAC3C,qDAAkD;AAAzC,gHAAA,aAAa,OAAA;AACtB,qDAAkD;AAAzC,gHAAA,aAAa,OAAA;AACtB,mDAAgD;AAAvC,8GAAA,YAAY,OAAA;AACrB,uCAAqC;AAA5B,mGAAA,OAAO,OAAA;AAChB,uCAAqC;AAA5B,mGAAA,OAAO,OAAA;AAChB,6CAA8D;AAArD,8GAAA,eAAe,OAAA;AAAE,2GAAA,YAAY,OAAA;AACtC,6CAAgE;AAAvD,6GAAA,cAAc,OAAA;AAAE,8GAAA,eAAe,OAAA;AACxC,yCAA8D;AAArD,2GAAA,cAAc,OAAA;AAAE,4GAAA,eAAe,OAAA;AACxC,2DAAoD;AAA3C,kHAAA,YAAY,OAAA;AACrB,iDAAiD;AAAxC,+GAAA,cAAc,OAAA;AACvB,iDAAgE;AAAvD,6GAAA,YAAY,OAAA;AAAE,gHAAA,eAAe,OAAA;AACtC,iDAAsF;AAA7E,6GAAA,YAAY,OAAA;AAAE,uHAAA,sBAAsB,OAAA;AAAE,8GAAA,aAAa,OAAA;AAC5D,qDAAqM;AAA5L,0HAAA,uBAAuB,OAAA;AAAE,wHAAA,qBAAqB,OAAA;AAAE,2HAAA,wBAAwB,OAAA;AAAE,0HAAA,uBAAuB,OAAA;AAAE,uHAAA,oBAAoB,OAAA;AAAE,4HAAA,yBAAyB,OAAA;AAAE,+GAAA,YAAY,OAAA;AACzK,2DAQ8B;AAP1B,qHAAA,eAAe,OAAA;AACf,wHAAA,kBAAkB,OAAA;AAClB,6HAAA,uBAAuB,OAAA;AACvB,4HAAA,sBAAsB,OAAA;AACtB,8HAAA,wBAAwB,OAAA;AACxB,4HAAA,sBAAsB,OAAA;AACtB,yHAAA,mBAAmB,OAAA;AAGvB,uCAA2E;AAAlE,4GAAA,gBAAgB,OAAA;AAAE,uGAAA,WAAW,OAAA;AAAE,0GAAA,cAAc,OAAA;AACtD,yCAAuC;AAA9B,qGAAA,QAAQ,OAAA;AAEjB,yCAA+D;AAAtD,2GAAA,cAAc,OAAA;AAAE,6GAAA,gBAAgB,OAAA;AAEzC,2CASsB;AARlB,wGAAA,UAAU,OAAA;AACV,yGAAA,WAAW,OAAA;AACX,6GAAA,eAAe,OAAA;AACf,yGAAA,WAAW,OAAA;AACX,mHAAA,qBAAqB,OAAA;AACrB,2HAAA,6BAA6B,OAAA;AAC7B,+GAAA,iBAAiB,OAAA;AACjB,6GAAA,eAAe,OAAA;AAEnB,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AACrB,yCASqB;AARjB,8GAAA,iBAAiB,OAAA;AACjB,uGAAA,UAAU,OAAA;AACV,uGAAA,UAAU,OAAA;AACV,8GAAA,iBAAiB,OAAA;AACjB,2GAAA,cAAc,OAAA;AACd,0GAAA,aAAa,OAAA;AACb,mHAAA,sBAAsB,OAAA;AACtB,mHAAA,sBAAsB,OAAA;AAE1B,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAC7B,+CAiCwB;AAhCpB,oHAAA,oBAAoB,OAAA;AACpB,kHAAA,kBAAkB,OAAA;AAClB,uHAAA,uBAAuB,OAAA;AACvB,0HAAA,0BAA0B,OAAA;AAC1B,kHAAA,kBAAkB,OAAA;AAClB,mHAAA,mBAAmB,OAAA;AACnB,wHAAA,wBAAwB,OAAA;AACxB,qHAAA,qBAAqB,OAAA;AACrB,mHAAA,mBAAmB,OAAA;AACnB,2HAAA,2BAA2B,OAAA;AAC3B,uHAAA,uBAAuB,OAAA;AACvB,wHAAA,wBAAwB,OAAA;AACxB,kIAAA,kCAAkC,OAAA;AAClC,sHAAA,sBAAsB,OAAA;AACtB,8HAAA,8BAA8B,OAAA;AAC9B,4HAAA,4BAA4B,OAAA;AAC5B,+IAAA,+CAA+C,OAAA;AAC/C,kHAAA,kBAAkB,OAAA;AAClB,6GAAA,aAAa,OAAA;AACb,yHAAA,yBAAyB,OAAA;AACzB,2HAAA,2BAA2B,OAAA;AAC3B,0HAAA,0BAA0B,OAAA;AAC1B,kHAAA,kBAAkB,OAAA;AAClB,4HAAA,4BAA4B,OAAA;AAC5B,wHAAA,wBAAwB,OAAA;AACxB,wHAAA,wBAAwB,OAAA;AACxB,yHAAA,yBAAyB,OAAA;AACzB,8GAAA,cAAc,OAAA;AACd,6GAAA,aAAa,OAAA;AACb,+GAAA,eAAe,OAAA;AACf,qHAAA,qBAAqB,OAAA;AACrB,8GAAA,cAAc,OAAA;AAElB,wFAAwF;AACxF,+CAcwB;AAbpB,kHAAA,kBAAkB,OAAA;AAClB,gHAAA,gBAAgB,OAAA;AAChB,iHAAA,iBAAiB,OAAA;AACjB,iHAAA,iBAAiB,OAAA;AACjB,mHAAA,mBAAmB,OAAA;AACnB,6GAAA,aAAa,OAAA;AACb,iHAAA,iBAAiB,OAAA;AACjB,sHAAA,sBAAsB,OAAA;AACtB,yHAAA,yBAAyB,OAAA;AACzB,iHAAA,iBAAiB,OAAA;AACjB,4GAAA,YAAY,OAAA;AACZ,gHAAA,gBAAgB,OAAA;AAChB,iHAAA,iBAAiB,OAAA;AAiBrB,mDAM0B;AALtB,gHAAA,cAAc,OAAA;AACd,8GAAA,YAAY,OAAA;AACZ,8GAAA,YAAY,OAAA;AACZ,qHAAA,mBAAmB,OAAA;AACnB,mHAAA,iBAAiB,OAAA;AAErB,6CAMuB;AALnB,yGAAA,UAAU,OAAA;AACV,6GAAA,cAAc,OAAA;AACd,uGAAA,QAAQ,OAAA;AACR,6GAAA,cAAc,OAAA;AACd,mHAAA,oBAAoB,OAAA;AAExB,uDAiB4B;AAhBxB,kHAAA,cAAc,OAAA;AACd,gHAAA,YAAY,OAAA;AACZ,gIAAA,4BAA4B,OAAA;AAC5B,sHAAA,kBAAkB,OAAA;AAClB,oHAAA,gBAAgB,OAAA;AAChB,sHAAA,kBAAkB,OAAA;AAClB,uHAAA,mBAAmB,OAAA;AACnB,oHAAA,gBAAgB,OAAA;AAChB,qHAAA,iBAAiB,OAAA;AACjB,+GAAA,WAAW,OAAA;AACX,uHAAA,mBAAmB,OAAA;AACnB,iHAAA,aAAa,OAAA;AACb,gHAAA,YAAY,OAAA;AACZ,yHAAA,qBAAqB,OAAA;AACrB,4HAAA,wBAAwB,OAAA;AACxB,uHAAA,mBAAmB,OAAA;AAGvB,6DAI+B;AAH3B,0HAAA,mBAAmB,OAAA;AACnB,4HAAA,qBAAqB,OAAA;AACrB,wHAAA,iBAAiB,OAAA;AAErB,qDAK2B;AAJvB,iHAAA,cAAc,OAAA;AACd,sHAAA,mBAAmB,OAAA;AACnB,4HAAA,yBAAyB,OAAA;AACzB,iIAAA,8BAA8B,OAAA","sourcesContent":["export { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nexport { InformAiError } from './inform-ai-error';\nexport { RuleFailError } from './rule-fail-error';\nexport { CliExitError } from './cli-exit-error';\nexport { runMain } from './run-main';\nexport { toError } from './to-error';\nexport { loadAndValidate, LoadedConfig } from './load-config';\nexport { findConfigFile, CONFIG_FILENAME } from './config-file';\nexport { RepoRootFinder, INSTRUCT_AI_DIR } from './repo-root';\nexport { ExcludePaths } from './exclude-hook-paths';\nexport { isPathExcluded } from './exclude-paths';\nexport { defaultRules, defaultRulesDir } from './default-rules';\nexport { loadTemplate, writeTemplateIfMissing, writeTemplate } from './load-template';\nexport { validateWebpiecesConfig, validatePrGateSection, validateSectionPlacement, validateCommandsSection, validateExcludePaths, validateMatchRulesSection, allRuleNames } from './validate-config';\nexport {\n MatchRuleConfig,\n MatchRuleViolation,\n findMatchRuleViolations,\n isMatchRuleAllowedPath,\n compileMatchRulePatterns,\n renderMatchRuleMessage,\n DEFAULT_MATCH_RULES,\n} from './match-rules-config';\nexport type { ConfigSection } from './sections';\nexport { HOOK_GUARD_NAMES, isHookGuard, sectionForRule } from './sections';\nexport { FieldDef } from './field-def';\nexport type { SchemaShape } from './field-def';\nexport { shouldSkipRule, getCurrentBranch } from './skip-rule';\nexport type { SkipRuleResult } from './skip-rule';\nexport {\n detectBase,\n resolveBase,\n getChangedFiles,\n getFileDiff,\n getChangedLineNumbers,\n findNewMethodSignaturesInDiff,\n hasChangesInRange,\n isNewOrModified,\n} from './diff-scope';\nexport { AbstractRule } from './abstract-rule';\nexport {\n WEBPIECES_DISABLE,\n RULE_NAMES,\n hasDisable,\n WEBPIECES_TMP_DIR,\n MERGE_INFO_DIR,\n PR_REVIEW_DIR,\n MERGE_IN_PROGRESS_FILE,\n MERGE_EXPLANATION_FILE,\n} from './constants';\nexport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\nexport {\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig,\n ThrowCauseRequiredConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n InjectAnnotationNotNeededForConcreteClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n BranchCreationGuardConfig,\n PrCreationOrPushGuardConfig,\n MergeInProgressGuardConfig,\n PrMergeGuardConfig,\n RedirectHowToMergeMainConfig,\n FeatureBranchGuardConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NxWiringConfig,\n DiGraphConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n BaseRuleConfig,\n} from './rule-configs';\n// Mode unions + their value arrays — the single source of truth shared with code-rules.\nexport {\n METHOD_LIMIT_MODES,\n FILE_LIMIT_MODES,\n RETURN_TYPE_MODES,\n INLINE_TYPE_MODES,\n MODIFIED_CODE_MODES,\n PROJECT_MODES,\n PRISMA_DTOS_MODES,\n PRISMA_CONVERTER_MODES,\n DIRECT_API_RESOLVER_MODES,\n THROW_CAUSE_MODES,\n ON_OFF_MODES,\n STRUCTURAL_MODES,\n VALIDATE_TS_MODES,\n} from './rule-configs';\nexport type {\n MethodLimitMode,\n FileLimitMode,\n ReturnTypeMode,\n InlineTypeMode,\n ModifiedCodeMode,\n ProjectMode,\n PrismaValidateDtosMode,\n PrismaConverterMode,\n DirectApiResolverMode,\n ThrowCauseMode,\n OnOffMode,\n StructuralMode,\n ValidateTsMode,\n} from './rule-configs';\nexport {\n GateDefinition,\n PrGateConfig,\n defaultGates,\n defaultPrGateConfig,\n buildPrGateConfig,\n} from './pr-gate-config';\nexport {\n ReviewJson,\n loadReviewJson,\n prDirFor,\n reviewJsonPath,\n reviewJsonSchemaHint,\n} from './review-json';\nexport {\n MainSyncStatus,\n MainSyncLock,\n DEFAULT_HANG_TIMEOUT_MINUTES,\n mainSyncStatusPath,\n mainSyncLockPath,\n readMainSyncStatus,\n writeMainSyncStatus,\n readMainSyncLock,\n writeMainSyncLock,\n isLockStale,\n isRefreshInProgress,\n inProcessLock,\n finishedLock,\n computeMainSyncStatus,\n stampCleanMainSyncStatus,\n squashRecoverySteps,\n} from './main-sync-status';\nexport type { MutationVerb, MutationPhase } from './branch-mutation-log';\nexport {\n BranchMutationEvent,\n branchMutationLogPath,\n logBranchMutation,\n} from './branch-mutation-log';\nexport {\n CommandsConfig,\n buildCommandsConfig,\n DEFAULT_UPSERT_PR_COMMAND,\n DEFAULT_MERGE_COMPLETE_COMMAND,\n} from './commands-config';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["export { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nexport { InformAiError } from './inform-ai-error';\nexport { RuleFailError } from './rule-fail-error';\nexport { CliExitError } from './cli-exit-error';\nexport { runMain } from './run-main';\nexport { toError } from './to-error';\nexport { loadAndValidate, LoadedConfig, ConfigLoader } from './load-config';\nexport { findConfigFile, CONFIG_FILENAME, ConfigFile } from './config-file';\nexport { RepoRootFinder, INSTRUCT_AI_DIR } from './repo-root';\nexport { RulesConfigDesign } from './rules-config-design';\nexport { ExcludePaths } from './exclude-hook-paths';\nexport { isPathExcluded } from './exclude-paths';\nexport { defaultRules, defaultRulesDir } from './default-rules';\nexport { loadTemplate, writeTemplateIfMissing, writeTemplate, TemplateWriter } from './load-template';\nexport { validateWebpiecesConfig, validatePrGateSection, validateSectionPlacement, validateCommandsSection, validateExcludePaths, validateMatchRulesSection, allRuleNames } from './validate-config';\nexport {\n MatchRuleConfig,\n MatchRuleViolation,\n findMatchRuleViolations,\n isMatchRuleAllowedPath,\n compileMatchRulePatterns,\n renderMatchRuleMessage,\n DEFAULT_MATCH_RULES,\n} from './match-rules-config';\nexport type { ConfigSection } from './sections';\nexport { HOOK_GUARD_NAMES, isHookGuard, sectionForRule } from './sections';\nexport { FieldDef } from './field-def';\nexport type { SchemaShape } from './field-def';\nexport { shouldSkipRule, getCurrentBranch } from './skip-rule';\nexport type { SkipRuleResult } from './skip-rule';\nexport {\n detectBase,\n resolveBase,\n getChangedFiles,\n getFileDiff,\n getChangedLineNumbers,\n findNewMethodSignaturesInDiff,\n hasChangesInRange,\n isNewOrModified,\n} from './diff-scope';\nexport { AbstractRule } from './abstract-rule';\nexport {\n WEBPIECES_DISABLE,\n RULE_NAMES,\n hasDisable,\n WEBPIECES_TMP_DIR,\n MERGE_INFO_DIR,\n PR_REVIEW_DIR,\n MERGE_IN_PROGRESS_FILE,\n MERGE_EXPLANATION_FILE,\n} from './constants';\nexport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\nexport {\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig,\n ThrowCauseRequiredConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n InjectAnnotationNotNeededForConcreteClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n BranchCreationGuardConfig,\n PrCreationOrPushGuardConfig,\n MergeInProgressGuardConfig,\n PrMergeGuardConfig,\n RedirectHowToMergeMainConfig,\n FeatureBranchGuardConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NxWiringConfig,\n DiGraphConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n BaseRuleConfig,\n} from './rule-configs';\n// Mode unions + their value arrays — the single source of truth shared with code-rules.\nexport {\n METHOD_LIMIT_MODES,\n FILE_LIMIT_MODES,\n RETURN_TYPE_MODES,\n INLINE_TYPE_MODES,\n MODIFIED_CODE_MODES,\n PROJECT_MODES,\n PRISMA_DTOS_MODES,\n PRISMA_CONVERTER_MODES,\n DIRECT_API_RESOLVER_MODES,\n THROW_CAUSE_MODES,\n ON_OFF_MODES,\n STRUCTURAL_MODES,\n VALIDATE_TS_MODES,\n} from './rule-configs';\nexport type {\n MethodLimitMode,\n FileLimitMode,\n ReturnTypeMode,\n InlineTypeMode,\n ModifiedCodeMode,\n ProjectMode,\n PrismaValidateDtosMode,\n PrismaConverterMode,\n DirectApiResolverMode,\n ThrowCauseMode,\n OnOffMode,\n StructuralMode,\n ValidateTsMode,\n} from './rule-configs';\nexport {\n GateDefinition,\n PrGateConfig,\n defaultGates,\n defaultPrGateConfig,\n buildPrGateConfig,\n} from './pr-gate-config';\nexport {\n ReviewJson,\n loadReviewJson,\n prDirFor,\n reviewJsonPath,\n reviewJsonSchemaHint,\n} from './review-json';\nexport {\n MainSyncStatus,\n MainSyncLock,\n DEFAULT_HANG_TIMEOUT_MINUTES,\n mainSyncStatusPath,\n mainSyncLockPath,\n readMainSyncStatus,\n writeMainSyncStatus,\n readMainSyncLock,\n writeMainSyncLock,\n isLockStale,\n isRefreshInProgress,\n inProcessLock,\n finishedLock,\n computeMainSyncStatus,\n stampCleanMainSyncStatus,\n squashRecoverySteps,\n} from './main-sync-status';\nexport type { MutationVerb, MutationPhase } from './branch-mutation-log';\nexport {\n BranchMutationEvent,\n branchMutationLogPath,\n logBranchMutation,\n} from './branch-mutation-log';\nexport {\n CommandsConfig,\n buildCommandsConfig,\n DEFAULT_UPSERT_PR_COMMAND,\n DEFAULT_MERGE_COMPLETE_COMMAND,\n} from './commands-config';\n"]}
@@ -1,4 +1,5 @@
1
1
  import { CommandsConfig } from './commands-config';
2
+ import { ConfigFile } from './config-file';
2
3
  import { ExcludePaths } from './exclude-hook-paths';
3
4
  import { PrGateConfig } from './pr-gate-config';
4
5
  import { ResolvedConfig } from './types';
@@ -6,14 +7,7 @@ import { MatchRuleConfig } from './match-rules-config';
6
7
  import { WebpiecesRulesConfig } from './WebpiecesRulesConfig';
7
8
  /**
8
9
  * Everything a consumer might need from webpieces.config.json, produced from ONE parse + ONE
9
- * validation pass. Data-only (per CLAUDE.md, classes for data):
10
- * - `resolved` — Map-based view merged with defaultRules (nx executors).
11
- * - `rulesConfig` — typed WebpiecesRulesConfig (ai-hook-rules, code-rules); rules + hookGuards merged.
12
- * - `commands` — the `commands` section (gated commands + pr-gate).
13
- * - `prGate` — convenience alias of `commands.prGate` (pr-gate scripts).
14
- * - `excludePaths`— the required `excludePaths` block (per-category glob suppression lists).
15
- * - `matchRules` — the required `match-rules` array (client-authored content guards).
16
- * - `configPath` — absolute path, or null when no config file was found.
10
+ * validation pass. Data-only (per CLAUDE.md, classes for data).
17
11
  */
18
12
  export declare class LoadedConfig {
19
13
  readonly resolved: ResolvedConfig;
@@ -27,8 +21,24 @@ export declare class LoadedConfig {
27
21
  }
28
22
  /**
29
23
  * The single load+validate entry point for ALL consumers (ai-hook-rules, code-rules,
30
- * nx-webpieces-rules, pr-gate scripts). Reads webpieces.config.json once, validates BOTH the `rules`
31
- * map and the top-level `pr-gate` block, and throws one InformAiError listing every error. When no
32
- * config file is found it returns lenient empties/defaults (matching prior no-file behavior).
24
+ * nx-webpieces-rules, pr-gate scripts). `@provideSingleton` + injects {@link ConfigFile} so it appears
25
+ * in the rules-config DI design.
33
26
  */
27
+ export declare class ConfigLoader {
28
+ private readonly configFile;
29
+ constructor(configFile: ConfigFile);
30
+ /**
31
+ * Reads webpieces.config.json once, validates BOTH the `rules` map and the top-level `pr-gate`
32
+ * block, and throws one InformAiError listing every error. When no config file is found it returns
33
+ * lenient empties/defaults (matching prior no-file behavior).
34
+ */
35
+ loadAndValidate(cwd: string): LoadedConfig;
36
+ private applyCommandDefaults;
37
+ private mergeRule;
38
+ private parseExcludePaths;
39
+ private parseMatchRules;
40
+ private buildWebpiecesRulesConfig;
41
+ private normalizeDeprecatedKeys;
42
+ private formatConfigErrorsBanner;
43
+ }
34
44
  export declare function loadAndValidate(cwd: string): LoadedConfig;
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LoadedConfig = void 0;
3
+ exports.ConfigLoader = exports.LoadedConfig = void 0;
4
4
  exports.loadAndValidate = loadAndValidate;
5
+ const tslib_1 = require("tslib");
6
+ const core_context_1 = require("@webpieces/core-context");
7
+ const inversify_1 = require("inversify");
5
8
  const commands_config_1 = require("./commands-config");
6
9
  const config_file_1 = require("./config-file");
7
10
  const default_rules_1 = require("./default-rules");
@@ -10,83 +13,9 @@ const inform_ai_error_1 = require("./inform-ai-error");
10
13
  const types_1 = require("./types");
11
14
  const validate_config_1 = require("./validate-config");
12
15
  const WebpiecesRulesConfig_1 = require("./WebpiecesRulesConfig");
13
- // Inject the canonical command strings (from the `commands` section) as the DEFAULT for the guards
14
- // that surface them in their fix hints, so a project renames a command in one place. Only fills a
15
- // gap — an explicit per-guard override wins. Mutates the merged guard entries in place.
16
- function applyCommandDefaults(
17
- // webpieces-disable no-any-unknown -- opaque merged rule/guard map
18
- rules, commands) {
19
- const prCreation = rules['pr-creation-or-push-guard'];
20
- if (prCreation && prCreation['upsertPrCommand'] === undefined) {
21
- prCreation['upsertPrCommand'] = commands.upsertPr;
22
- }
23
- const mergeInProgress = rules['merge-in-progress-guard'];
24
- if (mergeInProgress && mergeInProgress['mergeCompleteCommand'] === undefined) {
25
- mergeInProgress['mergeCompleteCommand'] = commands.mergeComplete;
26
- }
27
- }
28
- // webpieces-disable no-any-unknown -- merging opaque option bags from config JSON
29
- function mergeRule(
30
- // webpieces-disable no-any-unknown -- opaque option bag
31
- baseRule,
32
- // webpieces-disable no-any-unknown -- opaque option bag
33
- overrideRule) {
34
- if (!baseRule && !overrideRule)
35
- return new types_1.ResolvedRuleConfig({ mode: 'OFF' });
36
- if (!baseRule)
37
- return new types_1.ResolvedRuleConfig(overrideRule);
38
- if (!overrideRule)
39
- return new types_1.ResolvedRuleConfig(baseRule);
40
- // webpieces-disable no-any-unknown -- building merged option bag
41
- const merged = {};
42
- for (const key of Object.keys(baseRule))
43
- merged[key] = baseRule[key];
44
- for (const key of Object.keys(overrideRule))
45
- merged[key] = overrideRule[key];
46
- return new types_1.ResolvedRuleConfig(merged);
47
- }
48
- // Parse the (already-validated) raw excludePaths block into the typed ExcludePaths. Defensive
49
- // defaults keep this total even though validateExcludePaths guarantees both string[] lists are set.
50
- // webpieces-disable no-any-unknown -- `raw` is opaque consumer JSON until narrowed here
51
- function parseExcludePaths(raw) {
52
- if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
53
- return new exclude_hook_paths_1.ExcludePaths([], []);
54
- // webpieces-disable no-any-unknown -- validateExcludePaths already proved both are string[]
55
- const s = raw;
56
- const rules = Array.isArray(s['rules']) ? s['rules'].filter(p => typeof p === 'string') : [];
57
- const guards = Array.isArray(s['guards']) ? s['guards'].filter(p => typeof p === 'string') : [];
58
- return new exclude_hook_paths_1.ExcludePaths(rules, guards);
59
- }
60
- // Parse the (already-validated) raw match-rules array into typed MatchRuleConfig[]. Each entry is a
61
- // plain object from JSON; the engines consume its fields only (no methods), so a structural cast is
62
- // sufficient. Defensive [] keeps this total even though validateMatchRulesSection ran first.
63
- // webpieces-disable no-any-unknown -- validated array; each entry cast to the typed MatchRuleConfig
64
- function parseMatchRules(raw) {
65
- if (!Array.isArray(raw))
66
- return [];
67
- return raw;
68
- }
69
- function buildWebpiecesRulesConfig(
70
- // webpieces-disable no-any-unknown -- JSON values are opaque until assigned to typed fields
71
- rawRules, rulesDir) {
72
- const typed = new WebpiecesRulesConfig_1.WebpiecesRulesConfig();
73
- for (const key of Object.keys(rawRules)) {
74
- // webpieces-disable no-any-unknown -- dynamic key assignment to typed class
75
- typed[key] = rawRules[key];
76
- }
77
- typed.rulesDir = rulesDir;
78
- return typed;
79
- }
80
16
  /**
81
17
  * Everything a consumer might need from webpieces.config.json, produced from ONE parse + ONE
82
- * validation pass. Data-only (per CLAUDE.md, classes for data):
83
- * - `resolved` — Map-based view merged with defaultRules (nx executors).
84
- * - `rulesConfig` — typed WebpiecesRulesConfig (ai-hook-rules, code-rules); rules + hookGuards merged.
85
- * - `commands` — the `commands` section (gated commands + pr-gate).
86
- * - `prGate` — convenience alias of `commands.prGate` (pr-gate scripts).
87
- * - `excludePaths`— the required `excludePaths` block (per-category glob suppression lists).
88
- * - `matchRules` — the required `match-rules` array (client-authored content guards).
89
- * - `configPath` — absolute path, or null when no config file was found.
18
+ * validation pass. Data-only (per CLAUDE.md, classes for data).
90
19
  */
91
20
  class LoadedConfig {
92
21
  resolved;
@@ -111,86 +40,163 @@ exports.LoadedConfig = LoadedConfig;
111
40
  // Config keys that were renamed. A project's webpieces.config.json may still use the OLD key (it can
112
41
  // legitimately lag the published rules-config by a release), so normalize any deprecated key to its
113
42
  // canonical name BEFORE validation/placement/loading — every downstream consumer then sees one name.
114
- // The old key stays accepted indefinitely; teams can flip to the new name whenever convenient.
115
43
  const DEPRECATED_RULE_ALIASES = {
116
44
  'pr-merge-cleanup': 'pr-merge-guard',
117
- // pr-creation-guard was broadened to also block a manual `git push` and renamed accordingly.
118
45
  'pr-creation-guard': 'pr-creation-or-push-guard',
119
46
  };
120
- function normalizeDeprecatedKeys(section) {
121
- const out = {};
122
- for (const key of Object.keys(section)) {
123
- out[DEPRECATED_RULE_ALIASES[key] ?? key] = section[key];
124
- }
125
- return out;
126
- }
127
- // Assemble the validation-failure banner. Most of these errors are version skew, not bad config: a dep
128
- // bump updated webpieces.config.json (+ package.json/lockfile) with new rule names/values, but this
129
- // checkout's node_modules was never re-installed, so the OLDER guard binary that is running rejects the
130
- // newer config. Deleting the flagged keys destroys valid config — so lead the AI to `pnpm install`
131
- // first. Both `pnpm install` (installer bypass) and edits to webpieces.config.json (fix-target bypass)
132
- // are ALWAYS allowed through the guard even while the config is invalid, so neither step can deadlock.
133
- function formatConfigErrorsBanner(errors) {
134
- return (`webpieces.config.json has ${errors.length} validation error(s) — fix ALL, then retry:\n\n` +
135
- errors.map(e => ` • ${e}`).join('\n') +
136
- `\n\n👉 FIX ORDER (do NOT start by deleting keys — that usually deletes VALID config):\n` +
137
- ` 1. Run \`pnpm install\`. It is ALWAYS allowed through the guard (installer bypass), even ` +
138
- `while this config is invalid. This is the #1 cause: your installed @webpieces guard is a ` +
139
- `release BEHIND webpieces.config.json (a dep bump updated the config + lockfile, but ` +
140
- `node_modules here was never re-installed), so the running validator doesn't know the newer ` +
141
- `rule names/values yet. \`pnpm install\` syncs node_modules to the pinned version.\n` +
142
- ` 2. Retry your command. If the errors are gone, you're DONE — do not touch webpieces.config.json.\n` +
143
- ` 3. ONLY if an error survives a fresh install is it a genuine typo / removed / renamed rule. ` +
144
- `Then edit webpieces.config.json (edits to it are ALWAYS allowed) to fix each • above.`);
145
- }
146
47
  /**
147
48
  * The single load+validate entry point for ALL consumers (ai-hook-rules, code-rules,
148
- * nx-webpieces-rules, pr-gate scripts). Reads webpieces.config.json once, validates BOTH the `rules`
149
- * map and the top-level `pr-gate` block, and throws one InformAiError listing every error. When no
150
- * config file is found it returns lenient empties/defaults (matching prior no-file behavior).
49
+ * nx-webpieces-rules, pr-gate scripts). `@provideSingleton` + injects {@link ConfigFile} so it appears
50
+ * in the rules-config DI design.
151
51
  */
152
- function loadAndValidate(cwd) {
153
- const configPath = (0, config_file_1.findConfigFile)(cwd);
154
- if (!configPath) {
155
- const emptyCommands = (0, commands_config_1.buildCommandsConfig)(undefined);
156
- return new LoadedConfig(new types_1.ResolvedConfig(new Map(), new Set(), [], null), new WebpiecesRulesConfig_1.WebpiecesRulesConfig(), emptyCommands, emptyCommands.prGate, new exclude_hook_paths_1.ExcludePaths([], []), [], null);
52
+ let ConfigLoader = class ConfigLoader {
53
+ configFile;
54
+ constructor(configFile) {
55
+ this.configFile = configFile;
56
+ }
57
+ /**
58
+ * Reads webpieces.config.json once, validates BOTH the `rules` map and the top-level `pr-gate`
59
+ * block, and throws one InformAiError listing every error. When no config file is found it returns
60
+ * lenient empties/defaults (matching prior no-file behavior).
61
+ */
62
+ // webpieces-disable max-lines-new-methods -- the single load+validate pass is one cohesive method
63
+ loadAndValidate(cwd) {
64
+ const configPath = this.configFile.findConfigFile(cwd);
65
+ if (!configPath) {
66
+ const emptyCommands = (0, commands_config_1.buildCommandsConfig)(undefined);
67
+ return new LoadedConfig(new types_1.ResolvedConfig(new Map(), new Set(), [], null), new WebpiecesRulesConfig_1.WebpiecesRulesConfig(), emptyCommands, emptyCommands.prGate, new exclude_hook_paths_1.ExcludePaths([], []), [], null);
68
+ }
69
+ const consumerConfig = this.configFile.readRawConfig(configPath);
70
+ const rulesSection = this.normalizeDeprecatedKeys(consumerConfig.rules || {});
71
+ const hookGuardsSection = this.normalizeDeprecatedKeys(consumerConfig.hookGuards || {});
72
+ const legacyPrGate = consumerConfig['pr-gate'];
73
+ // rules + hookGuards are validated/loaded as one flat name→config map (the runtime dispatches
74
+ // by each rule's own `scope`). Placement is enforced separately.
75
+ const overrideRules = { ...rulesSection, ...hookGuardsSection };
76
+ const rulesDir = consumerConfig.rulesDir ?? [];
77
+ const errors = [
78
+ ...(0, validate_config_1.validateWebpiecesConfig)(overrideRules, rulesDir.length > 0),
79
+ ...(0, validate_config_1.validateSectionPlacement)(rulesSection, hookGuardsSection),
80
+ ...(0, validate_config_1.validateCommandsSection)(consumerConfig.commands, legacyPrGate),
81
+ ...(0, validate_config_1.validateExcludePaths)(consumerConfig.excludePaths),
82
+ ...(0, validate_config_1.validateMatchRulesSection)(consumerConfig['match-rules']),
83
+ ];
84
+ if (errors.length > 0) {
85
+ throw new inform_ai_error_1.InformAiError(this.formatConfigErrorsBanner(errors));
86
+ }
87
+ const commands = (0, commands_config_1.buildCommandsConfig)(consumerConfig.commands, legacyPrGate);
88
+ this.applyCommandDefaults(overrideRules, commands);
89
+ const userConfiguredRuleNames = new Set(Object.keys(overrideRules));
90
+ const mergedRules = new Map();
91
+ const allRuleNames = new Set([
92
+ ...Object.keys(default_rules_1.defaultRules),
93
+ ...Object.keys(overrideRules),
94
+ ]);
95
+ for (const name of allRuleNames) {
96
+ mergedRules.set(name, this.mergeRule(default_rules_1.defaultRules[name], overrideRules[name]));
97
+ }
98
+ const resolved = new types_1.ResolvedConfig(mergedRules, userConfiguredRuleNames, rulesDir, configPath);
99
+ const rulesConfig = this.buildWebpiecesRulesConfig(overrideRules, rulesDir);
100
+ const excludePaths = this.parseExcludePaths(consumerConfig.excludePaths);
101
+ const matchRules = this.parseMatchRules(consumerConfig['match-rules']);
102
+ return new LoadedConfig(resolved, rulesConfig, commands, commands.prGate, excludePaths, matchRules, configPath);
103
+ }
104
+ // Inject the canonical command strings (from the `commands` section) as the DEFAULT for the guards
105
+ // that surface them in their fix hints. Only fills a gap — an explicit per-guard override wins.
106
+ applyCommandDefaults(
107
+ // webpieces-disable no-any-unknown -- opaque merged rule/guard map
108
+ rules, commands) {
109
+ const prCreation = rules['pr-creation-or-push-guard'];
110
+ if (prCreation && prCreation['upsertPrCommand'] === undefined) {
111
+ prCreation['upsertPrCommand'] = commands.upsertPr;
112
+ }
113
+ const mergeInProgress = rules['merge-in-progress-guard'];
114
+ if (mergeInProgress && mergeInProgress['mergeCompleteCommand'] === undefined) {
115
+ mergeInProgress['mergeCompleteCommand'] = commands.mergeComplete;
116
+ }
117
+ }
118
+ // webpieces-disable no-any-unknown -- merging opaque option bags from config JSON
119
+ mergeRule(
120
+ // webpieces-disable no-any-unknown -- opaque option bag
121
+ baseRule,
122
+ // webpieces-disable no-any-unknown -- opaque option bag
123
+ overrideRule) {
124
+ if (!baseRule && !overrideRule)
125
+ return new types_1.ResolvedRuleConfig({ mode: 'OFF' });
126
+ if (!baseRule)
127
+ return new types_1.ResolvedRuleConfig(overrideRule);
128
+ if (!overrideRule)
129
+ return new types_1.ResolvedRuleConfig(baseRule);
130
+ // webpieces-disable no-any-unknown -- building merged option bag
131
+ const merged = {};
132
+ for (const key of Object.keys(baseRule))
133
+ merged[key] = baseRule[key];
134
+ for (const key of Object.keys(overrideRule))
135
+ merged[key] = overrideRule[key];
136
+ return new types_1.ResolvedRuleConfig(merged);
157
137
  }
158
- const consumerConfig = (0, config_file_1.readRawConfig)(configPath);
159
- const rulesSection = normalizeDeprecatedKeys(consumerConfig.rules || {});
160
- const hookGuardsSection = normalizeDeprecatedKeys(consumerConfig.hookGuards || {});
161
- const legacyPrGate = consumerConfig['pr-gate'];
162
- // rules + hookGuards are validated/loaded as one flat name→config map (the runtime dispatches by
163
- // each rule's own `scope`, so it needs no section knowledge). Placement is enforced separately.
164
- const overrideRules = { ...rulesSection, ...hookGuardsSection };
165
- // A non-empty rulesDir means custom rules exist (loaded at runtime by ai-hook-rules), so a config
166
- // key with no built-in schema may be a legitimate custom rule. With no rulesDir, an unknown key is
167
- // a dead/typo'd entry and is rejected (validateWebpiecesConfig).
168
- const rulesDir = consumerConfig.rulesDir ?? [];
169
- const errors = [
170
- ...(0, validate_config_1.validateWebpiecesConfig)(overrideRules, rulesDir.length > 0),
171
- ...(0, validate_config_1.validateSectionPlacement)(rulesSection, hookGuardsSection),
172
- ...(0, validate_config_1.validateCommandsSection)(consumerConfig.commands, legacyPrGate),
173
- ...(0, validate_config_1.validateExcludePaths)(consumerConfig.excludePaths),
174
- ...(0, validate_config_1.validateMatchRulesSection)(consumerConfig['match-rules']),
175
- ];
176
- if (errors.length > 0) {
177
- throw new inform_ai_error_1.InformAiError(formatConfigErrorsBanner(errors));
138
+ // Parse the (already-validated) raw excludePaths block into the typed ExcludePaths.
139
+ // webpieces-disable no-any-unknown -- `raw` is opaque consumer JSON until narrowed here
140
+ parseExcludePaths(raw) {
141
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
142
+ return new exclude_hook_paths_1.ExcludePaths([], []);
143
+ // webpieces-disable no-any-unknown -- validateExcludePaths already proved both are string[]
144
+ const s = raw;
145
+ const rules = Array.isArray(s['rules']) ? s['rules'].filter(p => typeof p === 'string') : [];
146
+ const guards = Array.isArray(s['guards']) ? s['guards'].filter(p => typeof p === 'string') : [];
147
+ return new exclude_hook_paths_1.ExcludePaths(rules, guards);
178
148
  }
179
- const commands = (0, commands_config_1.buildCommandsConfig)(consumerConfig.commands, legacyPrGate);
180
- applyCommandDefaults(overrideRules, commands);
181
- const userConfiguredRuleNames = new Set(Object.keys(overrideRules));
182
- const mergedRules = new Map();
183
- const allRuleNames = new Set([
184
- ...Object.keys(default_rules_1.defaultRules),
185
- ...Object.keys(overrideRules),
186
- ]);
187
- for (const name of allRuleNames) {
188
- mergedRules.set(name, mergeRule(default_rules_1.defaultRules[name], overrideRules[name]));
149
+ // Parse the (already-validated) raw match-rules array into typed MatchRuleConfig[].
150
+ // webpieces-disable no-any-unknown -- validated array; each entry cast to the typed MatchRuleConfig
151
+ parseMatchRules(raw) {
152
+ if (!Array.isArray(raw))
153
+ return [];
154
+ return raw;
189
155
  }
190
- const resolved = new types_1.ResolvedConfig(mergedRules, userConfiguredRuleNames, rulesDir, configPath);
191
- const rulesConfig = buildWebpiecesRulesConfig(overrideRules, rulesDir);
192
- const excludePaths = parseExcludePaths(consumerConfig.excludePaths);
193
- const matchRules = parseMatchRules(consumerConfig['match-rules']);
194
- return new LoadedConfig(resolved, rulesConfig, commands, commands.prGate, excludePaths, matchRules, configPath);
156
+ buildWebpiecesRulesConfig(
157
+ // webpieces-disable no-any-unknown -- JSON values are opaque until assigned to typed fields
158
+ rawRules, rulesDir) {
159
+ const typed = new WebpiecesRulesConfig_1.WebpiecesRulesConfig();
160
+ for (const key of Object.keys(rawRules)) {
161
+ // webpieces-disable no-any-unknown -- dynamic key assignment to typed class
162
+ typed[key] = rawRules[key];
163
+ }
164
+ typed.rulesDir = rulesDir;
165
+ return typed;
166
+ }
167
+ normalizeDeprecatedKeys(section) {
168
+ const out = {};
169
+ for (const key of Object.keys(section)) {
170
+ out[DEPRECATED_RULE_ALIASES[key] ?? key] = section[key];
171
+ }
172
+ return out;
173
+ }
174
+ // Assemble the validation-failure banner. Most of these errors are version skew, not bad config.
175
+ formatConfigErrorsBanner(errors) {
176
+ return (`webpieces.config.json has ${errors.length} validation error(s) — fix ALL, then retry:\n\n` +
177
+ errors.map(e => ` • ${e}`).join('\n') +
178
+ `\n\n👉 FIX ORDER (do NOT start by deleting keys — that usually deletes VALID config):\n` +
179
+ ` 1. Run \`pnpm install\`. It is ALWAYS allowed through the guard (installer bypass), even ` +
180
+ `while this config is invalid. This is the #1 cause: your installed @webpieces guard is a ` +
181
+ `release BEHIND webpieces.config.json (a dep bump updated the config + lockfile, but ` +
182
+ `node_modules here was never re-installed), so the running validator doesn't know the newer ` +
183
+ `rule names/values yet. \`pnpm install\` syncs node_modules to the pinned version.\n` +
184
+ ` 2. Retry your command. If the errors are gone, you're DONE — do not touch webpieces.config.json.\n` +
185
+ ` 3. ONLY if an error survives a fresh install is it a genuine typo / removed / renamed rule. ` +
186
+ `Then edit webpieces.config.json (edits to it are ALWAYS allowed) to fix each • above.`);
187
+ }
188
+ };
189
+ exports.ConfigLoader = ConfigLoader;
190
+ exports.ConfigLoader = ConfigLoader = tslib_1.__decorate([
191
+ (0, core_context_1.provideSingleton)(),
192
+ (0, inversify_1.injectable)(),
193
+ tslib_1.__metadata("design:paramtypes", [config_file_1.ConfigFile])
194
+ ], ConfigLoader);
195
+ // Temporary migration delegator — consumers migrate to injecting ConfigLoader over follow-up PRs,
196
+ // then this free function is removed. The logic now lives in the injected ConfigLoader class.
197
+ const configLoaderSvc = new ConfigLoader(new config_file_1.ConfigFile());
198
+ // webpieces-disable no-function-outside-class -- temporary back-compat delegator to ConfigLoader; removed once all 118 consumers inject it
199
+ function loadAndValidate(cwd) {
200
+ return configLoaderSvc.loadAndValidate(cwd);
195
201
  }
196
202
  //# sourceMappingURL=load-config.js.map
@@ -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 { buildCommandsConfig, CommandsConfig } from './commands-config';\nimport { findConfigFile, readRawConfig } 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// Inject the canonical command strings (from the `commands` section) as the DEFAULT for the guards\n// that surface them in their fix hints, so a project renames a command in one place. Only fills a\n// gap — an explicit per-guard override wins. Mutates the merged guard entries in place.\nfunction applyCommandDefaults(\n // webpieces-disable no-any-unknown -- opaque merged rule/guard map\n rules: Record<string, Record<string, unknown>>,\n commands: CommandsConfig,\n): void {\n const prCreation = rules['pr-creation-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\nfunction mergeRule(\n // webpieces-disable no-any-unknown -- opaque option bag\n baseRule: Record<string, unknown> | undefined,\n // webpieces-disable no-any-unknown -- opaque option bag\n overrideRule: Record<string, unknown> | undefined,\n): ResolvedRuleConfig {\n if (!baseRule && !overrideRule) return new ResolvedRuleConfig({ mode: 'OFF' });\n if (!baseRule) return new ResolvedRuleConfig(overrideRule! as RuleOptions);\n if (!overrideRule) return new ResolvedRuleConfig(baseRule as RuleOptions);\n\n // webpieces-disable no-any-unknown -- building merged option bag\n const merged: Record<string, unknown> = {};\n for (const key of Object.keys(baseRule)) merged[key] = baseRule[key];\n for (const key of Object.keys(overrideRule)) merged[key] = overrideRule[key];\n return new ResolvedRuleConfig(merged as RuleOptions);\n}\n\n// Parse the (already-validated) raw excludePaths block into the typed ExcludePaths. Defensive\n// defaults keep this total even though validateExcludePaths guarantees both string[] lists are set.\n// webpieces-disable no-any-unknown -- `raw` is opaque consumer JSON until narrowed here\nfunction 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 is a\n// plain object from JSON; the engines consume its fields only (no methods), so a structural cast is\n// sufficient. Defensive [] keeps this total even though validateMatchRulesSection ran first.\n// webpieces-disable no-any-unknown -- validated array; each entry cast to the typed MatchRuleConfig\nfunction parseMatchRules(raw: unknown): MatchRuleConfig[] {\n if (!Array.isArray(raw)) return [];\n return raw as MatchRuleConfig[];\n}\n\nfunction buildWebpiecesRulesConfig(\n // webpieces-disable no-any-unknown -- JSON values are opaque until assigned to typed fields\n rawRules: Record<string, Record<string, unknown>>,\n rulesDir: string[],\n): WebpiecesRulesConfig {\n const typed = new WebpiecesRulesConfig();\n for (const key of Object.keys(rawRules)) {\n // webpieces-disable no-any-unknown -- dynamic key assignment to typed class\n (typed as Record<string, unknown>)[key] = rawRules[key];\n }\n typed.rulesDir = rulesDir;\n return typed;\n}\n\n/**\n * Everything a consumer might need from webpieces.config.json, produced from ONE parse + ONE\n * validation pass. Data-only (per CLAUDE.md, classes for data):\n * - `resolved` — Map-based view merged with defaultRules (nx executors).\n * - `rulesConfig` — typed WebpiecesRulesConfig (ai-hook-rules, code-rules); rules + hookGuards merged.\n * - `commands` — the `commands` section (gated commands + pr-gate).\n * - `prGate` — convenience alias of `commands.prGate` (pr-gate scripts).\n * - `excludePaths`— the required `excludePaths` block (per-category glob suppression lists).\n * - `matchRules` — the required `match-rules` array (client-authored content guards).\n * - `configPath` — absolute path, or null when no config file was found.\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.\n// The old key stays accepted indefinitely; teams can flip to the new name whenever convenient.\nconst DEPRECATED_RULE_ALIASES: Readonly<Record<string, string>> = {\n 'pr-merge-cleanup': 'pr-merge-guard',\n // pr-creation-guard was broadened to also block a manual `git push` and renamed accordingly.\n 'pr-creation-guard': 'pr-creation-or-push-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\nfunction 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: a dep\n// bump updated webpieces.config.json (+ package.json/lockfile) with new rule names/values, but this\n// checkout's node_modules was never re-installed, so the OLDER guard binary that is running rejects the\n// newer config. Deleting the flagged keys destroys valid config — so lead the AI to `pnpm install`\n// first. Both `pnpm install` (installer bypass) and edits to webpieces.config.json (fix-target bypass)\n// are ALWAYS allowed through the guard even while the config is invalid, so neither step can deadlock.\nfunction 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 * The single load+validate entry point for ALL consumers (ai-hook-rules, code-rules,\n * nx-webpieces-rules, pr-gate scripts). Reads webpieces.config.json once, validates BOTH the `rules`\n * map and the top-level `pr-gate` block, and throws one InformAiError listing every error. When no\n * config file is found it returns lenient empties/defaults (matching prior no-file behavior).\n */\nexport function loadAndValidate(cwd: string): LoadedConfig {\n const configPath = findConfigFile(cwd);\n if (!configPath) {\n const emptyCommands = buildCommandsConfig(undefined);\n return new LoadedConfig(\n new ResolvedConfig(new Map(), new Set(), [], null),\n new WebpiecesRulesConfig(),\n emptyCommands,\n emptyCommands.prGate,\n new ExcludePaths([], []),\n [],\n null,\n );\n }\n\n const consumerConfig = readRawConfig(configPath);\n const rulesSection = normalizeDeprecatedKeys(consumerConfig.rules || {});\n const hookGuardsSection = 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 by\n // each rule's own `scope`, so it needs no section knowledge). Placement is enforced separately.\n const overrideRules = { ...rulesSection, ...hookGuardsSection };\n\n // A non-empty rulesDir means custom rules exist (loaded at runtime by ai-hook-rules), so a config\n // key with no built-in schema may be a legitimate custom rule. With no rulesDir, an unknown key is\n // a dead/typo'd entry and is rejected (validateWebpiecesConfig).\n const rulesDir = consumerConfig.rulesDir ?? [];\n\n const errors = [\n ...validateWebpiecesConfig(overrideRules, rulesDir.length > 0),\n ...validateSectionPlacement(rulesSection, hookGuardsSection),\n ...validateCommandsSection(consumerConfig.commands, legacyPrGate),\n ...validateExcludePaths(consumerConfig.excludePaths),\n ...validateMatchRulesSection(consumerConfig['match-rules']),\n ];\n if (errors.length > 0) {\n throw new InformAiError(formatConfigErrorsBanner(errors));\n }\n const commands = buildCommandsConfig(consumerConfig.commands, legacyPrGate);\n applyCommandDefaults(overrideRules, commands);\n\n const userConfiguredRuleNames = new Set(Object.keys(overrideRules));\n const mergedRules = new Map<string, ResolvedRuleConfig>();\n const allRuleNames = new Set([\n ...Object.keys(defaultRules),\n ...Object.keys(overrideRules),\n ]);\n for (const name of allRuleNames) {\n mergedRules.set(name, mergeRule(defaultRules[name], overrideRules[name]));\n }\n const resolved = new ResolvedConfig(mergedRules, userConfiguredRuleNames, rulesDir, configPath);\n\n const rulesConfig = buildWebpiecesRulesConfig(overrideRules, rulesDir);\n const excludePaths = parseExcludePaths(consumerConfig.excludePaths);\n const matchRules = parseMatchRules(consumerConfig['match-rules']);\n\n return new LoadedConfig(resolved, rulesConfig, commands, commands.prGate, excludePaths, matchRules, configPath);\n}\n"]}
1
+ {"version":3,"file":"load-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { provideSingleton } from '@webpieces/core-context';\nimport { injectable } 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};\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). `@provideSingleton` + injects {@link ConfigFile} so it appears\n * in the rules-config DI design.\n */\n@provideSingleton()\n@injectable()\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 const errors = [\n ...validateWebpiecesConfig(overrideRules, rulesDir.length > 0),\n ...validateSectionPlacement(rulesSection, hookGuardsSection),\n ...validateCommandsSection(consumerConfig.commands, legacyPrGate),\n ...validateExcludePaths(consumerConfig.excludePaths),\n ...validateMatchRulesSection(consumerConfig['match-rules']),\n ];\n if (errors.length > 0) {\n throw new InformAiError(this.formatConfigErrorsBanner(errors));\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[].\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"]}
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Writes the AI-facing instruct-ai template docs under `<workspaceRoot>/.webpieces/instruct-ai/`.
3
+ * `@provideSingleton` so it can be injected and appear in the rules-config DI design.
4
+ */
5
+ export declare class TemplateWriter {
6
+ loadTemplate(name: string): string;
7
+ writeTemplateIfMissing(workspaceRoot: string, name: string, instructDir?: string): void;
8
+ writeTemplate(workspaceRoot: string, name: string, instructDir?: string): string;
9
+ }
1
10
  export declare function loadTemplate(name: string): string;
2
11
  export declare function writeTemplateIfMissing(workspaceRoot: string, name: string, instructDir?: string): void;
3
12
  export declare function writeTemplate(workspaceRoot: string, name: string, instructDir?: string): string;
@@ -1,27 +1,52 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TemplateWriter = void 0;
3
4
  exports.loadTemplate = loadTemplate;
4
5
  exports.writeTemplateIfMissing = writeTemplateIfMissing;
5
6
  exports.writeTemplate = writeTemplate;
6
7
  const tslib_1 = require("tslib");
7
8
  const fs = tslib_1.__importStar(require("fs"));
8
9
  const path = tslib_1.__importStar(require("path"));
10
+ const core_context_1 = require("@webpieces/core-context");
11
+ const inversify_1 = require("inversify");
9
12
  const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
10
13
  const DEFAULT_INSTRUCT_DIR = '.webpieces/instruct-ai';
14
+ /**
15
+ * Writes the AI-facing instruct-ai template docs under `<workspaceRoot>/.webpieces/instruct-ai/`.
16
+ * `@provideSingleton` so it can be injected and appear in the rules-config DI design.
17
+ */
18
+ let TemplateWriter = class TemplateWriter {
19
+ loadTemplate(name) {
20
+ return fs.readFileSync(path.join(TEMPLATES_DIR, name), 'utf-8');
21
+ }
22
+ writeTemplateIfMissing(workspaceRoot, name, instructDir = DEFAULT_INSTRUCT_DIR) {
23
+ const dest = path.join(workspaceRoot, instructDir, name);
24
+ if (fs.existsSync(dest))
25
+ return;
26
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
27
+ fs.writeFileSync(dest, this.loadTemplate(name), 'utf-8');
28
+ }
29
+ writeTemplate(workspaceRoot, name, instructDir = DEFAULT_INSTRUCT_DIR) {
30
+ const dest = path.join(workspaceRoot, instructDir, name);
31
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
32
+ fs.writeFileSync(dest, this.loadTemplate(name), 'utf-8');
33
+ return dest;
34
+ }
35
+ };
36
+ exports.TemplateWriter = TemplateWriter;
37
+ exports.TemplateWriter = TemplateWriter = tslib_1.__decorate([
38
+ (0, core_context_1.provideSingleton)(),
39
+ (0, inversify_1.injectable)()
40
+ ], TemplateWriter);
41
+ // Temporary migration delegators — consumers migrate to injecting TemplateWriter over follow-up PRs.
42
+ const templateWriterSvc = new TemplateWriter();
11
43
  function loadTemplate(name) {
12
- return fs.readFileSync(path.join(TEMPLATES_DIR, name), 'utf-8');
44
+ return templateWriterSvc.loadTemplate(name);
13
45
  }
14
46
  function writeTemplateIfMissing(workspaceRoot, name, instructDir = DEFAULT_INSTRUCT_DIR) {
15
- const dest = path.join(workspaceRoot, instructDir, name);
16
- if (fs.existsSync(dest))
17
- return;
18
- fs.mkdirSync(path.dirname(dest), { recursive: true });
19
- fs.writeFileSync(dest, loadTemplate(name), 'utf-8');
47
+ templateWriterSvc.writeTemplateIfMissing(workspaceRoot, name, instructDir);
20
48
  }
21
49
  function writeTemplate(workspaceRoot, name, instructDir = DEFAULT_INSTRUCT_DIR) {
22
- const dest = path.join(workspaceRoot, instructDir, name);
23
- fs.mkdirSync(path.dirname(dest), { recursive: true });
24
- fs.writeFileSync(dest, loadTemplate(name), 'utf-8');
25
- return dest;
50
+ return templateWriterSvc.writeTemplate(workspaceRoot, name, instructDir);
26
51
  }
27
52
  //# sourceMappingURL=load-template.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"load-template.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-template.ts"],"names":[],"mappings":";;AAMA,oCAEC;AAED,wDASC;AAED,sCASC;;AA9BD,+CAAyB;AACzB,mDAA6B;AAE7B,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAC9D,MAAM,oBAAoB,GAAG,wBAAwB,CAAC;AAEtD,SAAgB,YAAY,CAAC,IAAY;IACrC,OAAO,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AACpE,CAAC;AAED,SAAgB,sBAAsB,CAClC,aAAqB,EACrB,IAAY,EACZ,cAAsB,oBAAoB;IAE1C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;IACzD,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO;IAChC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AACxD,CAAC;AAED,SAAgB,aAAa,CACzB,aAAqB,EACrB,IAAY,EACZ,cAAsB,oBAAoB;IAE1C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;IACzD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC;AAChB,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nconst TEMPLATES_DIR = path.join(__dirname, '..', 'templates');\nconst DEFAULT_INSTRUCT_DIR = '.webpieces/instruct-ai';\n\nexport function loadTemplate(name: string): string {\n return fs.readFileSync(path.join(TEMPLATES_DIR, name), 'utf-8');\n}\n\nexport function writeTemplateIfMissing(\n workspaceRoot: string,\n name: string,\n instructDir: string = DEFAULT_INSTRUCT_DIR\n): void {\n const dest = path.join(workspaceRoot, instructDir, name);\n if (fs.existsSync(dest)) return;\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, loadTemplate(name), 'utf-8');\n}\n\nexport function writeTemplate(\n workspaceRoot: string,\n name: string,\n instructDir: string = DEFAULT_INSTRUCT_DIR\n): string {\n const dest = path.join(workspaceRoot, instructDir, name);\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, loadTemplate(name), 'utf-8');\n return dest;\n}\n"]}
1
+ {"version":3,"file":"load-template.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-template.ts"],"names":[],"mappings":";;;AAqCA,oCAEC;AAED,wDAMC;AAED,sCAMC;;AAvDD,+CAAyB;AACzB,mDAA6B;AAC7B,0DAA2D;AAC3D,yCAAuC;AAEvC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAC9D,MAAM,oBAAoB,GAAG,wBAAwB,CAAC;AAEtD;;;GAGG;AAGI,IAAM,cAAc,GAApB,MAAM,cAAc;IACvB,YAAY,CAAC,IAAY;QACrB,OAAO,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;IACpE,CAAC;IAED,sBAAsB,CAAC,aAAqB,EAAE,IAAY,EAAE,cAAsB,oBAAoB;QAClG,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;QACzD,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO;QAChC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;IAC7D,CAAC;IAED,aAAa,CAAC,aAAqB,EAAE,IAAY,EAAE,cAAsB,oBAAoB;QACzF,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;QACzD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAA;AAlBY,wCAAc;yBAAd,cAAc;IAF1B,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;GACA,cAAc,CAkB1B;AAED,qGAAqG;AACrG,MAAM,iBAAiB,GAAG,IAAI,cAAc,EAAE,CAAC;AAE/C,SAAgB,YAAY,CAAC,IAAY;IACrC,OAAO,iBAAiB,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;AAChD,CAAC;AAED,SAAgB,sBAAsB,CAClC,aAAqB,EACrB,IAAY,EACZ,cAAsB,oBAAoB;IAE1C,iBAAiB,CAAC,sBAAsB,CAAC,aAAa,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAC/E,CAAC;AAED,SAAgB,aAAa,CACzB,aAAqB,EACrB,IAAY,EACZ,cAAsB,oBAAoB;IAE1C,OAAO,iBAAiB,CAAC,aAAa,CAAC,aAAa,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAC7E,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { provideSingleton } from '@webpieces/core-context';\nimport { injectable } from 'inversify';\n\nconst TEMPLATES_DIR = path.join(__dirname, '..', 'templates');\nconst DEFAULT_INSTRUCT_DIR = '.webpieces/instruct-ai';\n\n/**\n * Writes the AI-facing instruct-ai template docs under `<workspaceRoot>/.webpieces/instruct-ai/`.\n * `@provideSingleton` so it can be injected and appear in the rules-config DI design.\n */\n@provideSingleton()\n@injectable()\nexport class TemplateWriter {\n loadTemplate(name: string): string {\n return fs.readFileSync(path.join(TEMPLATES_DIR, name), 'utf-8');\n }\n\n writeTemplateIfMissing(workspaceRoot: string, name: string, instructDir: string = DEFAULT_INSTRUCT_DIR): void {\n const dest = path.join(workspaceRoot, instructDir, name);\n if (fs.existsSync(dest)) return;\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, this.loadTemplate(name), 'utf-8');\n }\n\n writeTemplate(workspaceRoot: string, name: string, instructDir: string = DEFAULT_INSTRUCT_DIR): string {\n const dest = path.join(workspaceRoot, instructDir, name);\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, this.loadTemplate(name), 'utf-8');\n return dest;\n }\n}\n\n// Temporary migration delegators — consumers migrate to injecting TemplateWriter over follow-up PRs.\nconst templateWriterSvc = new TemplateWriter();\n\nexport function loadTemplate(name: string): string {\n return templateWriterSvc.loadTemplate(name);\n}\n\nexport function writeTemplateIfMissing(\n workspaceRoot: string,\n name: string,\n instructDir: string = DEFAULT_INSTRUCT_DIR,\n): void {\n templateWriterSvc.writeTemplateIfMissing(workspaceRoot, name, instructDir);\n}\n\nexport function writeTemplate(\n workspaceRoot: string,\n name: string,\n instructDir: string = DEFAULT_INSTRUCT_DIR,\n): string {\n return templateWriterSvc.writeTemplate(workspaceRoot, name, instructDir);\n}\n"]}
package/src/repo-root.js CHANGED
@@ -4,6 +4,8 @@ exports.RepoRootFinder = exports.INSTRUCT_AI_DIR = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const child_process_1 = require("child_process");
6
6
  const path = tslib_1.__importStar(require("path"));
7
+ const core_context_1 = require("@webpieces/core-context");
8
+ const inversify_1 = require("inversify");
7
9
  const config_file_1 = require("./config-file");
8
10
  const constants_1 = require("./constants");
9
11
  // The single instruct-ai home under `.webpieces/`. Kept here (not just in load-template) so callers
@@ -19,7 +21,7 @@ exports.INSTRUCT_AI_DIR = `${constants_1.WEBPIECES_TMP_DIR}/instruct-ai`;
19
21
  * writer of `.webpieces/...` (logs, instruct-ai docs, sync cache, merge/pr state) MUST anchor its
20
22
  * path here rather than at `process.cwd()`.
21
23
  */
22
- class RepoRootFinder {
24
+ let RepoRootFinder = class RepoRootFinder {
23
25
  /**
24
26
  * The repo root for `startDir`. Resolution order (first hit wins):
25
27
  * 1. Directory holding webpieces.config.json — the webpieces workspace root, and the exact
@@ -59,6 +61,10 @@ class RepoRootFinder {
59
61
  const root = (r.stdout ?? '').trim();
60
62
  return root !== '' ? root : null;
61
63
  }
62
- }
64
+ };
63
65
  exports.RepoRootFinder = RepoRootFinder;
66
+ exports.RepoRootFinder = RepoRootFinder = tslib_1.__decorate([
67
+ (0, core_context_1.provideSingleton)(),
68
+ (0, inversify_1.injectable)()
69
+ ], RepoRootFinder);
64
70
  //# sourceMappingURL=repo-root.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"repo-root.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/repo-root.ts"],"names":[],"mappings":";;;;AAAA,iDAA0C;AAC1C,mDAA6B;AAE7B,+CAA+C;AAC/C,2CAAgD;AAEhD,oGAAoG;AACpG,0FAA0F;AAC7E,QAAA,eAAe,GAAG,GAAG,6BAAiB,cAAc,CAAC;AAElE;;;;;;;;;GASG;AACH,MAAa,cAAc;IACvB;;;;;;;OAOG;IACH,eAAe,CAAC,QAAgB;QAC5B,MAAM,UAAU,GAAG,IAAA,4BAAc,EAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,UAAU,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,OAAO,CAAC;QACrC,OAAO,QAAQ,CAAC;IACpB,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,QAAgB,EAAE,OAAe;QAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,uBAAe,EAAE,OAAO,CAAC,CAAC;IACzD,CAAC;IAED,gGAAgG;IAChG,WAAW,CAAC,QAAgB,EAAE,OAAe;QACzC,OAAO,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3E,CAAC;IAED,gGAAgG;IAChG,6FAA6F;IAC7F,8FAA8F;IACtF,WAAW,CAAC,GAAW;QAC3B,MAAM,CAAC,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,iBAAiB,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9F,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACrC,CAAC;CACJ;AAxCD,wCAwCC","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as path from 'path';\n\nimport { findConfigFile } from './config-file';\nimport { WEBPIECES_TMP_DIR } from './constants';\n\n// The single instruct-ai home under `.webpieces/`. Kept here (not just in load-template) so callers\n// building AI-facing messages can render the ABSOLUTE doc path from a resolved repo root.\nexport const INSTRUCT_AI_DIR = `${WEBPIECES_TMP_DIR}/instruct-ai`;\n\n/**\n * Resolves the single repo root that owns the `.webpieces/` tree, and renders absolute paths beneath\n * it.\n *\n * `.webpieces/` MUST live at the repo root — never in a CWD-dependent subdirectory. A tool invoked\n * from a subdir (a nested package, a `services/*` app) that naively joined `.webpieces` onto its own\n * cwd would scatter stray `.webpieces` trees across the tree — the exact bug this prevents. Every\n * writer of `.webpieces/...` (logs, instruct-ai docs, sync cache, merge/pr state) MUST anchor its\n * path here rather than at `process.cwd()`.\n */\nexport class RepoRootFinder {\n /**\n * The repo root for `startDir`. Resolution order (first hit wins):\n * 1. Directory holding webpieces.config.json — the webpieces workspace root, and the exact\n * anchor the hook runner already uses. Walks UP from startDir, so a subdir resolves to root.\n * 2. git toplevel (`git rev-parse --show-toplevel`) — the repo root when no config is present\n * yet (e.g. the installer runs before webpieces.config.json exists).\n * 3. `startDir` — last resort (git unavailable / not a repo / no config). Best-effort only.\n */\n resolveRepoRoot(startDir: string): string {\n const configPath = findConfigFile(startDir);\n if (configPath !== null) return path.dirname(configPath);\n const gitRoot = this.gitToplevel(startDir);\n if (gitRoot !== null) return gitRoot;\n return startDir;\n }\n\n /**\n * Absolute path to an instruct-ai doc under `repoRoot`. Hand THIS to the AI in a violation/fix\n * message — never a bare `.webpieces/instruct-ai/...` relative path, which an AI whose cwd is a\n * subdirectory would resolve against the wrong directory and fail to open.\n */\n instructAiDocPath(repoRoot: string, docName: string): string {\n return path.join(repoRoot, INSTRUCT_AI_DIR, docName);\n }\n\n /** Absolute instruct-ai doc path resolved directly from `startDir` (resolveRepoRoot + join). */\n docPathFrom(startDir: string, docName: string): string {\n return this.instructAiDocPath(this.resolveRepoRoot(startDir), docName);\n }\n\n // git repo root of `cwd`, or null when cwd is not in a git repo. `status !== 0` is the EXPECTED\n // \"not a repo\" value (spawnSync does not throw on non-zero exit), so we never swallow a real\n // failure with try/catch — a genuine git crash still surfaces. Mirrors runner.ts:gitToplevel.\n private gitToplevel(cwd: string): string | null {\n const r = spawnSync('git', ['-C', cwd, 'rev-parse', '--show-toplevel'], { encoding: 'utf8' });\n if (r.status !== 0) return null;\n const root = (r.stdout ?? '').trim();\n return root !== '' ? root : null;\n }\n}\n"]}
1
+ {"version":3,"file":"repo-root.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/repo-root.ts"],"names":[],"mappings":";;;;AAAA,iDAA0C;AAC1C,mDAA6B;AAC7B,0DAA2D;AAC3D,yCAAuC;AAEvC,+CAA+C;AAC/C,2CAAgD;AAEhD,oGAAoG;AACpG,0FAA0F;AAC7E,QAAA,eAAe,GAAG,GAAG,6BAAiB,cAAc,CAAC;AAElE;;;;;;;;;GASG;AAGI,IAAM,cAAc,GAApB,MAAM,cAAc;IACvB;;;;;;;OAOG;IACH,eAAe,CAAC,QAAgB;QAC5B,MAAM,UAAU,GAAG,IAAA,4BAAc,EAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,UAAU,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,OAAO,CAAC;QACrC,OAAO,QAAQ,CAAC;IACpB,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,QAAgB,EAAE,OAAe;QAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,uBAAe,EAAE,OAAO,CAAC,CAAC;IACzD,CAAC;IAED,gGAAgG;IAChG,WAAW,CAAC,QAAgB,EAAE,OAAe;QACzC,OAAO,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3E,CAAC;IAED,gGAAgG;IAChG,6FAA6F;IAC7F,8FAA8F;IACtF,WAAW,CAAC,GAAW;QAC3B,MAAM,CAAC,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,iBAAiB,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9F,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACrC,CAAC;CACJ,CAAA;AAxCY,wCAAc;yBAAd,cAAc;IAF1B,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;GACA,cAAc,CAwC1B","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as path from 'path';\nimport { provideSingleton } from '@webpieces/core-context';\nimport { injectable } from 'inversify';\n\nimport { findConfigFile } from './config-file';\nimport { WEBPIECES_TMP_DIR } from './constants';\n\n// The single instruct-ai home under `.webpieces/`. Kept here (not just in load-template) so callers\n// building AI-facing messages can render the ABSOLUTE doc path from a resolved repo root.\nexport const INSTRUCT_AI_DIR = `${WEBPIECES_TMP_DIR}/instruct-ai`;\n\n/**\n * Resolves the single repo root that owns the `.webpieces/` tree, and renders absolute paths beneath\n * it.\n *\n * `.webpieces/` MUST live at the repo root — never in a CWD-dependent subdirectory. A tool invoked\n * from a subdir (a nested package, a `services/*` app) that naively joined `.webpieces` onto its own\n * cwd would scatter stray `.webpieces` trees across the tree — the exact bug this prevents. Every\n * writer of `.webpieces/...` (logs, instruct-ai docs, sync cache, merge/pr state) MUST anchor its\n * path here rather than at `process.cwd()`.\n */\n@provideSingleton()\n@injectable()\nexport class RepoRootFinder {\n /**\n * The repo root for `startDir`. Resolution order (first hit wins):\n * 1. Directory holding webpieces.config.json — the webpieces workspace root, and the exact\n * anchor the hook runner already uses. Walks UP from startDir, so a subdir resolves to root.\n * 2. git toplevel (`git rev-parse --show-toplevel`) — the repo root when no config is present\n * yet (e.g. the installer runs before webpieces.config.json exists).\n * 3. `startDir` — last resort (git unavailable / not a repo / no config). Best-effort only.\n */\n resolveRepoRoot(startDir: string): string {\n const configPath = findConfigFile(startDir);\n if (configPath !== null) return path.dirname(configPath);\n const gitRoot = this.gitToplevel(startDir);\n if (gitRoot !== null) return gitRoot;\n return startDir;\n }\n\n /**\n * Absolute path to an instruct-ai doc under `repoRoot`. Hand THIS to the AI in a violation/fix\n * message — never a bare `.webpieces/instruct-ai/...` relative path, which an AI whose cwd is a\n * subdirectory would resolve against the wrong directory and fail to open.\n */\n instructAiDocPath(repoRoot: string, docName: string): string {\n return path.join(repoRoot, INSTRUCT_AI_DIR, docName);\n }\n\n /** Absolute instruct-ai doc path resolved directly from `startDir` (resolveRepoRoot + join). */\n docPathFrom(startDir: string, docName: string): string {\n return this.instructAiDocPath(this.resolveRepoRoot(startDir), docName);\n }\n\n // git repo root of `cwd`, or null when cwd is not in a git repo. `status !== 0` is the EXPECTED\n // \"not a repo\" value (spawnSync does not throw on non-zero exit), so we never swallow a real\n // failure with try/catch — a genuine git crash still surfaces. Mirrors runner.ts:gitToplevel.\n private gitToplevel(cwd: string): string | null {\n const r = spawnSync('git', ['-C', cwd, 'rev-parse', '--show-toplevel'], { encoding: 'utf8' });\n if (r.status !== 0) return null;\n const root = (r.stdout ?? '').trim();\n return root !== '' ? root : null;\n }\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { RepoRootFinder } from './repo-root';
2
+ import { ConfigLoader } from './load-config';
3
+ import { TemplateWriter } from './load-template';
4
+ /**
5
+ * DI-design root for @webpieces/rules-config (role:designed-lib).
6
+ *
7
+ * `@DocumentDesign` marks the top of the DAG the DI-design analyzer roots on, so the library's design
8
+ * (design.json / design.md / design.html) is generated. rules-config is the shared foundation whose
9
+ * utilities are being migrated from free functions to injected `@provideSingleton` service classes; as
10
+ * each service class lands (config loader, template writer, diff/git services, …) it is injected HERE
11
+ * so it appears in the drawn design.
12
+ */
13
+ export declare class RulesConfigDesign {
14
+ private readonly repoRootFinder;
15
+ private readonly configLoader;
16
+ private readonly templateWriter;
17
+ constructor(repoRootFinder: RepoRootFinder, configLoader: ConfigLoader, templateWriter: TemplateWriter);
18
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RulesConfigDesign = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const core_util_1 = require("@webpieces/core-util");
6
+ const core_context_1 = require("@webpieces/core-context");
7
+ const inversify_1 = require("inversify");
8
+ const repo_root_1 = require("./repo-root");
9
+ const load_config_1 = require("./load-config");
10
+ const load_template_1 = require("./load-template");
11
+ /**
12
+ * DI-design root for @webpieces/rules-config (role:designed-lib).
13
+ *
14
+ * `@DocumentDesign` marks the top of the DAG the DI-design analyzer roots on, so the library's design
15
+ * (design.json / design.md / design.html) is generated. rules-config is the shared foundation whose
16
+ * utilities are being migrated from free functions to injected `@provideSingleton` service classes; as
17
+ * each service class lands (config loader, template writer, diff/git services, …) it is injected HERE
18
+ * so it appears in the drawn design.
19
+ */
20
+ let RulesConfigDesign = class RulesConfigDesign {
21
+ repoRootFinder;
22
+ configLoader;
23
+ templateWriter;
24
+ constructor(repoRootFinder, configLoader, templateWriter) {
25
+ this.repoRootFinder = repoRootFinder;
26
+ this.configLoader = configLoader;
27
+ this.templateWriter = templateWriter;
28
+ }
29
+ };
30
+ exports.RulesConfigDesign = RulesConfigDesign;
31
+ exports.RulesConfigDesign = RulesConfigDesign = tslib_1.__decorate([
32
+ (0, core_util_1.DocumentDesign)(),
33
+ (0, core_context_1.provideSingleton)(),
34
+ (0, inversify_1.injectable)(),
35
+ tslib_1.__metadata("design:paramtypes", [repo_root_1.RepoRootFinder,
36
+ load_config_1.ConfigLoader,
37
+ load_template_1.TemplateWriter])
38
+ ], RulesConfigDesign);
39
+ //# sourceMappingURL=rules-config-design.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules-config-design.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/rules-config-design.ts"],"names":[],"mappings":";;;;AAAA,oDAAsD;AACtD,0DAA2D;AAC3D,yCAAuC;AAEvC,2CAA6C;AAC7C,+CAA6C;AAC7C,mDAAiD;AAEjD;;;;;;;;GAQG;AAII,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAEL;IACA;IACA;IAHrB,YACqB,cAA8B,EAC9B,YAA0B,EAC1B,cAA8B;QAF9B,mBAAc,GAAd,cAAc,CAAgB;QAC9B,iBAAY,GAAZ,YAAY,CAAc;QAC1B,mBAAc,GAAd,cAAc,CAAgB;IAChD,CAAC;CACP,CAAA;AANY,8CAAiB;4BAAjB,iBAAiB;IAH7B,IAAA,0BAAc,GAAE;IAChB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;6CAG4B,0BAAc;QAChB,0BAAY;QACV,8BAAc;GAJ1C,iBAAiB,CAM7B","sourcesContent":["import { DocumentDesign } from '@webpieces/core-util';\nimport { provideSingleton } from '@webpieces/core-context';\nimport { injectable } from 'inversify';\n\nimport { RepoRootFinder } from './repo-root';\nimport { ConfigLoader } from './load-config';\nimport { TemplateWriter } from './load-template';\n\n/**\n * DI-design root for @webpieces/rules-config (role:designed-lib).\n *\n * `@DocumentDesign` marks the top of the DAG the DI-design analyzer roots on, so the library's design\n * (design.json / design.md / design.html) is generated. rules-config is the shared foundation whose\n * utilities are being migrated from free functions to injected `@provideSingleton` service classes; as\n * each service class lands (config loader, template writer, diff/git services, …) it is injected HERE\n * so it appears in the drawn design.\n */\n@DocumentDesign()\n@provideSingleton()\n@injectable()\nexport class RulesConfigDesign {\n constructor(\n private readonly repoRootFinder: RepoRootFinder,\n private readonly configLoader: ConfigLoader,\n private readonly templateWriter: TemplateWriter,\n ) {}\n}\n"]}