footprintjs 5.0.0 → 6.0.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 (78) hide show
  1. package/CLAUDE.md +32 -8
  2. package/dist/advanced.js +2 -3
  3. package/dist/esm/advanced.js +2 -2
  4. package/dist/esm/index.js +1 -1
  5. package/dist/esm/lib/builder/FlowChartBuilder.js +364 -100
  6. package/dist/esm/lib/builder/index.js +1 -1
  7. package/dist/esm/lib/builder/structure/StructureRecorder.js +158 -0
  8. package/dist/esm/lib/builder/structure/StructureRecorderDispatcher.js +171 -0
  9. package/dist/esm/lib/builder/types.js +1 -1
  10. package/dist/esm/lib/engine/errors/errorInfo.js +4 -3
  11. package/dist/esm/lib/engine/handlers/ChildrenExecutor.js +11 -2
  12. package/dist/esm/lib/engine/handlers/DeciderHandler.js +5 -7
  13. package/dist/esm/lib/engine/handlers/RuntimeStructureManager.js +2 -2
  14. package/dist/esm/lib/engine/handlers/SelectorHandler.js +9 -7
  15. package/dist/esm/lib/engine/handlers/SubflowExecutor.js +7 -1
  16. package/dist/esm/lib/engine/handlers/index.js +1 -3
  17. package/dist/esm/lib/engine/handlers/types.js +2 -2
  18. package/dist/esm/lib/engine/narrative/CombinedNarrativeRecorder.js +12 -2
  19. package/dist/esm/lib/engine/narrative/FlowRecorderDispatcher.js +3 -3
  20. package/dist/esm/lib/engine/narrative/NarrativeFlowRecorder.js +8 -1
  21. package/dist/esm/lib/engine/narrative/types.js +1 -1
  22. package/dist/esm/lib/engine/runtimeStageId.js +64 -2
  23. package/dist/esm/lib/engine/traversal/FlowchartTraverser.js +11 -54
  24. package/dist/esm/lib/engine/types.js +1 -1
  25. package/dist/esm/lib/engine/walkSubflowSpec.js +144 -0
  26. package/dist/esm/lib/runner/FlowChartExecutor.js +4 -17
  27. package/dist/esm/lib/runner/getSubtreeSnapshot.js +3 -2
  28. package/dist/esm/trace.js +3 -2
  29. package/dist/index.js +1 -1
  30. package/dist/lib/builder/FlowChartBuilder.js +364 -100
  31. package/dist/lib/builder/index.js +1 -1
  32. package/dist/lib/builder/structure/StructureRecorder.js +159 -0
  33. package/dist/lib/builder/structure/StructureRecorderDispatcher.js +175 -0
  34. package/dist/lib/builder/types.js +1 -1
  35. package/dist/lib/engine/errors/errorInfo.js +4 -3
  36. package/dist/lib/engine/handlers/ChildrenExecutor.js +11 -2
  37. package/dist/lib/engine/handlers/DeciderHandler.js +5 -7
  38. package/dist/lib/engine/handlers/RuntimeStructureManager.js +2 -2
  39. package/dist/lib/engine/handlers/SelectorHandler.js +9 -7
  40. package/dist/lib/engine/handlers/SubflowExecutor.js +7 -1
  41. package/dist/lib/engine/handlers/index.js +2 -5
  42. package/dist/lib/engine/handlers/types.js +2 -2
  43. package/dist/lib/engine/narrative/CombinedNarrativeRecorder.js +12 -2
  44. package/dist/lib/engine/narrative/FlowRecorderDispatcher.js +3 -3
  45. package/dist/lib/engine/narrative/NarrativeFlowRecorder.js +8 -1
  46. package/dist/lib/engine/narrative/types.js +1 -1
  47. package/dist/lib/engine/runtimeStageId.js +66 -3
  48. package/dist/lib/engine/traversal/FlowchartTraverser.js +11 -54
  49. package/dist/lib/engine/types.js +1 -1
  50. package/dist/lib/engine/walkSubflowSpec.js +148 -0
  51. package/dist/lib/runner/FlowChartExecutor.js +4 -17
  52. package/dist/lib/runner/getSubtreeSnapshot.js +3 -2
  53. package/dist/trace.js +5 -2
  54. package/dist/types/advanced.d.ts +4 -4
  55. package/dist/types/index.d.ts +2 -0
  56. package/dist/types/lib/builder/FlowChartBuilder.d.ts +110 -14
  57. package/dist/types/lib/builder/index.d.ts +2 -1
  58. package/dist/types/lib/builder/structure/StructureRecorder.d.ts +349 -0
  59. package/dist/types/lib/builder/structure/StructureRecorderDispatcher.d.ts +77 -0
  60. package/dist/types/lib/builder/types.d.ts +20 -6
  61. package/dist/types/lib/engine/errors/errorInfo.d.ts +3 -2
  62. package/dist/types/lib/engine/handlers/DeciderHandler.d.ts +3 -3
  63. package/dist/types/lib/engine/handlers/RuntimeStructureManager.d.ts +1 -1
  64. package/dist/types/lib/engine/handlers/SelectorHandler.d.ts +2 -2
  65. package/dist/types/lib/engine/handlers/index.d.ts +1 -2
  66. package/dist/types/lib/engine/handlers/types.d.ts +2 -9
  67. package/dist/types/lib/engine/narrative/FlowRecorderDispatcher.d.ts +2 -2
  68. package/dist/types/lib/engine/narrative/types.d.ts +24 -2
  69. package/dist/types/lib/engine/runtimeStageId.d.ts +57 -1
  70. package/dist/types/lib/engine/traversal/FlowchartTraverser.d.ts +1 -6
  71. package/dist/types/lib/engine/types.d.ts +0 -26
  72. package/dist/types/lib/engine/walkSubflowSpec.d.ts +95 -0
  73. package/dist/types/lib/runner/FlowChartExecutor.d.ts +4 -16
  74. package/dist/types/trace.d.ts +3 -1
  75. package/package.json +1 -1
  76. package/dist/esm/lib/engine/handlers/ExtractorRunner.js +0 -122
  77. package/dist/lib/engine/handlers/ExtractorRunner.js +0 -126
  78. package/dist/types/lib/engine/handlers/ExtractorRunner.d.ts +0 -41
