@webpieces/ai-hook-rules 0.3.184 → 0.3.186

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.
Files changed (104) hide show
  1. package/package.json +4 -8
  2. package/src/adapters/global-hook.js +3 -3
  3. package/src/adapters/global-hook.js.map +1 -1
  4. package/src/core/custom-rule-adapter.d.ts +21 -0
  5. package/src/core/custom-rule-adapter.js +48 -0
  6. package/src/core/custom-rule-adapter.js.map +1 -0
  7. package/src/core/disable-directives.d.ts +1 -2
  8. package/src/core/disable-directives.js +17 -31
  9. package/src/core/disable-directives.js.map +1 -1
  10. package/src/core/load-rules.d.ts +3 -2
  11. package/src/core/load-rules.js +71 -48
  12. package/src/core/load-rules.js.map +1 -1
  13. package/src/core/rule-base.d.ts +38 -0
  14. package/src/core/rule-base.js +36 -0
  15. package/src/core/rule-base.js.map +1 -0
  16. package/src/core/rules/branch-creation-guard.d.ts +12 -3
  17. package/src/core/rules/branch-creation-guard.js +37 -37
  18. package/src/core/rules/branch-creation-guard.js.map +1 -1
  19. package/src/core/rules/catch-error-pattern.d.ts +10 -3
  20. package/src/core/rules/catch-error-pattern.js +27 -26
  21. package/src/core/rules/catch-error-pattern.js.map +1 -1
  22. package/src/core/rules/index.js +1 -0
  23. package/src/core/rules/index.js.map +1 -1
  24. package/src/core/rules/max-file-lines.d.ts +13 -3
  25. package/src/core/rules/max-file-lines.js +14 -15
  26. package/src/core/rules/max-file-lines.js.map +1 -1
  27. package/src/core/rules/merge-in-progress-guard.d.ts +9 -0
  28. package/src/core/rules/merge-in-progress-guard.js +65 -0
  29. package/src/core/rules/merge-in-progress-guard.js.map +1 -0
  30. package/src/core/rules/no-any-unknown.d.ts +10 -3
  31. package/src/core/rules/no-any-unknown.js +15 -13
  32. package/src/core/rules/no-any-unknown.js.map +1 -1
  33. package/src/core/rules/no-destructure.d.ts +13 -3
  34. package/src/core/rules/no-destructure.js +14 -12
  35. package/src/core/rules/no-destructure.js.map +1 -1
  36. package/src/core/rules/no-direct-main-update.d.ts +9 -3
  37. package/src/core/rules/no-direct-main-update.js +12 -13
  38. package/src/core/rules/no-direct-main-update.js.map +1 -1
  39. package/src/core/rules/no-edit-on-main.d.ts +13 -3
  40. package/src/core/rules/no-edit-on-main.js +14 -14
  41. package/src/core/rules/no-edit-on-main.js.map +1 -1
  42. package/src/core/rules/no-implicit-any.d.ts +10 -3
  43. package/src/core/rules/no-implicit-any.js +13 -12
  44. package/src/core/rules/no-implicit-any.js.map +1 -1
  45. package/src/core/rules/no-js-files.d.ts +13 -3
  46. package/src/core/rules/no-js-files.js +13 -16
  47. package/src/core/rules/no-js-files.js.map +1 -1
  48. package/src/core/rules/no-shell-substitution.d.ts +9 -3
  49. package/src/core/rules/no-shell-substitution.js +22 -23
  50. package/src/core/rules/no-shell-substitution.js.map +1 -1
  51. package/src/core/rules/no-symbol-di-tokens.d.ts +13 -3
  52. package/src/core/rules/no-symbol-di-tokens.js +15 -13
  53. package/src/core/rules/no-symbol-di-tokens.js.map +1 -1
  54. package/src/core/rules/no-unmanaged-exceptions.d.ts +10 -3
  55. package/src/core/rules/no-unmanaged-exceptions.js +18 -17
  56. package/src/core/rules/no-unmanaged-exceptions.js.map +1 -1
  57. package/src/core/rules/pr-creation-guard.d.ts +9 -4
  58. package/src/core/rules/pr-creation-guard.js +36 -72
  59. package/src/core/rules/pr-creation-guard.js.map +1 -1
  60. package/src/core/rules/pr-merge-cleanup.d.ts +9 -3
  61. package/src/core/rules/pr-merge-cleanup.js +13 -14
  62. package/src/core/rules/pr-merge-cleanup.js.map +1 -1
  63. package/src/core/rules/require-return-type.d.ts +10 -3
  64. package/src/core/rules/require-return-type.js +24 -23
  65. package/src/core/rules/require-return-type.js.map +1 -1
  66. package/src/core/rules/throw-cause-required.d.ts +10 -3
  67. package/src/core/rules/throw-cause-required.js +13 -12
  68. package/src/core/rules/throw-cause-required.js.map +1 -1
  69. package/src/core/rules/validate-ts-in-src.d.ts +14 -3
  70. package/src/core/rules/validate-ts-in-src.js +15 -18
  71. package/src/core/rules/validate-ts-in-src.js.map +1 -1
  72. package/src/core/runner.js +38 -67
  73. package/src/core/runner.js.map +1 -1
  74. package/src/core/to-error.js +0 -1
  75. package/src/core/to-error.js.map +1 -1
  76. package/src/core/types.d.ts +14 -13
  77. package/src/core/types.js.map +1 -1
  78. package/src/index.d.ts +21 -1
  79. package/src/index.js +47 -1
  80. package/src/index.js.map +1 -1
  81. package/src/core/configs/default.d.ts +0 -2
  82. package/src/core/configs/default.js +0 -22
  83. package/src/core/configs/default.js.map +0 -1
  84. package/src/scripts/git-gatherInfo.d.ts +0 -2
  85. package/src/scripts/git-gatherInfo.js +0 -98
  86. package/src/scripts/git-gatherInfo.js.map +0 -1
  87. package/src/scripts/git-mergeComplete.d.ts +0 -2
  88. package/src/scripts/git-mergeComplete.js +0 -58
  89. package/src/scripts/git-mergeComplete.js.map +0 -1
  90. package/src/scripts/git-updateFromMain.d.ts +0 -2
  91. package/src/scripts/git-updateFromMain.js +0 -290
  92. package/src/scripts/git-updateFromMain.js.map +0 -1
  93. package/src/scripts/workflow/cleanTmp.d.ts +0 -1
  94. package/src/scripts/workflow/cleanTmp.js +0 -57
  95. package/src/scripts/workflow/cleanTmp.js.map +0 -1
  96. package/src/scripts/workflow/git-findForkPoint.d.ts +0 -2
  97. package/src/scripts/workflow/git-findForkPoint.js +0 -120
  98. package/src/scripts/workflow/git-findForkPoint.js.map +0 -1
  99. package/src/scripts/workflow/git-readAiBranchName.d.ts +0 -2
  100. package/src/scripts/workflow/git-readAiBranchName.js +0 -20
  101. package/src/scripts/workflow/git-readAiBranchName.js.map +0 -1
  102. package/src/scripts/workflow/git-validateUpToDate.d.ts +0 -1
  103. package/src/scripts/workflow/git-validateUpToDate.js +0 -72
  104. package/src/scripts/workflow/git-validateUpToDate.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/ai-hook-rules",
3
- "version": "0.3.184",
3
+ "version": "0.3.186",
4
4
  "description": "Pluggable write-time validation framework for AI coding agents (@webpieces/ai-hook-rules). Claude Code PreToolUse + openclaw before_tool_call adapters share one rule engine.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -9,17 +9,13 @@
9
9
  "wp-setup-ai-hooks": "./bin/wp-setup-ai-hooks.js",
10
10
  "wp-setup-global-ai-hooks": "./bin/wp-setup-global-ai-hooks.js",
11
11
  "wp-dev-hook-install": "./bin/wp-dev-hook-install.js",
12
- "wp-dev-hook-uninstall": "./bin/wp-dev-hook-uninstall.js",
13
- "wp-git-update": "./src/scripts/git-updateFromMain.js",
14
- "wp-git-gather": "./src/scripts/git-gatherInfo.js",
15
- "wp-git-merge-complete": "./src/scripts/git-mergeComplete.js"
12
+ "wp-dev-hook-uninstall": "./bin/wp-dev-hook-uninstall.js"
16
13
  },
