@webpieces/ai-hook-rules 0.0.1 → 0.2.113

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 (99) hide show
  1. package/package.json +4 -3
  2. package/src/adapters/claude-code-hook.d.ts +1 -0
  3. package/src/adapters/claude-code-hook.js +112 -0
  4. package/src/adapters/claude-code-hook.js.map +1 -0
  5. package/src/adapters/openclaw-plugin.d.ts +14 -0
  6. package/src/adapters/openclaw-plugin.js +73 -0
  7. package/src/adapters/openclaw-plugin.js.map +1 -0
  8. package/src/core/build-context.d.ts +8 -0
  9. package/src/core/build-context.js +62 -0
  10. package/src/core/build-context.js.map +1 -0
  11. package/src/core/configs/default.d.ts +2 -0
  12. package/src/core/configs/{default.ts → default.js} +6 -3
  13. package/src/core/configs/default.js.map +1 -0
  14. package/src/core/disable-directives.d.ts +9 -0
  15. package/src/core/disable-directives.js +92 -0
  16. package/src/core/disable-directives.js.map +1 -0
  17. package/src/core/instruct-ai-writer.d.ts +1 -0
  18. package/src/core/instruct-ai-writer.js +18 -0
  19. package/src/core/instruct-ai-writer.js.map +1 -0
  20. package/src/core/load-config.d.ts +1 -0
  21. package/src/core/load-config.js +10 -0
  22. package/src/core/load-config.js.map +1 -0
  23. package/src/core/load-rules.d.ts +3 -0
  24. package/src/core/{load-rules.ts → load-rules.js} +33 -32
  25. package/src/core/load-rules.js.map +1 -0
  26. package/src/core/rejection-log.d.ts +2 -0
  27. package/src/core/{rejection-log.ts → rejection-log.js} +34 -51
  28. package/src/core/rejection-log.js.map +1 -0
  29. package/src/core/report.d.ts +2 -0
  30. package/src/core/{report.ts → report.js} +7 -8
  31. package/src/core/report.js.map +1 -0
  32. package/src/core/rules/catch-error-pattern.d.ts +3 -0
  33. package/src/core/rules/{catch-error-pattern.ts → catch-error-pattern.js} +25 -54
  34. package/src/core/rules/catch-error-pattern.js.map +1 -0
  35. package/src/core/rules/file-location.d.ts +3 -0
  36. package/src/core/rules/{file-location.ts → file-location.js} +29 -43
  37. package/src/core/rules/file-location.js.map +1 -0
  38. package/src/core/rules/index.d.ts +1 -0
  39. package/src/core/rules/{index.ts → index.js} +5 -1
  40. package/src/core/rules/index.js.map +1 -0
  41. package/src/core/rules/max-file-lines.d.ts +3 -0
  42. package/src/core/rules/{max-file-lines.ts → max-file-lines.js} +17 -23
  43. package/src/core/rules/max-file-lines.js.map +1 -0
  44. package/src/core/rules/no-any-unknown.d.ts +3 -0
  45. package/src/core/rules/no-any-unknown.js +30 -0
  46. package/src/core/rules/no-any-unknown.js.map +1 -0
  47. package/src/core/rules/no-destructure.d.ts +3 -0
  48. package/src/core/rules/{no-destructure.ts → no-destructure.js} +13 -17
  49. package/src/core/rules/no-destructure.js.map +1 -0
  50. package/src/core/rules/no-implicit-any.d.ts +3 -0
  51. package/src/core/rules/{no-implicit-any.ts → no-implicit-any.js} +32 -30
  52. package/src/core/rules/no-implicit-any.js.map +1 -0
  53. package/src/core/rules/no-shell-substitution.d.ts +3 -0
  54. package/src/core/rules/no-shell-substitution.js +54 -0
  55. package/src/core/rules/no-shell-substitution.js.map +1 -0
  56. package/src/core/rules/no-unmanaged-exceptions.d.ts +3 -0
  57. package/src/core/rules/{no-unmanaged-exceptions.ts → no-unmanaged-exceptions.js} +21 -24
  58. package/src/core/rules/no-unmanaged-exceptions.js.map +1 -0
  59. package/src/core/rules/require-return-type.d.ts +3 -0
  60. package/src/core/rules/{require-return-type.ts → require-return-type.js} +21 -28
  61. package/src/core/rules/require-return-type.js.map +1 -0
  62. package/src/core/runner.d.ts +3 -0
  63. package/src/core/runner.js +181 -0
  64. package/src/core/runner.js.map +1 -0
  65. package/src/core/strip-ts-noise.d.ts +1 -0
  66. package/src/core/strip-ts-noise.js +178 -0
  67. package/src/core/strip-ts-noise.js.map +1 -0
  68. package/src/core/to-error.d.ts +5 -0
  69. package/src/core/{to-error.ts → to-error.js} +7 -4
  70. package/src/core/to-error.js.map +1 -0
  71. package/src/core/types.d.ts +93 -0
  72. package/src/core/types.js +93 -0
  73. package/src/core/types.js.map +1 -0
  74. package/src/index.d.ts +5 -0
  75. package/src/index.js +25 -0
  76. package/src/index.js.map +1 -0
  77. package/LICENSE +0 -373
  78. package/src/adapters/claude-code-hook.ts +0 -117
  79. package/src/adapters/openclaw-plugin.ts +0 -88
  80. package/src/core/__tests__/disable-directives.test.ts +0 -114
  81. package/src/core/__tests__/rules/file-location.test.ts +0 -90
  82. package/src/core/__tests__/rules/max-file-lines.test.ts +0 -53
  83. package/src/core/__tests__/rules/no-any.test.ts +0 -68
  84. package/src/core/__tests__/rules/no-destructure.test.ts +0 -50
  85. package/src/core/__tests__/rules/no-shell-substitution.test.ts +0 -118
  86. package/src/core/__tests__/rules/no-unmanaged-exceptions.test.ts +0 -54
  87. package/src/core/__tests__/rules/require-return-type.test.ts +0 -79
  88. package/src/core/__tests__/runner.test.ts +0 -288
  89. package/src/core/__tests__/strip-ts-noise.test.ts +0 -109
  90. package/src/core/build-context.ts +0 -96
  91. package/src/core/disable-directives.ts +0 -90
  92. package/src/core/instruct-ai-writer.ts +0 -15
  93. package/src/core/load-config.ts +0 -3
  94. package/src/core/rules/no-any-unknown.ts +0 -35
  95. package/src/core/rules/no-shell-substitution.ts +0 -71
  96. package/src/core/runner.ts +0 -205
  97. package/src/core/strip-ts-noise.ts +0 -103
  98. package/src/core/types.ts +0 -196
  99. package/src/index.ts +0 -14
