NagLint hook setup
This commit is contained in:
parent
523c908b20
commit
0268422c81
77
.claude/hooks/naglint.sh
Executable file
77
.claude/hooks/naglint.sh
Executable 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
15
.claude/settings.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/Users/matsbosson/Documents/StayThree/PettyUI/.claude/hooks/naglint.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -14,27 +14,27 @@ export interface Dismiss {
|
||||
detach: () => void;
|
||||
}
|
||||
|
||||
// Global stack of active dismiss handlers (topmost is last)
|
||||
const layerStack: Dismiss[] = [];
|
||||
|
||||
/**
|
||||
* Handles dismiss interactions: Escape key and pointer-outside.
|
||||
* 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 {
|
||||
const dismissOnEscape = options.dismissOnEscape ?? true;
|
||||
const dismissOnPointerOutside = options.dismissOnPointerOutside ?? true;
|
||||
|
||||
/** Dismisses on Escape if this is the topmost layer and escape is enabled. */
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (!dismissOnEscape) return;
|
||||
if (e.key !== "Escape") return;
|
||||
// Only dismiss the topmost layer
|
||||
if (layerStack[layerStack.length - 1] !== dismiss) return;
|
||||
e.preventDefault();
|
||||
options.onDismiss();
|
||||
};
|
||||
|
||||
/** Dismisses on pointer-outside if this is the topmost layer and pointer-outside is enabled. */
|
||||
const handlePointerDown = (e: PointerEvent) => {
|
||||
if (!dismissOnPointerOutside) return;
|
||||
if (layerStack[layerStack.length - 1] !== dismiss) return;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user