nx_shelltool.nx source
↩ module page · 458 lines · 24597 B
1// nx_shelltool.nx -- the INVOCABLE + MCP-READY surface for the shell-replacement tier (07-15
2// operator: "build our nishi grep and glob and search ... as full MCP API state of the art ...
3// available to these workstreams ... anything else that uses wsl when we could just use nishi").
4// The S1-S9 organs (nx_grep etc.) are LIBS (functions, no main) -> not callable as tools + not on
5// MCP. This is the ONE CLI that COMPOSES the canonical nx_grep (per NISHI_SHELL_REPLACEMENT_ROADMAP
6// "consumers compose nx_grep", no proliferation) into filesystem verbs a workstream can invoke over
7// MCP tools/call instead of the Grep/Glob/Bash builtins + WSL:
8// nx_shelltool grep <pattern> <dir> [ext] -> recursive; prints path:lineno:line for matches
9// nx_shelltool glob <glob-pat> <dir> -> recursive; prints paths whose NAME matches (* wildcard)
10// nx_shelltool find <substr> <dir> -> recursive; prints paths whose NAME contains substr
11// Composes nx_grep_any (canonical match). Read-only. Exit 0; 2 usage; 3 baddir; 5 budget-partial.
12//
13// SEQ1292 ROOT FIX (2026-07-30, incident 2026-07-29 sitewide outage): every scan now runs under a
14// SCAN BUDGET -- an unbounded walk over a multi-GB store starved the whole hub (and the old code
15// mmap'd 1MiB per file, never unmapped => RSS grew linearly with files scanned = the thrash vector).
16// Budget source: shelltool_budget.conf in CWD (keys max_bytes= deadline_ms= max_files=), else
17// derived defaults: max_bytes 512MiB (~1.4% of the 36GB hub, seconds of sequential NAS IO),
18// deadline_ms 20000 (the proven WC_FETCH_BUDGET_MS wall-clock precedent), max_files 200000.
19// Checked per file + per getdents batch so exceeding STOPS IO, prints partial counts + a structured
20// NX-SHELLTOOL BUDGET-EXCEEDED line, exit 5 -- never a silent truncation, never a host wedge.
21// Buffers: ONE reused 1MiB read buffer; per-level walk buffers munmap'd; overlong paths (>4095)
22// skipped LOUDLY via skipped_toolong= in the summary.
23// license_tier: ORIGINAL expect_exit: 0
24import "nx_syscalls.nx"
25import "nx_grep_rt.nx"
26
27const S_EXIT_BADDIR: i64 = 3 // <dir> not a readable directory (loud, distinct from usage=2)
28const S_EXIT_BUDGET: i64 = 5 // budget exceeded -> PARTIAL results (loud, distinct)
29const S_PROBE_CAP: i64 = 4096 // one-getdents probe buffer (dir-ness check)
30const S_DEF_MAX_BYTES: i64 = 536870912 // 512MiB derived: ~1.4% hub RAM, seconds of NAS IO (seq1292)
31const S_DEF_DEADLINE_MS: i64 = 20000 // proven wall-clock budget precedent (WC_FETCH_BUDGET_MS)
32const S_DEF_MAX_FILES: i64 = 200000 // > any legitimate single-tree scan observed to date
33// --- out= support (id 1785516061: the MCP stdout capture truncates at 160KiB with NO marker, so a
34// large scan's result was arriving CLIPPED MID-LINE with its honesty trailer deleted. Same shape as
35// the nx_treediff OUTFILE fix: results to disk, envelope on the wire, coverage bounded by the corpus
36// and never by a caller payload cap.)
37const S_FD_STDOUT: i64 = 1
38const S_MODE_644: i64 = 420
39const S_EXIT_BADOUT: i64 = 6 // out= refused/unwritable (loud, distinct from usage/baddir/budget)
40const S_NUMBUF: i64 = 24
41const S_OUTKEY_LEN: i64 = 4 // len("out=")
42const S_OUT_MINLEN: i64 = 5 // at least one name char + ".out"
43const S_DOT: i64 = 46
44const S_SLASH: i64 = 47
45const S_CH_O: i64 = 111
46const S_CH_U: i64 = 117
47const S_CH_T: i64 = 116
48const S_BUD_OFD: i64 = 9 // bud[] already threads the scan context to every emit site --
49const S_BUD_OBYTES: i64 = 10 // reusing 2 spare slots avoids changing 3 function signatures
50const S_GBUF_CAP: i64 = 1048576 // the ONE shared read buffer; a CHUNK size now, not a coverage limit
51const S_BUD_LONGLINE: i64 = 11 // count of single lines longer than the whole buffer (declared, never silent)
52
53func s_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
54func s_write(b: *u8, n: i64) -> i64 { sys_write(1, b, n); return 0 }
55func s_putn(v: i64) -> i64 {
56 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 }
57 var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m }
58 let d: *u8 = sys_mmap(24); var k: i64 = 0
59 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
60 let o: *u8 = sys_mmap(24); var w: i64 = 0
61 while w < k { o[w] = d[k-1-w]; w = w + 1 }
62 sys_write(1, o, k)
63 sys_munmap(d, 24); sys_munmap(o, 24)
64 return 0
65}
66func s_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
67func s_starts(s: *u8, pre: *u8) -> i64 { var i: i64 = 0; while pre[i] != (0 as u8) { if s[i] != pre[i] { return 0 } i = i + 1 } return 1 }
68// fd-aware emit (id 1785516061): results may go to a FILE so stdout carries only the envelope.
69// Both return bytes written, so the envelope can report out_bytes without a second stat.
70func s_fputs(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return n }
71func s_fputn(fd: i64, v: i64) -> i64 {
72 if v == 0 { sys_write(fd, "0" as *u8, 1); return 1 }
73 var m: i64 = v
74 var w: i64 = 0
75 if m < 0 { sys_write(fd, "-" as *u8, 1); m = 0 - m; w = 1 }
76 let d: *u8 = sys_mmap(S_NUMBUF); var k: i64 = 0
77 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
78 let o: *u8 = sys_mmap(S_NUMBUF); var q: i64 = 0
79 while q < k { o[q] = d[k-1-q]; q = q + 1 }
80 sys_write(fd, o, k)
81 sys_munmap(d, S_NUMBUF); sys_munmap(o, S_NUMBUF)
82 return w + k
83}
84// out= turns a READ-ONLY tool into a writer, so the target is constrained BY CONSTRUCTION rather
85// than by convention: no absolute path, no .. traversal, and the name MUST end in .out -- which
86// makes clobbering any source (.nx), registry (.tsv/.conf) or binary (.elf) structurally impossible.
87func s_out_ok(p: *u8) -> i64 {
88 let n: i64 = s_slen(p)
89 if n < S_OUT_MINLEN { return 0 }
90 if p[0] == (S_SLASH as u8) { return 0 }
91 var i: i64 = 0
92 while i + 1 < n { if p[i] == (S_DOT as u8) { if p[i+1] == (S_DOT as u8) { return 0 } } i = i + 1 }
93 if p[n-4] != (S_DOT as u8) { return 0 }
94 if p[n-3] != (S_CH_O as u8) { return 0 }
95 if p[n-2] != (S_CH_U as u8) { return 0 }
96 if p[n-1] != (S_CH_T as u8) { return 0 }
97 return 1
98}
99func s_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 }
100
101// bounded whole-file read
102func s_read(path: *u8, buf: *u8, cap: i64) -> i64 {
103 let fd: i64 = sys_openat_rd(path)
104 if fd < 0 { return 0 - 1 }
105 var tot: i64 = 0
106 var n: i64 = sys_read(fd, buf, cap)
107 while n > 0 { tot = tot + n; if tot >= cap { n = 0 } else { n = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot) } }
108 sys_close(fd)
109 return tot
110}
111
112// first integer after <key> in buf (key includes the '='), -1 if absent/empty
113func s_num_after(buf: *u8, n: i64, key: *u8) -> i64 {
114 let kn: i64 = s_slen(key)
115 var i: i64 = 0
116 while i + kn <= n {
117 var j: i64 = 0
118 var ok: i64 = 1
119 while j < kn { if buf[i+j] != key[j] { ok = 0; j = kn } else { j = j + 1 } }
120 if ok == 1 {
121 var p: i64 = i + kn
122 var v: i64 = 0
123 var any: i64 = 0
124 var go: i64 = 1
125 while go == 1 {
126 go = 0
127 if p < n {
128 let c: i64 = buf[p] as i64
129 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1; p = p + 1; go = 1 } }
130 }
131 }
132 if any == 1 { return v }
133 return 0 - 1
134 }
135 i = i + 1
136 }
137 return 0 - 1
138}
139
140func s_ends_with(name: *u8, nn: i64, ext: *u8, en: i64) -> i64 {
141 if en == 0 { return 1 }
142 if en > nn { return 0 }
143 var i: i64 = 0
144 while i < en { if name[nn-en+i] != ext[i] { return 0 } i = i + 1 }
145 return 1
146}
147
148// iterative glob match with '*' (any sequence) + literal chars; backtracking. 1 if match.
149func s_glob(pat: *u8, pn: i64, str: *u8, sn: i64) -> i64 {
150 var pi: i64 = 0
151 var si: i64 = 0
152 var star: i64 = 0 - 1
153 var mark: i64 = 0
154 var run: i64 = 1
155 while run == 1 {
156 if si < sn {
157 var advanced: i64 = 0
158 if pi < pn { if pat[pi] == (42 as u8) { star = pi; mark = si; pi = pi + 1; advanced = 1 } }
159 if advanced == 0 { if pi < pn { if pat[pi] == str[si] { pi = pi + 1; si = si + 1; advanced = 1 } } }
160 if advanced == 0 { if star >= 0 { pi = star + 1; mark = mark + 1; si = mark; advanced = 1 } }
161 if advanced == 0 { return 0 }
162 } else { run = 0 }
163 }
164 while pi < pn { if pat[pi] == (42 as u8) { pi = pi + 1 } else { return 0 } }
165 return 1
166}
167
168// the sticky budget check: bud[0]=deadline_abs_us bud[1]=bytes_left bud[2]=files_left bud[3]=exceeded
169// bud[4]=bytes_read bud[5]=files_seen bud[6]=max_bytes_cfg bud[7]=deadline_ms_cfg bud[8]=skipped_toolong
170func s_bud_ok(bud: *i64) -> i64 {
171 if bud[3] == 1 { return 0 }
172 if bud[1] <= 0 { bud[3] = 1; return 0 }
173 if bud[2] <= 0 { bud[3] = 1; return 0 }
174 if sys_clock_now_us() > bud[0] { bud[3] = 1; return 0 }
175 return 1
176}
177
178// emit one matching line. Lifted out of s_grep_file so the chunked scanner below has exactly ONE
179// emit site for both the mid-chunk case and the final no-trailing-newline case.
180func s_grep_emit(path: *u8, ln: i64, gbuf: *u8, ls: i64, le: i64, bud: *i64) -> i64 {
181 let ofd: i64 = bud[S_BUD_OFD]
182 var wb: i64 = s_fputs(ofd, path)
183 sys_write(ofd, ":" as *u8, 1); wb = wb + 1
184 wb = wb + s_fputn(ofd, ln)
185 sys_write(ofd, ":" as *u8, 1); wb = wb + 1
186 sys_write(ofd, ((gbuf as i64) + ls) as *u8, le - ls); wb = wb + (le - ls)
187 sys_write(ofd, "\n" as *u8, 1); wb = wb + 1
188 bud[S_BUD_OBYTES] = bud[S_BUD_OBYTES] + wb
189 return 1
190}
191// CHUNKED LINE SCAN (2026-08-06, debt 1786057862). THE DEFECT REPLACED: this function did
192// let n: i64 = s_read(path, gbuf, 1048575)
193// -- ONE read from offset 0 into the shared 1MiB buffer, with n NEVER compared against the file size
194// and NO flag emitted anywhere. Any file larger than the buffer had its TAIL SILENTLY UNSEARCHED, so
195// matches=0 meant "absent OR beyond 1MiB" and the two were indistinguishable.
196// MEASURED on knowledge/status/ws_sync.jrnl (1383495B): `frontier-program` was found at lines 1..1530
197// while THREE tokens appended to the tail of that same file the same day -- segstore-lock-adoption,
198// DOUBLE-DRIVE, ROUND 2 LANDED -- all returned matches=0, i.e. 334920 bytes (24.2pc) unreachable.
199// CONTROL THAT ISOLATES SIZE AS THE CAUSE: the same token WAS found at claims.jrnl:719, a LATE line in
200// a SMALLER file. So late-in-file works; large-file does not.
201// ***A SEARCH TOOL THAT UNDER-REPORTS IS WORSE THAN ONE THAT ERRORS, BECAUSE matches=0 IS ACTED ON AS
202// PROOF OF ABSENCE.*** It cost precisely that: a CORRECT index was suspected of over-closing 17
203// workstreams because the reap frames proving otherwise sat past the cap.
204// THE FIX IS NOT A BIGGER BUFFER -- that only moves the cliff. This reads the file in successive chunks
205// and CARRIES the trailing incomplete line to the front, so coverage is COMPLETE at any file size while
206// memory stays bounded by the buffer. Line numbers accumulate across chunks so path:lineno stays true.
207// THE ONLY RESIDUAL is a single line longer than the entire buffer: it is COUNTED, DECLARED as
208// linetoolong= in the envelope, and SKIPPED to its real end -- never silently split, because half a line
209// matched as a whole one is a FABRICATED match, the opposite failure and the worse one.
210func s_grep_file(path: *u8, pat: *u8, pn: i64, gbuf: *u8, bud: *i64) -> i64 {
211 if s_bud_ok(bud) == 0 { return 0 }
212 let fd: i64 = sys_openat_rd(path)
213 if fd < 0 { return 0 }
214 var hits: i64 = 0
215 var ln: i64 = 1
216 var carry: i64 = 0
217 var skipping: i64 = 0
218 var go: i64 = 1
219 while go == 1 {
220 var r: i64 = 0
221 let room: i64 = S_GBUF_CAP - carry
222 if room > 0 { r = sys_read(fd, ((gbuf as i64) + carry) as *u8, room) }
223 if r <= 0 {
224 go = 0
225 // a final line with NO trailing newline is still a line and must still be matched
226 if skipping == 0 { if carry > 0 {
227 if nx_grep_any(gbuf, carry, pat, pn) == 1 { hits = hits + s_grep_emit(path, ln, gbuf, 0, carry, bud) }
228 } }
229 } else {
230 bud[1] = bud[1] - r
231 bud[4] = bud[4] + r
232 let n: i64 = carry + r
233 var ls: i64 = 0
234 if skipping == 1 {
235 // discard the remainder of an overlong line up to its real terminator
236 var p: i64 = 0
237 var ff: i64 = 1
238 while ff == 1 { ff = 0; if p < n { if gbuf[p] != (10 as u8) { p = p + 1; ff = 1 } } }
239 if p < n { skipping = 0; ln = ln + 1; ls = p + 1 } else { ls = n }
240 }
241 var scan: i64 = 1
242 while scan == 1 {
243 var le: i64 = ls
244 var f: i64 = 1
245 while f == 1 { f = 0; if le < n { if gbuf[le] != (10 as u8) { le = le + 1; f = 1 } } }
246 if le < n {
247 if nx_grep_any(((gbuf as i64) + ls) as *u8, le - ls, pat, pn) == 1 { hits = hits + s_grep_emit(path, ln, gbuf, ls, le, bud) }
248 ln = ln + 1
249 ls = le + 1
250 } else {
251 scan = 0
252 carry = n - ls
253 if carry >= S_GBUF_CAP {
254 bud[S_BUD_LONGLINE] = bud[S_BUD_LONGLINE] + 1
255 carry = 0
256 skipping = 1
257 } else {
258 var m: i64 = 0
259 while m < carry { gbuf[m] = gbuf[ls + m]; m = m + 1 }
260 }
261 }
262 }
263 }
264 if s_bud_ok(bud) == 0 { go = 0 }
265 }
266 sys_close(fd)
267 return hits
268}
269
270// recursive walk under budget. mode: 0 grep, 1 glob, 2 find. Frees its own level buffers (munmap).
271// Returns the total match count across the subtree (files matched, or grep lines matched).
272func s_walk(dir: *u8, mode: i64, pat: *u8, pn: i64, ext: *u8, en: i64, gbuf: *u8, bud: *i64) -> i64 {
273 let fd: i64 = sys_openat_rd(dir)
274 if fd < 0 { return 0 }
275 let dbuf: *u8 = sys_mmap(65536)
276 let path: *u8 = sys_mmap(4096)
277 let dl: i64 = s_slen(dir)
278 var hits: i64 = 0
279 var run: i64 = 1
280 while run == 1 {
281 if s_bud_ok(bud) == 0 { run = 0 } else {
282 let nn: i64 = sys_getdents64(fd, dbuf, 65536)
283 if nn <= 0 { run = 0 } else {
284 var off: i64 = 0
285 while off < nn {
286 let rec: *u8 = ((dbuf as i64 + off) as *u8)
287 let reclen: i64 = dirent_reclen(rec)
288 if reclen <= 0 { off = nn } else {
289 if s_bud_ok(bud) == 0 { off = nn } else {
290 let name: *u8 = dirent_name(rec)
291 let dt: i64 = dirent_type(rec)
292 var skip: i64 = 0
293 if name[0] == (46 as u8) { if name[1] == (0 as u8) { skip = 1 } else { if name[1] == (46 as u8) { if name[2] == (0 as u8) { skip = 1 } } } }
294 if skip == 0 {
295 let nl: i64 = s_slen(name)
296 if dl + 1 + nl + 1 >= 4096 { bud[8] = bud[8] + 1 } else {
297 // build dir + "/" + name (bounds proven above)
298 var o: i64 = 0
299 var a: i64 = 0
300 while dir[a] != (0 as u8) { path[o] = dir[a]; o = o + 1; a = a + 1 }
301 path[o] = 47 as u8; o = o + 1
302 a = 0
303 while name[a] != (0 as u8) { path[o] = name[a]; o = o + 1; a = a + 1 }
304 path[o] = 0 as u8
305 if dt == 4 { hits = hits + s_walk(path, mode, pat, pn, ext, en, gbuf, bud) } else {
306 bud[2] = bud[2] - 1
307 bud[5] = bud[5] + 1
308 if mode == 0 { if s_ends_with(name, nl, ext, en) == 1 { hits = hits + s_grep_file(path, pat, pn, gbuf, bud) } }
309 if mode == 1 { if s_glob(pat, pn, name, nl) == 1 { let og: i64 = bud[S_BUD_OFD]; bud[S_BUD_OBYTES] = bud[S_BUD_OBYTES] + s_fputs(og, path) + 1; sys_write(og, "\n" as *u8, 1); hits = hits + 1 } }
310 if mode == 2 { if nx_grep_any(name, nl, pat, pn) == 1 { let of2: i64 = bud[S_BUD_OFD]; bud[S_BUD_OBYTES] = bud[S_BUD_OBYTES] + s_fputs(of2, path) + 1; sys_write(of2, "\n" as *u8, 1); hits = hits + 1 } }
311 }
312 }
313 }
314 off = off + reclen
315 }
316 }
317 }
318 }
319 }
320 }
321 sys_close(fd)
322 sys_munmap(dbuf, 65536)
323 sys_munmap(path, 4096)
324 return hits
325}
326
327// dir must open AND getdents -- a file or missing path is a LOUD error, never a silent empty
328// (the MCP capture is stdout, so the error goes to stdout + a distinct exit code).
329func s_dir_ok(dir: *u8) -> i64 {
330 let fd: i64 = sys_openat_rd(dir)
331 if fd < 0 { return 0 }
332 let probe: *u8 = sys_mmap(S_PROBE_CAP)
333 let nn: i64 = sys_getdents64(fd, probe, S_PROBE_CAP)
334 sys_close(fd)
335 sys_munmap(probe, S_PROBE_CAP)
336 if nn < 0 { return 0 }
337 return 1
338}
339
340func main(argc: i64, argv: *i64) -> i64 {
341 if argc < 4 { s_puts("usage: nx_shelltool grep <pattern> <dir> [ext] | glob <pat> <dir> | find <substr> <dir>\n" as *u8); return 2 }
342 let verb: *u8 = argv[1] as *u8
343 let a2: *u8 = argv[2] as *u8
344 let a3: *u8 = argv[3] as *u8
345 var mode: i64 = 0 - 1
346 if s_streq(verb, "grep" as *u8) == 1 { mode = 0 }
347 if s_streq(verb, "glob" as *u8) == 1 { mode = 1 }
348 if s_streq(verb, "find" as *u8) == 1 { mode = 2 }
349 if mode < 0 { s_puts("usage: nx_shelltool grep <pattern> <dir> [ext] | glob <pat> <dir> | find <substr> <dir>\n" as *u8); return 2 }
350 if s_dir_ok(a3) == 0 {
351 s_puts("NX-SHELLTOOL ERROR: not a readable directory: " as *u8)
352 s_puts(a3)
353 s_puts(" (verbs take <pattern> <dir>; to search ONE file pass its parent dir + ext)\n" as *u8)
354 return S_EXIT_BADDIR
355 }
356 // budget: shelltool_budget.conf in CWD (max_bytes= deadline_ms= max_files=) else derived defaults
357 var maxb: i64 = S_DEF_MAX_BYTES
358 var dlms: i64 = S_DEF_DEADLINE_MS
359 var maxf: i64 = S_DEF_MAX_FILES
360 let cbuf: *u8 = sys_mmap(256)
361 let cn: i64 = s_read("shelltool_budget.conf" as *u8, cbuf, 255)
362 if cn > 0 {
363 let v1: i64 = s_num_after(cbuf, cn, "max_bytes=" as *u8)
364 if v1 > 0 { maxb = v1 }
365 let v2: i64 = s_num_after(cbuf, cn, "deadline_ms=" as *u8)
366 if v2 > 0 { dlms = v2 }
367 let v3: i64 = s_num_after(cbuf, cn, "max_files=" as *u8)
368 if v3 > 0 { maxf = v3 }
369 }
370 sys_munmap(cbuf, 256)
371 // 128 not 96: the chunked scanner adds a declared long-line counter at slot 11 and 96 bytes held
372 // exactly 12 slots with 11 already in use, so growing the allocation is what keeps the new counter
373 // from writing one past the end of a buffer whose bound nothing was checking.
374 let bud: *i64 = sys_mmap(128) as *i64
375 bud[0] = sys_clock_now_us() + dlms * 1000
376 bud[1] = maxb
377 bud[2] = maxf
378 bud[3] = 0
379 bud[4] = 0
380 bud[5] = 0
381 bud[6] = maxb
382 bud[7] = dlms
383 bud[8] = 0
384 bud[S_BUD_LONGLINE] = 0
385 var ext: *u8 = "" as *u8
386 var en: i64 = 0
387 if mode == 0 { if argc >= 5 { let c4: *u8 = argv[4] as *u8; if s_starts(c4, "out=" as *u8) == 0 { ext = c4; en = s_slen(ext) } } }
388 // out=<path>.out is a KEY=VALUE token accepted in ANY trailing position, so it can never be
389 // confused with grep's positional [ext] and all three verbs take it identically.
390 // Rule 19: absent out= is byte-for-byte the old stdout behaviour.
391 var outp: *u8 = 0 as *u8
392 var ai: i64 = 4
393 while ai < argc { let av: *u8 = argv[ai] as *u8; if s_starts(av, "out=" as *u8) == 1 { outp = ((av as i64) + S_OUTKEY_LEN) as *u8 } ai = ai + 1 }
394 var ofd: i64 = S_FD_STDOUT
395 if (outp as i64) != 0 {
396 if s_out_ok(outp) == 0 {
397 s_puts("NX-SHELLTOOL ERROR: out= refused: " as *u8); s_puts(outp)
398 s_puts(" (must be relative, contain no .., and end in .out -- so a scan can never clobber source, registry or binary)\n" as *u8)
399 return S_EXIT_BADOUT
400 }
401 ofd = sys_openat_wr(outp, S_MODE_644)
402 if ofd < 0 {
403 s_puts("NX-SHELLTOOL ERROR: out= unwritable: " as *u8); s_puts(outp); s_puts("\n" as *u8)
404 return S_EXIT_BADOUT
405 }
406 }
407 bud[S_BUD_OFD] = ofd
408 bud[S_BUD_OBYTES] = 0
409 let gbuf: *u8 = sys_mmap(1048576)
410 let hits: i64 = s_walk(a3, mode, a2, s_slen(a2), ext, en, gbuf, bud)
411 s_puts("-- matches=" as *u8); s_putn(hits)
412 s_puts(" bytes=" as *u8); s_putn(bud[4])
413 s_puts(" files=" as *u8); s_putn(bud[5])
414 if bud[8] > 0 { s_puts(" skipped_toolong=" as *u8); s_putn(bud[8]) }
415 // coverage is now COMPLETE for grep regardless of file size, so the envelope says so positively --
416 // a reader must be able to tell "I searched every byte" from "I stopped and did not mention it".
417 // ***SPELLED coverage_complete= BECAUSE LAW L011 DEFINES THAT TOKEN, NOT BECAUSE IT READS WELL.***
418 // I first emitted "per_file_coverage=complete", which is the same fact in a private dialect. It cost
419 // exactly what a dialect costs: nx_envelope_audit -- which had just been ALIGNED to L011's vocabulary
420 // (scanned / coverage_complete / capped) -- still classified this tool a silent-cap candidate, because
421 // "per_file_coverage=complete" does not contain the substring "coverage_complete" (underscore vs
422 // equals). A near-miss spelling is indistinguishable from silence to every consumer.
423 // The fix belongs HERE, not in the detector: widening a detector to accept dialects never converges,
424 // while a law that names its token converges the moment each emitter uses it.
425 // ★WHEN A LAW NAMES THE TOKEN, THE EMITTER SPEAKS THE LAW'S SPELLING -- THE DETECTOR IS NOT THE PLACE
426 // TO ABSORB EVERY AUTHOR'S PHRASING.
427 if mode == 0 { s_puts(" coverage_complete=1" as *u8) }
428 // CORPUS vs PER-FILE, SPLIT 2026-08-07 -- and the irony is that the comment directly above spends nine
429 // lines insisting the TOKEN be spelled the way the law names it, while the field itself answers a
430 // NARROWER question than its name implies. coverage_complete=1 means EVERY FILE THAT WAS OPENED was
431 // scanned to its end (the honest per-file fix of 08-06). It says nothing about whether every file was
432 // VISITED, and it is emitted even when the walk stopped early on the budget -- it is printed ABOVE the
433 // bud[3] check, so the two coexist.
434 // MEASURED 2026-08-07 by nx_absent neg-control: a whole-tree grep returned matches=0 coverage_complete=1
435 // TOGETHER WITH BUDGET-EXCEEDED partial=1. A consumer trusting the flag alone publishes ABSENCE for a
436 // corpus it never finished reading -- and absence is the one claim that requires exhaustive coverage.
437 // ★A FLAG NAMED FOR THE QUESTION THE READER IS ASKING, WHICH ANSWERS A NARROWER ONE, IS A FALSE PROOF
438 // WITH AN AUTHORITATIVE NAME. Additive per rule 19: coverage_complete keeps its exact meaning forever,
439 // and the corpus-level fact gets its OWN token so no reader must know which coverage question a single
440 // flag answers. Emitted for EVERY verb, because the walk budget binds grep, glob and find alike.
441 if bud[3] == 1 { s_puts(" corpus_complete=0" as *u8) }
442 else { s_puts(" corpus_complete=1" as *u8) }
443 if bud[S_BUD_LONGLINE] > 0 { s_puts(" linetoolong=" as *u8); s_putn(bud[S_BUD_LONGLINE]) }
444 if ofd != S_FD_STDOUT {
445 sys_close(ofd)
446 s_puts(" out=" as *u8); s_puts(outp)
447 s_puts(" out_bytes=" as *u8); s_putn(bud[S_BUD_OBYTES])
448 s_puts(" stdout_bounded=1" as *u8)
449 }
450 sys_write(1, "\n" as *u8, 1)
451 if bud[3] == 1 {
452 s_puts("NX-SHELLTOOL BUDGET-EXCEEDED partial=1 max_bytes=" as *u8); s_putn(bud[6])
453 s_puts(" deadline_ms=" as *u8); s_putn(bud[7])
454 s_puts(" (PARTIAL scan; raise shelltool_budget.conf deliberately, never remove the budget)\n" as *u8)
455 return S_EXIT_BUDGET
456 }
457 return 0
458}