navori 0.8.4 → 0.8.5
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.
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
"src": "scripts/tgrep-session.sh",
|
|
37
37
|
"dest": "tgrep-session.sh",
|
|
38
38
|
"exec": true
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"src": "scripts/guard-search-routing.sh",
|
|
42
|
+
"dest": "guard-search-routing.sh",
|
|
43
|
+
"exec": true
|
|
39
44
|
}
|
|
40
45
|
],
|
|
41
46
|
"hooks": [
|
|
@@ -44,6 +49,13 @@
|
|
|
44
49
|
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/scripts/tgrep-session.sh\"",
|
|
45
50
|
"timeout": 30,
|
|
46
51
|
"statusMessage": "navori/tgrep: search index"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"event": "PreToolUse",
|
|
55
|
+
"matcher": "Bash",
|
|
56
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/scripts/guard-search-routing.sh\"",
|
|
57
|
+
"timeout": 10,
|
|
58
|
+
"statusMessage": "navori/tgrep: routing guard"
|
|
47
59
|
}
|
|
48
60
|
],
|
|
49
61
|
"skills": [
|
|
@@ -77,5 +89,7 @@
|
|
|
77
89
|
"injectInto": ".claude/agents/reviewer.md"
|
|
78
90
|
}
|
|
79
91
|
],
|
|
80
|
-
"invariants": [
|
|
92
|
+
"invariants": [
|
|
93
|
+
"tgrep-search.sh"
|
|
94
|
+
]
|
|
81
95
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# PreToolUse(Bash) guard: content search goes through the tgrep wrapper.
|
|
4
|
+
#
|
|
5
|
+
# WHY THIS IS A GUARD AND NOT A SENTENCE IN CLAUDE.md. The doctrine telling the
|
|
6
|
+
# model to route content search through the wrapper has shipped since 0.7.8 and
|
|
7
|
+
# is measured at 7.4% adoption over 2,761 real searches. The session that
|
|
8
|
+
# produced the best analysis of tgrep anyone has written still went through
|
|
9
|
+
# shell for 9 of every 10 searches (#668), so comprehension was never the
|
|
10
|
+
# bottleneck. In this harness the mechanical layers hold — guard-destructive 14
|
|
11
|
+
# blocks out of 14, quality-gate-pre-commit 7 out of 7 — and the advisory ones
|
|
12
|
+
# do not. This moves one doctrine across that line.
|
|
13
|
+
#
|
|
14
|
+
# WHAT IT BLOCKS, and nothing else: a segment that STARTS a command (never one
|
|
15
|
+
# after a pipe) and invokes `grep`/`egrep`/`fgrep` recursively, or `rg` with no
|
|
16
|
+
# single file target. Measured against the 8,562 real grep/rg invocations of the
|
|
17
|
+
# park: it fires on 55% of genuine content searches and on ZERO extractions.
|
|
18
|
+
#
|
|
19
|
+
# WHAT IT DELIBERATELY LETS THROUGH, because a false block teaches the model to
|
|
20
|
+
# route AROUND the guard — which is worse than the search it would have stopped:
|
|
21
|
+
#
|
|
22
|
+
# · `… | grep x` filtering another command's output (46% of all
|
|
23
|
+
# invocations). Not a repo search; the wrapper cannot
|
|
24
|
+
# replace it.
|
|
25
|
+
# · `grep -n x file.ts` extracting from an ALREADY KNOWN file (22%). The
|
|
26
|
+
# harness's own doctrine prefers this to the wrapper.
|
|
27
|
+
# · anything it cannot parse with certainty — a quoted pattern carrying a
|
|
28
|
+
# newline, an unbalanced quote. Unsure means allow.
|
|
29
|
+
#
|
|
30
|
+
# The managed block below is regenerated by `navori render` and must NOT be
|
|
31
|
+
# edited by hand. Add repo-specific exceptions in the user section at the
|
|
32
|
+
# bottom — `$cmd` is already parsed and in scope there.
|
|
33
|
+
set -euo pipefail
|
|
34
|
+
|
|
35
|
+
# Command extraction (payload → $cmd). Shared body, single source of truth.
|
|
36
|
+
# navori:include extract-cmd
|
|
37
|
+
cmd=$(extract_cmd)
|
|
38
|
+
|
|
39
|
+
navori_audit_name="guard-search-routing"
|
|
40
|
+
navori_audit_phase="PreToolUse"
|
|
41
|
+
navori_audit_tool="Bash"
|
|
42
|
+
# `source` names the PLUGIN, not core: disabling tgrep changes which hooks run,
|
|
43
|
+
# and without this the report cannot explain why a phase thinned out between two
|
|
44
|
+
# sessions.
|
|
45
|
+
navori_audit_source="plugin:tgrep"
|
|
46
|
+
# Fallback no-ops, overwritten by the real definitions the include brings in.
|
|
47
|
+
# They exist because this hook is FAIL-OPEN: if the file ever runs WITHOUT its
|
|
48
|
+
# includes expanded — a raw copy of the asset, a render that half-finished — an
|
|
49
|
+
# undefined function would be exit 127, and under `set -e` that KILLS the hook.
|
|
50
|
+
navori_audit_begin() { :; }
|
|
51
|
+
navori_audit_log() { :; }
|
|
52
|
+
# navori:include audit-log
|
|
53
|
+
navori_audit_begin
|
|
54
|
+
|
|
55
|
+
# The verdict is resolved in a trap, not by a call at the end of the file: this
|
|
56
|
+
# hook's managed block ends before its user section, so a call placed after it
|
|
57
|
+
# would live in the user's territory and never reach the mirror.
|
|
58
|
+
navori_audit_verdict="allow"
|
|
59
|
+
navori_audit_reason=""
|
|
60
|
+
navori_audit_on_exit() {
|
|
61
|
+
navori_audit_log "$navori_audit_verdict" "$navori_audit_reason" || true
|
|
62
|
+
return 0
|
|
63
|
+
}
|
|
64
|
+
trap navori_audit_on_exit EXIT
|
|
65
|
+
|
|
66
|
+
if [ -z "$cmd" ]; then
|
|
67
|
+
navori_audit_verdict="skip"
|
|
68
|
+
navori_audit_reason="sin comando que inspeccionar"
|
|
69
|
+
exit 0
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# The wrapper itself is never blocked — it runs `grep -rn` internally on the
|
|
73
|
+
# fallback path, and this hook sees the command the agent typed, not the one the
|
|
74
|
+
# wrapper spawns. Checked FIRST so no rule below can ever fire on it.
|
|
75
|
+
case "$cmd" in
|
|
76
|
+
*tgrep-search.sh*)
|
|
77
|
+
navori_audit_verdict="skip"
|
|
78
|
+
navori_audit_reason="es el wrapper"
|
|
79
|
+
exit 0
|
|
80
|
+
;;
|
|
81
|
+
esac
|
|
82
|
+
|
|
83
|
+
# BOUNDED WORK: the guard runs under a wall-clock timeout it does not control,
|
|
84
|
+
# and being killed is indistinguishable from approving. Every pass below is one
|
|
85
|
+
# linear `sed`/`grep` over the command; a command past the ceiling is ALLOWED
|
|
86
|
+
# rather than inspected, because this guard redirects a search — it does not
|
|
87
|
+
# protect against data loss, so failing open is the right failure here (and the
|
|
88
|
+
# opposite of what guard-destructive does with the same trade-off).
|
|
89
|
+
if [ "${#cmd}" -gt 20000 ]; then
|
|
90
|
+
navori_audit_verdict="skip"
|
|
91
|
+
navori_audit_reason="comando demasiado grande para inspeccionar"
|
|
92
|
+
exit 0
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# SEGMENTS. A command is split so each rule only ever matches WITHIN one
|
|
96
|
+
# segment, and each segment carries whether it starts a command or continues a
|
|
97
|
+
# pipe. That distinction is the whole design: `grep -n foo` is extraction from
|
|
98
|
+
# stdin after a `|`, and a repo search at the start of a line. The segment alone
|
|
99
|
+
# cannot tell them apart.
|
|
100
|
+
#
|
|
101
|
+
# Order matters: `||` must be rewritten before `|`, or the first `|` of a `||`
|
|
102
|
+
# is consumed and the second starts a bogus "piped" segment.
|
|
103
|
+
#
|
|
104
|
+
# A line continuation is NOT a separator: without joining it first, `\` + newline
|
|
105
|
+
# splits a quoted pattern in half and the fragment lands with no target, which
|
|
106
|
+
# every heuristic below reads as a recursive search. Measured on the park: that
|
|
107
|
+
# alone mislabelled over a thousand extractions.
|
|
108
|
+
segments=$(printf '%s' "$cmd" \
|
|
109
|
+
| sed -e ':a' -e '$!N' -e 's/\\\n/ /' -e 'ta' -e 'P' -e 'D' \
|
|
110
|
+
| sed -e 's/||/\
|
|
111
|
+
@C@/g' -e 's/&&/\
|
|
112
|
+
@C@/g' -e 's/;/\
|
|
113
|
+
@C@/g' -e 's/|/\
|
|
114
|
+
@P@/g')
|
|
115
|
+
|
|
116
|
+
block() {
|
|
117
|
+
echo "[navori] BLOCKED by guard-search-routing: $1" >&2
|
|
118
|
+
echo "[navori] route content search through the indexed wrapper:" >&2
|
|
119
|
+
echo "[navori] bash .claude/scripts/tgrep-search.sh <same ripgrep flags>" >&2
|
|
120
|
+
echo "[navori] it carries its own 'allow' rule: no prompt, no classifier round-trip." >&2
|
|
121
|
+
echo "[navori] exit contract: 0 = match, 1 = no match, 2 = NOTHING WAS SEARCHED." >&2
|
|
122
|
+
echo "[navori] this does NOT apply to '| grep' (filtering output) nor to" >&2
|
|
123
|
+
echo "[navori] 'grep -n x known-file' (extracting from a file you already found):" >&2
|
|
124
|
+
echo "[navori] both of those stay correct, and the wrapper cannot replace them." >&2
|
|
125
|
+
# Only assignments here: nothing may come between the decision and `exit 2`.
|
|
126
|
+
# The recording happens in the trap, after the exit is already committed.
|
|
127
|
+
navori_audit_verdict="block"
|
|
128
|
+
navori_audit_reason="$1"
|
|
129
|
+
exit 2
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# Walk the command-start segments only. `@P@` lines are dropped here, and that
|
|
133
|
+
# is what keeps every pipe-filter out of every rule below: the first line of the
|
|
134
|
+
# command carries no marker (it starts the command), and each `@C@` line starts
|
|
135
|
+
# a new one.
|
|
136
|
+
starts=$(printf '%s\n' "$segments" | sed -n -e 's/^@C@//p' -e '1{/^@[CP]@/!p;}')
|
|
137
|
+
|
|
138
|
+
while IFS= read -r seg; do
|
|
139
|
+
[ -n "$seg" ] || continue
|
|
140
|
+
|
|
141
|
+
# Recursive grep: the flag may sit anywhere among the options, so the verb is
|
|
142
|
+
# anchored at the segment start and the flag matched as its own token.
|
|
143
|
+
# `-[a-zA-Z]*[rR][a-zA-Z]*` matches `-r`, `-R`, `-rn`, `-nr`; it cannot match
|
|
144
|
+
# `--include` or `--color`, because after the leading `-` the class does not
|
|
145
|
+
# accept another `-`.
|
|
146
|
+
if printf '%s' "$seg" | grep -qE '^[[:space:]]*(grep|egrep|fgrep)([[:space:]]|$)' \
|
|
147
|
+
&& printf '%s' "$seg" | grep -qE '(^|[[:space:]])(-[a-zA-Z]*[rR][a-zA-Z]*|--recursive)([[:space:]]|=|$)'; then
|
|
148
|
+
block "busqueda de contenido recursiva por shell"
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
# `rg` is recursive by DEFAULT, so the question inverts: it is extraction only
|
|
152
|
+
# when a single concrete file is named. The test is deliberately loose — any
|
|
153
|
+
# token that looks like a path with an extension — because the cost of the two
|
|
154
|
+
# errors is not symmetric. Missing a search costs one unmeasured call; blocking
|
|
155
|
+
# a legitimate extraction teaches the model to work around the guard.
|
|
156
|
+
if printf '%s' "$seg" | grep -qE '^[[:space:]]*rg([[:space:]]|$)' \
|
|
157
|
+
&& ! printf '%s' "$seg" | grep -qE '(^|[[:space:]])[^[:space:]]*\.[A-Za-z0-9]+([[:space:]]|$)'; then
|
|
158
|
+
block "busqueda de contenido por shell (rg es recursivo por defecto)"
|
|
159
|
+
fi
|
|
160
|
+
done <<EOF
|
|
161
|
+
$starts
|
|
162
|
+
EOF
|
|
163
|
+
|
|
164
|
+
# navori:user-section
|
|
165
|
+
# user: add repo-specific exceptions or extra redirects here. `$cmd` holds the
|
|
166
|
+
# full command and `block "<reason>"` aborts with exit 2.
|
|
167
|
+
|
|
168
|
+
exit 0
|