footprintjs 1.0.0 → 2.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 (43) hide show
  1. package/AGENTS.md +81 -87
  2. package/README.md +2 -2
  3. package/ai-instructions/claude-code/SKILL.md +204 -91
  4. package/ai-instructions/clinerules +71 -31
  5. package/ai-instructions/copilot-instructions.md +67 -34
  6. package/ai-instructions/cursor/footprint.md +73 -34
  7. package/ai-instructions/kiro/footprint.md +70 -30
  8. package/ai-instructions/windsurfrules +71 -31
  9. package/dist/esm/index.js +8 -11
  10. package/dist/esm/lib/builder/FlowChartBuilder.js +26 -2
  11. package/dist/esm/lib/builder/typedFlowChart.js +4 -2
  12. package/dist/esm/lib/builder/types.js +1 -1
  13. package/dist/esm/lib/engine/types.js +1 -1
  14. package/dist/esm/lib/reactive/createTypedScope.js +28 -6
  15. package/dist/esm/lib/runner/FlowChartExecutor.js +13 -3
  16. package/dist/esm/lib/runner/RunContext.js +75 -0
  17. package/dist/esm/lib/runner/RunnableChart.js +104 -0
  18. package/dist/esm/lib/runner/index.js +2 -1
  19. package/dist/esm/lib/scope/ScopeFacade.js +12 -4
  20. package/dist/esm/recorders.js +69 -0
  21. package/dist/index.js +43 -45
  22. package/dist/lib/builder/FlowChartBuilder.js +26 -2
  23. package/dist/lib/builder/typedFlowChart.js +4 -2
  24. package/dist/lib/builder/types.js +1 -1
  25. package/dist/lib/engine/types.js +1 -1
  26. package/dist/lib/reactive/createTypedScope.js +28 -6
  27. package/dist/lib/runner/FlowChartExecutor.js +13 -3
  28. package/dist/lib/runner/RunContext.js +79 -0
  29. package/dist/lib/runner/RunnableChart.js +108 -0
  30. package/dist/lib/runner/index.js +4 -2
  31. package/dist/lib/scope/ScopeFacade.js +12 -4
  32. package/dist/recorders.js +79 -0
  33. package/dist/types/index.d.ts +8 -6
  34. package/dist/types/lib/builder/FlowChartBuilder.d.ts +18 -1
  35. package/dist/types/lib/builder/types.d.ts +5 -3
  36. package/dist/types/lib/engine/types.d.ts +2 -0
  37. package/dist/types/lib/runner/FlowChartExecutor.d.ts +9 -0
  38. package/dist/types/lib/runner/RunContext.d.ts +38 -0
  39. package/dist/types/lib/runner/RunnableChart.d.ts +42 -0
  40. package/dist/types/lib/runner/index.d.ts +3 -0
  41. package/dist/types/lib/scope/ScopeFacade.d.ts +11 -3
  42. package/dist/types/recorders.d.ts +43 -0
  43. package/package.json +6 -1
@@ -1,10 +1,6 @@
1
1
  # footprint.js — Copilot Instructions
2
2
 
3
- This is the footprint.js library — the flowchart pattern for backend code.
4
-
5
- ## What It Does
6
-
7
- Structures backend logic as a graph of named functions with transactional state. Every run auto-generates a causal trace of what happened and why. LLMs read the trace for grounded explanations — no hallucination.
3
+ This is the footprint.js library — the flowchart pattern for backend code. Self-explainable systems that AI can reason about.
8
4
 
9
5
  ## Core Principle
10
6
 
@@ -16,8 +12,10 @@ Structures backend logic as a graph of named functions with transactional state.
16
12
  src/lib/
17
13
  ├── memory/ → Transactional state (SharedMemory, StageContext, TransactionBuffer)
18
14
  ├── schema/ → Validation (Zod optional, duck-typed)
19
- ├── builder/ → Fluent DSL (FlowChartBuilder, flowChart())
15
+ ├── builder/ → Fluent DSL (FlowChartBuilder, flowChart(), typedFlowChart())
20
16
  ├── scope/ → Per-stage facades + recorders + providers
