diff --git a/scripts/persistence-changes.spec.ts b/scripts/persistence-changes.spec.ts index f831de0a76..0cd7c2a37c 100644 --- a/scripts/persistence-changes.spec.ts +++ b/scripts/persistence-changes.spec.ts @@ -1031,6 +1031,26 @@ function reviewFile(root: string, review: unknown = COMPATIBILITY_REVIEW): strin } describe('authored same-version compatibility review', () => { + it('records and reloads evidence containing todo event names and test paths', () => { + const root = fixture() + baseline(root) + const review = { + oldReaders: 'Unknown event:todo/write records are refused before resume.', + newReaders: 'Historical event:todo/write records retain their meaning.', + verification: 'pnpm exec vitest run packages/todo/tool-todo/tests/tool-todo.spec.ts: passed.', + } + const after = inventory({ value: 'number' }) + runPersistenceChanges(['--record', NEXT_ID, '--review', reviewFile(root, review), '--prose', proseFile(root)], root, () => after) + expect(loadPersistenceHistory(root).entries.find(entry => entry.record.id === NEXT_ID)?.record.review).toEqual(review) + expect(runPersistenceChanges(['--check'], root, () => after)).toContain('roots match') + }) + + it.each(['TODO', 'tbd', 'FIXME: run tests', 'Evidence:\n todo: run tests', 'Reader behavior is TBD.'])( + 'rejects standalone placeholder evidence: %s', (verification) => { + expect(() => parsePersistenceReview({ ...COMPATIBILITY_REVIEW, verification })).toThrow('without placeholders') + }, + ) + it.each(['optional header', 'removed field', 'changed field'] as const)('admits a reviewed %s after finalization', (kind) => { const root = fixture() finalize(root) diff --git a/scripts/persistence-changes.ts b/scripts/persistence-changes.ts index 090a54c646..12fc380360 100644 --- a/scripts/persistence-changes.ts +++ b/scripts/persistence-changes.ts @@ -775,6 +775,7 @@ function scaffold(change: PersistenceChangeRecord, chinese: boolean, prose?: Per } /** Parse authored compatibility evidence for the exact schemas in an acknowledgement. + * Placeholder checks exclude marker names embedded in event identifiers and paths. * @param value - decoded JSON supplied through --review or a saved machine record. * @returns validated evidence with canonical field ordering. */ @@ -783,7 +784,7 @@ export function parsePersistenceReview(value: unknown): PersistenceCompatibility keys(input, ['oldReaders', 'newReaders', 'verification'], 'compatibility review') const authored = (name: string): string => { const text = textValue(input[name], `compatibility review ${name}`) - if (text.trim().length === 0 || /\b(?:TODO|TBD|FIXME)\b/iu.test(text)) { + if (text.trim().length === 0 || /(?:^|\s)(?:TODO|TBD|FIXME)(?=$|[\s:;,.!?])/iu.test(text)) { throw new Error(`compatibility review ${name} requires authored text without placeholders`) } return text