refactor: simplify pass (trekexecute)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-28 16:34:32 +01:00
commit c75ae2d3f5
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
4 changed files with 39 additions and 33 deletions

View file

@ -272,15 +272,18 @@ const commandView = commandPositionView(deobfuscated.replace(/\x1B\[[0-9;]*m/g,
// close early and an unquoted opener has nothing to expand, so it is skipped. // close early and an unquoted opener has nothing to expand, so it is skipped.
// An opener with a non-blank line after it is blocked unless it is quoted // An opener with a non-blank line after it is blocked unless it is quoted
// (<<'D', <<"D" or <<\D, whose body is not expanded) and exactly one line, // (<<'D', <<"D" or <<\D, whose body is not expanded) and exactly one line,
// trimmed, equals the delimiter with every line after that one empty. // trimmed, equals the delimiter with every line after that one empty. A
// command that never names the delimiter skips the whole check.
const ARGS_DELIM = 'VOYAGE_ARGS_EOF_7F3C9E'; const ARGS_DELIM = 'VOYAGE_ARGS_EOF_7F3C9E';
if (command.includes(ARGS_DELIM)) {
const argsOpeners = command.matchAll(new RegExp(`<<-?\\s*(['"\\\\]?)${ARGS_DELIM}(['"]?)`, 'g')); const argsOpeners = command.matchAll(new RegExp(`<<-?\\s*(['"\\\\]?)${ARGS_DELIM}(['"]?)`, 'g'));
const argsLines = command.split('\n'); const argsLines = command.split('\n');
const argsClosing = argsLines.flatMap((l, i) => (l.trim() === ARGS_DELIM ? [i] : [])); const argsClosing = argsLines.flatMap((l, i) => (l.trim() === ARGS_DELIM ? [i] : []));
const closesOnceAtEnd = argsClosing.length === 1 && const closesOnceAtEnd = argsClosing.length === 1 &&
argsLines.slice(argsClosing[0] + 1).every((line) => line.trim() === ''); argsLines.slice(argsClosing[0] + 1).every((line) => line.trim() === '');
for (const argsOpener of argsOpeners) { for (const argsOpener of argsOpeners) {
const hasBody = command.slice(argsOpener.index).split('\n').slice(1).some((l) => l.trim() !== ''); // Some line after the opener's line is non-blank (\s is the set trim() strips).
const hasBody = /\n\s*\S/.test(command.slice(argsOpener.index));
if (!hasBody) continue; if (!hasBody) continue;
const [, open, close] = argsOpener; const [, open, close] = argsOpener;
const quoted = open === '\\' ? close === '' : open !== '' && close === open; const quoted = open === '\\' ? close === '' : open !== '' && close === open;
@ -298,6 +301,7 @@ for (const argsOpener of argsOpeners) {
process.exit(2); process.exit(2);
} }
} }
}
// END args-heredoc-closed-early // END args-heredoc-closed-early
// Check BLOCK rules first // Check BLOCK rules first

View file

@ -330,7 +330,9 @@ function atomicWriteText(repo, rel, text) {
* 1 = not ignored; a spawn error or any exit other than 0, 1 or 128 is a * 1 = not ignored; a spawn error or any exit other than 0, 1 or 128 is a
* failed check. Exit 128 means "not a git repository" only some of the time: * failed check. Exit 128 means "not a git repository" only some of the time:
* inside a repository it also comes from a path beyond a symbolic link, a * inside a repository it also comes from a path beyond a symbolic link, a
* broken `.git/HEAD` or a `GIT_DIR` that does not exist (CKI). So on 128 a * broken `.git/HEAD` or a `GIT_DIR` that does not exist (CKI), and the probe's
* own `git rev-parse --git-dir` exits 128 too on dubious ownership, a bad
* `.git/config` or a garbage `.git/HEAD`. So on 128 a
* probe decides (see notARepoReason), and only a directory that is truly no * probe decides (see notARepoReason), and only a directory that is truly no
* repository gives `no-git-repo`; everything else, including a probe that * repository gives `no-git-repo`; everything else, including a probe that
* throws, is `failed`. No branch reads git's message text, so the answer does * throws, is `failed`. No branch reads git's message text, so the answer does
@ -360,9 +362,8 @@ function lessonsIgnored(repo, rel) {
/** /**
* After a check-ignore exit 128: null when `repo` is no git repository, else * After a check-ignore exit 128: null when `repo` is no git repository, else
* the reason it may be one. `git rev-parse --git-dir` exit 0 proves a * the reason it may be one. `git rev-parse --git-dir` exit 0 proves a
* repository; its exit 128 alone proves nothing (dubious ownership, a bad * repository; its exit 128 alone proves nothing (see lessonsIgnored), so then
* `.git/config` and a garbage `.git/HEAD` all exit 128 there), so then the * the answer is null only when GIT_DIR is unset and no `.git` entry (file or
* answer is null only when GIT_DIR is unset and no `.git` entry (file or
* directory) is found by gitEntryDir. A spawn error or any other exit is a * directory) is found by gitEntryDir. A spawn error or any other exit is a
* reason too. May throw (realpath of a vanished repo); the caller reads that * reason too. May throw (realpath of a vanished repo); the caller reads that
* as a failed check. * as a failed check.
@ -417,10 +418,10 @@ export function withPointer(claudeText) {
* The result's `pointer` (when written): 'written' | 'present' | 'no-claude-md' | * The result's `pointer` (when written): 'written' | 'present' | 'no-claude-md' |
* 'symlink-skipped' (CLAUDE.md is a symlink, DJ4) | 'ignored-skipped' (git * 'symlink-skipped' (CLAUDE.md is a symlink, DJ4) | 'ignored-skipped' (git
* ignores the lessons file, PTR) | 'check-failed' (git check-ignore did not * ignores the lessons file, PTR) | 'check-failed' (git check-ignore did not
* answer, or answered exit 128 for a directory that may be a repository: a * answer, or answered exit 128 for a directory that may be a repository, see
* path beyond a symbolic link, a broken repository, GIT_DIR set, or a `.git` * lessonsIgnored; `pointer_note` says how, and always names check-ignore).
* entry found; `pointer_note` says how, and always names check-ignore). Only * Only a directory that is truly no repository gets the pointer without the
* a directory that is truly no repository gets the pointer without the check. * check.
* Nothing is written through a symlinked * Nothing is written through a symlinked
* lessons file or a lessons directory outside the repo (R3): written false. * lessons file or a lessons directory outside the repo (R3): written false.
* @returns {{ written: boolean, reason: string, path?: string, pointer?: string, pointer_note?: string }} * @returns {{ written: boolean, reason: string, path?: string, pointer?: string, pointer_note?: string }}

View file

@ -73,7 +73,7 @@ hookrun() {
out="$1" out="$1"
shift shift
node -e 'process.stdout.write(JSON.stringify({ tool_name: "Bash", tool_input: { command: process.argv.slice(1).join("\n") } }));' "$@" \ node -e 'process.stdout.write(JSON.stringify({ tool_name: "Bash", tool_input: { command: process.argv.slice(1).join("\n") } }));' "$@" \
| node "$HOOK" > "$out.stdout" 2> "$out" | node "$HOOK" > /dev/null 2> "$out"
} }
D=VOYAGE_ARGS_EOF_7F3C9E D=VOYAGE_ARGS_EOF_7F3C9E

View file

@ -52,7 +52,8 @@ test('part-2 scripts: each is ASCII only, passes /bin/bash -n and uses no bash 4
const r = spawnSync('/bin/bash', ['-n', join(ROOT, rel)], { encoding: 'utf8', env: childEnv() }); const r = spawnSync('/bin/bash', ['-n', join(ROOT, rel)], { encoding: 'utf8', env: childEnv() });
assert.equal(r.status, 0, `${rel} does not pass /bin/bash -n: ${r.stderr}`); assert.equal(r.status, 0, `${rel} does not pass /bin/bash -n: ${r.stderr}`);
const text = buf.toString('utf8'); const text = buf.toString('utf8');
for (const bash4 of [/\bdeclare -A\b/, /\bmapfile\b/, /\breadarray\b/, /\|&/]) { for (const bash4 of [/\bdeclare -A\b/, /\bmapfile\b/, /\breadarray\b/, /\|&/, /^\s*[A-Za-z_]+=\(/m,
/\$\{[A-Za-z_]+,,\}/, /\$\{[A-Za-z_]+\^\^\}/]) {
assert.doesNotMatch(text, bash4, `${rel} uses ${bash4}, which bash 3.2 lacks`); assert.doesNotMatch(text, bash4, `${rel} uses ${bash4}, which bash 3.2 lacks`);
} }
} }