@webpieces/ai-hook-rules 0.3.168 → 0.3.169

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 (42) hide show
  1. package/package.json +2 -2
  2. package/src/adapters/global-hook.js +14 -25
  3. package/src/adapters/global-hook.js.map +1 -1
  4. package/src/core/rules/branch-creation-guard.js +3 -0
  5. package/src/core/rules/branch-creation-guard.js.map +1 -1
  6. package/src/core/rules/catch-error-pattern.js +1 -0
  7. package/src/core/rules/catch-error-pattern.js.map +1 -1
  8. package/src/core/rules/max-file-lines.js +4 -3
  9. package/src/core/rules/max-file-lines.js.map +1 -1
  10. package/src/core/rules/no-any-unknown.js +1 -0
  11. package/src/core/rules/no-any-unknown.js.map +1 -1
  12. package/src/core/rules/no-destructure.js +2 -1
  13. package/src/core/rules/no-destructure.js.map +1 -1
  14. package/src/core/rules/no-direct-main-update.js +1 -0
  15. package/src/core/rules/no-direct-main-update.js.map +1 -1
  16. package/src/core/rules/no-edit-on-main.js +4 -2
  17. package/src/core/rules/no-edit-on-main.js.map +1 -1
  18. package/src/core/rules/no-implicit-any.js +1 -0
  19. package/src/core/rules/no-implicit-any.js.map +1 -1
  20. package/src/core/rules/no-js-files.js +4 -3
  21. package/src/core/rules/no-js-files.js.map +1 -1
  22. package/src/core/rules/no-shell-substitution.js +1 -0
  23. package/src/core/rules/no-shell-substitution.js.map +1 -1
  24. package/src/core/rules/no-symbol-di-tokens.js +4 -1
  25. package/src/core/rules/no-symbol-di-tokens.js.map +1 -1
  26. package/src/core/rules/no-unmanaged-exceptions.js +1 -0
  27. package/src/core/rules/no-unmanaged-exceptions.js.map +1 -1
  28. package/src/core/rules/pr-creation-guard.js +4 -0
  29. package/src/core/rules/pr-creation-guard.js.map +1 -1
  30. package/src/core/rules/pr-merge-cleanup.js +1 -0
  31. package/src/core/rules/pr-merge-cleanup.js.map +1 -1
  32. package/src/core/rules/require-return-type.js +1 -0
  33. package/src/core/rules/require-return-type.js.map +1 -1
  34. package/src/core/rules/throw-cause-required.js +1 -0
  35. package/src/core/rules/throw-cause-required.js.map +1 -1
  36. package/src/core/rules/validate-ts-in-src.js +6 -6
  37. package/src/core/rules/validate-ts-in-src.js.map +1 -1
  38. package/src/core/runner.js +82 -32
  39. package/src/core/runner.js.map +1 -1
  40. package/src/core/types.d.ts +7 -0
  41. package/src/core/types.js +10 -1
  42. package/src/core/types.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/ai-hook-rules",
3
- "version": "0.3.168",
3
+ "version": "0.3.169",
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",
@@ -36,7 +36,7 @@
36
36
  "directory": "packages/tooling/ai-hook-rules"
37
37
  },
