@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.
Files changed (72) hide show
  1. package/README.md +43 -0
  2. package/bin/setup-ai-hooks.sh +137 -0
  3. package/openclaw.plugin.json +15 -0
  4. package/package.json +35 -0
  5. package/src/adapters/claude-code-hook.d.ts +1 -0
  6. package/src/adapters/claude-code-hook.js +97 -0
  7. package/src/adapters/claude-code-hook.js.map +1 -0
  8. package/src/adapters/openclaw-plugin.d.ts +14 -0
  9. package/src/adapters/openclaw-plugin.js +73 -0
  10. package/src/adapters/openclaw-plugin.js.map +1 -0
  11. package/src/core/build-context.d.ts +7 -0
  12. package/src/core/build-context.js +58 -0
  13. package/src/core/build-context.js.map +1 -0
  14. package/src/core/configs/default.d.ts +2 -0
  15. package/src/core/configs/default.js +22 -0
  16. package/src/core/configs/default.js.map +1 -0
  17. package/src/core/disable-directives.d.ts +9 -0
  18. package/src/core/disable-directives.js +92 -0
  19. package/src/core/disable-directives.js.map +1 -0
  20. package/src/core/load-config.d.ts +3 -0
  21. package/src/core/load-config.js +81 -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.js +131 -0
  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.js +146 -0
  28. package/src/core/rejection-log.js.map +1 -0
  29. package/src/core/report.d.ts +2 -0
  30. package/src/core/report.js +34 -0
  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.js +91 -0
  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.js +73 -0
  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.js +13 -0
  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.js +131 -0
  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.js +30 -0
  49. package/src/core/rules/no-destructure.js.map +1 -0
  50. package/src/core/rules/no-unmanaged-exceptions.d.ts +3 -0
  51. package/src/core/rules/no-unmanaged-exceptions.js +41 -0
  52. package/src/core/rules/no-unmanaged-exceptions.js.map +1 -0
  53. package/src/core/rules/require-return-type.d.ts +3 -0
  54. package/src/core/rules/require-return-type.js +52 -0
  55. package/src/core/rules/require-return-type.js.map +1 -0
  56. package/src/core/runner.d.ts +2 -0
  57. package/src/core/runner.js +127 -0
  58. package/src/core/runner.js.map +1 -0
  59. package/src/core/strip-ts-noise.d.ts +1 -0
  60. package/src/core/strip-ts-noise.js +178 -0
  61. package/src/core/strip-ts-noise.js.map +1 -0
  62. package/src/core/to-error.d.ts +5 -0
  63. package/src/core/to-error.js +38 -0
  64. package/src/core/to-error.js.map +1 -0
  65. package/src/core/types.d.ts +89 -0
  66. package/src/core/types.js +90 -0
  67. package/src/core/types.js.map +1 -0
  68. package/src/index.d.ts +5 -0
  69. package/src/index.js +24 -0
  70. package/src/index.js.map +1 -0
  71. package/templates/claude-settings-hook.json +15 -0
  72. 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":";;;AAwDA,0CA4BC;AAED,oDAGC;AAvFD,MAAM,YAAY,GACd,8GAA8G,CAAC;AAEnH,MAAa,cAAc;IAIvB,YAAY,YAAsC,EAAE,YAAyB;QACzE,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACrC,CAAC;IAED,cAAc,CAAC,OAAe,EAAE,QAAgB;QAC5C,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC/E,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3C,IAAI,CAAC,GAAG;YAAE,OAAO,KAAK,CAAC;QACvB,OAAO,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7C,CAAC;CACJ;AAfD,wCAeC;AAED,SAAS,aAAa,CAAC,GAAuB;IAC1C,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG;QAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAClE,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,cAAc,CAAC,KAAwB,EAAE,OAAe;IAC7D,KAAK,IAAI,CAAC,GAAG,OAAO,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAChC,IAAI,OAAO,KAAK,EAAE;YAAE,SAAS;QAC7B,IAAI,+CAA+C,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,SAAS;QAC5E,OAAO,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,GAA6B,EAAE,OAAe,EAAE,KAAwB;IACxF,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,GAAG,EAAU,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,OAAO,CAAE,CAAC;IAC9B,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,aAAa,CAClB,IAAY,EAAE,KAAwB,EAAE,CAAS,EACjD,GAA6B,EAAE,KAAwB;IAEvD,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/D,IAAI,aAAa,KAAK,EAAE,EAAE,CAAC;QACvB,UAAU,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;SAAM,CAAC;QACJ,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACxC,IAAI,MAAM,KAAK,IAAI;YAAE,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACxD,CAAC;AACL,CAAC;AAED,SAAgB,eAAe,CAAC,MAAc;IAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,YAAY,GAAG,IAAI,GAAG,EAAuB,CAAC;IACpD,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;IAEvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;QACjC,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAE5C,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;YACrB,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBAAC,KAAK,MAAM,CAAC,IAAI,KAAK;oBAAE,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAAC,CAAC;YAC3D,SAAS;QACb,CAAC;QACD,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;YACrB,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YACxC,IAAI,MAAM,KAAK,IAAI;gBAAE,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;YAC7D,SAAS;QACb,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;YACpB,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YACnD,SAAS;QACb,CAAC;QACD,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,IAAI,cAAc,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;AAC1D,CAAC;AAED,SAAgB,oBAAoB,CAAC,MAAc;IAC/C,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACtC,OAAO,CAAC,OAAe,EAAE,QAAgB,EAAW,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACnG,CAAC","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,3 @@
1
+ import { ResolvedConfig } from './types';
2
+ export declare function findConfigFile(startDir: string): string | null;
3
+ export declare function loadConfig(cwd: string): ResolvedConfig;
@@ -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,3 @@
1
+ import type { Rule, ResolvedConfig } from './types';
2
+ export declare function loadRules(config: ResolvedConfig, workspaceRoot: string): readonly Rule[];
3
+ export declare function globMatches(pattern: string, filePath: string): boolean;
@@ -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,2 @@
1
+ import type { ToolKind, NormalizedToolInput, BlockedResult } from './types';
2
+ export declare function logRejection(toolKind: ToolKind, input: NormalizedToolInput, result: BlockedResult, cwd: string): void;
@@ -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":"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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;
@@ -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"]}
@@ -0,0 +1,3 @@
1
+ import type { EditRule } from '../types';
2
+ declare const catchErrorPatternRule: EditRule;
3
+ export default catchErrorPatternRule;