17
+ ├── reactive/ → TypedScope<T> deep Proxy (typed property access, $-methods)
18
+ ├── decide/ → decide()/select() decision evidence capture
21
19
  ├── engine/ → DFS traversal + narrative + handlers
22
20
  ├── runner/ → FlowChartExecutor
23
21
  └── contract/ → I/O schema + OpenAPI
@@ -25,49 +23,82 @@ src/lib/
25
23
 
26
24
  Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
27
25
 
28
- ## Key API
29
-
30
- ### Builder Chain
26
+ ## Key API — TypedScope (Recommended)
31
27
 
32
28
  ```typescript
33
- flowChart(name, fn, id, extractor?, description?)
34
- .addFunction(name, fn, id, description?)
35
- .addDeciderFunction(name, fn, id, description?)
36
- .addFunctionBranch(branchId, name, fn) / .setDefault(id) / .end()
37
- .addSelectorFunction(name, fn, id, description?)
38
- .addListOfFunction([...], { failFast? })
39
- .addSubFlowChartNext(id, subflow, mount, { inputMapper?, outputMapper? })
40
- .loopTo(stageId)
29
+ import { typedFlowChart, createTypedScopeFactory, 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
41
  .setEnableNarrative()
42
- .setInputSchema(schema) / .setOutputSchema(schema) / .setOutputMapper(fn)
43
- .build() / .toSpec() / .toMermaid()
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, createTypedScopeFactory<State>());
58
+ await executor.run();
59
+ executor.getNarrative(); // causal trace with decision evidence
44
60
  ```
45
61
 
46
- ### Stage Functions
62
+ ### TypedScope $-methods (escape hatches)
47
63
 
48
64
  ```typescript
49
- (scope: ScopeFacade, breakPipeline: () => void, streamCallback?: StreamCallback) => void | Promise<void>
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
50
70
  ```
51
71
 
52
- ### ScopeFacade
72
+ ### decide() / select()
53
73
 
54
74
  ```typescript
55
- scope.getValue('key') // tracked read
56
- scope.setValue('key', value) // tracked write
57
- scope.updateValue('key', partial) // deep merge
58
- scope.deleteValue('key') // tracked delete
59
- scope.getArgs<T>() // frozen readonly input (NOT tracked)
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
+ ]);
60
90
  ```
61
91
 
62
92
  ### Executor
63
93
 
64
94
  ```typescript
65
- const executor = new FlowChartExecutor(chart);
66
- await executor.run({ input, timeoutMs?, signal? });
95
+ const executor = new FlowChartExecutor(chart, createTypedScopeFactory<State>());
96
+ await executor.run({ input, env: { traceId: 'req-123' } });
67
97
  executor.getNarrative() // string[]
68
98
  executor.getNarrativeEntries() // CombinedNarrativeEntry[]
69
99
  executor.getSnapshot() // memory state
70
- executor.attachFlowRecorder(r) // plug observer
100
+ executor.attachRecorder(recorder) // scope observer
101
+ executor.attachFlowRecorder(r) // flow observer
71
102
  executor.setRedactionPolicy({ keys, patterns, fields })
72
103
  ```
73
104
 
@@ -75,12 +106,14 @@ executor.setRedactionPolicy({ keys, patterns, fields })
75
106
 
76
107
  - **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
77
108
  - **FlowRecorder**: fires AFTER stage (`onStageExecuted`, `onDecision`, `onFork`, `onLoop`)
78
- - 8 built-in FlowRecorder strategies (Narrative, Adaptive, Windowed, RLE, Milestone, Progressive, Separate, Manifest)
79
- - `setEnableNarrative()` auto-attaches `CombinedNarrativeRecorder` (implements both)
109
+ - 8 built-in FlowRecorder strategies
110
+ - `setEnableNarrative()` auto-attaches `CombinedNarrativeRecorder`
80
111
 
81
112
  ## Rules
82
113
 