17
14
  "exports": {
18
15
  ".": "./src/index.js",
19
16
  "./package.json": "./package.json",
20
17
  "./claude-code": "./src/adapters/claude-code-hook.js",
21
- "./openclaw-plugin": "./src/adapters/openclaw-plugin.js",
22
- "./default": "./src/core/configs/default.js"
18
+ "./openclaw-plugin": "./src/adapters/openclaw-plugin.js"
23
19
  },
24
20
  "files": [
25
21
  "src/**/*",
@@ -36,7 +32,7 @@
36
32
  "directory": "packages/tooling/ai-hook-rules"
37
33
  },
38
34
  "dependencies": {
39
- "@webpieces/rules-config": "0.3.184"
35
+ "@webpieces/rules-config": "0.3.186"
40
36
  },
41
37
  "publishConfig": {
42
38
  "access": "public"
@@ -83,11 +83,11 @@ function run(rawInput) {
83
83
  'Present these THREE options to the human verbatim and wait for them to pick one.\n' +
84
84
  'Each option is a command the human can copy-paste into their own terminal:\n' +
85
85
  '\n' +
86
- ' A) PREFERRED — install the hook.\n' +
86
+ ' A) PREFERRED — install the webpieces rules bundle (one package: hook + all rules + pr-gate).\n' +
87
87
  ' If this is a pnpm workspace, run from the monorepo root:\n' +
88
- ' pnpm add -Dw @webpieces/ai-hook-rules\n' +
88
+ ' pnpm add -Dw @webpieces/nx-webpieces-rules\n' +
89
89
  ' Otherwise, run from the project root:\n' +
90
- ' npm install --save-dev @webpieces/ai-hook-rules\n' +
90
+ ' npm install --save-dev @webpieces/nx-webpieces-rules\n' +
91
91
  '\n' +
92
92
  ' B) Skip hooks temporarily (auto-expires). Ask the human how long, then have them run\n' +
93
93
  ' ONE of these from the project root:\n' +
@@ -1 +1 @@
1
- {"version":3,"file":"global-hook.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/global-hook.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from 'fs';\nimport { join, dirname, parse } from 'path';\nimport { spawnSync } from 'child_process';\n\ninterface SkipHooksFile {\n expires: number | null;\n reason?: string;\n}\n\n/**\n * Walk up from `startDir` until we find the directory containing `.git`, which marks\n * the root of the repo. Claude can `cd` into a sub-package before running a Bash tool,\n * which moves process.cwd() away from the repo root; without this traversal the hook\n * would look for `.webpieces/skiphooks` and `node_modules/.bin/wp-ai-hook` in the wrong\n * place and wrongly report webpieces as \"not installed\". `.git` can be a directory\n * (normal clone) or a file (worktree / submodule), so we accept either.\n * Falls back to `startDir` when no `.git` is found anywhere up the tree.\n */\nfunction findRepoRoot(startDir: string): string {\n let dir = startDir;\n const fsRoot = parse(dir).root;\n while (true) {\n if (existsSync(join(dir, '.git'))) return dir;\n if (dir === fsRoot) return startDir;\n dir = dirname(dir);\n }\n}\n\ninterface HookPayload {\n tool_name?: string;\n tool_input?: {\n file_path?: string;\n };\n}\n\nfunction readSkipHooks(cwd: string): SkipHooksFile | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const skipPath = join(cwd, '.webpieces', 'skiphooks');\n if (!existsSync(skipPath)) return null;\n return JSON.parse(readFileSync(skipPath, 'utf8')) as SkipHooksFile;\n } catch (err: unknown) {\n // eslint-disable-next-line @webpieces/catch-error-pattern -- intentionally discard; malformed .skiphooks must not crash global hook\n return null;\n }\n}\n\nfunction run(rawInput: string): void {\n const cwd = findRepoRoot(process.cwd());\n\n let payload: HookPayload | null = null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n if (rawInput.trim()) {\n payload = JSON.parse(rawInput) as HookPayload;\n }\n } catch (err: unknown) {\n //const error = toError(err); -- malformed stdin is intentionally ignored; must not crash global hook\n }\n\n // 1. Check .skiphooks — if valid and not expired, allow everything\n const skipHooks = readSkipHooks(cwd);\n if (skipHooks !== null) {\n const nowSeconds = Date.now() / 1000;\n if (skipHooks.expires === null || nowSeconds < skipHooks.expires) {\n process.exit(0);\n }\n }\n\n // 2. Always allow writing .webpieces/skiphooks (this is the escape hatch)\n const filePath = payload?.tool_input?.file_path ?? '';\n if (payload?.tool_name === 'Write' && filePath.endsWith('.webpieces/skiphooks')) {\n process.exit(0);\n }\n\n // 3. Delegate to per-repo hook if present\n const localHook = join(cwd, 'node_modules', '.bin', 'wp-ai-hook');\n if (existsSync(localHook)) {\n const result = spawnSync(localHook, [], {\n input: Buffer.from(rawInput),\n encoding: 'buffer',\n cwd,\n });\n if (result.stdout?.length) process.stdout.write(result.stdout);\n if (result.stderr?.length) process.stderr.write(result.stderr);\n process.exit(result.status ?? 0);\n }\n\n // 4. No local hook found — block and instruct AI\n process.stderr.write(\n 'STOP. You MUST ask the human to run one of 3 commands to continue.\\n' +\n '\\n' +\n 'This project is missing the webpieces AI hook (./node_modules/.bin/wp-ai-hook not found).\\n' +\n '\\n' +\n 'Present these THREE options to the human verbatim and wait for them to pick one.\\n' +\n 'Each option is a command the human can copy-paste into their own terminal:\\n' +\n '\\n' +\n ' A) PREFERRED — install the hook.\\n' +\n ' If this is a pnpm workspace, run from the monorepo root:\\n' +\n ' pnpm add -Dw @webpieces/ai-hook-rules\\n' +\n ' Otherwise, run from the project root:\\n' +\n ' npm install --save-dev @webpieces/ai-hook-rules\\n' +\n '\\n' +\n ' B) Skip hooks temporarily (auto-expires). Ask the human how long, then have them run\\n' +\n ' ONE of these from the project root:\\n' +\n ' 1 hour: mkdir -p .webpieces && printf \\'{\"expires\": %s, \"reason\": \"temporary skip\"}\\' \"$(($(date +%s) + 3600))\" > .webpieces/skiphooks\\n' +\n ' 1 week: mkdir -p .webpieces && printf \\'{\"expires\": %s, \"reason\": \"temporary skip\"}\\' \"$(($(date +%s) + 604800))\" > .webpieces/skiphooks\\n' +\n '\\n' +\n ' C) Disable permanently (NEVER expires — hooks stay off until this file is deleted).\\n' +\n ' Only choose this if the human explicitly wants it. Have them run from the project root:\\n' +\n ' mkdir -p .webpieces && printf \\'{\"expires\": null, \"reason\": \"permanently disabled\"}\\' > .webpieces/skiphooks\\n' +\n ' To re-enable later: rm .webpieces/skiphooks\\n' +\n '\\n' +\n 'You are BLOCKED. Ask the human now and wait for their response.\\n',\n );\n process.exit(2);\n}\n\nlet stdinData = '';\nprocess.stdin.resume();\nprocess.stdin.setEncoding('utf8');\nprocess.stdin.on('data', (chunk: string) => {\n stdinData += chunk;\n});\nprocess.stdin.on('end', () => {\n run(stdinData);\n});\n"]}
1
+ {"version":3,"file":"global-hook.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/global-hook.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from 'fs';\nimport { join, dirname, parse } from 'path';\nimport { spawnSync } from 'child_process';\n\ninterface SkipHooksFile {\n expires: number | null;\n reason?: string;\n}\n\n/**\n * Walk up from `startDir` until we find the directory containing `.git`, which marks\n * the root of the repo. Claude can `cd` into a sub-package before running a Bash tool,\n * which moves process.cwd() away from the repo root; without this traversal the hook\n * would look for `.webpieces/skiphooks` and `node_modules/.bin/wp-ai-hook` in the wrong\n * place and wrongly report webpieces as \"not installed\". `.git` can be a directory\n * (normal clone) or a file (worktree / submodule), so we accept either.\n * Falls back to `startDir` when no `.git` is found anywhere up the tree.\n */\nfunction findRepoRoot(startDir: string): string {\n let dir = startDir;\n const fsRoot = parse(dir).root;\n while (true) {\n if (existsSync(join(dir, '.git'))) return dir;\n if (dir === fsRoot) return startDir;\n dir = dirname(dir);\n }\n}\n\ninterface HookPayload {\n tool_name?: string;\n tool_input?: {\n file_path?: string;\n };\n}\n\nfunction readSkipHooks(cwd: string): SkipHooksFile | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const skipPath = join(cwd, '.webpieces', 'skiphooks');\n if (!existsSync(skipPath)) return null;\n return JSON.parse(readFileSync(skipPath, 'utf8')) as SkipHooksFile;\n } catch (err: unknown) {\n // eslint-disable-next-line @webpieces/catch-error-pattern -- intentionally discard; malformed .skiphooks must not crash global hook\n return null;\n }\n}\n\nfunction run(rawInput: string): void {\n const cwd = findRepoRoot(process.cwd());\n\n let payload: HookPayload | null = null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n if (rawInput.trim()) {\n payload = JSON.parse(rawInput) as HookPayload;\n }\n } catch (err: unknown) {\n //const error = toError(err); -- malformed stdin is intentionally ignored; must not crash global hook\n }\n\n // 1. Check .skiphooks — if valid and not expired, allow everything\n const skipHooks = readSkipHooks(cwd);\n if (skipHooks !== null) {\n const nowSeconds = Date.now() / 1000;\n if (skipHooks.expires === null || nowSeconds < skipHooks.expires) {\n process.exit(0);\n }\n }\n\n // 2. Always allow writing .webpieces/skiphooks (this is the escape hatch)\n const filePath = payload?.tool_input?.file_path ?? '';\n if (payload?.tool_name === 'Write' && filePath.endsWith('.webpieces/skiphooks')) {\n process.exit(0);\n }\n\n // 3. Delegate to per-repo hook if present\n const localHook = join(cwd, 'node_modules', '.bin', 'wp-ai-hook');\n if (existsSync(localHook)) {\n const result = spawnSync(localHook, [], {\n input: Buffer.from(rawInput),\n encoding: 'buffer',\n cwd,\n });\n if (result.stdout?.length) process.stdout.write(result.stdout);\n if (result.stderr?.length) process.stderr.write(result.stderr);\n process.exit(result.status ?? 0);\n }\n\n // 4. No local hook found — block and instruct AI\n process.stderr.write(\n 'STOP. You MUST ask the human to run one of 3 commands to continue.\\n' +\n '\\n' +\n 'This project is missing the webpieces AI hook (./node_modules/.bin/wp-ai-hook not found).\\n' +\n '\\n' +\n 'Present these THREE options to the human verbatim and wait for them to pick one.\\n' +\n 'Each option is a command the human can copy-paste into their own terminal:\\n' +\n '\\n' +\n ' A) PREFERRED — install the webpieces rules bundle (one package: hook + all rules + pr-gate).\\n' +\n ' If this is a pnpm workspace, run from the monorepo root:\\n' +\n ' pnpm add -Dw @webpieces/nx-webpieces-rules\\n' +\n ' Otherwise, run from the project root:\\n' +\n ' npm install --save-dev @webpieces/nx-webpieces-rules\\n' +\n '\\n' +\n ' B) Skip hooks temporarily (auto-expires). Ask the human how long, then have them run\\n' +\n ' ONE of these from the project root:\\n' +\n ' 1 hour: mkdir -p .webpieces && printf \\'{\"expires\": %s, \"reason\": \"temporary skip\"}\\' \"$(($(date +%s) + 3600))\" > .webpieces/skiphooks\\n' +\n ' 1 week: mkdir -p .webpieces && printf \\'{\"expires\": %s, \"reason\": \"temporary skip\"}\\' \"$(($(date +%s) + 604800))\" > .webpieces/skiphooks\\n' +\n '\\n' +\n ' C) Disable permanently (NEVER expires — hooks stay off until this file is deleted).\\n' +\n ' Only choose this if the human explicitly wants it. Have them run from the project root:\\n' +\n ' mkdir -p .webpieces && printf \\'{\"expires\": null, \"reason\": \"permanently disabled\"}\\' > .webpieces/skiphooks\\n' +\n ' To re-enable later: rm .webpieces/skiphooks\\n' +\n '\\n' +\n 'You are BLOCKED. Ask the human now and wait for their response.\\n',\n );\n process.exit(2);\n}\n\nlet stdinData = '';\nprocess.stdin.resume();\nprocess.stdin.setEncoding('utf8');\nprocess.stdin.on('data', (chunk: string) => {\n stdinData += chunk;\n});\nprocess.stdin.on('end', () => {\n run(stdinData);\n});\n"]}
@@ -0,0 +1,21 @@
1
+ import { AbstractRule, BaseRuleConfig, RuleOptions } from '@webpieces/rules-config';
2
+ import type { PlainRule, Rule, RuleScope, EditContext, FileContext, BashContext, Violation } from './types';
3
+ /**
4
+ * Wraps a custom rule loaded from a `rulesDir` (a plain object) so it satisfies the same
5
+ * runtime `Rule` contract as the built-in rule classes: it gains `shouldRun()` (driven by the
6
+ * rule's config entry in webpieces.config.json) from AbstractRule, and it seeds `ctx.options`
7
+ * with the merged option bag before delegating to the plain rule's `check()` — preserving the
8
+ * exact behavior custom rules had under the old options-based runner.
9
+ */
10
+ export declare class CustomRuleAdapter extends AbstractRule<BaseRuleConfig> implements Rule {
11
+ readonly scope: RuleScope;
12
+ readonly files: readonly string[];
13
+ readonly description: string;
14
+ readonly fixHint: readonly string[];
15
+ readonly defaultOptions: RuleOptions;
16
+ private readonly impl;
17
+ private readonly rawConfig;
18
+ constructor(impl: PlainRule, rawConfig: RuleOptions);
19
+ private buildOptions;
20
+ check(ctx: EditContext | FileContext | BashContext): readonly Violation[];
21
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CustomRuleAdapter = void 0;
4
+ const rules_config_1 = require("@webpieces/rules-config");
5
+ /**
6
+ * Wraps a custom rule loaded from a `rulesDir` (a plain object) so it satisfies the same
7
+ * runtime `Rule` contract as the built-in rule classes: it gains `shouldRun()` (driven by the
8
+ * rule's config entry in webpieces.config.json) from AbstractRule, and it seeds `ctx.options`
9
+ * with the merged option bag before delegating to the plain rule's `check()` — preserving the
10
+ * exact behavior custom rules had under the old options-based runner.
11
+ */
12
+ class CustomRuleAdapter extends rules_config_1.AbstractRule {
13
+ scope;
14
+ files;
15
+ description;
16
+ fixHint;
17
+ defaultOptions;
18
+ impl;
19
+ rawConfig;
20
+ constructor(impl, rawConfig) {
21
+ super(rawConfig, impl.name);
22
+ this.impl = impl;
23
+ this.scope = impl.scope;
24
+ this.files = impl.files;
25
+ this.description = impl.description;
26
+ this.fixHint = impl.fixHint;
27
+ this.defaultOptions = impl.defaultOptions;
28
+ this.rawConfig = rawConfig;
29
+ }
30
+ buildOptions() {
31
+ const out = {};
32
+ for (const key of Object.keys(this.defaultOptions))
33
+ out[key] = this.defaultOptions[key];
34
+ for (const key of Object.keys(this.rawConfig)) {
35
+ // 'mode' is the framework-level on/off switch, not a rule option.
36
+ if (key === 'mode')
37
+ continue;
38
+ out[key] = this.rawConfig[key];
39
+ }
40
+ return out;
41
+ }
42
+ check(ctx) {
43
+ ctx.options = this.buildOptions();
44
+ return this.impl.check(ctx);
45
+ }
46
+ }
47
+ exports.CustomRuleAdapter = CustomRuleAdapter;
48
+ //# sourceMappingURL=custom-rule-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-rule-adapter.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/custom-rule-adapter.ts"],"names":[],"mappings":";;;AAAA,0DAAoF;AAOpF;;;;;;GAMG;AACH,MAAa,iBAAkB,SAAQ,2BAA4B;IACtD,KAAK,CAAY;IACjB,KAAK,CAAoB;IACzB,WAAW,CAAS;IACpB,OAAO,CAAoB;IAC3B,cAAc,CAAc;IACpB,IAAI,CAAY;IAChB,SAAS,CAAc;IAExC,YAAY,IAAe,EAAE,SAAsB;QAC/C,KAAK,CAAC,SAA2B,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QACpC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC;QAC1C,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC/B,CAAC;IAEO,YAAY;QAChB,MAAM,GAAG,GAAgB,EAAE,CAAC;QAC5B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QACxF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5C,kEAAkE;YAClE,IAAI,GAAG,KAAK,MAAM;gBAAE,SAAS;YAC7B,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED,KAAK,CAAC,GAA4C;QAC9C,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;CACJ;AAnCD,8CAmCC","sourcesContent":["import { AbstractRule, BaseRuleConfig, RuleOptions } from '@webpieces/rules-config';\n\nimport type {\n PlainRule, Rule, RuleScope,\n EditContext, FileContext, BashContext, Violation,\n} from './types';\n\n/**\n * Wraps a custom rule loaded from a `rulesDir` (a plain object) so it satisfies the same\n * runtime `Rule` contract as the built-in rule classes: it gains `shouldRun()` (driven by the\n * rule's config entry in webpieces.config.json) from AbstractRule, and it seeds `ctx.options`\n * with the merged option bag before delegating to the plain rule's `check()` — preserving the\n * exact behavior custom rules had under the old options-based runner.\n */\nexport class CustomRuleAdapter extends AbstractRule<BaseRuleConfig> implements Rule {\n readonly scope: RuleScope;\n readonly files: readonly string[];\n readonly description: string;\n readonly fixHint: readonly string[];\n readonly defaultOptions: RuleOptions;\n private readonly impl: PlainRule;\n private readonly rawConfig: RuleOptions;\n\n constructor(impl: PlainRule, rawConfig: RuleOptions) {\n super(rawConfig as BaseRuleConfig, impl.name);\n this.impl = impl;\n this.scope = impl.scope;\n this.files = impl.files;\n this.description = impl.description;\n this.fixHint = impl.fixHint;\n this.defaultOptions = impl.defaultOptions;\n this.rawConfig = rawConfig;\n }\n\n private buildOptions(): RuleOptions {\n const out: RuleOptions = {};\n for (const key of Object.keys(this.defaultOptions)) out[key] = this.defaultOptions[key];\n for (const key of Object.keys(this.rawConfig)) {\n // 'mode' is the framework-level on/off switch, not a rule option.\n if (key === 'mode') continue;\n out[key] = this.rawConfig[key];\n }\n return out;\n }\n\n check(ctx: EditContext | FileContext | BashContext): readonly Violation[] {\n ctx.options = this.buildOptions();\n return this.impl.check(ctx);\n }\n}\n"]}
@@ -1,8 +1,7 @@
1
1
  import type { IsLineDisabled } from './types';
2
2
  export declare class DirectiveIndex {
3
3
  private readonly lineDisables;
4
- private readonly fileDisables;
5
- constructor(lineDisables: Map<number, Set<string>>, fileDisables: Set<string>);
4
+ constructor(lineDisables: Map<number, Set<string>>);
6
5
  isLineDisabled(lineNum: number, ruleName: string): boolean;
7
6
  }
8
7
  export declare function parseDirectives(source: string): DirectiveIndex;
@@ -3,35 +3,38 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DirectiveIndex = void 0;
4
4
  exports.parseDirectives = parseDirectives;
5
5
  exports.createIsLineDisabled = createIsLineDisabled;
6
- const DIRECTIVE_RE = /\/\/\s*(?:ai-hook-disable|webpieces-disable)(?:-(next|file|all))?(?:\s+([\w\-*,\s]+?))?(?:\s*--\s*(.*))?\s*$/;
6
+ const rules_config_1 = require("@webpieces/rules-config");
7
+ // Exactly ONE disable form: `// webpieces-disable <rule>[, <rule2>] -- reason`.
8
+ // A rule (or comma-list of rules) is REQUIRED — there is no bare/`*` "disable everything",
9
+ // no `ai-hook-disable` alias, and no `-file`/`-next`/`-all` variants. A disable applies to
10
+ // the same line (when code precedes the `//`) or the next non-comment line.
11
+ const DIRECTIVE_RE = new RegExp(`//\\s*${rules_config_1.WEBPIECES_DISABLE}\\s+([\\w-]+(?:\\s*,\\s*[\\w-]+)*)(?:\\s*--\\s*.*)?\\s*$`);
12
+ const COMMENT_PREFIX_RE = new RegExp(`^//\\s*${rules_config_1.WEBPIECES_DISABLE}`);
7
13
  class DirectiveIndex {
8
14
  lineDisables;
9
- fileDisables;
10
- constructor(lineDisables, fileDisables) {
15
+ constructor(lineDisables) {
11
16
  this.lineDisables = lineDisables;
12
- this.fileDisables = fileDisables;
13
17
  }
14
18
  isLineDisabled(lineNum, ruleName) {
15
- if (this.fileDisables.has('*') || this.fileDisables.has(ruleName))
16
- return true;
17
19
  const set = this.lineDisables.get(lineNum);
18
20
  if (!set)
19
21
  return false;
20
- return set.has('*') || set.has(ruleName);
22
+ return set.has(ruleName);
21
23
  }
22
24
  }
23
25
  exports.DirectiveIndex = DirectiveIndex;
24
26
  function parseRuleList(raw) {
25
- if (!raw || raw.trim() === '' || raw.trim() === '*')
26
- return ['*'];
27
- return raw.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
27
+ return raw
28
+ .split(',')
29
+ .map((s) => s.trim())
30
+ .filter((s) => s.length > 0 && s !== '*');
28
31
  }
29
32
  function nextTargetLine(lines, lineIdx) {
30
33
  for (let j = lineIdx + 1; j < lines.length; j += 1) {
31
34
  const trimmed = lines[j].trim();
32
35
  if (trimmed === '')
33
36
  continue;
34
- if (/^\/\/\s*(?:ai-hook-disable|webpieces-disable)/.test(trimmed))
37
+ if (COMMENT_PREFIX_RE.test(trimmed))
35
38
  continue;
36
39
  return j + 1;
37
40
  }
@@ -58,34 +61,17 @@ function resolveTarget(line, lines, i, map, rules) {
58
61
  function parseDirectives(source) {
59
62
  const lines = source.split('\n');
60
63
  const lineDisables = new Map();
61
- const fileDisables = new Set();
62
64
  for (let i = 0; i < lines.length; i += 1) {
63
65
  const line = lines[i];
64
66
  const match = line.match(DIRECTIVE_RE);
65
67
  if (!match)
66
68
  continue;
67
- const variant = match[1] || null;
68
- const rules = parseRuleList(match[2] || '');
69
- if (variant === 'file') {
70
- if (i < 20) {
71
- for (const r of rules)
72
- fileDisables.add(r);
73
- }
69
+ const rules = parseRuleList(match[1]);
70
+ if (rules.length === 0)
74
71
  continue;
75
- }
76
- if (variant === 'next') {
77
- const target = nextTargetLine(lines, i);
78
- if (target !== null)
79
- addDisable(lineDisables, target, rules);
80
- continue;
81
- }
82
- if (variant === 'all') {
83
- resolveTarget(line, lines, i, lineDisables, ['*']);
84
- continue;
85
- }
86
72
  resolveTarget(line, lines, i, lineDisables, rules);
87
73
  }
88
- return new DirectiveIndex(lineDisables, fileDisables);
74
+ return new DirectiveIndex(lineDisables);
89
75
  }
90
76
  function createIsLineDisabled(source) {
91
77
  const index = parseDirectives(source);
@@ -1 +1 @@
1
- {"version":3,"file":"disable-directives.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/disable-directives.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IsLineDisabled } from './types';\n\nconst DIRECTIVE_RE =\n /\\/\\/\\s*(?:ai-hook-disable|webpieces-disable)(?:-(next|file|all))?(?:\\s+([\\w\\-*,\\s]+?))?(?:\\s*--\\s*(.*))?\\s*$/;\n\nexport class DirectiveIndex {\n private readonly lineDisables: Map<number, Set<string>>;\n private readonly fileDisables: Set<string>;\n\n constructor(lineDisables: Map<number, Set<string>>, fileDisables: Set<string>) {\n this.lineDisables = lineDisables;\n this.fileDisables = fileDisables;\n }\n\n isLineDisabled(lineNum: number, ruleName: string): boolean {\n if (this.fileDisables.has('*') || this.fileDisables.has(ruleName)) return true;\n const set = this.lineDisables.get(lineNum);\n if (!set) return false;\n return set.has('*') || set.has(ruleName);\n }\n}\n\nfunction parseRuleList(raw: string | undefined): readonly string[] {\n if (!raw || raw.trim() === '' || raw.trim() === '*') return ['*'];\n return raw.split(',').map((s) => s.trim()).filter((s) => s.length > 0);\n}\n\nfunction nextTargetLine(lines: readonly string[], lineIdx: number): number | null {\n for (let j = lineIdx + 1; j < lines.length; j += 1) {\n const trimmed = lines[j].trim();\n if (trimmed === '') continue;\n if (/^\\/\\/\\s*(?:ai-hook-disable|webpieces-disable)/.test(trimmed)) continue;\n return j + 1;\n }\n return null;\n}\n\nfunction addDisable(map: Map<number, Set<string>>, lineNum: number, rules: readonly string[]): void {\n if (!map.has(lineNum)) map.set(lineNum, new Set<string>());\n const set = map.get(lineNum)!;\n for (const r of rules) set.add(r);\n}\n\nfunction resolveTarget(\n line: string, lines: readonly string[], i: number,\n map: Map<number, Set<string>>, rules: readonly string[],\n): void {\n const beforeComment = line.slice(0, line.indexOf('//')).trim();\n if (beforeComment !== '') {\n addDisable(map, i + 1, rules);\n } else {\n const target = nextTargetLine(lines, i);\n if (target !== null) addDisable(map, target, rules);\n }\n}\n\nexport function parseDirectives(source: string): DirectiveIndex {\n const lines = source.split('\\n');\n const lineDisables = new Map<number, Set<string>>();\n const fileDisables = new Set<string>();\n\n for (let i = 0; i < lines.length; i += 1) {\n const line = lines[i];\n const match = line.match(DIRECTIVE_RE);\n if (!match) continue;\n const variant = match[1] || null;\n const rules = parseRuleList(match[2] || '');\n\n if (variant === 'file') {\n if (i < 20) { for (const r of rules) fileDisables.add(r); }\n continue;\n }\n if (variant === 'next') {\n const target = nextTargetLine(lines, i);\n if (target !== null) addDisable(lineDisables, target, rules);\n continue;\n }\n if (variant === 'all') {\n resolveTarget(line, lines, i, lineDisables, ['*']);\n continue;\n }\n resolveTarget(line, lines, i, lineDisables, rules);\n }\n return new DirectiveIndex(lineDisables, fileDisables);\n}\n\nexport function createIsLineDisabled(source: string): IsLineDisabled {\n const index = parseDirectives(source);\n return (lineNum: number, ruleName: string): boolean => index.isLineDisabled(lineNum, ruleName);\n}\n"]}
1
+ {"version":3,"file":"disable-directives.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/disable-directives.ts"],"names":[],"mappings":"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","sourcesContent":["import { WEBPIECES_DISABLE } from '@webpieces/rules-config';\nimport type { IsLineDisabled } from './types';\n\n// Exactly ONE disable form: `// webpieces-disable <rule>[, <rule2>] -- reason`.\n// A rule (or comma-list of rules) is REQUIRED — there is no bare/`*` \"disable everything\",\n// no `ai-hook-disable` alias, and no `-file`/`-next`/`-all` variants. A disable applies to\n// the same line (when code precedes the `//`) or the next non-comment line.\nconst DIRECTIVE_RE = new RegExp(\n `//\\\\s*${WEBPIECES_DISABLE}\\\\s+([\\\\w-]+(?:\\\\s*,\\\\s*[\\\\w-]+)*)(?:\\\\s*--\\\\s*.*)?\\\\s*$`,\n);\nconst COMMENT_PREFIX_RE = new RegExp(`^//\\\\s*${WEBPIECES_DISABLE}`);\n\nexport class DirectiveIndex {\n private readonly lineDisables: Map<number, Set<string>>;\n\n constructor(lineDisables: Map<number, Set<string>>) {\n this.lineDisables = lineDisables;\n }\n\n isLineDisabled(lineNum: number, ruleName: string): boolean {\n const set = this.lineDisables.get(lineNum);\n if (!set) return false;\n return set.has(ruleName);\n }\n}\n\nfunction parseRuleList(raw: string): readonly string[] {\n return raw\n .split(',')\n .map((s: string): string => s.trim())\n .filter((s: string): boolean => s.length > 0 && s !== '*');\n}\n\nfunction nextTargetLine(lines: readonly string[], lineIdx: number): number | null {\n for (let j = lineIdx + 1; j < lines.length; j += 1) {\n const trimmed = lines[j].trim();\n if (trimmed === '') continue;\n if (COMMENT_PREFIX_RE.test(trimmed)) continue;\n return j + 1;\n }\n return null;\n}\n\nfunction addDisable(map: Map<number, Set<string>>, lineNum: number, rules: readonly string[]): void {\n if (!map.has(lineNum)) map.set(lineNum, new Set<string>());\n const set = map.get(lineNum)!;\n for (const r of rules) set.add(r);\n}\n\nfunction resolveTarget(\n line: string, lines: readonly string[], i: number,\n map: Map<number, Set<string>>, rules: readonly string[],\n): void {\n const beforeComment = line.slice(0, line.indexOf('//')).trim();\n if (beforeComment !== '') {\n addDisable(map, i + 1, rules);\n } else {\n const target = nextTargetLine(lines, i);\n if (target !== null) addDisable(map, target, rules);\n }\n}\n\nexport function parseDirectives(source: string): DirectiveIndex {\n const lines = source.split('\\n');\n const lineDisables = new Map<number, Set<string>>();\n\n for (let i = 0; i < lines.length; i += 1) {\n const line = lines[i];\n const match = line.match(DIRECTIVE_RE);\n if (!match) continue;\n const rules = parseRuleList(match[1]);\n if (rules.length === 0) continue;\n resolveTarget(line, lines, i, lineDisables, rules);\n }\n return new DirectiveIndex(lineDisables);\n}\n\nexport function createIsLineDisabled(source: string): IsLineDisabled {\n const index = parseDirectives(source);\n return (lineNum: number, ruleName: string): boolean => index.isLineDisabled(lineNum, ruleName);\n}\n"]}
@@ -1,3 +1,4 @@
1
- import type { Rule, ResolvedConfig } from './types';
2
- export declare function loadRules(config: ResolvedConfig, workspaceRoot: string): readonly Rule[];
1
+ import { WebpiecesRulesConfig } from '@webpieces/rules-config';
2
+ import type { Rule } from './types';
3
+ export declare function loadRules(config: WebpiecesRulesConfig, workspaceRoot: string): readonly Rule[];
3
4
  export declare function globMatches(pattern: string, filePath: string): boolean;
@@ -7,65 +7,86 @@ const fs = tslib_1.__importStar(require("fs"));
7
7
  const path = tslib_1.__importStar(require("path"));
8
8
  const types_1 = require("./types");
9
9
  const to_error_1 = require("./to-error");
10
+ const rule_base_1 = require("./rule-base");
11
+ const custom_rule_adapter_1 = require("./custom-rule-adapter");
10
12
  const index_1 = require("./rules/index");
11
- const no_any_unknown_1 = tslib_1.__importDefault(require("./rules/no-any-unknown"));
12
- const no_implicit_any_1 = tslib_1.__importDefault(require("./rules/no-implicit-any"));
13
- const max_file_lines_1 = tslib_1.__importDefault(require("./rules/max-file-lines"));
14
- const validate_ts_in_src_1 = tslib_1.__importDefault(require("./rules/validate-ts-in-src"));
15
- const no_destructure_1 = tslib_1.__importDefault(require("./rules/no-destructure"));
16
- const require_return_type_1 = tslib_1.__importDefault(require("./rules/require-return-type"));
17
- const no_unmanaged_exceptions_1 = tslib_1.__importDefault(require("./rules/no-unmanaged-exceptions"));
18
- const catch_error_pattern_1 = tslib_1.__importDefault(require("./rules/catch-error-pattern"));
19
- const throw_cause_required_1 = tslib_1.__importDefault(require("./rules/throw-cause-required"));
20
- const no_shell_substitution_1 = tslib_1.__importDefault(require("./rules/no-shell-substitution"));
21
- const no_symbol_di_tokens_1 = tslib_1.__importDefault(require("./rules/no-symbol-di-tokens"));
22
- const branch_creation_guard_1 = tslib_1.__importDefault(require("./rules/branch-creation-guard"));
23
- const pr_creation_guard_1 = tslib_1.__importDefault(require("./rules/pr-creation-guard"));
24
- const pr_merge_cleanup_1 = tslib_1.__importDefault(require("./rules/pr-merge-cleanup"));
25
- const no_direct_main_update_1 = tslib_1.__importDefault(require("./rules/no-direct-main-update"));
26
- const no_js_files_1 = tslib_1.__importDefault(require("./rules/no-js-files"));
27
- const no_edit_on_main_1 = tslib_1.__importDefault(require("./rules/no-edit-on-main"));
13
+ const no_any_unknown_1 = require("./rules/no-any-unknown");
14
+ const no_implicit_any_1 = require("./rules/no-implicit-any");
15
+ const max_file_lines_1 = require("./rules/max-file-lines");
16
+ const validate_ts_in_src_1 = require("./rules/validate-ts-in-src");
17
+ const no_destructure_1 = require("./rules/no-destructure");
18
+ const require_return_type_1 = require("./rules/require-return-type");
19
+ const no_unmanaged_exceptions_1 = require("./rules/no-unmanaged-exceptions");
20
+ const catch_error_pattern_1 = require("./rules/catch-error-pattern");
21
+ const throw_cause_required_1 = require("./rules/throw-cause-required");
22
+ const no_shell_substitution_1 = require("./rules/no-shell-substitution");
23
+ const no_symbol_di_tokens_1 = require("./rules/no-symbol-di-tokens");
24
+ const branch_creation_guard_1 = require("./rules/branch-creation-guard");
25
+ const pr_creation_guard_1 = require("./rules/pr-creation-guard");
26
+ const merge_in_progress_guard_1 = require("./rules/merge-in-progress-guard");
27
+ const pr_merge_cleanup_1 = require("./rules/pr-merge-cleanup");
28
+ const no_direct_main_update_1 = require("./rules/no-direct-main-update");
29
+ const no_js_files_1 = require("./rules/no-js-files");
30
+ const no_edit_on_main_1 = require("./rules/no-edit-on-main");
28
31
  const REQUIRED_FIELDS = ['name', 'description', 'scope', 'files', 'check'];
29
32
  const VALID_SCOPES = new Set(['edit', 'file', 'bash']);
30
33
  const BUILT_IN_RULE_MAP = {
31
- 'no-any-unknown': no_any_unknown_1.default,
32
- 'no-implicit-any': no_implicit_any_1.default,
33
- 'max-file-lines': max_file_lines_1.default,
34
- 'validate-ts-in-src': validate_ts_in_src_1.default,
35
- 'no-destructure': no_destructure_1.default,
36
- 'require-return-type': require_return_type_1.default,
37
- 'no-unmanaged-exceptions': no_unmanaged_exceptions_1.default,
38
- 'catch-error-pattern': catch_error_pattern_1.default,
39
- 'throw-cause-required': throw_cause_required_1.default,
40
- 'no-shell-substitution': no_shell_substitution_1.default,
41
- 'no-symbol-di-tokens': no_symbol_di_tokens_1.default,
42
- 'branch-creation-guard': branch_creation_guard_1.default,
43
- 'pr-creation-guard': pr_creation_guard_1.default,
44
- 'pr-merge-cleanup': pr_merge_cleanup_1.default,
45
- 'no-direct-main-update': no_direct_main_update_1.default,
46
- 'no-js-files': no_js_files_1.default,
47
- 'no-edit-on-main': no_edit_on_main_1.default,
34
+ 'no-any-unknown': (c) => new no_any_unknown_1.NoAnyUnknownRule(c),
35
+ 'no-implicit-any': (c) => new no_implicit_any_1.NoImplicitAnyRule(c),
36
+ 'max-file-lines': (c) => new max_file_lines_1.MaxFileLinesRule(c),
37
+ 'validate-ts-in-src': (c) => new validate_ts_in_src_1.ValidateTsInSrcRule(c),
38
+ 'no-destructure': (c) => new no_destructure_1.NoDestructureRule(c),
39
+ 'require-return-type': (c) => new require_return_type_1.RequireReturnTypeRule(c),
40
+ 'no-unmanaged-exceptions': (c) => new no_unmanaged_exceptions_1.NoUnmanagedExceptionsRule(c),
41
+ 'catch-error-pattern': (c) => new catch_error_pattern_1.CatchErrorPatternRule(c),
42
+ 'throw-cause-required': (c) => new throw_cause_required_1.ThrowCauseRequiredRule(c),
43
+ 'no-shell-substitution': (c) => new no_shell_substitution_1.NoShellSubstitutionRule(c),
44
+ 'no-symbol-di-tokens': (c) => new no_symbol_di_tokens_1.NoSymbolDiTokensRule(c),
45
+ 'branch-creation-guard': (c) => new branch_creation_guard_1.BranchCreationGuardRule(c),
46
+ 'pr-creation-guard': (c) => new pr_creation_guard_1.PrCreationGuardRule(c),
47
+ 'merge-in-progress-guard': (c) => new merge_in_progress_guard_1.MergeInProgressGuardRule(c),
48
+ 'pr-merge-cleanup': (c) => new pr_merge_cleanup_1.PrMergeCleanupRule(c),
49
+ 'no-direct-main-update': (c) => new no_direct_main_update_1.NoDirectMainUpdateRule(c),
50
+ 'no-js-files': (c) => new no_js_files_1.NoJsFilesRule(c),
51
+ 'no-edit-on-main': (c) => new no_edit_on_main_1.NoEditOnMainRule(c),
48
52
  };
53
+ // Index the typed config by rule name. Each value is the rule's *Config (a plain object from
54
+ // JSON), or undefined when the rule has no entry yet (the sync check reports those).
55
+ function asConfigMap(config) {
56
+ // webpieces-disable no-any-unknown -- index the typed config by dynamic rule name
57
+ return config;
58
+ }
49
59
  function loadRules(config, workspaceRoot) {
50
- const builtIns = loadBuiltInRules();
51
- const custom = loadCustomRules(config.rulesDir, workspaceRoot);
52
- const all = [...builtIns, ...custom];
53
- return all.filter((rule) => validateRule(rule));
60
+ const builtIns = loadBuiltInRules(config);
61
+ const custom = loadCustomRules(config, workspaceRoot);
62
+ return [...builtIns, ...custom];
54
63
  }
55
- function loadBuiltInRules() {
56
- const modules = [];
64
+ function loadBuiltInRules(config) {
65
+ const map = asConfigMap(config);
66
+ const rules = [];
57
67
  for (const name of index_1.builtInRuleNames) {
58
- const mod = BUILT_IN_RULE_MAP[name];
59
- if (mod) {
60
- modules.push(mod);
61
- }
62
- else {
68
+ const factory = BUILT_IN_RULE_MAP[name];
69
+ if (!factory) {
63
70
  process.stderr.write(`[ai-hooks] unknown built-in rule: ${name}\n`);
71
+ continue;
64
72
  }
73
+ const ruleConfig = map[name] ?? new rule_base_1.EmptyRuleConfig();
74
+ rules.push(factory(ruleConfig));
65
75
  }
66
- return modules;
76
+ return rules;
77
+ }
78
+ function loadCustomRules(config, workspaceRoot) {
79
+ const dirs = config.rulesDir ?? [];
80
+ // webpieces-disable no-any-unknown -- index the typed config by dynamic custom-rule name
81
+ const map = config;
82
+ const rules = [];
83
+ for (const plain of loadCustomPlainRules(dirs, workspaceRoot)) {
84
+ const rawConfig = map[plain.name] ?? {};
85
+ rules.push(new custom_rule_adapter_1.CustomRuleAdapter(plain, rawConfig));
86
+ }
87
+ return rules;
67
88
  }
68
- function loadCustomRules(rulesDirs, workspaceRoot) {
89
+ function loadCustomPlainRules(rulesDirs, workspaceRoot) {
69
90
  const modules = [];
70
91
  for (const dir of rulesDirs) {
71
92
  const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);
@@ -87,7 +108,9 @@ function loadCustomRules(rulesDirs, workspaceRoot) {
87
108
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
88
109
  try {
89
110
  const mod = require(full);
90
- modules.push(mod.default || mod);
111
+ const candidate = mod.default || mod;
112
+ if (validateRule(candidate))
113
+ modules.push(candidate);
91
114
  }
92
115
  catch (err) {
93
116
  const error = (0, to_error_1.toError)(err);
@@ -1 +1 @@
1
- {"version":3,"file":"load-rules.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport type { Rule, ResolvedConfig } from './types';\nimport { InformAiError } from './types';\nimport { toError } from './to-error';\nimport { builtInRuleNames } from './rules/index';\nimport noAnyUnknown from './rules/no-any-unknown';\nimport noImplicitAny from './rules/no-implicit-any';\nimport maxFileLines from './rules/max-file-lines';\nimport validateTsInSrc from './rules/validate-ts-in-src';\nimport noDestructure from './rules/no-destructure';\nimport requireReturnType from './rules/require-return-type';\nimport noUnmanagedExceptions from './rules/no-unmanaged-exceptions';\nimport catchErrorPattern from './rules/catch-error-pattern';\nimport throwCauseRequired from './rules/throw-cause-required';\nimport noShellSubstitution from './rules/no-shell-substitution';\nimport noSymbolDiTokens from './rules/no-symbol-di-tokens';\nimport branchCreationGuard from './rules/branch-creation-guard';\nimport prCreationGuard from './rules/pr-creation-guard';\nimport prMergeCleanup from './rules/pr-merge-cleanup';\nimport noDirectMainUpdate from './rules/no-direct-main-update';\nimport noJsFiles from './rules/no-js-files';\nimport noEditOnMain from './rules/no-edit-on-main';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\nconst BUILT_IN_RULE_MAP: Record<string, Rule> = {\n 'no-any-unknown': noAnyUnknown as Rule,\n 'no-implicit-any': noImplicitAny as Rule,\n 'max-file-lines': maxFileLines as Rule,\n 'validate-ts-in-src': validateTsInSrc as Rule,\n 'no-destructure': noDestructure as Rule,\n 'require-return-type': requireReturnType as Rule,\n 'no-unmanaged-exceptions': noUnmanagedExceptions as Rule,\n 'catch-error-pattern': catchErrorPattern as Rule,\n 'throw-cause-required': throwCauseRequired as Rule,\n 'no-shell-substitution': noShellSubstitution as Rule,\n 'no-symbol-di-tokens': noSymbolDiTokens as Rule,\n 'branch-creation-guard': branchCreationGuard as Rule,\n 'pr-creation-guard': prCreationGuard as Rule,\n 'pr-merge-cleanup': prMergeCleanup as Rule,\n 'no-direct-main-update': noDirectMainUpdate as Rule,\n 'no-js-files': noJsFiles as Rule,\n 'no-edit-on-main': noEditOnMain as Rule,\n};\n\nexport function loadRules(config: ResolvedConfig, workspaceRoot: string): readonly Rule[] {\n const builtIns = loadBuiltInRules();\n const custom = loadCustomRules(config.rulesDir, workspaceRoot);\n const all: Rule[] = [...builtIns, ...custom];\n return all.filter((rule: Rule) => validateRule(rule));\n}\n\nfunction loadBuiltInRules(): Rule[] {\n const modules: Rule[] = [];\n for (const name of builtInRuleNames) {\n const mod = BUILT_IN_RULE_MAP[name];\n if (mod) {\n modules.push(mod);\n } else {\n process.stderr.write(`[ai-hooks] unknown built-in rule: ${name}\\n`);\n }\n }\n return modules;\n}\n\nfunction loadCustomRules(rulesDirs: readonly string[], workspaceRoot: string): Rule[] {\n const modules: Rule[] = [];\n for (const dir of rulesDirs) {\n const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);\n if (!fs.existsSync(absDir)) {\n process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\\n`);\n continue;\n }\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(absDir).filter((e) => e.endsWith('.js'));\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot read custom rules directory '${absDir}'`, { cause: error });\n }\n for (const entry of entries) {\n const full = path.join(absDir, entry);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(full);\n modules.push(mod.default || mod);\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot load custom rule '${full}'`, { cause: error });\n }\n }\n }\n return modules;\n}\n\n// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary\nfunction validateRule(rule: unknown): rule is Rule {\n if (!rule || typeof rule !== 'object') {\n process.stderr.write('[ai-hooks] rule is not an object, skipping\\n');\n return false;\n }\n // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary\n const obj = rule as Record<string, unknown>;\n for (const field of REQUIRED_FIELDS) {\n if (obj[field] === undefined) {\n const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';\n process.stderr.write(`[ai-hooks] rule \"${name}\" missing required field: ${field}\\n`);\n return false;\n }\n }\n if (!VALID_SCOPES.has(obj['scope'] as string)) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" has invalid scope: ${String(obj['scope'])}\\n`);\n return false;\n }\n if (!Array.isArray(obj['files'])) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" files must be an array\\n`);\n return false;\n }\n if (typeof obj['check'] !== 'function') {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" check must be a function\\n`);\n return false;\n }\n return true;\n}\n\nexport function globMatches(pattern: string, filePath: string): boolean {\n const regex = globToRegex(pattern);\n return regex.test(filePath);\n}\n\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n"]}
1
+ {"version":3,"file":"load-rules.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport {\n BaseRuleConfig, RuleOptions, WebpiecesRulesConfig,\n NoAnyUnknownConfig, NoImplicitAnyConfig, MaxFileLinesConfig, ValidateTsInSrcConfig,\n NoDestructureConfig, RequireReturnTypeConfig, NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig, ThrowCauseRequiredConfig, NoShellSubstitutionConfig,\n NoSymbolDiTokensConfig, BranchCreationGuardConfig, PrCreationGuardConfig,\n MergeInProgressGuardConfig, PrMergeCleanupConfig, NoDirectMainUpdateConfig,\n NoJsFilesConfig, NoEditOnMainConfig,\n} from '@webpieces/rules-config';\n\nimport type { Rule, PlainRule } from './types';\nimport { InformAiError } from './types';\nimport { toError } from './to-error';\nimport { EmptyRuleConfig } from './rule-base';\nimport { CustomRuleAdapter } from './custom-rule-adapter';\nimport { builtInRuleNames } from './rules/index';\nimport { NoAnyUnknownRule } from './rules/no-any-unknown';\nimport { NoImplicitAnyRule } from './rules/no-implicit-any';\nimport { MaxFileLinesRule } from './rules/max-file-lines';\nimport { ValidateTsInSrcRule } from './rules/validate-ts-in-src';\nimport { NoDestructureRule } from './rules/no-destructure';\nimport { RequireReturnTypeRule } from './rules/require-return-type';\nimport { NoUnmanagedExceptionsRule } from './rules/no-unmanaged-exceptions';\nimport { CatchErrorPatternRule } from './rules/catch-error-pattern';\nimport { ThrowCauseRequiredRule } from './rules/throw-cause-required';\nimport { NoShellSubstitutionRule } from './rules/no-shell-substitution';\nimport { NoSymbolDiTokensRule } from './rules/no-symbol-di-tokens';\nimport { BranchCreationGuardRule } from './rules/branch-creation-guard';\nimport { PrCreationGuardRule } from './rules/pr-creation-guard';\nimport { MergeInProgressGuardRule } from './rules/merge-in-progress-guard';\nimport { PrMergeCleanupRule } from './rules/pr-merge-cleanup';\nimport { NoDirectMainUpdateRule } from './rules/no-direct-main-update';\nimport { NoJsFilesRule } from './rules/no-js-files';\nimport { NoEditOnMainRule } from './rules/no-edit-on-main';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\n// Each built-in rule is constructed from its typed *Config (the entry in webpieces.config.json).\n// The config arrives as a plain object structurally typed as the *Config class, so the `as`\n// narrows the shared BaseRuleConfig param back to the concrete config the rule consumes.\ntype RuleFactory = (config: BaseRuleConfig) => Rule;\n\nconst BUILT_IN_RULE_MAP: Record<string, RuleFactory> = {\n 'no-any-unknown': (c: BaseRuleConfig) => new NoAnyUnknownRule(c as NoAnyUnknownConfig),\n 'no-implicit-any': (c: BaseRuleConfig) => new NoImplicitAnyRule(c as NoImplicitAnyConfig),\n 'max-file-lines': (c: BaseRuleConfig) => new MaxFileLinesRule(c as MaxFileLinesConfig),\n 'validate-ts-in-src': (c: BaseRuleConfig) => new ValidateTsInSrcRule(c as ValidateTsInSrcConfig),\n 'no-destructure': (c: BaseRuleConfig) => new NoDestructureRule(c as NoDestructureConfig),\n 'require-return-type': (c: BaseRuleConfig) => new RequireReturnTypeRule(c as RequireReturnTypeConfig),\n 'no-unmanaged-exceptions': (c: BaseRuleConfig) => new NoUnmanagedExceptionsRule(c as NoUnmanagedExceptionsConfig),\n 'catch-error-pattern': (c: BaseRuleConfig) => new CatchErrorPatternRule(c as CatchErrorPatternConfig),\n 'throw-cause-required': (c: BaseRuleConfig) => new ThrowCauseRequiredRule(c as ThrowCauseRequiredConfig),\n 'no-shell-substitution': (c: BaseRuleConfig) => new NoShellSubstitutionRule(c as NoShellSubstitutionConfig),\n 'no-symbol-di-tokens': (c: BaseRuleConfig) => new NoSymbolDiTokensRule(c as NoSymbolDiTokensConfig),\n 'branch-creation-guard': (c: BaseRuleConfig) => new BranchCreationGuardRule(c as BranchCreationGuardConfig),\n 'pr-creation-guard': (c: BaseRuleConfig) => new PrCreationGuardRule(c as PrCreationGuardConfig),\n 'merge-in-progress-guard': (c: BaseRuleConfig) => new MergeInProgressGuardRule(c as MergeInProgressGuardConfig),\n 'pr-merge-cleanup': (c: BaseRuleConfig) => new PrMergeCleanupRule(c as PrMergeCleanupConfig),\n 'no-direct-main-update': (c: BaseRuleConfig) => new NoDirectMainUpdateRule(c as NoDirectMainUpdateConfig),\n 'no-js-files': (c: BaseRuleConfig) => new NoJsFilesRule(c as NoJsFilesConfig),\n 'no-edit-on-main': (c: BaseRuleConfig) => new NoEditOnMainRule(c as NoEditOnMainConfig),\n};\n\n// Index the typed config by rule name. Each value is the rule's *Config (a plain object from\n// JSON), or undefined when the rule has no entry yet (the sync check reports those).\nfunction asConfigMap(config: WebpiecesRulesConfig): Record<string, BaseRuleConfig | undefined> {\n // webpieces-disable no-any-unknown -- index the typed config by dynamic rule name\n return config as unknown as Record<string, BaseRuleConfig | undefined>;\n}\n\nexport function loadRules(config: WebpiecesRulesConfig, workspaceRoot: string): readonly Rule[] {\n const builtIns = loadBuiltInRules(config);\n const custom = loadCustomRules(config, workspaceRoot);\n return [...builtIns, ...custom];\n}\n\nfunction loadBuiltInRules(config: WebpiecesRulesConfig): Rule[] {\n const map = asConfigMap(config);\n const rules: Rule[] = [];\n for (const name of builtInRuleNames) {\n const factory = BUILT_IN_RULE_MAP[name];\n if (!factory) {\n process.stderr.write(`[ai-hooks] unknown built-in rule: ${name}\\n`);\n continue;\n }\n const ruleConfig = map[name] ?? new EmptyRuleConfig();\n rules.push(factory(ruleConfig));\n }\n return rules;\n}\n\nfunction loadCustomRules(config: WebpiecesRulesConfig, workspaceRoot: string): Rule[] {\n const dirs = config.rulesDir ?? [];\n // webpieces-disable no-any-unknown -- index the typed config by dynamic custom-rule name\n const map = config as unknown as Record<string, RuleOptions | undefined>;\n const rules: Rule[] = [];\n for (const plain of loadCustomPlainRules(dirs, workspaceRoot)) {\n const rawConfig = map[plain.name] ?? {};\n rules.push(new CustomRuleAdapter(plain, rawConfig));\n }\n return rules;\n}\n\nfunction loadCustomPlainRules(rulesDirs: readonly string[], workspaceRoot: string): PlainRule[] {\n const modules: PlainRule[] = [];\n for (const dir of rulesDirs) {\n const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);\n if (!fs.existsSync(absDir)) {\n process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\\n`);\n continue;\n }\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(absDir).filter((e: string) => e.endsWith('.js'));\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot read custom rules directory '${absDir}'`, { cause: error });\n }\n for (const entry of entries) {\n const full = path.join(absDir, entry);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(full);\n const candidate = mod.default || mod;\n if (validateRule(candidate)) modules.push(candidate);\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot load custom rule '${full}'`, { cause: error });\n }\n }\n }\n return modules;\n}\n\n// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary\nfunction validateRule(rule: unknown): rule is PlainRule {\n if (!rule || typeof rule !== 'object') {\n process.stderr.write('[ai-hooks] rule is not an object, skipping\\n');\n return false;\n }\n // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary\n const obj = rule as Record<string, unknown>;\n for (const field of REQUIRED_FIELDS) {\n if (obj[field] === undefined) {\n const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';\n process.stderr.write(`[ai-hooks] rule \"${name}\" missing required field: ${field}\\n`);\n return false;\n }\n }\n if (!VALID_SCOPES.has(obj['scope'] as string)) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" has invalid scope: ${String(obj['scope'])}\\n`);\n return false;\n }\n if (!Array.isArray(obj['files'])) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" files must be an array\\n`);\n return false;\n }\n if (typeof obj['check'] !== 'function') {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" check must be a function\\n`);\n return false;\n }\n return true;\n}\n\nexport function globMatches(pattern: string, filePath: string): boolean {\n const regex = globToRegex(pattern);\n return regex.test(filePath);\n}\n\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n"]}
@@ -0,0 +1,38 @@
1
+ import { AbstractRule, BaseRuleConfig, RuleOptions } from '@webpieces/rules-config';
2
+ import type { EditContext, BashContext, FileContext, Violation, Rule } from './types';
3
+ /**
4
+ * A concrete, instantiable BaseRuleConfig used as a fallback when a built-in rule has no
5
+ * entry in webpieces.config.json yet. It carries no values, so the rule's `description`,
6
+ * `fixHint`, and `defaultOptions` (which are config-independent) are still readable for the
7
+ * out-of-sync report — the rule never actually runs in that state (the sync check blocks first).
8
+ */
9
+ export declare class EmptyRuleConfig extends BaseRuleConfig {
10
+ }
11
+ /**
12
+ * Scope-specific base for edit rules. Extends the shared AbstractRule (which owns `name`,
13
+ * the typed `config`, and `shouldRun()`), and adds the ai-hook execution surface.
14
+ */
15
+ export declare abstract class EditRuleBase<C extends BaseRuleConfig> extends AbstractRule<C> implements Rule {
16
+ readonly scope: "edit";
17
+ readonly files: readonly string[];
18
+ readonly defaultOptions: RuleOptions;
19
+ abstract readonly description: string;
20
+ abstract readonly fixHint: readonly string[];
21
+ abstract check(ctx: EditContext): readonly Violation[];
22
+ }
23
+ export declare abstract class FileRuleBase<C extends BaseRuleConfig> extends AbstractRule<C> implements Rule {
24
+ readonly scope: "file";
25
+ readonly files: readonly string[];
26
+ readonly defaultOptions: RuleOptions;
27
+ abstract readonly description: string;
28
+ abstract readonly fixHint: readonly string[];
29
+ abstract check(ctx: FileContext): readonly Violation[];
30
+ }
31
+ export declare abstract class BashRuleBase<C extends BaseRuleConfig> extends AbstractRule<C> implements Rule {
32
+ readonly scope: "bash";
33
+ readonly files: readonly string[];
34
+ readonly defaultOptions: RuleOptions;
35
+ abstract readonly description: string;
36
+ abstract readonly fixHint: readonly string[];
37
+ abstract check(ctx: BashContext): readonly Violation[];
38
+ }