From f943b229c0e0216e82ca21acbd4a41c1fb41b0b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Thu, 21 May 2026 04:48:21 +0300 Subject: [PATCH] feat(observer): emit chain_ref in primary_rationale --- tools/observer-transcript-parser.mjs | 9 +++++++++ tools/observer-transcript-parser.test.mjs | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/tools/observer-transcript-parser.mjs b/tools/observer-transcript-parser.mjs index 748f3b03..e50113ad 100644 --- a/tools/observer-transcript-parser.mjs +++ b/tools/observer-transcript-parser.mjs @@ -16,6 +16,14 @@ */ import { detectChoiceProvenance, detectAskUserQuestionChoice } from './observer-choice-detector.mjs'; +import { loadChainMap, chainsFor } from './observer-chain-detector.mjs'; + +let CHAIN_MAP = null; +try { + CHAIN_MAP = loadChainMap(); +} catch { + CHAIN_MAP = new Map(); // битый/отсутствующий JSON -> chainsFor вернёт null, observer не падает +} const SUPERPOWERS_PREFIX = 'superpowers:'; @@ -694,6 +702,7 @@ export function parseTranscript(transcriptText, fallbackSessionId = null) { return { step: 1, node_chosen: skills.length > 0 ? skills[0] : 'direct', + chain_ref: chainsFor(skills.length > 0 ? skills[0] : 'direct', CHAIN_MAP), triggers_matched: merge(extractTriggers(turn), tag ? tag.triggers : []), candidates_considered: merge(extractCandidates(turn), tag ? tag.candidates : []), boundaries_applied: merge(extractBoundaries(turn), tag ? tag.boundaries : []), diff --git a/tools/observer-transcript-parser.test.mjs b/tools/observer-transcript-parser.test.mjs index a7df1560..09ac10e1 100644 --- a/tools/observer-transcript-parser.test.mjs +++ b/tools/observer-transcript-parser.test.mjs @@ -106,6 +106,25 @@ describe('parseTranscript', () => { expect(parseTranscript(t).primary_rationale.node_chosen).toBe('direct'); }); + it('attaches chain_ref for a node that belongs to a chain', () => { + const t = jsonl([ + userPrompt('go', '2026-05-19T10:00:00Z'), + assistantTurn( + [{ type: 'tool_use', id: 't1', name: 'Skill', input: { skill: 'billing-audit' } }], + '2026-05-19T10:01:00Z' + ), + ]); + expect(parseTranscript(t).primary_rationale.chain_ref).toEqual(['L13']); + }); + + it('sets chain_ref null for a direct episode', () => { + const t = jsonl([ + userPrompt('go', '2026-05-19T10:00:00Z'), + assistantTurn([{ type: 'tool_use', id: 't1', name: 'Read', input: {} }], '2026-05-19T10:01:00Z'), + ]); + expect(parseTranscript(t).primary_rationale.chain_ref).toBeNull(); + }); + it('hard_floor invoked when a superpowers skill is used', () => { const t = jsonl([ userPrompt('go', '2026-05-19T10:00:00Z'),