@@ -0,0 +1,77 @@
1
+ /**
2
+ * StructureRecorderDispatcher — Fans build-time structure events out to N
3
+ * attached `StructureRecorder` instances.
4
+ *
5
+ * Mirrors `FlowRecorderDispatcher` (engine/narrative/) exactly:
6
+ *
7
+ * - `recorders: StructureRecorder[]` — attach order preserved
8
+ * - per-event fire methods early-return when no recorders attached
9
+ * (zero-allocation fast path)
10
+ * - per-recorder try/catch isolates errors; one bad recorder cannot
11
+ * cascade into the chain build or sibling recorders
12
+ * - errors route to BOTH the dev-mode console warning (matches
13
+ * FlowRecorderDispatcher) AND a structured `buildErrors`
14
+ * accumulator so consumers can inspect failures post-build
15
+ * - spec payloads are NOT frozen at dispatch time — handlers must
16
+ * respect the `readonly` markers on event payload types (the
17
+ * builder still needs to mutate `spec.next` after the immediate
18
+ * `onStageAdded` fires; see `fireStageAdded` for the full note,
19
+ * and `StructureRecorder.ts` header "Spec mutation" for the
20
+ * trust-model implications)
21
+ *
22
+ * The dispatcher itself owns NO chart state. The builder owns the
23
+ * dispatcher; events fire from the natural mutation points in
24
+ * FlowChartBuilder (L7.3).
25
+ */
26
+ import type { FlowChartSpec } from '../types.js';
27
+ import type { StructureDeciderCompleteEvent, StructureEdgeAddedEvent, StructureLoopEdgeAddedEvent, StructureRecorder, StructureStageAddedEvent, StructureSubflowMountedEvent } from './StructureRecorder.js';
28
+ /** Structured error captured when a recorder throws. Read post-build
29
+ * via `builder.getStructureBuildErrors()` — call on the BUILDER
30
+ * reference (NOT the chart returned by `.build()`). Capture the
31
+ * builder reference before `.build()` if you need post-build access. */
32
+ export interface StructureBuildError {
33
+ /** Which recorder's handler threw. */
34
+ readonly recorderId: string;
35
+ /** Which event method (`'onStageAdded'`, `'onEdgeAdded'`, ...). */
36
+ readonly method: string;
37
+ /** Error message extracted from the thrown value. */
38
+ readonly message: string;
39
+ /** The original thrown value — `Error` instance or whatever the
40
+ * recorder threw. Useful when diagnosis needs a stack trace. */
41
+ readonly error: unknown;
42
+ }
43
+ export declare class StructureRecorderDispatcher {
44
+ private recorders;
45
+ private readonly errors;
46
+ private _truncated;
47
+ /** Attach a `StructureRecorder`. Multiple recorders with the same
48
+ * id are allowed; the convention is one id per logical concern. */
49
+ attach(recorder: StructureRecorder): void;
50
+ /** Detach every recorder with the given id. */
51
+ detach(id: string): void;
52
+ /** Defensive copy of the attached recorders — used in tests + by
53
+ * tooling that wants to inspect what's registered. */
54
+ getRecorders(): StructureRecorder[];
55
+ /** Find one recorder by id. */
56
+ getRecorderById<T extends StructureRecorder = StructureRecorder>(id: string): T | undefined;
57
+ /** Read accumulated errors from this build. Returns a defensive copy. */
58
+ getErrors(): StructureBuildError[];
59
+ fireStageAdded(event: StructureStageAddedEvent): void;
60
+ fireEdgeAdded(event: StructureEdgeAddedEvent): void;
61
+ fireLoopEdgeAdded(event: StructureLoopEdgeAddedEvent): void;
62
+ fireDeciderComplete(event: StructureDeciderCompleteEvent): void;
63
+ fireSubflowMounted(event: StructureSubflowMountedEvent): void;
64
+ /**
65
+ * Externally-callable error capture for events the builder fires
66
+ * OUTSIDE the normal fire* fan-out path — specifically the seed
67
+ * replay in `FlowChartBuilder.attachStructureRecorder()`, which
68
+ * targets one specific recorder rather than every attached recorder.
69
+ *
70
+ * Same observability contract as the internal `recordError`:
71
+ * accumulates on `getErrors()` AND logs in dev mode.
72
+ */
73
+ recordErrorForReplay(recorderId: string, method: string, err: unknown): void;
74
+ private recordError;
75
+ }
76
+ /** Re-export the FlowChartSpec type for downstream type completeness. */
77
+ export type { FlowChartSpec };
@@ -9,8 +9,9 @@
9
9
  * The builder remains standalone at runtime.
