feat(observer): opt-in reasoning-tag merges with heuristic primary_rationale

Closes brain-retro 2026-05-20 #11 — parseReasoningTag extracts opt-in
<!-- reasoning: triggers="..." candidates="..." boundaries="..." -->
HTML-comment from assistant text. Semicolon-separated values merged into
heuristic-derived primary_rationale arrays via Set-dedupe.

Conservative: tag is opt-in; heuristic still runs even when tag present
(heuristic provides baseline, tag enriches).

5 new vitest tests, 309/309 GREEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-20 13:20:45 +03:00
parent 884169e847
commit f54c82d682
2 changed files with 88 additions and 11 deletions
+48
View File
@@ -1327,3 +1327,51 @@ describe('classifyPromptSignal — extended dictionary (Task 9)', () => {
expect(classifyPromptSignal('переделай это')).toBe('correction');
});
});
import { parseReasoningTag } from './observer-transcript-parser.mjs';
describe('parseReasoningTag (Task 11)', () => {
it('parses opt-in reasoning tag from assistant text', () => {
const turn = [{ message: { role: 'assistant', content: [
{ type: 'text', text: '<!-- reasoning: triggers="Pravila §12.2;ADR-011" candidates="brain-retro;brainstorming" boundaries="Pravila §16" -->\nAnswer.' }
] } }];
const tag = parseReasoningTag(turn);
expect(tag).toEqual({
triggers: ['Pravila §12.2', 'ADR-011'],
candidates: ['brain-retro', 'brainstorming'],
boundaries: ['Pravila §16'],
});
});
it('returns null when no tag present', () => {
expect(parseReasoningTag([{ message: { role: 'assistant', content: [{ type: 'text', text: 'plain' }] } }])).toBeNull();
});
it('safe on null/empty turn', () => {
expect(parseReasoningTag(null)).toBeNull();
expect(parseReasoningTag([])).toBeNull();
});
it('skips non-text blocks', () => {
const turn = [{ message: { role: 'assistant', content: [{ type: 'tool_use', name: 'Read' }] } }];
expect(parseReasoningTag(turn)).toBeNull();
});
});
describe('parseTranscript — reasoning-tag merges with heuristic (Task 11)', () => {
it('merges tag triggers into heuristic triggers (deduped)', () => {
const transcript = [
JSON.stringify({ sessionId: 's' }),
JSON.stringify({ type: 'user', message: { role: 'user', content: 'делай' }, uuid: 'u1', timestamp: '2026-05-20T00:00:00Z' }),
JSON.stringify({ type: 'assistant', message: { role: 'assistant', content: [
{ type: 'text', text: 'Pravila §12.2 hard-rule\n<!-- reasoning: triggers="Pravila §12.2;ADR-011" candidates="brain-retro" boundaries="Pravila §16" -->' }
] }, uuid: 'u2', timestamp: '2026-05-20T00:01:00Z' }),
].join('\n');
const ep = parseTranscript(transcript);
// heuristic finds 'Pravila §12.2' from text + tag adds 'ADR-011'; dedup
expect(ep.primary_rationale.triggers_matched).toContain('Pravila §12.2');
expect(ep.primary_rationale.triggers_matched).toContain('ADR-011');
expect(ep.primary_rationale.triggers_matched.filter((t) => t === 'Pravila §12.2')).toHaveLength(1);
// candidates: tag contributes 'brain-retro' (no numbered list in text)
expect(ep.primary_rationale.candidates_considered).toContain('brain-retro');
// boundaries: heuristic empty (no marker in plain text), tag adds 'Pravila §16'
expect(ep.primary_rationale.boundaries_applied).toContain('Pravila §16');
});
});