@@ -1,49 +1,50 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
-
4
- import type { Rule, ResolvedConfig } from './types';
5
- import { toError } from './to-error';
6
-
7
- const REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadRules = loadRules;
4
+ exports.globMatches = globMatches;
5
+ const tslib_1 = require("tslib");
6
+ const fs = tslib_1.__importStar(require("fs"));
7
+ const path = tslib_1.__importStar(require("path"));
8
+ const to_error_1 = require("./to-error");
9
+ const REQUIRED_FIELDS = ['name', 'description', 'scope', 'files', 'check'];
8
10
  const VALID_SCOPES = new Set(['edit', 'file', 'bash']);
9
-
10
- export function loadRules(config: ResolvedConfig, workspaceRoot: string): readonly Rule[] {
11
+ function loadRules(config, workspaceRoot) {
11
12
  const builtIns = loadBuiltInRules();
12
13
  const custom = loadCustomRules(config.rulesDir, workspaceRoot);
13
14
  const all = [...builtIns, ...custom];
14
15
  return all.filter((rule) => validateRule(rule));
15
16
  }
16
-
17
- function loadBuiltInRules(): Rule[] {
18
- const registry: readonly string[] = require('./rules/index').builtInRuleNames;
19
- const modules: Rule[] = [];
17
+ function loadBuiltInRules() {
18
+ const registry = require('./rules/index').builtInRuleNames;
19
+ const modules = [];
20
20
  for (const name of registry) {
21
21
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
22
22
  try {
23
23
  const mod = require(`./rules/${name}`);
24
24
  modules.push(mod.default || mod);
25
- } catch (err: unknown) {
26
- const error = toError(err);
25
+ }
26
+ catch (err) {
27
+ const error = (0, to_error_1.toError)(err);
27
28
  process.stderr.write(`[ai-hooks] failed to load built-in rule ${name}: ${error.message}\n`);
28
29
  }
29
30
  }
30
31
  return modules;
31
32
  }
32
-
33
- function loadCustomRules(rulesDirs: readonly string[], workspaceRoot: string): Rule[] {
34
- const modules: Rule[] = [];
33
+ function loadCustomRules(rulesDirs, workspaceRoot) {
34
+ const modules = [];
35
35
  for (const dir of rulesDirs) {
36
36
  const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);
37
37
  if (!fs.existsSync(absDir)) {
38
38
  process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\n`);
39
39
  continue;
40
40
  }
41
- let entries: string[];
41
+ let entries;
42
42
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
43
43
  try {
44
44
  entries = fs.readdirSync(absDir).filter((e) => e.endsWith('.js'));
45
- } catch (err: unknown) {
46
- const error = toError(err);
45
+ }
46
+ catch (err) {
47
+ const error = (0, to_error_1.toError)(err);
47
48
  process.stderr.write(`[ai-hooks] cannot read rulesDir ${absDir}: ${error.message}\n`);
48
49
  continue;
49
50
  }
@@ -53,23 +54,23 @@ function loadCustomRules(rulesDirs: readonly string[], workspaceRoot: string): R
53
54
  try {
54
55
  const mod = require(full);
55
56
  modules.push(mod.default || mod);
56
- } catch (err: unknown) {
57
- const error = toError(err);
57
+ }
58
+ catch (err) {
59
+ const error = (0, to_error_1.toError)(err);
58
60
  process.stderr.write(`[ai-hooks] failed to load custom rule ${full}: ${error.message}\n`);
59
61
  }
60
62
  }
61
63
  }
62
64
  return modules;
63
65
  }
64
-
65
66
  // webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary
66
- function validateRule(rule: unknown): rule is Rule {
67
+ function validateRule(rule) {
67
68
  if (!rule || typeof rule !== 'object') {
68
69
  process.stderr.write('[ai-hooks] rule is not an object, skipping\n');
69
70
  return false;
70
71
  }
71
72
  // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary
72
- const obj = rule as Record<string, unknown>;
73
+ const obj = rule;
73
74
  for (const field of REQUIRED_FIELDS) {
74
75
  if (obj[field] === undefined) {
75
76
  const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';
@@ -77,7 +78,7 @@ function validateRule(rule: unknown): rule is Rule {
77
78
  return false;
78
79
  }
79
80
  }
80
- if (!VALID_SCOPES.has(obj['scope'] as string)) {
81
+ if (!VALID_SCOPES.has(obj['scope'])) {
81
82
  process.stderr.write(`[ai-hooks] rule "${obj['name']}" has invalid scope: ${String(obj['scope'])}\n`);
82
83
  return false;
83
84
  }
@@ -91,13 +92,11 @@ function validateRule(rule: unknown): rule is Rule {
91
92
  }
92
93
  return true;
93
94
  }
94
-
95
- export function globMatches(pattern: string, filePath: string): boolean {
95
+ function globMatches(pattern, filePath) {
96
96
  const regex = globToRegex(pattern);
97
97
  return regex.test(filePath);
98
98
  }
99
-
100
- function globToRegex(pattern: string): RegExp {
99
+ function globToRegex(pattern) {
101
100
  let re = '';
102
101
  let i = 0;
103
102
  while (i < pattern.length) {
@@ -106,7 +105,8 @@ function globToRegex(pattern: string): RegExp {
106
105
  if (pattern[i + 1] === '*') {
107
106
  re += '.*';
108
107
  i += 2;
109
- if (pattern[i] === '/') i += 1;
108
+ if (pattern[i] === '/')
109
+ i += 1;
110
110
  continue;
111
111
  }
112
112
  re += '[^/]*';
@@ -128,3 +128,4 @@ function globToRegex(pattern: string): RegExp {
128
128
  }
129
129
  return new RegExp('^' + re + '$');
130
130
  }
131
+ //# sourceMappingURL=load-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-rules.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport type { Rule, ResolvedConfig } from './types';\nimport { toError } from './to-error';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\nexport function loadRules(config: ResolvedConfig, workspaceRoot: string): readonly Rule[] {\n const builtIns = loadBuiltInRules();\n const custom = loadCustomRules(config.rulesDir, workspaceRoot);\n const all = [...builtIns, ...custom];\n return all.filter((rule) => validateRule(rule));\n}\n\nfunction loadBuiltInRules(): Rule[] {\n const registry: readonly string[] = require('./rules/index').builtInRuleNames;\n const modules: Rule[] = [];\n for (const name of registry) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(`./rules/${name}`);\n modules.push(mod.default || mod);\n } catch (err: unknown) {\n const error = toError(err);\n process.stderr.write(`[ai-hooks] failed to load built-in rule ${name}: ${error.message}\\n`);\n }\n }\n return modules;\n}\n\nfunction loadCustomRules(rulesDirs: readonly string[], workspaceRoot: string): Rule[] {\n const modules: Rule[] = [];\n for (const dir of rulesDirs) {\n const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);\n if (!fs.existsSync(absDir)) {\n process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\\n`);\n continue;\n }\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(absDir).filter((e) => e.endsWith('.js'));\n } catch (err: unknown) {\n const error = toError(err);\n process.stderr.write(`[ai-hooks] cannot read rulesDir ${absDir}: ${error.message}\\n`);\n continue;\n }\n for (const entry of entries) {\n const full = path.join(absDir, entry);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(full);\n modules.push(mod.default || mod);\n } catch (err: unknown) {\n const error = toError(err);\n process.stderr.write(`[ai-hooks] failed to load custom rule ${full}: ${error.message}\\n`);\n }\n }\n }\n return modules;\n}\n\n// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary\nfunction validateRule(rule: unknown): rule is Rule {\n if (!rule || typeof rule !== 'object') {\n process.stderr.write('[ai-hooks] rule is not an object, skipping\\n');\n return false;\n }\n // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary\n const obj = rule as Record<string, unknown>;\n for (const field of REQUIRED_FIELDS) {\n if (obj[field] === undefined) {\n const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';\n process.stderr.write(`[ai-hooks] rule \"${name}\" missing required field: ${field}\\n`);\n return false;\n }\n }\n if (!VALID_SCOPES.has(obj['scope'] as string)) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" has invalid scope: ${String(obj['scope'])}\\n`);\n return false;\n }\n if (!Array.isArray(obj['files'])) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" files must be an array\\n`);\n return false;\n }\n if (typeof obj['check'] !== 'function') {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" check must be a function\\n`);\n return false;\n }\n return true;\n}\n\nexport function globMatches(pattern: string, filePath: string): boolean {\n const regex = globToRegex(pattern);\n return regex.test(filePath);\n}\n\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { ToolKind, NormalizedToolInput, BlockedResult } from './types';
2
+ export declare function logRejection(toolKind: ToolKind, input: NormalizedToolInput, result: BlockedResult, cwd: string): void;
@@ -1,65 +1,53 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
-
4
- import type { ToolKind, NormalizedToolInput, BlockedResult } from './types';
5
-
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logRejection = logRejection;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
6
7
  const HOOKS_DIR = '.webpieces/hooks';
7
8
  const LOG_FILE = 'hook-rejection.log';
8
9
  const LOG_FILE_PREV = 'hook-rejection.1.log';
9
10
  const MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded
10
11
  const MAX_AGE_DAYS = 7;
11
-
12
12
  const RULE_NAME_RE = /^\[([^\]]+)\] \(/gm;
13
-
14
- export function logRejection(
15
- toolKind: ToolKind,
16
- input: NormalizedToolInput,
17
- result: BlockedResult,
18
- cwd: string,
19
- ): void {
13
+ function logRejection(toolKind, input, result, cwd) {
20
14
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
21
15
  try {
22
16
  const now = new Date();
23
17
  const timestamp = now.toISOString();
24
18
  const epochMs = String(now.getTime());
25
19
  const dateStr = timestamp.slice(0, 10);
26
-
27
20
  const hooksDir = path.join(cwd, HOOKS_DIR);
28
21
  const dayDir = path.join(hooksDir, dateStr);
29
22
  fs.mkdirSync(dayDir, { recursive: true });
30
-
31
23
  const relativePath = computeRelativePath(input.filePath, cwd);
32
24
  const ruleNames = extractRuleNames(result.report);
33
25
  const detailFileName = `writeInfo-${epochMs}.md`;
34
26
  const detailRelPath = `${dateStr}/${detailFileName}`;
35
-
36
27
  const detail = buildDetailContent(timestamp, toolKind, relativePath, ruleNames, result.report, input);
37
28
  fs.writeFileSync(path.join(dayDir, detailFileName), detail);
38
-
39
29
  const logPath = path.join(hooksDir, LOG_FILE);
40
30
  rotateLogFile(logPath, path.join(hooksDir, LOG_FILE_PREV));
41
-
42
31
  const logLine = `[${timestamp}]\t${toolKind}\t${relativePath}\t[${ruleNames.join(',')}]\t${detailRelPath}\n`;
43
32
  fs.appendFileSync(logPath, logLine);
44
-
45
33
  rotateOldDays(hooksDir, MAX_AGE_DAYS);
46
- } catch (err: unknown) {
34
+ }
35
+ catch (err) {
47
36
  //const error = toError(err);
48
37
  void err;
49
38
  }
50
39
  }
51
-
52
- function computeRelativePath(filePath: string, cwd: string): string {
40
+ function computeRelativePath(filePath, cwd) {
53
41
  if (filePath.startsWith(cwd)) {
54
42
  const rel = filePath.slice(cwd.length);
55
- if (rel.startsWith('/')) return rel.slice(1);
43
+ if (rel.startsWith('/'))
44
+ return rel.slice(1);
56
45
  return rel;
57
46
  }
58
47
  return filePath;
59
48
  }
60
-
61
- function extractRuleNames(report: string): string[] {
62
- const names: string[] = [];
49
+ function extractRuleNames(report) {
50
+ const names = [];
63
51
  let match = RULE_NAME_RE.exec(report);
64
52
  while (match !== null) {
65
53
  names.push(match[1]);
@@ -68,16 +56,8 @@ function extractRuleNames(report: string): string[] {
68
56
  RULE_NAME_RE.lastIndex = 0;
69
57
  return names;
70
58
  }
71
-
72
- function buildDetailContent(
73
- timestamp: string,
74
- toolKind: ToolKind,
75
- relativePath: string,
76
- ruleNames: string[],
77
- report: string,
78
- input: NormalizedToolInput,
79
- ): string {
80
- const lines: string[] = [];
59
+ function buildDetailContent(timestamp, toolKind, relativePath, ruleNames, report, input) {
60
+ const lines = [];
81
61
  lines.push('# Hook Rejection Detail');
82
62
  lines.push('');
83
63
  lines.push(`- **Timestamp:** ${timestamp}`);
@@ -93,13 +73,13 @@ function buildDetailContent(
93
73
  lines.push('');
94
74
  lines.push('## Content Being Written');
95
75
  lines.push('');
96
-
97
76
  if (toolKind === 'Write') {
98
77
  const content = input.edits.length > 0 ? input.edits[0].newString : '';
99
78
  lines.push('```typescript');
100
79
  lines.push(content.trimEnd());
101
80
  lines.push('```');
102
- } else {
81
+ }
82
+ else {
103
83
  for (let i = 0; i < input.edits.length; i += 1) {
104
84
  const edit = input.edits[i];
105
85
  lines.push(`### Edit ${String(i + 1)} of ${String(input.edits.length)}`);
@@ -116,48 +96,51 @@ function buildDetailContent(
116
96
  lines.push('');
117
97
  }
118
98
  }
119
-
120
99
  return lines.join('\n') + '\n';
121
100
  }
122
-
123
- function rotateLogFile(logPath: string, prevPath: string): void {
101
+ function rotateLogFile(logPath, prevPath) {
124
102
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
125
103
  try {
126
104
  const stat = fs.statSync(logPath);
127
105
  if (stat.size > MAX_LOG_BYTES) {
128
- if (fs.existsSync(prevPath)) fs.unlinkSync(prevPath);
106
+ if (fs.existsSync(prevPath))
107
+ fs.unlinkSync(prevPath);
129
108
  fs.renameSync(logPath, prevPath);
130
109
  }
131
- } catch (err: unknown) {
110
+ }
111
+ catch (err) {
132
112
  //const error = toError(err);
133
113
  void err;
134
114
  }
135
115
  }
136
-
137
- function rotateOldDays(hooksDir: string, maxAgeDays: number): void {
116
+ function rotateOldDays(hooksDir, maxAgeDays) {
138
117
  const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
139
- let entries: string[];
118
+ let entries;
140
119
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
141
120
  try {
142
121
  entries = fs.readdirSync(hooksDir);
143
- } catch (err: unknown) {
122
+ }
123
+ catch (err) {
144
124
  //const error = toError(err);
145
125
  void err;
146
126
  return;
147
127
  }
148
-
149
128
  for (const entry of entries) {
150
- if (!/^\d{4}-\d{2}-\d{2}$/.test(entry)) continue;
129
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(entry))
130
+ continue;
151
131
  const dirDate = new Date(entry + 'T00:00:00Z');
152
- if (isNaN(dirDate.getTime())) continue;
132
+ if (isNaN(dirDate.getTime()))
133
+ continue;
153
134
  if (dirDate.getTime() < cutoff) {
154
135
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
155
136
  try {
156
137
  fs.rmSync(path.join(hooksDir, entry), { recursive: true, force: true });
157
- } catch (err: unknown) {
138
+ }
139
+ catch (err) {
158
140
  //const error = toError(err);
159
141
  void err;
160
142
  }
161
143
  }
162
144
  }
163
145
  }
146
+ //# sourceMappingURL=rejection-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rejection-log.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/rejection-log.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport type { ToolKind, NormalizedToolInput, BlockedResult } from './types';\n\nconst HOOKS_DIR = '.webpieces/hooks';\nconst LOG_FILE = 'hook-rejection.log';\nconst LOG_FILE_PREV = 'hook-rejection.1.log';\nconst MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded\nconst MAX_AGE_DAYS = 7;\n\nconst RULE_NAME_RE = /^\\[([^\\]]+)\\] \\(/gm;\n\nexport function logRejection(\n toolKind: ToolKind,\n input: NormalizedToolInput,\n result: BlockedResult,\n cwd: string,\n): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const now = new Date();\n const timestamp = now.toISOString();\n const epochMs = String(now.getTime());\n const dateStr = timestamp.slice(0, 10);\n\n const hooksDir = path.join(cwd, HOOKS_DIR);\n const dayDir = path.join(hooksDir, dateStr);\n fs.mkdirSync(dayDir, { recursive: true });\n\n const relativePath = computeRelativePath(input.filePath, cwd);\n const ruleNames = extractRuleNames(result.report);\n const detailFileName = `writeInfo-${epochMs}.md`;\n const detailRelPath = `${dateStr}/${detailFileName}`;\n\n const detail = buildDetailContent(timestamp, toolKind, relativePath, ruleNames, result.report, input);\n fs.writeFileSync(path.join(dayDir, detailFileName), detail);\n\n const logPath = path.join(hooksDir, LOG_FILE);\n rotateLogFile(logPath, path.join(hooksDir, LOG_FILE_PREV));\n\n const logLine = `[${timestamp}]\\t${toolKind}\\t${relativePath}\\t[${ruleNames.join(',')}]\\t${detailRelPath}\\n`;\n fs.appendFileSync(logPath, logLine);\n\n rotateOldDays(hooksDir, MAX_AGE_DAYS);\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n}\n\nfunction computeRelativePath(filePath: string, cwd: string): string {\n if (filePath.startsWith(cwd)) {\n const rel = filePath.slice(cwd.length);\n if (rel.startsWith('/')) return rel.slice(1);\n return rel;\n }\n return filePath;\n}\n\nfunction extractRuleNames(report: string): string[] {\n const names: string[] = [];\n let match = RULE_NAME_RE.exec(report);\n while (match !== null) {\n names.push(match[1]);\n match = RULE_NAME_RE.exec(report);\n }\n RULE_NAME_RE.lastIndex = 0;\n return names;\n}\n\nfunction buildDetailContent(\n timestamp: string,\n toolKind: ToolKind,\n relativePath: string,\n ruleNames: string[],\n report: string,\n input: NormalizedToolInput,\n): string {\n const lines: string[] = [];\n lines.push('# Hook Rejection Detail');\n lines.push('');\n lines.push(`- **Timestamp:** ${timestamp}`);\n lines.push(`- **Tool:** ${toolKind}`);\n lines.push(`- **File:** ${relativePath}`);\n lines.push(`- **Rules violated:** ${ruleNames.join(', ')}`);\n lines.push('');\n lines.push('## Report');\n lines.push('');\n lines.push('```');\n lines.push(report.trimEnd());\n lines.push('```');\n lines.push('');\n lines.push('## Content Being Written');\n lines.push('');\n\n if (toolKind === 'Write') {\n const content = input.edits.length > 0 ? input.edits[0].newString : '';\n lines.push('```typescript');\n lines.push(content.trimEnd());\n lines.push('```');\n } else {\n for (let i = 0; i < input.edits.length; i += 1) {\n const edit = input.edits[i];\n lines.push(`### Edit ${String(i + 1)} of ${String(input.edits.length)}`);\n lines.push('');\n lines.push('**old_string:**');\n lines.push('```typescript');\n lines.push(edit.oldString.trimEnd());\n lines.push('```');\n lines.push('');\n lines.push('**new_string:**');\n lines.push('```typescript');\n lines.push(edit.newString.trimEnd());\n lines.push('```');\n lines.push('');\n }\n }\n\n return lines.join('\\n') + '\\n';\n}\n\nfunction rotateLogFile(logPath: string, prevPath: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const stat = fs.statSync(logPath);\n if (stat.size > MAX_LOG_BYTES) {\n if (fs.existsSync(prevPath)) fs.unlinkSync(prevPath);\n fs.renameSync(logPath, prevPath);\n }\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n}\n\nfunction rotateOldDays(hooksDir: string, maxAgeDays: number): void {\n const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(hooksDir);\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n return;\n }\n\n for (const entry of entries) {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(entry)) continue;\n const dirDate = new Date(entry + 'T00:00:00Z');\n if (isNaN(dirDate.getTime())) continue;\n if (dirDate.getTime() < cutoff) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.rmSync(path.join(hooksDir, entry), { recursive: true, force: true });\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n }\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { RuleGroup } from './types';
2
+ export declare function formatReport(relativePath: string, ruleGroups: readonly RuleGroup[]): string;
@@ -1,10 +1,10 @@
1
- import type { RuleGroup, Violation } from './types';
2
-
3
- export function formatReport(relativePath: string, ruleGroups: readonly RuleGroup[]): string {
4
- const lines: string[] = [];
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatReport = formatReport;
4
+ function formatReport(relativePath, ruleGroups) {
5
+ const lines = [];
5
6
  lines.push(`\u274c webpieces ai-hooks blocked this write: ${relativePath}`);
6
7
  lines.push('');
7
-
8
8
  for (const group of ruleGroups) {
9
9
  const count = group.violations.length;
10
10
  const label = count === 1 ? '1 violation' : `${count} violations`;
@@ -21,15 +21,14 @@ export function formatReport(relativePath: string, ruleGroups: readonly RuleGrou
21
21
  }
22
22
  lines.push('');
23
23
  }
24
-
25
24
  lines.push('This is a pre-write check. Fix and retry the Write/Edit.');
26
25
  lines.push('');
27
26
  return lines.join('\n');
28
27
  }
29
-
30
- function formatEditPrefix(v: Violation): string {
28
+ function formatEditPrefix(v) {
31
29
  if (v.editIndex !== undefined && v.editCount !== undefined && v.editCount > 1) {
32
30
  return `edit ${String(v.editIndex + 1)}/${String(v.editCount)} `;
33
31
  }
34
32
  return '';
35
33
  }
34
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/report.ts"],"names":[],"mappings":";;AAEA,oCAyBC;AAzBD,SAAgB,YAAY,CAAC,YAAoB,EAAE,UAAgC;IAC/E,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,iDAAiD,YAAY,EAAE,CAAC,CAAC;IAC5E,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;QACtC,MAAM,KAAK,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,KAAK,aAAa,CAAC;QAClE,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,QAAQ,MAAM,KAAK,GAAG,CAAC,CAAC;QAC7C,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YAC/B,MAAM,UAAU,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;YACvC,KAAK,CAAC,IAAI,CAAC,KAAK,UAAU,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAC/D,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1C,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC/C,KAAK,CAAC,IAAI,CAAC,gBAAgB,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACrE,CAAC;QACL,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;IACvE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,gBAAgB,CAAC,CAAY;IAClC,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;QAC5E,OAAO,QAAQ,MAAM,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC;IACrE,CAAC;IACD,OAAO,EAAE,CAAC;AACd,CAAC","sourcesContent":["import type { RuleGroup, Violation } from './types';\n\nexport function formatReport(relativePath: string, ruleGroups: readonly RuleGroup[]): string {\n const lines: string[] = [];\n lines.push(`\\u274c webpieces ai-hooks blocked this write: ${relativePath}`);\n lines.push('');\n\n for (const group of ruleGroups) {\n const count = group.violations.length;\n const label = count === 1 ? '1 violation' : `${count} violations`;\n lines.push(`[${group.ruleName}] (${label})`);\n for (const v of group.violations) {\n const editPrefix = formatEditPrefix(v);\n lines.push(` ${editPrefix}L${String(v.line)}: ${v.snippet}`);\n lines.push(` \\u2192 ${v.message}`);\n }\n if (group.fixHint.length > 0) {\n for (let i = 0; i < group.fixHint.length; i += 1) {\n lines.push(` Fix Option ${String(i + 1)}: ${group.fixHint[i]}`);\n }\n }\n lines.push('');\n }\n\n lines.push('This is a pre-write check. Fix and retry the Write/Edit.');\n lines.push('');\n return lines.join('\\n');\n}\n\nfunction formatEditPrefix(v: Violation): string {\n if (v.editIndex !== undefined && v.editCount !== undefined && v.editCount > 1) {\n return `edit ${String(v.editIndex + 1)}/${String(v.editCount)} `;\n }\n return '';\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { EditRule } from '../types';
2
+ declare const catchErrorPatternRule: EditRule;
3
+ export default catchErrorPatternRule;
@@ -1,20 +1,18 @@
1
- import type { EditRule, EditContext, Violation } from '../types';
2
- import { Violation as V } from '../types';
3
- import { writeTemplateIfMissing } from '../instruct-ai-writer';
4
-
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const types_1 = require("../types");
4
+ const instruct_ai_writer_1 = require("../instruct-ai-writer");
5
5
  /**
6
6
  * Matches a catch clause opening: } catch (paramName: typeAnnotation) {
7
7
  * Captures: group 1 = param name, group 2 = type annotation (if present)
8
8
  */
9
9
  const CATCH_PATTERN = /\bcatch\s*\(\s*(\w+)(?:\s*:\s*(\w+))?\s*\)/;
10
-
11
10
  /**
12
11
  * Matches the required toError first statement (with or without comment-out).
13
12
  * Group 1 = variable name, group 2 = param passed to toError
14
13
  */
15
14
  const TO_ERROR_PATTERN = /^\s*(?:\/\/\s*)?const\s+(\w+)\s*=\s*toError\(\s*(\w+)\s*\)\s*;?\s*$/;
16
-
17
- const catchErrorPatternRule: EditRule = {
15
+ const catchErrorPatternRule = {
18
16
  name: 'catch-error-pattern',
19
17
  description: 'Catch blocks must use: catch (err: unknown) { const error = toError(err); }',
20
18
  scope: 'edit',
@@ -27,89 +25,62 @@ const catchErrorPatternRule: EditRule = {
27
25
  'For nested catches: catch (err2: unknown) { const error2 = toError(err2); }',
28
26
  '// webpieces-disable catch-error-pattern -- <reason>',
29
27
  ],
30
-
31
- check(ctx: EditContext): readonly Violation[] {
32
- const violations: V[] = [];
28
+ check(ctx) {
29
+ const violations = [];
33
30
  const lines = ctx.strippedLines;
34
-
35
31
  for (let i = 0; i < lines.length; i += 1) {
36
32
  const stripped = lines[i];
37
33
  const catchMatch = CATCH_PATTERN.exec(stripped);
38
- if (!catchMatch) continue;
39
-
34
+ if (!catchMatch)
35
+ continue;
40
36
  const lineNum = i + 1;
41
- if (ctx.isLineDisabled(lineNum, 'catch-error-pattern')) continue;
42
-
37
+ if (ctx.isLineDisabled(lineNum, 'catch-error-pattern'))
38
+ continue;
43
39
  const actualParam = catchMatch[1];
44
40
  const typeAnnotation = catchMatch[2];
45
-
46
41
  // Determine expected names from suffix on the actual param (err, err2, err3...)
47
42
  const suffixMatch = actualParam.match(/^err(\d*)$/);
48
43
  const suffix = suffixMatch ? suffixMatch[1] : '';
49
44
  const expectedParam = 'err' + suffix;
50
45
  const expectedVar = 'error' + suffix;
51
-
52
46
  // Check parameter name
53
47
  if (actualParam !== expectedParam) {
54
- violations.push(new V(
55
- lineNum,
56
- ctx.lines[i].trim(),
57
- `Catch parameter must be named "${expectedParam}" (or "err2", "err3" for nested catches), got "${actualParam}"`,
58
- ));
48
+ violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim(), `Catch parameter must be named "${expectedParam}" (or "err2", "err3" for nested catches), got "${actualParam}"`));
59
49
  }
60
-
61
50
  // Check type annotation is unknown
62
51
  if (typeAnnotation !== 'unknown') {
63
52
  const msg = typeAnnotation
64
53
  ? `Catch parameter must be typed as "unknown": catch (${expectedParam}: unknown), got "${typeAnnotation}"`
65
54
  : `Catch parameter must be typed as "unknown": catch (${expectedParam}: unknown)`;
66
- violations.push(new V(lineNum, ctx.lines[i].trim(), msg));
55
+ violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim(), msg));
67
56
  }
68
-
69
57
  // Find next non-blank line after the catch opening to check for toError
70
58
  const toErrorResult = findToErrorStatement(lines, i + 1);
71
59
  if (toErrorResult === 'not-found') {
72
- violations.push(new V(
73
- lineNum,
74
- ctx.lines[i].trim(),
75
- `Catch block must call toError(${actualParam}) as first statement: const ${expectedVar} = toError(${actualParam}); or //const ${expectedVar} = toError(${actualParam});`,
76
- ));
77
- } else if (toErrorResult !== 'end-of-content') {
60
+ violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim(), `Catch block must call toError(${actualParam}) as first statement: const ${expectedVar} = toError(${actualParam}); or //const ${expectedVar} = toError(${actualParam});`));
61
+ }
62
+ else if (toErrorResult !== 'end-of-content') {
78
63
  // Validate variable name and param match
79
64
  if (toErrorResult.varName !== expectedVar) {
80
65
  const toErrorLineNum = toErrorResult.lineIndex + 1;
81
- violations.push(new V(
82
- toErrorLineNum,
83
- ctx.lines[toErrorResult.lineIndex].trim(),
84
- `Error variable must be named "${expectedVar}", got "${toErrorResult.varName}"`,
85
- ));
66
+ violations.push(new types_1.Violation(toErrorLineNum, ctx.lines[toErrorResult.lineIndex].trim(), `Error variable must be named "${expectedVar}", got "${toErrorResult.varName}"`));
86
67
  }
87
68
  if (toErrorResult.paramName !== actualParam) {
88
69
  const toErrorLineNum = toErrorResult.lineIndex + 1;
89
- violations.push(new V(
90
- toErrorLineNum,
91
- ctx.lines[toErrorResult.lineIndex].trim(),
92
- `toError() must be called with "${actualParam}", got "${toErrorResult.paramName}"`,
93
- ));
70
+ violations.push(new types_1.Violation(toErrorLineNum, ctx.lines[toErrorResult.lineIndex].trim(), `toError() must be called with "${actualParam}", got "${toErrorResult.paramName}"`));
94
71
  }
95
72
  }
96
73
  }
97
- if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');
74
+ if (violations.length > 0)
75
+ (0, instruct_ai_writer_1.writeTemplateIfMissing)(ctx.workspaceRoot, 'webpieces.exceptions.md');
98
76
  return violations;
99
77
  },
100
78
  };
101
-
102
- interface ToErrorMatch {
103
- varName: string;
104
- paramName: string;
105
- lineIndex: number;
106
- }
107
-
108
- function findToErrorStatement(lines: readonly string[], startIndex: number): ToErrorMatch | 'not-found' | 'end-of-content' {
79
+ function findToErrorStatement(lines, startIndex) {
109
80
  for (let j = startIndex; j < lines.length; j += 1) {
110
81
  const line = lines[j].trim();
111
- if (line === '' || line === '{') continue;
112
-
82
+ if (line === '' || line === '{')
83
+ continue;
113
84
  const match = TO_ERROR_PATTERN.exec(line);
114
85
  if (match) {
115
86
  return { varName: match[1], paramName: match[2], lineIndex: j };
@@ -120,5 +91,5 @@ function findToErrorStatement(lines: readonly string[], startIndex: number): ToE
120
91
  // Ran off the end of the edit content — can't validate further
121
92
  return 'end-of-content';
122
93
  }
123
-
124
- export default catchErrorPatternRule;
94
+ exports.default = catchErrorPatternRule;
95
+ //# sourceMappingURL=catch-error-pattern.js.map
@@ -0,0 +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":";;AACA,oCAA0C;AAC1C,8DAA+D;AAE/D;;;GAGG;AACH,MAAM,aAAa,GAAG,4CAA4C,CAAC;AAEnE;;;GAGG;AACH,MAAM,gBAAgB,GAAG,qEAAqE,CAAC;AAE/F,MAAM,qBAAqB,GAAa;IACpC,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EAAE,6EAA6E;IAC1F,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE;IAClB,OAAO,EAAE;QACL,4GAA4G;QAC5G,0DAA0D;QAC1D,iFAAiF;QACjF,6EAA6E;QAC7E,sDAAsD;KACzD;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,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAChD,IAAI,CAAC,UAAU;gBAAE,SAAS;YAE1B,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,qBAAqB,CAAC;gBAAE,SAAS;YAEjE,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAClC,MAAM,cAAc,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAErC,gFAAgF;YAChF,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YACpD,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,CAAC;YACrC,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;YAErC,uBAAuB;YACvB,IAAI,WAAW,KAAK,aAAa,EAAE,CAAC;gBAChC,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,kCAAkC,aAAa,kDAAkD,WAAW,GAAG,CAClH,CAAC,CAAC;YACP,CAAC;YAED,mCAAmC;YACnC,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;gBAC/B,MAAM,GAAG,GAAG,cAAc;oBACtB,CAAC,CAAC,sDAAsD,aAAa,oBAAoB,cAAc,GAAG;oBAC1G,CAAC,CAAC,sDAAsD,aAAa,YAAY,CAAC;gBACtF,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;YAC9D,CAAC;YAED,wEAAwE;YACxE,MAAM,aAAa,GAAG,oBAAoB,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YACzD,IAAI,aAAa,KAAK,WAAW,EAAE,CAAC;gBAChC,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,iCAAiC,WAAW,+BAA+B,WAAW,cAAc,WAAW,iBAAiB,WAAW,cAAc,WAAW,IAAI,CAC3K,CAAC,CAAC;YACP,CAAC;iBAAM,IAAI,aAAa,KAAK,gBAAgB,EAAE,CAAC;gBAC5C,yCAAyC;gBACzC,IAAI,aAAa,CAAC,OAAO,KAAK,WAAW,EAAE,CAAC;oBACxC,MAAM,cAAc,GAAG,aAAa,CAAC,SAAS,GAAG,CAAC,CAAC;oBACnD,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,cAAc,EACd,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EACzC,iCAAiC,WAAW,WAAW,aAAa,CAAC,OAAO,GAAG,CAClF,CAAC,CAAC;gBACP,CAAC;gBACD,IAAI,aAAa,CAAC,SAAS,KAAK,WAAW,EAAE,CAAC;oBAC1C,MAAM,cAAc,GAAG,aAAa,CAAC,SAAS,GAAG,CAAC,CAAC;oBACnD,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,cAAc,EACd,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EACzC,kCAAkC,WAAW,WAAW,aAAa,CAAC,SAAS,GAAG,CACrF,CAAC,CAAC;gBACP,CAAC;YACL,CAAC;QACL,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;AAQF,SAAS,oBAAoB,CAAC,KAAwB,EAAE,UAAkB;IACtE,KAAK,IAAI,CAAC,GAAG,UAAU,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,GAAG;YAAE,SAAS;QAE1C,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,KAAK,EAAE,CAAC;YACR,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;QACpE,CAAC;QACD,6CAA6C;QAC7C,OAAO,WAAW,CAAC;IACvB,CAAC;IACD,+DAA+D;IAC/D,OAAO,gBAAgB,CAAC;AAC5B,CAAC;AAED,kBAAe,qBAAqB,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 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 'VERY IMPORTANT: READ .webpieces/instruct-ai/webpieces.exceptions.md to understand why and how to fix this!',\n 'catch (err: unknown) { const error = toError(err); ... }',\n 'Or to explicitly ignore: catch (err: unknown) { //const error = toError(err); }',\n 'For nested catches: catch (err2: unknown) { const error2 = toError(err2); }',\n '// 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"]}
@@ -0,0 +1,3 @@
1
+ import type { FileRule } from '../types';
2
+ declare const fileLocationRule: FileRule;
3
+ export default fileLocationRule;