10
10
  */
11
11
  import type { StageNode } from '../engine/graph/StageNode.js';
12
- import type { ILogger, ScopeFactory, StageFunction, TraversalExtractor } from '../engine/types.js';
12
+ import type { ILogger, ScopeFactory, StageFunction } from '../engine/types.js';
13
13
  import type { ScopeProtectionMode } from '../scope/protection/types.js';
14
+ import type { StructureRecorder } from './structure/StructureRecorder.js';
14
15
  export type { ResumeFn, StageNode } from '../engine/graph/StageNode.js';
15
16
  export type { ILogger, StageFunction, StreamCallback, StreamHandlers, StreamLifecycleHandler, StreamTokenHandler, SubflowMountOptions, } from '../engine/types.js';
16
17
  export { ArrayMergeMode } from '../engine/types.js';
@@ -77,14 +78,27 @@ export interface FlowChartSpec {
77
78
  /** True when this node is a back-edge reference created by loopTo() — not an executable stage. */
78
79
  isLoopReference?: boolean;
79
80
  }
80
- /** Metadata provided to the build-time extractor for each node. */
81
- export type BuildTimeNodeMetadata = FlowChartSpec;
82
- export type BuildTimeExtractor<TResult = FlowChartSpec> = (metadata: BuildTimeNodeMetadata) => TResult;
83
- export type { TraversalExtractor } from '../engine/types.js';
81
+ /**
82
+ * Options-bag argument shape for the `flowChart()` factory.
83
+ */
84
+ export interface FlowChartOptions {
85
+ /**
86
+ * Build-time recorders to attach BEFORE `start()` fires. Equivalent
87
+ * to chaining `.attachStructureRecorder(rec)` immediately after the
88
+ * factory returns — but registered EARLIER, so even the seed event
89
+ * fires through the dispatcher without needing the seed-replay path.
90
+ *
91
+ * Multiple recorders attach in array order (same as
92
+ * `.attachStructureRecorder` repeated). See `StructureRecorder` JSDoc
93
+ * for event semantics, ordering invariants, and the trust model.
94
+ */
95
+ structureRecorders?: StructureRecorder[];
96
+ /** Free-form description shown on the root spec node. */
97
+ description?: string;
98
+ }
84
99
  export type FlowChart<TOut = any, TScope = any> = {
85
100
  root: StageNode<TOut, TScope>;
86
101
  stageMap: Map<string, StageFunction<TOut, TScope>>;
87
- extractor?: TraversalExtractor;
88
102
  subflows?: Record<string, {
89
103
  root: StageNode<TOut, TScope>;
90
104
  }>;
@@ -6,8 +6,9 @@
6
6
  * a single extraction point that preserves structured details while still
7
7
  * producing a human-readable message.
8
8
  *
9
- * Consumers (narrative recorders, extractors, diagnostic collectors) receive
10
- * StructuredErrorInfo instead of a flat string, and can decide how to render it.
9
+ * Consumers (narrative recorders, FlowRecorder.onError handlers, diagnostic
10
+ * collectors) receive StructuredErrorInfo instead of a flat string, and can
11
+ * decide how to render it.
11
12
  * String-ification happens only at the final rendering boundary.
12
13
  */
13
14
  import type { ValidationIssue } from '../../schema/errors.js';
@@ -8,8 +8,8 @@ import type { StageContext } from '../../memory/StageContext.js';
8
8
  import type { StageNode } from '../graph/StageNode.js';
9
9
  import type { TraversalContext } from '../narrative/types.js';
10
10
  import type { HandlerDeps, StageFunction } from '../types.js';
11
- import type { CallExtractorFn, ExecuteNodeFn, GetStagePathFn, RunStageFn } from './types.js';
12
- export type { CallExtractorFn, ExecuteNodeFn, GetStagePathFn, RunStageFn };
11
+ import type { ExecuteNodeFn, RunStageFn } from './types.js';
12
+ export type { ExecuteNodeFn, RunStageFn };
13
13
  export declare class DeciderHandler<TOut = any, TScope = any> {
14
14
  private readonly deps;
15
15
  constructor(deps: HandlerDeps<TOut, TScope>);
@@ -20,5 +20,5 @@ export declare class DeciderHandler<TOut = any, TScope = any> {
20
20
  */
21
21
  handleScopeBased(node: StageNode<TOut, TScope>, stageFunc: StageFunction<TOut, TScope>, context: StageContext, breakFlag: {
22
22
  shouldBreak: boolean;
23
- }, branchPath: string | undefined, runStage: RunStageFn<TOut, TScope>, executeNode: ExecuteNodeFn<TOut, TScope>, callExtractor: CallExtractorFn<TOut, TScope>, getStagePath: GetStagePathFn<TOut, TScope>, traversalContext?: TraversalContext): Promise<any>;
23
+ }, branchPath: string | undefined, runStage: RunStageFn<TOut, TScope>, executeNode: ExecuteNodeFn<TOut, TScope>, traversalContext?: TraversalContext): Promise<any>;
24
24
  }
@@ -11,7 +11,7 @@ import type { StageNode } from '../graph/StageNode.js';
11
11
  import type { SerializedPipelineStructure } from '../types.js';
12
12
  /**
13
13
  * Compute the node type from node properties.
14
- * Shared by RuntimeStructureManager (serialization) and ExtractorRunner (metadata).
14
+ * Used by RuntimeStructureManager for serialization.
15
15
  */
16
16
  export declare function computeNodeType(node: StageNode): 'stage' | 'decider' | 'selector' | 'fork' | 'streaming' | 'subflow' | 'loop';
17
17
  export declare class RuntimeStructureManager {
@@ -11,7 +11,7 @@ import type { StageNode } from '../graph/StageNode.js';
11
11
  import type { TraversalContext } from '../narrative/types.js';
12
12
  import type { HandlerDeps, NodeResultType, StageFunction } from '../types.js';
13
13
  import type { ChildrenExecutor } from './ChildrenExecutor.js';
14
- import type { CallExtractorFn, ExecuteNodeFn, GetStagePathFn, RunStageFn } from './types.js';
14
+ import type { ExecuteNodeFn, RunStageFn } from './types.js';
15
15
  export declare class SelectorHandler<TOut = any, TScope = any> {
16
16
  private readonly deps;
17
17
  private readonly childrenExecutor;
@@ -23,5 +23,5 @@ export declare class SelectorHandler<TOut = any, TScope = any> {
23
23
  */
24
24
  handleScopeBased(node: StageNode<TOut, TScope>, stageFunc: StageFunction<TOut, TScope>, context: StageContext, breakFlag: {
25
25
  shouldBreak: boolean;
26
- }, branchPath: string | undefined, runStage: RunStageFn<TOut, TScope>, executeNode: ExecuteNodeFn<TOut, TScope>, callExtractor: CallExtractorFn<TOut, TScope>, getStagePath: GetStagePathFn<TOut, TScope>, traversalContext?: TraversalContext): Promise<Record<string, NodeResultType>>;
26
+ }, branchPath: string | undefined, runStage: RunStageFn<TOut, TScope>, executeNode: ExecuteNodeFn<TOut, TScope>, traversalContext?: TraversalContext): Promise<Record<string, NodeResultType>>;
27
27
  }
@@ -3,12 +3,11 @@
3
3
  */
4
4
  export { StageRunner } from './StageRunner.js';
5
5
  export { NodeResolver } from './NodeResolver.js';
6
- export type { CallExtractorFn, ExecuteNodeFn, GetStagePathFn, RunStageFn } from './types.js';
6
+ export type { ExecuteNodeFn, RunStageFn } from './types.js';
7
7
  export { ChildrenExecutor } from './ChildrenExecutor.js';
8
8
  export { DeciderHandler } from './DeciderHandler.js';
9
9
  export { SelectorHandler } from './SelectorHandler.js';
10
10
  export { ContinuationResolver, DEFAULT_MAX_ITERATIONS } from './ContinuationResolver.js';
11
11
  export { SubflowExecutor } from './SubflowExecutor.js';
12
12
  export { applyOutputMapping, createSubflowHandlerDeps, extractParentScopeValues, getInitialScopeValues, seedSubflowGlobalStore, } from './SubflowInputMapper.js';
13
- export { ExtractorRunner } from './ExtractorRunner.js';
14
13
  export { computeNodeType, RuntimeStructureManager } from './RuntimeStructureManager.js';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * handlers/types.ts — Shared callback types for all handler modules.
3
3
  *
4
- * Avoids duplicate definitions of ExecuteNodeFn / CallExtractorFn / etc.
4
+ * Avoids duplicate definitions of ExecuteNodeFn / RunStageFn / etc.
5
5
  * across ChildrenExecutor, DeciderHandler, ContinuationResolver, SubflowExecutor.
6
6
  * All types are callbacks that break circular dependencies with FlowchartTraverser.
7
7
  */
@@ -28,12 +28,5 @@ export interface BreakFlag {
28
28
  }
29
29
  /** Recursive node execution — avoids circular dep with FlowchartTraverser. */
30
30
  export type ExecuteNodeFn<TOut = any, TScope = any> = (node: StageNode<TOut, TScope>, context: StageContext, breakFlag: BreakFlag, branchPath?: string) => Promise<any>;
31
- /** Run a stage function with commit + extractor. */
31
+ /** Run a stage function with commit. */
32
32
  export type RunStageFn<TOut = any, TScope = any> = (node: StageNode<TOut, TScope>, stageFunc: StageFunction<TOut, TScope>, context: StageContext, breakFn: () => void) => Promise<TOut>;
33
- /** Call the traversal extractor after stage execution. */
34
- export type CallExtractorFn<TOut = any, TScope = any> = (node: StageNode<TOut, TScope>, context: StageContext, stagePath: string, stageOutput?: unknown, errorInfo?: {
35
- type: string;
36
- message: string;
37
- }) => void;
38
- /** Compute the stage path string for extractor and narrative. */
39
- export type GetStagePathFn<TOut = any, TScope = any> = (node: StageNode<TOut, TScope>, branchPath?: string, contextStageName?: string) => string;
@@ -10,7 +10,7 @@
10
10
  * When no recorders are attached, every method is a fast no-op (empty array check).
11
11
  */
12
12
  import type { DecisionEvidence, SelectionEvidence } from '../../decide/types.js';
