footprintjs 0.10.2 → 0.10.3
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/README.md +80 -503
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,581 +10,158 @@
|
|
|
10
10
|
<a href="https://www.npmjs.com/package/footprintjs"><img src="https://img.shields.io/npm/v/footprintjs.svg?style=flat" alt="npm version"></a>
|
|
11
11
|
<a href="https://github.com/footprintjs/footPrint/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
|
|
12
12
|
<a href="https://www.npmjs.com/package/footprintjs"><img src="https://img.shields.io/npm/dm/footprintjs.svg" alt="Downloads"></a>
|
|
13
|
-
<a href="https://footprintjs.github.io/footprint-
|
|
13
|
+
<a href="https://footprintjs.github.io/footprint-demo/"><img src="https://img.shields.io/badge/Live_Demo-View_App-10b981?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJ3aGl0ZSI+PHBhdGggZD0iTTggNXYxNGwxMS03eiIvPjwvc3ZnPg==" alt="Live Demo"></a>
|
|
14
|
+
<a href="https://footprintjs.github.io/footprint-playground/"><img src="https://img.shields.io/badge/Playground-Try_it-6366f1?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJ3aGl0ZSI+PHBhdGggZD0iTTggNXYxNGwxMS03eiIvPjwvc3ZnPg==" alt="Interactive Playground"></a>
|
|
14
15
|
</p>
|
|
15
16
|
|
|
16
17
|
<br>
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="assets/hero.gif" alt="FootPrint demo — loan application with animated flowchart, memory inspector, and causal trace" width="800">
|
|
21
|
+
</p>
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
**MVC is a pattern for backends. FootPrint is a different pattern** — the flowchart pattern — where your business logic is a graph of functions with transactional state. The code becomes self-explainable: AI reads the structure, traces every decision, and explains what happened — no hallucination.
|
|
21
24
|
|
|
22
25
|
```bash
|
|
23
26
|
npm install footprintjs
|
|
24
27
|
```
|
|
25
28
|
|
|
26
|
-
## Quick Start
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
import { flowChart, FlowChartExecutor } from 'footprintjs';
|
|
30
|
-
|
|
31
|
-
const chart = flowChart('Greet', (scope) => {
|
|
32
|
-
scope.setValue('name', 'Alice');
|
|
33
|
-
})
|
|
34
|
-
.addFunction('Personalize', (scope) => {
|
|
35
|
-
const name = scope.getValue('name');
|
|
36
|
-
scope.setValue('message', `Hello, ${name}!`);
|
|
37
|
-
})
|
|
38
|
-
.setEnableNarrative()
|
|
39
|
-
.build();
|
|
40
|
-
|
|
41
|
-
const executor = new FlowChartExecutor(chart);
|
|
42
|
-
const result = await executor.run();
|
|
43
|
-
|
|
44
|
-
console.log(executor.getNarrative());
|
|
45
|
-
// Stage 1: The process began with Greet.
|
|
46
|
-
// Step 1: Write name = "Alice"
|
|
47
|
-
// Stage 2: Next, it moved on to Personalize.
|
|
48
|
-
// Step 1: Read name = "Alice"
|
|
49
|
-
// Step 2: Write message = "Hello, Alice!"
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
> **[Try it in the browser](https://footprintjs.github.io/footprint-playground/)** — no install needed
|
|
53
|
-
>
|
|
54
|
-
> **[Browse 25+ examples](https://github.com/footprintjs/footPrint-samples)** — features, flowchart patterns, flow recorder strategies, and a full loan underwriting demo
|
|
55
|
-
|
|
56
29
|
---
|
|
57
30
|
|
|
58
|
-
##
|
|
59
|
-
|
|
60
|
-
**MVC** separates concerns into Model, View, Controller. It works, but the code is opaque to AI — an LLM can't trace why a request produced a specific result without parsing scattered logs.
|
|
31
|
+
## The Problem
|
|
61
32
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
1. **Self-describing code** — The structure auto-generates tool descriptions for LLM agents. No hand-written descriptions that drift from reality.
|
|
65
|
-
2. **Self-explaining execution** — Every run produces a causal trace showing what happened and why. An LLM reads the trace and explains decisions accurately — no hallucination.
|
|
33
|
+
Your LLM needs to explain why your code made a decision. Without structure, it reconstructs reasoning from scattered logs — expensive, slow, and hallucinates.
|
|
66
34
|
|
|
67
35
|
| | MVC / Traditional | Flowchart Pattern (FootPrint) |
|
|
68
36
|
|---|---|---|
|
|
69
|
-
| **Code structure** | Controllers with implicit flow | Explicit graph of named functions |
|
|
70
37
|
| **LLM explains a decision** | Reconstruct from scattered logs | Read the causal trace directly |
|
|
71
38
|
| **Tool descriptions for agents** | Write and maintain by hand | Auto-generated from the graph |
|
|
72
39
|
| **State management** | Global/manual, race-prone | Transactional scope with atomic commits |
|
|
73
40
|
| **Debugging** | `console.log` + guesswork | Time-travel replay to any stage |
|
|
74
41
|
|
|
75
|
-
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## How It Works
|
|
76
45
|
|
|
77
|
-
A loan
|
|
46
|
+
A loan pipeline rejects Bob. The user asks: **"Why was I rejected?"**
|
|
78
47
|
|
|
79
|
-
|
|
48
|
+
The runtime auto-generates this trace from what the code actually did:
|
|
80
49
|
|
|
81
50
|
```
|
|
82
51
|
Stage 1: The process began with ReceiveApplication.
|
|
83
|
-
Step 1: Write app = {applicantName, annualIncome, monthlyDebts, creditScore,
|
|
52
|
+
Step 1: Write app = {applicantName, annualIncome, monthlyDebts, creditScore, ...}
|
|
84
53
|
Stage 2: Next, it moved on to PullCreditReport.
|
|
85
|
-
Step 1: Read app = {applicantName, annualIncome, monthlyDebts, creditScore,
|
|
54
|
+
Step 1: Read app = {applicantName, annualIncome, monthlyDebts, creditScore, ...}
|
|
86
55
|
Step 2: Write creditTier = "fair"
|
|
87
|
-
Step 3: Write creditFlags = (1 item)
|
|
88
56
|
Stage 3: Next, it moved on to CalculateDTI.
|
|
89
|
-
Step 1: Read app = {applicantName, annualIncome, monthlyDebts, creditScore,
|
|
57
|
+
Step 1: Read app = {applicantName, annualIncome, monthlyDebts, creditScore, ...}
|
|
90
58
|
Step 2: Write dtiRatio = 0.6
|
|
91
|
-
Step 3: Write
|
|
92
|
-
|
|
93
|
-
Step 5: Write dtiFlags = (1 item)
|
|
94
|
-
Stage 4: Next, it moved on to VerifyEmployment.
|
|
95
|
-
Step 1: Read app = {applicantName, annualIncome, monthlyDebts, creditScore, …}
|
|
96
|
-
Step 2: Write employmentVerified = true
|
|
97
|
-
Step 3: Write employmentFlags = (1 item)
|
|
98
|
-
Stage 5: Next, it moved on to AssessRisk.
|
|
59
|
+
Step 3: Write dtiStatus = "excessive"
|
|
60
|
+
Stage 4: Next, it moved on to AssessRisk.
|
|
99
61
|
Step 1: Read creditTier = "fair"
|
|
100
62
|
Step 2: Read dtiStatus = "excessive"
|
|
101
|
-
Step 3:
|
|
102
|
-
Step 4: Write riskTier = "high"
|
|
103
|
-
Step 5: Write riskFactors = (1 item)
|
|
63
|
+
Step 3: Write riskTier = "high"
|
|
104
64
|
[Condition]: A decision was made, and the path taken was RejectApplication.
|
|
105
65
|
```
|
|
106
66
|
|
|
107
|
-
The LLM backtracks
|
|
67
|
+
The LLM backtracks: `riskTier="high"` ← `dtiStatus="excessive"` ← `dtiRatio=0.6` ← `app.monthlyDebts=2100`. Every variable links to its cause:
|
|
108
68
|
|
|
109
|
-
> **LLM:** "Your application was rejected because your
|
|
69
|
+
> **LLM:** "Your application was rejected because your debt-to-income ratio of 60% exceeds the 43% maximum, your credit score of 580 falls in the 'fair' tier, and your self-employment tenure of 1 year is below the 2-year minimum."
|
|
110
70
|
|
|
111
71
|
That answer came from the trace — not from the LLM's imagination.
|
|
112
72
|
|
|
113
73
|
---
|
|
114
74
|
|
|
115
|
-
##
|
|
116
|
-
|
|
117
|
-
This is regular backend code — just structured as a flowchart instead of a controller. No one wrote those trace sentences. The functions just read and write scope; the pattern produces the narrative automatically:
|
|
75
|
+
## Quick Start
|
|
118
76
|
|
|
119
77
|
```typescript
|
|
120
|
-
import {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
: creditScore >= 670 ? 'good'
|
|
141
|
-
: creditScore >= 580 ? 'fair' : 'poor';
|
|
142
|
-
|
|
143
|
-
scope.setValue('creditTier', tier);
|
|
144
|
-
scope.setValue('creditFlags', tier === 'fair' ? ['below-average credit'] : []);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const calculateDTI = (scope: ScopeFacade) => {
|
|
148
|
-
const { annualIncome, monthlyDebts } = scope.getValue('app') as any;
|
|
149
|
-
const ratio = Math.round((monthlyDebts / (annualIncome / 12)) * 100) / 100;
|
|
150
|
-
|
|
151
|
-
scope.setValue('dtiRatio', ratio);
|
|
152
|
-
scope.setValue('dtiFlags', ratio > 0.43 ? [`DTI at ${Math.round(ratio * 100)}% exceeds 43%`] : []);
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const verifyEmployment = (scope: ScopeFacade) => {
|
|
156
|
-
const { employmentType, employmentYears } = scope.getValue('app') as any;
|
|
157
|
-
const verified = employmentType !== 'self-employed' || employmentYears >= 2;
|
|
158
|
-
|
|
159
|
-
scope.setValue('employmentVerified', verified);
|
|
160
|
-
scope.setValue('employmentFlags', !verified
|
|
161
|
-
? [`${employmentType}, ${employmentYears}yr < 2yr minimum`] : []);
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const assessRisk = (scope: ScopeFacade) => {
|
|
165
|
-
const tier = scope.getValue('creditTier') as string;
|
|
166
|
-
const ratio = scope.getValue('dtiRatio') as number;
|
|
167
|
-
const verified = scope.getValue('employmentVerified') as boolean;
|
|
168
|
-
|
|
169
|
-
scope.setValue('riskTier', (!verified || ratio > 0.43 || tier === 'poor') ? 'high' : 'low');
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
// Deciders return a branch ID — the only stage that needs a return value
|
|
173
|
-
const loanDecider = (scope: ScopeFacade): string => {
|
|
174
|
-
const tier = scope.getValue('riskTier') as string;
|
|
175
|
-
if (tier === 'low') return 'approved';
|
|
176
|
-
if (tier === 'high') return 'rejected';
|
|
177
|
-
return 'manual-review';
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
// ── Build → Run → Narrative (D3-style chaining) ──────────────────────
|
|
181
|
-
|
|
182
|
-
const chart = flowChart('ReceiveApplication', receiveApplication)
|
|
183
|
-
.setEnableNarrative()
|
|
184
|
-
.addFunction('PullCreditReport', pullCreditReport)
|
|
185
|
-
.addFunction('CalculateDTI', calculateDTI)
|
|
186
|
-
.addFunction('VerifyEmployment', verifyEmployment)
|
|
187
|
-
.addFunction('AssessRisk', assessRisk)
|
|
188
|
-
.addDeciderFunction('LoanDecision', loanDecider as any)
|
|
189
|
-
.addFunctionBranch('approved', 'Approve', () => {})
|
|
190
|
-
.addFunctionBranch('rejected', 'Reject', () => {})
|
|
191
|
-
.addFunctionBranch('manual-review', 'ManualReview', () => {})
|
|
192
|
-
.setDefault('manual-review')
|
|
78
|
+
import { flowChart, FlowChartExecutor, ScopeFacade } from 'footprintjs';
|
|
79
|
+
|
|
80
|
+
const chart = flowChart('FetchUser', (scope: ScopeFacade) => {
|
|
81
|
+
scope.setValue('user', { name: 'Alice', tier: 'premium' });
|
|
82
|
+
}, 'fetch-user')
|
|
83
|
+
.addFunction('ApplyDiscount', (scope: ScopeFacade) => {
|
|
84
|
+
const user = scope.getValue('user') as any;
|
|
85
|
+
const discount = user.tier === 'premium' ? 0.2 : 0.05;
|
|
86
|
+
scope.setValue('discount', discount);
|
|
87
|
+
}, 'apply-discount')
|
|
88
|
+
.addDeciderFunction('Route', (scope: ScopeFacade): string => {
|
|
89
|
+
return (scope.getValue('discount') as number) > 0.1 ? 'vip' : 'standard';
|
|
90
|
+
}, 'route')
|
|
91
|
+
.addFunctionBranch('vip', 'VIPCheckout', (scope: ScopeFacade) => {
|
|
92
|
+
scope.setValue('lane', 'VIP express');
|
|
93
|
+
})
|
|
94
|
+
.addFunctionBranch('standard', 'StandardCheckout', (scope: ScopeFacade) => {
|
|
95
|
+
scope.setValue('lane', 'Standard');
|
|
96
|
+
})
|
|
97
|
+
.setDefault('standard')
|
|
193
98
|
.end()
|
|
99
|
+
.setEnableNarrative()
|
|
194
100
|
.build();
|
|
195
101
|
|
|
196
|
-
const executor = new FlowChartExecutor(chart
|
|
102
|
+
const executor = new FlowChartExecutor(chart);
|
|
197
103
|
await executor.run();
|
|
198
104
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
The flowchart pattern produces two AI-readable outputs automatically — no extra code needed:
|
|
209
|
-
|
|
210
|
-
### Build-time: tool description for LLM agents
|
|
211
|
-
|
|
212
|
-
When you call `.build()`, the structure auto-generates `chart.description` — a complete description of what the code does:
|
|
213
|
-
|
|
214
|
-
```
|
|
215
|
-
FlowChart: ReceiveApplication
|
|
216
|
-
Steps:
|
|
217
|
-
1. ReceiveApplication
|
|
218
|
-
2. PullCreditReport
|
|
219
|
-
3. CalculateDTI
|
|
220
|
-
4. VerifyEmployment
|
|
221
|
-
5. AssessRisk
|
|
222
|
-
6. LoanDecision — Decides between: approved, rejected, manual-review
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
Register this as a **tool description**. When an LLM agent has multiple tools to choose from, it can read each tool's description and pick the right one — without you writing tool descriptions by hand:
|
|
226
|
-
|
|
227
|
-
```typescript
|
|
228
|
-
// Each flowchart's auto-generated description becomes the tool description
|
|
229
|
-
const tools = [
|
|
230
|
-
{
|
|
231
|
-
name: 'process-loan',
|
|
232
|
-
description: loanChart.description,
|
|
233
|
-
// FlowChart: ReceiveApplication
|
|
234
|
-
// Steps:
|
|
235
|
-
// 1. ReceiveApplication
|
|
236
|
-
// 2. PullCreditReport ...
|
|
237
|
-
// 6. LoanDecision — Decides between: approved, rejected, manual-review
|
|
238
|
-
handler: (input) => runWithChart(loanChart, input),
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
name: 'check-fraud',
|
|
242
|
-
description: fraudChart.description,
|
|
243
|
-
// FlowChart: AnalyzeTransaction
|
|
244
|
-
// Steps:
|
|
245
|
-
// 1. AnalyzeTransaction
|
|
246
|
-
// 2. CheckVelocity
|
|
247
|
-
// 3. CheckGeolocation
|
|
248
|
-
// 4. FraudDecision — Decides between: allow, block, review
|
|
249
|
-
handler: (input) => runWithChart(fraudChart, input),
|
|
250
|
-
},
|
|
251
|
-
];
|
|
252
|
-
|
|
253
|
-
// The LLM sees exactly what each tool does — enough to pick the right one.
|
|
254
|
-
// No manual description writing. The structure IS the description.
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
### Runtime: causal trace for LLM explanation
|
|
258
|
-
|
|
259
|
-
After `.run()`, the trace shows what the code *actually did* — every value read, every value written, every decision made. Ship it alongside the result so any LLM can explain what happened and why.
|
|
260
|
-
|
|
261
|
-
**Build-time tells the LLM what the code does. Runtime tells the LLM what the code did.** That's what makes it self-explainable — the pattern produces both automatically.
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
## How the pattern works
|
|
266
|
-
|
|
267
|
-
The flowchart pattern has three parts — the same way MVC has Model, View, Controller:
|
|
268
|
-
|
|
269
|
-
1. **Builder** — Define your business logic as a graph of named functions. Like how a controller defines routes, but the structure is an explicit flowchart.
|
|
270
|
-
2. **Scope** — Transactional state shared across stages. Writes are buffered and committed atomically. This replaces scattered state management.
|
|
271
|
-
3. **Engine** — Executes the graph and auto-generates the causal trace. You write functions; the pattern produces the explanation.
|
|
272
|
-
|
|
273
|
-
```
|
|
274
|
-
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
|
|
275
|
-
│ FlowChartBuilder │─────>│ FlowChart │─────>│ FlowChartExecutor │
|
|
276
|
-
│ (Build-time DSL) │ │ (Compiled Tree) │ │ (Runtime Engine) │
|
|
277
|
-
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
|
|
278
|
-
│ │ │
|
|
279
|
-
│ .start() │ .build() │ .run()
|
|
280
|
-
│ .addFunction() │ .description │ .getNarrative()
|
|
281
|
-
│ .addDeciderFunction() │ .stageDescriptions │ .getSnapshot()
|
|
282
|
-
│ .addSubFlowChart() │ │
|
|
283
|
-
└────────────────────────────┴────────────────────────────┘
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
---
|
|
287
|
-
|
|
288
|
-
## Documentation
|
|
289
|
-
|
|
290
|
-
| Guide | What it covers |
|
|
291
|
-
|-------|---------------|
|
|
292
|
-
| **[Patterns](docs/guides/patterns.md)** | All 7 flowchart patterns with diagrams |
|
|
293
|
-
| **[Scope](docs/guides/scope.md)** | Typed, raw, and Zod scope; recorders; protection |
|
|
294
|
-
| **[Execution Control](docs/guides/execution.md)** | breakFn, cancellation, timeout, fail-fast, loops |
|
|
295
|
-
| **[Error Handling](docs/guides/error-handling.md)** | Commit-on-error, debug recorder, post-mortem |
|
|
296
|
-
| **[Flow Recorders](docs/guides/flow-recorders.md)** | Pluggable observers for control flow narrative — 7 built-in strategies |
|
|
297
|
-
| **[Contracts](docs/guides/contracts.md)** | defineContract, OpenAPI 3.1, Zod vs JSON Schema |
|
|
298
|
-
| **[Internals](docs/internals/)** | Architecture deep-dives for each library |
|
|
299
|
-
|
|
300
|
-
---
|
|
301
|
-
|
|
302
|
-
## Patterns
|
|
303
|
-
|
|
304
|
-
Seven composition patterns — linear, parallel, conditional, multi-select, subflow, streaming, and loops:
|
|
305
|
-
|
|
306
|
-
```typescript
|
|
307
|
-
// Linear: A → B → C
|
|
308
|
-
flowChart('A', fnA).addFunction('B', fnB).addFunction('C', fnC).build();
|
|
309
|
-
|
|
310
|
-
// Parallel fork
|
|
311
|
-
flowChart('Fetch', fetchFn)
|
|
312
|
-
.addListOfFunction([
|
|
313
|
-
{ id: 'html', name: 'ParseHTML', fn: parseHTML },
|
|
314
|
-
{ id: 'css', name: 'ParseCSS', fn: parseCSS },
|
|
315
|
-
])
|
|
316
|
-
.addFunction('Merge', mergeFn)
|
|
317
|
-
.build();
|
|
318
|
-
|
|
319
|
-
// Conditional branching (decider)
|
|
320
|
-
flowChart('Classify', classifyFn)
|
|
321
|
-
.addDeciderFunction('Route', routeFn)
|
|
322
|
-
.addFunctionBranch('digital', 'DigitalDelivery', digitalFn)
|
|
323
|
-
.addFunctionBranch('physical', 'ShipPackage', shipFn)
|
|
324
|
-
.setDefault('physical')
|
|
325
|
-
.end()
|
|
326
|
-
.build();
|
|
327
|
-
|
|
328
|
-
// Subflow composition
|
|
329
|
-
flowChart('Router', routerFn)
|
|
330
|
-
.addSubFlowChart('faq', faqFlow, 'FAQ Handler')
|
|
331
|
-
.addSubFlowChart('rag', ragFlow, 'RAG Handler')
|
|
332
|
-
.build();
|
|
333
|
-
|
|
334
|
-
// Loops
|
|
335
|
-
flowChart('Init', initFn)
|
|
336
|
-
.addFunction('Retry', retryFn, 'retry')
|
|
337
|
-
.addDeciderFunction('Check', checkFn)
|
|
338
|
-
.addFunctionBranch('again', 'Process', processFn)
|
|
339
|
-
.addFunctionBranch('done', 'Finish', finishFn)
|
|
340
|
-
.end()
|
|
341
|
-
.loopTo('retry')
|
|
342
|
-
.build();
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
**[Full patterns guide →](docs/guides/patterns.md)** — all seven patterns with diagrams and composition examples
|
|
346
|
-
|
|
347
|
-
---
|
|
348
|
-
|
|
349
|
-
## Scope
|
|
350
|
-
|
|
351
|
-
Each stage receives a **scope** — a transactional interface to shared state:
|
|
352
|
-
|
|
353
|
-
```typescript
|
|
354
|
-
// Typed scope (recommended)
|
|
355
|
-
class LoanScope extends ScopeFacade {
|
|
356
|
-
get creditScore(): number { return this.getValue('creditScore') as number; }
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Validated scope (Zod)
|
|
360
|
-
const scopeFactory = defineScopeFromZod(z.object({
|
|
361
|
-
creditScore: z.number(),
|
|
362
|
-
riskTier: z.string().optional(),
|
|
363
|
-
}));
|
|
364
|
-
|
|
365
|
-
// Raw scope
|
|
366
|
-
scope.setValue('total', 79.98);
|
|
367
|
-
scope.updateValue('config', { retries: 3 });
|
|
105
|
+
console.log(executor.getNarrative());
|
|
106
|
+
// Stage 1: The process began with FetchUser.
|
|
107
|
+
// Step 1: Write user = {name: "Alice", tier: "premium"}
|
|
108
|
+
// Stage 2: Next, it moved on to ApplyDiscount.
|
|
109
|
+
// Step 1: Read user = {name: "Alice", tier: "premium"}
|
|
110
|
+
// Step 2: Write discount = 0.2
|
|
111
|
+
// Stage 3: A decision was made, and the path taken was VIPCheckout.
|
|
112
|
+
// Step 1: Write lane = "VIP express"
|
|
368
113
|
```
|
|
369
114
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
**[
|
|
115
|
+
> **[Try it in the browser](https://footprintjs.github.io/footprint-playground/)** — no install needed
|
|
116
|
+
>
|
|
117
|
+
> **[Browse 25+ examples](https://github.com/footprintjs/footPrint-samples)** — patterns, recorders, and a full loan underwriting demo
|
|
373
118
|
|
|
374
119
|
---
|
|
375
120
|
|
|
376
|
-
##
|
|
121
|
+
## Features
|
|
377
122
|
|
|
378
123
|
| Feature | Description |
|
|
379
124
|
|---------|-------------|
|
|
380
125
|
| **Causal Traces** | Every read/write captured — LLMs backtrack through variables to find causes |
|
|
381
126
|
| **Auto Narrative** | Build-time descriptions for tool selection, runtime traces for explanation |
|
|
382
|
-
| **
|
|
383
|
-
| **
|
|
384
|
-
| **
|
|
385
|
-
| **
|
|
386
|
-
| **
|
|
387
|
-
| **
|
|
388
|
-
| **Streaming** | Built-in streaming stages for LLM token emission |
|
|
389
|
-
| **PII Redaction** | Per-key `setValue(key, value, true)` or declarative `RedactionPolicy` with exact keys, regex patterns, and field-level scrubbing (dot-notation for nested paths) — plus `getRedactionReport()` audit trail ([guide](docs/guides/scope.md#redaction-pii-protection)) |
|
|
390
|
-
| **Pluggable Recorders** | DebugRecorder, MetricRecorder, NarrativeRecorder — or bring your own |
|
|
391
|
-
| **Flow Recorders** | 7 narrative strategies for loop summarization — Windowed, Silent, Adaptive, Progressive, Milestone, RLE, Separate — or build custom ([examples](https://github.com/footprintjs/footPrint-samples/tree/main/examples/flow-recorders)) |
|
|
392
|
-
|
|
393
|
-
---
|
|
394
|
-
|
|
395
|
-
## Execution Control & Error Handling
|
|
396
|
-
|
|
397
|
-
Three levels of control: `breakFn()` (graceful stop), `throw` (hard abort), `AbortSignal` (external cancellation):
|
|
398
|
-
|
|
399
|
-
```typescript
|
|
400
|
-
// Graceful stop — complete this stage, skip remaining
|
|
401
|
-
const validate = async (scope: ScopeFacade, breakFn: () => void) => {
|
|
402
|
-
if (scope.getValue('amount') > 50_000) { breakFn(); }
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
// Timeout / external cancellation
|
|
406
|
-
await executor.run({ timeoutMs: 30_000 });
|
|
407
|
-
await executor.run({ signal: controller.signal });
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
When a stage throws, the engine commits the trace *before* re-throwing — so `getSnapshot()` captures everything up to the failure point, including partial writes and error metadata.
|
|
411
|
-
|
|
412
|
-
**[Execution control guide →](docs/guides/execution.md)** — breakFn, cancellation, timeout, fail-fast forks, loops
|
|
413
|
-
|
|
414
|
-
**[Error handling guide →](docs/guides/error-handling.md)** — commit-on-error, debug recorder, error narrative, post-mortem
|
|
415
|
-
|
|
416
|
-
---
|
|
417
|
-
|
|
418
|
-
## API Reference
|
|
419
|
-
|
|
420
|
-
### Builder
|
|
421
|
-
|
|
422
|
-
| Method | Description |
|
|
423
|
-
|--------|-------------|
|
|
424
|
-
| `start(name, fn?)` | Define root stage |
|
|
425
|
-
| `addFunction(name, fn?)` | Add linear next stage |
|
|
426
|
-
| `addListOfFunction(specs, opts?)` | Add parallel children (fork). Options: `{ failFast? }` |
|
|
427
|
-
| `addDeciderFunction(name, fn)` | Single-choice branching (returns one branch ID) |
|
|
428
|
-
| `addSelectorFunction(name, fn)` | Multi-choice branching (returns multiple branch IDs) |
|
|
429
|
-
| `addSubFlowChart(id, flow)` | Mount subflow as parallel child |
|
|
430
|
-
| `addSubFlowChartNext(id, flow)` | Mount subflow as linear next |
|
|
431
|
-
| `addStreamingFunction(name, streamId?, fn?)` | Add streaming stage |
|
|
432
|
-
| `addTraversalExtractor(fn)` | Register per-stage data extractor |
|
|
433
|
-
| `setEnableNarrative()` | Enable runtime narrative generation |
|
|
434
|
-
| `loopTo(stageId)` | Loop back to earlier stage |
|
|
435
|
-
| `build()` | Compile to FlowChart |
|
|
436
|
-
| `execute(scopeFactory?)` | Build + run (convenience) |
|
|
437
|
-
| `toSpec()` | Export pure JSON (no functions) |
|
|
438
|
-
| `toMermaid()` | Generate Mermaid diagram |
|
|
439
|
-
|
|
440
|
-
### Executor
|
|
441
|
-
|
|
442
|
-
| Method | Description |
|
|
443
|
-
|--------|-------------|
|
|
444
|
-
| `run(options?)` | Execute the flowchart. Options: `{ signal?, timeoutMs? }` |
|
|
445
|
-
| `getNarrative()` | Combined narrative (flow + data) with ScopeFacade; flow-only otherwise |
|
|
446
|
-
| `getFlowNarrative()` | Flow-only narrative sentences |
|
|
447
|
-
| `attachFlowRecorder(recorder)` | Attach a FlowRecorder for pluggable narrative control |
|
|
448
|
-
| `detachFlowRecorder(id)` | Detach a FlowRecorder by id |
|
|
449
|
-
| `getNarrativeEntries()` | Structured `CombinedNarrativeEntry[]` for programmatic use |
|
|
450
|
-
| `getSnapshot()` | Full execution tree + state |
|
|
451
|
-
| `getExtractedResults()` | Extractor results map |
|
|
452
|
-
| `getEnrichedResults()` | Enriched snapshots (scope state, debug info, output) |
|
|
453
|
-
| `getSubflowResults()` | Nested subflow execution data |
|
|
454
|
-
| `getRuntimeStructure()` | Serialized pipeline for visualization |
|
|
455
|
-
|
|
456
|
-
---
|
|
457
|
-
|
|
458
|
-
## How FootPrint Compares
|
|
459
|
-
|
|
460
|
-
FootPrint is a **code pattern**, not an orchestrator. It runs in your process, not as a separate service.
|
|
461
|
-
|
|
462
|
-
| Aspect | MVC / async-await | FootPrint (flowchart pattern) | Temporal / Step Functions |
|
|
463
|
-
|--------|-------------------|-------------------------------|--------------------------|
|
|
464
|
-
| **What it is** | Code pattern | Code pattern | External orchestrator |
|
|
465
|
-
| **Runs where** | In your process | In your process | Separate service |
|
|
466
|
-
| **Control flow** | Implicit in code | Explicit graph of functions | External state machine |
|
|
467
|
-
| **State** | Manual / global | Transactional scope | Durable storage |
|
|
468
|
-
| **AI explains decisions** | Parse logs (hallucination-prone) | Read the causal trace (accurate) | Parse event history |
|
|
469
|
-
| **Tool descriptions** | Write by hand | Auto-generated from structure | Write by hand |
|
|
470
|
-
| **Debugging** | Stack traces | Time-travel replay | Event history |
|
|
471
|
-
| **Complexity** | Low | Low-medium | High |
|
|
472
|
-
|
|
473
|
-
---
|
|
474
|
-
|
|
475
|
-
## Performance
|
|
476
|
-
|
|
477
|
-
Measured on Node v22, Apple Silicon. Run `npm run bench` to reproduce.
|
|
478
|
-
|
|
479
|
-
| Benchmark | Time | Detail |
|
|
480
|
-
|-----------|------|--------|
|
|
481
|
-
| **Write 1K keys** | 811µs | ~1.2M ops/s |
|
|
482
|
-
| **Write 10K keys** | 5.4ms | ~1.8M ops/s |
|
|
483
|
-
| **Read 100K keys** | 8.7ms | ~11.5M ops/s |
|
|
484
|
-
| **10 stages (linear)** | 106µs | 0.011ms/stage |
|
|
485
|
-
| **200 stages (linear)** | 4.7ms | 0.023ms/stage |
|
|
486
|
-
| **500 stages (linear)** | 20ms | 0.040ms/stage |
|
|
487
|
-
| **100 concurrent pipelines** | 2.3ms | 3-stage each |
|
|
488
|
-
| **1,000 concurrent pipelines** | 24ms | 3-stage each |
|
|
489
|
-
| **structuredClone 1KB** | 2µs | per call |
|
|
490
|
-
| **structuredClone 100KB** | 76µs | per call |
|
|
491
|
-
| **structuredClone 1MB** | 2.5ms | per call |
|
|
492
|
-
| **Time-travel 100 commits** | 75µs | 0.001ms/commit |
|
|
493
|
-
| **Time-travel 500 commits** | 385µs | 0.001ms/commit |
|
|
494
|
-
| **Commit with 100 writes** | 375µs | single stage |
|
|
495
|
-
|
|
496
|
-
**Bottom line:** A 200-stage pipeline completes in under 5ms. The primary cost at scale is `structuredClone` — keep state objects under 100KB per stage for sub-millisecond commit overhead.
|
|
497
|
-
|
|
498
|
-
---
|
|
499
|
-
|
|
500
|
-
## Contract & OpenAPI
|
|
501
|
-
|
|
502
|
-
Define I/O schemas (Zod or raw JSON Schema) and auto-generate OpenAPI 3.1 specs:
|
|
503
|
-
|
|
504
|
-
```typescript
|
|
505
|
-
import { flowChart, defineContract } from 'footprintjs';
|
|
506
|
-
import { z } from 'zod';
|
|
507
|
-
|
|
508
|
-
const contract = defineContract(chart, {
|
|
509
|
-
inputSchema: z.object({ applicantName: z.string(), creditScore: z.number() }),
|
|
510
|
-
outputSchema: z.object({ decision: z.enum(['approved', 'rejected']) }),
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
const spec = contract.toOpenAPI({ version: '1.0.0', basePath: '/api' });
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
Zod is an **optional peer dependency** — zero bundle impact if not used.
|
|
517
|
-
|
|
518
|
-
**[Full contracts guide →](docs/guides/contracts.md)** — defineContract, OpenAPI generation, Zod vs JSON Schema, builder-level schemas
|
|
519
|
-
|
|
520
|
-
---
|
|
521
|
-
|
|
522
|
-
## Architecture
|
|
523
|
-
|
|
524
|
-
FootPrint is the reference implementation of the flowchart pattern — six independent libraries, each usable standalone:
|
|
525
|
-
|
|
526
|
-
```
|
|
527
|
-
src/lib/
|
|
528
|
-
├── memory/ Transactional state (SharedMemory, StageContext, EventLog, TransactionBuffer)
|
|
529
|
-
├── builder/ Fluent flowchart DSL (FlowChartBuilder, DeciderList, SelectorFnList)
|
|
530
|
-
├── scope/ Scope facades, recorders, protection, Zod integration
|
|
531
|
-
├── engine/ DFS traversal, handlers, narrative generators
|
|
532
|
-
├── runner/ Execution convenience (FlowChartExecutor, ExecutionRuntime)
|
|
533
|
-
└── contract/ I/O schemas, Zod→JSON Schema, OpenAPI 3.1 generation
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
**[Architecture deep-dives →](docs/internals/)** — each library has its own README with primitives, design decisions, and dependency graphs
|
|
127
|
+
| **7 Patterns** | Linear, parallel fork, conditional, multi-select, subflow, streaming, loops — [guide](docs/guides/patterns.md) |
|
|
128
|
+
| **Transactional State** | Atomic commits, safe merges, time-travel replay — [guide](docs/guides/scope.md) |
|
|
129
|
+
| **PII Redaction** | Per-key or declarative `RedactionPolicy` with audit trail — [guide](docs/guides/scope.md#redaction-pii-protection) |
|
|
130
|
+
| **Flow Recorders** | 8 narrative strategies for loop compression — [guide](docs/guides/flow-recorders.md) |
|
|
131
|
+
| **Contracts** | I/O schemas (Zod/JSON Schema) + OpenAPI 3.1 generation — [guide](docs/guides/contracts.md) |
|
|
132
|
+
| **Cancellation** | AbortSignal, timeout, early termination via `breakFn()` — [guide](docs/guides/execution.md) |
|
|
537
133
|
|
|
538
134
|
---
|
|
539
135
|
|
|
540
136
|
## AI Coding Tool Support
|
|
541
137
|
|
|
542
|
-
FootPrint ships with built-in instructions for every major AI coding assistant. Your AI tool understands the
|
|
543
|
-
|
|
544
|
-
### Quick setup
|
|
138
|
+
FootPrint ships with built-in instructions for every major AI coding assistant. Your AI tool understands the API, patterns, and anti-patterns out of the box.
|
|
545
139
|
|
|
546
140
|
```bash
|
|
547
|
-
# After installing footprintjs, run:
|
|
548
141
|
npx footprintjs-setup
|
|
549
142
|
```
|
|
550
143
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
|
554
|
-
|
|
555
|
-
| **
|
|
556
|
-
| **
|
|
557
|
-
| **
|
|
558
|
-
| **
|
|
559
|
-
| **
|
|
560
|
-
| **[Cline](https://github.com/cline/cline)** | `.clinerules` | Project rules |
|
|
561
|
-
| **[Kiro](https://kiro.dev)** | `.kiro/rules/footprint.md` | Project rules |
|
|
144
|
+
| Tool | What gets installed |
|
|
145
|
+
|------|-------------------|
|
|
146
|
+
| **Claude Code** | `.claude/skills/footprint/SKILL.md` + `CLAUDE.md` |
|
|
147
|
+
| **OpenAI Codex** | `AGENTS.md` |
|
|
148
|
+
| **GitHub Copilot** | `.github/copilot-instructions.md` |
|
|
149
|
+
| **Cursor** | `.cursor/rules/footprint.md` |
|
|
150
|
+
| **Windsurf** | `.windsurfrules` |
|
|
151
|
+
| **Cline** | `.clinerules` |
|
|
152
|
+
| **Kiro** | `.kiro/rules/footprint.md` |
|
|
562
153
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
```bash
|
|
566
|
-
# Claude Code skill
|
|
567
|
-
mkdir -p .claude/skills/footprint
|
|
568
|
-
cp node_modules/footprintjs/ai-instructions/claude-code/SKILL.md .claude/skills/footprint/
|
|
569
|
-
|
|
570
|
-
# Cursor rules
|
|
571
|
-
mkdir -p .cursor/rules
|
|
572
|
-
cp node_modules/footprintjs/ai-instructions/cursor/footprint.md .cursor/rules/
|
|
573
|
-
|
|
574
|
-
# Any other tool — files are in node_modules/footprintjs/ai-instructions/
|
|
575
|
-
```
|
|
154
|
+
---
|
|
576
155
|
|
|
577
|
-
|
|
578
|
-
- The **Builder**, **Executor**, and **ScopeFacade** APIs
|
|
579
|
-
- The **recorder system** (scope + flow, two observer layers)
|
|
580
|
-
- The **core principle**: collect during traversal, never post-process
|
|
581
|
-
- **Anti-patterns** to avoid (deprecated APIs, wrong state access patterns)
|
|
582
|
-
- **Event ordering** that makes inline narrative collection work
|
|
156
|
+
## Documentation
|
|
583
157
|
|
|
584
|
-
|
|
158
|
+
| Resource | Link |
|
|
159
|
+
|----------|------|
|
|
160
|
+
| **Guides** | [Patterns](docs/guides/patterns.md) · [Scope](docs/guides/scope.md) · [Execution](docs/guides/execution.md) · [Errors](docs/guides/error-handling.md) · [Flow Recorders](docs/guides/flow-recorders.md) · [Contracts](docs/guides/contracts.md) |
|
|
161
|
+
| **Reference** | [API Reference](docs/guides/api-reference.md) · [Performance Benchmarks](docs/guides/performance.md) |
|
|
162
|
+
| **Architecture** | [Internals](docs/internals/) — six independent libraries, each with its own README |
|
|
163
|
+
| **Try it** | [Interactive Playground](https://footprintjs.github.io/footprint-playground/) · [Live Demo](https://footprintjs.github.io/footprint-demo/) · [25+ Examples](https://github.com/footprintjs/footPrint-samples) |
|
|
585
164
|
|
|
586
165
|
---
|
|
587
166
|
|
|
588
|
-
## License
|
|
589
|
-
|
|
590
167
|
[MIT](./LICENSE) © [Sanjay Krishna Anbalagan](https://github.com/sanjay1909)
|
package/package.json
CHANGED