footprintjs 3.0.18 → 3.0.21

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.
@@ -1,118 +0,0 @@
1
- # footprint.js — Copilot Instructions
2
-
3
- This is the footprint.js library — the flowchart pattern for backend code. Self-explainable systems that AI can reason about.
4
-
5
- ## Core Principle
6
-
7
- **Collect during traversal, never post-process.** All data collection happens as side effects of the single DFS traversal. Never walk the tree after execution.
8
-
9
- ## Architecture
10
-
11
- ```
12
- src/lib/
13
- ├── memory/ → Transactional state (SharedMemory, StageContext, TransactionBuffer)
14
- ├── schema/ → Validation (Zod optional, duck-typed)
15
- ├── builder/ → Fluent DSL (FlowChartBuilder, flowChart())
16
- ├── scope/ → Per-stage facades + recorders + providers
17
- ├── reactive/ → TypedScope<T> deep Proxy (typed property access, $-methods)
18
- ├── decide/ → decide()/select() decision evidence capture
19
- ├── engine/ → DFS traversal + narrative + handlers
20
- ├── runner/ → FlowChartExecutor
21
- └── contract/ → I/O schema + OpenAPI
22
- ```
23
-
24
- Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
25
-
26
- ## Key API — TypedScope (Recommended)
27
-
28
- ```typescript
29
- import { flowChart, FlowChartExecutor, decide } from 'footprintjs';
30
-
31
- interface State {
32
- creditScore: number;
33
- riskTier: string;
34
- decision?: string;
35
- }
36
-
37
- const chart = flowChart<State>('Intake', async (scope) => {
38
- scope.creditScore = 750; // typed write (no setValue needed)
39
- scope.riskTier = 'low'; // typed write
40
- }, 'intake')
41
- .addDeciderFunction('Route', (scope) => {
42
- return decide(scope, [
43
- { when: { riskTier: { eq: 'low' } }, then: 'approved', label: 'Low risk' },
44
- ], 'rejected');
45
- }, 'route', 'Route based on risk')
46
- .addFunctionBranch('approved', 'Approve', async (scope) => {
47
- scope.decision = 'Approved';
48
- })
49
- .addFunctionBranch('rejected', 'Reject', async (scope) => {
50
- scope.decision = 'Rejected';
51
- })
52
- .setDefault('rejected')
53
- .end()
54
- .build();
55
-
56
- const executor = new FlowChartExecutor(chart);
57
- await executor.run();
58
- executor.getNarrative(); // causal trace with decision evidence
59
- ```
60
-
61
- ### TypedScope $-methods (escape hatches)
62
-
63
- ```typescript
64
- scope.$getArgs<T>() // frozen readonly input
65
- scope.$getEnv() // execution environment (signal, timeoutMs, traceId)
66
- scope.$break() // stop pipeline
67
- scope.$debug(key, value) // debug info
68
- scope.$metric(name, value) // metrics
69
- ```
70
-
71
- ### decide() / select()
72
-
73
- ```typescript
74
- // Filter syntax — captures operators + thresholds
75
- decide(scope, [
76
- { when: { creditScore: { gt: 700 }, dti: { lt: 0.43 } }, then: 'approved', label: 'Good credit' },
77
- ], 'rejected');
78
-
79
- // Function syntax — captures which keys were read
80
- decide(scope, [
81
- { when: (s) => s.creditScore > 700, then: 'approved' },
82
- ], 'rejected');
83
-
84
- // select() — all matching branches (not first-match)
85
- select(scope, [
86
- { when: { glucose: { gt: 100 } }, then: 'diabetes' },
87
- { when: { bmi: { gt: 30 } }, then: 'obesity' },
88
- ]);
89
- ```
90
-
91
- ### Executor
92
-
93
- ```typescript
94
- const executor = new FlowChartExecutor(chart);
95
- await executor.run({ input, env: { traceId: 'req-123' } });
96
- executor.getNarrative() // string[]
97
- executor.getNarrativeEntries() // CombinedNarrativeEntry[]
98
- executor.getSnapshot() // memory state
99
- executor.attachRecorder(recorder) // scope observer
100
- executor.attachFlowRecorder(r) // flow observer
101
- executor.setRedactionPolicy({ keys, patterns, fields })
102
- ```
103
-
104
- ## Observer Systems
105
-
106
- - **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
107
- - **FlowRecorder**: fires AFTER stage (`onStageExecuted`, `onDecision`, `onFork`, `onLoop`)
108
- - 8 built-in FlowRecorder strategies
109
- - Narrative via `executor.recorder(narrative())` at runtime
110
-
111
- ## Rules
112
-
113
- - Use `flowChart<T>()` — scopeFactory is auto-embedded
114
- - Use `decide()` / `select()` in decider/selector functions
115
- - Use typed property access (not getValue/setValue)
116
- - Use `$getArgs()` for input, `$getEnv()` for environment
117
- - Never post-process the tree — use recorders
118
- - Use `.recorder(narrative())` at runtime for narrative setup
@@ -1,118 +0,0 @@
1
- # footprint.js — Cursor Rules
2
-
3
- This is the footprint.js library — the flowchart pattern for backend code. Self-explainable systems that AI can reason about.
4
-
5
- ## Core Principle
6
-
7
- **Collect during traversal, never post-process.** All data collection happens as side effects of the single DFS traversal. Never walk the tree after execution.
8
-
9
- ## Architecture
10
-
11
- ```
12
- src/lib/
13
- ├── memory/ → Transactional state (SharedMemory, StageContext, TransactionBuffer)
14
- ├── schema/ → Validation (Zod optional, duck-typed)
15
- ├── builder/ → Fluent DSL (FlowChartBuilder, flowChart())
16
- ├── scope/ → Per-stage facades + recorders + providers
17
- ├── reactive/ → TypedScope<T> deep Proxy (typed property access, $-methods)
18
- ├── decide/ → decide()/select() decision evidence capture
19
- ├── engine/ → DFS traversal + narrative + handlers
20
- ├── runner/ → FlowChartExecutor
21
- └── contract/ → I/O schema + OpenAPI
22
- ```
23
-
24
- Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
25
-
26
- ## Key API — TypedScope (Recommended)
27
-
28
- ```typescript
29
- import { flowChart, FlowChartExecutor, decide } from 'footprintjs';
30
-
31
- interface State {
32
- creditScore: number;
33
- riskTier: string;
34
- decision?: string;
35
- }
36
-
37
- const chart = flowChart<State>('Intake', async (scope) => {
38
- scope.creditScore = 750; // typed write (no setValue needed)
39
- scope.riskTier = 'low'; // typed write
40
- }, 'intake')
41
- .addDeciderFunction('Route', (scope) => {
42
- return decide(scope, [
43
- { when: { riskTier: { eq: 'low' } }, then: 'approved', label: 'Low risk' },
44
- ], 'rejected');
45
- }, 'route', 'Route based on risk')
46
- .addFunctionBranch('approved', 'Approve', async (scope) => {
47
- scope.decision = 'Approved';
48
- })
49
- .addFunctionBranch('rejected', 'Reject', async (scope) => {
50
- scope.decision = 'Rejected';
51
- })
52
- .setDefault('rejected')
53
- .end()
54
- .build();
55
-
56
- const executor = new FlowChartExecutor(chart);
57
- await executor.run();
58
- executor.getNarrative(); // causal trace with decision evidence
59
- ```
60
-
61
- ### TypedScope $-methods (escape hatches)
62
-
63
- ```typescript
64
- scope.$getArgs<T>() // frozen readonly input
65
- scope.$getEnv() // execution environment (signal, timeoutMs, traceId)
66
- scope.$break() // stop pipeline
67
- scope.$debug(key, value) // debug info
68
- scope.$metric(name, value) // metrics
69
- ```
70
-
71
- ### decide() / select()
72
-
73
- ```typescript
74
- // Filter syntax — captures operators + thresholds
75
- decide(scope, [
76
- { when: { creditScore: { gt: 700 }, dti: { lt: 0.43 } }, then: 'approved', label: 'Good credit' },
77
- ], 'rejected');
78
-
79
- // Function syntax — captures which keys were read
80
- decide(scope, [
81
- { when: (s) => s.creditScore > 700, then: 'approved' },
82
- ], 'rejected');
83
-
84
- // select() — all matching branches (not first-match)
85
- select(scope, [
86
- { when: { glucose: { gt: 100 } }, then: 'diabetes' },
87
- { when: { bmi: { gt: 30 } }, then: 'obesity' },
88
- ]);
89
- ```
90
-
91
- ### Executor
92
-
93
- ```typescript
94
- const executor = new FlowChartExecutor(chart);
95
- await executor.run({ input, env: { traceId: 'req-123' } });
96
- executor.getNarrative() // string[]
97
- executor.getNarrativeEntries() // CombinedNarrativeEntry[]
98
- executor.getSnapshot() // memory state
99
- executor.attachRecorder(recorder) // scope observer
100
- executor.attachFlowRecorder(r) // flow observer
101
- executor.setRedactionPolicy({ keys, patterns, fields })
102
- ```
103
-
104
- ## Observer Systems
105
-
106
- - **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
107
- - **FlowRecorder**: fires AFTER stage (`onStageExecuted`, `onDecision`, `onFork`, `onLoop`)
108
- - 8 built-in FlowRecorder strategies
109
- - Narrative via `executor.recorder(narrative())` at runtime
110
-
111
- ## Rules
112
-
113
- - Use `flowChart<T>()` — scopeFactory is auto-embedded
114
- - Use `decide()` / `select()` in decider/selector functions
115
- - Use typed property access (not getValue/setValue)
116
- - Use `$getArgs()` for input, `$getEnv()` for environment
117
- - Never post-process the tree — use recorders
118
- - Use `.recorder(narrative())` at runtime for narrative setup
@@ -1,118 +0,0 @@
1
- # footprint.js — Kiro Rules
2
-
3
- This is the footprint.js library — the flowchart pattern for backend code. Self-explainable systems that AI can reason about.
4
-
5
- ## Core Principle
6
-
7
- **Collect during traversal, never post-process.** All data collection happens as side effects of the single DFS traversal. Never walk the tree after execution.
8
-
9
- ## Architecture
10
-
11
- ```
12
- src/lib/
13
- ├── memory/ → Transactional state (SharedMemory, StageContext, TransactionBuffer)
14
- ├── schema/ → Validation (Zod optional, duck-typed)
15
- ├── builder/ → Fluent DSL (FlowChartBuilder, flowChart())
16
- ├── scope/ → Per-stage facades + recorders + providers
17
- ├── reactive/ → TypedScope<T> deep Proxy (typed property access, $-methods)
18
- ├── decide/ → decide()/select() decision evidence capture
19
- ├── engine/ → DFS traversal + narrative + handlers
20
- ├── runner/ → FlowChartExecutor
21
- └── contract/ → I/O schema + OpenAPI
22
- ```
23
-
24
- Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
25
-
26
- ## Key API — TypedScope (Recommended)
27
-
28
- ```typescript
29
- import { flowChart, FlowChartExecutor, decide } from 'footprintjs';
30
-
31
- interface State {
32
- creditScore: number;
33
- riskTier: string;
34
- decision?: string;
35
- }
36
-
37
- const chart = flowChart<State>('Intake', async (scope) => {
38
- scope.creditScore = 750; // typed write (no setValue needed)
39
- scope.riskTier = 'low'; // typed write
40
- }, 'intake')
41
- .addDeciderFunction('Route', (scope) => {
42
- return decide(scope, [
43
- { when: { riskTier: { eq: 'low' } }, then: 'approved', label: 'Low risk' },
44
- ], 'rejected');
45
- }, 'route', 'Route based on risk')
46
- .addFunctionBranch('approved', 'Approve', async (scope) => {
47
- scope.decision = 'Approved';
48
- })
49
- .addFunctionBranch('rejected', 'Reject', async (scope) => {
50
- scope.decision = 'Rejected';
51
- })
52
- .setDefault('rejected')
53
- .end()
54
- .build();
55
-
56
- const executor = new FlowChartExecutor(chart);
57
- await executor.run();
58
- executor.getNarrative(); // causal trace with decision evidence
59
- ```
60
-
61
- ### TypedScope $-methods (escape hatches)
62
-
63
- ```typescript
64
- scope.$getArgs<T>() // frozen readonly input
65
- scope.$getEnv() // execution environment (signal, timeoutMs, traceId)
66
- scope.$break() // stop pipeline
67
- scope.$debug(key, value) // debug info
68
- scope.$metric(name, value) // metrics
69
- ```
70
-
71
- ### decide() / select()
72
-
73
- ```typescript
74
- // Filter syntax — captures operators + thresholds
75
- decide(scope, [
76
- { when: { creditScore: { gt: 700 }, dti: { lt: 0.43 } }, then: 'approved', label: 'Good credit' },
77
- ], 'rejected');
78
-
79
- // Function syntax — captures which keys were read
80
- decide(scope, [
81
- { when: (s) => s.creditScore > 700, then: 'approved' },
82
- ], 'rejected');
83
-
84
- // select() — all matching branches (not first-match)
85
- select(scope, [
86
- { when: { glucose: { gt: 100 } }, then: 'diabetes' },
87
- { when: { bmi: { gt: 30 } }, then: 'obesity' },
88
- ]);
89
- ```
90
-
91
- ### Executor
92
-
93
- ```typescript
94
- const executor = new FlowChartExecutor(chart);
95
- await executor.run({ input, env: { traceId: 'req-123' } });
96
- executor.getNarrative() // string[]
97
- executor.getNarrativeEntries() // CombinedNarrativeEntry[]
98
- executor.getSnapshot() // memory state
99
- executor.attachRecorder(recorder) // scope observer
100
- executor.attachFlowRecorder(r) // flow observer
101
- executor.setRedactionPolicy({ keys, patterns, fields })
102
- ```
103
-
104
- ## Observer Systems
105
-
106
- - **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
107
- - **FlowRecorder**: fires AFTER stage (`onStageExecuted`, `onDecision`, `onFork`, `onLoop`)
108
- - 8 built-in FlowRecorder strategies
109
- - Narrative via `executor.recorder(narrative())` at runtime
110
-
111
- ## Rules
112
-
113
- - Use `flowChart<T>()` — scopeFactory is auto-embedded
114
- - Use `decide()` / `select()` in decider/selector functions
115
- - Use typed property access (not getValue/setValue)
116
- - Use `$getArgs()` for input, `$getEnv()` for environment
117
- - Never post-process the tree — use recorders
118
- - Use `.recorder(narrative())` at runtime for narrative setup
@@ -1,140 +0,0 @@
1
- #!/usr/bin/env bash
2
- # footprint.js — AI coding tool setup
3
- # Installs instruction files for your preferred AI coding assistant.
4
- #
5
- # Usage:
6
- # npx footprintjs-setup (after npm install footprintjs)
7
- # bash node_modules/footprintjs/ai-instructions/setup.sh
8
-
9
- set -euo pipefail
10
-
11
- PKG_DIR="$(cd "$(dirname "$0")" && pwd)"
12
- PROJECT_DIR="$(pwd)"
13
-
14
- echo ""
15
- echo " footprint.js — AI Coding Tool Setup"
16
- echo " ──────────────────────────────────────"
17
- echo ""
18
- echo " This will copy instruction files so your AI coding"
19
- echo " assistant understands the footprint.js API."
20
- echo ""
21
-
22
- install_claude_code() {
23
- mkdir -p "$PROJECT_DIR/.claude/skills/footprint"
24
- cp "$PKG_DIR/claude-code/SKILL.md" "$PROJECT_DIR/.claude/skills/footprint/SKILL.md"
25
- # Also copy CLAUDE.md to project root if not present
26
- if [ ! -f "$PROJECT_DIR/CLAUDE.md" ]; then
27
- cp "$PKG_DIR/../CLAUDE.md" "$PROJECT_DIR/CLAUDE.md" 2>/dev/null || true
28
- fi
29
- echo " [ok] Claude Code — .claude/skills/footprint/SKILL.md"
30
- }
31
-
32
- install_codex() {
33
- if [ ! -f "$PROJECT_DIR/AGENTS.md" ]; then
34
- cp "$PKG_DIR/../AGENTS.md" "$PROJECT_DIR/AGENTS.md" 2>/dev/null || true
35
- echo " [ok] OpenAI Codex — AGENTS.md"
36
- else
37
- echo " [skip] AGENTS.md already exists"
38
- fi
39
- }
40
-
41
- install_copilot() {
42
- mkdir -p "$PROJECT_DIR/.github"
43
- if [ ! -f "$PROJECT_DIR/.github/copilot-instructions.md" ]; then
44
- cp "$PKG_DIR/copilot-instructions.md" "$PROJECT_DIR/.github/copilot-instructions.md"
45
- echo " [ok] GitHub Copilot — .github/copilot-instructions.md"
46
- else
47
- echo " [skip] .github/copilot-instructions.md already exists"
48
- fi
49
- }
50
-
51
- install_cursor() {
52
- mkdir -p "$PROJECT_DIR/.cursor/rules"
53
- cp "$PKG_DIR/cursor/footprint.md" "$PROJECT_DIR/.cursor/rules/footprint.md"
54
- echo " [ok] Cursor — .cursor/rules/footprint.md"
55
- }
56
-
57
- install_windsurf() {
58
- if [ ! -f "$PROJECT_DIR/.windsurfrules" ]; then
59
- cp "$PKG_DIR/windsurfrules" "$PROJECT_DIR/.windsurfrules"
60
- echo " [ok] Windsurf — .windsurfrules"
61
- else
62
- # Append footprint rules to existing file
63
- echo "" >> "$PROJECT_DIR/.windsurfrules"
64
- cat "$PKG_DIR/windsurfrules" >> "$PROJECT_DIR/.windsurfrules"
65
- echo " [ok] Windsurf — appended to .windsurfrules"
66
- fi
67
- }
68
-
69
- install_cline() {
70
- if [ ! -f "$PROJECT_DIR/.clinerules" ]; then
71
- cp "$PKG_DIR/clinerules" "$PROJECT_DIR/.clinerules"
72
- echo " [ok] Cline — .clinerules"
73
- else
74
- echo "" >> "$PROJECT_DIR/.clinerules"
75
- cat "$PKG_DIR/clinerules" >> "$PROJECT_DIR/.clinerules"
76
- echo " [ok] Cline — appended to .clinerules"
77
- fi
78
- }
79
-
80
- install_kiro() {
81
- mkdir -p "$PROJECT_DIR/.kiro/rules"
82
- cp "$PKG_DIR/kiro/footprint.md" "$PROJECT_DIR/.kiro/rules/footprint.md"
83
- echo " [ok] Kiro — .kiro/rules/footprint.md"
84
- }
85
-
86
- install_all() {
87
- install_claude_code
88
- install_codex
89
- install_copilot
90
- install_cursor
91
- install_windsurf
92
- install_cline
93
- install_kiro
94
- }
95
-
96
- # Interactive menu
97
- echo " Which tool(s) do you use?"
98
- echo ""
99
- echo " 1) Claude Code"
100
- echo " 2) OpenAI Codex"
101
- echo " 3) GitHub Copilot"
102
- echo " 4) Cursor"
103
- echo " 5) Windsurf"
104
- echo " 6) Cline"
105
- echo " 7) Kiro"
106
- echo " a) All of the above"
107
- echo " q) Quit"
108
- echo ""
109
- read -rp " Choose (e.g. 1,4 or a): " choice
110
-
111
- echo ""
112
-
113
- if [[ "$choice" == "q" ]]; then
114
- echo " Cancelled."
115
- exit 0
116
- fi
117
-
118
- if [[ "$choice" == "a" || "$choice" == "A" ]]; then
119
- install_all
120
- else
121
- IFS=',' read -ra selections <<< "$choice"
122
- for sel in "${selections[@]}"; do
123
- sel="$(echo "$sel" | tr -d ' ')"
124
- case "$sel" in
125
- 1) install_claude_code ;;
126
- 2) install_codex ;;
127
- 3) install_copilot ;;
128
- 4) install_cursor ;;
129
- 5) install_windsurf ;;
130
- 6) install_cline ;;
131
- 7) install_kiro ;;
132
- *) echo " [?] Unknown option: $sel" ;;
133
- esac
134
- done
135
- fi
136
-
137
- echo ""
138
- echo " Done! Your AI assistant now knows the footprint.js API."
139
- echo " Add the generated files to .gitignore if you don't want them in version control."
140
- echo ""
@@ -1,119 +0,0 @@
1
- # footprint.js
2
-
3
- This is the footprint.js library — the flowchart pattern for backend code. Self-explainable systems that AI can reason about.
4
-
5
- ## Core Principle
6
-
7
- **Collect during traversal, never post-process.** All data collection happens as side effects of the single DFS traversal. Never walk the tree after execution.
8
-
9
- ## Architecture
10
-
11
- ```
12
- src/lib/
13
- ├── memory/ → Transactional state (SharedMemory, StageContext, TransactionBuffer)
14
- ├── schema/ → Validation (Zod optional, duck-typed)
15
- ├── builder/ → Fluent DSL (FlowChartBuilder, flowChart(), typedFlowChart())
16
- ├── scope/ → Per-stage facades + recorders + providers
17
- ├── reactive/ → TypedScope<T> deep Proxy (typed property access, $-methods)
18
- ├── decide/ → decide()/select() decision evidence capture
19
- ├── engine/ → DFS traversal + narrative + handlers
20
- ├── runner/ → FlowChartExecutor
21
- └── contract/ → I/O schema + OpenAPI
22
- ```
23
-
24
- Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
25
-
26
- ## Key API — TypedScope (Recommended)
27
-
28
- ```typescript
29
- import { typedFlowChart, FlowChartExecutor, decide } from 'footprintjs';
30
-
31
- interface State {
32
- creditScore: number;
33
- riskTier: string;
34
- decision?: string;
35
- }
36
-
37
- const chart = typedFlowChart<State>('Intake', async (scope) => {
38
- scope.creditScore = 750; // typed write (no setValue needed)
39
- scope.riskTier = 'low'; // typed write
40
- }, 'intake')
41
- .setEnableNarrative()
42
- .addDeciderFunction('Route', (scope) => {
43
- return decide(scope, [
44
- { when: { riskTier: { eq: 'low' } }, then: 'approved', label: 'Low risk' },
45
- ], 'rejected');
46
- }, 'route', 'Route based on risk')
47
- .addFunctionBranch('approved', 'Approve', async (scope) => {
48
- scope.decision = 'Approved';
49
- })
50
- .addFunctionBranch('rejected', 'Reject', async (scope) => {
51
- scope.decision = 'Rejected';
52
- })
53
- .setDefault('rejected')
54
- .end()
55
- .build();
56
-
57
- const executor = new FlowChartExecutor(chart<State>());
58
- await executor.run();
59
- executor.getNarrative(); // causal trace with decision evidence
60
- ```
61
-
62
- ### TypedScope $-methods (escape hatches)
63
-
64
- ```typescript
65
- scope.$getArgs<T>() // frozen readonly input
66
- scope.$getEnv() // execution environment (signal, timeoutMs, traceId)
67
- scope.$break() // stop pipeline
68
- scope.$debug(key, value) // debug info
69
- scope.$metric(name, value) // metrics
70
- ```
71
-
72
- ### decide() / select()
73
-
74
- ```typescript
75
- // Filter syntax — captures operators + thresholds
76
- decide(scope, [
77
- { when: { creditScore: { gt: 700 }, dti: { lt: 0.43 } }, then: 'approved', label: 'Good credit' },
78
- ], 'rejected');
79
-
80
- // Function syntax — captures which keys were read
81
- decide(scope, [
82
- { when: (s) => s.creditScore > 700, then: 'approved' },
83
- ], 'rejected');
84
-
85
- // select() — all matching branches (not first-match)
86
- select(scope, [
87
- { when: { glucose: { gt: 100 } }, then: 'diabetes' },
88
- { when: { bmi: { gt: 30 } }, then: 'obesity' },
89
- ]);
90
- ```
91
-
92
- ### Executor
93
-
94
- ```typescript
95
- const executor = new FlowChartExecutor(chart<State>());
96
- await executor.run({ input, env: { traceId: 'req-123' } });
97
- executor.getNarrative() // string[]
98
- executor.getNarrativeEntries() // CombinedNarrativeEntry[]
99
- executor.getSnapshot() // memory state
100
- executor.attachRecorder(recorder) // scope observer
101
- executor.attachFlowRecorder(r) // flow observer
102
- executor.setRedactionPolicy({ keys, patterns, fields })
103
- ```
104
-
105
- ## Observer Systems
106
-
107
- - **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
108
- - **FlowRecorder**: fires AFTER stage (`onStageExecuted`, `onDecision`, `onFork`, `onLoop`)
109
- - 8 built-in FlowRecorder strategies
110
- - `setEnableNarrative()` auto-attaches `CombinedNarrativeRecorder`
111
-
112
- ## Rules
113
-
114
- - Use `typedFlowChart<T>()` — scopeFactory is auto-embedded, no `createTypedScopeFactory` needed
115
- - Use `decide()` / `select()` in decider/selector functions
116
- - Use typed property access (not getValue/setValue)
117
- - Use `$getArgs()` for input, `$getEnv()` for environment
118
- - Never post-process the tree — use recorders
119
- - `setEnableNarrative()` is all you need for narrative setup