Skip to content

The linter printed the column the hooks withhold, and said in the README that it did not (#124, #125) - #129

Merged
fdaviddpt merged 2 commits into
mainfrom
fix/124-125
Aug 14, 2026
Merged

The linter printed the column the hooks withhold, and said in the README that it did not (#124, #125)#129
fdaviddpt merged 2 commits into
mainfrom
fix/124-125

Conversation

@fdaviddpt

Copy link
Copy Markdown
Contributor

Closes #124
Closes #125

#124

jit-dry-run.sh printed the index's entry-file column raw. That column is chosen by whoever wrote the repository you cloned — the third instance of one channel: #35 in pre-tool-hook.sh, #113 across five reports in rebuild-tsv.sh, now the linter the hooks' own refusal notice sends authors to. README.md:507 claimed this tool "prints that text framed as untrusted", which was false for that column — a reader who checked the documentation before trusting the output got the wrong answer.

Every entry-name, layer-directory, tool-column and sample-call name now routes through jit_report_name(). check_entry_file() gained the row position, so a withheld name still leaves the author a handle.

REFUSED  paths/00-manual    <withheld: not a plain name>   undefined escape \\s — use …
untrusted> \\sbad
ok       paths/00-manual    ordinary.md                    engine: accepted

This repository's own tree still names all 8 entries and exits 0.

A correction to the brief, which changed the shape of the fix. jit_report_name() is not in common.sh#113 landed it inside scripts/rebuild-tsv.sh, which another lane owns right now, and jit-misses.sh does not source common.sh at all. Rather than move a function under another agent's feet, the canonical copy is appended to common.sh and rebuild-tsv.sh is left untouched: it sources common.sh and then redefines, so its copy wins there and the conflict surface is zero. A drift test pins the two, verified by injecting a 64-to-32 change into the rebuild-tsv.sh copy and watching it go red, and tooling.md records that the duplicate should be deleted once that lane clears.

#125

jit-misses.sh wrote its refusals to stdout while tooling.md promises stderr. Stdout is the report: a caller redirecting it captured the refusal as though it were a finding. All six refusal paths now go to stderr.

The three inside awk END are routed by buffering the run and choosing the stream from the exit code — deliberately not the /dev/stderr filename. All three awks here special-case that name, but an awk that opened it as a real file would abort where it does not exist, trading a stream bug for a silent tool.

Verification

Red, before the implementation existed: test-dry-run-names.sh 12 passed, 8 failed; test-jit-misses.sh 127/142, 15 failed.

Maintainer re-ran both against a clean checkout of main: 15 passed, 13 failed and 128/146, 18 failed — more failures than reported on both, not fewer.

Green: 28/28 and 148/148; bash tests/run-all.sh all suites passed; shellcheck -S warning clean; assemble_changelog.py --check rc 0.

Both directions in one tree: a hostile rule is withheld while an ordinary one is named; jit-misses puts the refusal on stderr with an empty report file, and an ordinary run's report on stdout with 0 bytes on stderr.

Two assertions in test-jit-dry-run.sh were reversed — they asserted the superseded README claim.

Review: flagged 5, fixed 4, refused 1

  • report_hook() printed fired entry names raw in Phase 2, underneath the new note claiming to describe the whole report. Real, and now pinned by a test.
  • common.sh and tooling.md both claimed an existing test pinned the two jit_report_name() copies. It did not. The drift test was written in response.
  • The jit-misses.sh exit-code comment claimed 2 was only ours; gawk and one-true-awk both exit 2 on a fatal. Behaviour was right, reasoning wrong.
  • An undriven prompts == 0 branch and a newline-free hostile STALE name.
  • Refused: a row position for the STALE report. STALE's printed remedy is rebuild-tsv.sh, which fixes every stale row in the layer at once, so there is nothing to hand-edit; a glob ordinal would be a handle that moves when the directory does and that no other tool agrees with. The reason is now written into the code, since the reviewer's confusion is evidence it was not.

Adjacent

Fixed in blast radius: the index's tool column was also raw tree text on the substring-rule line.

For filing: rebuild-tsv.sh should lose its duplicate jit_report_name() once the other lane lands — one hunk, and the drift test declares itself NOT EVALUATED loudly the moment it disappears.

Worth knowing, and it bit the maintainer writing this PR: this repository's own no-shell-writes-to-the-index.md block rule fires on a heredoc body that merely mentions a redirect beside the generated index filename. It made authoring the fixture impossible from inside a session running the plugin, and it blocked this description on the first attempt. Writes routed through a variable and documented in the test header. That is #92's stated non-goal, so it is left alone.

🤖 Generated with Claude Code

Florian DAVID and others added 2 commits August 13, 2026 18:12
…ld (#124, #125)

layer directory name are text a stranger picked. `jit-dry-run.sh` echoed both
verbatim at fifteen sites, while `print_untrusted()` -- the one function in that
file whose job is framing tree text -- was called on patterns alone.

Third instance of one channel: #35 in pre-tool-hook.sh, #113 across five reports
in rebuild-tsv.sh, and now the linter jit_refusal_notice() recommends by name.
That notice locates a refused row BY POSITION so its file-name column never
reaches the model, then closes by telling the reader to run the command that
printed it.

jit_report_name() moves from rebuild-tsv.sh to common.sh and every name site in
jit-dry-run.sh goes through it. Same policy, one copy: a second, subtly different
answer to this question would itself be the defect. rebuild-tsv.sh is another
lane file this hour and keeps its copy, which sources cleanly and is pinned to
the same behaviour by tests/test-report-names.sh.

A newline in a name forged a whole REFUSED row in the tool own voice. An index
row cannot carry one; a layer directory and an entry file on disk both can, and
the STALE report and the whole-body budget read those from the filesystem. Both
reproduced on APFS before the fix.

The linter keeps its reason to exist: the pattern is still verbatim on its own
`untrusted>` line, and check_entry_file() -- whose whole class is unprintable,
since a name carrying a separator is never a plain name either -- now closes with
the row position, worded as jit_row_id() words it for the hooks.

README.md claimed this tool "prints that text framed as untrusted". That
described a note above the output, not containment, and it was the sentence a
reader checked before trusting the report. tests/test-jit-dry-run.sh asserted the
same superseded claim; that section is reversed rather than deleted.

stdout, which is the report. A redirected report captured
"SKIPPED -- the file is empty" into the findings file under no heading. The three
refusals built inside the awk END block are routed by buffering the run and
choosing the stream from its exit code -- not `print > "/dev/stderr"`, because
Git Bash is a leg nobody here can observe and an awk that opened it as a real
file would abort where it does not exist. --help is not a refusal and keeps
stdout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three review findings, all applied.

report_hook() -- the sample-call half of the report -- reads entry names back out
of the hook own injected output rather than from an index row, so a sweep of the
index readers missed it. It printed them raw, under a note claiming to describe
every line below it. The channel is narrower (the grep stops at a space) and not
closed: a hyphenated instruction needs no space, and nothing bounded the length.
Display goes through jit_report_name(); the real name is kept everywhere it is
used to look something up, and the dedup list moved off the rendered string
because two withheld names render identically and would collapse into one row.

common.sh and tooling.md both said tests/test-report-names.sh pinned the two
copies of jit_report_name() to each other. It does not -- it only drives
rebuild-tsv.sh. Nothing enforced the claim. tests/test-dry-run-names.sh now
extracts the rebuild-tsv.sh copy and drives both over eleven cases including
both sides of the 64-byte cap; verified by injecting a 64 -> 32 drift and
watching it go red.

The jit-misses.sh comment claimed exit 2 could only be its own refusal. gawk and
one-true-awk both exit 2 on a fatal. The behaviour is right and the reasoning
was not: an awk that aborted mid-report produced a PARTIAL report, and a partial
report on stdout reads as a complete one. Both cases land on stderr with status
2, which is what the header already promises for a log that could not be
evaluated.

Refused: a row position for the STALE report. There is none to give -- that loop
walks a glob, not an index -- and the remedy is the difference. A refused
entry-file row is found and renamed one row at a time; a stale row is fixed by
the rebuild the next line already names, for every stale entry at once. Written
into the code rather than argued here.

Two fixture gaps closed with it: a STALE name that is hostile without a newline,
so that assertion is driven on a filesystem that refuses control characters too,
and the second reachable awk END refusal in jit-misses.sh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fdaviddpt
fdaviddpt merged commit 56b05f7 into main Aug 14, 2026
5 checks passed
@fdaviddpt
fdaviddpt deleted the fix/124-125 branch August 14, 2026 05:11
fdaviddpt added a commit that referenced this pull request Aug 14, 2026
…ing at the wrong pair (#131) (#142)

#129 left rebuild-tsv.sh's #113 copy in place because that file was owned by
another lane. The lane cleared, so the copy is deleted: rebuild-tsv.sh sources
common.sh and every bash call site there is now the one definition. Driven both
directions through the tool afterwards -- a plain entry name still prints, a name
carrying a space is still withheld -- and the report is byte-identical apart from
its timing line.

tests/test-dry-run-names.sh compared the two bash copies and anticipated the
deletion with a NOT EVALUATED block. Neither offered option was right: a block
that can only ever say it did not run is decoration, and deleting it outright
would have thrown away eleven boundary cases that DO still have a subject.
rebuild-tsv.sh builds three reports inside awk, awk cannot source a bash file,
and JIT_AWK_REPORT_NAME restates the same character set in another language --
so of the two pairs, the one that was pinned was the one about to stop existing.
The loop now drives bash against awk, and the two positive controls moved out of
the conditional so a failed extraction cannot take them down with it.

tooling.md's note that the copy exists went stale with it, and is rewritten in
the same commit. Body-only edit; the frontmatter is untouched, so no index
rebuild.

Co-authored-by: Florian DAVID <fdavid@digital-village.fr>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant