Files
deepseek-harness/scripts/translation-brief.spec.ts
Tianyi Cui bf87be0d7d feat(i18n): briefing generator and pair-scoped pairing gate
gen-translation-brief assembles the minimal-update working set for an
out-of-sync pair from its consistency record: the authored side's diff
since last confirmation, the counterpart sections that diff lands in
(heading-mapped only where the last-confirmed structures align), the
terminology rows the diff touches, and a per-direction rules digest.

verify-translation-pairing now accepts pair paths to check just the
named pairs during update iteration; --write requires naming the
confirmed pairs (--write --all is the explicit corpus form) so a bulk
re-record can no longer silently bless drifted pairs the caller never
reviewed. Each record's comment names its own scoped command.
2026-07-27 00:44:21 +08:00

158 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** Regression tests for the minimal-update briefing assembly. */
import { describe, expect, it } from 'vitest'
import {
changedLinesOfDiff,
extractCounterpartSections,
headingSections,
mapHunksToSections,
matchTerminologyRows,
parseUnifiedDiffHunks,
renderTranslationBrief,
} from './translation-brief.ts'
const DIFF = [
'@@ -3,3 +3,3 @@',
' unchanged context',
'-The agent loop retries once.',
'+The agent loop retries twice.',
'@@ -12 +12,2 @@',
'+A new sentence about the session log.',
].join('\n')
describe('unified diff parsing', () => {
it('reads hunk starts and counts, defaulting count to 1', () => {
expect(parseUnifiedDiffHunks(DIFF)).toEqual([
{ start: 3, count: 3 },
{ start: 12, count: 1 },
])
})
it('collects only changed lines, markers stripped', () => {
expect(changedLinesOfDiff(DIFF)).toBe([
'The agent loop retries once.',
'The agent loop retries twice.',
'A new sentence about the session log.',
].join('\n'))
})
it('ignores file header lines that also start with +/-', () => {
expect(changedLinesOfDiff('--- a/foo.md\n+++ b/foo.md\n+added')).toBe('added')
})
})
const DOC = [
'Preamble line.',
'',
'# Title',
'',
'Intro paragraph.',
'',
'## First',
'',
'First body.',
'',
'## Second',
'',
'Second body.',
].join('\n')
describe('section mapping', () => {
it('lists headings with lines, depths, and labels', () => {
expect(headingSections(DOC)).toEqual([
{ line: 3, depth: 1, label: 'Title' },
{ line: 7, depth: 2, label: 'First' },
{ line: 11, depth: 2, label: 'Second' },
])
})
it('maps hunks to the sections they span, including the preamble', () => {
const headings = headingSections(DOC)
expect(mapHunksToSections([{ start: 1, count: 1 }], headings)).toEqual([0])
expect(mapHunksToSections([{ start: 9, count: 1 }], headings)).toEqual([2])
expect(mapHunksToSections([{ start: 9, count: 4 }], headings)).toEqual([2, 3])
expect(mapHunksToSections([{ start: 0, count: 0 }], headings)).toEqual([0])
})
it('extracts counterpart section text with start lines and labels', () => {
expect(extractCounterpartSections(DOC, [0, 2])).toEqual([
{ label: '(preamble before the first heading)', startLine: 1, text: 'Preamble line.' },
{ label: '## First', startLine: 7, text: '## First\n\nFirst body.' },
])
})
})
const TERMINOLOGY = [
'| English | 中文 | 首次出现 | 不要译作 | 备注 |',
'|---|---|---|---|---|',
'| agent loop | agent loop | agent loop智能体循环 | | |',
'| session log | 会话日志 | | 会话记录 | |',
'| gate | 门禁 | | | |',
].join('\n')
describe('terminology matching', () => {
it('selects rows whose English term appears on a word boundary', () => {
const matches = matchTerminologyRows(TERMINOLOGY, 'The agent loop retries twice.')
expect(matches.rows).toEqual(['| agent loop | agent loop | agent loop智能体循环 | | |'])
expect(matches.header).toContain('English')
})
it('selects rows whose Chinese term appears when the source is Chinese', () => {
expect(matchTerminologyRows(TERMINOLOGY, '门禁在提交时运行。').rows).toEqual(['| gate | 门禁 | | | |'])
})
it('does not match substrings inside larger words', () => {
expect(matchTerminologyRows(TERMINOLOGY, 'delegate the work').rows).toEqual([])
})
})
describe('brief rendering', () => {
const base = {
sourcePath: 'docs/foo.md',
counterpartPath: 'docs/foo.zh.md',
direction: 'en-to-zh' as const,
diff: DIFF,
counterpartSections: [{ label: '## First', startLine: 7, text: '## First\n\n正文。' }],
bothDrifted: false,
terminology: matchTerminologyRows(TERMINOLOGY, changedLinesOfDiff(DIFF)),
}
it('renders diff, aligned sections, terminology, digest, and finish steps', () => {
const brief = renderTranslationBrief(base)
expect(brief).toContain('# Translation update briefing: docs/foo.md')
expect(brief).toContain('```diff')
expect(brief).toContain('docs/foo.zh.md:7')
expect(brief).toContain('agent loop智能体循环')
expect(brief).toContain('| 会话日志 |')
expect(brief).toContain('Rules digest')
expect(brief).toContain('verify-translation-pairing --write docs/foo.md')
expect(brief).toContain('smallest edit that covers the diff')
})
it('warns instead of showing sections when both sides drifted', () => {
const brief = renderTranslationBrief({ ...base, bothDrifted: true, counterpartSections: undefined })
expect(brief).toContain('BOTH sides changed')
expect(brief).toContain('locate the regions yourself')
expect(brief).not.toContain('docs/foo.zh.md:7')
})
it('renders the English-target digest for zh-to-en updates', () => {
const brief = renderTranslationBrief({
...base,
direction: 'zh-to-en',
sourcePath: 'docs/foo.zh.md',
counterpartPath: 'docs/foo.md',
})
expect(brief).toContain('exactly what the new Chinese states')
expect(brief).toContain('verify-translation-pairing --write docs/foo.md')
})
it('grows the section fence past tilde runs in the body', () => {
const brief = renderTranslationBrief({
...base,
counterpartSections: [{ label: '## First', startLine: 7, text: '~~~~\ninner\n~~~~' }],
})
expect(brief).toContain('~~~~~markdown')
})
})