@webpieces/ai-hooks 0.0.1
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/README.md +43 -0
- package/bin/setup-ai-hooks.sh +137 -0
- package/openclaw.plugin.json +15 -0
- package/package.json +35 -0
- package/src/adapters/claude-code-hook.d.ts +1 -0
- package/src/adapters/claude-code-hook.js +97 -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 +7 -0
- package/src/core/build-context.js +58 -0
- package/src/core/build-context.js.map +1 -0
- package/src/core/configs/default.d.ts +2 -0
- package/src/core/configs/default.js +22 -0
- 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/load-config.d.ts +3 -0
- package/src/core/load-config.js +81 -0
- package/src/core/load-config.js.map +1 -0
- package/src/core/load-rules.d.ts +3 -0
- package/src/core/load-rules.js +131 -0
- package/src/core/load-rules.js.map +1 -0
- package/src/core/rejection-log.d.ts +2 -0
- package/src/core/rejection-log.js +146 -0
- package/src/core/rejection-log.js.map +1 -0
- package/src/core/report.d.ts +2 -0
- package/src/core/report.js +34 -0
- 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.js +91 -0
- 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.js +73 -0
- package/src/core/rules/file-location.js.map +1 -0
- package/src/core/rules/index.d.ts +1 -0
- package/src/core/rules/index.js +13 -0
- 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.js +131 -0
- 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.js +30 -0
- package/src/core/rules/no-destructure.js.map +1 -0
- package/src/core/rules/no-unmanaged-exceptions.d.ts +3 -0
- package/src/core/rules/no-unmanaged-exceptions.js +41 -0
- 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.js +52 -0
- package/src/core/rules/require-return-type.js.map +1 -0
- package/src/core/runner.d.ts +2 -0
- package/src/core/runner.js +127 -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.js +38 -0
- package/src/core/to-error.js.map +1 -0
- package/src/core/types.d.ts +89 -0
- package/src/core/types.js +90 -0
- package/src/core/types.js.map +1 -0
- package/src/index.d.ts +5 -0
- package/src/index.js +24 -0
- package/src/index.js.map +1 -0
- package/templates/claude-settings-hook.json +15 -0
- package/templates/webpieces.ai-hooks.seed.json +16 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DirectiveIndex = void 0;
|
|
4
|
+
exports.parseDirectives = parseDirectives;
|
|
5
|
+
exports.createIsLineDisabled = createIsLineDisabled;
|
|
6
|
+
const DIRECTIVE_RE = /\/\/\s*(?:ai-hook-disable|webpieces-disable)(?:-(next|file|all))?(?:\s+([\w\-*,\s]+?))?(?:\s*--\s*(.*))?\s*$/;
|
|
7
|
+
class DirectiveIndex {
|
|
8
|
+
constructor(lineDisables, fileDisables) {
|
|
9
|
+
this.lineDisables = lineDisables;
|
|
10
|
+
this.fileDisables = fileDisables;
|
|
11
|
+
}
|
|
12
|
+
isLineDisabled(lineNum, ruleName) {
|
|
13
|
+
if (this.fileDisables.has('*') || this.fileDisables.has(ruleName))
|
|
14
|
+
return true;
|
|
15
|
+
const set = this.lineDisables.get(lineNum);
|
|
16
|
+
if (!set)
|
|
17
|
+
return false;
|
|
18
|
+
return set.has('*') || set.has(ruleName);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.DirectiveIndex = DirectiveIndex;
|
|
22
|
+
function parseRuleList(raw) {
|
|
23
|
+
if (!raw || raw.trim() === '' || raw.trim() === '*')
|
|
24
|
+
return ['*'];
|
|
25
|
+
return raw.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
|
|
26
|
+
}
|
|
27
|
+
function nextTargetLine(lines, lineIdx) {
|
|
28
|
+
for (let j = lineIdx + 1; j < lines.length; j += 1) {
|
|
29
|
+
const trimmed = lines[j].trim();
|
|
30
|
+
if (trimmed === '')
|
|
31
|
+
continue;
|
|
32
|
+
if (/^\/\/\s*(?:ai-hook-disable|webpieces-disable)/.test(trimmed))
|
|
33
|
+
continue;
|
|
34
|
+
return j + 1;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function addDisable(map, lineNum, rules) {
|
|
39
|
+
if (!map.has(lineNum))
|
|
40
|
+
map.set(lineNum, new Set());
|
|
41
|
+
const set = map.get(lineNum);
|
|
42
|
+
for (const r of rules)
|
|
43
|
+
set.add(r);
|
|
44
|
+
}
|
|
45
|
+
function resolveTarget(line, lines, i, map, rules) {
|
|
46
|
+
const beforeComment = line.slice(0, line.indexOf('//')).trim();
|
|
47
|
+
if (beforeComment !== '') {
|
|
48
|
+
addDisable(map, i + 1, rules);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const target = nextTargetLine(lines, i);
|
|
52
|
+
if (target !== null)
|
|
53
|
+
addDisable(map, target, rules);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function parseDirectives(source) {
|
|
57
|
+
const lines = source.split('\n');
|
|
58
|
+
const lineDisables = new Map();
|
|
59
|
+
const fileDisables = new Set();
|
|
60
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
61
|
+
const line = lines[i];
|
|
62
|
+
const match = line.match(DIRECTIVE_RE);
|
|
63
|
+
if (!match)
|
|
64
|
+
continue;
|
|
65
|
+
const variant = match[1] || null;
|
|
66
|
+
const rules = parseRuleList(match[2] || '');
|
|
67
|
+
if (variant === 'file') {
|
|
68
|
+
if (i < 20) {
|
|
69
|
+
for (const r of rules)
|
|
70
|
+
fileDisables.add(r);
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (variant === 'next') {
|
|
75
|
+
const target = nextTargetLine(lines, i);
|
|
76
|
+
if (target !== null)
|
|
77
|
+
addDisable(lineDisables, target, rules);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (variant === 'all') {
|
|
81
|
+
resolveTarget(line, lines, i, lineDisables, ['*']);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
resolveTarget(line, lines, i, lineDisables, rules);
|
|
85
|
+
}
|
|
86
|
+
return new DirectiveIndex(lineDisables, fileDisables);
|
|
87
|
+
}
|
|
88
|
+
function createIsLineDisabled(source) {
|
|
89
|
+
const index = parseDirectives(source);
|
|
90
|
+
return (lineNum, ruleName) => index.isLineDisabled(lineNum, ruleName);
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=disable-directives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disable-directives.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hooks/src/core/disable-directives.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IsLineDisabled } from './types';\n\nconst DIRECTIVE_RE =\n /\\/\\/\\s*(?:ai-hook-disable|webpieces-disable)(?:-(next|file|all))?(?:\\s+([\\w\\-*,\\s]+?))?(?:\\s*--\\s*(.*))?\\s*$/;\n\nexport class DirectiveIndex {\n private readonly lineDisables: Map<number, Set<string>>;\n private readonly fileDisables: Set<string>;\n\n constructor(lineDisables: Map<number, Set<string>>, fileDisables: Set<string>) {\n this.lineDisables = lineDisables;\n this.fileDisables = fileDisables;\n }\n\n isLineDisabled(lineNum: number, ruleName: string): boolean {\n if (this.fileDisables.has('*') || this.fileDisables.has(ruleName)) return true;\n const set = this.lineDisables.get(lineNum);\n if (!set) return false;\n return set.has('*') || set.has(ruleName);\n }\n}\n\nfunction parseRuleList(raw: string | undefined): readonly string[] {\n if (!raw || raw.trim() === '' || raw.trim() === '*') return ['*'];\n return raw.split(',').map((s) => s.trim()).filter((s) => s.length > 0);\n}\n\nfunction nextTargetLine(lines: readonly string[], lineIdx: number): number | null {\n for (let j = lineIdx + 1; j < lines.length; j += 1) {\n const trimmed = lines[j].trim();\n if (trimmed === '') continue;\n if (/^\\/\\/\\s*(?:ai-hook-disable|webpieces-disable)/.test(trimmed)) continue;\n return j + 1;\n }\n return null;\n}\n\nfunction addDisable(map: Map<number, Set<string>>, lineNum: number, rules: readonly string[]): void {\n if (!map.has(lineNum)) map.set(lineNum, new Set<string>());\n const set = map.get(lineNum)!;\n for (const r of rules) set.add(r);\n}\n\nfunction resolveTarget(\n line: string, lines: readonly string[], i: number,\n map: Map<number, Set<string>>, rules: readonly string[],\n): void {\n const beforeComment = line.slice(0, line.indexOf('//')).trim();\n if (beforeComment !== '') {\n addDisable(map, i + 1, rules);\n } else {\n const target = nextTargetLine(lines, i);\n if (target !== null) addDisable(map, target, rules);\n }\n}\n\nexport function parseDirectives(source: string): DirectiveIndex {\n const lines = source.split('\\n');\n const lineDisables = new Map<number, Set<string>>();\n const fileDisables = new Set<string>();\n\n for (let i = 0; i < lines.length; i += 1) {\n const line = lines[i];\n const match = line.match(DIRECTIVE_RE);\n if (!match) continue;\n const variant = match[1] || null;\n const rules = parseRuleList(match[2] || '');\n\n if (variant === 'file') {\n if (i < 20) { for (const r of rules) fileDisables.add(r); }\n continue;\n }\n if (variant === 'next') {\n const target = nextTargetLine(lines, i);\n if (target !== null) addDisable(lineDisables, target, rules);\n continue;\n }\n if (variant === 'all') {\n resolveTarget(line, lines, i, lineDisables, ['*']);\n continue;\n }\n resolveTarget(line, lines, i, lineDisables, rules);\n }\n return new DirectiveIndex(lineDisables, fileDisables);\n}\n\nexport function createIsLineDisabled(source: string): IsLineDisabled {\n const index = parseDirectives(source);\n return (lineNum: number, ruleName: string): boolean => index.isLineDisabled(lineNum, ruleName);\n}\n"]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findConfigFile = findConfigFile;
|
|
4
|
+
exports.loadConfig = loadConfig;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
7
|
+
const path = tslib_1.__importStar(require("path"));
|
|
8
|
+
const types_1 = require("./types");
|
|
9
|
+
function findConfigFile(startDir) {
|
|
10
|
+
let dir = startDir;
|
|
11
|
+
while (true) {
|
|
12
|
+
const candidate = path.join(dir, 'webpieces.ai-hooks.json');
|
|
13
|
+
if (fs.existsSync(candidate))
|
|
14
|
+
return candidate;
|
|
15
|
+
const parent = path.dirname(dir);
|
|
16
|
+
if (parent === dir)
|
|
17
|
+
return null;
|
|
18
|
+
dir = parent;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// webpieces-disable no-any-unknown -- default config returns opaque rule option bags
|
|
22
|
+
function loadDefaultConfig() {
|
|
23
|
+
const defaultModule = require('./configs/default');
|
|
24
|
+
// webpieces-disable no-any-unknown -- opaque rule option bags
|
|
25
|
+
return defaultModule.defaultRules;
|
|
26
|
+
}
|
|
27
|
+
// webpieces-disable no-any-unknown -- merging opaque option bags from config JSON
|
|
28
|
+
function mergeRule(
|
|
29
|
+
// webpieces-disable no-any-unknown -- opaque option bag
|
|
30
|
+
baseRule,
|
|
31
|
+
// webpieces-disable no-any-unknown -- opaque option bag
|
|
32
|
+
overrideRule) {
|
|
33
|
+
if (!baseRule && !overrideRule)
|
|
34
|
+
return new types_1.ResolvedRuleConfig(false, {});
|
|
35
|
+
if (!baseRule)
|
|
36
|
+
return new types_1.ResolvedRuleConfig(true, overrideRule);
|
|
37
|
+
if (!overrideRule) {
|
|
38
|
+
const enabled = baseRule['enabled'] !== false;
|
|
39
|
+
return new types_1.ResolvedRuleConfig(enabled, baseRule);
|
|
40
|
+
}
|
|
41
|
+
// webpieces-disable no-any-unknown -- building merged option bag
|
|
42
|
+
const merged = {};
|
|
43
|
+
for (const key of Object.keys(baseRule))
|
|
44
|
+
merged[key] = baseRule[key];
|
|
45
|
+
for (const key of Object.keys(overrideRule))
|
|
46
|
+
merged[key] = overrideRule[key];
|
|
47
|
+
const enabled = merged['enabled'] !== false;
|
|
48
|
+
return new types_1.ResolvedRuleConfig(enabled, merged);
|
|
49
|
+
}
|
|
50
|
+
function loadConfig(cwd) {
|
|
51
|
+
const configPath = findConfigFile(cwd);
|
|
52
|
+
const defaultRules = loadDefaultConfig();
|
|
53
|
+
if (!configPath) {
|
|
54
|
+
return new types_1.ResolvedConfig(new Map(), [], null);
|
|
55
|
+
}
|
|
56
|
+
let consumerConfig;
|
|
57
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
58
|
+
try {
|
|
59
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
60
|
+
consumerConfig = JSON.parse(raw);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
// eslint-disable-next-line @webpieces/catch-error-pattern -- malformed config fails open
|
|
64
|
+
void err;
|
|
65
|
+
return new types_1.ResolvedConfig(new Map(), [], configPath);
|
|
66
|
+
}
|
|
67
|
+
const overrideRules = consumerConfig.rules || {};
|
|
68
|
+
const mergedRules = new Map();
|
|
69
|
+
const allRuleNames = new Set([
|
|
70
|
+
...Object.keys(defaultRules),
|
|
71
|
+
...Object.keys(overrideRules),
|
|
72
|
+
]);
|
|
73
|
+
for (const name of allRuleNames) {
|
|
74
|
+
mergedRules.set(name, mergeRule(defaultRules[name], overrideRules[name]));
|
|
75
|
+
}
|
|
76
|
+
const baseDirs = [];
|
|
77
|
+
const overrideDirs = consumerConfig.rulesDir || [];
|
|
78
|
+
const rulesDir = [...baseDirs, ...overrideDirs];
|
|
79
|
+
return new types_1.ResolvedConfig(mergedRules, rulesDir, configPath);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=load-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-config.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hooks/src/core/load-config.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\n\n// webpieces-disable no-any-unknown -- consumer JSON config has opaque rule option values\ninterface RawConfigFile {\n extends?: string;\n rules?: Record<string, Record<string, unknown>>;\n rulesDir?: string[];\n}\n\nexport function findConfigFile(startDir: string): string | null {\n let dir = startDir;\n while (true) {\n const candidate = path.join(dir, 'webpieces.ai-hooks.json');\n if (fs.existsSync(candidate)) return candidate;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n// webpieces-disable no-any-unknown -- default config returns opaque rule option bags\nfunction loadDefaultConfig(): Record<string, Record<string, unknown>> {\n const defaultModule = require('./configs/default');\n // webpieces-disable no-any-unknown -- opaque rule option bags\n return defaultModule.defaultRules as Record<string, Record<string, unknown>>;\n}\n\n// webpieces-disable no-any-unknown -- merging opaque option bags from config JSON\nfunction mergeRule(\n // webpieces-disable no-any-unknown -- opaque option bag\n baseRule: Record<string, unknown> | undefined,\n // webpieces-disable no-any-unknown -- opaque option bag\n overrideRule: Record<string, unknown> | undefined,\n): ResolvedRuleConfig {\n if (!baseRule && !overrideRule) return new ResolvedRuleConfig(false, {});\n if (!baseRule) return new ResolvedRuleConfig(true, overrideRule as RuleOptions);\n if (!overrideRule) {\n const enabled = baseRule['enabled'] !== false;\n return new ResolvedRuleConfig(enabled, baseRule as RuleOptions);\n }\n // webpieces-disable no-any-unknown -- building merged option bag\n const merged: Record<string, unknown> = {};\n for (const key of Object.keys(baseRule)) merged[key] = baseRule[key];\n for (const key of Object.keys(overrideRule)) merged[key] = overrideRule[key];\n const enabled = merged['enabled'] !== false;\n return new ResolvedRuleConfig(enabled, merged as RuleOptions);\n}\n\nexport function loadConfig(cwd: string): ResolvedConfig {\n const configPath = findConfigFile(cwd);\n const defaultRules = loadDefaultConfig();\n\n if (!configPath) {\n return new ResolvedConfig(new Map(), [], null);\n }\n\n let consumerConfig: RawConfigFile;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = fs.readFileSync(configPath, 'utf8');\n consumerConfig = JSON.parse(raw) as RawConfigFile;\n } catch (err: unknown) {\n // eslint-disable-next-line @webpieces/catch-error-pattern -- malformed config fails open\n void err;\n return new ResolvedConfig(new Map(), [], configPath);\n }\n\n const overrideRules = consumerConfig.rules || {};\n const mergedRules = new Map<string, ResolvedRuleConfig>();\n\n const allRuleNames = new Set([\n ...Object.keys(defaultRules),\n ...Object.keys(overrideRules),\n ]);\n for (const name of allRuleNames) {\n mergedRules.set(name, mergeRule(defaultRules[name], overrideRules[name]));\n }\n\n const baseDirs: string[] = [];\n const overrideDirs = consumerConfig.rulesDir || [];\n const rulesDir = [...baseDirs, ...overrideDirs];\n\n return new ResolvedConfig(mergedRules, rulesDir, configPath);\n}\n"]}
|
|
@@ -0,0 +1,131 @@
|
|
|
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'];
|
|
10
|
+
const VALID_SCOPES = new Set(['edit', 'file']);
|
|
11
|
+
function loadRules(config, workspaceRoot) {
|
|
12
|
+
const builtIns = loadBuiltInRules();
|
|
13
|
+
const custom = loadCustomRules(config.rulesDir, workspaceRoot);
|
|
14
|
+
const all = [...builtIns, ...custom];
|
|
15
|
+
return all.filter((rule) => validateRule(rule));
|
|
16
|
+
}
|
|
17
|
+
function loadBuiltInRules() {
|
|
18
|
+
const registry = require('./rules/index').builtInRuleNames;
|
|
19
|
+
const modules = [];
|
|
20
|
+
for (const name of registry) {
|
|
21
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
22
|
+
try {
|
|
23
|
+
const mod = require(`./rules/${name}`);
|
|
24
|
+
modules.push(mod.default || mod);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
const error = (0, to_error_1.toError)(err);
|
|
28
|
+
process.stderr.write(`[ai-hooks] failed to load built-in rule ${name}: ${error.message}\n`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return modules;
|
|
32
|
+
}
|
|
33
|
+
function loadCustomRules(rulesDirs, workspaceRoot) {
|
|
34
|
+
const modules = [];
|
|
35
|
+
for (const dir of rulesDirs) {
|
|
36
|
+
const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);
|
|
37
|
+
if (!fs.existsSync(absDir)) {
|
|
38
|
+
process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\n`);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
let entries;
|
|
42
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
43
|
+
try {
|
|
44
|
+
entries = fs.readdirSync(absDir).filter((e) => e.endsWith('.js'));
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const error = (0, to_error_1.toError)(err);
|
|
48
|
+
process.stderr.write(`[ai-hooks] cannot read rulesDir ${absDir}: ${error.message}\n`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const full = path.join(absDir, entry);
|
|
53
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
54
|
+
try {
|
|
55
|
+
const mod = require(full);
|
|
56
|
+
modules.push(mod.default || mod);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
const error = (0, to_error_1.toError)(err);
|
|
60
|
+
process.stderr.write(`[ai-hooks] failed to load custom rule ${full}: ${error.message}\n`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return modules;
|
|
65
|
+
}
|
|
66
|
+
// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary
|
|
67
|
+
function validateRule(rule) {
|
|
68
|
+
if (!rule || typeof rule !== 'object') {
|
|
69
|
+
process.stderr.write('[ai-hooks] rule is not an object, skipping\n');
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
// webpieces-disable no-any-unknown -- narrowing from unknown at system boundary
|
|
73
|
+
const obj = rule;
|
|
74
|
+
for (const field of REQUIRED_FIELDS) {
|
|
75
|
+
if (obj[field] === undefined) {
|
|
76
|
+
const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';
|
|
77
|
+
process.stderr.write(`[ai-hooks] rule "${name}" missing required field: ${field}\n`);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!VALID_SCOPES.has(obj['scope'])) {
|
|
82
|
+
process.stderr.write(`[ai-hooks] rule "${obj['name']}" has invalid scope: ${String(obj['scope'])}\n`);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(obj['files'])) {
|
|
86
|
+
process.stderr.write(`[ai-hooks] rule "${obj['name']}" files must be an array\n`);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if (typeof obj['check'] !== 'function') {
|
|
90
|
+
process.stderr.write(`[ai-hooks] rule "${obj['name']}" check must be a function\n`);
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
function globMatches(pattern, filePath) {
|
|
96
|
+
const regex = globToRegex(pattern);
|
|
97
|
+
return regex.test(filePath);
|
|
98
|
+
}
|
|
99
|
+
function globToRegex(pattern) {
|
|
100
|
+
let re = '';
|
|
101
|
+
let i = 0;
|
|
102
|
+
while (i < pattern.length) {
|
|
103
|
+
const ch = pattern[i];
|
|
104
|
+
if (ch === '*') {
|
|
105
|
+
if (pattern[i + 1] === '*') {
|
|
106
|
+
re += '.*';
|
|
107
|
+
i += 2;
|
|
108
|
+
if (pattern[i] === '/')
|
|
109
|
+
i += 1;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
re += '[^/]*';
|
|
113
|
+
i += 1;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (ch === '?') {
|
|
117
|
+
re += '[^/]';
|
|
118
|
+
i += 1;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if ('.+^$(){}|[]\\'.includes(ch)) {
|
|
122
|
+
re += '\\' + ch;
|
|
123
|
+
i += 1;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
re += ch;
|
|
127
|
+
i += 1;
|
|
128
|
+
}
|
|
129
|
+
return new RegExp('^' + re + '$');
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=load-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-rules.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hooks/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']);\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,146 @@
|
|
|
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"));
|
|
7
|
+
const HOOKS_DIR = '.webpieces/hooks';
|
|
8
|
+
const LOG_FILE = 'hook-rejection.log';
|
|
9
|
+
const LOG_FILE_PREV = 'hook-rejection.1.log';
|
|
10
|
+
const MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded
|
|
11
|
+
const MAX_AGE_DAYS = 7;
|
|
12
|
+
const RULE_NAME_RE = /^\[([^\]]+)\] \(/gm;
|
|
13
|
+
function logRejection(toolKind, input, result, cwd) {
|
|
14
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
15
|
+
try {
|
|
16
|
+
const now = new Date();
|
|
17
|
+
const timestamp = now.toISOString();
|
|
18
|
+
const epochMs = String(now.getTime());
|
|
19
|
+
const dateStr = timestamp.slice(0, 10);
|
|
20
|
+
const hooksDir = path.join(cwd, HOOKS_DIR);
|
|
21
|
+
const dayDir = path.join(hooksDir, dateStr);
|
|
22
|
+
fs.mkdirSync(dayDir, { recursive: true });
|
|
23
|
+
const relativePath = computeRelativePath(input.filePath, cwd);
|
|
24
|
+
const ruleNames = extractRuleNames(result.report);
|
|
25
|
+
const detailFileName = `writeInfo-${epochMs}.md`;
|
|
26
|
+
const detailRelPath = `${dateStr}/${detailFileName}`;
|
|
27
|
+
const detail = buildDetailContent(timestamp, toolKind, relativePath, ruleNames, result.report, input);
|
|
28
|
+
fs.writeFileSync(path.join(dayDir, detailFileName), detail);
|
|
29
|
+
const logPath = path.join(hooksDir, LOG_FILE);
|
|
30
|
+
rotateLogFile(logPath, path.join(hooksDir, LOG_FILE_PREV));
|
|
31
|
+
const logLine = `[${timestamp}]\t${toolKind}\t${relativePath}\t[${ruleNames.join(',')}]\t${detailRelPath}\n`;
|
|
32
|
+
fs.appendFileSync(logPath, logLine);
|
|
33
|
+
rotateOldDays(hooksDir, MAX_AGE_DAYS);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
//const error = toError(err);
|
|
37
|
+
void err;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function computeRelativePath(filePath, cwd) {
|
|
41
|
+
if (filePath.startsWith(cwd)) {
|
|
42
|
+
const rel = filePath.slice(cwd.length);
|
|
43
|
+
if (rel.startsWith('/'))
|
|
44
|
+
return rel.slice(1);
|
|
45
|
+
return rel;
|
|
46
|
+
}
|
|
47
|
+
return filePath;
|
|
48
|
+
}
|
|
49
|
+
function extractRuleNames(report) {
|
|
50
|
+
const names = [];
|
|
51
|
+
let match = RULE_NAME_RE.exec(report);
|
|
52
|
+
while (match !== null) {
|
|
53
|
+
names.push(match[1]);
|
|
54
|
+
match = RULE_NAME_RE.exec(report);
|
|
55
|
+
}
|
|
56
|
+
RULE_NAME_RE.lastIndex = 0;
|
|
57
|
+
return names;
|
|
58
|
+
}
|
|
59
|
+
function buildDetailContent(timestamp, toolKind, relativePath, ruleNames, report, input) {
|
|
60
|
+
const lines = [];
|
|
61
|
+
lines.push('# Hook Rejection Detail');
|
|
62
|
+
lines.push('');
|
|
63
|
+
lines.push(`- **Timestamp:** ${timestamp}`);
|
|
64
|
+
lines.push(`- **Tool:** ${toolKind}`);
|
|
65
|
+
lines.push(`- **File:** ${relativePath}`);
|
|
66
|
+
lines.push(`- **Rules violated:** ${ruleNames.join(', ')}`);
|
|
67
|
+
lines.push('');
|
|
68
|
+
lines.push('## Report');
|
|
69
|
+
lines.push('');
|
|
70
|
+
lines.push('```');
|
|
71
|
+
lines.push(report.trimEnd());
|
|
72
|
+
lines.push('```');
|
|
73
|
+
lines.push('');
|
|
74
|
+
lines.push('## Content Being Written');
|
|
75
|
+
lines.push('');
|
|
76
|
+
if (toolKind === 'Write') {
|
|
77
|
+
const content = input.edits.length > 0 ? input.edits[0].newString : '';
|
|
78
|
+
lines.push('```typescript');
|
|
79
|
+
lines.push(content.trimEnd());
|
|
80
|
+
lines.push('```');
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
for (let i = 0; i < input.edits.length; i += 1) {
|
|
84
|
+
const edit = input.edits[i];
|
|
85
|
+
lines.push(`### Edit ${String(i + 1)} of ${String(input.edits.length)}`);
|
|
86
|
+
lines.push('');
|
|
87
|
+
lines.push('**old_string:**');
|
|
88
|
+
lines.push('```typescript');
|
|
89
|
+
lines.push(edit.oldString.trimEnd());
|
|
90
|
+
lines.push('```');
|
|
91
|
+
lines.push('');
|
|
92
|
+
lines.push('**new_string:**');
|
|
93
|
+
lines.push('```typescript');
|
|
94
|
+
lines.push(edit.newString.trimEnd());
|
|
95
|
+
lines.push('```');
|
|
96
|
+
lines.push('');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return lines.join('\n') + '\n';
|
|
100
|
+
}
|
|
101
|
+
function rotateLogFile(logPath, prevPath) {
|
|
102
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
103
|
+
try {
|
|
104
|
+
const stat = fs.statSync(logPath);
|
|
105
|
+
if (stat.size > MAX_LOG_BYTES) {
|
|
106
|
+
if (fs.existsSync(prevPath))
|
|
107
|
+
fs.unlinkSync(prevPath);
|
|
108
|
+
fs.renameSync(logPath, prevPath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
//const error = toError(err);
|
|
113
|
+
void err;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function rotateOldDays(hooksDir, maxAgeDays) {
|
|
117
|
+
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
|
|
118
|
+
let entries;
|
|
119
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
120
|
+
try {
|
|
121
|
+
entries = fs.readdirSync(hooksDir);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
//const error = toError(err);
|
|
125
|
+
void err;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(entry))
|
|
130
|
+
continue;
|
|
131
|
+
const dirDate = new Date(entry + 'T00:00:00Z');
|
|
132
|
+
if (isNaN(dirDate.getTime()))
|
|
133
|
+
continue;
|
|
134
|
+
if (dirDate.getTime() < cutoff) {
|
|
135
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
136
|
+
try {
|
|
137
|
+
fs.rmSync(path.join(hooksDir, entry), { recursive: true, force: true });
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
//const error = toError(err);
|
|
141
|
+
void err;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=rejection-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rejection-log.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hooks/src/core/rejection-log.ts"],"names":[],"mappings":";;AAaA,oCAoCC;;AAjDD,+CAAyB;AACzB,mDAA6B;AAI7B,MAAM,SAAS,GAAG,kBAAkB,CAAC;AACrC,MAAM,QAAQ,GAAG,oBAAoB,CAAC;AACtC,MAAM,aAAa,GAAG,sBAAsB,CAAC;AAC7C,MAAM,aAAa,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,gCAAgC;AAClE,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB,MAAM,YAAY,GAAG,oBAAoB,CAAC;AAE1C,SAAgB,YAAY,CACxB,QAAkB,EAClB,KAA0B,EAC1B,MAAqB,EACrB,GAAW;IAEX,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACtC,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAEvC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC5C,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE1C,MAAM,YAAY,GAAG,mBAAmB,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QAC9D,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAClD,MAAM,cAAc,GAAG,aAAa,OAAO,KAAK,CAAC;QACjD,MAAM,aAAa,GAAG,GAAG,OAAO,IAAI,cAAc,EAAE,CAAC;QAErD,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACtG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;QAE5D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC9C,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC;QAE3D,MAAM,OAAO,GAAG,IAAI,SAAS,MAAM,QAAQ,KAAK,YAAY,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,aAAa,IAAI,CAAC;QAC7G,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAEpC,aAAa,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,6BAA6B;QAC7B,KAAK,GAAG,CAAC;IACb,CAAC;AACL,CAAC;AAED,SAAS,mBAAmB,CAAC,QAAgB,EAAE,GAAW;IACtD,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7C,OAAO,GAAG,CAAC;IACf,CAAC;IACD,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAc;IACpC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,OAAO,KAAK,KAAK,IAAI,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACrB,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IACD,YAAY,CAAC,SAAS,GAAG,CAAC,CAAC;IAC3B,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,kBAAkB,CACvB,SAAiB,EACjB,QAAkB,EAClB,YAAoB,EACpB,SAAmB,EACnB,MAAc,EACd,KAA0B;IAE1B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IACtC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,oBAAoB,SAAS,EAAE,CAAC,CAAC;IAC5C,KAAK,CAAC,IAAI,CAAC,eAAe,QAAQ,EAAE,CAAC,CAAC;IACtC,KAAK,CAAC,IAAI,CAAC,eAAe,YAAY,EAAE,CAAC,CAAC;IAC1C,KAAK,CAAC,IAAI,CAAC,yBAAyB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACxB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IACvC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACvE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;SAAM,CAAC;QACJ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACzE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACnC,CAAC;AAED,SAAS,aAAa,CAAC,OAAe,EAAE,QAAgB;IACpD,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,IAAI,CAAC,IAAI,GAAG,aAAa,EAAE,CAAC;YAC5B,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;gBAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YACrD,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACrC,CAAC;IACL,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,6BAA6B;QAC7B,KAAK,GAAG,CAAC;IACb,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CAAC,QAAgB,EAAE,UAAkB;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC7D,IAAI,OAAiB,CAAC;IACtB,8DAA8D;IAC9D,IAAI,CAAC;QACD,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,6BAA6B;QAC7B,KAAK,GAAG,CAAC;QACT,OAAO;IACX,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,SAAS;QACjD,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC,CAAC;QAC/C,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YAAE,SAAS;QACvC,IAAI,OAAO,CAAC,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC;YAC7B,8DAA8D;YAC9D,IAAI,CAAC;gBACD,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5E,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACpB,6BAA6B;gBAC7B,KAAK,GAAG,CAAC;YACb,CAAC;QACL,CAAC;IACL,CAAC;AACL,CAAC","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,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatReport = formatReport;
|
|
4
|
+
function formatReport(relativePath, ruleGroups) {
|
|
5
|
+
const lines = [];
|
|
6
|
+
lines.push(`\u274c webpieces ai-hooks blocked this write: ${relativePath}`);
|
|
7
|
+
lines.push('');
|
|
8
|
+
for (const group of ruleGroups) {
|
|
9
|
+
const count = group.violations.length;
|
|
10
|
+
const label = count === 1 ? '1 violation' : `${count} violations`;
|
|
11
|
+
lines.push(`[${group.ruleName}] (${label})`);
|
|
12
|
+
for (const v of group.violations) {
|
|
13
|
+
const editPrefix = formatEditPrefix(v);
|
|
14
|
+
lines.push(` ${editPrefix}L${String(v.line)}: ${v.snippet}`);
|
|
15
|
+
lines.push(` \u2192 ${v.message}`);
|
|
16
|
+
}
|
|
17
|
+
if (group.fixHint.length > 0) {
|
|
18
|
+
for (let i = 0; i < group.fixHint.length; i += 1) {
|
|
19
|
+
lines.push(` Fix Option ${String(i + 1)}: ${group.fixHint[i]}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
lines.push('');
|
|
23
|
+
}
|
|
24
|
+
lines.push('This is a pre-write check. Fix and retry the Write/Edit.');
|
|
25
|
+
lines.push('');
|
|
26
|
+
return lines.join('\n');
|
|
27
|
+
}
|
|
28
|
+
function formatEditPrefix(v) {
|
|
29
|
+
if (v.editIndex !== undefined && v.editCount !== undefined && v.editCount > 1) {
|
|
30
|
+
return `edit ${String(v.editIndex + 1)}/${String(v.editCount)} `;
|
|
31
|
+
}
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hooks/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"]}
|