feat(plan): the operative line map and a deterministic line diff for the critic loop [skip-docs]
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c9248bbb52
commit
3a0c2d0c54
3 changed files with 506 additions and 0 deletions
|
|
@ -100,6 +100,47 @@ export function operativeText(text) {
|
|||
.trim() + '\n';
|
||||
}
|
||||
|
||||
/**
|
||||
* The operative text with its line map, built from the same transforms as
|
||||
* `operativeText` (whose output it returns unchanged).
|
||||
* @returns {{text: string, fullToOp: Array<number|null>, opToFull: Array<number|null>}}
|
||||
* 1-based both ways (index 0 is null). fullToOp[i] is null for a review-log
|
||||
* line, the quality line or a collapsed blank; opToFull[j] is null for an
|
||||
* operative line no file line carries (a blank left where a log section stood).
|
||||
*/
|
||||
export function operativeLines(text) {
|
||||
const src = norm(text);
|
||||
const full = src.split('\n');
|
||||
const excluded = new Set();
|
||||
for (const s of splitSections(src, 2)) {
|
||||
if (!isLogTitle(s.title)) continue;
|
||||
const n = logPart(s.text).log.split('\n').length;
|
||||
for (let i = s.start; i < s.start + n; i++) excluded.add(i);
|
||||
}
|
||||
full.forEach((l, i) => { if (QUALITY_LINE.test(l)) excluded.add(i); });
|
||||
const op = operativeText(text);
|
||||
const opL = op.split('\n');
|
||||
const opCount = opL.length - 1; // the text ends with '\n'; the last split element is not a line
|
||||
const fullToOp = new Array(full.length + 1).fill(null);
|
||||
const opToFull = new Array(opCount + 1).fill(null);
|
||||
let j = 0;
|
||||
for (let i = 0; i < full.length && j < opCount; i++) {
|
||||
if (excluded.has(i)) continue;
|
||||
let l = full[i].replace(/[ \t]+$/, '');
|
||||
if (j === 0) l = l.replace(/^\s+/, '');
|
||||
// Only blanks lose their partner, so a non-blank kept line facing an operative
|
||||
// blank means that blank has no file line: step past it.
|
||||
while (l !== '' && opL[j] === '' && j < opCount - 1) j += 1;
|
||||
const want = opL[j];
|
||||
if (l === want || (j === opCount - 1 && l.trimEnd() === want)) {
|
||||
fullToOp[i + 1] = j + 1;
|
||||
opToFull[j + 1] = i + 1;
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
return { text: op, fullToOp, opToFull };
|
||||
}
|
||||
|
||||
export function planHash(text) {
|
||||
return createHash('sha256').update(operativeText(text)).digest('hex');
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue