fix(docs-audit): applying the same findings twice is a no-op (GSTA-167)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-30 04:05:55 +00:00
1 parent 6e6700e099
commit 84b187cdde
2 files changed
+31 -1

No files matched your search

+4 -1
View File
@@ -2,7 +2,8 @@
* docs-audit-apply — gstack-docs-candidate apply: the parent's mechanical half of the
* Step 14.5 packet audit (lib/review-packet-docs.ts). The read-only reader writes one
* JSON finding per line and a LAST-line object; apply makes each \`edit\` whose \`old\`
* text occurs exactly once in a doc the candidate lists, refuses protected, generated,
* text occurs exactly once in a doc the candidate lists (an edit whose \`new\` text the doc
* already holds once, with no \`old\` outside it, is already applied and skipped), refuses protected, generated,
* out-of-tree and read-only-mode edits as blockers, turns \`block\` findings into
* blockers and \`note\` findings into decisions, and prints the audit result in the
* schema a /document-release child returns (ship/sections/documentation.md).
@@ -53,6 +54,8 @@ export function applyDocsFindings(root: string, candidate: { audit_id: string; m
if (!file.startsWith(realRoot + path.sep)) return refuse('resolves outside the repository');
const entry = edited.get(f.path) ?? (() => { const before = fs.readFileSync(file, 'utf8'); return { before, now: before, edits: 0 }; })();
if (GENERATED.test(entry.before)) return refuse('generated file; edit its template');
const count = (s: string, sub: string) => s.split(sub).length - 1;
if (f.new && count(entry.now, f.new) === 1 && count(entry.now, f.old) === count(f.new, f.old)) return;
const at = entry.now.indexOf(f.old);
if (at < 0 || entry.now.indexOf(f.old, at + 1) >= 0) return refuse(at < 0 ? 'old text not found' : 'old text occurs more than once');
entry.now = entry.now.slice(0, at) + f.new + entry.now.slice(at + f.old.length);
+27
View File
@@ -189,3 +189,30 @@ describe('docs audit apply: generated files behind frontmatter', () => {
} finally { fs.rmSync(root, { recursive: true, force: true }); }
});
});
describe('docs audit apply: applying the same findings twice', () => {
// Value: protects=a second apply of the same reader output is a no-op reported current, so an edit whose new text appends to its old text is not appended again (#3633 Step 14.5: the HANDOFF_PIN_MOVED sentence landed twice in docs/troubleshooting.md); fails_when=apply re-applies an edit whose new text the file already holds, or reports an applied replace edit as a blocker;
// why_new=GSTA-167 /ship run on #3633; seam=none; no_claim=edits whose new text appears elsewhere in the doc
test('the second apply leaves the file as after one apply and reports current with no blocker', async () => {
const { applyDocsFindings } = await import('../lib/docs-audit-apply');
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'docs-twice-'));
try {
fs.mkdirSync(path.join(root, 'docs'));
fs.writeFileSync(path.join(root, 'docs', 'fix.md'), 'Regenerate the handoff or stops.\nRun --fast.\n');
const out = [
JSON.stringify({ path: 'docs/fix.md', action: 'edit', kind: 'stale', summary: 'append', old: 'or stops.', new: 'or stops. The reconcile stops the run.' }),
JSON.stringify({ path: 'docs/fix.md', action: 'edit', kind: 'stale', summary: 'rename', old: '--fast', new: '--quick' }),
JSON.stringify({ schema_version: 1, audit_id: 'a1', documentation_section: 'Docs.' }),
].join('\n');
const cand = { audit_id: 'a1', mode: 'write', docs: ['docs/fix.md'] };
const first = applyDocsFindings(root, cand, out);
expect(first.result).toMatchObject({ status: 'updated', files_updated: ['docs/fix.md'], blockers: [] });
const once = fs.readFileSync(path.join(root, 'docs', 'fix.md'), 'utf8');
expect(once).toBe('Regenerate the handoff or stops. The reconcile stops the run.\nRun --quick.\n');
const second = applyDocsFindings(root, cand, out);
expect(second.exit).toBe(0);
expect(second.result).toMatchObject({ status: 'current', files_updated: [], blockers: [] });
expect(fs.readFileSync(path.join(root, 'docs', 'fix.md'), 'utf8')).toBe(once);
} finally { fs.rmSync(root, { recursive: true, force: true }); }
});
});