docs(plan): Handover 4, the planner and the template follow the 1.9 fixes
Operator decisions 20 and 22. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0142034979
commit
5fa956b974
4 changed files with 133 additions and 11 deletions
|
|
@ -41,6 +41,38 @@ function sentence(text, anchor, label) {
|
|||
return text.slice(start, end);
|
||||
}
|
||||
|
||||
// A line that opens a list item: its indent, then `-`, `*`, `+`, `1.` or `1)`.
|
||||
const LIST_ITEM_LINE = /^([ \t>]*)(?:[-*+]|\d+[.)]) /;
|
||||
|
||||
/**
|
||||
* The list item of `text` that holds the first match of `anchor`: from the
|
||||
* start of the line that opens it (`- `) to the next line that opens a list
|
||||
* item at the same indent (or a smaller one, which closes its list too), a
|
||||
* blank line, or the end of the text. A wrapped line of the item belongs to it.
|
||||
*/
|
||||
function bullet(text, anchor, label) {
|
||||
const m = anchor.exec(text);
|
||||
assert.ok(m, `${label}: anchor ${anchor} not found`);
|
||||
const lines = text.split('\n');
|
||||
let at = 0;
|
||||
let i = 0;
|
||||
while (i < lines.length - 1 && at + lines[i].length < m.index) { at += lines[i].length + 1; i++; }
|
||||
let first = i;
|
||||
while (first >= 0 && !LIST_ITEM_LINE.test(lines[first])) {
|
||||
assert.ok(lines[first].trim() !== '', `${label}: the match of ${anchor} stands in no list item`);
|
||||
first--;
|
||||
}
|
||||
assert.ok(first >= 0, `${label}: the match of ${anchor} stands in no list item`);
|
||||
const indent = LIST_ITEM_LINE.exec(lines[first])[1].length;
|
||||
let last = first + 1;
|
||||
for (; last < lines.length; last++) {
|
||||
if (lines[last].trim() === '' || /^[ \t>]*$/.test(lines[last])) break;
|
||||
const item = LIST_ITEM_LINE.exec(lines[last]);
|
||||
if (item && item[1].length <= indent) break;
|
||||
}
|
||||
return lines.slice(first, last).join('\n');
|
||||
}
|
||||
|
||||
function holds18OrLater(text, anchor, label) {
|
||||
const s = sentence(text, anchor, label);
|
||||
assert.match(s, /1\.8 or later/, `${label} must say "1.8 or later" in the sentence that carries the rule: ${JSON.stringify(s)}`);
|
||||
|
|
@ -126,6 +158,81 @@ test('/trekplan: the 1.8 additions heading says "1.8 or later", and a separate 1
|
|||
assert.ok(block.includes(ANNOTATION), `the 1.9 block says "${ANNOTATION}"`);
|
||||
});
|
||||
|
||||
// Step 12: the docs follow the 1.9 fixes. Operator decision 20 (12.10): the
|
||||
// `Test first:` files are the backticked paths right after `File:`, and the
|
||||
// red proof checks `--tests` against them (`tests-mismatch`). The 1.9 `File:`
|
||||
// line takes the numbered markers, a wrapped line joins it, a list item ends
|
||||
// it. Operator decision 22 (13.10): a backticked placeholder or a mixed-case
|
||||
// bare name is no path (`./Name` names such a file); the bare word none,
|
||||
// unticked, stays PLAN_STEP_PATH_NOT_FIRST.
|
||||
const BOTH_NONES = ['the bare word none', 'a backticked placeholder'];
|
||||
|
||||
function holdsBothNones(text, label) {
|
||||
for (const phrase of BOTH_NONES) {
|
||||
assert.ok(text.includes(phrase), `${label} must say "${phrase}": ${JSON.stringify(text)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const invariant7 = () => {
|
||||
const h4 = handover4();
|
||||
const m7 = /^7\. \*\*Every plan_version/m.exec(h4);
|
||||
assert.ok(m7, 'Handover 4 has invariant 7');
|
||||
const m8 = /^8\. \*\*/m.exec(h4.slice(m7.index));
|
||||
return h4.slice(m7.index, m8 ? m7.index + m8.index : undefined);
|
||||
};
|
||||
|
||||
const trekplan19Block = () => {
|
||||
const p = read('commands/trekplan.md');
|
||||
const h19 = p.indexOf('**plan_version 1.9 additions**');
|
||||
assert.ok(h19 >= 0, 'commands/trekplan.md has the "**plan_version 1.9 additions**" block');
|
||||
const end = p.indexOf('\n>\n> **', h19);
|
||||
return p.slice(h19, end < 0 ? undefined : end);
|
||||
};
|
||||
|
||||
const template19Paragraph = () => {
|
||||
const t = read('templates/plan-template.md');
|
||||
const start = t.indexOf('From plan_version 1.9 the rule is the file first on every line');
|
||||
assert.ok(start >= 0, 'the template has its 1.9 paragraph');
|
||||
const end = t.indexOf('\n### Step 1:', start);
|
||||
return t.slice(start, end < 0 ? undefined : end);
|
||||
};
|
||||
|
||||
test('Handover 4 invariant 7 names operator decision 20 and the red proof\'s tests-mismatch', () => {
|
||||
const inv7 = invariant7();
|
||||
assert.match(inv7, /operator decision 20\b/, `invariant 7 names operator decision 20: ${JSON.stringify(inv7)}`);
|
||||
assert.ok(inv7.includes('`tests-mismatch`'), `invariant 7 names \`tests-mismatch\`: ${JSON.stringify(inv7)}`);
|
||||
});
|
||||
|
||||
test('Handover 4 invariant 8, the Test first bullet: numbered markers, the wrapped line, a list item ends the entry', () => {
|
||||
const b = bullet(handover4(), /A line counts only when/, 'invariant 8 Test first bullet');
|
||||
assert.ok(b.includes('`1.`') && b.includes('`1)`'), `the Test first bullet names the \`1.\` and \`1)\` markers: ${JSON.stringify(b)}`);
|
||||
assert.match(b, /wrapped/, `the Test first bullet names the wrapped line: ${JSON.stringify(b)}`);
|
||||
assert.match(b, /any list item\b[^;]*\bends\b/, `the Test first bullet says any list item ends the entry: ${JSON.stringify(b)}`);
|
||||
});
|
||||
|
||||
test('Handover 4 invariant 8, the refusal bullet: operator decision 22 with PLAN_STEP_PATH_NOT_A_PATH, and both nones', () => {
|
||||
const b = bullet(handover4(), /an entry that does not open with a backticked span/, 'invariant 8 refusal bullet');
|
||||
const s = sentence(b, /operator decision 22\b/, 'invariant 8 refusal bullet, decision 22');
|
||||
assert.ok(s.includes('`PLAN_STEP_PATH_NOT_A_PATH`'), `operator decision 22 stands with \`PLAN_STEP_PATH_NOT_A_PATH\`: ${JSON.stringify(s)}`);
|
||||
holdsBothNones(b, 'the invariant 8 refusal bullet');
|
||||
});
|
||||
|
||||
test('Handover 4 code table: the PLAN_STEP_PATH_NOT_A_PATH row names placeholders and mixed-case bare names', () => {
|
||||
const m = /^\| `PLAN_STEP_PATH_NOT_A_PATH` \|.*$/m.exec(handover4());
|
||||
assert.ok(m, 'Handover 4 has a PLAN_STEP_PATH_NOT_A_PATH row');
|
||||
assert.match(m[0], /placeholder/, `the row names placeholders: ${JSON.stringify(m[0])}`);
|
||||
assert.match(m[0], /mixed-case/, `the row names mixed-case bare names: ${JSON.stringify(m[0])}`);
|
||||
});
|
||||
|
||||
for (const [label, slice] of [['the /trekplan 1.9 block', trekplan19Block], ['the template 1.9 paragraph', template19Paragraph]]) {
|
||||
test(`${label} names the placeholders, \`./Name\` and both nones`, () => {
|
||||
const text = slice();
|
||||
assert.ok(text.includes('`TBD`') && text.includes('`N/A`'), `${label} names the placeholders (\`TBD\`, \`N/A\`): ${JSON.stringify(text)}`);
|
||||
assert.ok(text.includes('`./Name`'), `${label} names \`./Name\`: ${JSON.stringify(text)}`);
|
||||
holdsBothNones(text, label);
|
||||
});
|
||||
}
|
||||
|
||||
test('the template\'s own example steps validate as 1.9 with none of the new path codes', () => {
|
||||
const r = validatePlanContent(read('templates/plan-template.md'));
|
||||
assert.equal(r.parsed.planVersion, '1.9');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue