What happens
ReadmeEditor.updateSection replaces the span between a marker pair, but the last byte of the old span survives when the end marker does not start its own line. That byte becomes part of the text after the section, so it is never replaced again.
Where
src/readme-editor.ts, getTokenIndexes. The end-marker pattern is (^|[^\]), and indexOfRegexreturns the match index. That index points at the **guard character**, not at<!--. When the end marker starts its line the guard character is the newline, which correctly stays outside the span. When it does not, the guard character is the span's own last byte, and updateSection` keeps it:
const afterContent = this.fileContent.slice(stopIndex); // starts at the guard byte
Reproduction
fs.writeFileSync(p, '# T\n<!-- start inputs -->old<!-- end inputs -->\ntail\n');
const editor = new ReadmeEditor(p);
editor.updateSection('inputs', 'new', false);
await editor.dumpToFile(false);
Result:
# T
<!-- start inputs -->newd<!-- end inputs -->
tail
The d is the last byte of old. It is now outside the span and will never be replaced. With pretty on the same byte survives, because the formatter works on the span and the byte is no longer in it.
Scope
Pre-existing, and independent of #668 and #691:
Every section updater in src/sections/ writes its markers on their own lines, so no shipped path hits this today. A third-party README with <!-- start X -->body<!-- end X --> on one line does.
Suggested direction
Take the offset of <!-- rather than the offset of the match. The guard group is capture 1, so match.index + match[1].length is where the marker text begins; capture 1 is empty when ^ matched. scripts/verify-readme-contract.mjs already does this in its own marker handling and can be read as a worked example.
Fixing it changes generated output for that shape, so it wants its own change and its own verification rather than riding along with a formatter fix.
What happens
ReadmeEditor.updateSectionreplaces the span between a marker pair, but the last byte of the old span survives when the end marker does not start its own line. That byte becomes part of the text after the section, so it is never replaced again.Where
src/readme-editor.ts,getTokenIndexes. The end-marker pattern is(^|[^\]), andindexOfRegexreturns the match index. That index points at the **guard character**, not at<!--. When the end marker starts its line the guard character is the newline, which correctly stays outside the span. When it does not, the guard character is the span's own last byte, andupdateSection` keeps it:Reproduction
Result:
The
dis the last byte ofold. It is now outside the span and will never be replaced. Withprettyon the same byte survives, because the formatter works on the span and the byte is no longer in it.Scope
Pre-existing, and independent of #668 and #691:
prettyon, generation reformats prose outside the markers #668 is formatter scope. The byte leaks withprettyoff too.Every section updater in
src/sections/writes its markers on their own lines, so no shipped path hits this today. A third-party README with<!-- start X -->body<!-- end X -->on one line does.Suggested direction
Take the offset of
<!--rather than the offset of the match. The guard group is capture 1, somatch.index + match[1].lengthis where the marker text begins; capture 1 is empty when^matched.scripts/verify-readme-contract.mjsalready does this in its own marker handling and can be read as a worked example.Fixing it changes generated output for that shape, so it wants its own change and its own verification rather than riding along with a formatter fix.