agentfootprint-lens 0.43.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +77 -0
  2. package/dist/{chunk-UWN5VKQ2.js → chunk-5YSPWSLW.js} +2 -2
  3. package/dist/{chunk-VBMVWQUO.js → chunk-CGR5VFJU.js} +2 -2
  4. package/dist/{chunk-VBRT372Q.js → chunk-LCIEJYZG.js} +33 -1
  5. package/dist/chunk-LCIEJYZG.js.map +1 -0
  6. package/dist/{chunk-CWKOGPOJ.js → chunk-LRGT7UQL.js} +4 -4
  7. package/dist/{chunk-FFQ5EARP.js → chunk-QNMA25X5.js} +38 -12
  8. package/dist/chunk-QNMA25X5.js.map +1 -0
  9. package/dist/{chunk-F473UD5M.js → chunk-YEJJOPOZ.js} +124 -7
  10. package/dist/chunk-YEJJOPOZ.js.map +1 -0
  11. package/dist/core.cjs +35 -0
  12. package/dist/core.cjs.map +1 -1
  13. package/dist/core.d.cts +107 -2
  14. package/dist/core.d.ts +107 -2
  15. package/dist/core.js +9 -3
  16. package/dist/index.cjs +187 -12
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +59 -6
  19. package/dist/index.d.ts +59 -6
  20. package/dist/index.js +12 -6
  21. package/dist/index.js.map +1 -1
  22. package/dist/{selectSkillFrameContext-CT_EKu4u.d.cts → selectSkillFrameContext-DJkvrggG.d.cts} +1 -1
  23. package/dist/{selectSkillFrameContext-D0xQV_px.d.ts → selectSkillFrameContext-IPsw4Pbj.d.ts} +1 -1
  24. package/dist/skillgraph.cjs +181 -12
  25. package/dist/skillgraph.cjs.map +1 -1
  26. package/dist/skillgraph.d.cts +40 -3
  27. package/dist/skillgraph.d.ts +40 -3
  28. package/dist/skillgraph.js +3 -3
  29. package/dist/why.cjs +60 -8
  30. package/dist/why.cjs.map +1 -1
  31. package/dist/why.js +4 -4
  32. package/package.json +1 -1
  33. package/dist/chunk-F473UD5M.js.map +0 -1
  34. package/dist/chunk-FFQ5EARP.js.map +0 -1
  35. package/dist/chunk-VBRT372Q.js.map +0 -1
  36. /package/dist/{chunk-UWN5VKQ2.js.map → chunk-5YSPWSLW.js.map} +0 -0
  37. /package/dist/{chunk-VBMVWQUO.js.map → chunk-CGR5VFJU.js.map} +0 -0
  38. /package/dist/{chunk-CWKOGPOJ.js.map → chunk-LRGT7UQL.js.map} +0 -0
package/README.md CHANGED
@@ -600,6 +600,83 @@ a Vue or CLI shell.
600
600
 
601
601
  ---
602
602
 
603
+ ## Scrubbing without dead air — snap stops
604
+
605
+ Sharing an axis is not the same as sharing a stride.
606
+
607
+ A view mounted inside the lens scrubs the **host's** axis — that is the
608
+ one-cursor law working correctly. But the host's axis is the *run's*. A
609
+ four-tool turn puts 73 stops on it, and a view like the Skill Graph changes at
610
+ eight of them: 65 presses of ◀ ▶ walk framework stages that change nothing in
611
+ its picture. The scrubbing is not wrong, it is **dead air**.
612
+
613
+ The fix that must not be taken is a second slider with its own numbers. Two
614
+ transports over one run is the drift this package refuses. So pass `snapSteps` —
615
+ an ascending list of positions on the axis the transport **already has** — and
616
+ only the step buttons' landing set narrows:
617
+
618
+ ```tsx
619
+ <TimeTravel
620
+ total={73}
621
+ focusSeq={step}
622
+ onFocusChange={setStep}
623
+ isLive={false}
624
+ // ◀ ▶ and ← → now land only here. Everything else is untouched.
625
+ snapSteps={[0, 9, 17, 31, 44, 58, 72]}
626
+ />
627
+ ```
628
+
629
+ What stays true when it is set:
630
+
631
+ - **the drag track and the step strip keep the full axis** — every position is
632
+ still reachable, just not by ◀ ▶;
633
+ - **Home / End still go to the axis endpoints**, because they are jumps, not
634
+ steps (band clicks and strip clicks are jumps too, and also unaffected);
635
+ - **a position between two stops is disclosed, never rounded down.** Drag to a
636
+ step no stop covers and the readout says so — `between stops 2 and 3 of 4 ·
637
+ 8 / 12` — because with the buttons narrowed, that is a position they cannot
638
+ reproduce and the reader has to be told;
639
+ - **nothing is stored.** The position is still the host's `step`, and every
640
+ move still leaves through `onFocusChange(step)`.
641
+
642
+ Entries the axis cannot hold (negative, past the end, non-integer) are ignored —
643
+ a stop the axis does not have is not a stop. An empty list, or one whose entries
644
+ are all off-axis, behaves exactly as passing nothing.
645
+
646
+ `snapSteps` is orthogonal to `bands`: bands **group** the axis, snaps narrow the
647
+ unit of **movement** along it. Set both and the readout names all three truths —
648
+ `Iteration 2 · stop 3 of 4 · 10 / 12`.
649
+
650
+ `<SkillGraphDebugger>` forwards the same prop to the transport it mounts, so a
651
+ host can hand it the steps its routing picture actually changes at:
652
+
653
+ ```tsx
654
+ import { selectSkillBeats, selectSkillRoute, stepForRuntimeStageId } from 'agentfootprint-lens/skillgraph';
655
+
656
+ const beats = selectSkillBeats({ route: selectSkillRoute({ log: recorder.selectEventLog() }) });
657
+ const snapSteps = [
658
+ ...new Set(
659
+ beats
660
+ .map((b) => (b.runtimeStageId !== undefined ? stepForRuntimeStageId(positions, b.runtimeStageId) : -1))
661
+ .filter((s) => s >= 0),
662
+ ),
663
+ ].sort((a, b) => a - b);
664
+
665
+ <SkillGraphDebugger step={step} totalSteps={totalSteps} onStepChange={onNavigate} snapSteps={snapSteps} … />
666
+ ```
667
+
668
+ The host builds the list because the host owns the axis. On the debugger the
669
+ prop is **ignored when it is scrubbing its own routing stops** (no `step` /
670
+ `totalSteps`) — every routing stop already changes the picture, so there is
671
+ nothing to narrow.
672
+
673
+ Headless: `snapPositionOf`, `nextSnapStep` and `prevSnapStep` on
674
+ `agentfootprint-lens/core` are the pure queries behind it — the same
675
+ at-or-before resolution (with its `exact` flag) and the same strict
676
+ next/previous laws, for a shell that draws its own transport.
677
+
678
+ ---
679
+
603
680
  ## Driving the cursor from your app
604
681
 
605
682
  **Omitting these props keeps the lens self-driving.** It holds the position
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  formatBytes,
3
3
  structureGraphFromRunner
4
- } from "./chunk-VBMVWQUO.js";
4
+ } from "./chunk-CGR5VFJU.js";
5
5
 
6
6
  // src/core/buildStepGraphFromSnapshot.ts
7
7
  function buildStepGraphFromSnapshot(snapshot) {
@@ -1189,4 +1189,4 @@ export {
1189
1189
  decisionSentence,
1190
1190
  isConsentDecision
1191
1191
  };
1192
- //# sourceMappingURL=chunk-UWN5VKQ2.js.map
1192
+ //# sourceMappingURL=chunk-5YSPWSLW.js.map
@@ -3,7 +3,7 @@ import {
3
3
  humanizeRouteConflict,
4
4
  humanizeSkillRejected,
5
5
  humanizeTurnRouted
6
- } from "./chunk-VBRT372Q.js";
6
+ } from "./chunk-LCIEJYZG.js";
7
7
  import {
8
8
  selectAgentInstances
9
9
  } from "./chunk-2JAWSPNG.js";
@@ -2804,4 +2804,4 @@ export {
2804
2804
  structureGraphFromSpec,
2805
2805
  cursorProvenance
2806
2806
  };
2807
- //# sourceMappingURL=chunk-VBMVWQUO.js.map
2807
+ //# sourceMappingURL=chunk-CGR5VFJU.js.map
@@ -1057,6 +1057,35 @@ function bandChartGroup(args) {
1057
1057
  };
1058
1058
  }
1059
1059
 
1060
+ // src/core/group/snapSteps.ts
1061
+ function snapPositionOf(snapSteps, step) {
1062
+ let index = -1;
1063
+ let best = -Infinity;
1064
+ for (let i = 0; i < snapSteps.length; i += 1) {
1065
+ const s = snapSteps[i];
1066
+ if (s === void 0 || s > step) continue;
1067
+ if (s >= best) {
1068
+ best = s;
1069
+ index = i;
1070
+ }
1071
+ }
1072
+ return { index, exact: index >= 0 && best === step };
1073
+ }
1074
+ function nextSnapStep(snapSteps, step) {
1075
+ let best;
1076
+ for (const s of snapSteps) {
1077
+ if (s > step && (best === void 0 || s < best)) best = s;
1078
+ }
1079
+ return best;
1080
+ }
1081
+ function prevSnapStep(snapSteps, step) {
1082
+ let best;
1083
+ for (const s of snapSteps) {
1084
+ if (s < step && (best === void 0 || s > best)) best = s;
1085
+ }
1086
+ return best;
1087
+ }
1088
+
1060
1089
  // src/core/group/resolveNavigation.ts