114
+ - Use `typedFlowChart<T>()` + `createTypedScopeFactory<T>()` (not flowChart + ScopeFacade)
115
+ - Use `decide()` / `select()` in decider/selector functions
116
+ - Use typed property access (not getValue/setValue)
117
+ - Use `$getArgs()` for input, `$getEnv()` for environment
83
118
  - Never post-process the tree — use recorders
84
- - `getValue()`/`setValue()` for tracked state; `getArgs()` for frozen readonly input
85
- - Don't use deprecated `CombinedNarrativeBuilder`
86
119
  - `setEnableNarrative()` is all you need for narrative setup
@@ -12,8 +12,10 @@ This is the footprint.js library — the flowchart pattern for backend code. Sel
12
12
  src/lib/
13
13
  ├── memory/ → Transactional state (SharedMemory, StageContext, TransactionBuffer)
14
14
  ├── schema/ → Validation (Zod optional, duck-typed)
15
- ├── builder/ → Fluent DSL (FlowChartBuilder, flowChart())
15
+ ├── builder/ → Fluent DSL (FlowChartBuilder, flowChart(), typedFlowChart())
16
16
  ├── scope/ → Per-stage facades + recorders + providers
17
+ ├── reactive/ → TypedScope<T> deep Proxy (typed property access, $-methods)
18
+ ├── decide/ → decide()/select() decision evidence capture
17
19
  ├── engine/ → DFS traversal + narrative + handlers
18
20
  ├── runner/ → FlowChartExecutor
19
21
  └── contract/ → I/O schema + OpenAPI
@@ -21,60 +23,97 @@ src/lib/
21
23
 
22
24
  Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
23
25
 
24
- ## Builder API
26
+ ## Key API — TypedScope (Recommended)
25
27
 
26
28
  ```typescript
27
- flowChart(name, fn, id, extractor?, description?)
28
- .addFunction(name, fn, id, description?)
29
- .addDeciderFunction(name, fn, id, description?)
30
- .addFunctionBranch(branchId, name, fn) / .setDefault(id) / .end()
31
- .addSelectorFunction(name, fn, id, description?)
32
- .addListOfFunction([...], { failFast? })
33
- .addSubFlowChartNext(id, subflow, mount, { inputMapper?, outputMapper? })
34
- .loopTo(stageId)
29
+ import { typedFlowChart, createTypedScopeFactory, 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')
35
41
  .setEnableNarrative()
36
- .build() / .toSpec() / .toMermaid()
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, createTypedScopeFactory<State>());
58
+ await executor.run();
59
+ executor.getNarrative(); // causal trace with decision evidence
37
60
  ```
38
61
 
39
- ## Stage Function Signature
62
+ ### TypedScope $-methods (escape hatches)
40
63
 
41
64
  ```typescript
42
- (scope: ScopeFacade, breakPipeline: () => void, streamCallback?: StreamCallback) => void | Promise<void>
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
43
70
  ```
44
71
 
45
- ## ScopeFacade
72
+ ### decide() / select()
46
73
 
47
74
  ```typescript
48
- scope.getValue('key') // tracked read narrative
49
- scope.setValue('key', value) // tracked write → narrative
50
- scope.updateValue('key', partial) // deep merge (tracked)
51
- scope.deleteValue('key') // tracked delete
52
- scope.getArgs<T>() // frozen readonly input (NOT tracked)
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
+ ]);
53
90
  ```
54
91
 
55
- ## Executor
92
+ ### Executor
56
93
 
57
94
  ```typescript
58
- const executor = new FlowChartExecutor(chart);
59
- await executor.run({ input, timeoutMs?, signal? });
60
- executor.getNarrative() // combined flow + data
61
- executor.getNarrativeEntries() // structured entries
95
+ const executor = new FlowChartExecutor(chart, createTypedScopeFactory<State>());
96
+ await executor.run({ input, env: { traceId: 'req-123' } });
97
+ executor.getNarrative() // string[]
98
+ executor.getNarrativeEntries() // CombinedNarrativeEntry[]
62
99
  executor.getSnapshot() // memory state
63
- executor.attachFlowRecorder(r) // plug flow observer
100
+ executor.attachRecorder(recorder) // scope observer
101
+ executor.attachFlowRecorder(r) // flow observer
64
102
  executor.setRedactionPolicy({ keys, patterns, fields })
65
103
  ```