38
38
  "dependencies": {
39
- "@webpieces/rules-config": "0.3.168"
39
+ "@webpieces/rules-config": "0.3.169"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public"
@@ -18,16 +18,6 @@ function readSkipHooks(cwd) {
18
18
  }
19
19
  function run(rawInput) {
20
20
  const cwd = process.cwd();
21
- let payload = null;
22
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
23
- try {
24
- if (rawInput.trim()) {
25
- payload = JSON.parse(rawInput);
26
- }
27
- }
28
- catch (err) {
29
- //const error = toError(err); -- malformed stdin is intentionally ignored; must not crash global hook
30
- }
31
21
  // 1. Check .skiphooks — if valid and not expired, allow everything
32
22
  const skipHooks = readSkipHooks(cwd);
33
23
  if (skipHooks !== null) {
@@ -36,12 +26,7 @@ function run(rawInput) {
36
26
  process.exit(0);
37
27
  }
38
28
  }
39
- // 2. Always allow writing .webpieces/skiphooks (this is the escape hatch)
40
- const filePath = payload?.tool_input?.file_path ?? '';
41
- if (payload?.tool_name === 'Write' && filePath.endsWith('.webpieces/skiphooks')) {
42
- process.exit(0);
43
- }
44
- // 3. Delegate to per-repo hook if present
29
+ // 2. Delegate to per-repo hook if present
45
30
  const localHook = (0, path_1.join)(cwd, 'node_modules', '.bin', 'wp-ai-hook');
46
31
  if ((0, fs_1.existsSync)(localHook)) {
47
32
  const result = (0, child_process_1.spawnSync)(localHook, [], {
@@ -54,18 +39,22 @@ function run(rawInput) {
54
39
  process.stderr.write(result.stderr);
55
40
  process.exit(result.status ?? 0);
56
41
  }
57
- // 4. No local hook found — block and instruct AI
58
- process.stderr.write('STOP. DO NOT TAKE ANY ACTION. You MUST ask the human and wait for their answer.\n' +
42
+ // 3. No local hook found — block and tell AI to present human-only options
43
+ const skipPath = (0, path_1.join)(cwd, '.webpieces', 'skiphooks');
44
+ process.stderr.write('STOP. DO NOT TAKE ANY ACTION. You MUST present these options to the human and wait.\n' +
59
45
  '\n' +
60
- 'This project is missing the webpieces AI hook (./node_modules/.bin/wp-ai-hook not found).\n' +
46
+ `Hook cwd: ${cwd}\n` +
47
+ `Expected binary: ${localHook} (not found)\n` +
61
48
  '\n' +
62
- 'Present these options to the human and wait for their choice:\n' +
63
- ' A) PREFERRED: Ask the human to run `npm install @webpieces/ai-hook-rules` (or pnpm install)\n' +
64
- ' B) Skip hooks temporarily: ask human how long (1 hour / 1 day / forever), then write\n' +
65
- ' .webpieces/skiphooks with {"expires": <unix epoch seconds>, "reason": "..."}\n' +
49
+ 'Tell the human to run ONE of:\n' +
50
+ ' A) Install the hook (preferred):\n' +
51
+ ' npm install @webpieces/ai-hook-rules\n' +
52
+ ' B) Skip hooks for 1 hour (human runs in terminal):\n' +
53
+ ` echo \'{"expires":\'$(( $(date +%s) + 3600 ))\', "reason":"<why>"}\' > ${skipPath}\n` +
54
+ ' C) Skip hooks indefinitely (requires explicit human approval):\n' +
55
+ ` echo \'{"expires":null, "reason":"<why>"}\' > ${skipPath}\n` +
66
56
  '\n' +
67
- 'WARNING: DO NOT disable permanently (expires: null) without explicit human permission.\n' +
68
- 'You are BLOCKED. Ask the human now and wait for their response.\n');
57
+ 'You are BLOCKED until the human runs one of the above commands.\n');
69
58
  process.exit(2);
70
59
  }
71
60
  let stdinData = '';
@@ -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 } from 'path';\nimport { spawnSync } from 'child_process';\n\ninterface SkipHooksFile {\n expires: number | null;\n reason?: string;\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 = 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 });\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. DO NOT TAKE ANY ACTION. You MUST ask the human and wait for their answer.\\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 options to the human and wait for their choice:\\n' +\n ' A) PREFERRED: Ask the human to run `npm install @webpieces/ai-hook-rules` (or pnpm install)\\n' +\n ' B) Skip hooks temporarily: ask human how long (1 hour / 1 day / forever), then write\\n' +\n ' .webpieces/skiphooks with {\"expires\": <unix epoch seconds>, \"reason\": \"...\"}\\n' +\n '\\n' +\n 'WARNING: DO NOT disable permanently (expires: null) without explicit human permission.\\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":";;AAAA,2BAA8C;AAC9C,+BAA4B;AAC5B,iDAA0C;AAO1C,SAAS,aAAa,CAAC,GAAW;IAC9B,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,IAAA,WAAI,EAAC,GAAG,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;QACtD,IAAI,CAAC,IAAA,eAAU,EAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QACvC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAA,iBAAY,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAkB,CAAC;IACvE,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,oIAAoI;QACpI,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED,SAAS,GAAG,CAAC,QAAgB;IACzB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAE1B,mEAAmE;IACnE,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACrB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QACrC,IAAI,SAAS,CAAC,OAAO,KAAK,IAAI,IAAI,UAAU,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC;YAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;IACL,CAAC;IAED,0CAA0C;IAC1C,MAAM,SAAS,GAAG,IAAA,WAAI,EAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;IAClE,IAAI,IAAA,eAAU,EAAC,SAAS,CAAC,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,IAAA,yBAAS,EAAC,SAAS,EAAE,EAAE,EAAE;YACpC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;YAC5B,QAAQ,EAAE,QAAQ;SACrB,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC/D,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC/D,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,IAAA,WAAI,EAAC,GAAG,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,uFAAuF;QACnF,IAAI;QACJ,aAAa,GAAG,IAAI;QACpB,oBAAoB,SAAS,gBAAgB;QAC7C,IAAI;QACJ,iCAAiC;QACjC,sCAAsC;QACtC,+CAA+C;QAC/C,wDAAwD;QACxD,iFAAiF,QAAQ,IAAI;QAC7F,oEAAoE;QACpE,wDAAwD,QAAQ,IAAI;QACpE,IAAI;QACJ,mEAAmE,CAC1E,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,IAAI,SAAS,GAAG,EAAE,CAAC;AACnB,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;AACvB,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;AAClC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;IACvC,SAAS,IAAI,KAAK,CAAC;AACvB,CAAC,CAAC,CAAC;AACH,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;IACzB,GAAG,CAAC,SAAS,CAAC,CAAC;AACnB,CAAC,CAAC,CAAC","sourcesContent":["import { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { spawnSync } from 'child_process';\n\ninterface SkipHooksFile {\n expires: number | null;\n reason?: string;\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 = process.cwd();\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. 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 });\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 // 3. No local hook found — block and tell AI to present human-only options\n const skipPath = join(cwd, '.webpieces', 'skiphooks');\n process.stderr.write(\n 'STOP. DO NOT TAKE ANY ACTION. You MUST present these options to the human and wait.\\n' +\n '\\n' +\n `Hook cwd: ${cwd}\\n` +\n `Expected binary: ${localHook} (not found)\\n` +\n '\\n' +\n 'Tell the human to run ONE of:\\n' +\n ' A) Install the hook (preferred):\\n' +\n ' npm install @webpieces/ai-hook-rules\\n' +\n ' B) Skip hooks for 1 hour (human runs in terminal):\\n' +\n ` echo \\'{\"expires\":\\'$(( $(date +%s) + 3600 ))\\', \"reason\":\"<why>\"}\\' > ${skipPath}\\n` +\n ' C) Skip hooks indefinitely (requires explicit human approval):\\n' +\n ` echo \\'{\"expires\":null, \"reason\":\"<why>\"}\\' > ${skipPath}\\n` +\n '\\n' +\n 'You are BLOCKED until the human runs one of the above commands.\\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"]}
@@ -12,6 +12,9 @@ const branchCreationGuard = {
12
12
  scope: 'bash',
13
13
  files: [],
14
14
  defaultOptions: { subBranchNaming: 'feature/<ticket>/<short-description>' },
15
+ configSchema: {
16
+ subBranchNaming: new types_1.FieldSchema('string', 'Naming convention shown when human approves a sub-branch off a feature branch'),
17
+ },
15
18
  fixHint: FIX_HINT,
16
19
  check(ctx) {
17
20
  const requestedName = extractBranchName(ctx.command);
@@ -1 +1 @@
1
- {"version":3,"file":"branch-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/branch-creation-guard.ts"],"names":[],"mappings":";;AAAA,iDAAyC;AAEzC,oCAA0C;AAE1C,MAAM,QAAQ,GAAsB;IAChC,oFAAoF;IACpF,yJAAyJ;CAC5J,CAAC;AAEF,MAAM,mBAAmB,GAAa;IAClC,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EAAE,oEAAoE;IACjF,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,EAAE;IACT,cAAc,EAAE,EAAE,eAAe,EAAE,sCAAsC,EAAE;IAC3E,OAAO,EAAE,QAAQ;IAEjB,KAAK,CAAC,GAAgB;QAClB,MAAM,aAAa,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrD,IAAI,CAAC,aAAa;YAAE,OAAO,EAAE,CAAC;QAE9B,MAAM,aAAa,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE;YAC9D,GAAG,EAAE,GAAG,CAAC,aAAa;YACtB,QAAQ,EAAE,MAAM;SACnB,CAAC,CAAC,IAAI,EAAE,CAAC;QAEV,IAAI,aAAa,KAAK,MAAM,EAAE,CAAC;YAC3B,OAAO,mBAAmB,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QACnD,CAAC;QAED,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,eAAe,aAAa,kDAAkD,CACjF,CAAC,CAAC;IACP,CAAC;CACJ,CAAC;AAEF,SAAS,mBAAmB,CAAC,GAAgB,EAAE,aAAqB;IAChE,IAAA,wBAAQ,EAAC,+BAA+B,EAAE;QACtC,GAAG,EAAE,GAAG,CAAC,aAAa;QACtB,QAAQ,EAAE,MAAM;KACnB,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,IAAA,wBAAQ,EAAC,wCAAwC,EAAE;QAChE,GAAG,EAAE,GAAG,CAAC,aAAa;QACtB,QAAQ,EAAE,MAAM;KACnB,CAAC,CAAC,IAAI,EAAE,CAAC;IACV,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACrC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,iBAAiB,KAAK,gGAAgG,aAAa,IAAI,CAC1I,CAAC,CAAC;IACP,CAAC;IACD,OAAO,EAAE,CAAC;AACd,CAAC;AAED,MAAM,eAAe,GAAa;IAC9B,mCAAmC;IACnC,iCAAiC;IACjC,8CAA8C;CACjD,CAAC;AAEF,SAAS,iBAAiB,CAAC,OAAe;IACtC,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;QACpC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,kBAAe,mBAAmB,CAAC","sourcesContent":["import { execSync } from 'child_process';\nimport type { BashRule, BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst FIX_HINT: readonly string[] = [\n \"Run 'git checkout main && git pull origin main', then create your branch from main\",\n \"If you truly need a sub-branch (requires human approval), name it using the convention in webpieces.config.json 'branch-creation-guard.subBranchNaming'\",\n];\n\nconst branchCreationGuard: BashRule = {\n name: 'branch-creation-guard',\n description: 'Block new-branch creation when main is stale, or when not on main.',\n scope: 'bash',\n files: [],\n defaultOptions: { subBranchNaming: 'feature/<ticket>/<short-description>' },\n fixHint: FIX_HINT,\n\n check(ctx: BashContext): readonly Violation[] {\n const requestedName = extractBranchName(ctx.command);\n if (!requestedName) return [];\n\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n\n if (currentBranch === 'main') {\n return checkMainIsUpToDate(ctx, requestedName);\n }\n\n return [new V(\n 1,\n truncate(ctx.command),\n `You are on '${currentBranch}', not main. Branches must be created from main.`,\n )];\n },\n};\n\nfunction checkMainIsUpToDate(ctx: BashContext, requestedName: string): readonly Violation[] {\n execSync('git fetch origin main --quiet', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n });\n const countStr = execSync('git rev-list HEAD..origin/main --count', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n const count = parseInt(countStr, 10);\n if (count > 0) {\n return [new V(\n 1,\n truncate(ctx.command),\n `Local main is ${count} commit(s) behind origin/main. Run 'git pull origin main' first, then retry creating branch '${requestedName}'.`,\n )];\n }\n return [];\n}\n\nconst BRANCH_PATTERNS: RegExp[] = [\n /git\\s+checkout\\s+-[bB]\\s+([^\\s]+)/,\n /git\\s+switch\\s+-[cC]\\s+([^\\s]+)/,\n /git\\s+branch\\s+(?!-[dDmMrRla])([^\\s-][^\\s]*)/,\n];\n\nfunction extractBranchName(command: string): string | null {\n for (const pattern of BRANCH_PATTERNS) {\n const m = pattern.exec(command);\n if (m) return m[1];\n }\n return null;\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport default branchCreationGuard;\n"]}
1
+ {"version":3,"file":"branch-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/branch-creation-guard.ts"],"names":[],"mappings":";;AAAA,iDAAyC;AAEzC,oCAAuD;AAEvD,MAAM,QAAQ,GAAsB;IAChC,oFAAoF;IACpF,yJAAyJ;CAC5J,CAAC;AAEF,MAAM,mBAAmB,GAAa;IAClC,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EAAE,oEAAoE;IACjF,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,EAAE;IACT,cAAc,EAAE,EAAE,eAAe,EAAE,sCAAsC,EAAE;IAC3E,YAAY,EAAE;QACV,eAAe,EAAE,IAAI,mBAAW,CAAC,QAAQ,EAAE,+EAA+E,CAAC;KAC9H;IACD,OAAO,EAAE,QAAQ;IAEjB,KAAK,CAAC,GAAgB;QAClB,MAAM,aAAa,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrD,IAAI,CAAC,aAAa;YAAE,OAAO,EAAE,CAAC;QAE9B,MAAM,aAAa,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE;YAC9D,GAAG,EAAE,GAAG,CAAC,aAAa;YACtB,QAAQ,EAAE,MAAM;SACnB,CAAC,CAAC,IAAI,EAAE,CAAC;QAEV,IAAI,aAAa,KAAK,MAAM,EAAE,CAAC;YAC3B,OAAO,mBAAmB,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QACnD,CAAC;QAED,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,eAAe,aAAa,kDAAkD,CACjF,CAAC,CAAC;IACP,CAAC;CACJ,CAAC;AAEF,SAAS,mBAAmB,CAAC,GAAgB,EAAE,aAAqB;IAChE,IAAA,wBAAQ,EAAC,+BAA+B,EAAE;QACtC,GAAG,EAAE,GAAG,CAAC,aAAa;QACtB,QAAQ,EAAE,MAAM;KACnB,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,IAAA,wBAAQ,EAAC,wCAAwC,EAAE;QAChE,GAAG,EAAE,GAAG,CAAC,aAAa;QACtB,QAAQ,EAAE,MAAM;KACnB,CAAC,CAAC,IAAI,EAAE,CAAC;IACV,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACrC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,iBAAiB,KAAK,gGAAgG,aAAa,IAAI,CAC1I,CAAC,CAAC;IACP,CAAC;IACD,OAAO,EAAE,CAAC;AACd,CAAC;AAED,MAAM,eAAe,GAAa;IAC9B,mCAAmC;IACnC,iCAAiC;IACjC,8CAA8C;CACjD,CAAC;AAEF,SAAS,iBAAiB,CAAC,OAAe;IACtC,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;QACpC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,kBAAe,mBAAmB,CAAC","sourcesContent":["import { execSync } from 'child_process';\nimport type { BashRule, BashContext, Violation } from '../types';\nimport { Violation as V, FieldSchema } from '../types';\n\nconst FIX_HINT: readonly string[] = [\n \"Run 'git checkout main && git pull origin main', then create your branch from main\",\n \"If you truly need a sub-branch (requires human approval), name it using the convention in webpieces.config.json 'branch-creation-guard.subBranchNaming'\",\n];\n\nconst branchCreationGuard: BashRule = {\n name: 'branch-creation-guard',\n description: 'Block new-branch creation when main is stale, or when not on main.',\n scope: 'bash',\n files: [],\n defaultOptions: { subBranchNaming: 'feature/<ticket>/<short-description>' },\n configSchema: {\n subBranchNaming: new FieldSchema('string', 'Naming convention shown when human approves a sub-branch off a feature branch'),\n },\n fixHint: FIX_HINT,\n\n check(ctx: BashContext): readonly Violation[] {\n const requestedName = extractBranchName(ctx.command);\n if (!requestedName) return [];\n\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n\n if (currentBranch === 'main') {\n return checkMainIsUpToDate(ctx, requestedName);\n }\n\n return [new V(\n 1,\n truncate(ctx.command),\n `You are on '${currentBranch}', not main. Branches must be created from main.`,\n )];\n },\n};\n\nfunction checkMainIsUpToDate(ctx: BashContext, requestedName: string): readonly Violation[] {\n execSync('git fetch origin main --quiet', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n });\n const countStr = execSync('git rev-list HEAD..origin/main --count', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n const count = parseInt(countStr, 10);\n if (count > 0) {\n return [new V(\n 1,\n truncate(ctx.command),\n `Local main is ${count} commit(s) behind origin/main. Run 'git pull origin main' first, then retry creating branch '${requestedName}'.`,\n )];\n }\n return [];\n}\n\nconst BRANCH_PATTERNS: RegExp[] = [\n /git\\s+checkout\\s+-[bB]\\s+([^\\s]+)/,\n /git\\s+switch\\s+-[cC]\\s+([^\\s]+)/,\n /git\\s+branch\\s+(?!-[dDmMrRla])([^\\s-][^\\s]*)/,\n];\n\nfunction extractBranchName(command: string): string | null {\n for (const pattern of BRANCH_PATTERNS) {\n const m = pattern.exec(command);\n if (m) return m[1];\n }\n return null;\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport default branchCreationGuard;\n"]}
@@ -18,6 +18,7 @@ const catchErrorPatternRule = {
18
18
  scope: 'edit',
19
19
  files: ['**/*.ts', '**/*.tsx'],
20
20
  defaultOptions: {},
21
+ configSchema: {},
21
22
  fixHint: [
22
23
  'Add as first statement inside the catch block: const error = toError(err);',
23
24
  'To explicitly ignore the error: //const error = toError(err);',
@@ -1 +1 @@
1
- {"version":3,"file":"catch-error-pattern.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/catch-error-pattern.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { writeTemplateIfMissing } from '../instruct-ai-writer';\n\n/**\n * Matches a catch clause opening: } catch (paramName: typeAnnotation) {\n * Captures: group 1 = param name, group 2 = type annotation (if present)\n */\nconst CATCH_PATTERN = /\\bcatch\\s*\\(\\s*(\\w+)(?:\\s*:\\s*(\\w+))?\\s*\\)/;\n\n/**\n * Matches the required toError first statement (with or without comment-out).\n * Group 1 = variable name, group 2 = param passed to toError\n */\nconst TO_ERROR_PATTERN = /^\\s*(?:\\/\\/\\s*)?const\\s+(\\w+)\\s*=\\s*toError\\(\\s*(\\w+)\\s*\\)\\s*;?\\s*$/;\n\nconst catchErrorPatternRule: EditRule = {\n name: 'catch-error-pattern',\n description: 'Catch blocks must use: catch (err: unknown) { const error = toError(err); }',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n fixHint: [\n 'Add as first statement inside the catch block: const error = toError(err);',\n 'To explicitly ignore the error: //const error = toError(err);',\n 'For nested catches use numbered names: catch (err2: unknown) { const error2 = toError(err2); }',\n 'To suppress this rule: // webpieces-disable catch-error-pattern -- <reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n const lines = ctx.strippedLines;\n\n for (let i = 0; i < lines.length; i += 1) {\n const stripped = lines[i];\n const catchMatch = CATCH_PATTERN.exec(stripped);\n if (!catchMatch) continue;\n\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'catch-error-pattern')) continue;\n\n const actualParam = catchMatch[1];\n const typeAnnotation = catchMatch[2];\n\n // Determine expected names from suffix on the actual param (err, err2, err3...)\n const suffixMatch = actualParam.match(/^err(\\d*)$/);\n const suffix = suffixMatch ? suffixMatch[1] : '';\n const expectedParam = 'err' + suffix;\n const expectedVar = 'error' + suffix;\n\n // Check parameter name\n if (actualParam !== expectedParam) {\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Catch parameter must be named \"${expectedParam}\" (or \"err2\", \"err3\" for nested catches), got \"${actualParam}\"`,\n ));\n }\n\n // Check type annotation is unknown\n if (typeAnnotation !== 'unknown') {\n const msg = typeAnnotation\n ? `Catch parameter must be typed as \"unknown\": catch (${expectedParam}: unknown), got \"${typeAnnotation}\"`\n : `Catch parameter must be typed as \"unknown\": catch (${expectedParam}: unknown)`;\n violations.push(new V(lineNum, ctx.lines[i].trim(), msg));\n }\n\n // Find next non-blank line after the catch opening to check for toError\n const toErrorResult = findToErrorStatement(lines, i + 1);\n if (toErrorResult === 'not-found') {\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Catch block must call toError(${actualParam}) as first statement: const ${expectedVar} = toError(${actualParam}); or //const ${expectedVar} = toError(${actualParam});`,\n ));\n } else if (toErrorResult !== 'end-of-content') {\n // Validate variable name and param match\n if (toErrorResult.varName !== expectedVar) {\n const toErrorLineNum = toErrorResult.lineIndex + 1;\n violations.push(new V(\n toErrorLineNum,\n ctx.lines[toErrorResult.lineIndex].trim(),\n `Error variable must be named \"${expectedVar}\", got \"${toErrorResult.varName}\"`,\n ));\n }\n if (toErrorResult.paramName !== actualParam) {\n const toErrorLineNum = toErrorResult.lineIndex + 1;\n violations.push(new V(\n toErrorLineNum,\n ctx.lines[toErrorResult.lineIndex].trim(),\n `toError() must be called with \"${actualParam}\", got \"${toErrorResult.paramName}\"`,\n ));\n }\n }\n }\n if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');\n return violations;\n },\n};\n\ninterface ToErrorMatch {\n varName: string;\n paramName: string;\n lineIndex: number;\n}\n\nfunction findToErrorStatement(lines: readonly string[], startIndex: number): ToErrorMatch | 'not-found' | 'end-of-content' {\n for (let j = startIndex; j < lines.length; j += 1) {\n const line = lines[j].trim();\n if (line === '' || line === '{') continue;\n\n const match = TO_ERROR_PATTERN.exec(line);\n if (match) {\n return { varName: match[1], paramName: match[2], lineIndex: j };\n }\n // First non-blank line is not a toError call\n return 'not-found';\n }\n // Ran off the end of the edit content — can't validate further\n return 'end-of-content';\n}\n\nexport default catchErrorPatternRule;\n"]}
1
+ {"version":3,"file":"catch-error-pattern.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/catch-error-pattern.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { writeTemplateIfMissing } from '../instruct-ai-writer';\n\n/**\n * Matches a catch clause opening: } catch (paramName: typeAnnotation) {\n * Captures: group 1 = param name, group 2 = type annotation (if present)\n */\nconst CATCH_PATTERN = /\\bcatch\\s*\\(\\s*(\\w+)(?:\\s*:\\s*(\\w+))?\\s*\\)/;\n\n/**\n * Matches the required toError first statement (with or without comment-out).\n * Group 1 = variable name, group 2 = param passed to toError\n */\nconst TO_ERROR_PATTERN = /^\\s*(?:\\/\\/\\s*)?const\\s+(\\w+)\\s*=\\s*toError\\(\\s*(\\w+)\\s*\\)\\s*;?\\s*$/;\n\nconst catchErrorPatternRule: EditRule = {\n name: 'catch-error-pattern',\n description: 'Catch blocks must use: catch (err: unknown) { const error = toError(err); }',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n configSchema: {},\n fixHint: [\n 'Add as first statement inside the catch block: const error = toError(err);',\n 'To explicitly ignore the error: //const error = toError(err);',\n 'For nested catches use numbered names: catch (err2: unknown) { const error2 = toError(err2); }',\n 'To suppress this rule: // webpieces-disable catch-error-pattern -- <reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n const lines = ctx.strippedLines;\n\n for (let i = 0; i < lines.length; i += 1) {\n const stripped = lines[i];\n const catchMatch = CATCH_PATTERN.exec(stripped);\n if (!catchMatch) continue;\n\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'catch-error-pattern')) continue;\n\n const actualParam = catchMatch[1];\n const typeAnnotation = catchMatch[2];\n\n // Determine expected names from suffix on the actual param (err, err2, err3...)\n const suffixMatch = actualParam.match(/^err(\\d*)$/);\n const suffix = suffixMatch ? suffixMatch[1] : '';\n const expectedParam = 'err' + suffix;\n const expectedVar = 'error' + suffix;\n\n // Check parameter name\n if (actualParam !== expectedParam) {\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Catch parameter must be named \"${expectedParam}\" (or \"err2\", \"err3\" for nested catches), got \"${actualParam}\"`,\n ));\n }\n\n // Check type annotation is unknown\n if (typeAnnotation !== 'unknown') {\n const msg = typeAnnotation\n ? `Catch parameter must be typed as \"unknown\": catch (${expectedParam}: unknown), got \"${typeAnnotation}\"`\n : `Catch parameter must be typed as \"unknown\": catch (${expectedParam}: unknown)`;\n violations.push(new V(lineNum, ctx.lines[i].trim(), msg));\n }\n\n // Find next non-blank line after the catch opening to check for toError\n const toErrorResult = findToErrorStatement(lines, i + 1);\n if (toErrorResult === 'not-found') {\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Catch block must call toError(${actualParam}) as first statement: const ${expectedVar} = toError(${actualParam}); or //const ${expectedVar} = toError(${actualParam});`,\n ));\n } else if (toErrorResult !== 'end-of-content') {\n // Validate variable name and param match\n if (toErrorResult.varName !== expectedVar) {\n const toErrorLineNum = toErrorResult.lineIndex + 1;\n violations.push(new V(\n toErrorLineNum,\n ctx.lines[toErrorResult.lineIndex].trim(),\n `Error variable must be named \"${expectedVar}\", got \"${toErrorResult.varName}\"`,\n ));\n }\n if (toErrorResult.paramName !== actualParam) {\n const toErrorLineNum = toErrorResult.lineIndex + 1;\n violations.push(new V(\n toErrorLineNum,\n ctx.lines[toErrorResult.lineIndex].trim(),\n `toError() must be called with \"${actualParam}\", got \"${toErrorResult.paramName}\"`,\n ));\n }\n }\n }\n if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');\n return violations;\n },\n};\n\ninterface ToErrorMatch {\n varName: string;\n paramName: string;\n lineIndex: number;\n}\n\nfunction findToErrorStatement(lines: readonly string[], startIndex: number): ToErrorMatch | 'not-found' | 'end-of-content' {\n for (let j = startIndex; j < lines.length; j += 1) {\n const line = lines[j].trim();\n if (line === '' || line === '{') continue;\n\n const match = TO_ERROR_PATTERN.exec(line);\n if (match) {\n return { varName: match[1], paramName: match[2], lineIndex: j };\n }\n // First non-blank line is not a toError call\n return 'not-found';\n }\n // Ran off the end of the edit content — can't validate further\n return 'end-of-content';\n}\n\nexport default catchErrorPatternRule;\n"]}
@@ -10,14 +10,15 @@ const maxFileLinesRule = {
10
10
  scope: 'file',
11
11
  files: ['**/*.ts', '**/*.tsx'],
12
12
  defaultOptions: { limit: DEFAULT_LIMIT },
13
+ configSchema: {
14
+ limit: new types_1.FieldSchema('number', 'Max lines a file may reach before the rule triggers'),
15
+ },
13
16
  fixHint: [
14
17
  'READ .webpieces/instruct-ai/webpieces.filesize.md for step-by-step refactoring guidance.',
15
18
  '// eslint-disable-next-line @webpieces/max-file-lines (also suppresses the eslint rule)',
16
19
  ],
17
20
  check(ctx) {
18
- const limit = typeof ctx.options['limit'] === 'number'
19
- ? ctx.options['limit']
20
- : DEFAULT_LIMIT;
21
+ const limit = ctx.options['limit'];
21
22
  if (ctx.projectedFileLines <= limit)
22
23
  return [];
23
24
  (0, rules_config_1.writeTemplateIfMissing)(ctx.workspaceRoot, INSTRUCT_FILE);
@@ -1 +1 @@
1
- {"version":3,"file":"max-file-lines.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/max-file-lines.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAC1C,0DAAiE;AAEjE,MAAM,aAAa,GAAG,GAAG,CAAC;AAC1B,MAAM,aAAa,GAAG,uBAAuB,CAAC;AAE9C,MAAM,gBAAgB,GAAa;IAC/B,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,6CAA6C;IAC1D,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE;IACxC,OAAO,EAAE;QACL,0FAA0F;QAC1F,0FAA0F;KAC7F;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,KAAK,GAAG,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,QAAQ;YAClD,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAW;YAChC,CAAC,CAAC,aAAa,CAAC;QACpB,IAAI,GAAG,CAAC,kBAAkB,IAAI,KAAK;YAAE,OAAO,EAAE,CAAC;QAC/C,IAAA,qCAAsB,EAAC,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QACzD,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,cAAc,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,SAAS,EACrD,gBAAgB,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,yBAAyB,MAAM,CAAC,KAAK,CAAC,sGAAsG,CAC7L,CAAC,CAAC;IACP,CAAC;CACJ,CAAC;AAEF,kBAAe,gBAAgB,CAAC","sourcesContent":["import type { FileRule, FileContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { writeTemplateIfMissing } from '@webpieces/rules-config';\n\nconst DEFAULT_LIMIT = 900;\nconst INSTRUCT_FILE = 'webpieces.filesize.md';\n\nconst maxFileLinesRule: FileRule = {\n name: 'max-file-lines',\n description: 'Cap file length at a configured line limit.',\n scope: 'file',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: { limit: DEFAULT_LIMIT },\n fixHint: [\n 'READ .webpieces/instruct-ai/webpieces.filesize.md for step-by-step refactoring guidance.',\n '// eslint-disable-next-line @webpieces/max-file-lines (also suppresses the eslint rule)',\n ],\n\n check(ctx: FileContext): readonly Violation[] {\n const limit = typeof ctx.options['limit'] === 'number'\n ? ctx.options['limit'] as number\n : DEFAULT_LIMIT;\n if (ctx.projectedFileLines <= limit) return [];\n writeTemplateIfMissing(ctx.workspaceRoot, INSTRUCT_FILE);\n return [new V(\n 1,\n `(projected ${String(ctx.projectedFileLines)} lines)`,\n `File will be ${String(ctx.projectedFileLines)} lines, exceeding the ${String(limit)}-line limit. See .webpieces/instruct-ai/webpieces.filesize.md for detailed refactoring instructions.`,\n )];\n },\n};\n\nexport default maxFileLinesRule;\n"]}
1
+ {"version":3,"file":"max-file-lines.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/max-file-lines.ts"],"names":[],"mappings":";;AACA,oCAAuD;AACvD,0DAAiE;AAEjE,MAAM,aAAa,GAAG,GAAG,CAAC;AAC1B,MAAM,aAAa,GAAG,uBAAuB,CAAC;AAE9C,MAAM,gBAAgB,GAAa;IAC/B,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,6CAA6C;IAC1D,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE;IACxC,YAAY,EAAE;QACV,KAAK,EAAE,IAAI,mBAAW,CAAC,QAAQ,EAAE,qDAAqD,CAAC;KAC1F;IACD,OAAO,EAAE;QACL,0FAA0F;QAC1F,0FAA0F;KAC7F;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,CAAW,CAAC;QAC7C,IAAI,GAAG,CAAC,kBAAkB,IAAI,KAAK;YAAE,OAAO,EAAE,CAAC;QAC/C,IAAA,qCAAsB,EAAC,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QACzD,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,cAAc,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,SAAS,EACrD,gBAAgB,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,yBAAyB,MAAM,CAAC,KAAK,CAAC,sGAAsG,CAC7L,CAAC,CAAC;IACP,CAAC;CACJ,CAAC;AAEF,kBAAe,gBAAgB,CAAC","sourcesContent":["import type { FileRule, FileContext, Violation } from '../types';\nimport { Violation as V, FieldSchema } from '../types';\nimport { writeTemplateIfMissing } from '@webpieces/rules-config';\n\nconst DEFAULT_LIMIT = 900;\nconst INSTRUCT_FILE = 'webpieces.filesize.md';\n\nconst maxFileLinesRule: FileRule = {\n name: 'max-file-lines',\n description: 'Cap file length at a configured line limit.',\n scope: 'file',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: { limit: DEFAULT_LIMIT },\n configSchema: {\n limit: new FieldSchema('number', 'Max lines a file may reach before the rule triggers'),\n },\n fixHint: [\n 'READ .webpieces/instruct-ai/webpieces.filesize.md for step-by-step refactoring guidance.',\n '// eslint-disable-next-line @webpieces/max-file-lines (also suppresses the eslint rule)',\n ],\n\n check(ctx: FileContext): readonly Violation[] {\n const limit = ctx.options['limit'] as number;\n if (ctx.projectedFileLines <= limit) return [];\n writeTemplateIfMissing(ctx.workspaceRoot, INSTRUCT_FILE);\n return [new V(\n 1,\n `(projected ${String(ctx.projectedFileLines)} lines)`,\n `File will be ${String(ctx.projectedFileLines)} lines, exceeding the ${String(limit)}-line limit. See .webpieces/instruct-ai/webpieces.filesize.md for detailed refactoring instructions.`,\n )];\n },\n};\n\nexport default maxFileLinesRule;\n"]}
@@ -8,6 +8,7 @@ const noAnyRule = {
8
8
  scope: 'edit',
9
9
  files: ['**/*.ts', '**/*.tsx'],
10
10
  defaultOptions: {},
11
+ configSchema: {},
11
12
  fixHint: [
12
13
  'Prefer: interface MyData { ... } or class MyData { ... }',
13
14
  '// webpieces-disable no-any-unknown -- <one-line reason>',
@@ -1 +1 @@
1
- {"version":3,"file":"no-any-unknown.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-any-unknown.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAE1C,MAAM,WAAW,GACb,4HAA4H,CAAC;AAEjI,MAAM,SAAS,GAAa;IACxB,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,+DAA+D;IAC5E,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE;IAClB,OAAO,EAAE;QACL,8DAA8D;QAC9D,0DAA0D;KAC7D;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC1C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,gBAAgB,CAAC;gBAAE,SAAS;YAC5D,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,kGAAkG,CACrG,CAAC,CAAC;QACP,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAC;AAEF,kBAAe,SAAS,CAAC","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst ANY_PATTERN =\n /(?::\\s*any\\b|\\bas\\s+any\\b|<any>|any\\[\\]|Array<any>|Promise<any>|Map<[^,<>]+,\\s*any\\s*>|Record<[^,<>]+,\\s*any\\s*>|Set<any>)/;\n\nconst noAnyRule: EditRule = {\n name: 'no-any-unknown',\n description: 'Disallow the `any` keyword. Use concrete types or interfaces.',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n fixHint: [\n 'Prefer: interface MyData { ... } or class MyData { ... }',\n '// webpieces-disable no-any-unknown -- <one-line reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!ANY_PATTERN.test(stripped)) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'no-any-unknown')) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n '`any` erases type information. Use a concrete type, an interface, or `unknown` with type guards.',\n ));\n }\n return violations;\n },\n};\n\nexport default noAnyRule;\n"]}
1
+ {"version":3,"file":"no-any-unknown.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-any-unknown.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAE1C,MAAM,WAAW,GACb,4HAA4H,CAAC;AAEjI,MAAM,SAAS,GAAa;IACxB,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,+DAA+D;IAC5E,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE;IAClB,YAAY,EAAE,EAAE;IAChB,OAAO,EAAE;QACL,8DAA8D;QAC9D,0DAA0D;KAC7D;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC1C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,gBAAgB,CAAC;gBAAE,SAAS;YAC5D,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,kGAAkG,CACrG,CAAC,CAAC;QACP,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAC;AAEF,kBAAe,SAAS,CAAC","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst ANY_PATTERN =\n /(?::\\s*any\\b|\\bas\\s+any\\b|<any>|any\\[\\]|Array<any>|Promise<any>|Map<[^,<>]+,\\s*any\\s*>|Record<[^,<>]+,\\s*any\\s*>|Set<any>)/;\n\nconst noAnyRule: EditRule = {\n name: 'no-any-unknown',\n description: 'Disallow the `any` keyword. Use concrete types or interfaces.',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n configSchema: {},\n fixHint: [\n 'Prefer: interface MyData { ... } or class MyData { ... }',\n '// webpieces-disable no-any-unknown -- <one-line reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!ANY_PATTERN.test(stripped)) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'no-any-unknown')) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n '`any` erases type information. Use a concrete type, an interface, or `unknown` with type guards.',\n ));\n }\n return violations;\n },\n};\n\nexport default noAnyRule;\n"]}
@@ -7,7 +7,8 @@ const noDestructureRule = {
7
7
  description: 'Disallow destructuring patterns. Assign the whole result and pass it around or access properties explicitly.',
8
8
  scope: 'edit',
9
9
  files: ['**/*.ts', '**/*.tsx'],
10
- defaultOptions: { allowTopLevel: true },
10
+ defaultOptions: {},
11
+ configSchema: {},
11
12
  fixHint: [
12
13
  'Instead of: const { x, y } = methodCall(); prefer const obj = methodCall(); then pass obj to other methods or use obj.x',
13
14
  '// webpieces-disable no-destructure -- <reason>',
@@ -1 +1 @@
1
- {"version":3,"file":"no-destructure.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-destructure.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAE1C,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAExD,MAAM,iBAAiB,GAAa;IAChC,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,8GAA8G;IAC3H,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;IACvC,OAAO,EAAE;QACL,yHAAyH;QACzH,iDAAiD;KACpD;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACnD,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,gBAAgB,CAAC;gBAAE,SAAS;YAC5D,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,qHAAqH,CACxH,CAAC,CAAC;QACP,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAC;AAEF,kBAAe,iBAAiB,CAAC","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst VARIABLE_DESTRUCTURE = /\\b(?:const|let|var)\\s*\\{/;\n\nconst noDestructureRule: EditRule = {\n name: 'no-destructure',\n description: 'Disallow destructuring patterns. Assign the whole result and pass it around or access properties explicitly.',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: { allowTopLevel: true },\n fixHint: [\n 'Instead of: const { x, y } = methodCall(); prefer const obj = methodCall(); then pass obj to other methods or use obj.x',\n '// webpieces-disable no-destructure -- <reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!VARIABLE_DESTRUCTURE.test(stripped)) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'no-destructure')) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n 'Destructuring pattern. Assign the whole result instead: const obj = methodCall(); then pass obj around or use obj.x',\n ));\n }\n return violations;\n },\n};\n\nexport default noDestructureRule;\n"]}
1
+ {"version":3,"file":"no-destructure.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-destructure.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAE1C,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAExD,MAAM,iBAAiB,GAAa;IAChC,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,8GAA8G;IAC3H,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE;IAClB,YAAY,EAAE,EAAE;IAChB,OAAO,EAAE;QACL,yHAAyH;QACzH,iDAAiD;KACpD;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACnD,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,gBAAgB,CAAC;gBAAE,SAAS;YAC5D,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,qHAAqH,CACxH,CAAC,CAAC;QACP,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAC;AAEF,kBAAe,iBAAiB,CAAC","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst VARIABLE_DESTRUCTURE = /\\b(?:const|let|var)\\s*\\{/;\n\nconst noDestructureRule: EditRule = {\n name: 'no-destructure',\n description: 'Disallow destructuring patterns. Assign the whole result and pass it around or access properties explicitly.',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n configSchema: {},\n fixHint: [\n 'Instead of: const { x, y } = methodCall(); prefer const obj = methodCall(); then pass obj to other methods or use obj.x',\n '// webpieces-disable no-destructure -- <reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!VARIABLE_DESTRUCTURE.test(stripped)) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'no-destructure')) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n 'Destructuring pattern. Assign the whole result instead: const obj = methodCall(); then pass obj around or use obj.x',\n ));\n }\n return violations;\n },\n};\n\nexport default noDestructureRule;\n"]}
@@ -16,6 +16,7 @@ const noDirectMainUpdate = {
16
16
  scope: 'bash',
17
17
  files: [],
18
18
  defaultOptions: {},
19
+ configSchema: {},
19
20
  fixHint: FIX_HINT,
20
21
  check(ctx) {
21
22
  const matched = WRONG_UPDATE_PATTERNS.some((p) => p.test(ctx.command));
@@ -1 +1 @@
1
- {"version":3,"file":"no-direct-main-update.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-direct-main-update.ts"],"names":[],"mappings":";;AAAA,iDAAyC;AAEzC,oCAA0C;AAE1C,MAAM,QAAQ,GAAsB;IAChC,6NAA6N;CAChO,CAAC;AAEF,MAAM,qBAAqB,GAAa;IACpC,qCAAqC;IACrC,sCAAsC;IACtC,8BAA8B;CACjC,CAAC;AAEF,MAAM,kBAAkB,GAAa;IACjC,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EAAE,0GAA0G;IACvH,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,EAAE;IACT,cAAc,EAAE,EAAE;IAClB,OAAO,EAAE,QAAQ;IAEjB,KAAK,CAAC,GAAgB;QAClB,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QAC/E,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAExB,0GAA0G;QAC1G,IAAI,oCAAoC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACzD,OAAO,EAAE,CAAC;QACd,CAAC;QAED,MAAM,aAAa,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE;YAC9D,GAAG,EAAE,GAAG,CAAC,aAAa;YACtB,QAAQ,EAAE,MAAM;SACnB,CAAC,CAAC,IAAI,EAAE,CAAC;QAEV,IAAI,aAAa,KAAK,MAAM,EAAE,CAAC;YAC3B,OAAO,EAAE,CAAC;QACd,CAAC;QAED,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB;gBACI,4CAA4C,aAAa,eAAe;gBACxE,4CAA4C;gBAC5C,wCAAwC;gBACxC,mCAAmC;gBACnC,uCAAuC;aAC1C,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;IACP,CAAC;CACJ,CAAC;AAEF,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,kBAAe,kBAAkB,CAAC","sourcesContent":["import { execSync } from 'child_process';\nimport type { BashRule, BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst FIX_HINT: readonly string[] = [\n \"Run './scripts/git-updateFromMain.sh' to squash-update from main. This preserves the 3-point fork-point system (fork-point=A, feature-HEAD=B, main-HEAD=C) needed for clean PR diffs. See docs/git-workflow.md for details.\",\n];\n\nconst WRONG_UPDATE_PATTERNS: RegExp[] = [\n /git\\s+merge\\s+(origin\\/main|main)\\b/,\n /git\\s+rebase\\s+(origin\\/main|main)\\b/,\n /git\\s+pull\\s+origin\\s+main\\b/,\n];\n\nconst noDirectMainUpdate: BashRule = {\n name: 'no-direct-main-update',\n description: 'Block direct git merge/rebase/pull from main on feature branches. Use the squash-update process instead.',\n scope: 'bash',\n files: [],\n defaultOptions: {},\n fixHint: FIX_HINT,\n\n check(ctx: BashContext): readonly Violation[] {\n const matched = WRONG_UPDATE_PATTERNS.some((p: RegExp) => p.test(ctx.command));\n if (!matched) return [];\n\n // Allow 'git checkout main && git pull origin main' — switching to main first is the recommended workflow\n if (/git\\s+(?:checkout|switch)\\s+main\\b/.test(ctx.command)) {\n return [];\n }\n\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n\n if (currentBranch === 'main') {\n return [];\n }\n\n return [new V(\n 1,\n truncate(ctx.command),\n [\n `Direct merge/rebase from main on branch '${currentBranch}' is blocked.`,\n 'This breaks the 3-point fork-point system.',\n 'Use the squash-update process instead:',\n ' ./scripts/git-updateFromMain.sh',\n 'See docs/git-workflow.md for details.',\n ].join('\\n'),\n )];\n },\n};\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport default noDirectMainUpdate;\n"]}
1
+ {"version":3,"file":"no-direct-main-update.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-direct-main-update.ts"],"names":[],"mappings":";;AAAA,iDAAyC;AAEzC,oCAA0C;AAE1C,MAAM,QAAQ,GAAsB;IAChC,6NAA6N;CAChO,CAAC;AAEF,MAAM,qBAAqB,GAAa;IACpC,qCAAqC;IACrC,sCAAsC;IACtC,8BAA8B;CACjC,CAAC;AAEF,MAAM,kBAAkB,GAAa;IACjC,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EAAE,0GAA0G;IACvH,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,EAAE;IACT,cAAc,EAAE,EAAE;IAClB,YAAY,EAAE,EAAE;IAChB,OAAO,EAAE,QAAQ;IAEjB,KAAK,CAAC,GAAgB;QAClB,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QAC/E,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAExB,0GAA0G;QAC1G,IAAI,oCAAoC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACzD,OAAO,EAAE,CAAC;QACd,CAAC;QAED,MAAM,aAAa,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE;YAC9D,GAAG,EAAE,GAAG,CAAC,aAAa;YACtB,QAAQ,EAAE,MAAM;SACnB,CAAC,CAAC,IAAI,EAAE,CAAC;QAEV,IAAI,aAAa,KAAK,MAAM,EAAE,CAAC;YAC3B,OAAO,EAAE,CAAC;QACd,CAAC;QAED,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB;gBACI,4CAA4C,aAAa,eAAe;gBACxE,4CAA4C;gBAC5C,wCAAwC;gBACxC,mCAAmC;gBACnC,uCAAuC;aAC1C,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;IACP,CAAC;CACJ,CAAC;AAEF,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,kBAAe,kBAAkB,CAAC","sourcesContent":["import { execSync } from 'child_process';\nimport type { BashRule, BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst FIX_HINT: readonly string[] = [\n \"Run './scripts/git-updateFromMain.sh' to squash-update from main. This preserves the 3-point fork-point system (fork-point=A, feature-HEAD=B, main-HEAD=C) needed for clean PR diffs. See docs/git-workflow.md for details.\",\n];\n\nconst WRONG_UPDATE_PATTERNS: RegExp[] = [\n /git\\s+merge\\s+(origin\\/main|main)\\b/,\n /git\\s+rebase\\s+(origin\\/main|main)\\b/,\n /git\\s+pull\\s+origin\\s+main\\b/,\n];\n\nconst noDirectMainUpdate: BashRule = {\n name: 'no-direct-main-update',\n description: 'Block direct git merge/rebase/pull from main on feature branches. Use the squash-update process instead.',\n scope: 'bash',\n files: [],\n defaultOptions: {},\n configSchema: {},\n fixHint: FIX_HINT,\n\n check(ctx: BashContext): readonly Violation[] {\n const matched = WRONG_UPDATE_PATTERNS.some((p: RegExp) => p.test(ctx.command));\n if (!matched) return [];\n\n // Allow 'git checkout main && git pull origin main' — switching to main first is the recommended workflow\n if (/git\\s+(?:checkout|switch)\\s+main\\b/.test(ctx.command)) {\n return [];\n }\n\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n\n if (currentBranch === 'main') {\n return [];\n }\n\n return [new V(\n 1,\n truncate(ctx.command),\n [\n `Direct merge/rebase from main on branch '${currentBranch}' is blocked.`,\n 'This breaks the 3-point fork-point system.',\n 'Use the squash-update process instead:',\n ' ./scripts/git-updateFromMain.sh',\n 'See docs/git-workflow.md for details.',\n ].join('\\n'),\n )];\n },\n};\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport default noDirectMainUpdate;\n"]}
@@ -11,6 +11,9 @@ const noEditOnMain = {
11
11
  defaultOptions: {
12
12
  branchNamingConvention: '{whoami}/{featurename}',
13
13
  },
14
+ configSchema: {
15
+ branchNamingConvention: new types_1.FieldSchema('string', 'Branch name template shown to AI when it tries to edit on main'),
16
+ },
14
17
  fixHint: [
15
18
  'You should not be working on main.',
16
19
  'Steps:',
@@ -35,8 +38,7 @@ const noEditOnMain = {
35
38
  }
36
39
  if (currentBranch !== 'main')
37
40
  return [];
38
- const convention = ctx.options['branchNamingConvention']
39
- ?? 'feature/<ticket-id>-<short-description>';
41
+ const convention = ctx.options['branchNamingConvention'];
40
42
  return [new types_1.Violation(1, ctx.relativePath, [
41
43
  'You should not be working on main.',
42
44
  'Do a `git pull origin main` to get latest, then create a feature branch based on the naming convention.',
@@ -1 +1 @@
1
- {"version":3,"file":"no-edit-on-main.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-edit-on-main.ts"],"names":[],"mappings":";;AAAA,iDAAyC;AAEzC,oCAA0C;AAC1C,0CAAsC;AAEtC,MAAM,YAAY,GAAa;IAC3B,IAAI,EAAE,iBAAiB;IACvB,WAAW,EAAE,sFAAsF;IACnG,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,MAAM,CAAC;IACf,cAAc,EAAE;QACZ,sBAAsB,EAAE,wBAAwB;KACnD;IACD,OAAO,EAAE;QACL,oCAAoC;QACpC,QAAQ;QACR,kDAAkD;QAClD,8DAA8D;QAC9D,4GAA4G;KAC/G;IAED,KAAK,CAAC,GAAgB;QAClB,IAAI,aAAqB,CAAC;QAC1B,8DAA8D;QAC9D,IAAI,CAAC;YACD,aAAa,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE;gBACxD,GAAG,EAAE,GAAG,CAAC,aAAa;gBACtB,QAAQ,EAAE,MAAM;gBAChB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;aAClC,CAAC,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,KAAK,KAAK,CAAC;YACX,OAAO,EAAE,CAAC;QACd,CAAC;QAED,IAAI,aAAa,KAAK,MAAM;YAAE,OAAO,EAAE,CAAC;QAExC,MAAM,UAAU,GAAI,GAAG,CAAC,OAAO,CAAC,wBAAwB,CAAwB;eACzE,yCAAyC,CAAC;QAEjD,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,GAAG,CAAC,YAAY,EAChB;gBACI,oCAAoC;gBACpC,yGAAyG;gBACzG,0DAA0D,UAAU,EAAE;gBACtE,2BAA2B,GAAG,UAAU,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC;aACxE,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;IACP,CAAC;CACJ,CAAC;AAEF,kBAAe,YAAY,CAAC","sourcesContent":["import { execSync } from 'child_process';\nimport type { FileRule, FileContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { toError } from '../to-error';\n\nconst noEditOnMain: FileRule = {\n name: 'no-edit-on-main',\n description: 'Block file edits when on the main branch — all work must happen on a feature branch.',\n scope: 'file',\n files: ['**/*'],\n defaultOptions: {\n branchNamingConvention: '{whoami}/{featurename}',\n },\n fixHint: [\n 'You should not be working on main.',\n 'Steps:',\n ' 1. git pull origin main ← get latest commits',\n ' 2. git checkout -b <branch-name> ← create feature branch',\n 'Branch naming convention is defined in webpieces.config.json under no-edit-on-main.branchNamingConvention.',\n ],\n\n check(ctx: FileContext): readonly Violation[] {\n let currentBranch: string;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return [];\n }\n\n if (currentBranch !== 'main') return [];\n\n const convention = (ctx.options['branchNamingConvention'] as string | undefined)\n ?? 'feature/<ticket-id>-<short-description>';\n\n return [new V(\n 1,\n ctx.relativePath,\n [\n 'You should not be working on main.',\n 'Do a `git pull origin main` to get latest, then create a feature branch based on the naming convention.',\n `Branch naming convention (from webpieces.config.json): ${convention}`,\n 'Example: git checkout -b ' + convention.replace(/<[^>]+>/g, 'value'),\n ].join('\\n'),\n )];\n },\n};\n\nexport default noEditOnMain;\n"]}
1
+ {"version":3,"file":"no-edit-on-main.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-edit-on-main.ts"],"names":[],"mappings":";;AAAA,iDAAyC;AAEzC,oCAAuD;AACvD,0CAAsC;AAEtC,MAAM,YAAY,GAAa;IAC3B,IAAI,EAAE,iBAAiB;IACvB,WAAW,EAAE,sFAAsF;IACnG,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,MAAM,CAAC;IACf,cAAc,EAAE;QACZ,sBAAsB,EAAE,wBAAwB;KACnD;IACD,YAAY,EAAE;QACV,sBAAsB,EAAE,IAAI,mBAAW,CAAC,QAAQ,EAAE,gEAAgE,CAAC;KACtH;IACD,OAAO,EAAE;QACL,oCAAoC;QACpC,QAAQ;QACR,kDAAkD;QAClD,8DAA8D;QAC9D,4GAA4G;KAC/G;IAED,KAAK,CAAC,GAAgB;QAClB,IAAI,aAAqB,CAAC;QAC1B,8DAA8D;QAC9D,IAAI,CAAC;YACD,aAAa,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE;gBACxD,GAAG,EAAE,GAAG,CAAC,aAAa;gBACtB,QAAQ,EAAE,MAAM;gBAChB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;aAClC,CAAC,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,KAAK,KAAK,CAAC;YACX,OAAO,EAAE,CAAC;QACd,CAAC;QAED,IAAI,aAAa,KAAK,MAAM;YAAE,OAAO,EAAE,CAAC;QAExC,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,wBAAwB,CAAW,CAAC;QAEnE,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,GAAG,CAAC,YAAY,EAChB;gBACI,oCAAoC;gBACpC,yGAAyG;gBACzG,0DAA0D,UAAU,EAAE;gBACtE,2BAA2B,GAAG,UAAU,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC;aACxE,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;IACP,CAAC;CACJ,CAAC;AAEF,kBAAe,YAAY,CAAC","sourcesContent":["import { execSync } from 'child_process';\nimport type { FileRule, FileContext, Violation } from '../types';\nimport { Violation as V, FieldSchema } from '../types';\nimport { toError } from '../to-error';\n\nconst noEditOnMain: FileRule = {\n name: 'no-edit-on-main',\n description: 'Block file edits when on the main branch — all work must happen on a feature branch.',\n scope: 'file',\n files: ['**/*'],\n defaultOptions: {\n branchNamingConvention: '{whoami}/{featurename}',\n },\n configSchema: {\n branchNamingConvention: new FieldSchema('string', 'Branch name template shown to AI when it tries to edit on main'),\n },\n fixHint: [\n 'You should not be working on main.',\n 'Steps:',\n ' 1. git pull origin main ← get latest commits',\n ' 2. git checkout -b <branch-name> ← create feature branch',\n 'Branch naming convention is defined in webpieces.config.json under no-edit-on-main.branchNamingConvention.',\n ],\n\n check(ctx: FileContext): readonly Violation[] {\n let currentBranch: string;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return [];\n }\n\n if (currentBranch !== 'main') return [];\n\n const convention = ctx.options['branchNamingConvention'] as string;\n\n return [new V(\n 1,\n ctx.relativePath,\n [\n 'You should not be working on main.',\n 'Do a `git pull origin main` to get latest, then create a feature branch based on the naming convention.',\n `Branch naming convention (from webpieces.config.json): ${convention}`,\n 'Example: git checkout -b ' + convention.replace(/<[^>]+>/g, 'value'),\n ].join('\\n'),\n )];\n },\n};\n\nexport default noEditOnMain;\n"]}
@@ -46,6 +46,7 @@ const noImplicitAnyRule = {
46
46
  scope: 'edit',
47
47
  files: ['**/*.ts', '**/*.tsx'],
48
48
  defaultOptions: {},
49
+ configSchema: {},
49
50
  fixHint: [
50
51
  'Add explicit types: (x: string) => ... or function foo(x: number)',
51
52
  '// webpieces-disable no-implicit-any -- <one-line reason>',
@@ -1 +1 @@
1
- {"version":3,"file":"no-implicit-any.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-implicit-any.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst ARROW_PARAMS_RE = /\\(([^()]*)\\)\\s*=>/g;\nconst FN_DECL_PARAMS_RE = /\\bfunction\\s*[\\w$]*\\s*\\(([^()]*)\\)/g;\n\nfunction firstUntypedParam(paramsStr: string): string | null {\n if (paramsStr.includes('{') || paramsStr.includes('[')) return null;\n const parts = paramsStr.split(',').map((p) => p.trim()).filter((p) => p.length > 0);\n for (const part of parts) {\n if (part.startsWith('...')) continue;\n if (part.includes(':')) continue;\n if (part.includes('=')) continue;\n if (part === 'this') continue;\n if (/^[a-zA-Z_$][\\w$]*$/.test(part)) return part;\n }\n return null;\n}\n\nfunction findOffender(line: string): string | null {\n ARROW_PARAMS_RE.lastIndex = 0;\n let m: RegExpExecArray | null = ARROW_PARAMS_RE.exec(line);\n while (m !== null) {\n const bad = firstUntypedParam(m[1]);\n if (bad) return bad;\n m = ARROW_PARAMS_RE.exec(line);\n }\n FN_DECL_PARAMS_RE.lastIndex = 0;\n m = FN_DECL_PARAMS_RE.exec(line);\n while (m !== null) {\n const bad = firstUntypedParam(m[1]);\n if (bad) return bad;\n m = FN_DECL_PARAMS_RE.exec(line);\n }\n return null;\n}\n\nconst noImplicitAnyRule: EditRule = {\n name: 'no-implicit-any',\n description: 'Disallow function parameters without explicit type annotations (implicit-any).',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n fixHint: [\n 'Add explicit types: (x: string) => ... or function foo(x: number)',\n '// webpieces-disable no-implicit-any -- <one-line reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'no-implicit-any')) continue;\n const offender = findOffender(stripped);\n if (!offender) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Parameter \"${offender}\" has no type annotation. Add an explicit type to avoid implicit-any.`,\n ));\n }\n return violations;\n },\n};\n\nexport default noImplicitAnyRule;\n"]}
1
+ {"version":3,"file":"no-implicit-any.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-implicit-any.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst ARROW_PARAMS_RE = /\\(([^()]*)\\)\\s*=>/g;\nconst FN_DECL_PARAMS_RE = /\\bfunction\\s*[\\w$]*\\s*\\(([^()]*)\\)/g;\n\nfunction firstUntypedParam(paramsStr: string): string | null {\n if (paramsStr.includes('{') || paramsStr.includes('[')) return null;\n const parts = paramsStr.split(',').map((p) => p.trim()).filter((p) => p.length > 0);\n for (const part of parts) {\n if (part.startsWith('...')) continue;\n if (part.includes(':')) continue;\n if (part.includes('=')) continue;\n if (part === 'this') continue;\n if (/^[a-zA-Z_$][\\w$]*$/.test(part)) return part;\n }\n return null;\n}\n\nfunction findOffender(line: string): string | null {\n ARROW_PARAMS_RE.lastIndex = 0;\n let m: RegExpExecArray | null = ARROW_PARAMS_RE.exec(line);\n while (m !== null) {\n const bad = firstUntypedParam(m[1]);\n if (bad) return bad;\n m = ARROW_PARAMS_RE.exec(line);\n }\n FN_DECL_PARAMS_RE.lastIndex = 0;\n m = FN_DECL_PARAMS_RE.exec(line);\n while (m !== null) {\n const bad = firstUntypedParam(m[1]);\n if (bad) return bad;\n m = FN_DECL_PARAMS_RE.exec(line);\n }\n return null;\n}\n\nconst noImplicitAnyRule: EditRule = {\n name: 'no-implicit-any',\n description: 'Disallow function parameters without explicit type annotations (implicit-any).',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n configSchema: {},\n fixHint: [\n 'Add explicit types: (x: string) => ... or function foo(x: number)',\n '// webpieces-disable no-implicit-any -- <one-line reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'no-implicit-any')) continue;\n const offender = findOffender(stripped);\n if (!offender) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Parameter \"${offender}\" has no type annotation. Add an explicit type to avoid implicit-any.`,\n ));\n }\n return violations;\n },\n};\n\nexport default noImplicitAnyRule;\n"]}
@@ -10,6 +10,9 @@ const noJsFiles = {
10
10
  defaultOptions: {
11
11
  allowedPaths: [],
12
12
  },
13
+ configSchema: {
14
+ allowedPaths: new types_1.FieldSchema('string[]', 'Glob patterns for .js/.jsx paths that are exempt from the rule'),
15
+ },
13
16
  fixHint: [
14
17
  'Write a .ts or .tsx file instead of .js/.jsx.',
15
18
  'If this path must be .js (e.g. a generated or legacy file), add it to no-js-files.allowedPaths in webpieces.config.json',
@@ -17,9 +20,7 @@ const noJsFiles = {
17
20
  check(ctx) {
18
21
  if (ctx.tool !== 'Write')
19
22
  return [];
20
- const allowedPaths = Array.isArray(ctx.options['allowedPaths'])
21
- ? ctx.options['allowedPaths']
22
- : [];
23
+ const allowedPaths = ctx.options['allowedPaths'];
23
24
  if ((0, rules_config_1.isPathExcluded)(ctx.relativePath, allowedPaths))
24
25
  return [];
25
26
  return [new types_1.Violation(1, ctx.relativePath, 'Writing .js/.jsx files is not allowed. Use .ts/.tsx instead.')];
@@ -1 +1 @@
1
- {"version":3,"file":"no-js-files.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-js-files.ts"],"names":[],"mappings":";;AAAA,0DAAyD;AAGzD,oCAA0C;AAE1C,MAAM,SAAS,GAAa;IACxB,IAAI,EAAE,aAAa;IACnB,WAAW,EAAE,4DAA4D;IACzE,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE;QACZ,YAAY,EAAE,EAAE;KACnB;IACD,OAAO,EAAE;QACL,+CAA+C;QAC/C,yHAAyH;KAC5H;IAED,KAAK,CAAC,GAAgB;QAClB,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,EAAE,CAAC;QAEpC,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;YAC3D,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAa;YACzC,CAAC,CAAC,EAAE,CAAC;QAET,IAAI,IAAA,6BAAc,EAAC,GAAG,CAAC,YAAY,EAAE,YAAY,CAAC;YAAE,OAAO,EAAE,CAAC;QAE9D,OAAO,CAAC,IAAI,iBAAC,CAAC,CAAC,EAAE,GAAG,CAAC,YAAY,EAAE,8DAA8D,CAAC,CAAC,CAAC;IACxG,CAAC;CACJ,CAAC;AAEF,kBAAe,SAAS,CAAC","sourcesContent":["import { isPathExcluded } from '@webpieces/rules-config';\n\nimport type { FileRule, FileContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst noJsFiles: FileRule = {\n name: 'no-js-files',\n description: 'Disallow writing new .js/.jsx files. Use .ts/.tsx instead.',\n scope: 'file',\n files: ['**/*.js', '**/*.jsx'],\n defaultOptions: {\n allowedPaths: [],\n },\n fixHint: [\n 'Write a .ts or .tsx file instead of .js/.jsx.',\n 'If this path must be .js (e.g. a generated or legacy file), add it to no-js-files.allowedPaths in webpieces.config.json',\n ],\n\n check(ctx: FileContext): readonly Violation[] {\n if (ctx.tool !== 'Write') return [];\n\n const allowedPaths = Array.isArray(ctx.options['allowedPaths'])\n ? ctx.options['allowedPaths'] as string[]\n : [];\n\n if (isPathExcluded(ctx.relativePath, allowedPaths)) return [];\n\n return [new V(1, ctx.relativePath, 'Writing .js/.jsx files is not allowed. Use .ts/.tsx instead.')];\n },\n};\n\nexport default noJsFiles;\n"]}
1
+ {"version":3,"file":"no-js-files.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-js-files.ts"],"names":[],"mappings":";;AAAA,0DAAyD;AAGzD,oCAAuD;AAEvD,MAAM,SAAS,GAAa;IACxB,IAAI,EAAE,aAAa;IACnB,WAAW,EAAE,4DAA4D;IACzE,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE;QACZ,YAAY,EAAE,EAAE;KACnB;IACD,YAAY,EAAE;QACV,YAAY,EAAE,IAAI,mBAAW,CAAC,UAAU,EAAE,gEAAgE,CAAC;KAC9G;IACD,OAAO,EAAE;QACL,+CAA+C;QAC/C,yHAAyH;KAC5H;IAED,KAAK,CAAC,GAAgB;QAClB,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,EAAE,CAAC;QAEpC,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,CAAa,CAAC;QAE7D,IAAI,IAAA,6BAAc,EAAC,GAAG,CAAC,YAAY,EAAE,YAAY,CAAC;YAAE,OAAO,EAAE,CAAC;QAE9D,OAAO,CAAC,IAAI,iBAAC,CAAC,CAAC,EAAE,GAAG,CAAC,YAAY,EAAE,8DAA8D,CAAC,CAAC,CAAC;IACxG,CAAC;CACJ,CAAC;AAEF,kBAAe,SAAS,CAAC","sourcesContent":["import { isPathExcluded } from '@webpieces/rules-config';\n\nimport type { FileRule, FileContext, Violation } from '../types';\nimport { Violation as V, FieldSchema } from '../types';\n\nconst noJsFiles: FileRule = {\n name: 'no-js-files',\n description: 'Disallow writing new .js/.jsx files. Use .ts/.tsx instead.',\n scope: 'file',\n files: ['**/*.js', '**/*.jsx'],\n defaultOptions: {\n allowedPaths: [],\n },\n configSchema: {\n allowedPaths: new FieldSchema('string[]', 'Glob patterns for .js/.jsx paths that are exempt from the rule'),\n },\n fixHint: [\n 'Write a .ts or .tsx file instead of .js/.jsx.',\n 'If this path must be .js (e.g. a generated or legacy file), add it to no-js-files.allowedPaths in webpieces.config.json',\n ],\n\n check(ctx: FileContext): readonly Violation[] {\n if (ctx.tool !== 'Write') return [];\n\n const allowedPaths = ctx.options['allowedPaths'] as string[];\n\n if (isPathExcluded(ctx.relativePath, allowedPaths)) return [];\n\n return [new V(1, ctx.relativePath, 'Writing .js/.jsx files is not allowed. Use .ts/.tsx instead.')];\n },\n};\n\nexport default noJsFiles;\n"]}
@@ -12,6 +12,7 @@ const noShellSubstitutionRule = {
12
12
  scope: 'bash',
13
13
  files: [],
14
14
  defaultOptions: {},
15
+ configSchema: {},
15
16
  fixHint: FIX_HINT,
16
17
  check(ctx) {
17
18
  const scanned = stripSingleQuoted(ctx.command);
@@ -1 +1 @@
1
- {"version":3,"file":"no-shell-substitution.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-shell-substitution.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAE1C,MAAM,QAAQ,GAAsB;IAChC,uGAAuG;IACvG,oFAAoF;IACpF,gGAAgG;CACnG,CAAC;AAEF,MAAM,uBAAuB,GAAa;IACtC,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EAAE,gFAAgF;IAC7F,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,EAAE;IACT,cAAc,EAAE,EAAE;IAClB,OAAO,EAAE,QAAQ;IAEjB,KAAK,CAAC,GAAgB;QAClB,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,UAAU,GAAgB,EAAE,CAAC;QAEnC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACvB,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,iDAAiD,CACpD,CAAC,CAAC;QACP,CAAC;QACD,IAAI,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC;YAChC,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,iDAAiD,CACpD,CAAC,CAAC;QACP,CAAC;QACD,IAAI,8BAA8B,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/E,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,yDAAyD,CAC5D,CAAC,CAAC;QACP,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAC;AAEF,SAAS,iBAAiB,CAAC,GAAW;IAClC,OAAO,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,oBAAoB,CAAC,GAAW;IACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACxE,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,mBAAmB,CAAC,GAAW;IACpC,MAAM,EAAE,GAAG,sCAAsC,CAAC;IAClD,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,IAAI,CAAC,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACjC,CAAC;AAED,kBAAe,uBAAuB,CAAC","sourcesContent":["import type { BashRule, BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst FIX_HINT: readonly string[] = [\n 'Build a payload file with the Write tool and pass it as stdin: node script.js < /path/to/payload.json',\n 'Use the Read, Grep, or Glob tools directly instead of capturing output with $(...)',\n 'Write a small script file with the Write tool and execute it directly: bash /path/to/script.sh',\n];\n\nconst noShellSubstitutionRule: BashRule = {\n name: 'no-shell-substitution',\n description: 'Reject Bash commands containing shell substitutions ($(...), backticks, $VAR).',\n scope: 'bash',\n files: [],\n defaultOptions: {},\n fixHint: FIX_HINT,\n\n check(ctx: BashContext): readonly Violation[] {\n const scanned = stripSingleQuoted(ctx.command);\n const violations: Violation[] = [];\n\n if (/\\$\\(/.test(scanned)) {\n violations.push(new V(\n 1,\n truncate(ctx.command),\n 'Command contains `$(...)` command substitution.',\n ));\n }\n if (hasUnescapedBacktick(scanned)) {\n violations.push(new V(\n 1,\n truncate(ctx.command),\n 'Command contains backtick command substitution.',\n ));\n }\n if (/\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}/.test(scanned) || hasBareVarExpansion(scanned)) {\n violations.push(new V(\n 1,\n truncate(ctx.command),\n 'Command contains `$VAR` or `${VAR}` variable expansion.',\n ));\n }\n return violations;\n },\n};\n\nfunction stripSingleQuoted(cmd: string): string {\n return cmd.replace(/'[^']*'/g, \"''\");\n}\n\nfunction hasUnescapedBacktick(cmd: string): boolean {\n for (let i = 0; i < cmd.length; i += 1) {\n if (cmd[i] === '`' && (i === 0 || cmd[i - 1] !== '\\\\')) return true;\n }\n return false;\n}\n\nfunction hasBareVarExpansion(cmd: string): boolean {\n const re = /(^|[^\\\\])\\$([A-Za-z_][A-Za-z0-9_]*)/g;\n return re.test(cmd);\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n if (s.length <= MAX) return s;\n return s.slice(0, MAX) + '…';\n}\n\nexport default noShellSubstitutionRule;\n"]}
1
+ {"version":3,"file":"no-shell-substitution.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-shell-substitution.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAE1C,MAAM,QAAQ,GAAsB;IAChC,uGAAuG;IACvG,oFAAoF;IACpF,gGAAgG;CACnG,CAAC;AAEF,MAAM,uBAAuB,GAAa;IACtC,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EAAE,gFAAgF;IAC7F,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,EAAE;IACT,cAAc,EAAE,EAAE;IAClB,YAAY,EAAE,EAAE;IAChB,OAAO,EAAE,QAAQ;IAEjB,KAAK,CAAC,GAAgB;QAClB,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,UAAU,GAAgB,EAAE,CAAC;QAEnC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACvB,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,iDAAiD,CACpD,CAAC,CAAC;QACP,CAAC;QACD,IAAI,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC;YAChC,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,iDAAiD,CACpD,CAAC,CAAC;QACP,CAAC;QACD,IAAI,8BAA8B,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/E,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,yDAAyD,CAC5D,CAAC,CAAC;QACP,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAC;AAEF,SAAS,iBAAiB,CAAC,GAAW;IAClC,OAAO,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,oBAAoB,CAAC,GAAW;IACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACxE,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,mBAAmB,CAAC,GAAW;IACpC,MAAM,EAAE,GAAG,sCAAsC,CAAC;IAClD,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,IAAI,CAAC,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACjC,CAAC;AAED,kBAAe,uBAAuB,CAAC","sourcesContent":["import type { BashRule, BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst FIX_HINT: readonly string[] = [\n 'Build a payload file with the Write tool and pass it as stdin: node script.js < /path/to/payload.json',\n 'Use the Read, Grep, or Glob tools directly instead of capturing output with $(...)',\n 'Write a small script file with the Write tool and execute it directly: bash /path/to/script.sh',\n];\n\nconst noShellSubstitutionRule: BashRule = {\n name: 'no-shell-substitution',\n description: 'Reject Bash commands containing shell substitutions ($(...), backticks, $VAR).',\n scope: 'bash',\n files: [],\n defaultOptions: {},\n configSchema: {},\n fixHint: FIX_HINT,\n\n check(ctx: BashContext): readonly Violation[] {\n const scanned = stripSingleQuoted(ctx.command);\n const violations: Violation[] = [];\n\n if (/\\$\\(/.test(scanned)) {\n violations.push(new V(\n 1,\n truncate(ctx.command),\n 'Command contains `$(...)` command substitution.',\n ));\n }\n if (hasUnescapedBacktick(scanned)) {\n violations.push(new V(\n 1,\n truncate(ctx.command),\n 'Command contains backtick command substitution.',\n ));\n }\n if (/\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}/.test(scanned) || hasBareVarExpansion(scanned)) {\n violations.push(new V(\n 1,\n truncate(ctx.command),\n 'Command contains `$VAR` or `${VAR}` variable expansion.',\n ));\n }\n return violations;\n },\n};\n\nfunction stripSingleQuoted(cmd: string): string {\n return cmd.replace(/'[^']*'/g, \"''\");\n}\n\nfunction hasUnescapedBacktick(cmd: string): boolean {\n for (let i = 0; i < cmd.length; i += 1) {\n if (cmd[i] === '`' && (i === 0 || cmd[i - 1] !== '\\\\')) return true;\n }\n return false;\n}\n\nfunction hasBareVarExpansion(cmd: string): boolean {\n const re = /(^|[^\\\\])\\$([A-Za-z_][A-Za-z0-9_]*)/g;\n return re.test(cmd);\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n if (s.length <= MAX) return s;\n return s.slice(0, MAX) + '…';\n}\n\nexport default noShellSubstitutionRule;\n"]}
@@ -46,6 +46,9 @@ const noSymbolDiTokensRule = {
46
46
  scope: 'edit',
47
47
  files: ['**/*.ts', '**/*.tsx'],
48
48
  defaultOptions: { allowedPaths: [] },
49
+ configSchema: {
50
+ allowedPaths: new types_1.FieldSchema('string[]', 'Glob patterns for paths exempt from the Symbol DI token rule'),
51
+ },
49
52
  fixHint: [
50
53
  'Option 1: Use @provideSingleton() on the class and inject by type — no Symbol needed.',
51
54
  'Option 2: Implement an API interface — import the Symbol from the API definition and use @provideSingletonAs(TOKEN).',
@@ -53,7 +56,7 @@ const noSymbolDiTokensRule = {
53
56
  'Option 4 (last resort): // webpieces-disable no-symbol-di-tokens -- <reason>',
54
57
  ],
55
58
  check(ctx) {
56
- const allowedPaths = ctx.options['allowedPaths'] ?? [];
59
+ const allowedPaths = ctx.options['allowedPaths'];
57
60
  if (isAllowedPath(ctx.relativePath, allowedPaths))
58
61
  return [];
59
62
  const violations = [];
@@ -1 +1 @@
1
- {"version":3,"file":"no-symbol-di-tokens.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-symbol-di-tokens.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst SYMBOL_DI_REGEX = /=\\s*Symbol(?:\\.for)?\\(/;\n\nconst TEST_PATHS: RegExp[] = [/\\.test\\.ts$/, /\\.spec\\.ts$/, /__tests__\\//];\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\nfunction isAllowedPath(relativePath: string, allowedPaths: readonly string[]): boolean {\n if (TEST_PATHS.some((re: RegExp) => re.test(relativePath))) return true;\n return allowedPaths.some((pattern: string) => globToRegex(pattern).test(relativePath));\n}\n\nconst noSymbolDiTokensRule: EditRule = {\n name: 'no-symbol-di-tokens',\n description: 'Disallow Symbol() DI tokens outside explicitly configured paths. Use @provideSingleton() + inject-by-type instead.',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: { allowedPaths: [] },\n fixHint: [\n 'Option 1: Use @provideSingleton() on the class and inject by type — no Symbol needed.',\n 'Option 2: Implement an API interface — import the Symbol from the API definition and use @provideSingletonAs(TOKEN).',\n 'Option 3: External lib class (DataSource, Anthropic, etc.) — bind<Cls>(Cls).toDynamicValue(...).inSingletonScope() — no Symbol.',\n 'Option 4 (last resort): // webpieces-disable no-symbol-di-tokens -- <reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const allowedPaths = (ctx.options['allowedPaths'] as string[] | undefined) ?? [];\n if (isAllowedPath(ctx.relativePath, allowedPaths)) return [];\n\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!SYMBOL_DI_REGEX.test(stripped ?? '')) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'no-symbol-di-tokens')) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i]?.trim() ?? '',\n 'Symbol() used as a DI token. Mostly we avoid Symbol if we can — see fix options below.',\n ));\n }\n return violations;\n },\n};\n\nexport default noSymbolDiTokensRule;\n"]}
1
+ {"version":3,"file":"no-symbol-di-tokens.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-symbol-di-tokens.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V, FieldSchema } from '../types';\n\nconst SYMBOL_DI_REGEX = /=\\s*Symbol(?:\\.for)?\\(/;\n\nconst TEST_PATHS: RegExp[] = [/\\.test\\.ts$/, /\\.spec\\.ts$/, /__tests__\\//];\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\nfunction isAllowedPath(relativePath: string, allowedPaths: readonly string[]): boolean {\n if (TEST_PATHS.some((re: RegExp) => re.test(relativePath))) return true;\n return allowedPaths.some((pattern: string) => globToRegex(pattern).test(relativePath));\n}\n\nconst noSymbolDiTokensRule: EditRule = {\n name: 'no-symbol-di-tokens',\n description: 'Disallow Symbol() DI tokens outside explicitly configured paths. Use @provideSingleton() + inject-by-type instead.',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: { allowedPaths: [] },\n configSchema: {\n allowedPaths: new FieldSchema('string[]', 'Glob patterns for paths exempt from the Symbol DI token rule'),\n },\n fixHint: [\n 'Option 1: Use @provideSingleton() on the class and inject by type — no Symbol needed.',\n 'Option 2: Implement an API interface — import the Symbol from the API definition and use @provideSingletonAs(TOKEN).',\n 'Option 3: External lib class (DataSource, Anthropic, etc.) — bind<Cls>(Cls).toDynamicValue(...).inSingletonScope() — no Symbol.',\n 'Option 4 (last resort): // webpieces-disable no-symbol-di-tokens -- <reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const allowedPaths = ctx.options['allowedPaths'] as string[];\n if (isAllowedPath(ctx.relativePath, allowedPaths)) return [];\n\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!SYMBOL_DI_REGEX.test(stripped ?? '')) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'no-symbol-di-tokens')) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i]?.trim() ?? '',\n 'Symbol() used as a DI token. Mostly we avoid Symbol if we can — see fix options below.',\n ));\n }\n return violations;\n },\n};\n\nexport default noSymbolDiTokensRule;\n"]}
@@ -11,6 +11,7 @@ const noUnmanagedExceptionsRule = {
11
11
  scope: 'edit',
12
12
  files: ['**/*.ts', '**/*.tsx'],
13
13
  defaultOptions: {},
14
+ configSchema: {},
14
15
  fixHint: [
15
16
  'Fix Option 1 (preferred): Remove the try/catch — let the exception bubble to the top-level chokepoint (filter, globalErrorHandler) where it is already logged and handled.',
16
17
  'Fix Option 2 (ask the human first): If you genuinely believe this IS a chokepoint, STOP and tell the human:',
@@ -1 +1 @@
1
- {"version":3,"file":"no-unmanaged-exceptions.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-unmanaged-exceptions.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAC1C,8DAA+D;AAE/D,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC,8EAA8E;AAC9E,MAAM,eAAe,GAAG,gGAAgG,CAAC;AAEzH,MAAM,yBAAyB,GAAa;IACxC,IAAI,EAAE,yBAAyB;IAC/B,WAAW,EAAE,uHAAuH;IACpI,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE;IAClB,OAAO,EAAE;QACL,4KAA4K;QAC5K,6GAA6G;QAC7G,yCAAyC;QACzC,2FAA2F;QAC3F,0EAA0E;QAC1E,uEAAuE;QACvE,4FAA4F;QAC5F,oMAAoM;KACvM;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC1C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,yBAAyB,CAAC;gBAAE,SAAS;YACrE,IAAI,mBAAmB,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;gBAAE,SAAS;YAChD,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,gLAAgL,CACnL,CAAC,CAAC;QACP,CAAC;QACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,IAAA,2CAAsB,EAAC,GAAG,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC;QAChG,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAC;AAEF,SAAS,mBAAmB,CAAC,KAAwB,EAAE,GAAW;IAC9D,IAAI,GAAG,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5B,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAChC,OAAO,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED,kBAAe,yBAAyB,CAAC","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { writeTemplateIfMissing } from '../instruct-ai-writer';\n\nconst TRY_PATTERN = /\\btry\\s*\\{/;\n\n// Both webpieces-disable and the existing ESLint directive suppress this rule\nconst DISABLE_PATTERN = /@webpieces\\/no-unmanaged-exceptions|webpieces-disable\\s+(?:[\\w-]+,\\s*)*no-unmanaged-exceptions/;\n\nconst noUnmanagedExceptionsRule: EditRule = {\n name: 'no-unmanaged-exceptions',\n description: 'try/catch is generally not allowed. Only allowed in chokepoints (filter, globalErrorHandler) or other rare locations.',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n fixHint: [\n 'Fix Option 1 (preferred): Remove the try/catch — let the exception bubble to the top-level chokepoint (filter, globalErrorHandler) where it is already logged and handled.',\n 'Fix Option 2 (ask the human first): If you genuinely believe this IS a chokepoint, STOP and tell the human:',\n ' - What exception could be thrown here',\n ' - What the current top-level chokepoint is (the try/catch at the top of the call stack)',\n ' - Why throwing to that chokepoint would be wrong in this specific case',\n ' Then ask: \"Should I add a disable comment or remove the try/catch?\"',\n ' Only add // webpieces-disable no-unmanaged-exceptions -- <reason> if the human says yes.',\n 'NOTE: If the code is calling an external process (execSync, fs, network), the correct answer is almost always Option 1 — let it throw. Hooks have a top-level runner that reports errors properly.',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!TRY_PATTERN.test(stripped)) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'no-unmanaged-exceptions')) continue;\n if (hasPrecedingDisable(ctx.lines, i)) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n 'try/catch is generally not allowed. READ .webpieces/instruct-ai/webpieces.exceptions.md to understand why. Only chokepoints (filter, globalErrorHandler) may catch exceptions.',\n ));\n }\n if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');\n return violations;\n },\n};\n\nfunction hasPrecedingDisable(lines: readonly string[], idx: number): boolean {\n if (idx === 0) return false;\n const prevLine = lines[idx - 1];\n return DISABLE_PATTERN.test(prevLine);\n}\n\nexport default noUnmanagedExceptionsRule;\n"]}
1
+ {"version":3,"file":"no-unmanaged-exceptions.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-unmanaged-exceptions.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAC1C,8DAA+D;AAE/D,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC,8EAA8E;AAC9E,MAAM,eAAe,GAAG,gGAAgG,CAAC;AAEzH,MAAM,yBAAyB,GAAa;IACxC,IAAI,EAAE,yBAAyB;IAC/B,WAAW,EAAE,uHAAuH;IACpI,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE;IAClB,YAAY,EAAE,EAAE;IAChB,OAAO,EAAE;QACL,4KAA4K;QAC5K,6GAA6G;QAC7G,yCAAyC;QACzC,2FAA2F;QAC3F,0EAA0E;QAC1E,uEAAuE;QACvE,4FAA4F;QAC5F,oMAAoM;KACvM;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC1C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,yBAAyB,CAAC;gBAAE,SAAS;YACrE,IAAI,mBAAmB,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;gBAAE,SAAS;YAChD,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,gLAAgL,CACnL,CAAC,CAAC;QACP,CAAC;QACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,IAAA,2CAAsB,EAAC,GAAG,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC;QAChG,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAC;AAEF,SAAS,mBAAmB,CAAC,KAAwB,EAAE,GAAW;IAC9D,IAAI,GAAG,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5B,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAChC,OAAO,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED,kBAAe,yBAAyB,CAAC","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { writeTemplateIfMissing } from '../instruct-ai-writer';\n\nconst TRY_PATTERN = /\\btry\\s*\\{/;\n\n// Both webpieces-disable and the existing ESLint directive suppress this rule\nconst DISABLE_PATTERN = /@webpieces\\/no-unmanaged-exceptions|webpieces-disable\\s+(?:[\\w-]+,\\s*)*no-unmanaged-exceptions/;\n\nconst noUnmanagedExceptionsRule: EditRule = {\n name: 'no-unmanaged-exceptions',\n description: 'try/catch is generally not allowed. Only allowed in chokepoints (filter, globalErrorHandler) or other rare locations.',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n configSchema: {},\n fixHint: [\n 'Fix Option 1 (preferred): Remove the try/catch — let the exception bubble to the top-level chokepoint (filter, globalErrorHandler) where it is already logged and handled.',\n 'Fix Option 2 (ask the human first): If you genuinely believe this IS a chokepoint, STOP and tell the human:',\n ' - What exception could be thrown here',\n ' - What the current top-level chokepoint is (the try/catch at the top of the call stack)',\n ' - Why throwing to that chokepoint would be wrong in this specific case',\n ' Then ask: \"Should I add a disable comment or remove the try/catch?\"',\n ' Only add // webpieces-disable no-unmanaged-exceptions -- <reason> if the human says yes.',\n 'NOTE: If the code is calling an external process (execSync, fs, network), the correct answer is almost always Option 1 — let it throw. Hooks have a top-level runner that reports errors properly.',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!TRY_PATTERN.test(stripped)) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'no-unmanaged-exceptions')) continue;\n if (hasPrecedingDisable(ctx.lines, i)) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n 'try/catch is generally not allowed. READ .webpieces/instruct-ai/webpieces.exceptions.md to understand why. Only chokepoints (filter, globalErrorHandler) may catch exceptions.',\n ));\n }\n if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');\n return violations;\n },\n};\n\nfunction hasPrecedingDisable(lines: readonly string[], idx: number): boolean {\n if (idx === 0) return false;\n const prevLine = lines[idx - 1];\n return DISABLE_PATTERN.test(prevLine);\n}\n\nexport default noUnmanagedExceptionsRule;\n"]}
@@ -19,6 +19,10 @@ const prCreationGuard = {
19
19
  scope: 'bash',
20
20
  files: [],
21
21
  defaultOptions: { buildCommand: '', requireTextInPr: '' },
22
+ configSchema: {
23
+ buildCommand: new types_1.FieldSchema('string', 'Build/test command AI must run before creating a PR (empty string disables the check)'),
24
+ requireTextInPr: new types_1.FieldSchema('string', 'Exact text AI must include in the PR body to confirm the build passed (empty string disables)'),
25
+ },
22
26
  fixHint: FIX_HINT,
23
27
  check(ctx) {
24
28
  if (!/gh\s+pr\s+create/.test(ctx.command))
@@ -1 +1 @@
1
- {"version":3,"file":"pr-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/pr-creation-guard.ts"],"names":[],"mappings":";;AAAA,iDAAoD;AACpD,2BAA8C;AAC9C,+BAAwC;AAExC,oCAA0C;AAE1C,MAAM,QAAQ,GAAsB;IAChC,4CAA4C;IAC5C,wDAAwD;IACxD,mCAAmC;IACnC,8BAA8B;IAC9B,EAAE;IACF,iGAAiG;IACjG,oEAAoE;CACvE,CAAC;AAEF,MAAM,eAAe,GAAa;IAC9B,IAAI,EAAE,mBAAmB;IACzB,WAAW,EAAE,uGAAuG;IACpH,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,EAAE;IACT,cAAc,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE;IACzD,OAAO,EAAE,QAAQ;IAEjB,KAAK,CAAC,GAAgB;QAClB,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAErD,IAAA,wBAAQ,EAAC,+BAA+B,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAExF,oFAAoF;QACpF,MAAM,MAAM,GAAG,IAAA,yBAAS,EACpB,KAAK,EAAE,CAAC,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,CAAC,EAC7D,EAAE,GAAG,EAAE,GAAG,CAAC,aAAa,EAAE,CAC7B,CAAC;QAEF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB;oBACI,4CAA4C;oBAC5C,sCAAsC;oBACtC,mCAAmC;oBACnC,iGAAiG;oBACjG,uCAAuC;oBACvC,0BAA0B;iBAC7B,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;QACP,CAAC;QAED,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,CAAW,CAAC;QACvD,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAW,CAAC;QAC7D,IAAI,QAAQ,IAAI,WAAW,EAAE,CAAC;YAC1B,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;YAC3D,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC/C,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB;wBACI,kDAAkD;wBAClD,oCAAoC;wBACpC,KAAK,QAAQ,EAAE;wBACf,gEAAgE;wBAChE,MAAM,WAAW,GAAG;qBACvB,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;YACP,CAAC;QACL,CAAC;QACD,OAAO,EAAE,CAAC;IACd,CAAC;CACJ,CAAC;AAEF,SAAS,aAAa,CAAC,OAAe,EAAE,aAAqB;IACzD,MAAM,SAAS,GAAG,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtD,IAAI,SAAS,EAAE,CAAC;QACZ,MAAM,QAAQ,GAAG,IAAA,iBAAU,EAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAA,WAAI,EAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7F,IAAI,CAAC,IAAA,eAAU,EAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QACvC,OAAO,IAAA,iBAAY,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IACD,MAAM,gBAAgB,GAAG,8BAA8B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtE,IAAI,gBAAgB;QAAE,OAAO,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,gBAAgB,GAAG,8BAA8B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtE,IAAI,gBAAgB;QAAE,OAAO,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACjD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,kBAAe,eAAe,CAAC","sourcesContent":["import { execSync, spawnSync } from 'child_process';\nimport { readFileSync, existsSync } from 'fs';\nimport { isAbsolute, join } from 'path';\nimport type { BashRule, BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst FIX_HINT: readonly string[] = [\n 'Branch is not up-to-date with origin/main.',\n 'Run the squash-update process to sync with main first:',\n ' ./scripts/git-updateFromMain.sh',\n 'Then retry: gh pr create ...',\n '',\n 'Do NOT use \"git merge origin/main\" or \"git rebase\" — these break the 3-point fork-point system.',\n 'See docs/git-workflow.md for the full squash-merge update process.',\n];\n\nconst prCreationGuard: BashRule = {\n name: 'pr-creation-guard',\n description: 'Block PR creation when the branch has not been updated from origin/main via the squash-merge process.',\n scope: 'bash',\n files: [],\n defaultOptions: { buildCommand: '', requireTextInPr: '' },\n fixHint: FIX_HINT,\n\n check(ctx: BashContext): readonly Violation[] {\n if (!/gh\\s+pr\\s+create/.test(ctx.command)) return [];\n\n execSync('git fetch origin main --quiet', { cwd: ctx.workspaceRoot, encoding: 'utf8' });\n\n // spawnSync is used here because exit code 1 means \"not an ancestor\" (not an error)\n const result = spawnSync(\n 'git', ['merge-base', '--is-ancestor', 'origin/main', 'HEAD'],\n { cwd: ctx.workspaceRoot },\n );\n\n if (result.status !== 0) {\n return [new V(\n 1,\n truncate(ctx.command),\n [\n 'Branch is not up-to-date with origin/main.',\n 'Run the squash-update process first:',\n ' ./scripts/git-updateFromMain.sh',\n 'Do NOT use \"git merge origin/main\" or \"git rebase\" — these break the 3-point fork-point system.',\n 'See docs/git-workflow.md for details.',\n 'Then retry: gh pr create',\n ].join('\\n'),\n )];\n }\n\n const buildCmd = ctx.options['buildCommand'] as string;\n const requireText = ctx.options['requireTextInPr'] as string;\n if (buildCmd && requireText) {\n const body = extractPrBody(ctx.command, ctx.workspaceRoot);\n if (body !== null && !body.includes(requireText)) {\n return [new V(\n 1,\n truncate(ctx.command),\n [\n 'PR body is missing the required CI confirmation.',\n 'Before creating a PR you must run:',\n ` ${buildCmd}`,\n 'After it passes, add this exact phrase to your PR description:',\n ` \"${requireText}\"`,\n ].join('\\n'),\n )];\n }\n }\n return [];\n },\n};\n\nfunction extractPrBody(command: string, workspaceRoot: string): string | null {\n const fileMatch = /--body-file\\s+(\\S+)/.exec(command);\n if (fileMatch) {\n const resolved = isAbsolute(fileMatch[1]) ? fileMatch[1] : join(workspaceRoot, fileMatch[1]);\n if (!existsSync(resolved)) return null;\n return readFileSync(resolved, 'utf8');\n }\n const doubleQuoteMatch = /--body\\s+\"((?:[^\"\\\\]|\\\\.)*)\"/.exec(command);\n if (doubleQuoteMatch) return doubleQuoteMatch[1];\n const singleQuoteMatch = /--body\\s+'((?:[^'\\\\]|\\\\.)*)'/.exec(command);\n if (singleQuoteMatch) return singleQuoteMatch[1];\n return null;\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport default prCreationGuard;\n"]}
1
+ {"version":3,"file":"pr-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/pr-creation-guard.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawnSync } from 'child_process';\nimport { readFileSync, existsSync } from 'fs';\nimport { isAbsolute, join } from 'path';\nimport type { BashRule, BashContext, Violation } from '../types';\nimport { Violation as V, FieldSchema } from '../types';\n\nconst FIX_HINT: readonly string[] = [\n 'Branch is not up-to-date with origin/main.',\n 'Run the squash-update process to sync with main first:',\n ' ./scripts/git-updateFromMain.sh',\n 'Then retry: gh pr create ...',\n '',\n 'Do NOT use \"git merge origin/main\" or \"git rebase\" — these break the 3-point fork-point system.',\n 'See docs/git-workflow.md for the full squash-merge update process.',\n];\n\nconst prCreationGuard: BashRule = {\n name: 'pr-creation-guard',\n description: 'Block PR creation when the branch has not been updated from origin/main via the squash-merge process.',\n scope: 'bash',\n files: [],\n defaultOptions: { buildCommand: '', requireTextInPr: '' },\n configSchema: {\n buildCommand: new FieldSchema('string', 'Build/test command AI must run before creating a PR (empty string disables the check)'),\n requireTextInPr: new FieldSchema('string', 'Exact text AI must include in the PR body to confirm the build passed (empty string disables)'),\n },\n fixHint: FIX_HINT,\n\n check(ctx: BashContext): readonly Violation[] {\n if (!/gh\\s+pr\\s+create/.test(ctx.command)) return [];\n\n execSync('git fetch origin main --quiet', { cwd: ctx.workspaceRoot, encoding: 'utf8' });\n\n // spawnSync is used here because exit code 1 means \"not an ancestor\" (not an error)\n const result = spawnSync(\n 'git', ['merge-base', '--is-ancestor', 'origin/main', 'HEAD'],\n { cwd: ctx.workspaceRoot },\n );\n\n if (result.status !== 0) {\n return [new V(\n 1,\n truncate(ctx.command),\n [\n 'Branch is not up-to-date with origin/main.',\n 'Run the squash-update process first:',\n ' ./scripts/git-updateFromMain.sh',\n 'Do NOT use \"git merge origin/main\" or \"git rebase\" — these break the 3-point fork-point system.',\n 'See docs/git-workflow.md for details.',\n 'Then retry: gh pr create',\n ].join('\\n'),\n )];\n }\n\n const buildCmd = ctx.options['buildCommand'] as string;\n const requireText = ctx.options['requireTextInPr'] as string;\n if (buildCmd && requireText) {\n const body = extractPrBody(ctx.command, ctx.workspaceRoot);\n if (body !== null && !body.includes(requireText)) {\n return [new V(\n 1,\n truncate(ctx.command),\n [\n 'PR body is missing the required CI confirmation.',\n 'Before creating a PR you must run:',\n ` ${buildCmd}`,\n 'After it passes, add this exact phrase to your PR description:',\n ` \"${requireText}\"`,\n ].join('\\n'),\n )];\n }\n }\n return [];\n },\n};\n\nfunction extractPrBody(command: string, workspaceRoot: string): string | null {\n const fileMatch = /--body-file\\s+(\\S+)/.exec(command);\n if (fileMatch) {\n const resolved = isAbsolute(fileMatch[1]) ? fileMatch[1] : join(workspaceRoot, fileMatch[1]);\n if (!existsSync(resolved)) return null;\n return readFileSync(resolved, 'utf8');\n }\n const doubleQuoteMatch = /--body\\s+\"((?:[^\"\\\\]|\\\\.)*)\"/.exec(command);\n if (doubleQuoteMatch) return doubleQuoteMatch[1];\n const singleQuoteMatch = /--body\\s+'((?:[^'\\\\]|\\\\.)*)'/.exec(command);\n if (singleQuoteMatch) return singleQuoteMatch[1];\n return null;\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport default prCreationGuard;\n"]}
@@ -8,6 +8,7 @@ const prMergeCleanup = {
8
8
  scope: 'bash',
9
9
  files: [],
10
10
  defaultOptions: {},
11
+ configSchema: {},
11
12
  fixHint: [
12
13
  'After merging a PR you must clean up the local branch.',
13
14
  'Run this combined command instead:',
@@ -1 +1 @@
1
- {"version":3,"file":"pr-merge-cleanup.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/pr-merge-cleanup.ts"],"names":[],"mappings":";;AAAA,iDAAyC;AAEzC,oCAA0C;AAE1C,MAAM,cAAc,GAAa;IAC7B,IAAI,EAAE,kBAAkB;IACxB,WAAW,EAAE,wFAAwF;IACrG,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,EAAE;IACT,cAAc,EAAE,EAAE;IAClB,OAAO,EAAE;QACL,wDAAwD;QACxD,oCAAoC;QACpC,2FAA2F;KAC9F;IAED,KAAK,CAAC,GAAgB;QAClB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAEpD,MAAM,WAAW,GAAG,gCAAgC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvE,MAAM,SAAS,GAAG,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAE3D,IAAI,WAAW,IAAI,SAAS;YAAE,OAAO,EAAE,CAAC;QAExC,MAAM,aAAa,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE;YAC9D,GAAG,EAAE,GAAG,CAAC,aAAa;YACtB,QAAQ,EAAE,MAAM;SACnB,CAAC,CAAC,IAAI,EAAE,CAAC;QAEV,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB;gBACI,2EAA2E;gBAC3E,oCAAoC;gBACpC,4EAA4E,aAAa,EAAE;aAC9F,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;IACP,CAAC;CACJ,CAAC;AAEF,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,kBAAe,cAAc,CAAC","sourcesContent":["import { execSync } from 'child_process';\nimport type { BashRule, BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst prMergeCleanup: BashRule = {\n name: 'pr-merge-cleanup',\n description: 'After merging a PR, require switching to main, pulling, and deleting the local branch.',\n scope: 'bash',\n files: [],\n defaultOptions: {},\n fixHint: [\n 'After merging a PR you must clean up the local branch.',\n 'Run this combined command instead:',\n ' gh pr merge --squash && git checkout main && git pull && git branch -d <current-branch>',\n ],\n\n check(ctx: BashContext): readonly Violation[] {\n if (!/gh\\s+pr\\s+merge/.test(ctx.command)) return [];\n\n const hasCheckout = /git\\s+(checkout|switch)\\s+main/.test(ctx.command);\n const hasDelete = /git\\s+branch\\s+-[dD]/.test(ctx.command);\n\n if (hasCheckout && hasDelete) return [];\n\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n\n return [new V(\n 1,\n truncate(ctx.command),\n [\n '[pr-merge-cleanup] After merging a PR you must clean up the local branch.',\n 'Run this combined command instead:',\n ` gh pr merge --squash && git checkout main && git pull && git branch -d ${currentBranch}`,\n ].join('\\n'),\n )];\n },\n};\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport default prMergeCleanup;\n"]}
1
+ {"version":3,"file":"pr-merge-cleanup.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/pr-merge-cleanup.ts"],"names":[],"mappings":";;AAAA,iDAAyC;AAEzC,oCAA0C;AAE1C,MAAM,cAAc,GAAa;IAC7B,IAAI,EAAE,kBAAkB;IACxB,WAAW,EAAE,wFAAwF;IACrG,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,EAAE;IACT,cAAc,EAAE,EAAE;IAClB,YAAY,EAAE,EAAE;IAChB,OAAO,EAAE;QACL,wDAAwD;QACxD,oCAAoC;QACpC,2FAA2F;KAC9F;IAED,KAAK,CAAC,GAAgB;QAClB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAEpD,MAAM,WAAW,GAAG,gCAAgC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvE,MAAM,SAAS,GAAG,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAE3D,IAAI,WAAW,IAAI,SAAS;YAAE,OAAO,EAAE,CAAC;QAExC,MAAM,aAAa,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE;YAC9D,GAAG,EAAE,GAAG,CAAC,aAAa;YACtB,QAAQ,EAAE,MAAM;SACnB,CAAC,CAAC,IAAI,EAAE,CAAC;QAEV,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB;gBACI,2EAA2E;gBAC3E,oCAAoC;gBACpC,4EAA4E,aAAa,EAAE;aAC9F,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;IACP,CAAC;CACJ,CAAC;AAEF,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,kBAAe,cAAc,CAAC","sourcesContent":["import { execSync } from 'child_process';\nimport type { BashRule, BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst prMergeCleanup: BashRule = {\n name: 'pr-merge-cleanup',\n description: 'After merging a PR, require switching to main, pulling, and deleting the local branch.',\n scope: 'bash',\n files: [],\n defaultOptions: {},\n configSchema: {},\n fixHint: [\n 'After merging a PR you must clean up the local branch.',\n 'Run this combined command instead:',\n ' gh pr merge --squash && git checkout main && git pull && git branch -d <current-branch>',\n ],\n\n check(ctx: BashContext): readonly Violation[] {\n if (!/gh\\s+pr\\s+merge/.test(ctx.command)) return [];\n\n const hasCheckout = /git\\s+(checkout|switch)\\s+main/.test(ctx.command);\n const hasDelete = /git\\s+branch\\s+-[dD]/.test(ctx.command);\n\n if (hasCheckout && hasDelete) return [];\n\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n\n return [new V(\n 1,\n truncate(ctx.command),\n [\n '[pr-merge-cleanup] After merging a PR you must clean up the local branch.',\n 'Run this combined command instead:',\n ` gh pr merge --squash && git checkout main && git pull && git branch -d ${currentBranch}`,\n ].join('\\n'),\n )];\n },\n};\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport default prMergeCleanup;\n"]}
@@ -18,6 +18,7 @@ const requireReturnTypeRule = {
18
18
  scope: 'edit',
19
19
  files: ['**/*.ts', '**/*.tsx'],
20
20
  defaultOptions: {},
21
+ configSchema: {},
21
22
  fixHint: [
22
23
  'Add return type: function foo(x: T): ReturnType { ... }',
23
24
  '// webpieces-disable require-return-type -- <reason>',
@@ -1 +1 @@
1
- {"version":3,"file":"require-return-type.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/require-return-type.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAE1C,gGAAgG;AAChG,4EAA4E;AAC5E,MAAM,iBAAiB,GAAG,kDAAkD,CAAC;AAE7E,8EAA8E;AAC9E,MAAM,cAAc,GAAG,yDAAyD,CAAC;AAEjF,qFAAqF;AACrF,MAAM,aAAa,GAAG,gEAAgE,CAAC;AAEvF,qEAAqE;AACrE,MAAM,eAAe,GAAG,aAAa,CAAC;AAEtC,iEAAiE;AACjE,MAAM,YAAY,GAAG,kFAAkF,CAAC;AAExG,MAAM,qBAAqB,GAAa;IACpC,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EAAE,yDAAyD;IACtE,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE;IAClB,OAAO,EAAE;QACL,yDAAyD;QACzD,sDAAsD;KACzD;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC7C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,qBAAqB,CAAC;gBAAE,SAAS;YACjE,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,iCAAiC,CACpC,CAAC,CAAC;QACP,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAC;AAEF,SAAS,mBAAmB,CAAC,IAAY;IACrC,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,MAAM,UAAU,GACZ,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5B,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QACzB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAC9B,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,kBAAe,qBAAqB,CAAC","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\n// Matches function/method signatures that don't have `: ReturnType` before the `{` body opener.\n// Pattern: function name(<params>) { — missing `: Type` between `)` and `{`\nconst FUNC_DECL_MISSING = /\\bfunction\\s+\\w+\\s*(?:<[^>]*>)?\\s*\\([^)]*\\)\\s*\\{/;\n\n// Matches class method signatures: indented, optional async, name(<params>) {\nconst METHOD_MISSING = /^\\s{2,}(?:async\\s+)?\\w+\\s*(?:<[^>]*>)?\\s*\\([^)]*\\)\\s*\\{/;\n\n// Arrow function: const name = (async)? (<params>) => — missing `: Type` before `=>`\nconst ARROW_MISSING = /\\bconst\\s+\\w+\\s*=\\s*(?:async\\s+)?(?:<[^>]*>)?\\s*\\([^)]*\\)\\s*=>/;\n\n// Lines that have ): ReturnType before { or => — these are COMPLIANT\nconst HAS_RETURN_TYPE = /\\)\\s*:\\s*\\S/;\n\n// Skip constructors, getters, setters, and control flow keywords\nconst SKIP_PATTERN = /\\b(?:constructor|get\\s+\\w+|set\\s+\\w+|if|else|while|for|switch|catch|return)\\s*\\(/;\n\nconst requireReturnTypeRule: EditRule = {\n name: 'require-return-type',\n description: 'Every function and method must declare its return type.',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n fixHint: [\n 'Add return type: function foo(x: T): ReturnType { ... }',\n '// webpieces-disable require-return-type -- <reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!isMissingReturnType(stripped)) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'require-return-type')) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n 'Missing return type annotation.',\n ));\n }\n return violations;\n },\n};\n\nfunction isMissingReturnType(line: string): boolean {\n if (SKIP_PATTERN.test(line)) return false;\n const isFuncLike =\n FUNC_DECL_MISSING.test(line) ||\n METHOD_MISSING.test(line) ||\n ARROW_MISSING.test(line);\n if (!isFuncLike) return false;\n if (HAS_RETURN_TYPE.test(line)) return false;\n return true;\n}\n\nexport default requireReturnTypeRule;\n"]}
1
+ {"version":3,"file":"require-return-type.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/require-return-type.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAE1C,gGAAgG;AAChG,4EAA4E;AAC5E,MAAM,iBAAiB,GAAG,kDAAkD,CAAC;AAE7E,8EAA8E;AAC9E,MAAM,cAAc,GAAG,yDAAyD,CAAC;AAEjF,qFAAqF;AACrF,MAAM,aAAa,GAAG,gEAAgE,CAAC;AAEvF,qEAAqE;AACrE,MAAM,eAAe,GAAG,aAAa,CAAC;AAEtC,iEAAiE;AACjE,MAAM,YAAY,GAAG,kFAAkF,CAAC;AAExG,MAAM,qBAAqB,GAAa;IACpC,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EAAE,yDAAyD;IACtE,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE;IAClB,YAAY,EAAE,EAAE;IAChB,OAAO,EAAE;QACL,yDAAyD;QACzD,sDAAsD;KACzD;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC7C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,qBAAqB,CAAC;gBAAE,SAAS;YACjE,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,iCAAiC,CACpC,CAAC,CAAC;QACP,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAC;AAEF,SAAS,mBAAmB,CAAC,IAAY;IACrC,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,MAAM,UAAU,GACZ,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5B,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QACzB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAC9B,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,kBAAe,qBAAqB,CAAC","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\n// Matches function/method signatures that don't have `: ReturnType` before the `{` body opener.\n// Pattern: function name(<params>) { — missing `: Type` between `)` and `{`\nconst FUNC_DECL_MISSING = /\\bfunction\\s+\\w+\\s*(?:<[^>]*>)?\\s*\\([^)]*\\)\\s*\\{/;\n\n// Matches class method signatures: indented, optional async, name(<params>) {\nconst METHOD_MISSING = /^\\s{2,}(?:async\\s+)?\\w+\\s*(?:<[^>]*>)?\\s*\\([^)]*\\)\\s*\\{/;\n\n// Arrow function: const name = (async)? (<params>) => — missing `: Type` before `=>`\nconst ARROW_MISSING = /\\bconst\\s+\\w+\\s*=\\s*(?:async\\s+)?(?:<[^>]*>)?\\s*\\([^)]*\\)\\s*=>/;\n\n// Lines that have ): ReturnType before { or => — these are COMPLIANT\nconst HAS_RETURN_TYPE = /\\)\\s*:\\s*\\S/;\n\n// Skip constructors, getters, setters, and control flow keywords\nconst SKIP_PATTERN = /\\b(?:constructor|get\\s+\\w+|set\\s+\\w+|if|else|while|for|switch|catch|return)\\s*\\(/;\n\nconst requireReturnTypeRule: EditRule = {\n name: 'require-return-type',\n description: 'Every function and method must declare its return type.',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n configSchema: {},\n fixHint: [\n 'Add return type: function foo(x: T): ReturnType { ... }',\n '// webpieces-disable require-return-type -- <reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!isMissingReturnType(stripped)) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'require-return-type')) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n 'Missing return type annotation.',\n ));\n }\n return violations;\n },\n};\n\nfunction isMissingReturnType(line: string): boolean {\n if (SKIP_PATTERN.test(line)) return false;\n const isFuncLike =\n FUNC_DECL_MISSING.test(line) ||\n METHOD_MISSING.test(line) ||\n ARROW_MISSING.test(line);\n if (!isFuncLike) return false;\n if (HAS_RETURN_TYPE.test(line)) return false;\n return true;\n}\n\nexport default requireReturnTypeRule;\n"]}
@@ -20,6 +20,7 @@ const throwCauseRequiredRule = {
20
20
  scope: 'edit',
21
21
  files: ['**/*.ts', '**/*.tsx'],
22
22
  defaultOptions: {},
23
+ configSchema: {},
23
24
  fixHint: [
24
25
  'Option 1 — Remove the try-catch entirely. Letting the original exception bubble is usually the best option.',
25
26
  'Option 2 — throw new Error("add more info here", { cause: error }); chains the error and preserves the full stack trace.',
@@ -1 +1 @@
1
- {"version":3,"file":"throw-cause-required.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/throw-cause-required.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAC1C,8DAA+D;AAE/D;;GAEG;AACH,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;AAErD;;GAEG;AACH,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AAEtD;;GAEG;AACH,MAAM,aAAa,GAAG,0BAA0B,CAAC;AAEjD,MAAM,sBAAsB,GAAa;IACrC,IAAI,EAAE,sBAAsB;IAC5B,WAAW,EAAE,4GAA4G;IACzH,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE;IAClB,OAAO,EAAE;QACL,6GAA6G;QAC7G,0HAA0H;QAC1H,0IAA0I;QAC1I,gGAAgG;KACnG;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,GAAG,CAAC,aAAa,CAAC;QAEhC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAChD,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACpD,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAE3C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,sBAAsB,CAAC;gBAAE,SAAS;YAElE,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,iGAAiG,CACpG,CAAC,CAAC;QACP,CAAC;QAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,IAAA,2CAAsB,EAAC,GAAG,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC;QAChG,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAC;AAEF,kBAAe,sBAAsB,CAAC","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { writeTemplateIfMissing } from '../instruct-ai-writer';\n\n/**\n * Matches: throw new SomeClass(\n */\nconst THROW_NEW_PATTERN = /\\bthrow\\s+new\\s+\\w+\\s*\\(/;\n\n/**\n * Matches: error.message or error2.message or error3.message\n */\nconst ERROR_MESSAGE_PATTERN = /\\berror\\d*\\.message\\b/;\n\n/**\n * Matches: cause: error or cause: error2 etc. (the good pattern)\n */\nconst CAUSE_PATTERN = /\\bcause\\s*:\\s*error\\d*\\b/;\n\nconst throwCauseRequiredRule: EditRule = {\n name: 'throw-cause-required',\n description: 'When rethrowing with added context, chain the original exception: throw new Error(\"msg\", { cause: error })',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n fixHint: [\n 'Option 1 — Remove the try-catch entirely. Letting the original exception bubble is usually the best option.',\n 'Option 2 — throw new Error(\"add more info here\", { cause: error }); chains the error and preserves the full stack trace.',\n 'Option 3 — throw new SpecificError(\"add info here\", { cause: error }); e.g. new InformAiError(\"what happened for AI\", { cause: error }).',\n '[Only if disableAllowed:true] Option 4 — // webpieces-disable throw-cause-required -- <reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n const lines = ctx.strippedLines;\n\n for (let i = 0; i < lines.length; i += 1) {\n const stripped = lines[i];\n if (!THROW_NEW_PATTERN.test(stripped)) continue;\n if (!ERROR_MESSAGE_PATTERN.test(stripped)) continue;\n if (CAUSE_PATTERN.test(stripped)) continue;\n\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'throw-cause-required')) continue;\n\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n 'Rethrowing with error.message loses the original stack trace. Use { cause: error } to chain it.',\n ));\n }\n\n if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');\n return violations;\n },\n};\n\nexport default throwCauseRequiredRule;\n"]}
1
+ {"version":3,"file":"throw-cause-required.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/throw-cause-required.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAC1C,8DAA+D;AAE/D;;GAEG;AACH,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;AAErD;;GAEG;AACH,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AAEtD;;GAEG;AACH,MAAM,aAAa,GAAG,0BAA0B,CAAC;AAEjD,MAAM,sBAAsB,GAAa;IACrC,IAAI,EAAE,sBAAsB;IAC5B,WAAW,EAAE,4GAA4G;IACzH,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE;IAClB,YAAY,EAAE,EAAE;IAChB,OAAO,EAAE;QACL,6GAA6G;QAC7G,0HAA0H;QAC1H,0IAA0I;QAC1I,gGAAgG;KACnG;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,GAAG,CAAC,aAAa,CAAC;QAEhC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAChD,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACpD,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAE3C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,sBAAsB,CAAC;gBAAE,SAAS;YAElE,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,iGAAiG,CACpG,CAAC,CAAC;QACP,CAAC;QAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,IAAA,2CAAsB,EAAC,GAAG,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC;QAChG,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAC;AAEF,kBAAe,sBAAsB,CAAC","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { writeTemplateIfMissing } from '../instruct-ai-writer';\n\n/**\n * Matches: throw new SomeClass(\n */\nconst THROW_NEW_PATTERN = /\\bthrow\\s+new\\s+\\w+\\s*\\(/;\n\n/**\n * Matches: error.message or error2.message or error3.message\n */\nconst ERROR_MESSAGE_PATTERN = /\\berror\\d*\\.message\\b/;\n\n/**\n * Matches: cause: error or cause: error2 etc. (the good pattern)\n */\nconst CAUSE_PATTERN = /\\bcause\\s*:\\s*error\\d*\\b/;\n\nconst throwCauseRequiredRule: EditRule = {\n name: 'throw-cause-required',\n description: 'When rethrowing with added context, chain the original exception: throw new Error(\"msg\", { cause: error })',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n configSchema: {},\n fixHint: [\n 'Option 1 — Remove the try-catch entirely. Letting the original exception bubble is usually the best option.',\n 'Option 2 — throw new Error(\"add more info here\", { cause: error }); chains the error and preserves the full stack trace.',\n 'Option 3 — throw new SpecificError(\"add info here\", { cause: error }); e.g. new InformAiError(\"what happened for AI\", { cause: error }).',\n '[Only if disableAllowed:true] Option 4 — // webpieces-disable throw-cause-required -- <reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n const lines = ctx.strippedLines;\n\n for (let i = 0; i < lines.length; i += 1) {\n const stripped = lines[i];\n if (!THROW_NEW_PATTERN.test(stripped)) continue;\n if (!ERROR_MESSAGE_PATTERN.test(stripped)) continue;\n if (CAUSE_PATTERN.test(stripped)) continue;\n\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'throw-cause-required')) continue;\n\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n 'Rethrowing with error.message loses the original stack trace. Use { cause: error } to chain it.',\n ));\n }\n\n if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');\n return violations;\n },\n};\n\nexport default throwCauseRequiredRule;\n"]}
@@ -28,6 +28,10 @@ const validateTsInSrcRule = {
28
28
  excludePaths: DEFAULT_EXCLUDE_PATHS,
29
29
  allowedRootFiles: DEFAULT_ALLOWED_ROOT_FILES,
30
30
  },
31
+ configSchema: {
32
+ excludePaths: new types_1.FieldSchema('string[]', 'Paths/globs excluded from the src/ validation check'),
33
+ allowedRootFiles: new types_1.FieldSchema('string[]', 'Filenames allowed at the workspace root (outside any project src/)'),
34
+ },
31
35
  fixHint: [
32
36
  'Move the file into an existing project\'s src/ directory, or create a new project with project.json that owns the directory.',
33
37
  'Add a dir or glob (e.g. "**/codegen.ts") to validate-ts-in-src.excludePaths in webpieces.config.json',
@@ -35,12 +39,8 @@ const validateTsInSrcRule = {
35
39
  check(ctx) {
36
40
  if (ctx.tool !== 'Write')
37
41
  return [];
38
- const excludePaths = Array.isArray(ctx.options['excludePaths'])
39
- ? ctx.options['excludePaths']
40
- : DEFAULT_EXCLUDE_PATHS;
41
- const allowedRootFiles = Array.isArray(ctx.options['allowedRootFiles'])
42
- ? ctx.options['allowedRootFiles']
43
- : DEFAULT_ALLOWED_ROOT_FILES;
42
+ const excludePaths = ctx.options['excludePaths'];
43
+ const allowedRootFiles = ctx.options['allowedRootFiles'];
44
44
  // Holistic exclusion (Layer 1 + Layer 2): bare dir names + globs.
45
45
  if ((0, rules_config_1.isPathExcluded)(ctx.relativePath, excludePaths))
46
46
  return [];
@@ -1 +1 @@
1
- {"version":3,"file":"validate-ts-in-src.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/validate-ts-in-src.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { isPathExcluded } from '@webpieces/rules-config';\n\nimport type { FileRule, FileContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst DEFAULT_EXCLUDE_PATHS = [\n 'node_modules', 'dist', '.nx', '.git',\n '**/*.d.ts', '**/jest.config.ts',\n];\nconst DEFAULT_ALLOWED_ROOT_FILES = ['jest.setup.ts'];\n\nfunction findProjectRoot(filePath: string, workspaceRoot: string): string | null {\n let dir = path.dirname(filePath);\n while (dir !== workspaceRoot && dir.startsWith(workspaceRoot)) {\n if (fs.existsSync(path.join(dir, 'project.json'))) return dir;\n dir = path.dirname(dir);\n }\n return null;\n}\n\nconst validateTsInSrcRule: FileRule = {\n name: 'validate-ts-in-src',\n description: 'Every .ts file must belong to a project\\'s src/ directory.',\n scope: 'file',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {\n excludePaths: DEFAULT_EXCLUDE_PATHS,\n allowedRootFiles: DEFAULT_ALLOWED_ROOT_FILES,\n },\n fixHint: [\n 'Move the file into an existing project\\'s src/ directory, or create a new project with project.json that owns the directory.',\n 'Add a dir or glob (e.g. \"**/codegen.ts\") to validate-ts-in-src.excludePaths in webpieces.config.json',\n ],\n\n check(ctx: FileContext): readonly Violation[] {\n if (ctx.tool !== 'Write') return [];\n\n const excludePaths = Array.isArray(ctx.options['excludePaths'])\n ? ctx.options['excludePaths'] as string[]\n : DEFAULT_EXCLUDE_PATHS;\n const allowedRootFiles = Array.isArray(ctx.options['allowedRootFiles'])\n ? ctx.options['allowedRootFiles'] as string[]\n : DEFAULT_ALLOWED_ROOT_FILES;\n\n // Holistic exclusion (Layer 1 + Layer 2): bare dir names + globs.\n if (isPathExcluded(ctx.relativePath, excludePaths)) return [];\n\n const relParts = ctx.relativePath.split(path.sep);\n if (relParts.length === 1 && allowedRootFiles.indexOf(relParts[0] ?? '') >= 0) return [];\n\n const projectRoot = findProjectRoot(ctx.filePath, ctx.workspaceRoot);\n\n if (!projectRoot) {\n return [new V(\n 1,\n ctx.relativePath,\n 'File is not inside any Nx project. Move it into a project\\'s src/ directory.',\n )];\n }\n\n const relToProject = path.relative(projectRoot, ctx.filePath);\n if (!relToProject.startsWith('src' + path.sep) && relToProject !== 'src') {\n const projectName = path.relative(ctx.workspaceRoot, projectRoot);\n return [new V(\n 1,\n ctx.relativePath,\n `File is inside project \\`${projectName}\\` but outside its src/ directory. Move it into src/.`,\n )];\n }\n\n return [];\n },\n};\n\nexport default validateTsInSrcRule;\n"]}
1
+ {"version":3,"file":"validate-ts-in-src.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/validate-ts-in-src.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { isPathExcluded } from '@webpieces/rules-config';\n\nimport type { FileRule, FileContext, Violation } from '../types';\nimport { Violation as V, FieldSchema } from '../types';\n\nconst DEFAULT_EXCLUDE_PATHS = [\n 'node_modules', 'dist', '.nx', '.git',\n '**/*.d.ts', '**/jest.config.ts',\n];\nconst DEFAULT_ALLOWED_ROOT_FILES = ['jest.setup.ts'];\n\nfunction findProjectRoot(filePath: string, workspaceRoot: string): string | null {\n let dir = path.dirname(filePath);\n while (dir !== workspaceRoot && dir.startsWith(workspaceRoot)) {\n if (fs.existsSync(path.join(dir, 'project.json'))) return dir;\n dir = path.dirname(dir);\n }\n return null;\n}\n\nconst validateTsInSrcRule: FileRule = {\n name: 'validate-ts-in-src',\n description: 'Every .ts file must belong to a project\\'s src/ directory.',\n scope: 'file',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {\n excludePaths: DEFAULT_EXCLUDE_PATHS,\n allowedRootFiles: DEFAULT_ALLOWED_ROOT_FILES,\n },\n configSchema: {\n excludePaths: new FieldSchema('string[]', 'Paths/globs excluded from the src/ validation check'),\n allowedRootFiles: new FieldSchema('string[]', 'Filenames allowed at the workspace root (outside any project src/)'),\n },\n fixHint: [\n 'Move the file into an existing project\\'s src/ directory, or create a new project with project.json that owns the directory.',\n 'Add a dir or glob (e.g. \"**/codegen.ts\") to validate-ts-in-src.excludePaths in webpieces.config.json',\n ],\n\n check(ctx: FileContext): readonly Violation[] {\n if (ctx.tool !== 'Write') return [];\n\n const excludePaths = ctx.options['excludePaths'] as string[];\n const allowedRootFiles = ctx.options['allowedRootFiles'] as string[];\n\n // Holistic exclusion (Layer 1 + Layer 2): bare dir names + globs.\n if (isPathExcluded(ctx.relativePath, excludePaths)) return [];\n\n const relParts = ctx.relativePath.split(path.sep);\n if (relParts.length === 1 && allowedRootFiles.indexOf(relParts[0] ?? '') >= 0) return [];\n\n const projectRoot = findProjectRoot(ctx.filePath, ctx.workspaceRoot);\n\n if (!projectRoot) {\n return [new V(\n 1,\n ctx.relativePath,\n 'File is not inside any Nx project. Move it into a project\\'s src/ directory.',\n )];\n }\n\n const relToProject = path.relative(projectRoot, ctx.filePath);\n if (!relToProject.startsWith('src' + path.sep) && relToProject !== 'src') {\n const projectName = path.relative(ctx.workspaceRoot, projectRoot);\n return [new V(\n 1,\n ctx.relativePath,\n `File is inside project \\`${projectName}\\` but outside its src/ directory. Move it into src/.`,\n )];\n }\n\n return [];\n },\n};\n\nexport default validateTsInSrcRule;\n"]}
@@ -28,7 +28,7 @@ function runInternal(toolKind, input, cwd) {
28
28
  const rules = (0, load_rules_1.loadRules)(config, workspaceRoot);
29
29
  if (rules.length === 0)
30
30
  return null;
31
- const outOfSync = checkConfigSync(rules, config);
31
+ const outOfSync = validateConfig(rules, config);
32
32
  if (outOfSync)
33
33
  return outOfSync;
34
34
  const contexts = (0, build_context_1.buildContexts)(toolKind, input, workspaceRoot);
@@ -55,7 +55,7 @@ function runBashInternal(command, cwd) {
55
55
  const rules = (0, load_rules_1.loadRules)(config, workspaceRoot);
56
56
  if (rules.length === 0)
57
57
  return null;
58
- const outOfSync = checkConfigSync(rules, config);
58
+ const outOfSync = validateConfig(rules, config);
59
59
  if (outOfSync)
60
60
  return outOfSync;
61
61
  const ctx = (0, build_context_1.buildBashContext)(command, workspaceRoot);
@@ -65,40 +65,90 @@ function runBashInternal(command, cwd) {
65
65
  const report = (0, report_1.formatReport)('<bash>', groups);
66
66
  return new types_1.BlockedResult(report);
67
67
  }
68
- function checkConfigSync(rules, config) {
69
- const unconfiguredRules = rules.filter((r) => !config.userConfiguredRuleNames.has(r.name));
70
- if (unconfiguredRules.length === 0)
68
+ function validateConfig(rules, config) {
69
+ const errors = [];
70
+ // Pass 1 — unconfigured rules
71
+ const unconfigured = rules.filter((r) => !config.userConfiguredRuleNames.has(r.name));
72
+ for (const rule of unconfigured) {
73
+ errors.push(`[${rule.name}] UNCONFIGURED — add an entry to webpieces.config.json`, ` Description: ${rule.description}`, ` Required fields: mode ("ON" or "OFF")${schemaFieldList(rule.configSchema)}`, ` Example: ${buildExampleEntry(rule)}`, '');
74
+ }
75
+ // Pass 2 — validate configured rules
76
+ for (const name of config.userConfiguredRuleNames) {
77
+ const rule = rules.find((r) => r.name === name);
78
+ if (!rule)
79
+ continue;
80
+ const entryErrors = validateRuleEntry(rule, name, config);
81
+ if (entryErrors.length > 0) {
82
+ errors.push(`[${name}] INVALID entry in webpieces.config.json:`, ...entryErrors, ` Correct example: ${buildExampleEntry(rule)}`, '');
83
+ }
84
+ }
85
+ if (errors.length === 0)
71
86
  return null;
72
- const lines = [
73
- 'webpieces.config.json is out of sync — new built-in rules are present that have no entry in webpieces.config.json.',
74
- '',
75
- 'Tell the human: the following rules need to be configured. Ask for each one:',
76
- ' - Should this rule be ON, OFF, MODIFIED_CODE, or MODIFIED_FILES?',
77
- ' - What values do you want for the options listed below?',
78
- 'Then update webpieces.config.json and retry.',
79
- '',
80
- 'Do NOT proceed until webpieces.config.json has an entry for every rule below.',
81
- '',
82
- ];
83
- for (const rule of unconfiguredRules) {
84
- lines.push(`--- ${rule.name} ---`);
85
- lines.push(`Description: ${rule.description}`);
86
- const opts = rule.defaultOptions;
87
- const optKeys = Object.keys(opts);
88
- if (optKeys.length > 0) {
89
- lines.push(`Available options (suggested defaults shown):`);
90
- for (const key of optKeys) {
91
- lines.push(` ${key}: ${JSON.stringify(opts[key])}`);
92
- }
87
+ return new types_1.BlockedResult(['STOP. DO NOT PROCEED. webpieces.config.json has validation errors.', 'Fix ALL of the following, then retry:', '', ...errors].join('\n'));
88
+ }
89
+ function validateRuleEntry(rule, name, config) {
90
+ const entry = config.rules.get(name);
91
+ const rawEntry = getRawUserEntry(config, name);
92
+ const errs = [];
93
+ if (typeof entry?.options['mode'] !== 'string' || entry.options['mode'] === '') {
94
+ const found = rawEntry ? Object.keys(rawEntry) : [];
95
+ errs.push(` ✗ "mode" is required (must be "ON" or "OFF"). Found keys: [${found.join(', ') || 'none'}]`);
96
+ }
97
+ for (const [key, schema] of Object.entries(rule.configSchema)) {
98
+ const val = rawEntry?.[key];
99
+ if (val === undefined) {
100
+ errs.push(` ✗ "${key}" is required (type: ${schema.type}) — ${schema.description}`);
101
+ }
102
+ else if (!matchesType(val, schema.type)) {
103
+ errs.push(` ✗ "${key}" must be type ${schema.type}, got ${typeof val}`);
93
104
  }
94
- else {
95
- lines.push('Available options: none beyond mode');
105
+ }
106
+ const knownKeys = new Set(['mode', ...Object.keys(rule.configSchema)]);
107
+ for (const key of Object.keys(rawEntry ?? {})) {
108
+ if (!knownKeys.has(key)) {
109
+ errs.push(` ✗ Unknown key "${key}" — remove it. Valid keys: [${[...knownKeys].join(', ')}]`);
96
110
  }
97
- lines.push(`Example entry for webpieces.config.json:`);
98
- lines.push(` "${rule.name}": { "mode": "ON" }`);
99
- lines.push('');
100
111
  }
101
- return new types_1.BlockedResult(lines.join('\n'));
112
+ return errs;
113
+ }
114
+ function getRawUserEntry(config, name) {
115
+ return config.rawUserRules.get(name);
116
+ }
117
+ function matchesType(val, type) {
118
+ if (type === 'string')
119
+ return typeof val === 'string';
120
+ if (type === 'number')
121
+ return typeof val === 'number';
122
+ if (type === 'boolean')
123
+ return typeof val === 'boolean';
124
+ if (type === 'string[]')
125
+ return Array.isArray(val) && val.every((v) => typeof v === 'string');
126
+ return false;
127
+ }
128
+ function schemaFieldList(schema) {
129
+ const keys = Object.keys(schema);
130
+ if (keys.length === 0)
131
+ return '';
132
+ return ', ' + keys.map((k) => `${k} (${schema[k].type})`).join(', ');
133
+ }
134
+ function buildExampleEntry(rule) {
135
+ const obj = { mode: 'ON' };
136
+ for (const [key, schema] of Object.entries(rule.configSchema)) {
137
+ obj[key] = rule.defaultOptions[key] ?? defaultForType(schema.type);
138
+ }
139
+ return `"${rule.name}": ${JSON.stringify(obj)}`;
140
+ }
141
+ function defaultForType(type) {
142
+ if (type === 'string')
143
+ return '';
144
+ if (type === 'number')
145
+ return 0;
146
+ if (type === 'boolean')
147
+ return false;
148
+ if (type === 'string[]')
149
+ return [];
150
+ const exhaustive = type;
151
+ throw new Error(`Unhandled FieldSchema type: ${exhaustive}`);
102
152
  }
103
153
  // N-legs pattern: each rule runs independently; crash → visible violation so AI sees it, not silent []
104
154
  function runRuleCheck(rule, ctx) {
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/runner.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\n\nimport { buildContexts, buildBashContext } from './build-context';\nimport { loadConfig } from './load-config';\nimport { loadRules, globMatches } from './load-rules';\nimport { toError } from './to-error';\nimport { formatReport } from './report';\nimport {\n ToolKind, NormalizedToolInput, BlockedResult,\n Rule, EditRule, FileRule, BashRule, Violation, RuleGroup,\n EditContext, FileContext, BashContext,\n ResolvedConfig, ResolvedRuleConfig, RuleOptions,\n} from './types';\n\nexport function run(\n toolKind: ToolKind,\n input: NormalizedToolInput,\n cwd: string,\n): BlockedResult | null {\n return runInternal(toolKind, input, cwd);\n}\n\nfunction runInternal(\n toolKind: ToolKind,\n input: NormalizedToolInput,\n cwd: string,\n): BlockedResult | null {\n const config = loadConfig(cwd);\n if (!config.configPath) {\n return new BlockedResult(\n 'webpieces.config.json not found.\\n' +\n 'Tell the human: run `./node_modules/.bin/wp-setup-ai-hooks` to initialize the project configuration.\\n' +\n 'Do not proceed until the human has done this.',\n );\n }\n\n const workspaceRoot = path.dirname(config.configPath);\n\n // Always allow edits to webpieces.config.json — it's the fix target when out of sync\n if (path.resolve(input.filePath) === path.resolve(config.configPath)) {\n return null;\n }\n\n const rules = loadRules(config, workspaceRoot);\n if (rules.length === 0) return null;\n\n const outOfSync = checkConfigSync(rules, config);\n if (outOfSync) return outOfSync;\n\n const contexts = buildContexts(toolKind, input, workspaceRoot);\n const relativePath = path.relative(workspaceRoot, input.filePath);\n\n const editGroups = runEditRules(rules, contexts.editContexts, config);\n const fileGroups = runFileRules(rules, contexts.fileContext, config);\n const allGroups = [...editGroups, ...fileGroups];\n\n if (allGroups.length === 0) return null;\n\n const report = formatReport(relativePath, allGroups);\n return new BlockedResult(report);\n}\n\nexport function runBash(command: string, cwd: string): BlockedResult | null {\n return runBashInternal(command, cwd);\n}\n\nfunction runBashInternal(command: string, cwd: string): BlockedResult | null {\n const config = loadConfig(cwd);\n if (!config.configPath) {\n return new BlockedResult(\n 'webpieces.config.json not found.\\n' +\n 'Tell the human: run `./node_modules/.bin/wp-setup-ai-hooks` to initialize the project configuration.\\n' +\n 'Do not proceed until the human has done this.',\n );\n }\n\n const workspaceRoot = path.dirname(config.configPath);\n const rules = loadRules(config, workspaceRoot);\n if (rules.length === 0) return null;\n\n const outOfSync = checkConfigSync(rules, config);\n if (outOfSync) return outOfSync;\n\n const ctx = buildBashContext(command, workspaceRoot);\n const groups = runBashRules(rules, ctx, config);\n if (groups.length === 0) return null;\n\n const report = formatReport('<bash>', groups);\n return new BlockedResult(report);\n}\n\nfunction checkConfigSync(rules: readonly Rule[], config: ResolvedConfig): BlockedResult | null {\n const unconfiguredRules = rules.filter((r: Rule) => !config.userConfiguredRuleNames.has(r.name));\n if (unconfiguredRules.length === 0) return null;\n\n const lines = [\n 'webpieces.config.json is out of sync — new built-in rules are present that have no entry in webpieces.config.json.',\n '',\n 'Tell the human: the following rules need to be configured. Ask for each one:',\n ' - Should this rule be ON, OFF, MODIFIED_CODE, or MODIFIED_FILES?',\n ' - What values do you want for the options listed below?',\n 'Then update webpieces.config.json and retry.',\n '',\n 'Do NOT proceed until webpieces.config.json has an entry for every rule below.',\n '',\n ];\n\n for (const rule of unconfiguredRules) {\n lines.push(`--- ${rule.name} ---`);\n lines.push(`Description: ${rule.description}`);\n const opts = rule.defaultOptions;\n const optKeys = Object.keys(opts);\n if (optKeys.length > 0) {\n lines.push(`Available options (suggested defaults shown):`);\n for (const key of optKeys) {\n lines.push(` ${key}: ${JSON.stringify(opts[key])}`);\n }\n } else {\n lines.push('Available options: none beyond mode');\n }\n lines.push(`Example entry for webpieces.config.json:`);\n lines.push(` \"${rule.name}\": { \"mode\": \"ON\" }`);\n lines.push('');\n }\n\n return new BlockedResult(lines.join('\\n'));\n}\n\n// N-legs pattern: each rule runs independently; crash → visible violation so AI sees it, not silent []\nfunction runRuleCheck(rule: Rule, ctx: EditContext | FileContext | BashContext): readonly Violation[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return (rule as EditRule | FileRule | BashRule).check(ctx as never);\n } catch (err: unknown) {\n const error = toError(err);\n return [new Violation(0, '', `Rule '${rule.name}' crashed: ${error.message}`)];\n }\n}\n\nfunction runBashRules(\n rules: readonly Rule[],\n bashContext: BashContext,\n config: ResolvedConfig,\n): readonly RuleGroup[] {\n const groups: RuleGroup[] = [];\n for (const rule of rules) {\n if (rule.scope !== 'bash') continue;\n const ruleConfig = config.rules.get(rule.name);\n if (!ruleConfig || ruleConfig.isOff) continue;\n bashContext.options = mergeOptions(rule.defaultOptions, ruleConfig);\n const vs = runRuleCheck(rule, bashContext);\n if (vs.length > 0) {\n groups.push(new RuleGroup(\n rule.name, rule.description, [...rule.fixHint], [...vs],\n ));\n }\n }\n return groups;\n}\n\nfunction ruleMatchesFile(rule: Rule, relativePath: string): boolean {\n for (const pattern of rule.files) {\n if (globMatches(pattern, relativePath)) return true;\n }\n return false;\n}\n\nfunction mergeOptions(defaultOptions: RuleOptions, ruleConfig: ResolvedRuleConfig): RuleOptions {\n // webpieces-disable no-any-unknown -- building an options bag from opaque RuleOptions\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(defaultOptions)) out[key] = defaultOptions[key];\n for (const key of Object.keys(ruleConfig.options)) {\n // 'mode' is the framework-level on/off switch, not a rule option.\n if (key === 'mode') continue;\n out[key] = ruleConfig.options[key];\n }\n return out;\n}\n\nfunction runEditRules(\n rules: readonly Rule[],\n editContexts: readonly EditContext[],\n config: ResolvedConfig,\n): readonly RuleGroup[] {\n const groups: RuleGroup[] = [];\n for (const rule of rules) {\n if (rule.scope !== 'edit') continue;\n const ruleConfig = config.rules.get(rule.name);\n if (!ruleConfig || ruleConfig.isOff) continue;\n const allViolations: Violation[] = [];\n for (const ctx of editContexts) {\n if (!ruleMatchesFile(rule, ctx.relativePath)) continue;\n ctx.options = mergeOptions(rule.defaultOptions, ruleConfig);\n const vs = runRuleCheck(rule, ctx);\n for (const v of vs) {\n const copy = new Violation(v.line, v.snippet, v.message);\n copy.editIndex = ctx.editIndex;\n copy.editCount = ctx.editCount;\n allViolations.push(copy);\n }\n }\n if (allViolations.length > 0) {\n groups.push(new RuleGroup(\n rule.name, rule.description, [...rule.fixHint], allViolations,\n ));\n }\n }\n return groups;\n}\n\nfunction runFileRules(\n rules: readonly Rule[],\n fileContext: FileContext,\n config: ResolvedConfig,\n): readonly RuleGroup[] {\n const groups: RuleGroup[] = [];\n for (const rule of rules) {\n if (rule.scope !== 'file') continue;\n const ruleConfig = config.rules.get(rule.name);\n if (!ruleConfig || ruleConfig.isOff) continue;\n if (!ruleMatchesFile(rule, fileContext.relativePath)) continue;\n fileContext.options = mergeOptions(rule.defaultOptions, ruleConfig);\n const vs = runRuleCheck(rule, fileContext);\n if (vs.length > 0) {\n groups.push(new RuleGroup(\n rule.name, rule.description, [...rule.fixHint], [...vs],\n ));\n }\n }\n return groups;\n}\n"]}
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/runner.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\n\nimport { buildContexts, buildBashContext } from './build-context';\nimport { loadConfig } from './load-config';\nimport { loadRules, globMatches } from './load-rules';\nimport { toError } from './to-error';\nimport { formatReport } from './report';\nimport {\n ToolKind, NormalizedToolInput, BlockedResult,\n Rule, EditRule, FileRule, BashRule, Violation, RuleGroup,\n EditContext, FileContext, BashContext,\n ResolvedConfig, ResolvedRuleConfig, RuleOptions, FieldSchema,\n} from './types';\n\nexport function run(\n toolKind: ToolKind,\n input: NormalizedToolInput,\n cwd: string,\n): BlockedResult | null {\n return runInternal(toolKind, input, cwd);\n}\n\nfunction runInternal(\n toolKind: ToolKind,\n input: NormalizedToolInput,\n cwd: string,\n): BlockedResult | null {\n const config = loadConfig(cwd);\n if (!config.configPath) {\n return new BlockedResult(\n 'webpieces.config.json not found.\\n' +\n 'Tell the human: run `./node_modules/.bin/wp-setup-ai-hooks` to initialize the project configuration.\\n' +\n 'Do not proceed until the human has done this.',\n );\n }\n\n const workspaceRoot = path.dirname(config.configPath);\n\n // Always allow edits to webpieces.config.json — it's the fix target when out of sync\n if (path.resolve(input.filePath) === path.resolve(config.configPath)) {\n return null;\n }\n\n const rules = loadRules(config, workspaceRoot);\n if (rules.length === 0) return null;\n\n const outOfSync = validateConfig(rules, config);\n if (outOfSync) return outOfSync;\n\n const contexts = buildContexts(toolKind, input, workspaceRoot);\n const relativePath = path.relative(workspaceRoot, input.filePath);\n\n const editGroups = runEditRules(rules, contexts.editContexts, config);\n const fileGroups = runFileRules(rules, contexts.fileContext, config);\n const allGroups = [...editGroups, ...fileGroups];\n\n if (allGroups.length === 0) return null;\n\n const report = formatReport(relativePath, allGroups);\n return new BlockedResult(report);\n}\n\nexport function runBash(command: string, cwd: string): BlockedResult | null {\n return runBashInternal(command, cwd);\n}\n\nfunction runBashInternal(command: string, cwd: string): BlockedResult | null {\n const config = loadConfig(cwd);\n if (!config.configPath) {\n return new BlockedResult(\n 'webpieces.config.json not found.\\n' +\n 'Tell the human: run `./node_modules/.bin/wp-setup-ai-hooks` to initialize the project configuration.\\n' +\n 'Do not proceed until the human has done this.',\n );\n }\n\n const workspaceRoot = path.dirname(config.configPath);\n const rules = loadRules(config, workspaceRoot);\n if (rules.length === 0) return null;\n\n const outOfSync = validateConfig(rules, config);\n if (outOfSync) return outOfSync;\n\n const ctx = buildBashContext(command, workspaceRoot);\n const groups = runBashRules(rules, ctx, config);\n if (groups.length === 0) return null;\n\n const report = formatReport('<bash>', groups);\n return new BlockedResult(report);\n}\n\nfunction validateConfig(rules: readonly Rule[], config: ResolvedConfig): BlockedResult | null {\n const errors: string[] = [];\n\n // Pass 1 — unconfigured rules\n const unconfigured = rules.filter((r: Rule) => !config.userConfiguredRuleNames.has(r.name));\n for (const rule of unconfigured) {\n errors.push(\n `[${rule.name}] UNCONFIGURED — add an entry to webpieces.config.json`,\n ` Description: ${rule.description}`,\n ` Required fields: mode (\"ON\" or \"OFF\")${schemaFieldList(rule.configSchema)}`,\n ` Example: ${buildExampleEntry(rule)}`,\n '',\n );\n }\n\n // Pass 2 — validate configured rules\n for (const name of config.userConfiguredRuleNames) {\n const rule = rules.find((r: Rule) => r.name === name);\n if (!rule) continue;\n const entryErrors = validateRuleEntry(rule, name, config);\n if (entryErrors.length > 0) {\n errors.push(`[${name}] INVALID entry in webpieces.config.json:`, ...entryErrors, ` Correct example: ${buildExampleEntry(rule)}`, '');\n }\n }\n\n if (errors.length === 0) return null;\n return new BlockedResult(['STOP. DO NOT PROCEED. webpieces.config.json has validation errors.', 'Fix ALL of the following, then retry:', '', ...errors].join('\\n'));\n}\n\nfunction validateRuleEntry(rule: Rule, name: string, config: ResolvedConfig): string[] {\n const entry = config.rules.get(name);\n const rawEntry = getRawUserEntry(config, name);\n const errs: string[] = [];\n\n if (typeof entry?.options['mode'] !== 'string' || entry.options['mode'] === '') {\n const found = rawEntry ? Object.keys(rawEntry) : [];\n errs.push(` ✗ \"mode\" is required (must be \"ON\" or \"OFF\"). Found keys: [${found.join(', ') || 'none'}]`);\n }\n\n for (const [key, schema] of Object.entries(rule.configSchema)) {\n const val = rawEntry?.[key];\n if (val === undefined) {\n errs.push(` ✗ \"${key}\" is required (type: ${schema.type}) — ${schema.description}`);\n } else if (!matchesType(val, schema.type)) {\n errs.push(` ✗ \"${key}\" must be type ${schema.type}, got ${typeof val}`);\n }\n }\n\n const knownKeys = new Set(['mode', ...Object.keys(rule.configSchema)]);\n for (const key of Object.keys(rawEntry ?? {})) {\n if (!knownKeys.has(key)) {\n errs.push(` ✗ Unknown key \"${key}\" — remove it. Valid keys: [${[...knownKeys].join(', ')}]`);\n }\n }\n return errs;\n}\n\nfunction getRawUserEntry(config: ResolvedConfig, name: string): RuleOptions | undefined {\n return config.rawUserRules.get(name);\n}\n\nfunction matchesType(val: RuleOptions[string], type: FieldSchema['type']): boolean {\n if (type === 'string') return typeof val === 'string';\n if (type === 'number') return typeof val === 'number';\n if (type === 'boolean') return typeof val === 'boolean';\n if (type === 'string[]') return Array.isArray(val) && val.every((v: RuleOptions[string]) => typeof v === 'string');\n return false;\n}\n\nfunction schemaFieldList(schema: Record<string, FieldSchema>): string {\n const keys = Object.keys(schema);\n if (keys.length === 0) return '';\n return ', ' + keys.map((k: string) => `${k} (${schema[k].type})`).join(', ');\n}\n\nfunction buildExampleEntry(rule: Rule): string {\n const obj: RuleOptions = { mode: 'ON' };\n for (const [key, schema] of Object.entries(rule.configSchema)) {\n obj[key] = rule.defaultOptions[key] ?? defaultForType(schema.type);\n }\n return `\"${rule.name}\": ${JSON.stringify(obj)}`;\n}\n\nfunction defaultForType(type: FieldSchema['type']): string | number | boolean | string[] {\n if (type === 'string') return '';\n if (type === 'number') return 0;\n if (type === 'boolean') return false;\n if (type === 'string[]') return [];\n const exhaustive: never = type;\n throw new Error(`Unhandled FieldSchema type: ${exhaustive}`);\n}\n\n// N-legs pattern: each rule runs independently; crash → visible violation so AI sees it, not silent []\nfunction runRuleCheck(rule: Rule, ctx: EditContext | FileContext | BashContext): readonly Violation[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return (rule as EditRule | FileRule | BashRule).check(ctx as never);\n } catch (err: unknown) {\n const error = toError(err);\n return [new Violation(0, '', `Rule '${rule.name}' crashed: ${error.message}`)];\n }\n}\n\nfunction runBashRules(\n rules: readonly Rule[],\n bashContext: BashContext,\n config: ResolvedConfig,\n): readonly RuleGroup[] {\n const groups: RuleGroup[] = [];\n for (const rule of rules) {\n if (rule.scope !== 'bash') continue;\n const ruleConfig = config.rules.get(rule.name);\n if (!ruleConfig || ruleConfig.isOff) continue;\n bashContext.options = mergeOptions(rule.defaultOptions, ruleConfig);\n const vs = runRuleCheck(rule, bashContext);\n if (vs.length > 0) {\n groups.push(new RuleGroup(\n rule.name, rule.description, [...rule.fixHint], [...vs],\n ));\n }\n }\n return groups;\n}\n\nfunction ruleMatchesFile(rule: Rule, relativePath: string): boolean {\n for (const pattern of rule.files) {\n if (globMatches(pattern, relativePath)) return true;\n }\n return false;\n}\n\nfunction mergeOptions(defaultOptions: RuleOptions, ruleConfig: ResolvedRuleConfig): RuleOptions {\n // webpieces-disable no-any-unknown -- building an options bag from opaque RuleOptions\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(defaultOptions)) out[key] = defaultOptions[key];\n for (const key of Object.keys(ruleConfig.options)) {\n // 'mode' is the framework-level on/off switch, not a rule option.\n if (key === 'mode') continue;\n out[key] = ruleConfig.options[key];\n }\n return out;\n}\n\nfunction runEditRules(\n rules: readonly Rule[],\n editContexts: readonly EditContext[],\n config: ResolvedConfig,\n): readonly RuleGroup[] {\n const groups: RuleGroup[] = [];\n for (const rule of rules) {\n if (rule.scope !== 'edit') continue;\n const ruleConfig = config.rules.get(rule.name);\n if (!ruleConfig || ruleConfig.isOff) continue;\n const allViolations: Violation[] = [];\n for (const ctx of editContexts) {\n if (!ruleMatchesFile(rule, ctx.relativePath)) continue;\n ctx.options = mergeOptions(rule.defaultOptions, ruleConfig);\n const vs = runRuleCheck(rule, ctx);\n for (const v of vs) {\n const copy = new Violation(v.line, v.snippet, v.message);\n copy.editIndex = ctx.editIndex;\n copy.editCount = ctx.editCount;\n allViolations.push(copy);\n }\n }\n if (allViolations.length > 0) {\n groups.push(new RuleGroup(\n rule.name, rule.description, [...rule.fixHint], allViolations,\n ));\n }\n }\n return groups;\n}\n\nfunction runFileRules(\n rules: readonly Rule[],\n fileContext: FileContext,\n config: ResolvedConfig,\n): readonly RuleGroup[] {\n const groups: RuleGroup[] = [];\n for (const rule of rules) {\n if (rule.scope !== 'file') continue;\n const ruleConfig = config.rules.get(rule.name);\n if (!ruleConfig || ruleConfig.isOff) continue;\n if (!ruleMatchesFile(rule, fileContext.relativePath)) continue;\n fileContext.options = mergeOptions(rule.defaultOptions, ruleConfig);\n const vs = runRuleCheck(rule, fileContext);\n if (vs.length > 0) {\n groups.push(new RuleGroup(\n rule.name, rule.description, [...rule.fixHint], [...vs],\n ));\n }\n }\n return groups;\n}\n"]}
@@ -60,11 +60,18 @@ export declare class FileContext {
60
60
  options: RuleOptions;
61
61
  constructor(tool: ToolKind, filePath: string, relativePath: string, workspaceRoot: string, currentFileLines: number, linesAdded: number, linesRemoved: number, projectedFileLines: number);
62
62
  }
63
+ export type FieldType = 'string' | 'number' | 'boolean' | 'string[]';
64
+ export declare class FieldSchema {
65
+ readonly type: FieldType;
66
+ readonly description: string;
67
+ constructor(type: FieldType, description: string);
68
+ }
63
69
  interface RuleBase {
64
70
  readonly name: string;
65
71
  readonly description: string;
66
72
  readonly files: readonly string[];
67
73
  readonly defaultOptions: RuleOptions;
74
+ readonly configSchema: Record<string, FieldSchema>;
68
75
  readonly fixHint: readonly string[];
69
76
  }
70
77
  export interface EditRule extends RuleBase {
package/src/core/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BlockedResult = exports.RuleGroup = exports.FileContext = exports.BashContext = exports.EditContext = exports.NormalizedBashInput = exports.NormalizedToolInput = exports.NormalizedEdit = exports.Violation = exports.InformAiError = exports.ResolvedRuleConfig = exports.ResolvedConfig = void 0;
3
+ exports.BlockedResult = exports.RuleGroup = exports.FieldSchema = exports.FileContext = exports.BashContext = exports.EditContext = exports.NormalizedBashInput = exports.NormalizedToolInput = exports.NormalizedEdit = exports.Violation = exports.InformAiError = exports.ResolvedRuleConfig = exports.ResolvedConfig = void 0;
4
4
  var rules_config_1 = require("@webpieces/rules-config");
5
5
  Object.defineProperty(exports, "ResolvedConfig", { enumerable: true, get: function () { return rules_config_1.ResolvedConfig; } });
6
6
  Object.defineProperty(exports, "ResolvedRuleConfig", { enumerable: true, get: function () { return rules_config_1.ResolvedRuleConfig; } });
@@ -112,6 +112,15 @@ class FileContext {
112
112
  }
113
113
  }
114
114
  exports.FileContext = FileContext;
115
+ class FieldSchema {
116
+ type;
117
+ description;
118
+ constructor(type, description) {
119
+ this.type = type;
120
+ this.description = description;
121
+ }
122
+ }
123
+ exports.FieldSchema = FieldSchema;
115
124
  class RuleGroup {
116
125
  ruleName;
117
126
  ruleDescription;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/types.ts"],"names":[],"mappings":"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","sourcesContent":["// ResolvedConfig / ResolvedRuleConfig / RuleOptions now live in @webpieces/rules-config\n// so ai-hooks and the Nx validate-code executor share one loader and one config file.\nimport { RuleOptions } from '@webpieces/rules-config';\nexport { ResolvedConfig, ResolvedRuleConfig, RuleOptions, InformAiError } from '@webpieces/rules-config';\n\nexport type ToolKind = 'Write' | 'Edit' | 'MultiEdit';\nexport type RuleScope = 'edit' | 'file' | 'bash';\nexport type IsLineDisabled = (lineNum: number, ruleName: string) => boolean;\n\nexport class Violation {\n readonly line: number;\n readonly snippet: string;\n readonly message: string;\n editIndex: number | undefined;\n editCount: number | undefined;\n\n constructor(line: number, snippet: string, message: string) {\n this.line = line;\n this.snippet = snippet;\n this.message = message;\n this.editIndex = undefined;\n this.editCount = undefined;\n }\n}\n\nexport class NormalizedEdit {\n readonly oldString: string;\n readonly newString: string;\n\n constructor(oldString: string, newString: string) {\n this.oldString = oldString;\n this.newString = newString;\n }\n}\n\nexport class NormalizedToolInput {\n readonly filePath: string;\n readonly edits: readonly NormalizedEdit[];\n\n constructor(filePath: string, edits: readonly NormalizedEdit[]) {\n this.filePath = filePath;\n this.edits = edits;\n }\n}\n\nexport class NormalizedBashInput {\n readonly command: string;\n\n constructor(command: string) {\n this.command = command;\n }\n}\n\nexport class EditContext {\n readonly tool: ToolKind;\n readonly editIndex: number;\n readonly editCount: number;\n readonly filePath: string;\n readonly relativePath: string;\n readonly workspaceRoot: string;\n readonly addedContent: string;\n readonly strippedContent: string;\n readonly lines: readonly string[];\n readonly strippedLines: readonly string[];\n readonly removedContent: string;\n readonly isLineDisabled: IsLineDisabled;\n options: RuleOptions;\n\n constructor(\n tool: ToolKind,\n editIndex: number,\n editCount: number,\n filePath: string,\n relativePath: string,\n workspaceRoot: string,\n addedContent: string,\n strippedContent: string,\n lines: readonly string[],\n strippedLines: readonly string[],\n removedContent: string,\n isLineDisabled: IsLineDisabled,\n ) {\n this.tool = tool;\n this.editIndex = editIndex;\n this.editCount = editCount;\n this.filePath = filePath;\n this.relativePath = relativePath;\n this.workspaceRoot = workspaceRoot;\n this.addedContent = addedContent;\n this.strippedContent = strippedContent;\n this.lines = lines;\n this.strippedLines = strippedLines;\n this.removedContent = removedContent;\n this.isLineDisabled = isLineDisabled;\n this.options = {};\n }\n}\n\nexport class BashContext {\n readonly tool: 'Bash';\n readonly command: string;\n readonly workspaceRoot: string;\n options: RuleOptions;\n\n constructor(command: string, workspaceRoot: string) {\n this.tool = 'Bash';\n this.command = command;\n this.workspaceRoot = workspaceRoot;\n this.options = {};\n }\n}\n\nexport class FileContext {\n readonly tool: ToolKind;\n readonly filePath: string;\n readonly relativePath: string;\n readonly workspaceRoot: string;\n readonly currentFileLines: number;\n readonly linesAdded: number;\n readonly linesRemoved: number;\n readonly projectedFileLines: number;\n options: RuleOptions;\n\n constructor(\n tool: ToolKind,\n filePath: string,\n relativePath: string,\n workspaceRoot: string,\n currentFileLines: number,\n linesAdded: number,\n linesRemoved: number,\n projectedFileLines: number,\n ) {\n this.tool = tool;\n this.filePath = filePath;\n this.relativePath = relativePath;\n this.workspaceRoot = workspaceRoot;\n this.currentFileLines = currentFileLines;\n this.linesAdded = linesAdded;\n this.linesRemoved = linesRemoved;\n this.projectedFileLines = projectedFileLines;\n this.options = {};\n }\n}\n\ninterface RuleBase {\n readonly name: string;\n readonly description: string;\n readonly files: readonly string[];\n readonly defaultOptions: RuleOptions;\n readonly fixHint: readonly string[];\n}\n\nexport interface EditRule extends RuleBase {\n readonly scope: 'edit';\n check(ctx: EditContext): readonly Violation[];\n}\n\nexport interface FileRule extends RuleBase {\n readonly scope: 'file';\n check(ctx: FileContext): readonly Violation[];\n}\n\nexport interface BashRule extends RuleBase {\n readonly scope: 'bash';\n check(ctx: BashContext): readonly Violation[];\n}\n\nexport type Rule = EditRule | FileRule | BashRule;\n\nexport class RuleGroup {\n readonly ruleName: string;\n readonly ruleDescription: string;\n readonly fixHint: readonly string[];\n readonly violations: readonly Violation[];\n\n constructor(\n ruleName: string,\n ruleDescription: string,\n fixHint: readonly string[],\n violations: readonly Violation[],\n ) {\n this.ruleName = ruleName;\n this.ruleDescription = ruleDescription;\n this.fixHint = fixHint;\n this.violations = violations;\n }\n}\n\nexport class BlockedResult {\n readonly report: string;\n\n constructor(report: string) {\n this.report = report;\n }\n}\n\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/types.ts"],"names":[],"mappings":"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","sourcesContent":["// ResolvedConfig / ResolvedRuleConfig / RuleOptions now live in @webpieces/rules-config\n// so ai-hooks and the Nx validate-code executor share one loader and one config file.\nimport { RuleOptions } from '@webpieces/rules-config';\nexport { ResolvedConfig, ResolvedRuleConfig, RuleOptions, InformAiError } from '@webpieces/rules-config';\n\nexport type ToolKind = 'Write' | 'Edit' | 'MultiEdit';\nexport type RuleScope = 'edit' | 'file' | 'bash';\nexport type IsLineDisabled = (lineNum: number, ruleName: string) => boolean;\n\nexport class Violation {\n readonly line: number;\n readonly snippet: string;\n readonly message: string;\n editIndex: number | undefined;\n editCount: number | undefined;\n\n constructor(line: number, snippet: string, message: string) {\n this.line = line;\n this.snippet = snippet;\n this.message = message;\n this.editIndex = undefined;\n this.editCount = undefined;\n }\n}\n\nexport class NormalizedEdit {\n readonly oldString: string;\n readonly newString: string;\n\n constructor(oldString: string, newString: string) {\n this.oldString = oldString;\n this.newString = newString;\n }\n}\n\nexport class NormalizedToolInput {\n readonly filePath: string;\n readonly edits: readonly NormalizedEdit[];\n\n constructor(filePath: string, edits: readonly NormalizedEdit[]) {\n this.filePath = filePath;\n this.edits = edits;\n }\n}\n\nexport class NormalizedBashInput {\n readonly command: string;\n\n constructor(command: string) {\n this.command = command;\n }\n}\n\nexport class EditContext {\n readonly tool: ToolKind;\n readonly editIndex: number;\n readonly editCount: number;\n readonly filePath: string;\n readonly relativePath: string;\n readonly workspaceRoot: string;\n readonly addedContent: string;\n readonly strippedContent: string;\n readonly lines: readonly string[];\n readonly strippedLines: readonly string[];\n readonly removedContent: string;\n readonly isLineDisabled: IsLineDisabled;\n options: RuleOptions;\n\n constructor(\n tool: ToolKind,\n editIndex: number,\n editCount: number,\n filePath: string,\n relativePath: string,\n workspaceRoot: string,\n addedContent: string,\n strippedContent: string,\n lines: readonly string[],\n strippedLines: readonly string[],\n removedContent: string,\n isLineDisabled: IsLineDisabled,\n ) {\n this.tool = tool;\n this.editIndex = editIndex;\n this.editCount = editCount;\n this.filePath = filePath;\n this.relativePath = relativePath;\n this.workspaceRoot = workspaceRoot;\n this.addedContent = addedContent;\n this.strippedContent = strippedContent;\n this.lines = lines;\n this.strippedLines = strippedLines;\n this.removedContent = removedContent;\n this.isLineDisabled = isLineDisabled;\n this.options = {};\n }\n}\n\nexport class BashContext {\n readonly tool: 'Bash';\n readonly command: string;\n readonly workspaceRoot: string;\n options: RuleOptions;\n\n constructor(command: string, workspaceRoot: string) {\n this.tool = 'Bash';\n this.command = command;\n this.workspaceRoot = workspaceRoot;\n this.options = {};\n }\n}\n\nexport class FileContext {\n readonly tool: ToolKind;\n readonly filePath: string;\n readonly relativePath: string;\n readonly workspaceRoot: string;\n readonly currentFileLines: number;\n readonly linesAdded: number;\n readonly linesRemoved: number;\n readonly projectedFileLines: number;\n options: RuleOptions;\n\n constructor(\n tool: ToolKind,\n filePath: string,\n relativePath: string,\n workspaceRoot: string,\n currentFileLines: number,\n linesAdded: number,\n linesRemoved: number,\n projectedFileLines: number,\n ) {\n this.tool = tool;\n this.filePath = filePath;\n this.relativePath = relativePath;\n this.workspaceRoot = workspaceRoot;\n this.currentFileLines = currentFileLines;\n this.linesAdded = linesAdded;\n this.linesRemoved = linesRemoved;\n this.projectedFileLines = projectedFileLines;\n this.options = {};\n }\n}\n\nexport type FieldType = 'string' | 'number' | 'boolean' | 'string[]';\n\nexport class FieldSchema {\n readonly type: FieldType;\n readonly description: string;\n\n constructor(type: FieldType, description: string) {\n this.type = type;\n this.description = description;\n }\n}\n\ninterface RuleBase {\n readonly name: string;\n readonly description: string;\n readonly files: readonly string[];\n readonly defaultOptions: RuleOptions;\n readonly configSchema: Record<string, FieldSchema>;\n readonly fixHint: readonly string[];\n}\n\nexport interface EditRule extends RuleBase {\n readonly scope: 'edit';\n check(ctx: EditContext): readonly Violation[];\n}\n\nexport interface FileRule extends RuleBase {\n readonly scope: 'file';\n check(ctx: FileContext): readonly Violation[];\n}\n\nexport interface BashRule extends RuleBase {\n readonly scope: 'bash';\n check(ctx: BashContext): readonly Violation[];\n}\n\nexport type Rule = EditRule | FileRule | BashRule;\n\nexport class RuleGroup {\n readonly ruleName: string;\n readonly ruleDescription: string;\n readonly fixHint: readonly string[];\n readonly violations: readonly Violation[];\n\n constructor(\n ruleName: string,\n ruleDescription: string,\n fixHint: readonly string[],\n violations: readonly Violation[],\n ) {\n this.ruleName = ruleName;\n this.ruleDescription = ruleDescription;\n this.fixHint = fixHint;\n this.violations = violations;\n }\n}\n\nexport class BlockedResult {\n readonly report: string;\n\n constructor(report: string) {\n this.report = report;\n }\n}\n\n"]}