@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
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @webpieces/ai-hooks
2
+
3
+ Pluggable write-time validation framework for AI coding agents.
4
+
5
+ **Status: under construction.** See the plan file for the full design.
6
+
7
+ ## What it does
8
+
9
+ Intercepts AI file writes before they happen. Runs a configurable rule set against the proposed content. Rejects writes that violate rules with an educational message the AI can use to fix its own output, instead of waiting for a build to catch the problem.
10
+
11
+ ## Harnesses supported
12
+
13
+ - **Claude Code** — via `PreToolUse` hook in `.claude/settings.json`
14
+ - **openclaw** — via `before_tool_call` plugin hook
15
+
16
+ Both share the same rules and the same `webpieces.ai-hooks.json` config file.
17
+
18
+ ## Install (Claude Code, per project)
19
+
20
+ ```bash
21
+ npm install --save-dev @webpieces/dev-config # pulls in ai-hooks transitively
22
+ npx wp-setup-ai-hooks
23
+ # Restart your Claude Code session
24
+ ```
25
+
26
+ ## Install (openclaw, per user)
27
+
28
+ ```bash
29
+ openclaw plugins install @webpieces/ai-hooks
30
+ openclaw plugins enable @webpieces/ai-hooks
31
+ # Drop webpieces.ai-hooks.json into any project you want checked
32
+ ```
33
+
34
+ ## Starter rules
35
+
36
+ - `no-any` — disallow the `any` keyword
37
+ - `max-file-lines` — cap file length
38
+ - `file-location` — every `.ts` must belong to a project's `src/`
39
+ - `no-destructure` — use explicit property access
40
+ - `require-return-type` — every function declares its return type
41
+ - `no-unmanaged-exceptions` — `try/catch` requires an explicit disable comment
42
+
43
+ See `webpieces.ai-hooks.json` at your project root to toggle rules or tune options.
@@ -0,0 +1,137 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # setup-ai-hooks.sh — Wires the @webpieces/ai-hooks framework into a project.
5
+ #
6
+ # For Claude Code: creates .webpieces/ai-hooks/claude-code-hook.js bootstrap,
7
+ # seeds webpieces.ai-hooks.json, and merges .claude/settings.json.
8
+ #
9
+ # Usage:
10
+ # npx wp-setup-ai-hooks
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+
14
+ # Detect workspace vs consumer
15
+ if [[ "$SCRIPT_DIR" == *"node_modules/@webpieces/ai-hooks"* ]]; then
16
+ # Running in consumer project (from node_modules)
17
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
18
+ AI_HOOKS_PKG="@webpieces/ai-hooks"
19
+ ADAPTER_REQUIRE="require('${AI_HOOKS_PKG}/claude-code').main();"
20
+ TEMPLATES_DIR="$SCRIPT_DIR/../templates"
21
+ else
22
+ # Running in webpieces-ts workspace
23
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
24
+ ADAPTER_REQUIRE="require('${PROJECT_ROOT}/dist/packages/tooling/ai-hooks/src/adapters/claude-code-hook').main();"
25
+ TEMPLATES_DIR="$SCRIPT_DIR/../templates"
26
+ fi
27
+
28
+ cd "$PROJECT_ROOT" || exit 1
29
+
30
+ echo ""
31
+ echo "🔧 Setting up @webpieces/ai-hooks..."
32
+ echo " Project root: $PROJECT_ROOT"
33
+ echo ""
34
+
35
+ # 1. Create .webpieces/ai-hooks/
36
+ mkdir -p .webpieces/ai-hooks
37
+
38
+ # 2. Generate the bootstrap
39
+ BOOTSTRAP=".webpieces/ai-hooks/claude-code-hook.js"
40
+ cat > "$BOOTSTRAP" <<JSEOF
41
+ #!/usr/bin/env node
42
+ ${ADAPTER_REQUIRE}
43
+ JSEOF
44
+ chmod +x "$BOOTSTRAP"
45
+ echo " ✅ Created $BOOTSTRAP"
46
+
47
+ # 3. Seed webpieces.ai-hooks.json if missing
48
+ if [ ! -f "webpieces.ai-hooks.json" ]; then
49
+ cp "$TEMPLATES_DIR/webpieces.ai-hooks.seed.json" "webpieces.ai-hooks.json"
50
+ echo " ✅ Created webpieces.ai-hooks.json (default config)"
51
+ else
52
+ echo " ℹ️ webpieces.ai-hooks.json already exists (keeping yours)"
53
+ fi
54
+
55
+ # 4. Add .webpieces/ to .gitignore if missing
56
+ if [ -f ".gitignore" ]; then
57
+ if ! grep -q "^\.webpieces/" ".gitignore" 2>/dev/null; then
58
+ echo "" >> .gitignore
59
+ echo "# Generated @webpieces/ai-hooks artifacts" >> .gitignore
60
+ echo ".webpieces/" >> .gitignore
61
+ echo " ✅ Added .webpieces/ to .gitignore"
62
+ fi
63
+ else
64
+ echo ".webpieces/" > .gitignore
65
+ echo " ✅ Created .gitignore with .webpieces/"
66
+ fi
67
+
68
+ # 5. Create or merge .claude/settings.json
69
+ mkdir -p .claude
70
+
71
+ SETTINGS=".claude/settings.json"
72
+ HOOK_COMMAND="node .webpieces/ai-hooks/claude-code-hook.js"
73
+
74
+ if [ ! -f "$SETTINGS" ]; then
75
+ # Fresh settings file
76
+ cat > "$SETTINGS" <<JSONEOF
77
+ {
78
+ "hooks": {
79
+ "PreToolUse": [
80
+ {
81
+ "matcher": "Write|Edit|MultiEdit",
82
+ "hooks": [
83
+ {
84
+ "type": "command",
85
+ "command": "${HOOK_COMMAND}"
86
+ }
87
+ ]
88
+ }
89
+ ]
90
+ }
91
+ }
92
+ JSONEOF
93
+ echo " ✅ Created $SETTINGS with PreToolUse hook"
94
+ else
95
+ # Settings exist — check if hook is already wired
96
+ if grep -q "claude-code-hook.js" "$SETTINGS" 2>/dev/null; then
97
+ echo " ℹ️ $SETTINGS already has the ai-hooks hook wired"
98
+ else
99
+ echo ""
100
+ echo " ⚠️ $SETTINGS exists but doesn't have the ai-hooks hook."
101
+ echo " Please manually add this to your hooks.PreToolUse array:"
102
+ echo ""
103
+ echo ' {'
104
+ echo ' "matcher": "Write|Edit|MultiEdit",'
105
+ echo ' "hooks": [{'
106
+ echo ' "type": "command",'
107
+ echo " \"command\": \"${HOOK_COMMAND}\""
108
+ echo ' }]'
109
+ echo ' }'
110
+ echo ""
111
+ fi
112
+ fi
113
+
114
+ # 6. Smoke test
115
+ echo ""
116
+ echo "🧪 Running smoke test..."
117
+ SMOKE_RESULT=$(echo '{"tool_name":"Write","tool_input":{"file_path":"'${PROJECT_ROOT}'/x.ts","content":"const x: any = 1;"}}' | node "$BOOTSTRAP" 2>&1; echo "EXIT:$?")
118
+ SMOKE_EXIT=$(echo "$SMOKE_RESULT" | grep "EXIT:" | sed 's/EXIT://')
119
+
120
+ if [ "$SMOKE_EXIT" = "2" ]; then
121
+ echo " ✅ Smoke test passed (exit 2 = correctly blocked)"
122
+ else
123
+ echo " ⚠️ Smoke test returned exit $SMOKE_EXIT (expected 2). The hook may not be working."
124
+ echo " Check that the project built successfully: nx build ai-hooks"
125
+ fi
126
+
127
+ echo ""
128
+ echo "🎉 Setup complete!"
129
+ echo ""
130
+ echo " Claude Code: restart your session for the hook to activate."
131
+ echo " Edit webpieces.ai-hooks.json to toggle rules or tune options."
132
+ echo ""
133
+ echo " Openclaw: install globally with:"
134
+ echo " openclaw plugins install @webpieces/ai-hooks"
135
+ echo " openclaw plugins enable @webpieces/ai-hooks"
136
+ echo " Then drop webpieces.ai-hooks.json into any project."
137
+ echo ""
@@ -0,0 +1,15 @@
1
+ {
2
+ "id": "webpieces-ai-hooks",
3
+ "name": "webpieces AI Hooks",
4
+ "description": "Pre-write validation rules for AI-driven development. Blocks writes containing any, missing return types, destructuring, unmanaged exceptions, orphan files, or oversized files.",
5
+ "hooks": {
6
+ "before_tool_call": {
7
+ "handler": "./src/adapters/openclaw-plugin.js"
8
+ }
9
+ },
10
+ "configSchema": {
11
+ "type": "object",
12
+ "additionalProperties": false,
13
+ "properties": {}
14
+ }
15
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@webpieces/ai-hooks",
3
+ "version": "0.0.1",
4
+ "description": "Pluggable write-time validation framework for AI coding agents. Claude Code PreToolUse + openclaw before_tool_call adapters share one rule engine.",
5
+ "type": "commonjs",
6
+ "main": "./src/index.js",
7
+ "bin": {
8
+ "wp-setup-ai-hooks": "./bin/setup-ai-hooks.sh"
9
+ },
10
+ "exports": {
11
+ ".": "./src/index.js",
12
+ "./package.json": "./package.json",
13
+ "./claude-code": "./src/adapters/claude-code-hook.js",
14
+ "./openclaw-plugin": "./src/adapters/openclaw-plugin.js",
15
+ "./default": "./src/core/configs/default.js"
16
+ },
17
+ "files": [
18
+ "src/**/*",
19
+ "bin/**/*",
20
+ "templates/**/*",
21
+ "openclaw.plugin.json",
22
+ "README.md"
23
+ ],
24
+ "author": "Dean Hiller",
25
+ "license": "Apache-2.0",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/deanhiller/webpieces-ts.git",
29
+ "directory": "packages/tooling/ai-hooks"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "types": "./src/index.d.ts"
35
+ }
@@ -0,0 +1 @@
1
+ export declare function main(): Promise<void>;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ const runner_1 = require("../core/runner");
5
+ const rejection_log_1 = require("../core/rejection-log");
6
+ const types_1 = require("../core/types");
7
+ const to_error_1 = require("../core/to-error");
8
+ const HANDLED_TOOLS = new Set(['Write', 'Edit', 'MultiEdit']);
9
+ function readStdin() {
10
+ return new Promise((resolve) => {
11
+ let data = '';
12
+ process.stdin.setEncoding('utf8');
13
+ process.stdin.on('data', (chunk) => { data += chunk; });
14
+ process.stdin.on('end', () => resolve(data));
15
+ process.stdin.on('error', () => resolve(''));
16
+ if (process.stdin.isTTY)
17
+ resolve('');
18
+ });
19
+ }
20
+ function safeParse(raw) {
21
+ if (!raw || raw.trim() === '')
22
+ return null;
23
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
24
+ try {
25
+ return JSON.parse(raw);
26
+ }
27
+ catch (err) {
28
+ const error = (0, to_error_1.toError)(err);
29
+ void error;
30
+ return null;
31
+ }
32
+ }
33
+ function normalizeToolKind(toolName) {
34
+ if (HANDLED_TOOLS.has(toolName))
35
+ return toolName;
36
+ return null;
37
+ }
38
+ function normalizeToolInput(toolKind, toolInput) {
39
+ const filePath = toolInput.file_path;
40
+ if (!filePath)
41
+ return null;
42
+ if (toolKind === 'Write') {
43
+ return new types_1.NormalizedToolInput(filePath, [
44
+ new types_1.NormalizedEdit('', toolInput.content || ''),
45
+ ]);
46
+ }
47
+ if (toolKind === 'Edit') {
48
+ return new types_1.NormalizedToolInput(filePath, [
49
+ new types_1.NormalizedEdit(toolInput.old_string || '', toolInput.new_string || ''),
50
+ ]);
51
+ }
52
+ if (toolKind === 'MultiEdit') {
53
+ const raw = Array.isArray(toolInput.edits) ? toolInput.edits : [];
54
+ const edits = raw.map((e) => new types_1.NormalizedEdit(e.old_string || '', e.new_string || ''));
55
+ return new types_1.NormalizedToolInput(filePath, edits);
56
+ }
57
+ return null;
58
+ }
59
+ async function main() {
60
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
61
+ try {
62
+ const raw = await readStdin();
63
+ const payload = safeParse(raw);
64
+ if (!payload) {
65
+ process.exit(0);
66
+ return;
67
+ }
68
+ const toolKind = normalizeToolKind(payload.tool_name);
69
+ if (!toolKind) {
70
+ process.exit(0);
71
+ return;
72
+ }
73
+ const input = normalizeToolInput(toolKind, payload.tool_input);
74
+ if (!input) {
75
+ process.exit(0);
76
+ return;
77
+ }
78
+ const cwd = process.cwd();
79
+ const result = (0, runner_1.run)(toolKind, input, cwd);
80
+ if (!result) {
81
+ process.exit(0);
82
+ return;
83
+ }
84
+ (0, rejection_log_1.logRejection)(toolKind, input, result, cwd);
85
+ process.stderr.write(result.report);
86
+ process.exit(2);
87
+ }
88
+ catch (err) {
89
+ const error = (0, to_error_1.toError)(err);
90
+ process.stderr.write(`[ai-hooks] claude-code adapter crashed (failing open): ${error.message}\n`);
91
+ process.exit(0);
92
+ }
93
+ }
94
+ if (require.main === module) {
95
+ main();
96
+ }
97
+ //# sourceMappingURL=claude-code-hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-code-hook.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hooks/src/adapters/claude-code-hook.ts"],"names":[],"mappings":"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","sourcesContent":["import { run } from '../core/runner';\nimport { logRejection } from '../core/rejection-log';\nimport { NormalizedToolInput, NormalizedEdit, ToolKind } from '../core/types';\nimport { toError } from '../core/to-error';\n\nconst HANDLED_TOOLS = new Set(['Write', 'Edit', 'MultiEdit']);\n\ninterface ClaudeCodePayload {\n tool_name: string;\n tool_input: ClaudeCodeToolInput;\n}\n\ninterface ClaudeCodeToolInput {\n file_path?: string;\n content?: string;\n old_string?: string;\n new_string?: string;\n edits?: ClaudeCodeEditEntry[];\n}\n\ninterface ClaudeCodeEditEntry {\n old_string?: string;\n new_string?: string;\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let data = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => { data += chunk; });\n process.stdin.on('end', () => resolve(data));\n process.stdin.on('error', () => resolve(''));\n if (process.stdin.isTTY) resolve('');\n });\n}\n\nfunction safeParse(raw: string): ClaudeCodePayload | null {\n if (!raw || raw.trim() === '') return null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(raw) as ClaudeCodePayload;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n}\n\nfunction normalizeToolKind(toolName: string): ToolKind | null {\n if (HANDLED_TOOLS.has(toolName)) return toolName as ToolKind;\n return null;\n}\n\nfunction normalizeToolInput(toolKind: ToolKind, toolInput: ClaudeCodeToolInput): NormalizedToolInput | null {\n const filePath = toolInput.file_path;\n if (!filePath) return null;\n\n if (toolKind === 'Write') {\n return new NormalizedToolInput(filePath, [\n new NormalizedEdit('', toolInput.content || ''),\n ]);\n }\n if (toolKind === 'Edit') {\n return new NormalizedToolInput(filePath, [\n new NormalizedEdit(toolInput.old_string || '', toolInput.new_string || ''),\n ]);\n }\n if (toolKind === 'MultiEdit') {\n const raw = Array.isArray(toolInput.edits) ? toolInput.edits : [];\n const edits = raw.map((e) => new NormalizedEdit(e.old_string || '', e.new_string || ''));\n return new NormalizedToolInput(filePath, edits);\n }\n return null;\n}\n\nexport async function main(): Promise<void> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = await readStdin();\n const payload = safeParse(raw);\n if (!payload) { process.exit(0); return; }\n\n const toolKind = normalizeToolKind(payload.tool_name);\n if (!toolKind) { process.exit(0); return; }\n\n const input = normalizeToolInput(toolKind, payload.tool_input);\n if (!input) { process.exit(0); return; }\n\n const cwd = process.cwd();\n const result = run(toolKind, input, cwd);\n if (!result) { process.exit(0); return; }\n\n logRejection(toolKind, input, result, cwd);\n process.stderr.write(result.report);\n process.exit(2);\n } catch (err: unknown) {\n const error = toError(err);\n process.stderr.write(`[ai-hooks] claude-code adapter crashed (failing open): ${error.message}\\n`);\n process.exit(0);\n }\n}\n\nif (require.main === module) {\n main();\n}\n"]}
@@ -0,0 +1,14 @@
1
+ interface ToolCallEvent {
2
+ toolName: string;
3
+ arguments: Record<string, unknown>;
4
+ }
5
+ interface HookContext {
6
+ [key: string]: unknown;
7
+ }
8
+ declare class OpenclawHandlerResult {
9
+ readonly status: 'approved' | 'rejected';
10
+ readonly reason: string | undefined;
11
+ constructor(status: 'approved' | 'rejected', reason?: string);
12
+ }
13
+ export default function handler(event: ToolCallEvent, _context: HookContext): Promise<OpenclawHandlerResult | undefined>;
14
+ export {};
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = handler;
4
+ const tslib_1 = require("tslib");
5
+ const path = tslib_1.__importStar(require("path"));
6
+ const fs = tslib_1.__importStar(require("fs"));
7
+ const runner_1 = require("../core/runner");
8
+ const types_1 = require("../core/types");
9
+ const to_error_1 = require("../core/to-error");
10
+ class OpenclawHandlerResult {
11
+ constructor(status, reason) {
12
+ this.status = status;
13
+ this.reason = reason;
14
+ }
15
+ }
16
+ const TOOL_MAP = {
17
+ 'write': 'Write',
18
+ 'edit': 'Edit',
19
+ };
20
+ function mapToolName(openclawName) {
21
+ return TOOL_MAP[openclawName] || null;
22
+ }
23
+ // webpieces-disable no-any-unknown -- openclaw SDK passes opaque tool arguments
24
+ function mapToolInput(toolName, args) {
25
+ const filePath = typeof args['path'] === 'string' ? args['path'] : null;
26
+ if (!filePath)
27
+ return null;
28
+ if (toolName === 'write') {
29
+ const content = typeof args['content'] === 'string' ? args['content'] : '';
30
+ return new types_1.NormalizedToolInput(filePath, [new types_1.NormalizedEdit('', content)]);
31
+ }
32
+ if (toolName === 'edit') {
33
+ const oldStr = typeof args['old_string'] === 'string' ? args['old_string'] : '';
34
+ const newStr = typeof args['new_string'] === 'string' ? args['new_string'] : '';
35
+ return new types_1.NormalizedToolInput(filePath, [new types_1.NormalizedEdit(oldStr, newStr)]);
36
+ }
37
+ return null;
38
+ }
39
+ function findWorkspaceRoot(filePath) {
40
+ let dir = path.dirname(filePath);
41
+ while (true) {
42
+ if (fs.existsSync(path.join(dir, 'webpieces.ai-hooks.json')))
43
+ return dir;
44
+ const parent = path.dirname(dir);
45
+ if (parent === dir)
46
+ return null;
47
+ dir = parent;
48
+ }
49
+ }
50
+ async function handler(event, _context) {
51
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
52
+ try {
53
+ const toolKind = mapToolName(event.toolName);
54
+ if (!toolKind)
55
+ return undefined;
56
+ const input = mapToolInput(event.toolName, event.arguments);
57
+ if (!input)
58
+ return undefined;
59
+ const wsRoot = findWorkspaceRoot(input.filePath);
60
+ if (!wsRoot)
61
+ return undefined;
62
+ const result = (0, runner_1.run)(toolKind, input, wsRoot);
63
+ if (!result)
64
+ return new OpenclawHandlerResult('approved');
65
+ return new OpenclawHandlerResult('rejected', result.report);
66
+ }
67
+ catch (err) {
68
+ const error = (0, to_error_1.toError)(err);
69
+ console.error(`[ai-hooks] openclaw adapter crashed (failing open): ${error.message}`);
70
+ return undefined;
71
+ }
72
+ }
73
+ //# sourceMappingURL=openclaw-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openclaw-plugin.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hooks/src/adapters/openclaw-plugin.ts"],"names":[],"mappings":";;AAgEA,0BAuBC;;AAvFD,mDAA6B;AAC7B,+CAAyB;AAEzB,2CAAqC;AACrC,yCAA8E;AAC9E,+CAA2C;AAa3C,MAAM,qBAAqB;IAIvB,YAAY,MAA+B,EAAE,MAAe;QACxD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC;CACJ;AAED,MAAM,QAAQ,GAA6B;IACvC,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE,MAAM;CACjB,CAAC;AAEF,SAAS,WAAW,CAAC,YAAoB;IACrC,OAAO,QAAQ,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC;AAC1C,CAAC;AAED,gFAAgF;AAChF,SAAS,YAAY,CAAC,QAAgB,EAAE,IAA6B;IACjE,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAW,CAAC,CAAC,CAAC,IAAI,CAAC;IAClF,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QACrF,OAAO,IAAI,2BAAmB,CAAC,QAAQ,EAAE,CAAC,IAAI,sBAAc,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1F,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1F,OAAO,IAAI,2BAAmB,CAAC,QAAQ,EAAE,CAAC,IAAI,sBAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAgB;IACvC,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjC,OAAO,IAAI,EAAE,CAAC;QACV,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QACzE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAChC,GAAG,GAAG,MAAM,CAAC;IACjB,CAAC;AACL,CAAC;AAEc,KAAK,UAAU,OAAO,CACjC,KAAoB,EACpB,QAAqB;IAErB,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAC;QAEhC,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAE7B,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM;YAAE,OAAO,SAAS,CAAC;QAE9B,MAAM,MAAM,GAAG,IAAA,YAAG,EAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,qBAAqB,CAAC,UAAU,CAAC,CAAC;QAC1D,OAAO,IAAI,qBAAqB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAChE,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,uDAAuD,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACtF,OAAO,SAAS,CAAC;IACrB,CAAC;AACL,CAAC","sourcesContent":["import * as path from 'path';\nimport * as fs from 'fs';\n\nimport { run } from '../core/runner';\nimport { NormalizedToolInput, NormalizedEdit, ToolKind } from '../core/types';\nimport { toError } from '../core/to-error';\n\ninterface ToolCallEvent {\n toolName: string;\n // webpieces-disable no-any-unknown -- openclaw SDK passes opaque tool arguments\n arguments: Record<string, unknown>;\n}\n\ninterface HookContext {\n // webpieces-disable no-any-unknown -- openclaw SDK context shape is opaque\n [key: string]: unknown;\n}\n\nclass OpenclawHandlerResult {\n readonly status: 'approved' | 'rejected';\n readonly reason: string | undefined;\n\n constructor(status: 'approved' | 'rejected', reason?: string) {\n this.status = status;\n this.reason = reason;\n }\n}\n\nconst TOOL_MAP: Record<string, ToolKind> = {\n 'write': 'Write',\n 'edit': 'Edit',\n};\n\nfunction mapToolName(openclawName: string): ToolKind | null {\n return TOOL_MAP[openclawName] || null;\n}\n\n// webpieces-disable no-any-unknown -- openclaw SDK passes opaque tool arguments\nfunction mapToolInput(toolName: string, args: Record<string, unknown>): NormalizedToolInput | null {\n const filePath = typeof args['path'] === 'string' ? args['path'] as string : null;\n if (!filePath) return null;\n\n if (toolName === 'write') {\n const content = typeof args['content'] === 'string' ? args['content'] as string : '';\n return new NormalizedToolInput(filePath, [new NormalizedEdit('', content)]);\n }\n if (toolName === 'edit') {\n const oldStr = typeof args['old_string'] === 'string' ? args['old_string'] as string : '';\n const newStr = typeof args['new_string'] === 'string' ? args['new_string'] as string : '';\n return new NormalizedToolInput(filePath, [new NormalizedEdit(oldStr, newStr)]);\n }\n return null;\n}\n\nfunction findWorkspaceRoot(filePath: string): string | null {\n let dir = path.dirname(filePath);\n while (true) {\n if (fs.existsSync(path.join(dir, 'webpieces.ai-hooks.json'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nexport default async function handler(\n event: ToolCallEvent,\n _context: HookContext,\n): Promise<OpenclawHandlerResult | undefined> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const toolKind = mapToolName(event.toolName);\n if (!toolKind) return undefined;\n\n const input = mapToolInput(event.toolName, event.arguments);\n if (!input) return undefined;\n\n const wsRoot = findWorkspaceRoot(input.filePath);\n if (!wsRoot) return undefined;\n\n const result = run(toolKind, input, wsRoot);\n if (!result) return new OpenclawHandlerResult('approved');\n return new OpenclawHandlerResult('rejected', result.report);\n } catch (err: unknown) {\n const error = toError(err);\n console.error(`[ai-hooks] openclaw adapter crashed (failing open): ${error.message}`);\n return undefined;\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import { ToolKind, NormalizedToolInput, EditContext, FileContext } from './types';
2
+ export declare class BuiltContexts {
3
+ readonly fileContext: FileContext;
4
+ readonly editContexts: readonly EditContext[];
5
+ constructor(fileContext: FileContext, editContexts: readonly EditContext[]);
6
+ }
7
+ export declare function buildContexts(toolKind: ToolKind, input: NormalizedToolInput, workspaceRoot: string): BuiltContexts;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BuiltContexts = void 0;
4
+ exports.buildContexts = buildContexts;
5
+ const tslib_1 = require("tslib");
6
+ const fs = tslib_1.__importStar(require("fs"));
7
+ const path = tslib_1.__importStar(require("path"));
8
+ const strip_ts_noise_1 = require("./strip-ts-noise");
9
+ const disable_directives_1 = require("./disable-directives");
10
+ const types_1 = require("./types");
11
+ class BuiltContexts {
12
+ constructor(fileContext, editContexts) {
13
+ this.fileContext = fileContext;
14
+ this.editContexts = editContexts;
15
+ }
16
+ }
17
+ exports.BuiltContexts = BuiltContexts;
18
+ function buildContexts(toolKind, input, workspaceRoot) {
19
+ const filePath = input.filePath;
20
+ const relativePath = path.relative(workspaceRoot, filePath);
21
+ const edits = input.edits;
22
+ const currentFileLines = readCurrentFileLines(filePath);
23
+ let linesAdded = 0;
24
+ let linesRemoved = 0;
25
+ for (const e of edits) {
26
+ linesAdded += countLines(e.newString);
27
+ linesRemoved += countLines(e.oldString);
28
+ }
29
+ const projectedFileLines = toolKind === 'Write'
30
+ ? countLines(edits.length > 0 ? edits[0].newString : '')
31
+ : currentFileLines + linesAdded - linesRemoved;
32
+ const fileContext = new types_1.FileContext(toolKind, filePath, relativePath, workspaceRoot, currentFileLines, linesAdded, linesRemoved, projectedFileLines);
33
+ const editContexts = edits.map((e, idx) => {
34
+ const addedContent = e.newString;
35
+ const stripped = (0, strip_ts_noise_1.stripTsNoise)(addedContent);
36
+ const isLineDisabled = (0, disable_directives_1.createIsLineDisabled)(addedContent);
37
+ return new types_1.EditContext(toolKind, idx, edits.length, filePath, relativePath, workspaceRoot, addedContent, stripped, addedContent.split('\n'), stripped.split('\n'), e.oldString, isLineDisabled);
38
+ });
39
+ return new BuiltContexts(fileContext, editContexts);
40
+ }
41
+ function readCurrentFileLines(filePath) {
42
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
43
+ try {
44
+ const content = fs.readFileSync(filePath, 'utf8');
45
+ return countLines(content);
46
+ }
47
+ catch (err) {
48
+ // eslint-disable-next-line @webpieces/catch-error-pattern -- file-not-found is expected for new Write targets
49
+ void err;
50
+ return 0;
51
+ }
52
+ }
53
+ function countLines(s) {
54
+ if (!s)
55
+ return 0;
56
+ return s.split('\n').length;
57
+ }
58
+ //# sourceMappingURL=build-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-context.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hooks/src/core/build-context.ts"],"names":[],"mappings":";;;AAoBA,sCAsDC;;AA1ED,+CAAyB;AACzB,mDAA6B;AAE7B,qDAAgD;AAChD,6DAA4D;AAC5D,mCAGiB;AAEjB,MAAa,aAAa;IAItB,YAAY,WAAwB,EAAE,YAAoC;QACtE,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACrC,CAAC;CACJ;AARD,sCAQC;AAED,SAAgB,aAAa,CACzB,QAAkB,EAClB,KAA0B,EAC1B,aAAqB;IAErB,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;IAChC,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAE1B,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACxD,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACpB,UAAU,IAAI,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACtC,YAAY,IAAI,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,kBAAkB,GACpB,QAAQ,KAAK,OAAO;QAChB,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,CAAC,CAAC,gBAAgB,GAAG,UAAU,GAAG,YAAY,CAAC;IAEvD,MAAM,WAAW,GAAG,IAAI,mBAAW,CAC/B,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,YAAY,EACZ,kBAAkB,CACrB,CAAC;IAEF,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE;QACtC,MAAM,YAAY,GAAG,CAAC,CAAC,SAAS,CAAC;QACjC,MAAM,QAAQ,GAAG,IAAA,6BAAY,EAAC,YAAY,CAAC,CAAC;QAC5C,MAAM,cAAc,GAAG,IAAA,yCAAoB,EAAC,YAAY,CAAC,CAAC;QAC1D,OAAO,IAAI,mBAAW,CAClB,QAAQ,EACR,GAAG,EACH,KAAK,CAAC,MAAM,EACZ,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,QAAQ,EACR,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,EACxB,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EACpB,CAAC,CAAC,SAAS,EACX,cAAc,CACjB,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,OAAO,IAAI,aAAa,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,oBAAoB,CAAC,QAAgB;IAC1C,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAClD,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,8GAA8G;QAC9G,KAAK,GAAG,CAAC;QACT,OAAO,CAAC,CAAC;IACb,CAAC;AACL,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IACzB,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACjB,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;AAChC,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { stripTsNoise } from './strip-ts-noise';\nimport { createIsLineDisabled } from './disable-directives';\nimport {\n ToolKind, NormalizedToolInput, NormalizedEdit,\n EditContext, FileContext,\n} from './types';\n\nexport class BuiltContexts {\n readonly fileContext: FileContext;\n readonly editContexts: readonly EditContext[];\n\n constructor(fileContext: FileContext, editContexts: readonly EditContext[]) {\n this.fileContext = fileContext;\n this.editContexts = editContexts;\n }\n}\n\nexport function buildContexts(\n toolKind: ToolKind,\n input: NormalizedToolInput,\n workspaceRoot: string,\n): BuiltContexts {\n const filePath = input.filePath;\n const relativePath = path.relative(workspaceRoot, filePath);\n const edits = input.edits;\n\n const currentFileLines = readCurrentFileLines(filePath);\n let linesAdded = 0;\n let linesRemoved = 0;\n for (const e of edits) {\n linesAdded += countLines(e.newString);\n linesRemoved += countLines(e.oldString);\n }\n\n const projectedFileLines =\n toolKind === 'Write'\n ? countLines(edits.length > 0 ? edits[0].newString : '')\n : currentFileLines + linesAdded - linesRemoved;\n\n const fileContext = new FileContext(\n toolKind,\n filePath,\n relativePath,\n workspaceRoot,\n currentFileLines,\n linesAdded,\n linesRemoved,\n projectedFileLines,\n );\n\n const editContexts = edits.map((e, idx) => {\n const addedContent = e.newString;\n const stripped = stripTsNoise(addedContent);\n const isLineDisabled = createIsLineDisabled(addedContent);\n return new EditContext(\n toolKind,\n idx,\n edits.length,\n filePath,\n relativePath,\n workspaceRoot,\n addedContent,\n stripped,\n addedContent.split('\\n'),\n stripped.split('\\n'),\n e.oldString,\n isLineDisabled,\n );\n });\n\n return new BuiltContexts(fileContext, editContexts);\n}\n\nfunction readCurrentFileLines(filePath: string): number {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const content = fs.readFileSync(filePath, 'utf8');\n return countLines(content);\n } catch (err: unknown) {\n // eslint-disable-next-line @webpieces/catch-error-pattern -- file-not-found is expected for new Write targets\n void err;\n return 0;\n }\n}\n\nfunction countLines(s: string): number {\n if (!s) return 0;\n return s.split('\\n').length;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export declare const defaultRules: Record<string, Record<string, unknown>>;
2
+ export declare const defaultRulesDir: readonly string[];
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultRulesDir = exports.defaultRules = void 0;
4
+ // webpieces-disable no-any-unknown -- rule options are opaque at framework level
5
+ exports.defaultRules = {
6
+ 'no-any-unknown': { enabled: true },
7
+ 'max-file-lines': { enabled: true, limit: 900 },
8
+ 'file-location': {
9
+ enabled: true,
10
+ allowedRootFiles: ['jest.setup.ts'],
11
+ excludePaths: [
12
+ 'node_modules', 'dist', '.nx', '.git',
13
+ 'architecture', 'tmp', 'scripts',
14
+ ],
15
+ },
16
+ 'no-destructure': { enabled: true, allowTopLevel: true },
17
+ 'require-return-type': { enabled: true },
18
+ 'no-unmanaged-exceptions': { enabled: true },
19
+ 'catch-error-pattern': { enabled: true },
20
+ };
21
+ exports.defaultRulesDir = [];
22
+ //# sourceMappingURL=default.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hooks/src/core/configs/default.ts"],"names":[],"mappings":";;;AAAA,iFAAiF;AACpE,QAAA,YAAY,GAA4C;IACjE,gBAAgB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;IACnC,gBAAgB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE;IAC/C,eAAe,EAAE;QACb,OAAO,EAAE,IAAI;QACb,gBAAgB,EAAE,CAAC,eAAe,CAAC;QACnC,YAAY,EAAE;YACV,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;YACrC,cAAc,EAAE,KAAK,EAAE,SAAS;SACnC;KACJ;IACD,gBAAgB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;IACxD,qBAAqB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;IACxC,yBAAyB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;IAC5C,qBAAqB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;CAC3C,CAAC;AAEW,QAAA,eAAe,GAAsB,EAAE,CAAC","sourcesContent":["// webpieces-disable no-any-unknown -- rule options are opaque at framework level\nexport const defaultRules: Record<string, Record<string, unknown>> = {\n 'no-any-unknown': { enabled: true },\n 'max-file-lines': { enabled: true, limit: 900 },\n 'file-location': {\n enabled: true,\n allowedRootFiles: ['jest.setup.ts'],\n excludePaths: [\n 'node_modules', 'dist', '.nx', '.git',\n 'architecture', 'tmp', 'scripts',\n ],\n },\n 'no-destructure': { enabled: true, allowTopLevel: true },\n 'require-return-type': { enabled: true },\n 'no-unmanaged-exceptions': { enabled: true },\n 'catch-error-pattern': { enabled: true },\n};\n\nexport const defaultRulesDir: readonly string[] = [];\n"]}
@@ -0,0 +1,9 @@
1
+ import type { IsLineDisabled } from './types';
2
+ export declare class DirectiveIndex {
3
+ private readonly lineDisables;
4
+ private readonly fileDisables;
5
+ constructor(lineDisables: Map<number, Set<string>>, fileDisables: Set<string>);
6
+ isLineDisabled(lineNum: number, ruleName: string): boolean;
7
+ }
8
+ export declare function parseDirectives(source: string): DirectiveIndex;
9
+ export declare function createIsLineDisabled(source: string): IsLineDisabled;