66
104
 
67
- ## Two Observer Systems (intentionally separate)
105
+ ## Observer Systems
68
106
 
69
- - **Scope Recorder**: `onRead`, `onWrite`, `onCommit`, `onStageStart/End` — fires DURING execution
70
- - **FlowRecorder**: `onStageExecuted`, `onDecision`, `onFork`, `onNext`, `onLoop` — fires AFTER stage
71
- - 8 strategies: Narrative, Adaptive, Windowed, RLE, Milestone, Progressive, Separate, Manifest
72
- - `CombinedNarrativeRecorder` implements both — auto-attached by `setEnableNarrative()`
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`
73
111
 
74
112
  ## Rules
75
113
 
114
+ - Use `typedFlowChart<T>()` + `createTypedScopeFactory<T>()` (not flowChart + ScopeFacade)
115
+ - Use `decide()` / `select()` in decider/selector functions
116
+ - Use typed property access (not getValue/setValue)
117
+ - Use `$getArgs()` for input, `$getEnv()` for environment
76
118
  - Never post-process the tree — use recorders
77
- - `getValue()`/`setValue()` for tracked state; `getArgs()` for frozen readonly input
78
- - Don't use deprecated `CombinedNarrativeBuilder` — use `CombinedNarrativeRecorder`
79
- - Don't extract shared base for Recorder/FlowRecorder — coincidence, not pattern
80
- - `setEnableNarrative()` is all you need
119
+ - `setEnableNarrative()` is all you need for narrative setup
@@ -12,8 +12,10 @@ This is the footprint.js library — the flowchart pattern for backend code. Sel
12
12
  src/lib/
13
13
  ├── memory/ → Transactional state (SharedMemory, StageContext, TransactionBuffer)
14
14
  ├── schema/ → Validation (Zod optional, duck-typed)
15
- ├── builder/ → Fluent DSL (FlowChartBuilder, flowChart())
15
+ ├── builder/ → Fluent DSL (FlowChartBuilder, flowChart(), typedFlowChart())
16
16
  ├── scope/ → Per-stage facades + recorders + providers
17
+ ├── reactive/ → TypedScope<T> deep Proxy (typed property access, $-methods)
18
+ ├── decide/ → decide()/select() decision evidence capture
17
19
  ├── engine/ → DFS traversal + narrative + handlers
18
20
  ├── runner/ → FlowChartExecutor
19
21
  └── contract/ → I/O schema + OpenAPI
@@ -21,46 +23,82 @@ src/lib/
21
23
 
22
24
  Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
23
25
 
24
- ## Builder API
26
+ ## Key API — TypedScope (Recommended)
25
27
 
26
28
  ```typescript
27
- flowChart(name, fn, id, extractor?, description?)
28
- .addFunction(name, fn, id, description?)
29
- .addDeciderFunction(name, fn, id, description?)
30
- .addFunctionBranch(branchId, name, fn) / .setDefault(id) / .end()
31
- .addSelectorFunction(name, fn, id, description?)
32
- .addListOfFunction([...], { failFast? })
33
- .addSubFlowChartNext(id, subflow, mount, { inputMapper?, outputMapper? })
34
- .loopTo(stageId)
29
+ import { typedFlowChart, createTypedScopeFactory, 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')
35
41
  .setEnableNarrative()
36
- .build() / .toSpec() / .toMermaid()
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, createTypedScopeFactory<State>());
58
+ await executor.run();
59
+ executor.getNarrative(); // causal trace with decision evidence
37
60
  ```
38
61
 
39
- ## Stage Functions
62
+ ### TypedScope $-methods (escape hatches)
40
63
 
41
64
  ```typescript
42
- (scope: ScopeFacade, breakPipeline: () => void, streamCallback?: StreamCallback) => void | Promise<void>
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
43
70
  ```
44
71
 
45
- ## ScopeFacade
72
+ ### decide() / select()
46
73
 
47
74
  ```typescript