1061
1090
  function execIndex3(runtimeStageId) {
1062
1091
  const hash = runtimeStageId.lastIndexOf("#");
@@ -1172,7 +1201,10 @@ export {
1172
1201
  stepBands,
1173
1202
  bandIndexOf,
1174
1203
  bandChartGroup,
1204
+ snapPositionOf,
1205
+ nextSnapStep,
1206
+ prevSnapStep,
1175
1207
  resolveNavigation,
1176
1208
  stepForRuntimeStageId
1177
1209
  };
1178
- //# sourceMappingURL=chunk-VBRT372Q.js.map
1210
+ //# sourceMappingURL=chunk-LCIEJYZG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/selectors/selectContextEngineeringInjections.ts","../src/core/selectors/selectSkillRoute.ts","../src/core/humanizeRouting.ts","../src/core/selectors/selectSkillBeats.ts","../src/core/selectors/selectSkillTopology.ts","../src/core/selectors/selectSkillFrameContext.ts","../src/core/group/Group.ts","../src/core/group/activeChartGroup.ts","../src/core/group/stepBands.ts","../src/core/group/snapSteps.ts","../src/core/group/resolveNavigation.ts","../src/core/group/stepForRuntimeStageId.ts"],"sourcesContent":["/**\n * `selectContextEngineeringInjections` — filter injections to the\n * ENGINEERED set (exclude baseline user + tool-result).\n *\n * Pattern: pure function over a step's injections. Applies the stable\n * `BASELINE_SOURCES` filter: `user` and `tool-result` are the\n * standard LLM-API flow and don't constitute \"context\n * engineering.\" Everything else (`rag`, `skill`, `memory`,\n * `instruction`, `grounding`, consumer-custom) is engineered.\n * Role: The teaching surface for Lens's Context Engineering bin.\n * Matches the filter that `agent.enable.contextEngineering()`\n * will apply once that API lands — consumers get identical\n * results whether they derive client-side via this selector\n * or subscribe to the enable handle.\n *\n * Why the filter lives here (selector) AND in agentfootprint (enable\n * handle) when that lands:\n * - Today's Lens consumes `StepNode.injections[]` directly; the\n * selector is the only place to drop baseline.\n * - Tomorrow, `enable.contextEngineering({ onInjection })` gives\n * non-Lens consumers a pre-filtered stream — they don't need the\n * selector at all.\n * - The filter rule is the SAME (stable set of baseline sources),\n * so the two paths produce identical output. Documenting once\n * here keeps them in sync.\n */\n\nimport type { ContextInjection } from 'agentfootprint/observe';\n\n/**\n * Sources that represent baseline LLM-API flow — NOT context engineering:\n *\n * - `user` → the user's current-turn message OR prior user-turn\n * history replay (standard conversation flow)\n * - `tool-result` → tool return for the current call OR prior-turn\n * tool-result history replay\n * - `assistant` → prior-turn assistant output replayed as history\n * (standard conversation continuity)\n * - `base` → static system prompt configured at build time\n * via `.system('...')` — NOT engineered\n * - `registry` → tool registry configured at build time via\n * `.tool(...)` — the static tool list\n *\n * What's left — the ENGINEERED sources (chips in the Lens bin):\n * `rag` · `skill` · `memory` · `instruction` · `grounding` +\n * consumer-custom sources\n *\n * Contract for library extensions: if you re-inject content with\n * engineered intent (memory strategy, RAG retriever, skill activator,\n * instruction system, grounding rule), you MUST set your own source\n * at the injection site — don't let role-based inference drop you\n * into the baseline bucket.\n */\nexport const BASELINE_SOURCES: ReadonlySet<string> = new Set([\n 'user',\n 'tool-result',\n 'assistant',\n 'base',\n 'registry',\n]);\n\n/**\n * True when this injection represents engineered context (not baseline\n * API flow). Reads the immutable `source` field — no derivation from\n * role or slot.\n */\nexport function isContextEngineering(inj: ContextInjection): boolean {\n return !BASELINE_SOURCES.has(inj.source);\n}\n\n/**\n * Filter a step's injections to the engineered subset. Returns a new\n * readonly array; empty if the step had only baseline injections.\n *\n * Use: Lens's ContextBinNode gets this array; empty → \"No engineered\n * context yet\" empty state. Full → the 5-axis teaching chips.\n */\nexport function selectContextEngineeringInjections(\n injections: readonly ContextInjection[] | undefined,\n): readonly ContextInjection[] {\n if (!injections || injections.length === 0) return [];\n return injections.filter(isContextEngineering);\n}\n","/**\n * `selectSkillRoute` — fold a recording's event log into the ROUTING RECORD:\n * where the skill-graph cursor stood on every iteration, what moved it, what\n * the model was refused, and what it was looking at when it asked.\n *\n * Pattern: pure function over the lens event log (`recorder.selectEventLog()`),\n * same shape as `selectHops` — no framework, no caching, no state.\n * Role: the STRUCTURED half of routing. Until now every routing fact in this\n * package went through `defaultHumanizer` and came out a `string`;\n * a panel that wants to draw the graph, group by skill, or answer\n * \"why was I refused\" had to re-parse prose. This is the same facts,\n * still typed.\n *\n * THE TWO VOCABULARIES (do not conflate them — they answer different questions):\n *\n * • `SkillCursorCause` — `context.evaluated.cursorMove.by`. \"What moved the\n * cursor on THIS iteration.\" Nine values, `'stay'` and `'route'` among them.\n * • `SkillTurnVerdict` — `skill.turn_routed.by`. \"Which tier decided where the\n * TURN started.\" Six values, `'menu'` among them — and `'menu'` is not a\n * cursor cause at all (a menu is an offer; the cursor has not moved yet).\n *\n * They are kept on different TYPES at different levels (`SkillHop.by` vs\n * `SkillTurnStart.by`) rather than in one field with a merged union, because a\n * merged union is exactly the mistake a reader cannot see themselves making.\n *\n * THE ONE-ITERATION LAG (measured on a real run, not assumed):\n *\n * iter 1 the model calls `read_skill('audit-log')` → the gate refuses;\n * `skill.rejected { iteration: 1 }`\n * iter 2 the Evaluate stage resolves the cursor and finds no pending pick →\n * `context.evaluated { iteration: 2, cursorMove: { by: 'stay' } }`\n *\n * The refusal and its CONSEQUENCE are stamped with different iterations, so a\n * naive same-iteration join shows a refusal beside the cursor state that\n * preceded it. Each refusal therefore carries `cursorAfter` — the next\n * iteration's cursor row — which is what makes \"the pick was refused AND the\n * cursor did not move\" one fact instead of two rows a reader has to pair up.\n * `undefined` when the run ended on the refusing iteration: no next row exists,\n * and inventing one would be a claim about a resolution that never ran.\n *\n * ERA NOTE (the `humanizeRouting` precedent, same reasoning): several of these\n * events postdate the agentfootprint version this package compiles against\n * (`agent.evidence_checked` shipped in 9.35.0, after the 9.30.0 devDependency),\n * so they are matched by raw type STRING and read through structural mirrors.\n * A recording from an older era simply carries fewer fields, and the house law\n * holds throughout: report ONLY what the event carries — an absent field is\n * absent, never guessed.\n */\n\nimport type { EventLogEntry } from '../types.js';\n\n// ─── The two vocabularies ────────────────────────────────────────────────\n\n/**\n * How the skill-graph cursor moved on one iteration — agentfootprint's\n * `CursorMoveCause`, all nine of it (`skillGraph.ts`). The library's own\n * `ContextEvaluatedPayload` docstring lists only seven; it is stale (missing\n * `'tool-proposal'` and `'decider'`) and the union is the source of truth.\n *\n * The `(string & {})` arm keeps a future era's tenth value readable instead of\n * un-assignable, while editors still autocomplete the nine that exist.\n */\nexport type SkillCursorCause =\n | 'entry' // a start rule matched (cold start)\n | 'route' // an author-declared edge fired\n | 'model-pick' // the model's accepted `read_skill` pick moved it\n | 'tool-proposal' // an accepted `propose-transition` tool effect moved it\n | 'intent' // the turn-start scorer routed the turn\n | 'continuity' // the inherited conversation cursor held\n | 'decider' // a configured tier-3 decider resolved an outstanding menu\n | 'stay' // nothing fired; the cursor is sticky and stayed put\n | 'none' // no cursor at all\n | (string & {});\n\n/**\n * Which TIER decided where a turn started — agentfootprint's\n * `SkillTurnRoutedPayload.by`. NOT the cursor vocabulary: `'menu'` lives only\n * here (an offer is not a move), and `'route'` / `'model-pick'` / `'stay'` live\n * only in {@link SkillCursorCause}.\n */\nexport type SkillTurnVerdict =\n | 'entry'\n | 'intent'\n | 'continuity'\n | 'menu'\n | 'decider'\n | 'none'\n | (string & {});\n\n// ─── The folded shapes ───────────────────────────────────────────────────\n\n/** What the message said that routed a hop (`match:` rules only). */\nexport interface SkillRouteWitness {\n readonly text: string;\n readonly keyword?: string;\n}\n\n/** A skill the run knew about — the catalog the model was offered. */\nexport interface SkillRouteNode {\n readonly id: string;\n /** The catalog description, verbatim — the same text the model read. */\n readonly description?: string;\n /** The drawn kind (`'skill'` box / `'predicate'` diamond), when the\n * recording carried the declared map (`skill.graph_declared`, 9.50.0). */\n readonly kind?: string;\n /** The drawn caption, when the declared map named one (predicate nodes). */\n readonly label?: string;\n /** The cursor stood here on at least one iteration. */\n readonly visited: boolean;\n}\n\n/** One (turn, iteration) address — how a hop is referred to from elsewhere. */\nexport interface SkillHopRef {\n readonly turnIndex: number;\n readonly iteration: number;\n}\n\n/** A hop the cursor was OBSERVED to take, aggregated over the run. */\nexport interface SkillObservedEdge {\n /** The cursor before the hop. `undefined` = cold start (there was none). */\n readonly from?: string;\n readonly to: string;\n readonly by: SkillCursorCause;\n /** Every (turn, iteration) this hop was taken on, in order. */\n readonly takenAt: readonly SkillHopRef[];\n /** The author's caption, when `routing[]` named this edge. */\n readonly label?: string;\n /** The compiled trigger kind of the declared edge, when there was one. */\n readonly triggerKind?: string;\n}\n\n/** An edge the AUTHOR declared. Reported by `routing[]` provenance (an edge\n * is named once it FIRES — a lower bound), or verbatim by the run's\n * `skill.graph_declared` map (9.50.0 — the WHOLE declaration; see\n * {@link SkillRoute.declaredComplete}). `triggerKind` carries `routing[]`'s\n * compiled trigger kind or the declared map's edge `kind` — one vocabulary\n * (`'entry' | 'predicate' | 'on-tool-return' | 'on-tool-status' | 'model'`). */\nexport interface SkillDeclaredEdge {\n readonly from: string;\n readonly to: string;\n readonly label?: string;\n readonly triggerKind?: string;\n}\n\n/** The cursor row that FOLLOWED a refusal — the proof it did or did not move. */\nexport interface SkillCursorAfter {\n readonly iteration: number;\n readonly from?: string;\n readonly to?: string;\n readonly by?: SkillCursorCause;\n /** `false` is the refusal's whole point: the pick changed nothing. */\n readonly moved: boolean;\n}\n\n/** A `read_skill` pick the gate refused. */\nexport interface SkillRefusal {\n readonly requestedId: string;\n readonly currentSkillId?: string;\n /** The REACHABILITY set named in the re-prompt (not the offered menu). */\n readonly allowed: readonly string[];\n /** `'guard'` / `'rails'` when a POSTURE refused, absent for reachability. */\n readonly posture?: string;\n readonly turnIndex: number;\n readonly iteration: number;\n /** The provider's tool-call id of the refused call, when it was paired. */\n readonly toolCallId?: string;\n /**\n * The refusal sentence the MODEL read — the refused `read_skill` call's own\n * tool result, paired by tool-call id. Absent when the recording carried no\n * `stream.tool_end` for it (a truncated log, or a refusal raised outside a\n * tool call).\n */\n readonly refusalText?: string;\n /** Where the cursor stood on the next iteration — see the lag note above. */\n readonly cursorAfter?: SkillCursorAfter;\n}\n\n/** Two tool results of one batch matched edges to different targets. */\nexport interface SkillRouteConflict {\n readonly fromSkillId?: string;\n readonly winner?: { readonly toolName?: string; readonly target?: string; readonly toolCallId?: string };\n readonly losers: readonly {\n readonly toolName?: string;\n readonly target?: string;\n readonly toolCallId?: string;\n }[];\n /** `'tool-proposal'` when proposals conflicted rather than declared edges. */\n readonly source?: string;\n}\n\n/** An accepted pick a declared edge outranked. */\nexport interface SkillSuperseded {\n readonly volunteeredId: string;\n readonly wonId?: string;\n readonly fromSkillId?: string;\n readonly source?: string;\n}\n\n/** One tool as it was SENT to the model on this iteration. */\nexport interface SkillToolAsSent {\n readonly name: string;\n /** Verbatim, as the model saw it. */\n readonly description?: string;\n}\n\n/** Something a SKILL put into the prompt on this iteration. */\nexport interface SkillInjectionSeen {\n readonly slot: string;\n /** The skill that injected it. */\n readonly skillId?: string;\n readonly summary: string;\n /** The full text, when the recording captured it (`rawContent`). */\n readonly text?: string;\n}\n\n/** The evidence gate's verdict on an iteration's answer (agentfootprint 9.35.0+). */\nexport interface SkillEvidenceCheck {\n readonly posture?: string;\n readonly candidates?: number;\n readonly unsupported: readonly { readonly value: string; readonly shape?: string }[];\n readonly action?: 'grounded' | 'flagged' | 'revision-asked' | 'refused' | (string & {});\n readonly afterRevision?: boolean;\n /** The evidence index hit its ceiling — the verdict judged a partial corpus. */\n readonly evidenceTruncated?: boolean;\n}\n\n/** The turn-start verdict — one per turn on a cascade graph. */\nexport interface SkillTurnStart {\n readonly turnIndex: number;\n readonly by: SkillTurnVerdict;\n readonly from?: string;\n readonly to?: string;\n readonly scorer?: string;\n readonly scores: readonly {\n readonly id: string;\n readonly score: number;\n readonly relevance: number;\n }[];\n readonly runnerUp?: { readonly id: string; readonly gap: number };\n readonly decisive?: boolean;\n readonly witness?: SkillRouteWitness;\n readonly offered?: readonly string[];\n readonly stayOffered?: boolean;\n readonly policy?: {\n readonly nearTieMargin?: number;\n readonly menuSize?: number;\n readonly floor?: number;\n };\n readonly window?: number;\n readonly droppedResume?: { readonly id: string; readonly reason?: string };\n readonly decider?: { readonly provider?: string; readonly model?: string; readonly picked?: string };\n}\n\n/**\n * One iteration of the run, from the routing point of view. There is a hop for\n * every iteration the log mentions — including iterations whose evaluation\n * carried no `cursorMove` (`by` is then `undefined`), because dropping those\n * would drop the refusals and tool menus stamped on them.\n */\nexport interface SkillHop {\n readonly turnIndex: number;\n readonly iteration: number;\n /** The evaluate stage this iteration's cursor resolved at, when known —\n * the lens's ONE cursor, so a view can jump straight to it. */\n readonly runtimeStageId?: string;\n readonly from?: string;\n readonly to?: string;\n readonly by?: SkillCursorCause;\n /** Did the cursor actually change? Computed once so no view re-derives it. */\n readonly moved: boolean;\n readonly witness?: SkillRouteWitness;\n /** The turn-start menu this pick resolved, when the move closed one. */\n readonly offered?: readonly string[];\n /** The accepted pick was reachable but NOT on the offered menu. */\n readonly declinedOffer?: boolean;\n readonly activeIds: readonly string[];\n /** Entries whose own rule matched but which the cursor law kept off the wire. */\n readonly supersededIds: readonly string[];\n readonly refusals: readonly SkillRefusal[];\n readonly conflicts: readonly SkillRouteConflict[];\n readonly superseded: readonly SkillSuperseded[];\n /** The tool catalog as sent on this iteration — what the model chose from. */\n readonly toolsAsSent: readonly SkillToolAsSent[];\n /** `read_skill`'s description, verbatim: the menu that told the model what\n * was reachable. The highest-value field here — it is the model's own view\n * of the graph, and it is free on `stream.llm_start`. */\n readonly readSkillDescription?: string;\n /**\n * The REACHABLE set from the cursor this iteration's move landed on —\n * `cursorMove.reachable` (9.50.0), the same set the gate builds the\n * `read_skill` menu and its refusals from, as DATA. `[]` is a fact (a dead\n * end: nothing was admissible); ABSENT means the recording's era predates\n * the field and the set was honestly not on the record.\n */\n readonly reachable?: readonly string[];\n /**\n * The ASSEMBLED system prompt, byte-for-byte as sent to the provider\n * (`llm_start.systemPromptText`, 9.50.0). Present ONLY when the producer\n * opted in (`recordSystemPrompt: true` — off by default, for privacy);\n * absent, the string is honestly not in the recording.\n */\n readonly systemPromptText?: string;\n /** What the active skills put into the prompt on this iteration. */\n readonly skillInjections: readonly SkillInjectionSeen[];\n readonly evidence?: SkillEvidenceCheck;\n}\n\n/** The whole routing record for a run. */\nexport interface SkillRoute {\n /**\n * Did this run route at all? `true` when the log carried ANY routing fact —\n * a skill catalog, a cursor move, a turn verdict, a refusal, a conflict, a\n * superseded pick. `false` on an agent with no skills, so a view says \"this\n * run had no skill graph\" instead of drawing an empty one. Note that\n * `context.evaluated` fires on EVERY agent run (the injection engine\n * evaluates whether or not skills exist), so its mere presence proves\n * nothing and is deliberately not the signal.\n */\n readonly hasRouting: boolean;\n readonly nodes: readonly SkillRouteNode[];\n readonly hops: readonly SkillHop[];\n readonly observedEdges: readonly SkillObservedEdge[];\n readonly declaredEdges: readonly SkillDeclaredEdge[];\n /**\n * `true` when the run's `agentfootprint.skill.graph_declared` event (9.50.0)\n * supplied the declared set — the author's WHOLE map, verbatim from the\n * built graph. `false` (or absent, on a route folded elsewhere) means\n * `declaredEdges` is the `routing[]` LOWER BOUND — an edge is named only\n * once it fires — and a view must say so.\n */\n readonly declaredComplete?: boolean;\n /**\n * Entry skills — targets of the declared map's synthetic-START edges\n * (`from: null` in `graph_declared`). Where a turn CAN begin, as the author\n * drew it. Absent when the recording carried no declared map (or it had no\n * entry edge), never guessed from observed cold starts.\n */\n readonly entryIds?: readonly string[];\n readonly turns: readonly SkillTurnStart[];\n}\n\nexport interface SelectSkillRouteArgs {\n /** The lens event log, in order (`recorder.selectEventLog()`). */\n readonly log: readonly EventLogEntry[];\n}\n\n// ─── Event types read here (raw strings — see the era note) ──────────────\n\nconst EVALUATED = 'agentfootprint.context.evaluated';\nconst INJECTED = 'agentfootprint.context.injected';\nconst LLM_START = 'agentfootprint.stream.llm_start';\nconst TOOL_START = 'agentfootprint.stream.tool_start';\nconst TOOL_END = 'agentfootprint.stream.tool_end';\nconst REJECTED = 'agentfootprint.skill.rejected';\nconst GRAPH_DECLARED = 'agentfootprint.skill.graph_declared';\nconst ROUTE_CONFLICT = 'agentfootprint.skill.route_conflict';\nconst REROUTE_SUPERSEDED = 'agentfootprint.skill.reroute_superseded';\nconst TURN_ROUTED = 'agentfootprint.skill.turn_routed';\nconst TURN_START = 'agentfootprint.agent.turn_start';\nconst ITERATION_START = 'agentfootprint.agent.iteration_start';\nconst EVIDENCE_CHECKED = 'agentfootprint.agent.evidence_checked';\n\n/** The gate's own tool — the one whose description carries the menu. */\nconst READ_SKILL = 'read_skill';\n\n// ─── Small readers (payloads are data, never trusted shapes) ─────────────\n\ntype Payload = Record<string, unknown>;\n\nfunction payloadOf(entry: EventLogEntry): Payload {\n const p = (entry.event as { payload?: unknown }).payload;\n return p !== null && typeof p === 'object' ? (p as Payload) : {};\n}\n\nfunction str(v: unknown): string | undefined {\n return typeof v === 'string' ? v : undefined;\n}\n\nfunction num(v: unknown): number | undefined {\n return typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n}\n\nfunction strList(v: unknown): readonly string[] {\n return Array.isArray(v) ? v.filter((x): x is string => typeof x === 'string') : [];\n}\n\nfunction obj(v: unknown): Payload | undefined {\n return v !== null && typeof v === 'object' && !Array.isArray(v) ? (v as Payload) : undefined;\n}\n\n/**\n * `{ key: value }` when the value is there, `{}` when it is not — the\n * \"absent ⇒ omitted\" law as one expression, so every optional field on every\n * folded shape obeys it the same way instead of each spread re-deriving it.\n */\nfunction some<K extends string, V>(key: K, value: V | undefined): { [P in K]?: V } {\n return value === undefined ? {} : ({ [key]: value } as { [P in K]?: V });\n}\n\nfunction witnessOf(v: unknown): SkillRouteWitness | undefined {\n const w = obj(v);\n const text = str(w?.text);\n if (text === undefined) return undefined;\n const keyword = str(w?.keyword);\n return keyword === undefined ? { text } : { text, keyword };\n}\n\n// ─── The fold ────────────────────────────────────────────────────────────\n\n/** A hop under construction — the readonly `SkillHop` is built from it at the end. */\ninterface HopDraft {\n turnIndex: number;\n iteration: number;\n runtimeStageId?: string;\n from?: string;\n to?: string;\n by?: SkillCursorCause;\n witness?: SkillRouteWitness;\n offered?: readonly string[];\n declinedOffer?: boolean;\n activeIds: readonly string[];\n supersededIds: readonly string[];\n refusals: SkillRefusal[];\n conflicts: SkillRouteConflict[];\n superseded: SkillSuperseded[];\n toolsAsSent: readonly SkillToolAsSent[];\n readSkillDescription?: string;\n reachable?: readonly string[];\n systemPromptText?: string;\n skillInjections: SkillInjectionSeen[];\n evidence?: SkillEvidenceCheck;\n}\n\n/**\n * Did the cursor change on this iteration? A cold start (no `from`, a `to`) IS\n * a move — the cursor went from nowhere to somewhere; `by: 'stay'` never is,\n * and `by: 'none'` has no `to` to move to.\n */\nfunction didMove(d: Pick<HopDraft, 'by' | 'from' | 'to'>): boolean {\n return d.by !== undefined && d.to !== undefined && d.to !== d.from;\n}\n\n/**\n * A refusal that has been recorded but whose SENTENCE has not arrived yet —\n * the gate fires `skill.rejected` between the refused call's `tool_start` and\n * its `tool_end`, so the text the model read is always one event behind.\n */\ninterface PendingRefusal {\n /** Where the refusal was filed, so the result can be written back to it. */\n draftKey: string;\n index: number;\n requestedId: string;\n /** Still waiting for the tool result that carries the refusal sentence. */\n awaitingResult: boolean;\n}\n\n/**\n * Fold the log into the routing record.\n *\n * Never throws: a payload that is not the shape this reads contributes nothing\n * rather than aborting the fold — a debugger that dies on one malformed event\n * is worse than one that shows the rest of the run.\n *\n * @example\n * ```ts\n * const route = selectSkillRoute({ log: recorder.selectEventLog() });\n * const refused = route.hops.flatMap((h) => h.refusals);\n * // \"audit-log was refused on iteration 1, and the cursor stayed at triage\"\n * refused[0].requestedId; // 'audit-log'\n * refused[0].allowed; // ['volume-lookup']\n * refused[0].cursorAfter?.by; // 'stay'\n * refused[0].cursorAfter?.moved; // false\n * ```\n */\nexport function selectSkillRoute(args: SelectSkillRouteArgs): SkillRoute {\n const { log } = args;\n\n interface CatalogRow {\n description?: string;\n kind?: string;\n label?: string;\n }\n const drafts = new Map<string, HopDraft>();\n const catalog = new Map<string, CatalogRow>();\n const visited = new Set<string>();\n const declared = new Map<string, SkillDeclaredEdge>();\n const turns: SkillTurnStart[] = [];\n const pendingRefusals: PendingRefusal[] = [];\n /** Open tool calls, by the provider's call id — `tool_end` carries no name. */\n const openCalls = new Map<string, { name: string; requestedId?: string }>();\n\n let hasRouting = false;\n let declaredComplete = false;\n const entryIds: string[] = [];\n // The running address. Iterations restart at 1 on every turn, so a hop key\n // without the turn would collide across turns of one conversation.\n let turnIndex = 0;\n let iteration = 0;\n\n const keyOf = (t: number, i: number): string => `${t}:${i}`;\n\n const draftAt = (t: number, i: number): HopDraft => {\n const key = keyOf(t, i);\n let draft = drafts.get(key);\n if (draft === undefined) {\n draft = {\n turnIndex: t,\n iteration: i,\n activeIds: [],\n supersededIds: [],\n refusals: [],\n conflicts: [],\n superseded: [],\n toolsAsSent: [],\n skillInjections: [],\n };\n drafts.set(key, draft);\n }\n return draft;\n };\n\n for (const entry of log) {\n const type = str((entry.event as { type?: unknown }).type) ?? '';\n const p = payloadOf(entry);\n\n // Keep the running address current BEFORE the event is folded: every\n // iteration-stamped payload updates it, and the un-stamped events\n // (tool_start / tool_end) inherit whatever the last stamp said — which is\n // exactly where they ran.\n if (type === TURN_START) {\n turnIndex = num(p.turnIndex) ?? turnIndex;\n iteration = 0;\n continue;\n }\n if (type === ITERATION_START) {\n turnIndex = num(p.turnIndex) ?? turnIndex;\n iteration = num(p.iterIndex) ?? iteration;\n draftAt(turnIndex, iteration);\n continue;\n }\n const stamped = num(p.iteration);\n if (stamped !== undefined) iteration = stamped;\n\n switch (type) {\n case EVALUATED: {\n const draft = draftAt(turnIndex, iteration);\n // The evaluate stage IS where the cursor resolved, so its stage id is\n // the one a view should move the lens cursor to for this hop.\n draft.runtimeStageId = entry.runtimeStageId ?? draft.runtimeStageId;\n draft.activeIds = strList(p.activeIds);\n draft.supersededIds = strList(p.supersededIds);\n\n for (const row of Array.isArray(p.skillCatalog) ? p.skillCatalog : []) {\n const c = obj(row);\n const id = str(c?.id);\n if (id === undefined) continue;\n hasRouting = true;\n const known = catalog.get(id) ?? {};\n if (known.description === undefined) known.description = str(c?.description);\n catalog.set(id, known);\n }\n\n for (const row of Array.isArray(p.routing) ? p.routing : []) {\n const r = obj(row);\n const to = str(r?.injectionId);\n const from = str(r?.from);\n // Only a `route` entry names an edge; `entry` / `tree` / `model`\n // provenance says how a skill is REACHABLE, not that an edge exists.\n if (to === undefined || from === undefined) continue;\n const edgeKey = `${from}->${to}`;\n if (!declared.has(edgeKey)) {\n const label = str(r?.label);\n const triggerKind = str(r?.triggerKind);\n declared.set(edgeKey, {\n from,\n to,\n ...(label !== undefined ? { label } : {}),\n ...(triggerKind !== undefined ? { triggerKind } : {}),\n });\n }\n }\n\n const move = obj(p.cursorMove);\n if (move !== undefined) {\n hasRouting = true;\n draft.from = str(move.from);\n draft.to = str(move.to);\n draft.by = str(move.by);\n draft.witness = witnessOf(move.witness);\n if (Array.isArray(move.offered)) draft.offered = strList(move.offered);\n // `[]` is kept: an empty reachable set is a dead end, a FACT — only\n // a missing field (an older era) leaves the hop's set absent.\n if (Array.isArray(move.reachable)) draft.reachable = strList(move.reachable);\n if (typeof move.declinedOffer === 'boolean') draft.declinedOffer = move.declinedOffer;\n if (draft.to !== undefined) visited.add(draft.to);\n if (draft.from !== undefined) visited.add(draft.from);\n }\n break;\n }\n\n case LLM_START: {\n const draft = draftAt(turnIndex, iteration);\n const tools: SkillToolAsSent[] = [];\n for (const row of Array.isArray(p.tools) ? p.tools : []) {\n const t = obj(row);\n const name = str(t?.name);\n if (name === undefined) continue;\n const description = str(t?.description);\n tools.push(description === undefined ? { name } : { name, description });\n // The gate rewrites this description every iteration with the\n // reachable set — it is the model's own view of the graph, and the\n // only place a reader can see the menu the model was actually given.\n if (name === READ_SKILL && description !== undefined) {\n draft.readSkillDescription = description;\n }\n }\n draft.toolsAsSent = tools;\n // The assembled prompt rides llm_start only when the producer opted\n // in (`recordSystemPrompt: true`) — absent is the privacy default.\n const promptText = str(p.systemPromptText);\n if (promptText !== undefined) draft.systemPromptText = promptText;\n break;\n }\n\n case GRAPH_DECLARED: {\n // The author's map, verbatim from the built graph (9.50.0) — fired\n // once per run, right after the run manifest. Everything it names is\n // COMPLETE (the whole declaration), unlike `routing[]`'s fired-only\n // lower bound; `declaredComplete` records which one this route has.\n hasRouting = true;\n declaredComplete = true;\n for (const row of Array.isArray(p.nodes) ? p.nodes : []) {\n const n = obj(row);\n const id = str(n?.id);\n if (id === undefined) continue;\n const existing = catalog.get(id) ?? {};\n if (existing.description === undefined) existing.description = str(n?.description);\n if (existing.kind === undefined) existing.kind = str(n?.kind);\n if (existing.label === undefined) existing.label = str(n?.label);\n catalog.set(id, existing);\n }\n for (const row of Array.isArray(p.edges) ? p.edges : []) {\n const e = obj(row);\n const to = str(e?.to);\n if (to === undefined) continue;\n // `from: null` is the synthetic START — an entry fact, not an edge\n // between two skills. Kept apart so a canvas can mark entries\n // without drawing a node that does not exist.\n if (e?.from === null) {\n if (!entryIds.includes(to)) entryIds.push(to);\n continue;\n }\n const from = str(e?.from);\n if (from === undefined) continue;\n const edgeKey = `${from}->${to}`;\n if (!declared.has(edgeKey)) {\n declared.set(edgeKey, {\n from,\n to,\n ...some('label', str(e?.label)),\n ...some('triggerKind', str(e?.kind)),\n });\n }\n }\n break;\n }\n\n case INJECTED: {\n // Skill-sourced injections only: this is the ROUTING record, and a\n // user message or the base prompt is not a routing fact.\n if (str(p.source) !== 'skill') break;\n const draft = draftAt(turnIndex, iteration);\n const slot = str(p.slot) ?? 'unknown';\n const skillId = str(p.sourceId);\n const text = str(p.rawContent);\n draft.skillInjections.push({\n slot,\n ...(skillId !== undefined ? { skillId } : {}),\n summary: str(p.contentSummary) ?? '',\n ...(text !== undefined ? { text } : {}),\n });\n break;\n }\n\n case TOOL_START: {\n const id = str(p.toolCallId);\n const name = str(p.toolName);\n if (id === undefined || name === undefined) break;\n const requestedId = str(obj(p.args)?.id);\n openCalls.set(id, { name, ...(requestedId !== undefined ? { requestedId } : {}) });\n break;\n }\n\n case TOOL_END: {\n const id = str(p.toolCallId);\n if (id === undefined) break;\n const call = openCalls.get(id);\n openCalls.delete(id);\n if (call === undefined || call.name !== READ_SKILL) break;\n const result = str(p.result);\n if (result === undefined) break;\n // Pair the refusal with the sentence the model read back. The gate\n // fires `skill.rejected` BETWEEN this call's start and end, so the\n // still-unanswered refusal for this same requested id is this call's.\n for (let i = pendingRefusals.length - 1; i >= 0; i--) {\n const pending = pendingRefusals[i]!;\n if (!pending.awaitingResult) continue;\n if (call.requestedId !== undefined && pending.requestedId !== call.requestedId) continue;\n const draft = drafts.get(pending.draftKey);\n const refusal = draft?.refusals[pending.index];\n if (refusal === undefined) continue;\n draft!.refusals[pending.index] = { ...refusal, toolCallId: id, refusalText: result };\n pending.awaitingResult = false;\n break;\n }\n break;\n }\n\n case REJECTED: {\n hasRouting = true;\n const requestedId = str(p.requestedId);\n if (requestedId === undefined) break;\n const draft = draftAt(turnIndex, iteration);\n const currentSkillId = str(p.currentSkillId);\n const posture = str(p.posture);\n draft.refusals.push({\n requestedId,\n ...(currentSkillId !== undefined ? { currentSkillId } : {}),\n allowed: strList(p.allowed),\n ...(posture !== undefined ? { posture } : {}),\n turnIndex,\n iteration,\n });\n pendingRefusals.push({\n draftKey: keyOf(turnIndex, iteration),\n index: draft.refusals.length - 1,\n requestedId,\n awaitingResult: true,\n });\n break;\n }\n\n case ROUTE_CONFLICT: {\n hasRouting = true;\n const draft = draftAt(turnIndex, iteration);\n const outcome = (row: Payload): SkillRouteConflict['losers'][number] => ({\n ...some('toolName', str(row.toolName)),\n ...some('target', str(row.target)),\n ...some('toolCallId', str(row.toolCallId)),\n });\n const winner = obj(p.winner);\n draft.conflicts.push({\n ...some('fromSkillId', str(p.fromSkillId)),\n ...some('winner', winner !== undefined ? outcome(winner) : undefined),\n losers: (Array.isArray(p.losers) ? p.losers : [])\n .map((row) => obj(row))\n .filter((l): l is Payload => l !== undefined)\n .map(outcome),\n ...some('source', str(p.source)),\n });\n break;\n }\n\n case REROUTE_SUPERSEDED: {\n hasRouting = true;\n const volunteeredId = str(p.volunteeredId);\n if (volunteeredId === undefined) break;\n const draft = draftAt(turnIndex, iteration);\n draft.superseded.push({\n volunteeredId,\n ...some('wonId', str(p.wonId)),\n ...some('fromSkillId', str(p.fromSkillId)),\n ...some('source', str(p.source)),\n });\n break;\n }\n\n case TURN_ROUTED: {\n hasRouting = true;\n const by = str(p.by);\n if (by === undefined) break;\n const scores = (Array.isArray(p.scores) ? p.scores : [])\n .map((row) => obj(row))\n .filter((s): s is Payload => s !== undefined)\n .map((s) => ({\n id: str(s.id) ?? '',\n score: num(s.score) ?? 0,\n relevance: num(s.relevance) ?? 0,\n }));\n const runnerUp = obj(p.runnerUp);\n const runnerUpId = str(runnerUp?.id);\n const policy = obj(p.policy);\n const dropped = obj(p.droppedResume);\n const droppedId = str(dropped?.id);\n const decider = obj(p.decider);\n turns.push({\n turnIndex,\n by,\n ...some('from', str(p.from)),\n ...some('to', str(p.to)),\n ...some('scorer', str(p.scorer)),\n scores,\n ...some(\n 'runnerUp',\n runnerUpId !== undefined\n ? { id: runnerUpId, gap: num(runnerUp?.gap) ?? 0 }\n : undefined,\n ),\n ...some('decisive', typeof p.decisive === 'boolean' ? p.decisive : undefined),\n ...some('witness', witnessOf(p.witness)),\n ...some('offered', Array.isArray(p.offered) ? strList(p.offered) : undefined),\n ...some('stayOffered', typeof p.stayOffered === 'boolean' ? p.stayOffered : undefined),\n ...some(\n 'policy',\n policy !== undefined\n ? {\n ...some('nearTieMargin', num(policy.nearTieMargin)),\n ...some('menuSize', num(policy.menuSize)),\n ...some('floor', num(policy.floor)),\n }\n : undefined,\n ),\n ...some('window', num(p.window)),\n ...some(\n 'droppedResume',\n droppedId !== undefined\n ? { id: droppedId, ...some('reason', str(dropped?.reason)) }\n : undefined,\n ),\n ...some(\n 'decider',\n decider !== undefined\n ? {\n ...some('provider', str(decider.provider)),\n ...some('model', str(decider.model)),\n ...some('picked', str(decider.picked)),\n }\n : undefined,\n ),\n });\n break;\n }\n\n case EVIDENCE_CHECKED: {\n const draft = draftAt(turnIndex, iteration);\n const unsupported = (Array.isArray(p.unsupported) ? p.unsupported : [])\n .map((row) => obj(row))\n .filter((u): u is Payload => u !== undefined)\n .map((u) => ({ value: str(u.value) ?? '', ...some('shape', str(u.shape)) }));\n draft.evidence = {\n ...some('posture', str(p.posture)),\n ...some('candidates', num(p.candidates)),\n unsupported,\n ...some('action', str(p.action)),\n ...some('afterRevision', typeof p.afterRevision === 'boolean' ? p.afterRevision : undefined),\n ...some(\n 'evidenceTruncated',\n typeof p.evidenceTruncated === 'boolean' ? p.evidenceTruncated : undefined,\n ),\n };\n break;\n }\n\n default:\n break;\n }\n }\n\n // ── Link each refusal to the cursor row that answered it ───────────────\n // The lag is the law (see the header): the resolution of an iteration's pick\n // is reported by the NEXT iteration's evaluate. No next row ⇒ no claim.\n for (const draft of drafts.values()) {\n if (draft.refusals.length === 0) continue;\n const next = drafts.get(keyOf(draft.turnIndex, draft.iteration + 1));\n if (next === undefined || next.by === undefined) continue;\n const cursorAfter: SkillCursorAfter = {\n iteration: next.iteration,\n ...(next.from !== undefined ? { from: next.from } : {}),\n ...(next.to !== undefined ? { to: next.to } : {}),\n by: next.by,\n moved: didMove(next),\n };\n draft.refusals = draft.refusals.map((refusal) => ({ ...refusal, cursorAfter }));\n }\n\n // ── Freeze the drafts, in the order the run produced them ──────────────\n const hops: SkillHop[] = [...drafts.values()].map((d) => ({\n turnIndex: d.turnIndex,\n iteration: d.iteration,\n ...(d.runtimeStageId !== undefined ? { runtimeStageId: d.runtimeStageId } : {}),\n ...(d.from !== undefined ? { from: d.from } : {}),\n ...(d.to !== undefined ? { to: d.to } : {}),\n ...(d.by !== undefined ? { by: d.by } : {}),\n moved: didMove(d),\n ...(d.witness !== undefined ? { witness: d.witness } : {}),\n ...(d.offered !== undefined ? { offered: d.offered } : {}),\n ...(d.declinedOffer !== undefined ? { declinedOffer: d.declinedOffer } : {}),\n activeIds: d.activeIds,\n supersededIds: d.supersededIds,\n refusals: d.refusals,\n conflicts: d.conflicts,\n superseded: d.superseded,\n toolsAsSent: d.toolsAsSent,\n ...(d.readSkillDescription !== undefined\n ? { readSkillDescription: d.readSkillDescription }\n : {}),\n ...(d.reachable !== undefined ? { reachable: d.reachable } : {}),\n ...(d.systemPromptText !== undefined ? { systemPromptText: d.systemPromptText } : {}),\n skillInjections: d.skillInjections,\n ...(d.evidence !== undefined ? { evidence: d.evidence } : {}),\n }));\n\n // ── The graph: catalog nodes, the hops the cursor was seen to take ─────\n const nodes: SkillRouteNode[] = [...catalog.entries()].map(([id, row]) => ({\n id,\n ...(row.description !== undefined ? { description: row.description } : {}),\n ...(row.kind !== undefined ? { kind: row.kind } : {}),\n ...(row.label !== undefined ? { label: row.label } : {}),\n visited: visited.has(id),\n }));\n // A cursor position the catalog never listed is still a node of the graph\n // the run walked — report it rather than silently dropping the hop's target.\n for (const id of visited) {\n if (!catalog.has(id)) nodes.push({ id, visited: true });\n }\n\n const edges = new Map<string, { edge: SkillObservedEdge; takenAt: SkillHopRef[] }>();\n for (const hop of hops) {\n if (!hop.moved || hop.to === undefined || hop.by === undefined) continue;\n const key = `${hop.from ?? ''}->${hop.to}#${hop.by}`;\n const declaredEdge = hop.from !== undefined ? declared.get(`${hop.from}->${hop.to}`) : undefined;\n let row = edges.get(key);\n if (row === undefined) {\n const takenAt: SkillHopRef[] = [];\n row = {\n edge: {\n ...(hop.from !== undefined ? { from: hop.from } : {}),\n to: hop.to,\n by: hop.by,\n takenAt,\n ...(declaredEdge?.label !== undefined ? { label: declaredEdge.label } : {}),\n ...(declaredEdge?.triggerKind !== undefined\n ? { triggerKind: declaredEdge.triggerKind }\n : {}),\n },\n takenAt,\n };\n edges.set(key, row);\n }\n row.takenAt.push({ turnIndex: hop.turnIndex, iteration: hop.iteration });\n }\n\n return {\n hasRouting,\n nodes,\n hops,\n observedEdges: [...edges.values()].map((r) => r.edge),\n declaredEdges: [...declared.values()],\n declaredComplete,\n ...(entryIds.length > 0 ? { entryIds } : {}),\n turns,\n };\n}\n","/**\n * humanizeRouting — natural-language lines for the skill-graph ROUTING\n * events (agentfootprint 9.16.0 / 9.17.0):\n *\n * • `agentfootprint.skill.turn_routed` — the turn-start routing verdict\n * • `agentfootprint.skill.route_conflict`— edge-vs-edge inside one tool batch\n * • `agentfootprint.skill.rejected` — now with a `posture` refusal reason\n * • `context.evaluated`'s `cursorMove` — the model-pick decorations\n * (`offered` / `declinedOffer`)\n *\n * Pattern: pure sentence builders, composed by `defaultHumanizer`.\n * Role: plain-language commentary for a NON-developer reader.\n *\n * Era note (the budget_pressure precedent, applied at the TYPE level):\n * `turn_routed` / `route_conflict` postdate the agentfootprint version this\n * package compiles against, so its `AgentfootprintEvent` union does not name\n * them yet. They are matched by raw type STRING and their payloads are read\n * through the local structural interfaces below — mirrors of the shipped\n * shapes (agentfootprint src/events/payloads.ts), every field optional-safe.\n * A recording from an older era simply carries fewer fields, and the\n * house law applies: render ONLY what the event carries — an absent field\n * means its clause is OMITTED, never guessed.\n *\n * Vocabulary note — TWO vocabularies, and they are not the same list:\n *\n * • `turn_routed.by` (which TIER decided where the turn started) ships as\n * `'entry' | 'intent' | 'continuity' | 'menu' | 'decider' | 'none'`.\n * `'entry'` is the recorded vocabulary for rule-won starts, `'menu'` for an\n * in-band menu, `'decider'` for a tier-3 resolver (agentfootprint 9.19.0).\n * Design-era aliases (`'rule'`, `'model-pick'`) are tolerated as synonyms\n * so a recording from an intermediate build still renders.\n * • `cursorMove.by` (what moved the CURSOR this iteration) ships as\n * `'entry' | 'route' | 'model-pick' | 'tool-proposal' | 'intent' |\n * 'continuity' | 'decider' | 'stay' | 'none'` — nine causes, the union\n * `CursorMoveCause` in agentfootprint's `skillGraph.ts`. The library's own\n * `ContextEvaluatedPayload` docstring lists only seven (it predates\n * `'tool-proposal'` and `'decider'`); the union is the source of truth.\n * `'menu'` is NOT one of them — an offer is not a move.\n *\n * Anything outside either list falls back to an honest raw line.\n */\n\n// ─── Structural payload mirrors (events read, never constructed here) ───\n\n/** Mirror of `SkillTurnRoutedPayload` (agentfootprint 9.17.0). */\nexport interface TurnRoutedLike {\n readonly by: string;\n readonly from?: string;\n readonly to?: string;\n readonly scorer?: string;\n readonly window?: number;\n readonly scores?: ReadonlyArray<{\n readonly id: string;\n readonly score: number;\n readonly relevance: number;\n }>;\n readonly runnerUp?: { readonly id: string; readonly gap: number };\n readonly decisive?: boolean;\n readonly offered?: readonly string[];\n readonly stayOffered?: boolean;\n /** `reason` is optional because a RECORDING's is: the fold that reads these\n * events (`selectSkillRoute`) omits a field the payload did not carry, and\n * no sentence here reads it — only the id is spoken. Requiring it would\n * make a real recording un-narratable over a word nobody says. */\n readonly droppedResume?: { readonly id: string; readonly reason?: string };\n readonly policy?: {\n readonly nearTieMargin?: number;\n readonly menuSize?: number;\n readonly floor?: number;\n };\n}\n\n/** Mirror of `SkillRouteConflictPayload` (agentfootprint 9.16.0). */\nexport interface RouteConflictLike {\n readonly iteration?: number;\n readonly fromSkillId?: string;\n readonly winner?: {\n readonly toolCallId?: string;\n readonly toolName?: string;\n readonly target?: string;\n };\n readonly losers?: ReadonlyArray<{\n readonly toolCallId?: string;\n readonly toolName?: string;\n readonly target?: string;\n }>;\n}\n\n/** Mirror of `SkillRejectedPayload` + the 9.17.0 `posture` widening. */\nexport interface SkillRejectedLike {\n readonly requestedId: string;\n readonly currentSkillId?: string;\n /** The REACHABILITY set (hops + open skills) — NOT the offered menu. A\n * guard refusal fires only AFTER reachability passed, so under\n * posture='guard' this list always contains the refused `requestedId`\n * itself; the menu the model was shown is not on the event at all. */\n readonly allowed?: readonly string[];\n readonly iteration?: number;\n readonly posture?: string; // 'guard' | 'rails' when a posture refused\n}\n\n/**\n * Mirror of `ContextEvaluatedPayload.cursorMove` — the 9.17.0 decorations\n * (`offered` / `declinedOffer`) and the 9.28.0 `witness` included.\n *\n * `by` is typed as the NINE shipped causes plus a `(string & {})` arm, so\n * editors autocomplete the real vocabulary while a future era's tenth value\n * still reads (and renders through the honest raw fallback).\n */\nexport interface CursorMoveLike {\n readonly from?: string;\n readonly to?: string;\n readonly by:\n | 'entry'\n | 'route'\n | 'model-pick'\n | 'tool-proposal'\n | 'intent'\n | 'continuity'\n | 'decider'\n | 'stay'\n | 'none'\n | (string & {});\n readonly offered?: readonly string[];\n readonly declinedOffer?: boolean;\n /** WHAT the message said that routed this hop — `match:` rules only. */\n readonly witness?: { readonly text: string; readonly keyword?: string };\n}\n\n// ─── Small shared formatters ─────────────────────────────────────────────\n\n/** Raw scorer score → short human number (\"0.87\"). */\nfunction fmtScore(n: number): string {\n return String(Number(n.toFixed(2)));\n}\n\n/** Softmax relevance share → whole percent (\"44%\"). */\nfunction fmtShare(n: number): string {\n return `${Math.round(n * 100)}%`;\n}\n\nfunction quoteList(ids: readonly string[]): string {\n return ids.join(', ');\n}\n\n/** \"billing\" → `\"billing\"`, undefined → undefined. */\nfunction q(id: string | undefined): string | undefined {\n return id === undefined ? undefined : `\"${id}\"`;\n}\n\n/** The movement clause shared by several verdicts:\n * from+to (different) → Moved from A to B; same / no from → started/stayed. */\nfunction movement(p: { from?: string; to?: string }): string {\n const { from, to } = p;\n if (to !== undefined && from !== undefined) {\n return from === to ? `Stayed in ${q(to)}` : `Moved from ${q(from)} to ${q(to)}`;\n }\n if (to !== undefined) return `Turn started in ${q(to)}`;\n if (from !== undefined) return `Stayed in ${q(from)}`;\n return 'The turn was routed';\n}\n\n/** The droppedResume clause — appended to any verdict it decorates. */\nfunction droppedResumeNote(p: TurnRoutedLike): string {\n const d = p.droppedResume;\n if (!d) return '';\n return ` The conversation's saved place (${q(d.id)}) no longer exists in this deployment.`;\n}\n\n// ─── agentfootprint.skill.turn_routed ────────────────────────────────────\n\nexport function humanizeTurnRouted(p: TurnRoutedLike): string {\n switch (p.by) {\n case 'entry':\n case 'rule': // design-era alias for 'entry'\n return `${movement(p)} — a rule matched (tier 1).${droppedResumeNote(p)}`;\n\n case 'intent': {\n // Tier-2 scorer decided. Winner score = scores[0] (ranked best-first);\n // the runner-up's own score comes from its scores[] row.\n const winner = p.scores?.[0];\n const ruRow =\n p.runnerUp && p.scores ? p.scores.find((s) => s.id === p.runnerUp!.id) : undefined;\n let clause = ' — intent';\n if (winner) {\n clause += ` scored ${fmtScore(winner.score)}`;\n if (ruRow) clause += ` vs ${fmtScore(ruRow.score)} for runner-up ${q(ruRow.id)}`;\n else if (p.runnerUp)\n clause += `, ${fmtScore(p.runnerUp.gap)} ahead of ${q(p.runnerUp.id)}`;\n } else {\n clause += ' matched';\n }\n const scorer = p.scorer ? ` (${p.scorer} scorer)` : '';\n return `${movement(p)}${clause}${scorer}.${droppedResumeNote(p)}`;\n }\n\n case 'continuity': {\n // Near-tie stays carry the losing numbers; sticky stays carry none.\n const nearTie = p.decisive === false && p.scores && p.scores.length >= 2;\n if (nearTie) {\n const [a, b] = p.scores!;\n const where = p.to ?? p.from;\n const tail = where !== undefined ? `; continuing in ${q(where)}` : '';\n return (\n `Stayed — near-tie between ${q(a.id)} (${fmtShare(a.relevance)}) and ` +\n `${q(b.id)} (${fmtShare(b.relevance)})${tail}.${droppedResumeNote(p)}`\n );\n }\n const where = p.to ?? p.from;\n const head = where !== undefined ? `Stayed in ${q(where)}` : 'Stayed';\n return `${head} — treated as a continuation of the conversation.${droppedResumeNote(p)}`;\n }\n\n case 'menu':\n case 'model-pick': {\n // A menu went in-band; the turn's start awaits the model's pick\n // (the pick itself lands as a cursorMove `model-pick`).\n let line = 'No single skill won — the model was offered a menu';\n if (p.offered && p.offered.length > 0)\n line += ` of ${p.offered.length}: ${quoteList(p.offered)}`;\n if (p.stayOffered) line += `; 'stay' was on the menu`;\n return `${line}.${droppedResumeNote(p)}`;\n }\n\n case 'none': {\n // Nothing the loop may act on: a dropped resume, or a rails-mode\n // menu whose offer is recorded but not actionable by the model.\n if (p.droppedResume) {\n let line =\n `The conversation's saved place (${q(p.droppedResume.id)}) no longer exists ` +\n `in this deployment — the turn started fresh.`;\n if (p.offered && p.offered.length > 0)\n line += ` Options on the record: ${quoteList(p.offered)}.`;\n return line;\n }\n if (p.offered && p.offered.length > 0) {\n // WHY the cascade ended in a menu is said only when the event carries\n // the evidence (house law): `decisive: false` with a floor-clearing\n // top score is a near-tie — intent DID match, more than once, and\n // `offered` is the tied set; a top score at/below the recorded floor\n // is honestly unmatched — `offered` is the full menu, so \"close\n // candidates\" would be a lie there. No numbers → posture-only prose.\n const top = p.scores?.[0]?.score;\n const floor = p.policy?.floor;\n const cleared =\n top !== undefined && Number.isFinite(top) && (floor === undefined || top > floor);\n if (p.decisive === false && (p.scores?.length ?? 0) >= 2 && cleared) {\n return (\n `This message matched ${quoteList(p.offered)} too closely to call, and rails ` +\n `mode does not let the model break the tie — the turn ran on the base prompt.`\n );\n }\n if (top !== undefined && !cleared) {\n return (\n `No rule or intent matched this message strongly enough, and rails mode does ` +\n `not let the model route — the turn ran on the base prompt. The full menu ` +\n `stayed on the record: ${quoteList(p.offered)}.`\n );\n }\n return (\n `Routing ended in a menu, but rails mode does not let the model route — ` +\n `the turn ran on the base prompt. On the record: ${quoteList(p.offered)}.`\n );\n }\n return 'Routing decided nothing this turn — the turn ran on the base prompt.';\n }\n\n default:\n // Unknown verdict vocabulary (a future era): render honestly, raw.\n return `Turn routing: ${p.by}${p.to !== undefined ? ` → ${q(p.to)}` : ''}.`;\n }\n}\n\n// ─── agentfootprint.skill.route_conflict ─────────────────────────────────\n\nconst COUNT_WORDS = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five'] as const;\n\nexport function humanizeRouteConflict(p: RouteConflictLike): string {\n const winner = p.winner;\n const losers = p.losers ?? [];\n const winnerClause = winner\n ? `${winner.toolName ?? 'a tool'} → ${q(winner.target) ?? 'its target'} won (first in the batch)`\n : 'the first match in the batch won';\n\n if (losers.length === 0) {\n return `Tool results conflicted on the next skill — ${winnerClause}.`;\n }\n\n const count = losers.length + 1;\n const countWord = count < COUNT_WORDS.length ? COUNT_WORDS[count] : String(count);\n const loserClause = losers\n .map((l) => `${l.toolName ?? 'a tool'} → ${q(l.target) ?? 'another skill'}`)\n .join(', ');\n const verb = losers.length === 1 ? 'was' : 'were';\n return (\n `${countWord} tool results wanted different next skills — ${winnerClause}; ` +\n `${loserClause} ${verb} suppressed and ${losers.length === 1 ? 'is' : 'are'} on the record.`\n );\n}\n\n// ─── agentfootprint.skill.rejected (with 9.17.0 posture) ─────────────────\n\nexport function humanizeSkillRejected(p: SkillRejectedLike): string {\n const pick = `The model tried to switch to ${q(p.requestedId)}`;\n if (p.posture === 'guard') {\n // `allowed` is the reachability set, which ALWAYS contains the refused\n // pick under guard — presenting it as \"the menu\" would put the refused\n // pick on the very menu it was refused for. The actual offered menu is\n // not on the event, so the sentence labels the set as what it is.\n let line = `${pick}, but that pick was off the menu it was given — refused (guard).`;\n if (p.allowed && p.allowed.length > 0) line += ` Reachable: ${quoteList(p.allowed)}.`;\n return line;\n }\n if (p.posture === 'rails') {\n return `${pick}, but rails mode does not let the model route — refused.`;\n }\n // Today's reachability refusal, unchanged semantics.\n const at = p.currentSkillId !== undefined ? q(p.currentSkillId) : 'the start';\n let line = `${pick}, but it is not reachable from ${at} — refused.`;\n if (p.allowed && p.allowed.length > 0) line += ` Reachable: ${quoteList(p.allowed)}.`;\n return line;\n}\n\n// ─── context.evaluated cursorMove — the model-pick decorations ───────────\n\n/**\n * Renders the model-pick line, or `null` to keep the event silent.\n *\n * `context.evaluated` fires every iteration and stays hidden from commentary\n * (low signal); the ONE exception is the iteration a model pick lands WITH\n * its 9.17.0 decorations (`offered` / `declinedOffer`) — the assist-posture\n * divergence the record exists to explain. Undecorated model picks (older\n * recordings) stay silent, exactly as they rendered before.\n */\nexport function humanizeCursorMovePick(move: CursorMoveLike | undefined): string | null {\n if (!move || move.by !== 'model-pick') return null;\n if (move.offered === undefined && move.declinedOffer === undefined) return null;\n\n let line =\n move.to !== undefined ? `The model chose ${q(move.to)}` : 'The model made a pick';\n // A declined offer means the pick was NOT on this menu — and the emitter\n // always stamps `offered` alongside `declinedOffer` (they are one resolved\n // menu object), so the menu clause must stay silent here or the sentence\n // claims the pick came \"from\" a menu it then says the pick was not on.\n if (move.offered !== undefined && move.declinedOffer !== true) {\n line += ` from a ${move.offered.length}-item menu`;\n const rest = move.to !== undefined ? move.offered.filter((id) => id !== move.to) : move.offered;\n if (rest.length > 0) line += ` (${quoteList(rest)} were offered too)`;\n }\n if (move.from !== undefined && move.from !== move.to) {\n line += `, moving from ${q(move.from)}`;\n }\n line += '.';\n if (move.declinedOffer === true) {\n line += ' Its pick was not on the offered menu — the divergence is on the record.';\n }\n return line;\n}\n\n// ─── context.evaluated cursorMove — all NINE causes ──────────────────────\n\n/**\n * Where the cursor ended up, in plain words. `Moved from A to B` when it\n * changed, `Started in B` on a cold start, `Stayed in A` when it did not.\n */\nfunction cursorMovement(move: CursorMoveLike): string {\n const { from, to } = move;\n if (to !== undefined && from !== undefined) {\n return from === to ? `Stayed in ${q(to)}` : `Moved from ${q(from)} to ${q(to)}`;\n }\n if (to !== undefined) return `Started in ${q(to)}`;\n if (from !== undefined) return `Stayed in ${q(from)}`;\n return 'The skill in play was settled';\n}\n\n/**\n * The evidence clause for a rule that matched on the message TEXT — the\n * `witness` agentfootprint 9.28.0 added. Quoted, never paraphrased: the whole\n * value of a witness is that it is the user's own words.\n */\nfunction witnessNote(move: CursorMoveLike): string {\n const w = move.witness;\n if (!w || typeof w.text !== 'string' || w.text === '') return '';\n return w.keyword !== undefined\n ? ` The message said \"${w.text}\" — the word ${q(w.keyword)} is what matched.`\n : ` The message said \"${w.text}\".`;\n}\n\n/**\n * Renders the cursor hop for ANY of the nine causes, or `null` to stay silent.\n *\n * Coverage note (why this exists): `humanizeCursorMovePick` renders exactly one\n * cause, `'model-pick'`, and only when the 9.17.0 menu decorations are on it —\n * so on a skill-graph run the other eight causes rendered NOTHING at all, and\n * a reader watching a routed conversation saw no routing. This is that gap\n * closed. `'model-pick'` is deliberately delegated to the existing function\n * rather than rewritten: its sentence (and its silence on an undecorated\n * pick, which is what an older recording carries) is unchanged, byte for byte.\n *\n * The audience is a non-developer, so the vocabulary is deliberately plain:\n * \"a rule the author wrote\", \"a separate chooser\", \"nothing moved\".\n */\nexport function humanizeCursorMove(move: CursorMoveLike | undefined): string | null {\n if (!move || typeof move.by !== 'string') return null;\n\n switch (move.by) {\n case 'model-pick':\n // Unchanged — see the note above.\n return humanizeCursorMovePick(move);\n\n case 'entry':\n return `${cursorMovement(move)} — a rule the author wrote matched this message.${witnessNote(move)}`;\n\n case 'route':\n return `${cursorMovement(move)} — the author drew this path, and the run took it automatically.`;\n\n case 'tool-proposal':\n return `${cursorMovement(move)} — a tool asked for this move, and it was allowed.`;\n\n case 'intent': {\n const fit = move.to !== undefined ? ` ${q(move.to)} fit best` : ' one skill fit best';\n return `${cursorMovement(move)} — this message was compared against the skills, and${fit}.`;\n }\n\n case 'continuity':\n return `${cursorMovement(move)} — the conversation was already there, so it carried on.`;\n\n case 'decider':\n return `${cursorMovement(move)} — a separate chooser was asked to settle it, and this is what it picked.`;\n\n case 'stay': {\n // The sticky-stay clause: the resolver found nothing that could move the\n // cursor. This is the sentence a refused `read_skill` pick ends up\n // explaining, so it says plainly that nothing happened.\n const where = move.to ?? move.from;\n return where !== undefined\n ? `Nothing moved — it stayed in ${q(where)}.`\n : 'Nothing moved — the skill in play stayed as it was.';\n }\n\n case 'none':\n return 'No skill was in play — nothing routed this turn.';\n\n default:\n // A future era's cause: say what the record says, invent nothing.\n return `The skill in play was settled by \"${move.by}\"${\n move.to !== undefined ? ` → ${q(move.to)}` : ''\n }.`;\n }\n}\n","/**\n * `selectSkillBeats` — the routing record as a TIME axis, and the resolver that\n * puts the Lens's ONE cursor on it.\n *\n * Pattern: pure functions over `selectSkillRoute`'s output — no framework, no\n * state, no caching. `selectSkillRoute` answers \"what happened\";\n * this answers \"what is true AT this point\", which is the only extra\n * question a scrubbing view asks.\n * Role: the headless half of the SkillGraph debugger. A beat is one hop\n * plus the facts that ACCUMULATE (where the cursor stands, what has\n * been visited) and the SENTENCE the library already writes for it.\n *\n * THE ONE-CURSOR LAW (this package's founding rule, `lens_v0_1_one_cursor`):\n * a beat is NOT a second cursor. It carries the hop's `runtimeStageId` — the\n * lens's own address — and {@link selectSkillBeatAt} RESOLVES the one cursor\n * onto the beat list. Nothing here stores a position; a view that renders\n * beats must derive the active one from the cursor on every render, and move\n * the cursor (never a local index) when the reader clicks a beat.\n *\n * WHERE THE SENTENCES COME FROM (and why none are written here):\n * `headline` and `notes` are `humanizeCursorMove` / `humanizeSkillRejected` /\n * `humanizeRouteConflict` / `humanizeTurnRouted` — the SAME builders\n * `defaultHumanizer` composes for the Commentary panel. A product-facing rail\n * shows the library's sentences revealed in cursor order; it does not get its\n * own prose, because two prose systems drift and only one of them is tested.\n * The two exceptions are spelled out at {@link fallbackHeadline} — both are\n * formatters over the hop's own fields, for the two cases the humanizer\n * answers `null` to.\n *\n * WHAT IS DELIBERATELY NOT DERIVED — the reachable set:\n * The gate rewrites `read_skill`'s description every iteration with the\n * reachable set, so the words are on the record — as PROSE. Parsing them back\n * into ids would re-create the exact mistake `selectSkillRoute` exists to\n * remove (a view re-parsing sentences), and would break on the first era that\n * rewords the menu. So `reachable` is filled only from a TYPED list, and it\n * names which one it used ({@link SkillReachableSet.source}), in this\n * priority order:\n *\n * `'cursor-move'` `cursorMove.reachable` (9.50.0) — the gate's own\n * admissible set from the landed cursor, on EVERY move.\n * Exact, and `[]` is kept: an empty set is a dead end,\n * a fact, never rendered as absence.\n * `'refusal'` `skill.rejected.allowed` — the gate's own reachability\n * set. Exact, but present only on iterations where the\n * model was refused.\n * `'declared-edges'` the edges `context.evaluated`'s `routing[]` named. A\n * LOWER BOUND: `routing[]` names an edge only once it\n * fires, so a graph's unfired edges are missing.\n *\n * The last two are fallbacks for recordings older than `cursorMove.reachable`;\n * `undefined` (no typed list at all) is the honest fourth answer — absent,\n * never empty.\n */\n\nimport {\n humanizeCursorMove,\n humanizeRouteConflict,\n humanizeSkillRejected,\n humanizeTurnRouted,\n} from '../humanizeRouting.js';\nimport type {\n SkillCursorCause,\n SkillHop,\n SkillRoute,\n SkillTurnStart,\n} from './selectSkillRoute.js';\n\n// ─── Shapes ──────────────────────────────────────────────────────────────\n\n/** A reachable set, and the typed field it was read from — never prose. */\nexport interface SkillReachableSet {\n readonly ids: readonly string[];\n /** Which typed field supplied it, in the priority order the header states:\n * `'cursor-move'` is exact and present on every 9.50.0+ move (`ids: []` is\n * a dead end — a fact); the other two are older-era fallbacks. */\n readonly source: 'cursor-move' | 'refusal' | 'declared-edges';\n}\n\n/**\n * One stop on the routing axis: an iteration's hop, plus what is TRUE there.\n *\n * Everything accumulating (`cursorSkillId`, `visited`) is computed once, in\n * run order, so no view re-folds the run to answer \"where am I\".\n */\nexport interface SkillBeat {\n /** Position in the beat list. A LIST INDEX, not a cursor — see the header. */\n readonly index: number;\n readonly turnIndex: number;\n readonly iteration: number;\n /** The evaluate stage this iteration resolved at — the lens's ONE cursor\n * address for this beat. Absent when the recording carried no stage id. */\n readonly runtimeStageId?: string;\n /** The fold row this beat projects, verbatim — every fact, un-summarized. */\n readonly hop: SkillHop;\n /** Where the cursor stands AFTER this beat. Carried forward across\n * iterations that recorded no `cursorMove`, so a beat always knows the\n * skill in play; `undefined` only before the first one. */\n readonly cursorSkillId?: string;\n readonly cause?: SkillCursorCause;\n readonly moved: boolean;\n /** The skill the MODEL picked on this beat (`by: 'model-pick'`). */\n readonly modelPickedId?: string;\n /** Skills the gate refused on this beat, in the order they were asked for. */\n readonly refusedIds: readonly string[];\n /** Every skill the cursor has stood in up to AND INCLUDING this beat. */\n readonly visited: readonly string[];\n /** A typed reachable set, when one is on the record. See the header. */\n readonly reachable?: SkillReachableSet;\n /** The stop's name, as a strip spells it (\"Iteration 3\"). */\n readonly label: string;\n /** The library's sentence for this hop — `humanizeCursorMove`. */\n readonly headline: string;\n /** The library's sentences for what ELSE the beat carried: the turn-start\n * verdict, refusals, route conflicts, superseded picks. In that order. */\n readonly notes: readonly string[];\n}\n\nexport interface SelectSkillBeatsArgs {\n readonly route: SkillRoute;\n}\n\n// ─── The projection ──────────────────────────────────────────────────────\n\n/**\n * The two cases `humanizeCursorMove` answers `null` to, and what a routing\n * view says instead. Both are formatters over the hop's OWN fields — never a\n * story, and never a sentence a different cause could have produced.\n *\n * NO CAUSE the iteration carried no `cursorMove` at all. The beat still\n * exists (refusals and menus are stamped on such iterations),\n * so it states the ABSENCE — a fact about the record.\n * UNDECORATED a `'model-pick'` without the 9.17.0 menu decorations. The\n * MODEL PICK humanizer stays deliberately silent there because an\n * undecorated pick is low signal in a COMMENTARY stream; in a\n * routing view the pick is the whole point, so the movement\n * clause is rendered — the same clause, minus the menu it does\n * not have.\n */\nfunction fallbackHeadline(hop: SkillHop): string {\n if (hop.by === undefined) {\n return hop.refusals.length > 0\n ? 'This iteration recorded no cursor resolution — only the refusal below.'\n : 'This iteration recorded no cursor resolution.';\n }\n if (hop.by === 'model-pick') {\n if (hop.to === undefined) return 'The model made a pick.';\n const from = hop.from !== undefined && hop.from !== hop.to ? `, moving from \"${hop.from}\"` : '';\n return `The model chose \"${hop.to}\"${from}.`;\n }\n const to = hop.to !== undefined ? ` → \"${hop.to}\"` : '';\n return `The skill in play was settled by \"${hop.by}\"${to}.`;\n}\n\n/** The library's line for a superseded pick. Kept beside the other three\n * humanizers even though `humanizeRouting` ships none: the fields are the\n * whole sentence, so there is nothing to interpret. */\nfunction supersededLine(s: {\n readonly volunteeredId: string;\n readonly wonId?: string;\n readonly fromSkillId?: string;\n}): string {\n const from = s.fromSkillId !== undefined ? ` from \"${s.fromSkillId}\"` : '';\n return s.wonId !== undefined\n ? `The model volunteered \"${s.volunteeredId}\", but the author's edge${from} to \"${s.wonId}\" outranked it.`\n : `The model volunteered \"${s.volunteeredId}\", but an author-declared edge${from} outranked it.`;\n}\n\n/** Turn-start verdicts, indexed by the turn they opened. */\nfunction turnsByIndex(turns: readonly SkillTurnStart[]): Map<number, SkillTurnStart[]> {\n const byTurn = new Map<number, SkillTurnStart[]>();\n for (const t of turns) {\n const rows = byTurn.get(t.turnIndex);\n if (rows === undefined) byTurn.set(t.turnIndex, [t]);\n else rows.push(t);\n }\n return byTurn;\n}\n\n/**\n * Project the routing record onto the time axis: one beat per hop, in run\n * order, each carrying what accumulated up to it.\n *\n * Never throws, and never invents: a route with no hops projects to no beats\n * (which is what a run with no skill graph must look like).\n *\n * @example\n * ```ts\n * const route = selectSkillRoute({ log: recorder.selectEventLog() });\n * const beats = selectSkillBeats({ route });\n * const here = selectSkillBeatAt(beats, cursorRuntimeStageId);\n * here?.headline; // 'Moved from \"support\" to \"billing\" — …'\n * here?.visited; // ['support', 'billing']\n * ```\n */\nexport function selectSkillBeats({ route }: SelectSkillBeatsArgs): readonly SkillBeat[] {\n const byTurn = turnsByIndex(route.turns);\n const seenTurn = new Set<number>();\n const visited: string[] = [];\n const seenSkill = new Set<string>();\n // Declared targets per source skill — the `'declared-edges'` lower bound.\n const declaredFrom = new Map<string, string[]>();\n for (const e of route.declaredEdges) {\n const rows = declaredFrom.get(e.from);\n if (rows === undefined) declaredFrom.set(e.from, [e.to]);\n else if (!rows.includes(e.to)) rows.push(e.to);\n }\n\n let cursor: string | undefined;\n\n return route.hops.map((hop, index) => {\n // `from` first: on a cold start there is none, and on every later hop it\n // is where the cursor already stood — so the visited list stays in the\n // order the run walked it.\n for (const id of [hop.from, hop.to]) {\n if (id === undefined || seenSkill.has(id)) continue;\n seenSkill.add(id);\n visited.push(id);\n }\n if (hop.to !== undefined) cursor = hop.to;\n else if (hop.from !== undefined) cursor = hop.from;\n\n const notes: string[] = [];\n // The turn-start verdict opens the first beat of its turn.\n if (!seenTurn.has(hop.turnIndex)) {\n seenTurn.add(hop.turnIndex);\n for (const t of byTurn.get(hop.turnIndex) ?? []) notes.push(humanizeTurnRouted(t));\n }\n for (const r of hop.refusals) {\n notes.push(\n humanizeSkillRejected({\n requestedId: r.requestedId,\n ...(r.currentSkillId !== undefined ? { currentSkillId: r.currentSkillId } : {}),\n allowed: r.allowed,\n ...(r.posture !== undefined ? { posture: r.posture } : {}),\n }),\n );\n }\n for (const c of hop.conflicts) notes.push(humanizeRouteConflict(c));\n for (const s of hop.superseded) notes.push(supersededLine(s));\n\n // No cause ⇒ no move to narrate. `humanizeCursorMove` is not asked to\n // invent one (an empty `by` would fall through to its unknown-era arm and\n // print a sentence about a cause that does not exist).\n const headline =\n (hop.by === undefined\n ? null\n : humanizeCursorMove({\n ...(hop.from !== undefined ? { from: hop.from } : {}),\n ...(hop.to !== undefined ? { to: hop.to } : {}),\n by: hop.by,\n ...(hop.offered !== undefined ? { offered: hop.offered } : {}),\n ...(hop.declinedOffer !== undefined ? { declinedOffer: hop.declinedOffer } : {}),\n ...(hop.witness !== undefined ? { witness: hop.witness } : {}),\n })) ?? fallbackHeadline(hop);\n\n // Reachability: the move's own typed set first (`cursorMove.reachable`,\n // exact and present on every 9.50.0+ move — `[]` KEPT, a dead end is a\n // fact); else the gate's list when a refusal put one on the record; else\n // the declared edges leaving the skill the cursor stood in BEFORE the\n // hop (which is the position the reachability question was asked from).\n const askedFrom = hop.from ?? hop.to;\n const declared = askedFrom !== undefined ? declaredFrom.get(askedFrom) : undefined;\n const allowed = hop.refusals.find((r) => r.allowed.length > 0)?.allowed;\n const reachable: SkillReachableSet | undefined =\n hop.reachable !== undefined\n ? { ids: hop.reachable, source: 'cursor-move' }\n : allowed !== undefined\n ? { ids: allowed, source: 'refusal' }\n : declared !== undefined && declared.length > 0\n ? { ids: declared, source: 'declared-edges' }\n : undefined;\n\n return {\n index,\n turnIndex: hop.turnIndex,\n iteration: hop.iteration,\n ...(hop.runtimeStageId !== undefined ? { runtimeStageId: hop.runtimeStageId } : {}),\n hop,\n ...(cursor !== undefined ? { cursorSkillId: cursor } : {}),\n ...(hop.by !== undefined ? { cause: hop.by } : {}),\n moved: hop.moved,\n ...(hop.by === 'model-pick' && hop.to !== undefined ? { modelPickedId: hop.to } : {}),\n refusedIds: hop.refusals.map((r) => r.requestedId),\n visited: [...visited],\n ...(reachable !== undefined ? { reachable } : {}),\n label: `Iteration ${hop.iteration}`,\n headline,\n notes,\n } satisfies SkillBeat;\n });\n}\n\n// ─── The cursor resolver ─────────────────────────────────────────────────\n\n/** Parse the `#N` executionIndex suffix off a runtimeStageId. */\nfunction execIndex(runtimeStageId: string): number | undefined {\n const hash = runtimeStageId.lastIndexOf('#');\n if (hash < 0) return undefined;\n const n = Number(runtimeStageId.slice(hash + 1));\n return Number.isInteger(n) && n >= 0 ? n : undefined;\n}\n\n/**\n * Resolve the ONE cursor to the beat in effect there — the same rule\n * `selectToolChoiceCall` resolves a tool-choice call with, for the same\n * reason: the skill view SCRUBS the lens's cursor, it does not keep its own.\n *\n * 1. cursor AT an evaluate stage (exact `runtimeStageId`) → that beat;\n * 2. cursor INSIDE the evaluate stage's subflow (the cursor is the subflow\n * root, a beat ran under it) → the FIRST such beat after the cursor;\n * 3. otherwise → the nearest-PREVIOUS beat (largest executionIndex ≤ the\n * cursor's) — \"the routing state you are standing in\".\n *\n * Bookends: `__root__` at a run-start position is \"nothing has routed yet\"\n * (`undefined`); at a run-end position it is the whole run (the last beat).\n * An empty cursor (a live run before its first commit) also resolves to the\n * last beat, so a live view follows the run.\n *\n * Monotone with the cursor, and never throws.\n */\nexport function selectSkillBeatAt(\n beats: readonly SkillBeat[],\n cursorRuntimeStageId: string,\n cursorKind?: string,\n): SkillBeat | undefined {\n if (beats.length === 0) return undefined;\n const last = beats[beats.length - 1];\n\n if (cursorKind === 'user-in') return undefined;\n if (cursorKind === 'user-out') return last;\n if (!cursorRuntimeStageId) return last; // live edge, no positions yet\n\n const base = cursorRuntimeStageId.split('#')[0] ?? '';\n if (base === '__root__') return cursorKind === 'group-start' ? undefined : last;\n\n const exact = beats.find((b) => b.runtimeStageId === cursorRuntimeStageId);\n if (exact !== undefined) return exact;\n\n const cursorIdx = execIndex(cursorRuntimeStageId);\n if (cursorIdx === undefined) return undefined; // unparsable synthetic id\n\n const prefix = `${base}/`;\n let within: SkillBeat | undefined;\n let withinIdx = Infinity;\n let prev: SkillBeat | undefined;\n let prevIdx = -1;\n for (const b of beats) {\n const id = b.runtimeStageId;\n if (id === undefined) continue;\n const idx = execIndex(id);\n if (idx === undefined) continue;\n if (id.startsWith(prefix) && idx > cursorIdx && idx < withinIdx) {\n within = b;\n withinIdx = idx;\n }\n if (idx <= cursorIdx && idx > prevIdx) {\n prev = b;\n prevIdx = idx;\n }\n }\n return within ?? prev;\n}\n","/**\n * `selectSkillTopology` — the skill graph as it stands AT one beat: which\n * nodes the run touched, which edges are the author's and which the run was\n * observed to take, and what state each node is in right now.\n *\n * Pattern: pure function over `selectSkillRoute`'s record + one {@link SkillBeat}.\n * Space, where `selectSkillBeats` is time. No framework, no state.\n * Role: the headless half of the debugger's topology canvas. The canvas\n * places these nodes and paints these states; it decides nothing.\n *\n * DECLARED vs OBSERVED — the distinction the whole canvas rests on:\n *\n * DECLARED an edge the AUTHOR drew. On the record it comes from one of\n * two places, and they differ in COMPLETENESS:\n * `skill.graph_declared` (9.50.0) is the author's WHOLE map,\n * verbatim from the built graph — complete; `routing[]`\n * provenance names an edge only once it FIRES — a LOWER BOUND.\n * A consumer holding the built graph can still pass it in\n * through {@link SelectSkillTopologyArgs.declaredEdges}; without\n * either complete source, the view must say the topology is\n * partial rather than imply the author drew only what fired.\n * {@link SkillTopology.declaredSource} (and its one-boolean\n * digest {@link SkillTopology.declaredComplete}) is that flag,\n * so no caller has to remember the rule.\n * OBSERVED a hop the CURSOR was seen to take, with the cause that moved it.\n * An observed edge with no declared twin is the interesting case:\n * the model routed somewhere the recording never saw declared.\n *\n * They are one edge row with two booleans rather than two lists, because a\n * view that draws them as two lists draws the common case — an author's edge\n * that fired — twice.\n */\n\nimport type { SkillBeat } from './selectSkillBeats.js';\nimport type { SkillCursorCause, SkillHopRef, SkillRoute } from './selectSkillRoute.js';\n\n/**\n * What a node is, at the beat being shown. One value, in precedence order:\n *\n * `'current'` the cursor stands here.\n * `'refused'` the model asked to go here on this beat and the gate said no.\n * `'reachable'` a TYPED reachable set named it (see `SkillReachableSet`).\n * `'visited'` the cursor stood here earlier in the run.\n * `'idle'` the catalog listed it; the run has not been there.\n *\n * `'model-picked'` is deliberately NOT in this list: a picked node is also the\n * current one, and a state machine that has to choose between the two loses a\n * fact. It rides {@link SkillTopologyNode.pickedByModel} instead.\n */\nexport type SkillNodeState = 'current' | 'refused' | 'reachable' | 'visited' | 'idle';\n\nexport interface SkillTopologyNode {\n readonly id: string;\n /** The catalog description, verbatim — the text the model read. */\n readonly description?: string;\n /** The drawn kind (`'skill'` / `'predicate'`), when the declared map named it. */\n readonly kind?: string;\n /** The drawn caption, when the declared map named one. */\n readonly label?: string;\n /** A synthetic-START edge points here — a turn CAN begin at this skill, as\n * the author drew it. Only ever `true` when the recording carried the\n * declared map; never guessed from an observed cold start. */\n readonly isEntry: boolean;\n readonly state: SkillNodeState;\n /** The cursor moved here on this beat because the MODEL picked it. */\n readonly pickedByModel: boolean;\n /** The gate refused a move here on this beat. */\n readonly refusedHere: boolean;\n /** The cursor stood here at some point in the WHOLE run (not just so far) —\n * `route.nodes[].visited`, kept so a view can dim never-entered skills. */\n readonly visitedInRun: boolean;\n}\n\nexport interface SkillTopologyEdge {\n /** `from->to` — stable across beats, so a canvas can key on it. */\n readonly id: string;\n readonly from: string;\n readonly to: string;\n /** The author drew this edge (as far as the record shows — see the header). */\n readonly declared: boolean;\n /** The cursor was observed to take it. */\n readonly observed: boolean;\n readonly label?: string;\n readonly triggerKind?: string;\n /** What moved the cursor along it, when it was observed. */\n readonly by?: SkillCursorCause;\n /** Every (turn, iteration) it was taken on. Empty for a never-taken edge. */\n readonly takenAt: readonly SkillHopRef[];\n /** THIS beat is the hop along this edge. */\n readonly active: boolean;\n}\n\nexport interface SkillTopology {\n readonly nodes: readonly SkillTopologyNode[];\n readonly edges: readonly SkillTopologyEdge[];\n /**\n * Where the declared edges came from — and, with it, whether they are the\n * WHOLE map or only what fired:\n * `'recording-declared'` the run's own `skill.graph_declared` event\n * (9.50.0) — the author's complete declaration,\n * carried by the recording itself.\n * `'recording'` the log's `routing[]` only — a LOWER BOUND (an\n * edge is named once it fires), and a view must\n * say so.\n * `'graph'` the caller passed the built graph's edges —\n * complete.\n * `'none'` the recording named no declared edge and none\n * was passed.\n */\n readonly declaredSource: 'recording-declared' | 'recording' | 'graph' | 'none';\n /**\n * The one-boolean digest of `declaredSource` a caveat line branches on:\n * `true` for `'recording-declared'` and `'graph'` (the whole map is drawn —\n * no \"the author may have drawn more\" warning), `false` for `'recording'`\n * and `'none'` (a lower bound — the warning stays).\n */\n readonly declaredComplete: boolean;\n /** Entry skills — the declared map's synthetic-START targets. Empty when\n * the recording carried no declared map. */\n readonly entryIds: readonly string[];\n}\n\n/** A declared edge as a caller supplies it (`graph.edges`, `from !== null`). */\nexport interface DeclaredEdgeInput {\n readonly from: string;\n readonly to: string;\n readonly label?: string;\n readonly triggerKind?: string;\n}\n\nexport interface SelectSkillTopologyArgs {\n readonly route: SkillRoute;\n /** The beat being shown. `undefined` = before the run's first routing stop:\n * every node is `'idle'` and no edge is active, which is the truth then. */\n readonly beat?: SkillBeat;\n /**\n * The author's edges, from the BUILT graph — what a recording cannot carry.\n * A consumer holding `skillGraph().build()` passes\n * `graph.edges.filter((e) => e.from !== null)`.\n */\n readonly declaredEdges?: readonly DeclaredEdgeInput[];\n}\n\n/**\n * Fold the record + one beat into the drawable graph.\n *\n * @example\n * ```ts\n * const topo = selectSkillTopology({ route, beat });\n * topo.nodes.find((n) => n.state === 'current')?.id; // 'billing'\n * topo.edges.filter((e) => e.observed && !e.declared); // the model's own hops\n * topo.declaredSource; // 'recording' → partial\n * ```\n */\nexport function selectSkillTopology({\n route,\n beat,\n declaredEdges,\n}: SelectSkillTopologyArgs): SkillTopology {\n const visitedSoFar = new Set(beat?.visited ?? []);\n const reachable = new Set(beat?.reachable?.ids ?? []);\n const refused = new Set(beat?.refusedIds ?? []);\n const current = beat?.cursorSkillId;\n\n const edges = new Map<string, {\n from: string;\n to: string;\n declared: boolean;\n observed: boolean;\n label?: string;\n triggerKind?: string;\n by?: SkillCursorCause;\n takenAt: readonly SkillHopRef[];\n }>();\n\n const put = (from: string, to: string): NonNullable<ReturnType<typeof edges.get>> => {\n const id = `${from}->${to}`;\n let row = edges.get(id);\n if (row === undefined) {\n row = { from, to, declared: false, observed: false, takenAt: [] };\n edges.set(id, row);\n }\n return row;\n };\n\n // Declared first, so an author's caption survives the observed merge.\n const declaredRows = declaredEdges ?? route.declaredEdges;\n for (const e of declaredRows) {\n const row = put(e.from, e.to);\n row.declared = true;\n if (e.label !== undefined) row.label = e.label;\n if (e.triggerKind !== undefined) row.triggerKind = e.triggerKind;\n }\n // Anything the RECORDING declared is true regardless of what was passed in:\n // a caller's list can be stale, but an edge that fired demonstrably exists.\n if (declaredEdges !== undefined) {\n for (const e of route.declaredEdges) {\n const row = put(e.from, e.to);\n row.declared = true;\n if (row.label === undefined && e.label !== undefined) row.label = e.label;\n if (row.triggerKind === undefined && e.triggerKind !== undefined) {\n row.triggerKind = e.triggerKind;\n }\n }\n }\n\n for (const e of route.observedEdges) {\n // A cold start has no `from` — it is an entry, not an edge between nodes.\n if (e.from === undefined) continue;\n const row = put(e.from, e.to);\n row.observed = true;\n row.by = e.by;\n row.takenAt = e.takenAt;\n if (row.label === undefined && e.label !== undefined) row.label = e.label;\n if (row.triggerKind === undefined && e.triggerKind !== undefined) {\n row.triggerKind = e.triggerKind;\n }\n }\n\n const activeId =\n beat !== undefined && beat.moved && beat.hop.from !== undefined && beat.hop.to !== undefined\n ? `${beat.hop.from}->${beat.hop.to}`\n : undefined;\n\n // Every endpoint is a node, even one the catalog never listed — the run\n // walked it, so hiding it would hide a hop.\n const entryIds = route.entryIds ?? [];\n const entrySet = new Set(entryIds);\n const nodes = new Map<string, SkillTopologyNode>();\n const state = (id: string): SkillNodeState => {\n if (id === current) return 'current';\n if (refused.has(id)) return 'refused';\n if (reachable.has(id)) return 'reachable';\n if (visitedSoFar.has(id)) return 'visited';\n return 'idle';\n };\n const addNode = (\n id: string,\n detail?: { description?: string; kind?: string; label?: string },\n visitedInRun?: boolean,\n ): void => {\n const existing = nodes.get(id);\n if (existing !== undefined) {\n if (existing.description === undefined && detail?.description !== undefined) {\n nodes.set(id, { ...existing, description: detail.description });\n }\n return;\n }\n nodes.set(id, {\n id,\n ...(detail?.description !== undefined ? { description: detail.description } : {}),\n ...(detail?.kind !== undefined ? { kind: detail.kind } : {}),\n ...(detail?.label !== undefined ? { label: detail.label } : {}),\n isEntry: entrySet.has(id),\n state: state(id),\n pickedByModel: beat?.modelPickedId === id,\n refusedHere: refused.has(id),\n visitedInRun: visitedInRun ?? visitedSoFar.has(id),\n });\n };\n\n for (const n of route.nodes) {\n addNode(\n n.id,\n {\n ...(n.description !== undefined ? { description: n.description } : {}),\n ...(n.kind !== undefined ? { kind: n.kind } : {}),\n ...(n.label !== undefined ? { label: n.label } : {}),\n },\n n.visited,\n );\n }\n for (const row of edges.values()) {\n addNode(row.from);\n addNode(row.to);\n }\n for (const id of refused) addNode(id);\n\n const declaredSource: SkillTopology['declaredSource'] =\n route.declaredComplete === true\n ? 'recording-declared'\n : declaredEdges !== undefined && declaredEdges.length > 0\n ? 'graph'\n : route.declaredEdges.length > 0\n ? 'recording'\n : 'none';\n\n return {\n nodes: [...nodes.values()],\n edges: [...edges.entries()].map(([id, row]) => ({\n id,\n from: row.from,\n to: row.to,\n declared: row.declared,\n observed: row.observed,\n ...(row.label !== undefined ? { label: row.label } : {}),\n ...(row.triggerKind !== undefined ? { triggerKind: row.triggerKind } : {}),\n ...(row.by !== undefined ? { by: row.by } : {}),\n takenAt: row.takenAt,\n active: id === activeId,\n })),\n // The run's own declared map outranks a passed-in graph for the LABEL —\n // both are complete, but the recording's copy is from the run that\n // actually executed, so it is the truer provenance to state.\n declaredSource,\n declaredComplete: declaredSource === 'recording-declared' || declaredSource === 'graph',\n entryIds,\n };\n}\n","/**\n * `selectSkillFrameContext` — what reached the model on ONE routing beat.\n *\n * Pattern: pure function over the StepGraph + a {@link SkillBeat}. No\n * framework, no state.\n * Role: the developer lens's right panel asks a question the routing fold\n * alone cannot answer. `SkillHop.skillInjections` carries what the\n * SKILLS put in the prompt; the prompt also carried RAG passages,\n * memory, instructions — everything\n * {@link selectContextEngineeringInjections} calls engineered. Those\n * live on the StepGraph's LLM-call step, not on the routing events.\n *\n * THE PAIRING (why it is index arithmetic and not a join):\n * a beat is stamped at the Evaluate stage that resolved the cursor; the call\n * it prepared is the NEXT LLM step. footprintjs's `executionIndex` is globally\n * monotonic within a run, so \"the first LLM step after this beat's evaluate,\n * and before the next beat's\" identifies it exactly, with no id convention to\n * break. A beat with no LLM step after it (the run ended at the evaluate)\n * pairs with nothing, and says so — `stepRuntimeStageId` absent, `injections`\n * empty, {@link SkillFrameContext.paired} `false`. Absent is not empty here:\n * `paired: false` means \"no call was recorded for this beat\", while `paired:\n * true` with no engineered injections means \"the call carried none\".\n */\n\nimport type { ContextInjection, StepGraph, StepNode } from 'agentfootprint/observe';\nimport { selectContextEngineeringInjections } from './selectContextEngineeringInjections.js';\nimport type { SkillBeat } from './selectSkillBeats.js';\n\n/** What the model was handed on one beat, as the recording carried it. */\nexport interface SkillFrameContext {\n /** An LLM call was found for this beat. `false` ⇒ nothing to report, not\n * \"the call was empty\". */\n readonly paired: boolean;\n /** The paired call's address, so a view can jump the ONE cursor to it. */\n readonly stepRuntimeStageId?: string;\n /** The ENGINEERED injections on that call — baseline user / tool-result /\n * assistant / base prompt / static tool registry filtered out. */\n readonly injections: readonly ContextInjection[];\n /** How many injections the call carried in total, engineered or not. Lets a\n * panel say \"4 of 11 were engineered\" instead of implying 4 was all. */\n readonly totalInjections: number;\n}\n\nexport interface SelectSkillFrameContextArgs {\n /** The recording's step graph (`recorder.getStepGraph()`). */\n readonly graph: Pick<StepGraph, 'nodes'>;\n /** The beat being shown. */\n readonly beat: SkillBeat;\n /** The beat AFTER it, when there is one — the upper bound of the window. */\n readonly nextBeat?: SkillBeat;\n}\n\n/** Parse the `#N` executionIndex suffix off a runtimeStageId. */\nfunction execIndex(runtimeStageId: string | undefined): number | undefined {\n if (runtimeStageId === undefined) return undefined;\n const hash = runtimeStageId.lastIndexOf('#');\n if (hash < 0) return undefined;\n const n = Number(runtimeStageId.slice(hash + 1));\n return Number.isInteger(n) && n >= 0 ? n : undefined;\n}\n\n/** An LLM-facing step — the kinds whose node carries the call's injections. */\nfunction isLLMStep(node: StepNode): boolean {\n return node.kind === 'user->llm' || node.kind === 'tool->llm';\n}\n\n/**\n * Resolve the beat to the call it prepared, and report that call's engineered\n * context. Never throws; a graph with no steps pairs with nothing.\n *\n * @example\n * ```ts\n * const ctx = selectSkillFrameContext({ graph: recorder.getStepGraph(), beat, nextBeat });\n * ctx.paired; // true\n * ctx.injections.map((i) => i.source); // ['skill', 'rag']\n * `${ctx.injections.length} of ${ctx.totalInjections} engineered`;\n * ```\n */\nexport function selectSkillFrameContext({\n graph,\n beat,\n nextBeat,\n}: SelectSkillFrameContextArgs): SkillFrameContext {\n const from = execIndex(beat.runtimeStageId);\n const to = execIndex(nextBeat?.runtimeStageId);\n\n let best: StepNode | undefined;\n let bestIdx = Infinity;\n for (const node of graph.nodes ?? []) {\n if (!isLLMStep(node)) continue;\n const idx = execIndex(node.runtimeStageId);\n if (idx === undefined) continue;\n if (from !== undefined && idx <= from) continue;\n if (to !== undefined && idx >= to) continue;\n if (idx < bestIdx) {\n best = node;\n bestIdx = idx;\n }\n }\n\n if (best === undefined) return { paired: false, injections: [], totalInjections: 0 };\n const all = best.injections ?? [];\n return {\n paired: true,\n ...(best.runtimeStageId !== undefined ? { stepRuntimeStageId: best.runtimeStageId } : {}),\n injections: selectContextEngineeringInjections(all),\n totalInjections: all.length,\n };\n}\n","/**\n * Group — a subflow execution. The conceptual container the Lens chart\n * highlights at any cursor position.\n *\n * Layer 1 / Lens v0.1.\n *\n * Naming convention (locked — see\n * `memory/lens_v0_1_one_cursor_architecture.md`):\n *\n * - `runtimeStageId` = the leaf (one stage execution that wrote one\n * commit). THE cursor of the slider.\n * - `runtimeGroupId` = the group (a subflow execution). Sibling key\n * used for chart-box highlight and breadcrumb.\n *\n * Both are runtimeStageIds in form (string `[subflowPath/]stageId#executionIndex`).\n * `runtimeGroupId` is derived from `runtimeStageId` via the boundary index\n * — same address space, two views of the same subflow stack.\n *\n * A Group is a developer-friendly alias for what footprintjs internally\n * exposes as a `BoundaryRangeLabel`. Same data, easier to talk about.\n *\n * Why the alias\n * ─────────────\n * `BoundaryRangeLabel` is the wire type from agentfootprint. `Group` is\n * the Lens-facing API name developers see — short, domain-neutral.\n * Reducing the surface to the fields Lens actually needs makes\n * testing trivial (we don't need to invent `ts` / `slotKind` etc.\n * in synthetic test inputs).\n */\n\nexport interface Group {\n /** Identity = the subflow root's runtimeStageId. Same format and\n * address space as a commit's runtimeStageId — both are\n * `[subflowPath/]stageId#executionIndex`. */\n readonly runtimeGroupId: string;\n\n /** Display name (the subflow's `subflowName` or the synthetic run-root label). */\n readonly name: string;\n\n /** Parent group's runtimeGroupId, if nested. `undefined` for the\n * top-level Run group (no parent). */\n readonly parentGroupId: string | undefined;\n\n /** Subflow path the engine assigned (e.g. `['__root__', 'sf-Committee', 'sf-ethics']`). */\n readonly subflowPath: readonly string[];\n\n /** 0 = top-level run, 1 = first subflow level, etc. */\n readonly depth: number;\n\n /** Commit index at which this group opened. Use with the `commitLog`\n * array to find the first commit in the group. */\n readonly opensAtCommitIdx: number;\n\n /** Commit index at which this group closed. `undefined` while the\n * group is still in-flight (subflow hasn't exited yet — useful for\n * live-mode rendering). */\n readonly closesAtCommitIdx: number | undefined;\n\n /** True when the group represents the synthetic Run root (`run.entry`),\n * false for real subflow boundaries. Lens uses this to skip the root\n * in breadcrumbs (since the root is implicit). */\n readonly isRoot: boolean;\n\n /** Composition primitive — set when the boundary range came from a\n * `composition.start` event (Parallel / Sequence / Loop / Conditional).\n * Undefined for plain subflows. v0.1 special-cases `'Parallel'` for\n * inline-pills rendering + start/end-as-positions. */\n readonly compositionKind?: 'Parallel' | 'Sequence' | 'Loop' | 'Conditional';\n\n /** Slot kind — set on Agent/LLMCall internal context-engineering\n * subflows (sf-system-prompt / sf-messages / sf-tools). Slider\n * filters these out at top level; they become visible when the\n * containing Agent or LLMCall is drilled into. */\n readonly slotKind?: string;\n\n /** Primitive kind from the subflow root description ('Agent' / 'LLMCall'\n * / 'Sequence' / etc.). Used to identify drillable leaves (Agent,\n * LLMCall) vs. compositions. */\n readonly primitiveKind?: string;\n}\n\n/** Check whether `commitIdx` falls inside this group's open/close range. */\nexport function groupContainsCommit(group: Group, commitIdx: number): boolean {\n if (commitIdx < group.opensAtCommitIdx) return false;\n if (group.closesAtCommitIdx === undefined) return true; // open-ended\n return commitIdx <= group.closesAtCommitIdx;\n}\n","/**\n * activeChartGroup — which chart nodes belong to the group the cursor is in.\n *\n * Pure function. Layer 1 / Tier B / Lens v0.1.\n *\n * ── Why this exists ─────────────────────────────────────────────────────────\n * On the GROUPED ruler (🔍 Why Lens) one slider stop is one boundary, so the\n * cursor's position IS a group — not a stage. The chart has to say that: the\n * group's members light as ONE unit and everything else recedes. To draw that,\n * a renderer needs the members as CHART NODE IDS, and nothing upstream hands it\n * that list.\n *\n * It is derivable from the recording alone — no new fetch, no new recorder:\n *\n * the boundary index → the group's commit range (`buildGroups`)\n * the commit log → the runtimeStageId per commit in that range\n * `chartNodeIdOf` → the chart id for each (strip `#executionIndex`)\n *\n * The last step is the rule every other id-matching site in the ecosystem uses\n * (Lens's `coActiveStageIds`, the be-server's chart-click jump): a chart node id\n * is a runtimeStageId minus its `#executionIndex`. The subflow path stays — it\n * is part of the id.\n *\n * ── What the group's box contains ───────────────────────────────────────────\n * Every commit inside `[opensAtCommitIdx … closesAtCommitIdx]`, PLUS the group's\n * own node (its mount). Nested child groups fall inside that range, so their\n * commits are members too — a place contains its rooms. At a drill level where\n * a subflow renders as ONE card, the mount is the only member on screen, and the\n * boundary is drawn around that card; that is why the mount is included rather\n * than assumed present.\n *\n * A member id that is not on the chart right now (a subflow's internals while\n * the chart shows the collapsed card) simply has no node to light. The renderer\n * skips it; nothing is invented.\n */\n\nimport type { Group } from './Group.js';\nimport { groupContainsCommit } from './Group.js';\n\n/**\n * The chart id for a runtimeStageId: everything before `#executionIndex`.\n *\n * `sf-committee/legal/call-llm#4` → `sf-committee/legal/call-llm`. The subflow\n * path is part of the chart id (that is how `structureGraphFromRunner` names\n * the nodes it materialises for a subflow's internals), so only the execution\n * index is dropped. Last-`#` based, like every runtimeStageId parser in the\n * engine.\n */\nexport function chartNodeIdOf(runtimeStageId: string): string {\n const hash = runtimeStageId.lastIndexOf('#');\n return hash < 0 ? runtimeStageId : runtimeStageId.slice(0, hash);\n}\n\n/** The group at the cursor, resolved to chart ids — what a grouped-ruler chart\n * needs to light one group as a unit. */\nexport interface ChartGroupHighlight {\n /** The group's identity (its subflow root's runtimeStageId). */\n readonly runtimeGroupId: string;\n /** The group's name — `groupDisplayName`, the same spelling the WHAT\n * HAPPENED boundary rail uses. Goes on the boundary's chip. */\n readonly name: string;\n /** Chart node ids belonging to this group. Never empty: the group's own\n * mount is always a member. */\n readonly memberNodeIds: ReadonlySet<string>;\n /** The commit range the membership was read from (inclusive). `closesAt` is\n * undefined while the group is still in flight. */\n readonly opensAtCommitIdx: number;\n readonly closesAtCommitIdx: number | undefined;\n /** Depth in the boundary tree — 0 is the run root. */\n readonly depth: number;\n}\n\n/** One commit as this function reads it — the only field it needs. Structural,\n * so a `CommitBundle`, a `CommitSyncEntry`, or the be-server's `CommitStop`\n * all fit without a cast. */\nexport interface CommitWithStage {\n readonly runtimeStageId?: string | undefined;\n}\n\nexport interface ActiveChartGroupArgs {\n /** Every group in the run (from `buildGroups`). */\n readonly groups: readonly Group[];\n /** The run's commit log, in commit order (index === commitIdx). */\n readonly commits: readonly CommitWithStage[];\n /** THE cursor, as a commit index. */\n readonly commitIdx: number;\n /**\n * Let the synthetic Run root be the active group. Default `false`: a boundary\n * drawn around the WHOLE chart states nothing, so at a commit no subflow\n * encloses, the honest answer is \"no group here\" and the chart renders\n * unchanged.\n */\n readonly includeRoot?: boolean;\n}\n\n/**\n * The innermost group enclosing `commitIdx`, with its members resolved to chart\n * node ids. `undefined` when no group encloses the cursor (or only the run root\n * does and `includeRoot` is off) — the caller renders the chart as it always did.\n */\nexport function activeChartGroup(args: ActiveChartGroupArgs): ChartGroupHighlight | undefined {\n const { groups, commits, commitIdx, includeRoot = false } = args;\n if (!Number.isFinite(commitIdx) || commitIdx < 0) return undefined;\n\n // Innermost enclosing = deepest; ties (a composition and its first member\n // opening on the same commit) break to the one that opened LAST, which is the\n // inner one. Same order `CommitRangeIndex.enclosing()` returns outer→inner.\n let best: Group | undefined;\n for (const group of groups) {\n if (group.isRoot && !includeRoot) continue;\n if (!groupContainsCommit(group, commitIdx)) continue;\n if (best === undefined) {\n best = group;\n continue;\n }\n if (group.depth > best.depth) best = group;\n else if (group.depth === best.depth && group.opensAtCommitIdx >= best.opensAtCommitIdx) best = group;\n }\n if (best === undefined) return undefined;\n\n const memberNodeIds = new Set<string>();\n // The group's own mount is always a member — at a collapsed drill level it is\n // the only node on screen that IS the group.\n memberNodeIds.add(chartNodeIdOf(best.runtimeGroupId));\n\n const from = Math.max(0, best.opensAtCommitIdx);\n const to = Math.min(commits.length - 1, best.closesAtCommitIdx ?? commits.length - 1);\n for (let i = from; i <= to; i++) {\n const rid = commits[i]?.runtimeStageId;\n if (rid === undefined || rid === '') continue;\n memberNodeIds.add(chartNodeIdOf(rid));\n }\n\n return {\n runtimeGroupId: best.runtimeGroupId,\n name: best.name,\n memberNodeIds,\n opensAtCommitIdx: best.opensAtCommitIdx,\n closesAtCommitIdx: best.closesAtCommitIdx,\n depth: best.depth,\n };\n}\n","/**\n * stepBands — the grouped ruler's BANDS, as a pure projection of the step axis.\n *\n * ── The one-cursor law, restated for this file ──────────────────────────────\n * The only cursor in the system is the step on the lens's scrub axis (whose\n * address is a runtimeStageId — see `cursorPositionsAtDrill`). A band is NOT a\n * second axis and NOT a stored position: it is a contiguous RANGE of steps,\n * derived from the positions array alone. Which band is \"active\" is derived\n * from which range contains the cursor; clicking a band moves the ONE cursor\n * to the band's first step. Nothing here holds state, and nothing downstream\n * may store a band index — a stored band index would be a beat counter, the\n * exact thing the locked v0.1 architecture bans.\n *\n * ── What a band IS ──────────────────────────────────────────────────────────\n * The grouped reading of an agent run is its LOOP PASSES. The step axis\n * already knows where they begin: the domain's milestone classifier marks the\n * per-iteration boundary stop as `'iteration'` (`CursorPosition.milestone`),\n * and everything after it — context, LLM turn, routing, tool call — belongs to\n * that pass until the next one begins. So:\n *\n * • a depth-0 `group-start` / `group-end` stop (Run · start / Run · end) is\n * a band of its own — the bookends;\n * • an `'iteration'` milestone OPENS a band, labelled with that stop's own\n * label (\"Iteration 2\" — the spelling the step strip already used);\n * • every other stop extends the band it follows.\n *\n * ── Honesty fallback ────────────────────────────────────────────────────────\n * An axis with NO iteration milestones (a plain Sequence chart, a drilled\n * subflow's internals) has no grouped structure to read — so every step is its\n * own band, and the grouped ruler degenerates to the per-step one. That is a\n * true statement about the chart, not a rendering choice: inventing bands for\n * a run that has none would be fabricating structure.\n *\n * Invariant (pinned in tests): the bands tile the axis exactly — every step is\n * in exactly one band, in order, with no gaps.\n */\n\nimport type { CursorPosition } from './cursorPositionsAtDrill.js';\nimport type { CommitWithStage, ChartGroupHighlight } from './activeChartGroup.js';\nimport { chartNodeIdOf } from './activeChartGroup.js';\n\nexport interface StepBand {\n /** The band's name, as the strip shows it — the opening stop's own label. */\n readonly label: string;\n /** First step (inclusive) of the range this band covers. */\n readonly firstStep: number;\n /** Last step (inclusive). `firstStep === lastStep` for a one-stop band. */\n readonly lastStep: number;\n /** What opened the band — the root bookends against a real group of steps. */\n readonly kind: 'run-start' | 'run-end' | 'group';\n}\n\n/** Is this stop a depth-0 run bookend? */\nfunction isRootBookend(p: CursorPosition): boolean {\n return p.depth === 0 && (p.kind === 'group-start' || p.kind === 'group-end');\n}\n\nexport function stepBands(positions: readonly CursorPosition[]): readonly StepBand[] {\n if (positions.length === 0) return [];\n\n // Honesty fallback: no iteration milestones → no grouped structure → one\n // band per step (the grouped ruler degenerates to the per-step one).\n const hasIterations = positions.some((p) => p.milestone === 'iteration');\n if (!hasIterations) {\n return positions.map((p, i) => ({\n label: p.label,\n firstStep: i,\n lastStep: i,\n kind: bookendKind(p) ?? 'group',\n }));\n }\n\n const bands: StepBand[] = [];\n let open: { label: string; firstStep: number; kind: StepBand['kind'] } | undefined;\n\n const close = (lastStep: number): void => {\n if (open === undefined) return;\n bands.push({ label: open.label, firstStep: open.firstStep, lastStep, kind: open.kind });\n open = undefined;\n };\n\n positions.forEach((p, i) => {\n const bookend = bookendKind(p);\n if (bookend !== undefined) {\n close(i - 1);\n bands.push({ label: p.label, firstStep: i, lastStep: i, kind: bookend });\n return;\n }\n if (p.milestone === 'iteration' || open === undefined) {\n close(i - 1);\n open = { label: p.label, firstStep: i, kind: 'group' };\n return;\n }\n // extends the open band\n });\n close(positions.length - 1);\n\n return bands;\n}\n\nfunction bookendKind(p: CursorPosition): 'run-start' | 'run-end' | undefined {\n if (!isRootBookend(p)) return undefined;\n return p.kind === 'group-start' ? 'run-start' : 'run-end';\n}\n\n/** The band containing `step` — derived, never stored. `-1` when out of range. */\nexport function bandIndexOf(bands: readonly StepBand[], step: number): number {\n return bands.findIndex((b) => step >= b.firstStep && step <= b.lastStep);\n}\n\n/**\n * The band the cursor stands in, resolved to CHART NODE IDS — the grouped\n * ruler's chart highlight, shaped exactly like `activeChartGroup`'s answer so\n * the same boundary renderer draws it.\n *\n * Membership is the band's COMMIT SPAN: from the band's first stop's commit to\n * just before the NEXT band's first commit (the last band runs to the end of\n * the log). That span is what the group of steps actually did — including the\n * commits between the strip's stops — so the whole pass lights as one place.\n *\n * The bookend bands return `undefined`: a boundary drawn around the whole\n * chart states nothing (the same judgement `useChartGroup`'s `includeRoot`\n * default makes).\n */\nexport function bandChartGroup(args: {\n readonly bands: readonly StepBand[];\n readonly bandIndex: number;\n readonly positions: readonly CursorPosition[];\n /** The run's commit log, in commit order (index === commitIdx). */\n readonly commits: readonly CommitWithStage[];\n}): ChartGroupHighlight | undefined {\n const { bands, bandIndex, positions, commits } = args;\n const band = bands[bandIndex];\n if (band === undefined || band.kind !== 'group') return undefined;\n\n const first = positions[band.firstStep];\n if (first === undefined) return undefined;\n const from = Math.max(0, first.commitIdx);\n\n // Up to (not including) the next band's first commit; the last band runs out\n // the log. A next band that is a bookend anchors the end the same way.\n const next = bands[bandIndex + 1];\n const nextFirst = next !== undefined ? positions[next.firstStep] : undefined;\n const to = Math.min(\n commits.length - 1,\n nextFirst !== undefined ? nextFirst.commitIdx - 1 : commits.length - 1,\n );\n\n const memberNodeIds = new Set<string>();\n for (let i = from; i <= to; i++) {\n const rid = commits[i]?.runtimeStageId;\n if (rid === undefined || rid === '') continue;\n memberNodeIds.add(chartNodeIdOf(rid));\n }\n if (memberNodeIds.size === 0) return undefined;\n\n return {\n runtimeGroupId: first.runtimeGroupId,\n name: band.label,\n memberNodeIds,\n opensAtCommitIdx: from,\n closesAtCommitIdx: to,\n depth: 1,\n };\n}\n","/**\n * snapSteps — the stops a transport's ◀ ▶ is allowed to LAND on, as pure\n * queries over the ONE step axis.\n *\n * ── The problem, from a real consumer ───────────────────────────────────────\n * A hosted view shares the host's axis. That is the one-cursor law working\n * correctly — but the host's axis is the run's, not the view's. A four-tool\n * turn puts 73 stops on it, and a view that only changes at eight of them\n * spends 65 presses of ◀ ▶ showing the reader the same picture twice. The\n * scrubbing is not wrong; it is DEAD AIR.\n *\n * The fix that must NOT be taken is a second slider with its own numbers: two\n * transports over one run is the drift class this package keeps refusing\n * (`lens_v0_1_one_cursor_architecture`). So the axis stays the host's, the\n * numbers stay the host's, and the only thing that changes is WHICH of those\n * numbers the step buttons stop at.\n *\n * ── What a snap list IS ─────────────────────────────────────────────────────\n * An ascending list of positions on the axis the transport already receives.\n * It is not an axis, not a projection, and carries no addresses: every entry\n * is a step the host could already have moved to. Nothing here is stored, and\n * nothing downstream may store an index into it — a stored snap index would be\n * a second cursor, the exact thing the locked v0.1 architecture bans.\n *\n * ── The two laws, and why they are STRICT ───────────────────────────────────\n * `nextSnapStep` takes the least stop strictly GREATER than the cursor;\n * `prevSnapStep` the greatest stop strictly LESS. Strictness is what makes a\n * cursor parked BETWEEN two stops behave: ◀ from step 5 with stops\n * `[0, 4, 9]` lands on 4 — back onto the stop you are standing past — while ▶\n * lands on 9. A non-strict `prev` would skip 4 and jump to 0, stranding the\n * reader before the stop the readout just told them they were near.\n *\n * Both are min/max scans, so they answer correctly whatever order the list\n * arrives in; `snapPositionOf`'s INDEX is the one answer that assumes the\n * documented ascending order (it is a display position — \"stop 2 of 5\").\n *\n * Never throw, never invent, and never move a cursor on their own: they answer\n * `undefined` for \"there is no such stop\" and let the caller decide that a\n * button is disabled.\n */\n\n/**\n * Where a step stands relative to a list of snap stops.\n *\n * The two fields answer two different questions, and a readout that collapses\n * them lies: `index` is which stop the cursor is AT OR PAST, `exact` is\n * whether it is actually ON it.\n */\nexport interface SnapPosition {\n /**\n * Index in the snap list of the stop AT-OR-BEFORE the step — `-1` when the\n * step falls before the first stop (or the list is empty).\n */\n readonly index: number;\n /**\n * `true` when the step IS that stop; `false` when it stands BETWEEN that\n * stop and the next (or past the last one). A cursor between stops is a real\n * position, not a stop, and a readout must say so rather than rounding it\n * down silently.\n */\n readonly exact: boolean;\n}\n\n/**\n * Resolve a step onto a snap list: the stop at-or-before it, and whether the\n * step is that stop or merely past it.\n *\n * @param snapSteps the permitted stops, ascending.\n * @param step the cursor's position on the same axis.\n *\n * @example\n * ```ts\n * snapPositionOf([0, 4, 9], 4); // → { index: 1, exact: true } — on stop 2\n * snapPositionOf([0, 4, 9], 5); // → { index: 1, exact: false } — between 2 and 3\n * snapPositionOf([4, 9], 0); // → { index: -1, exact: false } — before stop 1\n * ```\n */\nexport function snapPositionOf(snapSteps: readonly number[], step: number): SnapPosition {\n let index = -1;\n let best = -Infinity;\n for (let i = 0; i < snapSteps.length; i += 1) {\n const s = snapSteps[i];\n if (s === undefined || s > step) continue;\n if (s >= best) {\n best = s;\n index = i;\n }\n }\n return { index, exact: index >= 0 && best === step };\n}\n\n/**\n * The next stop after `step` — the least entry strictly greater than it.\n *\n * `undefined` means \"no stop ahead\", which is what disables a ▶ button. There\n * is deliberately no wrap: a transport that wrapped would make the end of a\n * run indistinguishable from its start.\n *\n * @example\n * ```ts\n * nextSnapStep([0, 4, 9], 5); // → 9\n * nextSnapStep([0, 4, 9], 9); // → undefined\n * ```\n */\nexport function nextSnapStep(snapSteps: readonly number[], step: number): number | undefined {\n let best: number | undefined;\n for (const s of snapSteps) {\n if (s > step && (best === undefined || s < best)) best = s;\n }\n return best;\n}\n\n/**\n * The previous stop before `step` — the greatest entry strictly less than it.\n *\n * Strictly less is what makes a between-position walk back onto the stop it is\n * standing past: from 5 with stops `[0, 4, 9]` this answers 4, not 0.\n *\n * @example\n * ```ts\n * prevSnapStep([0, 4, 9], 5); // → 4 — the stop you are standing past\n * prevSnapStep([0, 4, 9], 4); // → 0 — already on a stop, so the one before it\n * prevSnapStep([0, 4, 9], 0); // → undefined\n * ```\n */\nexport function prevSnapStep(snapSteps: readonly number[], step: number): number | undefined {\n let best: number | undefined;\n for (const s of snapSteps) {\n if (s < step && (best === undefined || s > best)) best = s;\n }\n return best;\n}\n","/**\n * `resolveNavigation` — \"take me to this stage\", answered honestly.\n *\n * A host that wants to POINT at evidence (a chat answer citing the step it\n * means, a dashboard row, a deep link) knows one thing: a `runtimeStageId`.\n * The lens's cursor, though, is owned in STEPS on whichever axis the active\n * surface scrubs — and that axis may stop somewhere coarser than the address\n * (a whole iteration rather than the stage inside it), or may not reach it at\n * all. This is the one rule that turns the address into an answer.\n *\n * THE LADDER (the same rungs `stepForRuntimeStageId` has always climbed —\n * this function IS that ladder, with each rung NAMED instead of flattened\n * into a number):\n *\n * 1. **exact** — a stop whose `runtimeStageId` is the one asked for. Several\n * stops can share an id (a group's start and end); the FIRST is returned,\n * because a mover means \"take me there\", not \"take me to the end of\n * there\". → `{ ok: true, match: 'exact' }`\n * 2. **enclosing** — the stop whose subflow CONTAINS the address. An address\n * inside a subflow (`sf-x/inner#7`) is held by that subflow's own stop\n * (`sf-x#5`). Landing there is a true landing: the cursor really is\n * standing on the evidence, at the granularity this axis has. The stop's\n * own id comes back in `runtimeStageId`, so the caller always knows where\n * it actually went. → `{ ok: true, match: 'enclosing' }`\n * 3. **nearest-previous — OFFERED, NEVER TAKEN.** When no stop holds the\n * address, the stop with the largest executionIndex ≤ the address's is\n * handed back as DATA on a MISS (`{ ok: false, nearest }`). Standing just\n * before something is a true answer, but it is not the answer that was\n * asked for — so the caller decides whether to take it. A jump that\n * silently lands somewhere else is the failure this shape exists to\n * prevent.\n * 4. **nothing** — no stop is at or before the address (it pre-dates the\n * first stop, the axis is empty, or the string is not an address at all).\n * → `{ ok: false }` with no `nearest`. Do not move.\n *\n * Pure, never throws, and never guesses FORWARD: a cursor that lands past its\n * target has answered a question nobody asked.\n *\n * @example Headless — a server-rendered link that needs a step, no React.\n * ```ts\n * import { scrubAxisFor, resolveNavigation } from 'agentfootprint-lens/core';\n *\n * const positions = scrubAxisFor(recorder, 'step');\n * const to = resolveNavigation(positions, 'llm#7');\n * const href = to.ok ? `/run/${runId}?step=${to.step}` : undefined;\n * ```\n *\n * @example The miss, offered rather than taken.\n * ```ts\n * const to = resolveNavigation(positions, 'sf-tools/search#42');\n * if (!to.ok && to.nearest) {\n * // Render it as a choice, not a jump:\n * // \"That step isn't on this ruler. Go to the one before it (Iteration 3)?\"\n * }\n * ```\n */\n\nimport type { CursorPosition } from './cursorPositionsAtDrill.js';\n\n/** How the address was matched to the stop the cursor lands on. */\nexport type NavigationMatch =\n /** The stop IS the address. */\n | 'exact'\n /** The stop's subflow CONTAINS the address — a true landing on a coarser\n * axis. Read `runtimeStageId` for where the cursor actually stands. */\n | 'enclosing';\n\n/** Why an address could not be moved to. Branch on this, not on `message`. */\nexport type NavigationMiss =\n /** No address was given (empty string). */\n | 'no-id'\n /** The axis has no stops yet — nothing to move to. */\n | 'empty-axis'\n /** The address is real but no stop holds it. `nearest` may offer the stop\n * standing just before it. */\n | 'not-on-axis'\n /** Every stop on the axis comes AFTER the address. There is nothing at or\n * before it, so there is no honest place to stand. */\n | 'before-first-stop';\n\n/** The cursor moved (or, headlessly, can move) — this is where it lands. */\nexport interface NavigationHit {\n readonly ok: true;\n /** The step on the axis that was passed in. */\n readonly step: number;\n /** The address of the stop landed on — the one asked for when `match` is\n * `'exact'`, the ENCLOSING stop's own address when it is `'enclosing'`. */\n readonly runtimeStageId: string;\n /** Which rung of the ladder answered. */\n readonly match: NavigationMatch;\n /** The stop's human label, as the step strip and timeline spell it. */\n readonly label: string;\n}\n\n/** The cursor did NOT move, and here is what is true instead. */\nexport interface NavigationRefusal {\n readonly ok: false;\n /** The machine-checkable reason. */\n readonly reason: NavigationMiss;\n /** The same reason as one plain sentence, safe to show a person. */\n readonly message: string;\n /**\n * The nearest stop BEFORE the address, when there is one — an OFFER, not a\n * move. Take it by navigating to `nearest.runtimeStageId` (an exact hit, by\n * construction), or ignore it.\n */\n readonly nearest?: {\n readonly runtimeStageId: string;\n readonly step: number;\n readonly label: string;\n };\n}\n\nexport type NavigationResult = NavigationHit | NavigationRefusal;\n\n/** Parse the `#N` executionIndex suffix off a runtimeStageId. */\nfunction execIndex(runtimeStageId: string): number | undefined {\n const hash = runtimeStageId.lastIndexOf('#');\n if (hash < 0) return undefined;\n const n = Number(runtimeStageId.slice(hash + 1));\n return Number.isInteger(n) && n >= 0 ? n : undefined;\n}\n\n/**\n * Resolve an address to a step on the given axis.\n *\n * @param positions the scrub axis to resolve against — the ACTIVE one. From\n * `scrubAxisFor(recorder, granularity)` outside React, or\n * `useCursorPositions(recorder, drillPath)` inside it.\n * @param runtimeStageId the address to move to.\n */\nexport function resolveNavigation(\n positions: readonly CursorPosition[],\n runtimeStageId: string,\n): NavigationResult {\n if (typeof runtimeStageId !== 'string' || runtimeStageId === '') {\n return {\n ok: false,\n reason: 'no-id',\n message:\n 'No address was given. navigateTo needs a runtimeStageId — footprintjs\\'s ' +\n 'own address for a stage, like \"llm#3\" or \"sf-tools/search#7\".',\n };\n }\n if (positions.length === 0) {\n return {\n ok: false,\n reason: 'empty-axis',\n message:\n 'This ruler has no stops yet, so there is nowhere to move. A run that has ' +\n 'not committed a stage has no positions to scrub.',\n };\n }\n\n // Rung 1 — exact.\n const exact = positions.findIndex((p) => p.runtimeStageId === runtimeStageId);\n if (exact >= 0) {\n return {\n ok: true,\n step: exact,\n runtimeStageId,\n match: 'exact',\n label: positions[exact]?.label ?? '',\n };\n }\n\n const target = execIndex(runtimeStageId);\n if (target === undefined) {\n return {\n ok: false,\n reason: 'not-on-axis',\n message:\n `\"${runtimeStageId}\" is not a stop on this ruler, and it carries no ` +\n '#executionIndex, so no nearby stop can be worked out from it either. ' +\n 'A runtimeStageId looks like \"llm#3\".',\n };\n }\n\n // Rung 2 — enclosing. The address's scopes, innermost first: `a/b/c#7` is\n // held by `a/b`, then by `a`.\n const scopes: string[] = [];\n const base = runtimeStageId.split('#')[0] ?? '';\n for (let cut = base.lastIndexOf('/'); cut > 0; cut = base.lastIndexOf('/', cut - 1)) {\n scopes.push(base.slice(0, cut));\n }\n for (const scope of scopes) {\n let best = -1;\n let bestIdx = -1;\n for (let i = 0; i < positions.length; i += 1) {\n const id = positions[i]?.runtimeStageId ?? '';\n if ((id.split('#')[0] ?? '') !== scope) continue;\n const idx = execIndex(id);\n if (idx === undefined || idx > target || idx <= bestIdx) continue;\n best = i;\n bestIdx = idx;\n }\n if (best >= 0) {\n return {\n ok: true,\n step: best,\n runtimeStageId: positions[best]?.runtimeStageId ?? '',\n match: 'enclosing',\n label: positions[best]?.label ?? '',\n };\n }\n }\n\n // Rung 3 — nearest-previous. Computed, then OFFERED on a miss.\n let prev = -1;\n let prevIdx = -1;\n for (let i = 0; i < positions.length; i += 1) {\n const idx = execIndex(positions[i]?.runtimeStageId ?? '');\n // `<=` not `<`: two stops can share an executionIndex (a group's start and\n // its end), and the FIRST of them is the one a mover means.\n if (idx === undefined || idx > target || idx <= prevIdx) continue;\n prev = i;\n prevIdx = idx;\n }\n if (prev >= 0) {\n const at = positions[prev]!;\n return {\n ok: false,\n reason: 'not-on-axis',\n message:\n `\"${runtimeStageId}\" is not a stop on this ruler. The nearest stop before ` +\n `it is \"${at.runtimeStageId}\" (step ${prev}${at.label ? `, ${at.label}` : ''}) — ` +\n 'offered, not taken: moving there is your call.',\n nearest: { runtimeStageId: at.runtimeStageId, step: prev, label: at.label },\n };\n }\n\n // Rung 4 — nothing at or before it.\n return {\n ok: false,\n reason: 'before-first-stop',\n message:\n `\"${runtimeStageId}\" comes before this ruler's first stop, so there is nothing ` +\n 'at or before it to stand on. The cursor did not move.',\n };\n}\n","/**\n * `stepForRuntimeStageId` — the inverse of the cursor's `describe`: an address\n * back to the position that HOLDS it.\n *\n * The lens's cursor is owned in STEPS (`<Lens step onStepChange>`) and reported\n * in three units, `runtimeStageId` among them. A view that wants to MOVE the\n * cursor, though, only ever knows an address — a slice frame's stage, a\n * routing beat's evaluate stage, a chart node — and the step axis at the\n * current drill level may not contain it. Every such view was re-deriving the\n * same rule; this is it, once.\n *\n * THE RULE (positions are a COARSER axis than addresses, never a wrong one):\n *\n * 1. exact — a position whose `runtimeStageId` is the one asked for. Several\n * positions can share an id (a group's start and end); the FIRST is\n * returned, because a mover means \"take me there\", not \"take me to the\n * end of there\".\n * 2. enclosing — the position whose subflow CONTAINS the address. An\n * address inside a subflow (`sf-x/inner#7`) is held by that subflow's own\n * position (`sf-x#5`): the nearest one whose executionIndex is ≤ the\n * address's. This is what makes \"jump to iteration 3's evaluate stage\"\n * work on an axis whose stops are whole iterations.\n * 3. nearest-previous — the position with the largest executionIndex ≤ the\n * address's. Standing just before something is a true answer; standing\n * after it is not.\n * 4. `-1` — no position is at or before the address (it is before the run's\n * first stop, or the axis is empty). A caller must not move.\n *\n * Deliberately never throws and never guesses forward: a jump that silently\n * lands past its target is worse than one that does not happen.\n *\n * ONE LADDER, TWO READINGS (0.42.0): the rungs live in `resolveNavigation`,\n * which NAMES each one and hands rung 3 back as an OFFER on a miss rather than\n * taking it. This function is the terse reading of the same climb — it takes\n * the offer and answers with a bare step, which is what its callers (chart\n * clicks, beat jumps, provenance frames) have always wanted. A host that must\n * distinguish \"landed on it\" from \"landed just before it\" — a chat pointing at\n * evidence — calls `resolveNavigation` instead. There is no second rule here:\n * change the ladder in one place and both readings follow.\n */\n\nimport type { CursorPosition } from './cursorPositionsAtDrill.js';\nimport { resolveNavigation } from './resolveNavigation.js';\n\n/**\n * Resolve an address to a step on the given position list.\n *\n * @param positions the cursor axis at the CURRENT drill level\n * (`useCursorPositions(recorder, drillPath)`).\n * @param runtimeStageId the address to move to.\n * @returns the step index, or `-1` when no position holds it.\n *\n * @example\n * ```ts\n * // The axis stops at whole iterations; the beat is a stage inside one.\n * stepForRuntimeStageId(positions, 'sf-injection-engine/evaluate#3'); // → the\n * // step of `sf-injection-engine#1`, the iteration that contains it.\n * ```\n */\nexport function stepForRuntimeStageId(\n positions: readonly CursorPosition[],\n runtimeStageId: string,\n): number {\n const to = resolveNavigation(positions, runtimeStageId);\n // Rungs 1–2 landed; else take rung 3's offer, else `-1`. Taking the offer\n // silently is exactly what this narrower reading promises to do.\n return to.ok ? to.step : (to.nearest?.step ?? -1);\n}\n"],"mappings":"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","names":["where","line","execIndex","execIndex"]}
@@ -9,19 +9,19 @@ import {
9
9
  selectHops,
10
10
  selectToolChoiceCall,
11
11
  structureGraphFromRunner
12
- } from "./chunk-VBMVWQUO.js";
12
+ } from "./chunk-CGR5VFJU.js";
13
13
  import {
14
14
  MODE_PALETTES,
15
15
  T,
16
16
  TimeTravel,
17
17
  useNarrowRow
18
- } from "./chunk-FFQ5EARP.js";
18
+ } from "./chunk-QNMA25X5.js";
19
19
  import {
20
20
  bandChartGroup,
21
21
  bandIndexOf,
22
22
  resolveNavigation,
23
23
  stepBands
24
- } from "./chunk-VBRT372Q.js";
24
+ } from "./chunk-LCIEJYZG.js";
25
25
  import {
26
26
  selectAgentInstances
27
27
  } from "./chunk-2JAWSPNG.js";
@@ -4172,4 +4172,4 @@ export {
4172
4172
  WhereFrom,
4173
4173
  Lens
4174
4174
  };
4175
- //# sourceMappingURL=chunk-CWKOGPOJ.js.map
4175
+ //# sourceMappingURL=chunk-LRGT7UQL.js.map