fix: parse paired status from leading lines
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
|||||||
} from './paired-verdict.js';
|
} from './paired-verdict.js';
|
||||||
|
|
||||||
describe('paired verdict parser', () => {
|
describe('paired verdict parser', () => {
|
||||||
it('parses visible verdicts from the first summary line only', () => {
|
it('parses visible verdicts from leading summary lines', () => {
|
||||||
expect(parseVisibleVerdict('STEP_DONE\nmore to do')).toBe('step_done');
|
expect(parseVisibleVerdict('STEP_DONE\nmore to do')).toBe('step_done');
|
||||||
expect(parseVisibleVerdict('TASK_DONE\nall done')).toBe('task_done');
|
expect(parseVisibleVerdict('TASK_DONE\nall done')).toBe('task_done');
|
||||||
expect(
|
expect(
|
||||||
@@ -18,6 +18,46 @@ describe('paired verdict parser', () => {
|
|||||||
expect(parseVisibleVerdict('random prose')).toBe('continue');
|
expect(parseVisibleVerdict('random prose')).toBe('continue');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('accepts status tokens a few visible lines into the summary', () => {
|
||||||
|
expect(
|
||||||
|
parseVisibleVerdict(
|
||||||
|
[
|
||||||
|
'판단부터 적겠습니다.',
|
||||||
|
'검증 결과는 아래와 같습니다.',
|
||||||
|
'STEP_DONE',
|
||||||
|
'',
|
||||||
|
'나머지 설명입니다.',
|
||||||
|
].join('\n'),
|
||||||
|
),
|
||||||
|
).toBe('step_done');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores status tokens in prose or code fences', () => {
|
||||||
|
expect(
|
||||||
|
parseVisibleVerdict('검토 결과 STEP_DONE으로 보입니다.\n세부 내용'),
|
||||||
|
).toBe('continue');
|
||||||
|
expect(
|
||||||
|
parseVisibleVerdict(
|
||||||
|
['```text', 'TASK_DONE', '```', '본문에는 상태가 없습니다.'].join('\n'),
|
||||||
|
),
|
||||||
|
).toBe('continue');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not scan beyond the leading visible line window', () => {
|
||||||
|
expect(
|
||||||
|
parseVisibleVerdict(
|
||||||
|
[
|
||||||
|
'첫 줄',
|
||||||
|
'둘째 줄',
|
||||||
|
'셋째 줄',
|
||||||
|
'넷째 줄',
|
||||||
|
'다섯째 줄',
|
||||||
|
'TASK_DONE',
|
||||||
|
].join('\n'),
|
||||||
|
),
|
||||||
|
).toBe('continue');
|
||||||
|
});
|
||||||
|
|
||||||
it('classifies arbiter verdicts from the first visible line', () => {
|
it('classifies arbiter verdicts from the first visible line', () => {
|
||||||
expect(classifyArbiterVerdict('PROCEED\ncontinue')).toBe('proceed');
|
expect(classifyArbiterVerdict('PROCEED\ncontinue')).toBe('proceed');
|
||||||
expect(classifyArbiterVerdict('**VERDICT: REVISE**\nfix it')).toBe(
|
expect(classifyArbiterVerdict('**VERDICT: REVISE**\nfix it')).toBe(
|
||||||
|
|||||||
@@ -14,22 +14,56 @@ export type ArbiterVerdictResult =
|
|||||||
| 'escalate'
|
| 'escalate'
|
||||||
| 'unknown';
|
| 'unknown';
|
||||||
|
|
||||||
|
const VISIBLE_VERDICT_SCAN_LINE_LIMIT = 5;
|
||||||
|
|
||||||
|
function leadingVisibleLines(text: string, limit: number): string[] {
|
||||||
|
const lines: string[] = [];
|
||||||
|
let inFence = false;
|
||||||
|
|
||||||
|
for (const rawLine of text.split('\n')) {
|
||||||
|
const line = rawLine.trim();
|
||||||
|
if (/^(```|~~~)/.test(line)) {
|
||||||
|
inFence = !inFence;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inFence || line.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lines.push(line);
|
||||||
|
if (lines.length >= limit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseVisibleVerdictLine(line: string): VisibleVerdict | null {
|
||||||
|
if (/^\*{0,2}BLOCKED(?:\*{0,2})?\b/i.test(line)) return 'blocked';
|
||||||
|
if (/^\*{0,2}NEEDS_CONTEXT(?:\*{0,2})?\b/i.test(line)) return 'needs_context';
|
||||||
|
if (/^\*{0,2}STEP_DONE(?:\*{0,2})?\b/i.test(line)) return 'step_done';
|
||||||
|
if (/^\*{0,2}TASK_DONE(?:\*{0,2})?\b/i.test(line)) return 'task_done';
|
||||||
|
if (/^\*{0,2}DONE_WITH_CONCERNS(?:\*{0,2})?\b/i.test(line))
|
||||||
|
return 'done_with_concerns';
|
||||||
|
if (/^\*{0,2}DONE(?:\*{0,2})?\b/i.test(line)) return 'done';
|
||||||
|
if (/^\*{0,2}Approved\.?(?:\*{0,2})?/i.test(line)) return 'done';
|
||||||
|
if (/^\*{0,2}LGTM(?:\*{0,2})?/i.test(line)) return 'done';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function parseVisibleVerdict(
|
export function parseVisibleVerdict(
|
||||||
summary: string | null | undefined,
|
summary: string | null | undefined,
|
||||||
): VisibleVerdict {
|
): VisibleVerdict {
|
||||||
if (!summary) return 'continue';
|
if (!summary) return 'continue';
|
||||||
const cleaned = summary.replace(/<internal>[\s\S]*?<\/internal>/g, '').trim();
|
const cleaned = summary.replace(/<internal>[\s\S]*?<\/internal>/g, '').trim();
|
||||||
if (!cleaned) return 'continue';
|
if (!cleaned) return 'continue';
|
||||||
const firstLine = cleaned.split('\n')[0].trim();
|
for (const line of leadingVisibleLines(
|
||||||
if (/^\*{0,2}BLOCKED\*{0,2}\b/i.test(firstLine)) return 'blocked';
|
cleaned,
|
||||||
if (/^\*{0,2}NEEDS_CONTEXT\*{0,2}\b/i.test(firstLine)) return 'needs_context';
|
VISIBLE_VERDICT_SCAN_LINE_LIMIT,
|
||||||
if (/^\*{0,2}STEP_DONE\*{0,2}\b/i.test(firstLine)) return 'step_done';
|
)) {
|
||||||
if (/^\*{0,2}TASK_DONE\*{0,2}\b/i.test(firstLine)) return 'task_done';
|
const verdict = parseVisibleVerdictLine(line);
|
||||||
if (/^\*{0,2}DONE_WITH_CONCERNS\*{0,2}\b/i.test(firstLine))
|
if (verdict) return verdict;
|
||||||
return 'done_with_concerns';
|
}
|
||||||
if (/^\*{0,2}DONE\*{0,2}\b/i.test(firstLine)) return 'done';
|
|
||||||
if (/^\*{0,2}Approved\.?\*{0,2}/i.test(firstLine)) return 'done';
|
|
||||||
if (/^\*{0,2}LGTM\*{0,2}/i.test(firstLine)) return 'done';
|
|
||||||
return 'continue';
|
return 'continue';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user