48
- scope.getValue('key') // tracked read narrative
49
- scope.setValue('key', value) // tracked write → narrative
50
- scope.updateValue('key', partial) // deep merge (tracked)
51
- scope.deleteValue('key') // tracked delete
52
- scope.getArgs<T>() // frozen readonly input (NOT tracked)
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
+ ]);
53
90
  ```
54
91
 
55
- ## Executor
92
+ ### Executor
56
93
 
57
94
  ```typescript
58
- const executor = new FlowChartExecutor(chart);
59
- await executor.run({ input, timeoutMs?, signal? });
60
- executor.getNarrative() // combined flow + data
61
- executor.getNarrativeEntries() // structured entries
95
+ const executor = new FlowChartExecutor(chart, createTypedScopeFactory<State>());
96
+ await executor.run({ input, env: { traceId: 'req-123' } });
97
+ executor.getNarrative() // string[]
98
+ executor.getNarrativeEntries() // CombinedNarrativeEntry[]
62
99
  executor.getSnapshot() // memory state
63
- executor.attachFlowRecorder(r) // plug flow observer
100
+ executor.attachRecorder(recorder) // scope observer
101
+ executor.attachFlowRecorder(r) // flow observer
64
102
  executor.setRedactionPolicy({ keys, patterns, fields })
65
103
  ```
66
104
 
@@ -68,12 +106,14 @@ executor.setRedactionPolicy({ keys, patterns, fields })
68
106
 
69
107
  - **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
70
108
  - **FlowRecorder**: fires AFTER stage (`onStageExecuted`, `onDecision`, `onFork`, `onLoop`)
71
- - 8 strategies: Narrative, Adaptive, Windowed, RLE, Milestone, Progressive, Separate, Manifest
72
- - `CombinedNarrativeRecorder` implements both — auto-attached by `setEnableNarrative()`
109
+ - 8 built-in FlowRecorder strategies
110
+ - `setEnableNarrative()` auto-attaches `CombinedNarrativeRecorder`
73
111
 
74
112
  ## Rules
75
113
 
114
+ - Use `typedFlowChart<T>()` + `createTypedScopeFactory<T>()` (not flowChart + ScopeFacade)
115
+ - Use `decide()` / `select()` in decider/selector functions
116
+ - Use typed property access (not getValue/setValue)
117
+ - Use `$getArgs()` for input, `$getEnv()` for environment
76
118
  - Never post-process the tree — use recorders
77
- - `getValue()`/`setValue()` for tracked state; `getArgs()` for frozen input
78
- - Don't use deprecated `CombinedNarrativeBuilder`
79
- - `setEnableNarrative()` is all you need
119
+ - `setEnableNarrative()` is all you need for narrative setup
@@ -1,4 +1,4 @@
1
- # footprint.js — Windsurf Rules
1
+ # footprint.js
2
2
 
3
3
  This is the footprint.js library — the flowchart pattern for backend code. Self-explainable systems that AI can reason about.
4
4
 
@@ -12,8 +12,10 @@ This is the footprint.js library — the flowchart pattern for backend code. Sel
12
12
  src/lib/
13
13
  ├── memory/ → Transactional state (SharedMemory, StageContext, TransactionBuffer)
14
14
  ├── schema/ → Validation (Zod optional, duck-typed)
15
- ├── builder/ → Fluent DSL (FlowChartBuilder, flowChart())
15
+ ├── builder/ → Fluent DSL (FlowChartBuilder, flowChart(), typedFlowChart())
16
16
  ├── scope/ → Per-stage facades + recorders + providers
17
+ ├── reactive/ → TypedScope<T> deep Proxy (typed property access, $-methods)
18
+ ├── decide/ → decide()/select() decision evidence capture
17
19
  ├── engine/ → DFS traversal + narrative + handlers
18
20
  ├── runner/ → FlowChartExecutor
19
21
  └── contract/ → I/O schema + OpenAPI
@@ -21,46 +23,82 @@ src/lib/
21
23
 
22
24
  Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
23
25
 
24
- ## Builder API
26
+ ## Key API — TypedScope (Recommended)
25
27
 
26
28
  ```typescript
