footprintjs 3.0.0 → 3.0.2
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.
- package/AGENTS.md +8 -9
- package/CLAUDE.md +7 -9
- package/README.md +2 -3
- package/ai-instructions/claude-code/SKILL.md +48 -46
- package/ai-instructions/copilot-instructions.md +8 -9
- package/ai-instructions/cursor/footprint.md +8 -9
- package/ai-instructions/kiro/footprint.md +8 -9
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -12,7 +12,7 @@ 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())
|
|
16
16
|
├── scope/ → Per-stage facades + recorders + providers
|
|
17
17
|
├── reactive/ → TypedScope<T> deep Proxy (typed property access, $-methods)
|
|
18
18
|
├── decide/ → decide()/select() decision evidence capture
|
|
@@ -26,7 +26,7 @@ Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
|
|
|
26
26
|
## Key API — TypedScope (Recommended)
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
|
-
import {
|
|
29
|
+
import { flowChart, FlowChartExecutor, decide } from 'footprintjs';
|
|
30
30
|
|
|
31
31
|
interface State {
|
|
32
32
|
creditScore: number;
|
|
@@ -34,11 +34,10 @@ interface State {
|
|
|
34
34
|
decision?: string;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const chart =
|
|
37
|
+
const chart = flowChart<State>('Intake', async (scope) => {
|
|
38
38
|
scope.creditScore = 750; // typed write (no setValue needed)
|
|
39
39
|
scope.riskTier = 'low'; // typed write
|
|
40
40
|
}, 'intake')
|
|
41
|
-
.setEnableNarrative()
|
|
42
41
|
.addDeciderFunction('Route', (scope) => {
|
|
43
42
|
return decide(scope, [
|
|
44
43
|
{ when: { riskTier: { eq: 'low' } }, then: 'approved', label: 'Low risk' },
|
|
@@ -54,7 +53,7 @@ const chart = typedFlowChart<State>('Intake', async (scope) => {
|
|
|
54
53
|
.end()
|
|
55
54
|
.build();
|
|
56
55
|
|
|
57
|
-
const executor = new FlowChartExecutor(chart
|
|
56
|
+
const executor = new FlowChartExecutor(chart);
|
|
58
57
|
await executor.run();
|
|
59
58
|
executor.getNarrative(); // causal trace with decision evidence
|
|
60
59
|
```
|
|
@@ -92,7 +91,7 @@ select(scope, [
|
|
|
92
91
|
### Executor
|
|
93
92
|
|
|
94
93
|
```typescript
|
|
95
|
-
const executor = new FlowChartExecutor(chart
|
|
94
|
+
const executor = new FlowChartExecutor(chart);
|
|
96
95
|
await executor.run({ input, env: { traceId: 'req-123' } });
|
|
97
96
|
executor.getNarrative() // string[]
|
|
98
97
|
executor.getNarrativeEntries() // CombinedNarrativeEntry[]
|
|
@@ -107,13 +106,13 @@ executor.setRedactionPolicy({ keys, patterns, fields })
|
|
|
107
106
|
- **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
|
|
108
107
|
- **FlowRecorder**: fires AFTER stage (`onStageExecuted`, `onDecision`, `onFork`, `onLoop`)
|
|
109
108
|
- 8 built-in FlowRecorder strategies
|
|
110
|
-
- `
|
|
109
|
+
- Narrative via `executor.recorder(narrative())` at runtime
|
|
111
110
|
|
|
112
111
|
## Rules
|
|
113
112
|
|
|
114
|
-
- Use `
|
|
113
|
+
- Use `flowChart<T>()` — scopeFactory is auto-embedded
|
|
115
114
|
- Use `decide()` / `select()` in decider/selector functions
|
|
116
115
|
- Use typed property access (not getValue/setValue)
|
|
117
116
|
- Use `$getArgs()` for input, `$getEnv()` for environment
|
|
118
117
|
- Never post-process the tree — use recorders
|
|
119
|
-
-
|
|
118
|
+
- Use `.recorder(narrative())` at runtime for narrative setup
|
package/CLAUDE.md
CHANGED
|
@@ -32,7 +32,7 @@ Two entry points:
|
|
|
32
32
|
### TypedScope (Recommended)
|
|
33
33
|
|
|
34
34
|
```typescript
|
|
35
|
-
import {
|
|
35
|
+
import { flowChart, FlowChartExecutor } from 'footprintjs';
|
|
36
36
|
|
|
37
37
|
interface LoanState {
|
|
38
38
|
creditTier: string;
|
|
@@ -42,7 +42,7 @@ interface LoanState {
|
|
|
42
42
|
approved?: boolean;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const chart =
|
|
45
|
+
const chart = flowChart<LoanState>('Intake', async (scope) => {
|
|
46
46
|
scope.creditTier = 'A'; // typed write
|
|
47
47
|
scope.amount = 50000; // typed write
|
|
48
48
|
scope.customer.address.zip = '90210'; // deep write (updateValue)
|
|
@@ -56,10 +56,9 @@ const chart = typedFlowChart<LoanState>('Intake', async (scope) => {
|
|
|
56
56
|
const env = scope.$getEnv();
|
|
57
57
|
scope.$break(); // stop pipeline
|
|
58
58
|
}, 'intake')
|
|
59
|
-
.setEnableNarrative()
|
|
60
59
|
.build();
|
|
61
60
|
|
|
62
|
-
const executor = new FlowChartExecutor(chart
|
|
61
|
+
const executor = new FlowChartExecutor(chart);
|
|
63
62
|
await executor.run({ input: { requestId: 'req-123' } });
|
|
64
63
|
```
|
|
65
64
|
|
|
@@ -91,11 +90,10 @@ const chart = flowChart('Stage1', fn1, 'stage-1', undefined, 'Description')
|
|
|
91
90
|
.addFunctionBranch('low', 'Approve', approveFn)
|
|
92
91
|
.setDefault('high')
|
|
93
92
|
.end()
|
|
94
|
-
.setEnableNarrative()
|
|
95
93
|
.build();
|
|
96
94
|
```
|
|
97
95
|
|
|
98
|
-
Methods: `start()`, `addFunction()`, `addStreamingFunction()`, `addDeciderFunction()`, `addSelectorFunction()`, `addListOfFunction()`, `addSubFlowChart()`, `addSubFlowChartNext()`, `loopTo()`, `
|
|
96
|
+
Methods: `start()`, `addFunction()`, `addStreamingFunction()`, `addDeciderFunction()`, `addSelectorFunction()`, `addListOfFunction()`, `addSubFlowChart()`, `addSubFlowChartNext()`, `loopTo()`, `contract()`, `build()`, `toSpec()`, `toMermaid()`
|
|
99
97
|
|
|
100
98
|
### ScopeFacade (Internal — use TypedScope for new code)
|
|
101
99
|
|
|
@@ -114,7 +112,7 @@ scope.getEnv() // frozen execution environment (NOT tracked)
|
|
|
114
112
|
### Executor
|
|
115
113
|
|
|
116
114
|
```typescript
|
|
117
|
-
const executor = new FlowChartExecutor(chart
|
|
115
|
+
const executor = new FlowChartExecutor(chart);
|
|
118
116
|
await executor.run({ input: data, env: { traceId: 'req-123' } });
|
|
119
117
|
|
|
120
118
|
executor.attachRecorder(recorder) // plug scope observer
|
|
@@ -150,7 +148,7 @@ Both use `{ id, hooks } -> dispatcher -> error isolation -> attach/detach`. Inte
|
|
|
150
148
|
- `onDecision`/`onSelected` carry optional `evidence` from decide()/select()
|
|
151
149
|
- Built-in: 8 strategies (Narrative, Adaptive, Windowed, RLE, Milestone, Progressive, Separate, Manifest, Silent)
|
|
152
150
|
|
|
153
|
-
**CombinedNarrativeRecorder** implements BOTH interfaces.
|
|
151
|
+
**CombinedNarrativeRecorder** implements BOTH interfaces. Attached via `executor.recorder(narrative())` at runtime.
|
|
154
152
|
|
|
155
153
|
## Event Ordering
|
|
156
154
|
|
|
@@ -172,7 +170,7 @@ Both use `{ id, hooks } -> dispatcher -> error isolation -> attach/detach`. Inte
|
|
|
172
170
|
- Don't extract shared base for Recorder/FlowRecorder — two instances = coincidence
|
|
173
171
|
- Don't use `getArgs()` for tracked data — use typed scope properties
|
|
174
172
|
- Don't put infrastructure data in `getArgs()` — use `getEnv()` via `run({ env })`
|
|
175
|
-
- Don't manually create `CombinedNarrativeRecorder` — `
|
|
173
|
+
- Don't manually create `CombinedNarrativeRecorder` — `executor.recorder(narrative())` handles it
|
|
176
174
|
|
|
177
175
|
## Build & Test
|
|
178
176
|
|
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ That answer came from the trace — not from the LLM's imagination.
|
|
|
73
73
|
## Quick Start
|
|
74
74
|
|
|
75
75
|
```typescript
|
|
76
|
-
import {
|
|
76
|
+
import { flowChart, FlowChartExecutor, decide } from 'footprintjs';
|
|
77
77
|
|
|
78
78
|
interface State {
|
|
79
79
|
user: { name: string; tier: string };
|
|
@@ -81,7 +81,7 @@ interface State {
|
|
|
81
81
|
lane: string;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const chart =
|
|
84
|
+
const chart = flowChart<State>('FetchUser', async (scope) => {
|
|
85
85
|
scope.user = { name: 'Alice', tier: 'premium' };
|
|
86
86
|
}, 'fetch-user')
|
|
87
87
|
.addFunction('ApplyDiscount', async (scope) => {
|
|
@@ -100,7 +100,6 @@ const chart = typedFlowChart<State>('FetchUser', async (scope) => {
|
|
|
100
100
|
})
|
|
101
101
|
.setDefault('standard')
|
|
102
102
|
.end()
|
|
103
|
-
.setEnableNarrative()
|
|
104
103
|
.build();
|
|
105
104
|
|
|
106
105
|
const executor = new FlowChartExecutor(chart);
|
|
@@ -18,7 +18,7 @@ npm install footprintjs
|
|
|
18
18
|
## Quick Start
|
|
19
19
|
|
|
20
20
|
```typescript
|
|
21
|
-
import {
|
|
21
|
+
import { flowChart, FlowChartExecutor } from 'footprintjs';
|
|
22
22
|
|
|
23
23
|
interface OrderState {
|
|
24
24
|
orderId: string;
|
|
@@ -26,7 +26,7 @@ interface OrderState {
|
|
|
26
26
|
paymentStatus: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const chart =
|
|
29
|
+
const chart = flowChart<OrderState>('ReceiveOrder', (scope) => {
|
|
30
30
|
scope.orderId = 'ORD-123';
|
|
31
31
|
scope.amount = 49.99;
|
|
32
32
|
}, 'receive-order', undefined, 'Receive and validate the incoming order')
|
|
@@ -34,10 +34,9 @@ const chart = typedFlowChart<OrderState>('ReceiveOrder', (scope) => {
|
|
|
34
34
|
const amount = scope.amount;
|
|
35
35
|
scope.paymentStatus = amount < 100 ? 'approved' : 'review';
|
|
36
36
|
}, 'process-payment', 'Charge customer and record payment status')
|
|
37
|
-
.setEnableNarrative()
|
|
38
37
|
.build();
|
|
39
38
|
|
|
40
|
-
const executor = new FlowChartExecutor(chart
|
|
39
|
+
const executor = new FlowChartExecutor(chart);
|
|
41
40
|
await executor.run({ input: { orderId: 'ORD-123' } });
|
|
42
41
|
|
|
43
42
|
console.log(executor.getNarrative());
|
|
@@ -53,12 +52,12 @@ console.log(executor.getNarrative());
|
|
|
53
52
|
|
|
54
53
|
## FlowChartBuilder API
|
|
55
54
|
|
|
56
|
-
Always chain from `
|
|
55
|
+
Always chain from `flowChart<T>()` (recommended) or `flowChart()`.
|
|
57
56
|
|
|
58
57
|
### Linear Stages
|
|
59
58
|
|
|
60
59
|
```typescript
|
|
61
|
-
import {
|
|
60
|
+
import { flowChart } from 'footprintjs';
|
|
62
61
|
|
|
63
62
|
interface MyState {
|
|
64
63
|
valueA: string;
|
|
@@ -66,7 +65,7 @@ interface MyState {
|
|
|
66
65
|
valueC: boolean;
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
const chart =
|
|
68
|
+
const chart = flowChart<MyState>('StageA', fnA, 'stage-a', undefined, 'Description of A')
|
|
70
69
|
.addFunction('StageB', fnB, 'stage-b', 'Description of B')
|
|
71
70
|
.addFunction('StageC', fnC, 'stage-c', 'Description of C')
|
|
72
71
|
.build();
|
|
@@ -81,7 +80,7 @@ const chart = typedFlowChart<MyState>('StageA', fnA, 'stage-a', undefined, 'Desc
|
|
|
81
80
|
|
|
82
81
|
### Stage Function Signature (TypedScope)
|
|
83
82
|
|
|
84
|
-
With `
|
|
83
|
+
With `flowChart<T>()`, stage functions receive a `TypedScope<T>` proxy. All reads and writes use typed property access:
|
|
85
84
|
|
|
86
85
|
```typescript
|
|
87
86
|
interface LoanState {
|
|
@@ -133,7 +132,7 @@ interface RiskState {
|
|
|
133
132
|
riskTier: string;
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
const chart =
|
|
135
|
+
const chart = flowChart<RiskState>('Intake', intakeFn, 'intake')
|
|
137
136
|
.addDeciderFunction('AssessRisk', (scope) => {
|
|
138
137
|
// decide() captures filter evidence automatically
|
|
139
138
|
return decide(scope, [
|
|
@@ -168,7 +167,7 @@ interface CheckState {
|
|
|
168
167
|
needsIdentity: boolean;
|
|
169
168
|
}
|
|
170
169
|
|
|
171
|
-
const chart =
|
|
170
|
+
const chart = flowChart<CheckState>('Intake', intakeFn, 'intake')
|
|
172
171
|
.addSelectorFunction('SelectChecks', (scope) => {
|
|
173
172
|
return select(scope, [
|
|
174
173
|
{ when: { needsCredit: { eq: true } }, then: 'credit-check', label: 'Credit required' },
|
|
@@ -196,7 +195,7 @@ builder.addListOfFunction([
|
|
|
196
195
|
|
|
197
196
|
```typescript
|
|
198
197
|
// Build a reusable sub-pipeline
|
|
199
|
-
const creditSubflow =
|
|
198
|
+
const creditSubflow = flowChart<CreditState>('PullReport', pullReportFn, 'pull-report')
|
|
200
199
|
.addFunction('ScoreReport', scoreReportFn, 'score-report')
|
|
201
200
|
.build();
|
|
202
201
|
|
|
@@ -233,13 +232,14 @@ builder
|
|
|
233
232
|
|
|
234
233
|
```typescript
|
|
235
234
|
builder
|
|
236
|
-
.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
235
|
+
.contract({ // I/O schemas + output mapper
|
|
236
|
+
input: zodSchema, // validate input (Zod or JSON Schema)
|
|
237
|
+
output: outputZodSchema, // declare output shape
|
|
238
|
+
mapper: (state) => ({ // map final state to response
|
|
239
|
+
decision: state.decision,
|
|
240
|
+
reason: state.reason,
|
|
241
|
+
}),
|
|
242
|
+
});
|
|
243
243
|
```
|
|
244
244
|
|
|
245
245
|
### Output
|
|
@@ -264,7 +264,7 @@ interface AppState {
|
|
|
264
264
|
decision?: string;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
const executor = new FlowChartExecutor(chart
|
|
267
|
+
const executor = new FlowChartExecutor(chart);
|
|
268
268
|
|
|
269
269
|
// Run with input and optional execution environment
|
|
270
270
|
const result = await executor.run({
|
|
@@ -373,7 +373,7 @@ executor.attachFlowRecorder(myRecorder);
|
|
|
373
373
|
|
|
374
374
|
This is what powers `getNarrative()` and `getNarrativeEntries()`. It implements BOTH `Recorder` (scope) and `FlowRecorder` (engine) interfaces. It buffers scope ops per-stage, then flushes when the flow event arrives — producing merged entries in a single pass.
|
|
375
375
|
|
|
376
|
-
**You don't need to create this manually.**
|
|
376
|
+
**You don't need to create this manually.** Use `executor.recorder(narrative())` at runtime to attach it.
|
|
377
377
|
|
|
378
378
|
---
|
|
379
379
|
|
|
@@ -399,25 +399,28 @@ const report = executor.getRedactionReport();
|
|
|
399
399
|
## Contracts & OpenAPI
|
|
400
400
|
|
|
401
401
|
```typescript
|
|
402
|
-
import {
|
|
402
|
+
import { flowChart } from 'footprintjs';
|
|
403
403
|
import { z } from 'zod';
|
|
404
404
|
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
405
|
+
const chart = flowChart('ProcessLoan', receiveFn)
|
|
406
|
+
.addFunction('Assess', assessFn)
|
|
407
|
+
.contract({
|
|
408
|
+
input: z.object({
|
|
409
|
+
applicantName: z.string(),
|
|
410
|
+
income: z.number(),
|
|
411
|
+
}),
|
|
412
|
+
output: z.object({
|
|
413
|
+
decision: z.enum(['approved', 'rejected']),
|
|
414
|
+
reason: z.string(),
|
|
415
|
+
}),
|
|
416
|
+
mapper: (state) => ({
|
|
417
|
+
decision: state.decision,
|
|
418
|
+
reason: state.reason,
|
|
419
|
+
}),
|
|
420
|
+
})
|
|
421
|
+
.build();
|
|
419
422
|
|
|
420
|
-
const openApiSpec =
|
|
423
|
+
const openApiSpec = chart.toOpenAPI({
|
|
421
424
|
title: 'Loan Underwriting API',
|
|
422
425
|
version: '1.0.0',
|
|
423
426
|
});
|
|
@@ -452,11 +455,11 @@ When a stage executes, events fire in this exact order:
|
|
|
452
455
|
1. **Never post-process the tree.** Don't walk the spec after execution to collect data. Use recorders.
|
|
453
456
|
2. **Don't use `getValue()`/`setValue()` in TypedScope stages.** Use typed property access (`scope.amount = 50000`). The old ScopeFacade API is internal only.
|
|
454
457
|
3. **Don't use `$`-prefixed state keys** (e.g., `$break` as a property name) — they collide with TypedScope's `$`-prefixed escape hatches (`$getArgs`, `$getEnv`, `$break`, `$debug`, `$metric`).
|
|
455
|
-
4. **Never use `CombinedNarrativeBuilder`** — it's deprecated. Use `CombinedNarrativeRecorder` (
|
|
458
|
+
4. **Never use `CombinedNarrativeBuilder`** — it's deprecated. Use `CombinedNarrativeRecorder` (attached via `executor.recorder(narrative())`).
|
|
456
459
|
5. **Don't extract a shared base class** for Recorder and FlowRecorder. They look similar but serve different layers. Two instances = coincidence.
|
|
457
460
|
6. **Don't call `$getArgs()` for tracked data.** `$getArgs()` returns frozen readonly input. Use typed scope properties for state that should appear in the narrative.
|
|
458
461
|
7. **Don't put infrastructure data in `$getArgs()`.** Use `$getEnv()` via `run({ env })` for signals, timeouts, and trace IDs.
|
|
459
|
-
8. **Don't create scope recorders manually** unless building a custom recorder. `
|
|
462
|
+
8. **Don't create scope recorders manually** unless building a custom recorder. `executor.recorder(narrative())` handles everything.
|
|
460
463
|
|
|
461
464
|
---
|
|
462
465
|
|
|
@@ -466,13 +469,13 @@ When a stage executes, events fire in this exact order:
|
|
|
466
469
|
src/lib/
|
|
467
470
|
├── memory/ → SharedMemory, StageContext, TransactionBuffer, EventLog (foundation)
|
|
468
471
|
├── schema/ → detectSchema, validate, InputValidationError (foundation)
|
|
469
|
-
├── builder/ → FlowChartBuilder, flowChart(),
|
|
472
|
+
├── builder/ → FlowChartBuilder, flowChart(), DeciderList, SelectorFnList (standalone)
|
|
470
473
|
├── scope/ → ScopeFacade, recorders/, providers/, protection/ (depends: memory)
|
|
471
474
|
├── reactive/ → TypedScope<T> deep Proxy, typed property access, $-methods, cycle-safe (depends: scope)
|
|
472
475
|
├── decide/ → decide()/select() decision evidence capture, filter + function when formats (depends: scope)
|
|
473
476
|
├── engine/ → FlowchartTraverser, handlers/, narrative/ (depends: memory, scope, reactive, builder)
|
|
474
477
|
├── runner/ → FlowChartExecutor, ExecutionRuntime (depends: engine, scope, schema)
|
|
475
|
-
└── contract/ →
|
|
478
|
+
└── contract/ → I/O schema + OpenAPI generation (depends: schema)
|
|
476
479
|
```
|
|
477
480
|
|
|
478
481
|
Dependency DAG: `memory <- scope <- reactive <- engine <- runner`, `schema <- engine`, `builder (standalone) -> engine`, `contract <- schema`, `decide -> scope`
|
|
@@ -488,7 +491,7 @@ Two entry points:
|
|
|
488
491
|
### Pipeline with decide() + narrative
|
|
489
492
|
|
|
490
493
|
```typescript
|
|
491
|
-
import {
|
|
494
|
+
import { flowChart, FlowChartExecutor, decide } from 'footprintjs';
|
|
492
495
|
|
|
493
496
|
interface LoanState {
|
|
494
497
|
applicantName: string;
|
|
@@ -499,7 +502,7 @@ interface LoanState {
|
|
|
499
502
|
reason?: string;
|
|
500
503
|
}
|
|
501
504
|
|
|
502
|
-
const chart =
|
|
505
|
+
const chart = flowChart<LoanState>('Receive', (scope) => {
|
|
503
506
|
const args = scope.$getArgs<{ applicantName: string; income: number }>();
|
|
504
507
|
scope.applicantName = args.applicantName;
|
|
505
508
|
scope.income = args.income;
|
|
@@ -524,10 +527,9 @@ const chart = typedFlowChart<LoanState>('Receive', (scope) => {
|
|
|
524
527
|
})
|
|
525
528
|
.setDefault('reject')
|
|
526
529
|
.end()
|
|
527
|
-
.setEnableNarrative()
|
|
528
530
|
.build();
|
|
529
531
|
|
|
530
|
-
const executor = new FlowChartExecutor(chart
|
|
532
|
+
const executor = new FlowChartExecutor(chart);
|
|
531
533
|
await executor.run({ input: { applicantName: 'Bob', income: 42000 } });
|
|
532
534
|
const trace = executor.getNarrative();
|
|
533
535
|
// Feed trace to LLM for grounded explanations
|
|
@@ -547,11 +549,11 @@ interface MainState {
|
|
|
547
549
|
creditScore?: number;
|
|
548
550
|
}
|
|
549
551
|
|
|
550
|
-
const subflow =
|
|
552
|
+
const subflow = flowChart<SubState>('SubStart', subStartFn, 'sub-start')
|
|
551
553
|
.addFunction('SubProcess', subProcessFn, 'sub-process')
|
|
552
554
|
.build();
|
|
553
555
|
|
|
554
|
-
const main =
|
|
556
|
+
const main = flowChart<MainState>('Main', mainFn, 'main')
|
|
555
557
|
.addSubFlowChartNext('my-subflow', subflow, 'SubflowMount', {
|
|
556
558
|
inputMapper: (scope) => ({ ssn: scope.ssn }),
|
|
557
559
|
outputMapper: (subOut) => ({ creditScore: subOut.score }),
|
|
@@ -12,7 +12,7 @@ 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())
|
|
16
16
|
├── scope/ → Per-stage facades + recorders + providers
|
|
17
17
|
├── reactive/ → TypedScope<T> deep Proxy (typed property access, $-methods)
|
|
18
18
|
├── decide/ → decide()/select() decision evidence capture
|
|
@@ -26,7 +26,7 @@ Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
|
|
|
26
26
|
## Key API — TypedScope (Recommended)
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
|
-
import {
|
|
29
|
+
import { flowChart, FlowChartExecutor, decide } from 'footprintjs';
|
|
30
30
|
|
|
31
31
|
interface State {
|
|
32
32
|
creditScore: number;
|
|
@@ -34,11 +34,10 @@ interface State {
|
|
|
34
34
|
decision?: string;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const chart =
|
|
37
|
+
const chart = flowChart<State>('Intake', async (scope) => {
|
|
38
38
|
scope.creditScore = 750; // typed write (no setValue needed)
|
|
39
39
|
scope.riskTier = 'low'; // typed write
|
|
40
40
|
}, 'intake')
|
|
41
|
-
.setEnableNarrative()
|
|
42
41
|
.addDeciderFunction('Route', (scope) => {
|
|
43
42
|
return decide(scope, [
|
|
44
43
|
{ when: { riskTier: { eq: 'low' } }, then: 'approved', label: 'Low risk' },
|
|
@@ -54,7 +53,7 @@ const chart = typedFlowChart<State>('Intake', async (scope) => {
|
|
|
54
53
|
.end()
|
|
55
54
|
.build();
|
|
56
55
|
|
|
57
|
-
const executor = new FlowChartExecutor(chart
|
|
56
|
+
const executor = new FlowChartExecutor(chart);
|
|
58
57
|
await executor.run();
|
|
59
58
|
executor.getNarrative(); // causal trace with decision evidence
|
|
60
59
|
```
|
|
@@ -92,7 +91,7 @@ select(scope, [
|
|
|
92
91
|
### Executor
|
|
93
92
|
|
|
94
93
|
```typescript
|
|
95
|
-
const executor = new FlowChartExecutor(chart
|
|
94
|
+
const executor = new FlowChartExecutor(chart);
|
|
96
95
|
await executor.run({ input, env: { traceId: 'req-123' } });
|
|
97
96
|
executor.getNarrative() // string[]
|
|
98
97
|
executor.getNarrativeEntries() // CombinedNarrativeEntry[]
|
|
@@ -107,13 +106,13 @@ executor.setRedactionPolicy({ keys, patterns, fields })
|
|
|
107
106
|
- **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
|
|
108
107
|
- **FlowRecorder**: fires AFTER stage (`onStageExecuted`, `onDecision`, `onFork`, `onLoop`)
|
|
109
108
|
- 8 built-in FlowRecorder strategies
|
|
110
|
-
- `
|
|
109
|
+
- Narrative via `executor.recorder(narrative())` at runtime
|
|
111
110
|
|
|
112
111
|
## Rules
|
|
113
112
|
|
|
114
|
-
- Use `
|
|
113
|
+
- Use `flowChart<T>()` — scopeFactory is auto-embedded
|
|
115
114
|
- Use `decide()` / `select()` in decider/selector functions
|
|
116
115
|
- Use typed property access (not getValue/setValue)
|
|
117
116
|
- Use `$getArgs()` for input, `$getEnv()` for environment
|
|
118
117
|
- Never post-process the tree — use recorders
|
|
119
|
-
-
|
|
118
|
+
- Use `.recorder(narrative())` at runtime for narrative setup
|
|
@@ -12,7 +12,7 @@ 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())
|
|
16
16
|
├── scope/ → Per-stage facades + recorders + providers
|
|
17
17
|
├── reactive/ → TypedScope<T> deep Proxy (typed property access, $-methods)
|
|
18
18
|
├── decide/ → decide()/select() decision evidence capture
|
|
@@ -26,7 +26,7 @@ Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
|
|
|
26
26
|
## Key API — TypedScope (Recommended)
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
|
-
import {
|
|
29
|
+
import { flowChart, FlowChartExecutor, decide } from 'footprintjs';
|
|
30
30
|
|
|
31
31
|
interface State {
|
|
32
32
|
creditScore: number;
|
|
@@ -34,11 +34,10 @@ interface State {
|
|
|
34
34
|
decision?: string;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const chart =
|
|
37
|
+
const chart = flowChart<State>('Intake', async (scope) => {
|
|
38
38
|
scope.creditScore = 750; // typed write (no setValue needed)
|
|
39
39
|
scope.riskTier = 'low'; // typed write
|
|
40
40
|
}, 'intake')
|
|
41
|
-
.setEnableNarrative()
|
|
42
41
|
.addDeciderFunction('Route', (scope) => {
|
|
43
42
|
return decide(scope, [
|
|
44
43
|
{ when: { riskTier: { eq: 'low' } }, then: 'approved', label: 'Low risk' },
|
|
@@ -54,7 +53,7 @@ const chart = typedFlowChart<State>('Intake', async (scope) => {
|
|
|
54
53
|
.end()
|
|
55
54
|
.build();
|
|
56
55
|
|
|
57
|
-
const executor = new FlowChartExecutor(chart
|
|
56
|
+
const executor = new FlowChartExecutor(chart);
|
|
58
57
|
await executor.run();
|
|
59
58
|
executor.getNarrative(); // causal trace with decision evidence
|
|
60
59
|
```
|
|
@@ -92,7 +91,7 @@ select(scope, [
|
|
|
92
91
|
### Executor
|
|
93
92
|
|
|
94
93
|
```typescript
|
|
95
|
-
const executor = new FlowChartExecutor(chart
|
|
94
|
+
const executor = new FlowChartExecutor(chart);
|
|
96
95
|
await executor.run({ input, env: { traceId: 'req-123' } });
|
|
97
96
|
executor.getNarrative() // string[]
|
|
98
97
|
executor.getNarrativeEntries() // CombinedNarrativeEntry[]
|
|
@@ -107,13 +106,13 @@ executor.setRedactionPolicy({ keys, patterns, fields })
|
|
|
107
106
|
- **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
|
|
108
107
|
- **FlowRecorder**: fires AFTER stage (`onStageExecuted`, `onDecision`, `onFork`, `onLoop`)
|
|
109
108
|
- 8 built-in FlowRecorder strategies
|
|
110
|
-
- `
|
|
109
|
+
- Narrative via `executor.recorder(narrative())` at runtime
|
|
111
110
|
|
|
112
111
|
## Rules
|
|
113
112
|
|
|
114
|
-
- Use `
|
|
113
|
+
- Use `flowChart<T>()` — scopeFactory is auto-embedded
|
|
115
114
|
- Use `decide()` / `select()` in decider/selector functions
|
|
116
115
|
- Use typed property access (not getValue/setValue)
|
|
117
116
|
- Use `$getArgs()` for input, `$getEnv()` for environment
|
|
118
117
|
- Never post-process the tree — use recorders
|
|
119
|
-
-
|
|
118
|
+
- Use `.recorder(narrative())` at runtime for narrative setup
|
|
@@ -12,7 +12,7 @@ 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())
|
|
16
16
|
├── scope/ → Per-stage facades + recorders + providers
|
|
17
17
|
├── reactive/ → TypedScope<T> deep Proxy (typed property access, $-methods)
|
|
18
18
|
├── decide/ → decide()/select() decision evidence capture
|
|
@@ -26,7 +26,7 @@ Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
|
|
|
26
26
|
## Key API — TypedScope (Recommended)
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
|
-
import {
|
|
29
|
+
import { flowChart, FlowChartExecutor, decide } from 'footprintjs';
|
|
30
30
|
|
|
31
31
|
interface State {
|
|
32
32
|
creditScore: number;
|
|
@@ -34,11 +34,10 @@ interface State {
|
|
|
34
34
|
decision?: string;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const chart =
|
|
37
|
+
const chart = flowChart<State>('Intake', async (scope) => {
|
|
38
38
|
scope.creditScore = 750; // typed write (no setValue needed)
|
|
39
39
|
scope.riskTier = 'low'; // typed write
|
|
40
40
|
}, 'intake')
|
|
41
|
-
.setEnableNarrative()
|
|
42
41
|
.addDeciderFunction('Route', (scope) => {
|
|
43
42
|
return decide(scope, [
|
|
44
43
|
{ when: { riskTier: { eq: 'low' } }, then: 'approved', label: 'Low risk' },
|
|
@@ -54,7 +53,7 @@ const chart = typedFlowChart<State>('Intake', async (scope) => {
|
|
|
54
53
|
.end()
|
|
55
54
|
.build();
|
|
56
55
|
|
|
57
|
-
const executor = new FlowChartExecutor(chart
|
|
56
|
+
const executor = new FlowChartExecutor(chart);
|
|
58
57
|
await executor.run();
|
|
59
58
|
executor.getNarrative(); // causal trace with decision evidence
|
|
60
59
|
```
|
|
@@ -92,7 +91,7 @@ select(scope, [
|
|
|
92
91
|
### Executor
|
|
93
92
|
|
|
94
93
|
```typescript
|
|
95
|
-
const executor = new FlowChartExecutor(chart
|
|
94
|
+
const executor = new FlowChartExecutor(chart);
|
|
96
95
|
await executor.run({ input, env: { traceId: 'req-123' } });
|
|
97
96
|
executor.getNarrative() // string[]
|
|
98
97
|
executor.getNarrativeEntries() // CombinedNarrativeEntry[]
|
|
@@ -107,13 +106,13 @@ executor.setRedactionPolicy({ keys, patterns, fields })
|
|
|
107
106
|
- **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
|
|
108
107
|
- **FlowRecorder**: fires AFTER stage (`onStageExecuted`, `onDecision`, `onFork`, `onLoop`)
|
|
109
108
|
- 8 built-in FlowRecorder strategies
|
|
110
|
-
- `
|
|
109
|
+
- Narrative via `executor.recorder(narrative())` at runtime
|
|
111
110
|
|
|
112
111
|
## Rules
|
|
113
112
|
|
|
114
|
-
- Use `
|
|
113
|
+
- Use `flowChart<T>()` — scopeFactory is auto-embedded
|
|
115
114
|
- Use `decide()` / `select()` in decider/selector functions
|
|
116
115
|
- Use typed property access (not getValue/setValue)
|
|
117
116
|
- Use `$getArgs()` for input, `$getEnv()` for environment
|
|
118
117
|
- Never post-process the tree — use recorders
|
|
119
|
-
-
|
|
118
|
+
- Use `.recorder(narrative())` at runtime for narrative setup
|
package/package.json
CHANGED