docs(changelog): fix round 4.5b part 4, the review's other findings as known issues, and the TOK claim without never
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9f7ba6503f
commit
8b9b9439cf
4 changed files with 169 additions and 4 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
|
@ -73,7 +73,7 @@ Fix round 4.5b (decisions 80 to 87): the round's 39 findings, from the 4.5a revi
|
|||
|
||||
Part 2 fixes the four defects that part 1 introduced and its own review found, plus the Bash guard's first-opener-only check; each has a test seen failing first at part 1's end point `52c382c` (`tests/fix-round-4-5b-part-2-red.sh` proves it).
|
||||
|
||||
- **A quoted value stops at a quote that starts a word, and never swallows a later flag** (TOK). In `lib/parsers/arg-parser.mjs`, a closing quote must end a word, and the search for it stops at a quote of the same kind that starts a word; with no pair, the opening quote stays a literal character. Before, `--project "my dir"/x --since "HEAD~1"` gave one `--project` value that held `--since`, so `/trekreview` reviewed the wrong range; now `--since` is `HEAD~1`. An apostrophe inside a word keeps its value (`'Fix the user's login'` is one value), and nothing is expanded.
|
||||
- **A quoted value stops at a quote that starts a word** (TOK). In `lib/parsers/arg-parser.mjs`, a closing quote must end a word, and the search for it stops at a quote of the same kind that starts a word; with no pair, the opening quote stays a literal character. Before, `--project "my dir"/x --since "HEAD~1"` gave one `--project` value that held `--since`, so `/trekreview` reviewed the wrong range; now `--since` is `HEAD~1`. An apostrophe inside a word keeps its value (`'Fix the user's login'` is one value), and nothing is expanded.
|
||||
- **The Bash guard checks every opener of the parser's heredoc that has a line after it, and lets a one-line mention through** (HDG). `hooks/scripts/pre-bash-executor.mjs` checks an opener of `VOYAGE_ARGS_EOF_7F3C9E` only when a non-blank line follows the opener's line, because bash reads a heredoc body only from the lines after it, so a one-line `grep`, `sed` or `git commit -m` that mentions the opener is no longer refused. It now checks every opener, not only the first, so a quoted first mention can no longer hide a later unquoted heredoc whose arguments would run as commands.
|
||||
- **A failed `git check-ignore` writes no lessons pointer, whatever language git speaks** (CKI). `lib/review/recurrence.mjs` no longer reads every `git check-ignore` exit 128 as "no repository": on 128 it runs `git rev-parse --git-dir` and looks for a `.git` entry from the repo up to the nearest `GIT_CEILING_DIRECTORIES` entry above it, and writes the `CLAUDE.md` pointer only when rev-parse also exits 128, `GIT_DIR` is unset and no `.git` entry is found. Anything else gives `check-failed` with a note that names the reason, and no branch reads git's message text. A `.claude` that is a symlink inside the repo now gives `check-failed` (no pointer), also when it points at a tracked directory.
|
||||
- **The executor records the full commit id for the done line** (DLC). `/trekexecute` Sub-step G says `{hash}` is the full commit id from `git rev-parse HEAD` after the step's last commit, read again after any later commit of the step, never the short id `git commit` prints: `lib/verification/done-line.mjs --check` refuses a short id with exit 2, which sent a passing step to the plan. The progress line stores that full id, and Handover 5's `commit` row says so.
|
||||
|
|
@ -85,6 +85,15 @@ Part 3 fixes two MAJOR findings of part 2's review, in the two places part 2 rew
|
|||
- **A quoted value closes at the first quote of its kind that ends a word, whatever stands before it** (CQW). In `lib/parsers/arg-parser.mjs`, the closing quote no longer has to follow a non-blank character. So `"my dir "`, `" "` and a value closed on its own line pair again. A quote that starts a word still stops the search, so `--since` after `"my dir"/x` stays `HEAD~1`. One qualification: a value whose text ends in whitespace may now hold what follows it, up to a quote that ends a word (see Known issues). It names review finding `87a693f0`.
|
||||
- **The Bash guard also reads a heredoc opener's word as the shell does** (PQO). `hooks/scripts/pre-bash-executor.mjs` reads the word when it is partly quoted, `$'...'`, `$"..."`, or holds a backslash-newline anywhere from the first `<` to the end of the word. It checks every opener whose word is the parser's delimiter, and treats a word it cannot read as unquoted. Every command it blocked before stays blocked. A typed argument whose `<<` runs into a quote with no partner (`fix "<<" handling`) still passes inside the commands' argument heredoc. It names review finding `021996c6`.
|
||||
|
||||
**Fix round 4.5b, part 4 (decision 90)**
|
||||
|
||||
Part 4 fixes four MAJOR findings of part 3's review in the Bash guard's check of the argument heredoc; each has a test seen failing first at part 3's end point `591f3c4` (`tests/fix-round-4-5b-part-4-red.sh` proves it). The tokenizer headline in part 2's group above no longer claims more than the tokenizer does (see the tokenizer Known issue).
|
||||
|
||||
- **The guard checks the parser's delimiter when a backslash-newline splits it** (CSG). `hooks/scripts/pre-bash-executor.mjs` checks a command that names the parser's delimiter only once a backslash-newline is removed, so an unquoted opener split that way with a body and no closing line is blocked as unquoted; such a command with a quoted opener and no closing line, or with an unreadable opener elsewhere, is now checked too and refused, as it would be with the delimiter written out. It names review finding `3a34adbe`.
|
||||
- **A `<<` cut by the quote that closes a string on its line is text** (SLT). A `<<` inside a string on the opener's line, whose cutting quote closes that string, is text and passes, so commit messages, grep patterns and file writes that hold `"<<"` and name the delimiter pass again; a quote with no string open before it stays unreadable and blocked; a string opened on an earlier line is still refused (see Known issues). It names review finding `ad8ad828`.
|
||||
- **A backslash-escaped `<` with no quote or heredoc operator before it starts no opener** (ESC). A `<` after an odd run of backslashes starts no opener when no quote, backquote or heredoc operator stands before the run, in both readings, so `echo \<<` followed by the delimiter and a line passes; the `<` after it is read as usual, so a `<<` right after an escaped `<`, which the guard took for part of a here-string before, is now checked; an escaped `<` inside a heredoc body fed to a shell is still checked. It names review finding `797784e0`.
|
||||
- **The guard's error path is tested** (CTB). A test forces an error inside the guard's shell reading and sees it block with exit 2, through a test-side preload; the hook gains no switch. It names review finding `1658fc64`.
|
||||
|
||||
Fix round 4.5a (decisions 57, 67 and 68): the round's 30 findings, where a Voyage control could be bypassed or was too lax, or where the text gave a guarantee no code held. Each is fixed, and each has a test that fails on the code before it, except DJ, whose sentences already stood and whose pins fail when they are deleted (`tests/fix-round-4-5a-red.sh` proves every other id red at the round's start point, and DJ under its two mutations).
|
||||
|
||||
**Execute**
|
||||
|
|
@ -169,10 +178,16 @@ Fix round 4.5a (decisions 57, 67 and 68): the round's 30 findings, where a Voyag
|
|||
- **The Bash guard knows one heredoc delimiter** (MINOR `8959d2cd`, `hooks/scripts/pre-bash-executor.mjs`; decision 88). It has no general rule for a quoted heredoc's closing line, so another heredoc in the commands, such as a stats record's, can still be closed early by a typed line equal to its delimiter.
|
||||
- **A command that mentions the parser's heredoc opener with a line after it stays blocked** (fix round 4.5b part 2, decision 88). The guard checks every opener of `VOYAGE_ARGS_EOF_7F3C9E` that has a non-blank line after the opener's line, so such a command stays blocked unless it is a quoted heredoc whose closing line is the one last non-empty line, for example a commit message written as a heredoc that mentions the opener. Write it on one line, or read it from a file.
|
||||
- **A `bash -c $'...'` command on one physical line is not checked** (fix round 4.5b part 2, decision 88). It carries no literal newline, so the guard sees no line after the opener even when `bash -c` expands it into a heredoc of the parser's delimiter; part 1 blocked it by accident. The shipped commands never produce it from `$ARGUMENTS`.
|
||||
- **The tokenizer splits a quoted value at a quote of the same kind that starts a word** (TOK, decision 88). `'the '90s were great'` gives `'the` and `90s were great` (accepted by the operator on 2026-10-27, part 2's brief). The argv form's self-quoting of a word that holds spaces (`-- 'say "hi"'`) splits (accepted on 2026-10-28). Since part 3, a quoted value that ends in whitespace pairs again, so such a value may also hold what follows it up to a quote that ends a word: `--project "x --since " HEAD` gives `--project` = `x --since `.
|
||||
- **The tokenizer splits a quoted value at a quote of the same kind that starts a word** (TOK, decision 88). `'the '90s were great'` gives `'the` and `90s were great` (accepted by the operator on 2026-10-27, part 2's brief). The argv form's self-quoting of a word that holds spaces (`-- 'say "hi"'`) splits (accepted on 2026-10-28). Since part 3, a quoted value that ends in whitespace pairs again, so such a value may also hold what follows it up to a quote that ends a word: `--project "x --since " HEAD` gives `--project` = `x --since `. Review finding `564d6dff` (decision 90) names this case: such a value can swallow a later flag with no error.
|
||||
- **`gitEntryDir` does not follow git's `GIT_CEILING_DIRECTORIES` rules** (MAJOR `3b98d18b`, `lib/review/recurrence.mjs`; decision 89). It resolves the entries after an empty one through realpath, and it keeps relative entries and entries whose realpath fails. So a ceiling written that way can give `no-git-repo` and a written pointer where git sees a repository.
|
||||
- **A failed checkpoint commit leaves `{hash}` on the previous step's commit** (MAJOR `69b086eb`, `commands/trekexecute.md`; decision 89). When the checkpoint commit fails, `{hash}` is the current HEAD, which is the previous step's commit. So the done line and the progress record name a commit without step N's changes.
|
||||
- **Some typed arguments that hold `<<` are refused** (fix round 4.5b part 3, decision 89). A typed argument holding `<<` followed by a word the guard cannot read, other than through a quote with no partner (for example `<<$'\t'` or a backslash inside `"..."`), is refused by `/trekreview`, `/trekcontinue` and `/trekendsession`; write the argument another way.
|
||||
- **Part 3 added a reader rule without a plan revision** (MAJOR `4a880f70`, `hooks/scripts/pre-bash-executor.mjs`; decision 90). Part 3's execution added the reader rule for a `$` followed by a backslash-newline and a quote (the word is unreadable) without a plan revision; it stays, and part 4 runs its pins under `npm test`.
|
||||
- **Part 3 compares the opener's word after `trim()`** (MAJOR `fb643fb0`, `hooks/scripts/pre-bash-executor.mjs`; decision 90). Part 3's execution compares the opener's word after `trim()`, not exactly as its plan said, without a plan revision; it stays, pinned the same way.
|
||||
- **An unreadable opener gets the unquoted reason** (MAJOR `73eed642`, `hooks/scripts/pre-bash-executor.mjs`; decision 90). An opener blocked because its word cannot be read is given the unquoted reason and told to open the heredoc quoted, also when its word is not the delimiter and the argument heredoc is already quoted; write the argument another way.
|
||||
- **Part 3's entry did not name the false stops of a string-borne `<<`** (MAJOR `6f09b931`, `CHANGELOG.md`; decision 90). Part 4's SLT bullet lets most of them through, and a command whose string opens on an earlier line than its `<<`, for example a commit message heredoc whose quoted text spans two lines, is still refused.
|
||||
- **An error inside the guard is reported as the command's fault** (MAJOR `161545f6`, `hooks/scripts/pre-bash-executor.mjs`; decision 90). An error inside the guard's own reading is reported as an unquoted opener whose word could not be read, which blames the command for the guard's fault.
|
||||
- **The guard re-splits the command for each candidate opener** (MINOR `fe3fe55b`, `hooks/scripts/pre-bash-executor.mjs`; decision 90). For each candidate opener the guard re-splits the command to find its line, quadratic work on a large command.
|
||||
|
||||
### Doors
|
||||
|
||||
|
|
@ -184,6 +199,7 @@ Fix round 4.5a (decisions 57, 67 and 68): the round's 30 findings, where a Voyag
|
|||
- two-way: fix round 4.5b (decisions 80 to 87) — a revert restores the old checks, texts and red script and takes out the Bash guard's heredoc rule; `review.md` keeps `review_version` 1.1 either way.
|
||||
- two-way: fix round 4.5b part 2 (decision 88) — a revert restores part 1's four behaviours (the tokenizer's quote search, the guard's refusal of a one-line mention, the pointer written on any `git check-ignore` exit 128, the undefined `{hash}`) and the guard's first-opener-only check.
|
||||
- two-way: fix round 4.5b part 3 (decision 89) — a revert restores part 2's closing-quote search (a value whose closing quote follows whitespace splits again) and the guard's regex-only opener check.
|
||||
- two-way: fix round 4.5b part 4 (decision 90) — a revert restores part 3's literal-delimiter gate and its refusal of a heredoc operator inside a string or after a backslash; the catch-branch test and the TOK wording go with it.
|
||||
|
||||
## v6.5.0 — 2026-10-18 — Three skills for brief, criteria and review, English labels, and three trial-5 fixes
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ PARSER_LINE='node /plugin/lib/parsers/arg-parser.mjs --command trekendsession --
|
|||
# ---- 1. TOK: the arg parser keeps a later flag ----
|
||||
|
||||
case_tok() {
|
||||
name="arg-parser --stdin: a quoted value never swallows --since"
|
||||
name="arg-parser --stdin: a quoted value stops at the quote that starts --since's value"
|
||||
W="$TMP/args"
|
||||
mkdir -p "$W" || { bad "$name: mkdir"; return; }
|
||||
( cd "$W" && node "$ARGS" --command trekreview --stdin > "$TMP/args.json" ) <<'VOYAGE_ARGS_EOF_7F3C9E'
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { parseArgs, tokenize } from '../../lib/parsers/arg-parser.mjs';
|
|||
|
||||
const values = (text) => tokenize(text).map((t) => t.value);
|
||||
|
||||
test('[TOK] a quoted value never swallows a later flag, and an apostrophe inside a word keeps its value', () => {
|
||||
test('[TOK] a quoted value stops at a quote that starts a word, and an apostrophe inside a word keeps its value', () => {
|
||||
const text = '--project "my dir"/x --since "HEAD~1"';
|
||||
assert.equal(parseArgs(text, 'trekreview').flags['--since'], 'HEAD~1',
|
||||
'the quote after `dir` is followed by `/`, so it does not close; the search stops at the quote that starts `"HEAD~1"`');
|
||||
|
|
|
|||
149
tests/lib/fix-round-4-5b-part-4-prose.test.mjs
Normal file
149
tests/lib/fix-round-4-5b-part-4-prose.test.mjs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
// tests/lib/fix-round-4-5b-part-4-prose.test.mjs
|
||||
// Prose pins for fix round 4-5b, part 4 (plan
|
||||
// .claude/projects/2026-10-31-fix-round-4-5b-part-4, decision 90): the fixes
|
||||
// that are text a session reads. The tag in brackets is the part-4 id from the
|
||||
// round's brief; the tagged test fails on an assertion at 591f3c4, part 3's end
|
||||
// point. The keep-behaviour checks sit inside the tagged test as "Control:"
|
||||
// assertions.
|
||||
// KI4 the CHANGELOG lists part 4's fixes (CSG, SLT, ESC, CTB) under ### Fixed,
|
||||
// its door under ### Doors, the review's other findings under
|
||||
// ### Known issues, and the TOK headline no longer claims `never`.
|
||||
// NVR the [TOK] test title and part 2's end-to-end case name no longer claim
|
||||
// `never`.
|
||||
// No static import of a new export: the file must load at 591f3c4.
|
||||
// Fix the SOURCE a pin guards, not the pin.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
||||
|
||||
const readCache = new Map();
|
||||
|
||||
/** A repo file's text; the file must exist. Read from disk once per run. */
|
||||
function read(rel) {
|
||||
if (!readCache.has(rel)) {
|
||||
assert.ok(existsSync(join(ROOT, rel)), `missing file ${rel}`);
|
||||
readCache.set(rel, readFileSync(join(ROOT, rel), 'utf8'));
|
||||
}
|
||||
return readCache.get(rel);
|
||||
}
|
||||
|
||||
/** Whitespace (line breaks too) collapsed to one space. */
|
||||
const flat = (s) => s.replace(/\s+/g, ' ').trim();
|
||||
|
||||
// --- KI4: the round's CHANGELOG entry ----------------------------------------------
|
||||
// Part 4 stands in the same Unreleased section as parts 1 to 3. The section is
|
||||
// found by its title, by the rule tests/lib/fix-round-4-5b-part-3-prose.test.mjs
|
||||
// uses (copied, not imported): `## Unreleased — <title>` before the release,
|
||||
// `## vX.Y.Z — <date> — <title>` after it, exactly once, up to the next `## `.
|
||||
|
||||
const CHANGELOG_TITLE = "The annotating skill, the run coupling and P10's model-free part";
|
||||
const CHANGELOG_HEADING = new RegExp(`^## (?:Unreleased|v\\d+\\.\\d+\\.\\d+ — \\d{4}-\\d{2}-\\d{2}) — ${CHANGELOG_TITLE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`);
|
||||
|
||||
/** The lines of the round's CHANGELOG section, heading excluded. */
|
||||
function changelogSection() {
|
||||
const lines = read('CHANGELOG.md').split('\n');
|
||||
const at = [];
|
||||
lines.forEach((l, i) => { if (CHANGELOG_HEADING.test(l.replace(/\s+$/, ''))) at.push(i); });
|
||||
assert.equal(at.length, 1, `CHANGELOG.md must hold the "${CHANGELOG_TITLE}" section exactly once (found ${at.length})`);
|
||||
let end = lines.length;
|
||||
for (let i = at[0] + 1; i < lines.length; i++) if (/^## /.test(lines[i])) { end = i; break; }
|
||||
return lines.slice(at[0] + 1, end);
|
||||
}
|
||||
|
||||
/** The bullets of one `### ` subsection of the round's CHANGELOG section, each flattened to one line. */
|
||||
function changelogBullets(heading) {
|
||||
const lines = changelogSection();
|
||||
const s = lines.findIndex((l) => l.replace(/\s+$/, '') === heading);
|
||||
assert.ok(s !== -1, `the "${CHANGELOG_TITLE}" section of CHANGELOG.md must have "${heading}"`);
|
||||
const out = [];
|
||||
for (const l of lines.slice(s + 1)) {
|
||||
if (/^### /.test(l)) break;
|
||||
if (/^- /.test(l)) out.push(l);
|
||||
else if (out.length && l.trim() !== '' && /^\s/.test(l)) out[out.length - 1] += ` ${l}`;
|
||||
else if (l.trim() !== '') out.push('');
|
||||
}
|
||||
return out.filter((b) => b !== '').map(flat);
|
||||
}
|
||||
|
||||
// The review's four MAJOR findings part 4 fixes, with their tags, and the
|
||||
// review's other findings it lists as open; the ids are the review's, written
|
||||
// out here.
|
||||
const FIXED = [['(CSG)', '3a34adbe'], ['(SLT)', 'ad8ad828'], ['(ESC)', '797784e0'], ['(CTB)', '1658fc64']];
|
||||
const OPEN_IDS = ['4a880f70', 'fb643fb0', '73eed642', '6f09b931', '161545f6', 'fe3fe55b'];
|
||||
|
||||
const NEVER = /\bnever\b/i;
|
||||
|
||||
test('[KI4] the CHANGELOG lists part 4\'s four fixes, the Doors line and a TOK headline without never', () => {
|
||||
const fixed = changelogBullets('### Fixed');
|
||||
|
||||
// Red: one ### Fixed bullet per part-4 tag, each naming its review finding.
|
||||
for (const [tag, id] of FIXED) {
|
||||
const hits = fixed.filter((b) => b.includes(tag) && b.includes(id));
|
||||
assert.equal(hits.length, 1, `### Fixed must hold exactly one bullet marked ${tag} naming ${id}; found ${hits.length}`);
|
||||
}
|
||||
|
||||
// Red: a door for the round.
|
||||
const doors = changelogBullets('### Doors').filter((b) => b.includes('decision 90'));
|
||||
assert.equal(doors.length, 1, `### Doors must hold one bullet naming decision 90; found ${doors.length}`);
|
||||
|
||||
// Control: exactly one Fixed bullet is marked (TOK).
|
||||
const tok = fixed.filter((b) => b.includes('(TOK)'));
|
||||
assert.equal(tok.length, 1, `Control: ### Fixed must hold exactly one bullet marked (TOK); found ${tok.length}`);
|
||||
|
||||
// Red: its bold headline no longer claims `never`.
|
||||
const head = /^- \*\*(.+?)\*\*/.exec(tok[0]);
|
||||
assert.ok(head, `the ### Fixed bullet marked (TOK) must open with a bold headline: ${tok[0]}`);
|
||||
assert.ok(!NEVER.test(head[1]), `the (TOK) headline must not hold the word never: ${head[1]}`);
|
||||
|
||||
// Control: no fixed finding is listed as open.
|
||||
const known = changelogBullets('### Known issues');
|
||||
for (const [, id] of FIXED) {
|
||||
assert.deepEqual(known.filter((b) => b.includes(id)), [], `Control: the fixed finding ${id} must not stand under ### Known issues`);
|
||||
}
|
||||
});
|
||||
|
||||
test('[KI4] the CHANGELOG lists the review\'s other findings as known issues', () => {
|
||||
const known = changelogBullets('### Known issues');
|
||||
|
||||
// Red: each open finding has exactly one Known-issues bullet, and that bullet names decision 90.
|
||||
for (const id of OPEN_IDS) {
|
||||
const hits = known.filter((b) => b.includes(id));
|
||||
assert.equal(hits.length, 1, `### Known issues must hold exactly one bullet naming ${id}; found ${hits.length}`);
|
||||
assert.ok(hits[0].includes('decision 90'), `the Known-issues bullet naming ${id} does not say decision 90: ${hits[0]}`);
|
||||
}
|
||||
|
||||
// Red: the tokenizer bullet names review finding 564d6dff.
|
||||
const tok = known.filter((b) => b.includes("'90s"));
|
||||
assert.equal(tok.length, 1, `### Known issues must hold exactly one bullet holding '90s; found ${tok.length}`);
|
||||
assert.ok(tok[0].includes('564d6dff'), `the tokenizer Known-issues bullet must name review finding 564d6dff: ${tok[0]}`);
|
||||
|
||||
// Control: the tokenizer bullet keeps what part 3 pinned.
|
||||
for (const part of ['say "hi"', 'x --since ', '2026-10-27', '2026-10-28']) {
|
||||
assert.ok(tok[0].includes(part), `Control: the tokenizer Known-issues bullet must hold ${JSON.stringify(part)}: ${tok[0]}`);
|
||||
}
|
||||
for (const gone of ['"a "', 'accepted these']) {
|
||||
assert.ok(!tok[0].includes(gone), `Control: the tokenizer Known-issues bullet must not hold ${JSON.stringify(gone)}: ${tok[0]}`);
|
||||
}
|
||||
});
|
||||
|
||||
// --- NVR: the [TOK] test title and part 2's case name ------------------------------
|
||||
|
||||
test('[NVR] the [TOK] test title and part 2\'s case name no longer claim never', () => {
|
||||
// The one test title in the tokenizer tests that starts with [TOK], read as text.
|
||||
const titles = [...read('tests/lib/arg-parser-tokenize.test.mjs').matchAll(/test\('\[TOK\] ([^']*)'/g)].map((m) => m[1]);
|
||||
assert.equal(titles.length, 1, `tests/lib/arg-parser-tokenize.test.mjs must hold exactly one test title starting with [TOK]; found ${titles.length}`);
|
||||
assert.ok(!NEVER.test(titles[0]), `the [TOK] test title must not hold the word never: ${titles[0]}`);
|
||||
|
||||
// The first name=" line after `case_tok() {` in part 2's end-to-end script.
|
||||
const lines = read('tests/e2e-fix-round-4-5b-part-2.sh').split('\n');
|
||||
const starts = lines.map((l, i) => (l.trim() === 'case_tok() {' ? i : -1)).filter((i) => i !== -1);
|
||||
assert.equal(starts.length, 1, `tests/e2e-fix-round-4-5b-part-2.sh must define case_tok() exactly once; found ${starts.length}`);
|
||||
const names = lines.slice(starts[0] + 1).filter((l) => /^\s*name="/.test(l));
|
||||
assert.ok(names.length >= 1, 'case_tok() in tests/e2e-fix-round-4-5b-part-2.sh must set name=');
|
||||
assert.ok(!NEVER.test(names[0]), `the name= line of case_tok() must not hold the word never: ${names[0]}`);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue