footprintjs 4.7.0 → 4.9.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 (33) hide show
  1. package/CLAUDE.md +38 -14
  2. package/dist/esm/lib/engine/narrative/CombinedNarrativeRecorder.js +97 -90
  3. package/dist/esm/lib/engine/narrative/narrativeTypes.js +1 -1
  4. package/dist/esm/lib/engine/traversal/FlowchartTraverser.js +8 -6
  5. package/dist/esm/lib/memory/StageContext.js +2 -1
  6. package/dist/esm/lib/memory/types.js +1 -1
  7. package/dist/esm/lib/recorder/KeyedRecorder.js +56 -7
  8. package/dist/esm/lib/recorder/SequenceRecorder.js +194 -0
  9. package/dist/esm/lib/recorder/index.js +2 -1
  10. package/dist/esm/lib/scope/recorders/MetricRecorder.js +85 -82
  11. package/dist/esm/lib/scope/recorders/index.js +1 -1
  12. package/dist/esm/trace.js +10 -5
  13. package/dist/lib/engine/narrative/CombinedNarrativeRecorder.js +97 -90
  14. package/dist/lib/engine/narrative/narrativeTypes.js +1 -1
  15. package/dist/lib/engine/traversal/FlowchartTraverser.js +8 -6
  16. package/dist/lib/memory/StageContext.js +2 -1
  17. package/dist/lib/memory/types.js +1 -1
  18. package/dist/lib/recorder/KeyedRecorder.js +56 -7
  19. package/dist/lib/recorder/SequenceRecorder.js +198 -0
  20. package/dist/lib/recorder/index.js +4 -2
  21. package/dist/lib/scope/recorders/MetricRecorder.js +85 -82
  22. package/dist/lib/scope/recorders/index.js +1 -1
  23. package/dist/trace.js +12 -6
  24. package/dist/types/lib/engine/narrative/CombinedNarrativeRecorder.d.ts +8 -18
  25. package/dist/types/lib/engine/narrative/narrativeTypes.d.ts +7 -0
  26. package/dist/types/lib/memory/types.d.ts +2 -0
  27. package/dist/types/lib/recorder/KeyedRecorder.d.ts +29 -6
  28. package/dist/types/lib/recorder/SequenceRecorder.d.ts +112 -0
  29. package/dist/types/lib/recorder/index.d.ts +1 -0
  30. package/dist/types/lib/scope/recorders/MetricRecorder.d.ts +47 -40
  31. package/dist/types/lib/scope/recorders/index.d.ts +1 -1
  32. package/dist/types/trace.d.ts +7 -3
  33. package/package.json +1 -1
package/CLAUDE.md CHANGED
@@ -16,7 +16,7 @@ src/lib/
16
16
  ├── scope/ → Per-stage facades + recorders + providers
17
17
  ├── reactive/ → TypedScope<T> deep Proxy (typed property access, $-methods, cycle-safe)
18
18
  ├── decide/ → decide()/select() decision evidence capture (filter + function)
19
- ├── recorder/ → CompositeRecorder, KeyedRecorder<T> base class, composition primitives
19
+ ├── recorder/ → CompositeRecorder, KeyedRecorder<T>, SequenceRecorder<T>, composition primitives
20
20
  ├── pause/ → Pause/Resume (PauseSignal, FlowchartCheckpoint, PausableHandler)
21
21
  ├── engine/ → DFS traversal + narrative + 13 handlers
22
22
  ├── runner/ → High-level executor (FlowChartExecutor)
@@ -27,7 +27,7 @@ Dependency DAG: `memory <- scope <- reactive <- engine <- runner`, `schema <- en
27
27
 
28
28
  Three entry points:
29
29
  - `import { ... } from 'footprintjs'` — public API
30
- - `import { ... } from 'footprintjs/trace'` — execution tracing: runtimeStageId, commitLog queries, KeyedRecorder
30
+ - `import { ... } from 'footprintjs/trace'` — execution tracing: runtimeStageId, commitLog queries, KeyedRecorder, SequenceRecorder
31
31
  - `import { ... } from 'footprintjs/advanced'` — engine internals (also re-exports trace)
32
32
 
33
33
  ## Key API
@@ -266,25 +266,49 @@ const llmCommit = findCommit(commitLog, 'call-llm', 'adapterRawResponse');
266
266
  | `findCommit(commitLog, stageId, key?)` | `CommitBundle \| undefined` | Find first commit by stageId |
267
267
  | `findCommits(commitLog, stageId)` | `CommitBundle[]` | Find all commits by stageId |
268
268
  | `findLastWriter(commitLog, key, beforeIdx?)` | `CommitBundle \| undefined` | Search backwards for who wrote a key |
269
- | `KeyedRecorder<T>` | abstract class | Base for Map-based recorders |
269
+ | `KeyedRecorder<T>` | abstract class | Base for 1:1 Map-based recorders |
270
+ | `SequenceRecorder<T>` | abstract class | Base for 1:N ordered sequence recorders (has `getEntryRanges()` for O(1) time-travel) |
270
271
 
271
- **KeyedRecorder<T>** abstract base class for recorders that store data as `Map<runtimeStageId, T>`:
272
+ **Two recorder base classes** choose based on data shape:
273
+
274
+ | Base Class | Relationship | Use When |
275
+ |------------|-------------|----------|
276
+ | `KeyedRecorder<T>` | 1:1 Map | Each step produces one record (MetricRecorder, TokenRecorder) |
277
+ | `SequenceRecorder<T>` | 1:N sequence + Map | Multiple records per step, ordering matters (CombinedNarrativeRecorder) |
272
278
 
273
279
  ```typescript
274
- import { KeyedRecorder } from 'footprintjs/trace';
280
+ import { KeyedRecorder, SequenceRecorder } from 'footprintjs/trace';
275
281
 
276
- class MyRecorder extends KeyedRecorder<MyEntry> {
277
- readonly id = 'my-recorder'; // required (abstract)
278
- onSomeEvent(event) {
279
- this.store(event.runtimeStageId, { ... }); // protected
280
- }
282
+ // KeyedRecorder: one entry per step
283
+ class TokenRecorder extends KeyedRecorder<TokenEntry> {
284
+ readonly id = 'tokens';
285
+ onLLMCall(event) { this.store(event.runtimeStageId, { tokens: event.usage }); }
286
+ }
287
+ recorder.getByKey('call-llm#5'); // Translate: per-step value
288
+ recorder.aggregate((sum, e) => sum + e.tokens, 0); // Aggregate: grand total
289
+ recorder.accumulate((sum, e) => sum + e.tokens, 0, visibleKeys); // Accumulate: up to slider
290
+
291
+ // SequenceRecorder: multiple entries per step, ordered
292
+ class AuditRecorder extends SequenceRecorder<AuditEntry> {
293
+ readonly id = 'audit';
294
+ onRead(event) { this.emit({ runtimeStageId: event.runtimeStageId, type: 'read', key: event.key }); }
295
+ onDecision(event) { this.emit({ runtimeStageId: event.traversalContext?.runtimeStageId, ... }); }
281
296
  }
282
- recorder.getByKey('call-llm#5'); // O(1) lookup
283
- recorder.getMap(); // ReadonlyMap
284
- recorder.values(); // MyEntry[] in insertion order
285
- recorder.clear(); // reset
297
+ recorder.getEntriesForStep('call-llm#5'); // Translate: per-step entries
298
+ recorder.aggregate((count, _) => count + 1, 0); // Aggregate: grand total
299
+ recorder.getEntriesUpTo(visibleKeys); // Progressive: up to slider
300
+ recorder.getEntryRanges(); // Range index: O(1) slider sync
286
301
  ```
287
302
 
303
+ **`getEntryRanges()`** returns a precomputed `Map<runtimeStageId, {firstIdx, endIdx}>` maintained during `emit()`. Use for O(1) per-step range lookups during time-travel scrubbing. Same shape as `buildEntryRangeIndex()` in `footprint-explainable-ui`.
304
+
305
+ **`CombinedNarrativeEntry.direction`** — subflow entries carry `direction: 'entry' | 'exit'`. Use for programmatic subflow boundary detection instead of text scanning (which breaks with custom `NarrativeRenderer`).
306
+
307
+ **`footprint-explainable-ui` narrative utilities** — for consumers building custom shells without `ExplainableShell`:
308
+ - `buildEntryRangeIndex(entries)` — build range index from flat array (when no recorder access)
309
+ - `computeRevealedEntryCount(entries, snapshots, idx, rangeIndex?)` — slider position → entry count
310
+ - `extractSubflowNarrative(entries, subflowId)` — three-tier subflow entry extraction
311
+
288
312
  **How runtimeStageId is generated:** A counter starts at 0 and increments by 1 for each stage execution across the entire run, including subflow stages. Subflow child traversers share the parent counter so indices are globally unique. Stages inside subflows have stageIds already prefixed by the builder (e.g., `sf-tools/execute-tool-calls`), so `buildRuntimeStageId` just appends `#index`.
289
313
 
290
314
  ## Anti-Patterns