27
- flowChart(name, fn, id, extractor?, description?)
28
- .addFunction(name, fn, id, description?)
29
- .addDeciderFunction(name, fn, id, description?)
30
- .addFunctionBranch(branchId, name, fn) / .setDefault(id) / .end()
31
- .addSelectorFunction(name, fn, id, description?)
32
- .addListOfFunction([...], { failFast? })
33
- .addSubFlowChartNext(id, subflow, mount, { inputMapper?, outputMapper? })
34
- .loopTo(stageId)
29
+ import { typedFlowChart, createTypedScopeFactory, 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')
35
41
  .setEnableNarrative()
36
- .build() / .toSpec() / .toMermaid()
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, createTypedScopeFactory<State>());
58
+ await executor.run();
59
+ executor.getNarrative(); // causal trace with decision evidence
37
60
  ```
38
61
 
39
- ## Stage Functions
62
+ ### TypedScope $-methods (escape hatches)
40
63
 
41
64
  ```typescript
42
- (scope: ScopeFacade, breakPipeline: () => void, streamCallback?: StreamCallback) => void | Promise<void>
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
43
70
  ```
44
71
 
45
- ## ScopeFacade
72
+ ### decide() / select()
46
73
 
47
74
  ```typescript
48
- scope.getValue('key') // tracked read narrative
49
- scope.setValue('key', value) // tracked write → narrative
50
- scope.updateValue('key', partial) // deep merge (tracked)
51
- scope.deleteValue('key') // tracked delete
52
- scope.getArgs<T>() // frozen readonly input (NOT tracked)
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
+ ]);
53
90
  ```
54
91
 
55
- ## Executor
92
+ ### Executor
56
93
 
57
94
  ```typescript
58
- const executor = new FlowChartExecutor(chart);
59
- await executor.run({ input, timeoutMs?, signal? });
60
- executor.getNarrative() // combined flow + data
61
- executor.getNarrativeEntries() // structured entries
95
+ const executor = new FlowChartExecutor(chart, createTypedScopeFactory<State>());
96
+ await executor.run({ input, env: { traceId: 'req-123' } });
97
+ executor.getNarrative() // string[]
98
+ executor.getNarrativeEntries() // CombinedNarrativeEntry[]
62
99
  executor.getSnapshot() // memory state
63
- executor.attachFlowRecorder(r) // plug flow observer
100
+ executor.attachRecorder(recorder) // scope observer
101
+ executor.attachFlowRecorder(r) // flow observer
64
102
  executor.setRedactionPolicy({ keys, patterns, fields })
65
103
  ```
66
104
 
@@ -68,12 +106,14 @@ executor.setRedactionPolicy({ keys, patterns, fields })
68
106
 
69
107
  - **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
70
108
  - **FlowRecorder**: fires AFTER stage (`onStageExecuted`, `onDecision`, `onFork`, `onLoop`)
71
- - 8 built-in strategies (Narrative, Adaptive, Windowed, RLE, Milestone, Progressive, Separate, Manifest)
72
- - `CombinedNarrativeRecorder` implements both — auto-attached by `setEnableNarrative()`
109
+ - 8 built-in FlowRecorder strategies
110
+ - `setEnableNarrative()` auto-attaches `CombinedNarrativeRecorder`
73
111
 
74
112
  ## Rules
75
113
 
114
+ - Use `typedFlowChart<T>()` + `createTypedScopeFactory<T>()` (not flowChart + ScopeFacade)
115
+ - Use `decide()` / `select()` in decider/selector functions
116
+ - Use typed property access (not getValue/setValue)
117
+ - Use `$getArgs()` for input, `$getEnv()` for environment
76
118
  - Never post-process the tree — use recorders
77
- - `getValue()`/`setValue()` for tracked state; `getArgs()` for frozen input
78
- - Don't use deprecated `CombinedNarrativeBuilder`
79
- - `setEnableNarrative()` is all you need for narrative
119
+ - `setEnableNarrative()` is all you need for narrative setup