@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
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":"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","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,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;
|