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
|
|
@ -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