@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.
- package/package.json +4 -3
- package/src/adapters/claude-code-hook.d.ts +1 -0
- package/src/adapters/claude-code-hook.js +112 -0
- package/src/adapters/claude-code-hook.js.map +1 -0
- package/src/adapters/openclaw-plugin.d.ts +14 -0
- package/src/adapters/openclaw-plugin.js +73 -0
- package/src/adapters/openclaw-plugin.js.map +1 -0
- package/src/core/build-context.d.ts +8 -0
- package/src/core/build-context.js +62 -0
- package/src/core/build-context.js.map +1 -0
- package/src/core/configs/default.d.ts +2 -0
- package/src/core/configs/{default.ts → default.js} +6 -3
- package/src/core/configs/default.js.map +1 -0
- package/src/core/disable-directives.d.ts +9 -0
- package/src/core/disable-directives.js +92 -0
- package/src/core/disable-directives.js.map +1 -0
- package/src/core/instruct-ai-writer.d.ts +1 -0
- package/src/core/instruct-ai-writer.js +18 -0
- package/src/core/instruct-ai-writer.js.map +1 -0
- package/src/core/load-config.d.ts +1 -0
- package/src/core/load-config.js +10 -0
- package/src/core/load-config.js.map +1 -0
- package/src/core/load-rules.d.ts +3 -0
- package/src/core/{load-rules.ts → load-rules.js} +33 -32
- package/src/core/load-rules.js.map +1 -0
- package/src/core/rejection-log.d.ts +2 -0
- package/src/core/{rejection-log.ts → rejection-log.js} +34 -51
- package/src/core/rejection-log.js.map +1 -0
- package/src/core/report.d.ts +2 -0
- package/src/core/{report.ts → report.js} +7 -8
- package/src/core/report.js.map +1 -0
- package/src/core/rules/catch-error-pattern.d.ts +3 -0
- package/src/core/rules/{catch-error-pattern.ts → catch-error-pattern.js} +25 -54
- package/src/core/rules/catch-error-pattern.js.map +1 -0
- package/src/core/rules/file-location.d.ts +3 -0
- package/src/core/rules/{file-location.ts → file-location.js} +29 -43
- package/src/core/rules/file-location.js.map +1 -0
- package/src/core/rules/index.d.ts +1 -0
- package/src/core/rules/{index.ts → index.js} +5 -1
- package/src/core/rules/index.js.map +1 -0
- package/src/core/rules/max-file-lines.d.ts +3 -0
- package/src/core/rules/{max-file-lines.ts → max-file-lines.js} +17 -23
- package/src/core/rules/max-file-lines.js.map +1 -0
- package/src/core/rules/no-any-unknown.d.ts +3 -0
- package/src/core/rules/no-any-unknown.js +30 -0
- package/src/core/rules/no-any-unknown.js.map +1 -0
- package/src/core/rules/no-destructure.d.ts +3 -0
- package/src/core/rules/{no-destructure.ts → no-destructure.js} +13 -17
- package/src/core/rules/no-destructure.js.map +1 -0
- package/src/core/rules/no-implicit-any.d.ts +3 -0
- package/src/core/rules/{no-implicit-any.ts → no-implicit-any.js} +32 -30
- package/src/core/rules/no-implicit-any.js.map +1 -0
- package/src/core/rules/no-shell-substitution.d.ts +3 -0
- package/src/core/rules/no-shell-substitution.js +54 -0
- package/src/core/rules/no-shell-substitution.js.map +1 -0
- package/src/core/rules/no-unmanaged-exceptions.d.ts +3 -0
- package/src/core/rules/{no-unmanaged-exceptions.ts → no-unmanaged-exceptions.js} +21 -24
- package/src/core/rules/no-unmanaged-exceptions.js.map +1 -0
- package/src/core/rules/require-return-type.d.ts +3 -0
- package/src/core/rules/{require-return-type.ts → require-return-type.js} +21 -28
- package/src/core/rules/require-return-type.js.map +1 -0
- package/src/core/runner.d.ts +3 -0
- package/src/core/runner.js +181 -0
- package/src/core/runner.js.map +1 -0
- package/src/core/strip-ts-noise.d.ts +1 -0
- package/src/core/strip-ts-noise.js +178 -0
- package/src/core/strip-ts-noise.js.map +1 -0
- package/src/core/to-error.d.ts +5 -0
- package/src/core/{to-error.ts → to-error.js} +7 -4
- package/src/core/to-error.js.map +1 -0
- package/src/core/types.d.ts +93 -0
- package/src/core/types.js +93 -0
- package/src/core/types.js.map +1 -0
- package/src/index.d.ts +5 -0
- package/src/index.js +25 -0
- package/src/index.js.map +1 -0
- package/LICENSE +0 -373
- package/src/adapters/claude-code-hook.ts +0 -117
- package/src/adapters/openclaw-plugin.ts +0 -88
- package/src/core/__tests__/disable-directives.test.ts +0 -114
- package/src/core/__tests__/rules/file-location.test.ts +0 -90
- package/src/core/__tests__/rules/max-file-lines.test.ts +0 -53
- package/src/core/__tests__/rules/no-any.test.ts +0 -68
- package/src/core/__tests__/rules/no-destructure.test.ts +0 -50
- package/src/core/__tests__/rules/no-shell-substitution.test.ts +0 -118
- package/src/core/__tests__/rules/no-unmanaged-exceptions.test.ts +0 -54
- package/src/core/__tests__/rules/require-return-type.test.ts +0 -79
- package/src/core/__tests__/runner.test.ts +0 -288
- package/src/core/__tests__/strip-ts-noise.test.ts +0 -109
- package/src/core/build-context.ts +0 -96
- package/src/core/disable-directives.ts +0 -90
- package/src/core/instruct-ai-writer.ts +0 -15
- package/src/core/load-config.ts +0 -3
- package/src/core/rules/no-any-unknown.ts +0 -35
- package/src/core/rules/no-shell-substitution.ts +0 -71
- package/src/core/runner.ts +0 -205
- package/src/core/strip-ts-noise.ts +0 -103
- package/src/core/types.ts +0 -196
- package/src/index.ts +0 -14
|
@@ -1,49 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
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
|
-
|
|
18
|
-
const
|
|
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
|
-
}
|
|
26
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
}
|
|
46
|
-
|
|
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
|
-
}
|
|
57
|
-
|
|
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
|
|
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
|
|
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']
|
|
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] === '/')
|
|
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"]}
|
|
@@ -1,65 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
}
|
|
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('/'))
|
|
43
|
+
if (rel.startsWith('/'))
|
|
44
|
+
return rel.slice(1);
|
|
56
45
|
return rel;
|
|
57
46
|
}
|
|
58
47
|
return filePath;
|
|
59
48
|
}
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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))
|
|
106
|
+
if (fs.existsSync(prevPath))
|
|
107
|
+
fs.unlinkSync(prevPath);
|
|
129
108
|
fs.renameSync(logPath, prevPath);
|
|
130
109
|
}
|
|
131
|
-
}
|
|
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
|
|
118
|
+
let entries;
|
|
140
119
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
141
120
|
try {
|
|
142
121
|
entries = fs.readdirSync(hooksDir);
|
|
143
|
-
}
|
|
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))
|
|
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()))
|
|
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
|
-
}
|
|
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"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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"]}
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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)
|
|
39
|
-
|
|
34
|
+
if (!catchMatch)
|
|
35
|
+
continue;
|
|
40
36
|
const lineNum = i + 1;
|
|
41
|
-
if (ctx.isLineDisabled(lineNum, 'catch-error-pattern'))
|
|
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
|
|
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
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
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
|
|
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)
|
|
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 === '{')
|
|
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
|
-
|
|
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":"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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 '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"]}
|