13
- import type { FlowRecorder, IControlFlowNarrative, TraversalContext } from './types.js';
13
+ import type { FlowRecorder, IControlFlowNarrative, StageType, TraversalContext } from './types.js';
14
14
  export declare class FlowRecorderDispatcher implements IControlFlowNarrative {
15
15
  private recorders;
16
16
  /** Attach a FlowRecorder. Duplicate IDs are allowed (same as scope ScopeRecorder). */
@@ -21,7 +21,7 @@ export declare class FlowRecorderDispatcher implements IControlFlowNarrative {
21
21
  getScopeRecorders(): FlowRecorder[];
22
22
  /** Find a recorder by ID. Useful for retrieving built-in recorders like NarrativeFlowRecorder. */
23
23
  getRecorderById<T extends FlowRecorder = FlowRecorder>(id: string): T | undefined;
24
- onStageExecuted(stageName: string, description?: string, traversalContext?: TraversalContext): void;
24
+ onStageExecuted(stageName: string, description: string | undefined, traversalContext: TraversalContext | undefined, stageType: StageType): void;
25
25
  onNext(fromStage: string, toStage: string, description?: string, traversalContext?: TraversalContext): void;
26
26
  onDecision(deciderName: string, chosenBranch: string, rationale?: string, deciderDescription?: string, traversalContext?: TraversalContext, evidence?: DecisionEvidence): void;
27
27
  onFork(parentStage: string, childNames: string[], traversalContext?: TraversalContext): void;
@@ -13,9 +13,25 @@ import type { StructuredErrorInfo } from '../errors/errorInfo.js';
13
13
  * Uses Null Object pattern: NullControlFlowNarrativeGenerator satisfies this
14
14
  * interface with empty methods for zero-cost disabled path.
15
15
  */
16
+ /**
17
+ * The kind of stage that completed. Lets consumers route uniform
18
+ * "did this stage execute" handling without a side-table lookup into
19
+ * the chart spec. Required on every `onStageExecuted` event since
20
+ * proposal #003 unified the event to fire for ALL stage kinds.
21
+ */
22
+ export type StageType = 'linear' | 'decider' | 'fork' | 'selector' | 'subflow-mount';
16
23
  export interface IControlFlowNarrative {
17
- /** Called when a stage executes. First stage uses distinct opening pattern. */
18
- onStageExecuted(stageName: string, description?: string, traversalContext?: TraversalContext): void;
24
+ /**
25
+ * Called when a stage completes its main work. Fires for ALL stage
26
+ * kinds (`'linear'`, `'decider'`, `'fork'`, `'selector'`, `'subflow-mount'`)
27
+ * AFTER the corresponding specialized event (`onDecision`, `onFork`,
28
+ * `onSelected`, `onSubflowEntry`). For linear stages, fires after
29
+ * the stage function returns.
30
+ *
31
+ * Consumers tracking "did this stage run?" use this event uniformly
32
+ * and switch on `stageType` for kind-specific work.
33
+ */
34
+ onStageExecuted(stageName: string, description: string | undefined, traversalContext: TraversalContext | undefined, stageType: StageType): void;
19
35
  /** Called on linear continuation from one stage to the next. */
20
36
  onNext(fromStage: string, toStage: string, description?: string, traversalContext?: TraversalContext): void;
21
37
  /** Called when a decider selects a branch. Most valuable for LLM context. */
@@ -115,6 +131,12 @@ export interface FlowStageEvent {
115
131
  description?: string;
116
132
  /** Traversal context from the engine — read-only, set by traverser. */
117
133
  traversalContext?: TraversalContext;
134
+ /**
135
+ * Which kind of stage completed. The engine fires `onStageExecuted`
136
+ * uniformly for every stage kind (proposal #003); consumers route by
137
+ * `stageType` without a chart-spec lookup.
138
+ */
139
+ stageType: StageType;
118
140
  }
119
141
  /** Event passed to FlowRecorder.onNext. */
120
142
  export interface FlowNextEvent {
@@ -14,6 +14,15 @@
14
14
  * - Human-readable ('sf-tools/execute-tool-calls#8')
15
15
  * - Parseable (split on '#' for stageId and index, split stageId on '/' for subflow path)
16
16
  *
17
+ * Naming-collision warning
18
+ * ────────────────────────
19
+ * The parsed-output `.stageId` field below is the LOCAL form (segment
20
+ * after the last '/'). This is NOT the same as `spec.id` / `node.id`
21
+ * for subflow-nested stages, which carry the FULL prefixed form
22
+ * (`'sf-tools/execute-tool-calls'`). To compare safely, use
23
+ * `splitStageId(spec.id)` to decompose the prefixed form the same
24
+ * way `parseRuntimeStageId` decomposes a runtimeStageId.
25
+ *
17
26
  * @example
18
27
  * ```
19
28
  * buildRuntimeStageId('call-llm', 5) // 'call-llm#5'
@@ -29,12 +38,59 @@
29
38
  * consumers constructing IDs from parsed components (round-trip via parseRuntimeStageId).
30
39
  */
31
40
  export declare function buildRuntimeStageId(stageId: string, executionIndex: number, subflowPath?: string): string;
32
- /** Parse a runtimeStageId into its components. */
41
+ /**
42
+ * Parse a runtimeStageId into its components.
43
+ *
44
+ * IMPORTANT — naming collision: the returned `stageId` is the LOCAL
45
+ * form (the segment between the last '/' and the '#'). This is NOT
46
+ * the same as `spec.id` or `node.id` for subflow-nested stages,
47
+ * which contain the FULL prefixed form.
48
+ *
49
+ * parseRuntimeStageId('sf-tools/execute-tool-calls#8').stageId
50
+ * // → 'execute-tool-calls' (LOCAL)
51
+ *
52
+ * node.id // (post-mount, in a spec that contains subflows)
53
+ * // → 'sf-tools/execute-tool-calls' (FULL prefixed)
54
+ *
55
+ * To compare these two safely, use `splitStageId(node.id)` to get
56
+ * the local form, OR reconstruct the full form via
57
+ * `(subflowPath ? subflowPath + '/' : '') + stageId`.
58
+ */
33
59
  export declare function parseRuntimeStageId(runtimeStageId: string): {
34
60
  stageId: string;
35
61
  executionIndex: number;
36
62
  subflowPath: string | undefined;
37
63
  };
64
+ /**
65
+ * Decompose a (possibly prefixed) stage id into its components.
66
+ *
67
+ * Use this when you have an id WITHOUT the `#N` execution suffix and
68
+ * need the local stage name and/or the subflow path. Common sources
69
+ * of such ids:
70
+ * - `spec.id` (post-mount the id includes any subflow prefix)
71
+ * - `CommitBundle.stageId` (post-mount id)
72
+ * - `node.id` from xyflow nodes built off the spec
73
+ * - the segment of `runtimeStageId` BEFORE the `#` (use
74
+ * `parseRuntimeStageId` directly for full runtimeStageId strings)
75
+ *
76
+ * Mirrors the decomposition `parseRuntimeStageId` performs on the
77
+ * stageId portion of a runtimeStageId, so the two helpers stay in
78
+ * lockstep on naming and behavior.
79
+ *
80
+ * @example
81
+ * splitStageId('sf-tools/execute-tool-calls')
82
+ * // → { localStageId: 'execute-tool-calls', subflowPath: 'sf-tools' }
83
+ *
84
+ * splitStageId('execute-tool-calls')
85
+ * // → { localStageId: 'execute-tool-calls', subflowPath: undefined }
86
+ *
87
+ * splitStageId('sf-outer/sf-inner/validate')
88
+ * // → { localStageId: 'validate', subflowPath: 'sf-outer/sf-inner' }
89
+ */
90
+ export declare function splitStageId(prefixedStageId: string): {
91
+ localStageId: string;
92
+ subflowPath: string | undefined;
93
+ };
38
94
  /**
39
95
  * Shared mutable counter for execution index.
40
96
  * Passed by reference to child traversers (subflows) so they
@@ -21,7 +21,7 @@
21
21
  import type { ScopeProtectionMode } from '../../scope/protection/types.js';
22
22
  import { FlowRecorderDispatcher } from '../narrative/FlowRecorderDispatcher.js';
23
23
  import type { FlowRecorder, IControlFlowNarrative } from '../narrative/types.js';
24
- import type { ExtractorError, IExecutionRuntime, ILogger, ScopeFactory, SerializedPipelineStructure, StageFunction, StageNode, StreamHandlers, SubflowResult, TraversalExtractor, TraversalResult } from '../types.js';
24
+ import type { IExecutionRuntime, ILogger, ScopeFactory, SerializedPipelineStructure, StageFunction, StageNode, StreamHandlers, SubflowResult, TraversalResult } from '../types.js';
25
25
  export interface TraverserOptions<TOut = any, TScope = any> {
26
26
  root: StageNode<TOut, TScope>;
27
27
  stageMap: Map<string, StageFunction<TOut, TScope>>;
@@ -32,12 +32,10 @@ export interface TraverserOptions<TOut = any, TScope = any> {
32
32
  executionEnv?: import('../../engine/types').ExecutionEnv;
33
33
  throttlingErrorChecker?: (error: unknown) => boolean;
34
34
  streamHandlers?: StreamHandlers;
35
- extractor?: TraversalExtractor;
36
35
  scopeProtectionMode?: ScopeProtectionMode;
37
36
  subflows?: Record<string, {
38
37
  root: StageNode<TOut, TScope>;
39
38
  }>;
40
- enrichSnapshots?: boolean;
41
39
  narrativeEnabled?: boolean;
42
40
  buildTimeStructure?: SerializedPipelineStructure;
43
41
  logger: ILogger;
@@ -103,7 +101,6 @@ export declare class FlowchartTraverser<TOut = any, TScope = any> {
103
101
  private readonly deciderHandler;
104
102
  private readonly selectorHandler;
105
103
  private readonly structureManager;
106
- private readonly extractorRunner;
107
104
  private readonly narrativeGenerator;
108
105
  private readonly flowRecorderDispatcher;
109
106
  private subflowResults;
@@ -194,8 +191,6 @@ export declare class FlowchartTraverser<TOut = any, TScope = any> {
194
191
  getBranchIds(): string[];
195
192
  getRuntimeRoot(): StageNode;
196
193
  getSubflowResults(): Map<string, SubflowResult>;
197
- getExtractedResults<TResult = unknown>(): Map<string, TResult>;
198
- getExtractorErrors(): ExtractorError[];
199
194
  getNarrative(): string[];
200
195
  /** Returns the FlowRecorderDispatcher, or undefined if narrative is disabled. */
201
196
  getFlowRecorderDispatcher(): FlowRecorderDispatcher | undefined;
@@ -323,32 +323,6 @@ export interface RuntimeStructureMetadata {
323
323
  isLoopReference?: boolean;
324
324
  streamId?: string;
325
325
  }
326
- export interface StageSnapshot<TOut = any, TScope = any> {
327
- node: StageNode<TOut, TScope>;
328
- context: StageContext;
329
- stepNumber: number;
330
- structureMetadata: RuntimeStructureMetadata;
331
- scopeState?: Record<string, unknown>;
332
- debugInfo?: {
333
- logs: Record<string, unknown>;
334
- errors: Record<string, unknown>;
335
- metrics: Record<string, unknown>;
336
- evals: Record<string, unknown>;
337
- flowMessages?: FlowMessage[];
338
- };
339
- stageOutput?: unknown;
340
- errorInfo?: {
341
- type: string;
342
- message: string;
343
- };
344
- historyIndex?: number;
345
- }
346
- export type TraversalExtractor<TResult = unknown> = (snapshot: StageSnapshot) => TResult | undefined | null;
347
- export interface ExtractorError {
348
- stagePath: string;
349
- message: string;
350
- error: unknown;
351
- }
352
326
  export type NodeResultType = {
353
327
  id: string;
354
328
  result: unknown;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * walkSubflowSpec — yield the structural shape of a subflow spec as a
3
+ * flat ordered stream of items, with `subflowPath` already composed
4
+ * for nested subflows.
5
+ *
6
+ * This is the public contract for traversing the structure delivered
7
+ * via `StructureSubflowMountedEvent.subflowSpec`. Item shapes mirror
8
+ * the corresponding Structure event payloads so consumers can route
9
+ * walker items through the same handlers they use for live events.
10
+ *
11
+ * Walker contract (LOCKED):
12
+ * 1. AUTO-RECURSE by default into nested subflows, with composed
13
+ * paths (`parent/child/...`). Pass `{ recurse: false }` to walk
14
+ * only one level.
15
+ * 2. ENTRY-STAGE MARKER FIRST: for each subflow (top-level and
16
+ * nested), yields a `{ kind: 'subflow-start', ... }` item BEFORE
17
+ * any stage/edge items from that subflow. Lets consumers draw the
18
+ * boundary edge from the mount node to the entry stage.
19
+ * 3. COMPOSED PATHS: nested subflows get `parentPath + '/' + localId`.
20
+ * Top-level mount paths are local-only (`'auth'`, NOT `'__root__/auth'`).
21
+ * 4. SHAPE MIRRORING: stage/edge/loop items have the same payload
22
+ * shape as Structure events, with `subflowPath` added.
23
+ * 5. SOURCE DISCRIMINATOR: every walker item carries `source: 'walker'`
24
+ * (Structure events do NOT). Lets consumers distinguish event vs
25
+ * walker in logs/debuggers while still sharing handler code paths.
26
+ * 6. STAGE-ID PREFIXING: stage IDs in nested subflows are already
27
+ * prefixed by the spec (e.g. `'auth/verify/check'`). Walker
28
+ * preserves this; `subflowPath` field is redundant-but-explicit.
29
+ */
30
+ import type { SerializedPipelineStructure } from '../builder/types.js';
31
+ export interface WalkerOptions {
32
+ /** Auto-recurse into nested subflows (default: true). When false,
33
+ * nested subflow items are yielded but their internals are not
34
+ * traversed. */
35
+ recurse?: boolean;
36
+ }
37
+ export type WalkerItem = {
38
+ kind: 'subflow-start';
39
+ stageId: string;
40
+ subflowPath: string;
41
+ source: 'walker';
42
+ } | {
43
+ kind: 'stage';
44
+ stageId: string;
45
+ name: string;
46
+ type: NonNullable<SerializedPipelineStructure['type']>;
47
+ isPausable?: boolean;
48
+ spec: SerializedPipelineStructure;
49
+ subflowPath: string;
50
+ source: 'walker';
51
+ } | {
52
+ kind: 'edge';
53
+ from: string;
54
+ to: string;
55
+ edgeKind: 'next' | 'fork-branch' | 'decision-branch';
56
+ label?: string;
57
+ subflowPath: string;
58
+ source: 'walker';
59
+ } | {
60
+ kind: 'loop';
61
+ from: string;
62
+ to: string;
63
+ subflowPath: string;
64
+ source: 'walker';
65
+ } | {
66
+ kind: 'subflow';
67
+ mountStageId: string;
68
+ subflowId: string;
69
+ subflowName: string;
70
+ subflowSpec: SerializedPipelineStructure;
71
+ subflowPath: string;
72
+ source: 'walker';
73
+ };
74
+ /**
75
+ * Walk a subflow spec, yielding its structure as flat ordered items.
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * import { walkSubflowSpec } from 'footprintjs/trace';
80
+ *
81
+ * onSubflowMounted(event) {
82
+ * if (!event.subflowSpec) return; // lazy mount — no spec yet
83
+ * for (const item of walkSubflowSpec(event.subflowSpec, event.subflowPath)) {
84
+ * switch (item.kind) {
85
+ * case 'subflow-start': break; // entry boundary
86
+ * case 'stage': break; // inner stage
87
+ * case 'edge': break; // inner edge
88
+ * case 'loop': break; // inner loop back-edge
89
+ * case 'subflow': break; // nested mount marker
90
+ * }
91
+ * }
92
+ * }
93
+ * ```
94
+ */
95
+ export declare function walkSubflowSpec(spec: SerializedPipelineStructure, subflowPath: string, options?: WalkerOptions): Generator<WalkerItem, void, void>;
@@ -9,7 +9,7 @@
9
9
  * const executor = new FlowChartExecutor(chart);
10
10
  *
11
11
  * // Options-object form (preferred when you need to customize behavior):
12
- * const executor = new FlowChartExecutor(chart, { scopeFactory: myFactory, enrichSnapshots: true });
12
+ * const executor = new FlowChartExecutor(chart, { scopeFactory: myFactory });
13
13
  *
14
14
  * // 2-param form (accepts a ScopeFactory directly, for backward compatibility):
15
15
  * const executor = new FlowChartExecutor(chart, myFactory);
@@ -21,7 +21,7 @@ import type { CombinedNarrativeRecorderOptions } from '../engine/narrative/Combi
21
21
  import type { CombinedNarrativeEntry } from '../engine/narrative/narrativeTypes.js';
22
22
  import type { ManifestEntry } from '../engine/narrative/recorders/ManifestFlowRecorder.js';
23
23
  import type { FlowRecorder } from '../engine/narrative/types.js';
24
- import { type ExecutorResult, type ExtractorError, type RunOptions, type ScopeFactory, type SerializedPipelineStructure, type StageNode, type StreamHandlers, type SubflowResult } from '../engine/types.js';
24
+ import { type ExecutorResult, type RunOptions, type ScopeFactory, type SerializedPipelineStructure, type StageNode, type StreamHandlers, type SubflowResult } from '../engine/types.js';
25
25
  import type { FlowchartCheckpoint } from '../pause/types.js';
26
26
  import type { CombinedRecorder } from '../recorder/CombinedRecorder.js';
27
27
  import type { EmitRecorder } from '../recorder/EmitRecorder.js';
@@ -34,7 +34,7 @@ import { type RuntimeSnapshot } from './ExecutionRuntime.js';
34
34
  * ```typescript
35
35
  * const ex = new FlowChartExecutor(chart, {
36
36
  * scopeFactory: myFactory,
37
- * enrichSnapshots: true,
37
+ * defaultValuesForContext: { ... },
38
38
  * });
39
39
  * ```
40
40
  *
@@ -50,14 +50,6 @@ import { type RuntimeSnapshot } from './ExecutionRuntime.js';
50
50
  export interface FlowChartExecutorOptions<TScope = any> {
51
51
  /** Custom scope factory. Defaults to TypedScope or ScopeFacade auto-detection. */
52
52
  scopeFactory?: ScopeFactory<TScope>;
53
- /**
54
- * Attach a per-stage scope snapshot to each extractor result. When `true`, the
55
- * extraction callback receives the full shared state at the point that stage
56
- * committed — useful for debugging multi-stage state transitions. Defaults to
57
- * `false` (no scope snapshot attached). Can also be set on the chart via
58
- * `flowChart(...).enrichSnapshots(true)`.
59
- */
60
- enrichSnapshots?: boolean;
61
53
  /**
62
54
  * Default values pre-populated into the shared context before **each** stage
63
55
  * (re-applied every stage, acting as baseline defaults).
@@ -121,7 +113,7 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
121
113
  *
122
114
  * **Options object form** (preferred):
123
115
  * ```typescript
124
- * new FlowChartExecutor(chart, { scopeFactory, enrichSnapshots: true })
116
+ * new FlowChartExecutor(chart, { scopeFactory, defaultValuesForContext })
125
117
  * ```
126
118
  *
127
119
  * **2-param form** (also supported):
@@ -419,10 +411,6 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
419
411
  getRuntimeStructure(): SerializedPipelineStructure | undefined;
420
412
  /** @internal */
421
413
  getSubflowResults(): Map<string, SubflowResult>;
422
- /** @internal */
423
- getExtractedResults<TResult = unknown>(): Map<string, TResult>;
424
- /** @internal */
425
- getExtractorErrors(): ExtractorError[];
426
414
  /**
427
415
  * Returns the subflow manifest from an attached ManifestFlowRecorder.
428
416
  * Returns empty array if no ManifestFlowRecorder is attached.
@@ -21,7 +21,9 @@
21
21
  * ```
22
22
  */
23
23
  export type { ExecutionCounter } from './lib/engine/runtimeStageId.js';
24
- export { buildRuntimeStageId, createExecutionCounter, parseRuntimeStageId } from './lib/engine/runtimeStageId.js';
24
+ export { buildRuntimeStageId, createExecutionCounter, parseRuntimeStageId, splitStageId, } from './lib/engine/runtimeStageId.js';
25
+ export type { WalkerItem, WalkerOptions } from './lib/engine/walkSubflowSpec.js';
26
+ export { walkSubflowSpec } from './lib/engine/walkSubflowSpec.js';
25
27
  export { findCommit, findCommits, findLastWriter } from './lib/memory/commitLogUtils.js';
26
28
  export type { CausalChainOptions, CausalNode, KeysReadLookup } from './lib/memory/backtrack.js';
27
29
  export { causalChain, flattenCausalDAG, formatCausalChain } from './lib/memory/backtrack.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "footprintjs",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "Explainable backend flows — automatic causal traces, decision evidence, and MCP tool generation for AI agents",
5
5
  "license": "MIT",
6
6
  "author": "Sanjay Krishna Anbalagan",