NagLint hook setup

This commit is contained in:
Mats Bosson 2026-03-29 05:18:07 +07:00
parent 523c908b20
commit 0268422c81
3 changed files with 97 additions and 5 deletions

77
.claude/hooks/naglint.sh Executable file
View File

@ -0,0 +1,77 @@
#!/bin/bash
# NagLint PostToolUse hook — runs after Write/Edit on .ts/.tsx/.js/.jsx files
# Reads tool_use JSON from stdin, extracts file_path, runs nag --json
NAG=/Users/matsbosson/Documents/StayThree/NagLint/target/release/nag
INPUT=$(cat -)
FILE=$(echo "$INPUT" | python3 -c "
import sys, json
d = json.load(sys.stdin)
print(d.get('tool_input', {}).get('file_path', ''))
" 2>/dev/null)
if [ -z "$FILE" ]; then
echo "{}"
exit 0
fi
# Only lint source files — skip config, json, yaml, md, etc.
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx) ;;
*) echo "{}"; exit 0 ;;
esac
if [ ! -f "$FILE" ]; then
echo "{}"
exit 0
fi
RAW=$("$NAG" "$FILE" --json 2>/dev/null)
if [ "$RAW" = "{}" ] || [ -z "$RAW" ]; then
echo "{}"
exit 0
fi
# For .tsx/.jsx files: filter out AI-08 (logic density) — JSX is declarative
# and the 30% threshold doesn't apply to component files.
case "$FILE" in
*.tsx|*.jsx)
FILTERED=$(echo "$RAW" | python3 -c "
import sys, json
raw = sys.stdin.read().strip()
try:
d = json.loads(raw)
except Exception:
print(raw)
sys.exit(0)
# If no decision:block, pass through as-is
if d.get('decision') != 'block':
print(raw)
sys.exit(0)
reason = d.get('reason', '')
# Filter AI-08 lines from reason
filtered_lines = [l for l in reason.splitlines() if '[AI-08]' not in l]
filtered_reason = '\n'.join(filtered_lines).strip()
if not filtered_reason:
# All violations were AI-08 — file is clean for our purposes
print('{}')
else:
d['reason'] = filtered_reason
# Also update additionalContext
ctx = d.get('hookSpecificOutput', {}).get('additionalContext', '')
ctx_lines = [l for l in ctx.splitlines() if '[AI-08]' not in l]
d['hookSpecificOutput']['additionalContext'] = '\n'.join(ctx_lines)
print(json.dumps(d))
" 2>/dev/null)
echo "${FILTERED:-$RAW}"
;;
*)
echo "$RAW"
;;
esac

15
.claude/settings.json Normal file
View File

@ -0,0 +1,15 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "/Users/matsbosson/Documents/StayThree/PettyUI/.claude/hooks/naglint.sh"
}
]
}
]
}
}

View File

@ -14,27 +14,27 @@ export interface Dismiss {
detach: () => void; detach: () => void;
} }
// Global stack of active dismiss handlers (topmost is last)
const layerStack: Dismiss[] = [];
/** /**
* Handles dismiss interactions: Escape key and pointer-outside. * Handles dismiss interactions: Escape key and pointer-outside.
* Uses a global layer stack so nested overlays only dismiss the topmost layer. * Uses a global layer stack so nested overlays only dismiss the topmost layer.
*/ */
// Global stack of active dismiss handlers (topmost is last)
const layerStack: Dismiss[] = [];
export function createDismiss(options: CreateDismissOptions): Dismiss { export function createDismiss(options: CreateDismissOptions): Dismiss {
const dismissOnEscape = options.dismissOnEscape ?? true; const dismissOnEscape = options.dismissOnEscape ?? true;
const dismissOnPointerOutside = options.dismissOnPointerOutside ?? true; const dismissOnPointerOutside = options.dismissOnPointerOutside ?? true;
/** Dismisses on Escape if this is the topmost layer and escape is enabled. */
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
if (!dismissOnEscape) return; if (!dismissOnEscape) return;
if (e.key !== "Escape") return; if (e.key !== "Escape") return;
// Only dismiss the topmost layer
if (layerStack[layerStack.length - 1] !== dismiss) return; if (layerStack[layerStack.length - 1] !== dismiss) return;
e.preventDefault(); e.preventDefault();
options.onDismiss(); options.onDismiss();
}; };
/** Dismisses on pointer-outside if this is the topmost layer and pointer-outside is enabled. */
const handlePointerDown = (e: PointerEvent) => { const handlePointerDown = (e: PointerEvent) => {
if (!dismissOnPointerOutside) return; if (!dismissOnPointerOutside) return;
if (layerStack[layerStack.length - 1] !== dismiss) return; if (layerStack[layerStack.length - 1] !== dismiss) return;