footprintjs 0.10.1 → 0.10.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 +125 -0
- package/CLAUDE.md +127 -0
- package/README.md +48 -0
- package/ai-instructions/claude-code/SKILL.md +465 -0
- package/ai-instructions/clinerules +79 -0
- package/ai-instructions/copilot-instructions.md +86 -0
- package/ai-instructions/cursor/footprint.md +80 -0
- package/ai-instructions/kiro/footprint.md +79 -0
- package/ai-instructions/setup.sh +140 -0
- package/ai-instructions/windsurfrules +79 -0
- package/package.json +8 -2
package/AGENTS.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# footprint.js — AI Agent Instructions
|
|
2
|
+
|
|
3
|
+
> The flowchart pattern for backend code — self-explainable systems that AI can reason about.
|
|
4
|
+
|
|
5
|
+
## What This Library Does
|
|
6
|
+
|
|
7
|
+
footprint.js structures backend logic as a graph of named functions with transactional state. Every run auto-generates a causal trace showing what happened and why. An LLM reads the trace and explains decisions accurately — no hallucination.
|
|
8
|
+
|
|
9
|
+
## Core Principle
|
|
10
|
+
|
|
11
|
+
**Collect during traversal, never post-process.** All data collection (narrative, metrics, manifest) happens as side effects of the single DFS traversal pass. Never walk the tree again after execution.
|
|
12
|
+
|
|
13
|
+
## Architecture
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
src/lib/
|
|
17
|
+
├── memory/ → Transactional state (SharedMemory, StageContext, TransactionBuffer)
|
|
18
|
+
├── schema/ → Validation (Zod optional, duck-typed detection)
|
|
19
|
+
├── builder/ → Fluent DSL (FlowChartBuilder, flowChart())
|
|
20
|
+
├── scope/ → Per-stage facades + recorders + providers
|
|
21
|
+
├── engine/ → DFS traversal + narrative + handlers
|
|
22
|
+
├── runner/ → High-level executor (FlowChartExecutor)
|
|
23
|
+
└── contract/ → I/O schema + OpenAPI generation
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Two entry points:
|
|
27
|
+
- `import { ... } from 'footprintjs'` — public API
|
|
28
|
+
- `import { ... } from 'footprintjs/advanced'` — internals
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { flowChart, FlowChartExecutor } from 'footprintjs';
|
|
34
|
+
|
|
35
|
+
const chart = flowChart('ReceiveOrder', (scope) => {
|
|
36
|
+
scope.setValue('orderId', 'ORD-123');
|
|
37
|
+
scope.setValue('amount', 49.99);
|
|
38
|
+
}, 'receive-order', undefined, 'Receive the incoming order')
|
|
39
|
+
.addFunction('ProcessPayment', (scope) => {
|
|
40
|
+
const amount = scope.getValue('amount');
|
|
41
|
+
scope.setValue('paymentStatus', amount < 100 ? 'approved' : 'review');
|
|
42
|
+
}, 'process-payment', 'Charge customer')
|
|
43
|
+
.setEnableNarrative()
|
|
44
|
+
.build();
|
|
45
|
+
|
|
46
|
+
const executor = new FlowChartExecutor(chart);
|
|
47
|
+
await executor.run({ input: { orderId: 'ORD-123' } });
|
|
48
|
+
console.log(executor.getNarrative());
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Builder API
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
flowChart(name, fn, id, extractor?, description?) // start chain
|
|
55
|
+
.addFunction(name, fn, id, description?) // linear stage
|
|
56
|
+
.addDeciderFunction(name, fn, id, description?) // single-choice branch
|
|
57
|
+
.addFunctionBranch(branchId, name, fn) // branch option
|
|
58
|
+
.addSubFlowChartBranch(branchId, subflow) // subflow branch
|
|
59
|
+
.setDefault(branchId) // fallback
|
|
60
|
+
.end() // close decider
|
|
61
|
+
.addSelectorFunction(name, fn, id, description?) // multi-choice fan-out
|
|
62
|
+
.addListOfFunction([...], { failFast? }) // parallel fork
|
|
63
|
+
.addSubFlowChartNext(id, subflow, mount, opts?) // mount subflow inline
|
|
64
|
+
.loopTo(stageId) // back-edge loop
|
|
65
|
+
.setEnableNarrative() // enable narrative
|
|
66
|
+
.setInputSchema(schema) // Zod or JSON Schema
|
|
67
|
+
.build() // compile
|
|
68
|
+
.toSpec() // JSON structure
|
|
69
|
+
.toMermaid() // diagram
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Stage Function Signature
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
(scope: ScopeFacade, breakPipeline: () => void, streamCallback?: StreamCallback) => void | Promise<void>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## ScopeFacade (per-stage state access)
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
scope.getValue('key') // tracked read → appears in narrative
|
|
82
|
+
scope.setValue('key', value) // tracked write → appears in narrative
|
|
83
|
+
scope.updateValue('key', partial) // deep merge (tracked)
|
|
84
|
+
scope.deleteValue('key') // tracked delete
|
|
85
|
+
scope.getArgs<T>() // frozen readonly input (NOT tracked)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Executor
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
const executor = new FlowChartExecutor(chart);
|
|
92
|
+
await executor.run({ input, timeoutMs?, signal? });
|
|
93
|
+
|
|
94
|
+
executor.getNarrative() // string[] — combined flow + data
|
|
95
|
+
executor.getNarrativeEntries() // CombinedNarrativeEntry[] — structured
|
|
96
|
+
executor.getSnapshot() // full memory state
|
|
97
|
+
executor.attachFlowRecorder(r) // plug observer before run()
|
|
98
|
+
executor.setRedactionPolicy({ // PII protection
|
|
99
|
+
keys: ['ssn'], patterns: [/password/i]
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Two Observer Systems
|
|
104
|
+
|
|
105
|
+
**Scope Recorder** — fires DURING stage: `onRead`, `onWrite`, `onCommit`, `onStageStart/End`
|
|
106
|
+
**FlowRecorder** — fires AFTER stage: `onStageExecuted`, `onDecision`, `onFork`, `onNext`, `onLoop`, `onError`
|
|
107
|
+
|
|
108
|
+
8 built-in FlowRecorder strategies: Narrative, Adaptive, Windowed, RLE, Milestone, Progressive, Separate, Manifest, Silent.
|
|
109
|
+
|
|
110
|
+
`CombinedNarrativeRecorder` implements BOTH — auto-attached by `setEnableNarrative()`.
|
|
111
|
+
|
|
112
|
+
## Anti-Patterns
|
|
113
|
+
|
|
114
|
+
- Never post-process the tree — use recorders
|
|
115
|
+
- Never use deprecated `CombinedNarrativeBuilder`
|
|
116
|
+
- Don't use `getArgs()` for tracked data — use `getValue()`/`setValue()`
|
|
117
|
+
- Don't manually create `CombinedNarrativeRecorder` — `setEnableNarrative()` handles it
|
|
118
|
+
|
|
119
|
+
## Build
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm install footprintjs
|
|
123
|
+
npm run build # CJS + ESM dual output
|
|
124
|
+
npm test
|
|
125
|
+
```
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# footprint.js — AI Coding 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 (narrative, metrics, manifest, identity) happens as side effects of the single DFS traversal pass. Never walk the tree again after execution.
|
|
8
|
+
|
|
9
|
+
## Architecture — Library of Libraries
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
src/lib/
|
|
13
|
+
├── memory/ → Transactional state (SharedMemory, StageContext, TransactionBuffer, EventLog)
|
|
14
|
+
├── schema/ → Validation abstraction (Zod optional, duck-typed detection)
|
|
15
|
+
├── builder/ → Fluent DSL (FlowChartBuilder, flowChart(), DeciderList, SelectorFnList)
|
|
16
|
+
├── scope/ → Per-stage facades + recorders + providers (largest module)
|
|
17
|
+
├── engine/ → DFS traversal + narrative + 13 handlers
|
|
18
|
+
├── runner/ → High-level executor (FlowChartExecutor)
|
|
19
|
+
└── contract/ → I/O schema + OpenAPI generation
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Dependency DAG: `memory ← scope ← engine ← runner`, `schema ← engine`, `builder (standalone) → engine`, `contract ← schema`
|
|
23
|
+
|
|
24
|
+
Two entry points:
|
|
25
|
+
- `import { ... } from 'footprintjs'` — public API
|
|
26
|
+
- `import { ... } from 'footprintjs/advanced'` — internals
|
|
27
|
+
|
|
28
|
+
## Key API
|
|
29
|
+
|
|
30
|
+
### Builder
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { flowChart, FlowChartBuilder } from 'footprintjs';
|
|
34
|
+
|
|
35
|
+
// flowChart(name, fn, id, buildTimeExtractor?, description?)
|
|
36
|
+
const chart = flowChart('Stage1', fn1, 'stage-1', undefined, 'Description')
|
|
37
|
+
.addFunction('Stage2', fn2, 'stage-2', 'Description')
|
|
38
|
+
.addDeciderFunction('Decide', deciderFn, 'decide', 'Route based on risk')
|
|
39
|
+
.addFunctionBranch('high', 'Reject', rejectFn)
|
|
40
|
+
.addFunctionBranch('low', 'Approve', approveFn)
|
|
41
|
+
.setDefault('high')
|
|
42
|
+
.end()
|
|
43
|
+
.setEnableNarrative()
|
|
44
|
+
.build();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Methods: `start()`, `addFunction()`, `addStreamingFunction()`, `addDeciderFunction()`, `addSelectorFunction()`, `addListOfFunction()`, `addSubFlowChart()`, `addSubFlowChartNext()`, `loopTo()`, `setEnableNarrative()`, `setInputSchema()`, `setOutputSchema()`, `setOutputMapper()`, `build()`, `toSpec()`, `toMermaid()`
|
|
48
|
+
|
|
49
|
+
### Stage Functions
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
type PipelineStageFunction = (
|
|
53
|
+
scope: ScopeFacade,
|
|
54
|
+
breakPipeline: () => void,
|
|
55
|
+
streamCallback?: StreamCallback,
|
|
56
|
+
) => Promise<void> | void;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### ScopeFacade
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
scope.getValue('key') // tracked read (appears in narrative)
|
|
63
|
+
scope.setValue('key', value) // tracked write
|
|
64
|
+
scope.updateValue('key', partial) // deep merge
|
|
65
|
+
scope.deleteValue('key') // tracked delete
|
|
66
|
+
scope.getArgs<T>() // frozen readonly input (NOT tracked)
|
|
67
|
+
scope.attachRecorder(recorder) // plug observer
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**IMPORTANT:** `getValue`/`setValue` are tracked. `getArgs()` is NOT tracked and returns frozen input.
|
|
71
|
+
|
|
72
|
+
### Executor
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const executor = new FlowChartExecutor(chart);
|
|
76
|
+
await executor.run({ input: data, timeoutMs: 5000, signal: abortSignal });
|
|
77
|
+
|
|
78
|
+
executor.getNarrative() // combined flow + data narrative
|
|
79
|
+
executor.getNarrativeEntries() // structured entries with type/depth/stageName
|
|
80
|
+
executor.getFlowNarrative() // flow-only (no data ops)
|
|
81
|
+
executor.getSnapshot() // full memory state
|
|
82
|
+
executor.attachFlowRecorder(r) // plug flow observer
|
|
83
|
+
executor.setRedactionPolicy({}) // PII protection
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Two Observer Systems
|
|
87
|
+
|
|
88
|
+
Both use `{ id, hooks } → dispatcher → error isolation → attach/detach`. Intentionally NOT unified.
|
|
89
|
+
|
|
90
|
+
**Scope Recorder** (data ops — fires DURING stage execution):
|
|
91
|
+
- `onRead`, `onWrite`, `onCommit`, `onError`, `onStageStart`, `onStageEnd`
|
|
92
|
+
- Built-in: `NarrativeRecorder`, `MetricRecorder`, `DebugRecorder`
|
|
93
|
+
|
|
94
|
+
**FlowRecorder** (control flow — fires AFTER stage execution):
|
|
95
|
+
- `onStageExecuted`, `onNext`, `onDecision`, `onFork`, `onSelected`, `onSubflowEntry/Exit`, `onLoop`, `onBreak`, `onError`
|
|
96
|
+
- Built-in: 8 strategies (Narrative, Adaptive, Windowed, RLE, Milestone, Progressive, Separate, Manifest, Silent)
|
|
97
|
+
|
|
98
|
+
**CombinedNarrativeRecorder** implements BOTH interfaces. Auto-attached by `setEnableNarrative()`.
|
|
99
|
+
|
|
100
|
+
## Event Ordering
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
1. Recorder.onStageStart — stage begins
|
|
104
|
+
2. Recorder.onRead/onWrite — DURING execution (buffered per-stage)
|
|
105
|
+
3. Recorder.onCommit — transaction flush
|
|
106
|
+
4. Recorder.onStageEnd — stage completes
|
|
107
|
+
5. FlowRecorder.onStageExecuted — CombinedNarrativeRecorder flushes buffered ops
|
|
108
|
+
6. FlowRecorder.onNext/onDecision/onFork — control flow continues
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Anti-Patterns
|
|
112
|
+
|
|
113
|
+
- Never post-process the tree — use recorders
|
|
114
|
+
- Never use deprecated `CombinedNarrativeBuilder` — use `CombinedNarrativeRecorder`
|
|
115
|
+
- Don't extract shared base for Recorder/FlowRecorder — two instances = coincidence
|
|
116
|
+
- Don't use `getArgs()` for tracked data — use `getValue()`/`setValue()`
|
|
117
|
+
- Don't manually create `CombinedNarrativeRecorder` — `setEnableNarrative()` handles it
|
|
118
|
+
|
|
119
|
+
## Build & Test
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm run build # tsc (CJS) + tsc -p tsconfig.esm.json (ESM)
|
|
123
|
+
npm test # full suite
|
|
124
|
+
npm run test:unit
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Dual output: CommonJS (`dist/`) + ESM (`dist/esm/`) + types (`dist/types/`)
|
package/README.md
CHANGED
|
@@ -537,6 +537,54 @@ src/lib/
|
|
|
537
537
|
|
|
538
538
|
---
|
|
539
539
|
|
|
540
|
+
## AI Coding Tool Support
|
|
541
|
+
|
|
542
|
+
FootPrint ships with built-in instructions for every major AI coding assistant. Your AI tool understands the library's API, patterns, and anti-patterns out of the box.
|
|
543
|
+
|
|
544
|
+
### Quick setup
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
# After installing footprintjs, run:
|
|
548
|
+
npx footprintjs-setup
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
This interactive installer copies the right instruction files for your tool(s):
|
|
552
|
+
|
|
553
|
+
| Tool | What gets installed | Format |
|
|
554
|
+
|------|-------------------|--------|
|
|
555
|
+
| **[Claude Code](https://claude.com/claude-code)** | `.claude/skills/footprint/SKILL.md` + `CLAUDE.md` | Interactive skill + project rules |
|
|
556
|
+
| **[OpenAI Codex](https://openai.com/index/codex/)** | `AGENTS.md` | Agent instructions |
|
|
557
|
+
| **[GitHub Copilot](https://github.com/features/copilot)** | `.github/copilot-instructions.md` | Custom instructions |
|
|
558
|
+
| **[Cursor](https://cursor.com)** | `.cursor/rules/footprint.md` | Project rules |
|
|
559
|
+
| **[Windsurf](https://windsurf.com)** | `.windsurfrules` | Project rules |
|
|
560
|
+
| **[Cline](https://github.com/cline/cline)** | `.clinerules` | Project rules |
|
|
561
|
+
| **[Kiro](https://kiro.dev)** | `.kiro/rules/footprint.md` | Project rules |
|
|
562
|
+
|
|
563
|
+
Or install manually:
|
|
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
|
+
```
|
|
576
|
+
|
|
577
|
+
Every file teaches the AI assistant:
|
|
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
|
|
583
|
+
|
|
584
|
+
This means any AI coding tool can help you build flowchart pipelines correctly from day one.
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
540
588
|
## License
|
|
541
589
|
|
|
542
590
|
[MIT](./LICENSE) © [Sanjay Krishna Anbalagan](https://github.com/sanjay1909)
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: footprint
|
|
3
|
+
description: Use when building flowchart pipelines with footprintjs — stage functions, decider branches, selectors, subflows, loops, narrative traces, recorders, redaction, contracts, and LLM-ready output. Also use when someone asks how footprint.js works or wants to understand the library.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# footprint.js — The Flowchart Pattern for Backend Code
|
|
7
|
+
|
|
8
|
+
footprint.js structures backend logic as a graph of named functions with transactional state. The code becomes self-explainable: every run auto-generates a causal trace of what happened and why.
|
|
9
|
+
|
|
10
|
+
**Core principle:** All data collection happens during the single DFS traversal pass — never post-process or walk the tree again.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install footprintjs
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { flowChart, FlowChartExecutor } from 'footprintjs';
|
|
22
|
+
|
|
23
|
+
const chart = flowChart('ReceiveOrder', (scope) => {
|
|
24
|
+
scope.setValue('orderId', 'ORD-123');
|
|
25
|
+
scope.setValue('amount', 49.99);
|
|
26
|
+
}, 'receive-order', undefined, 'Receive and validate the incoming order')
|
|
27
|
+
.addFunction('ProcessPayment', (scope) => {
|
|
28
|
+
const amount = scope.getValue('amount');
|
|
29
|
+
scope.setValue('paymentStatus', amount < 100 ? 'approved' : 'review');
|
|
30
|
+
}, 'process-payment', 'Charge customer and record payment status')
|
|
31
|
+
.setEnableNarrative()
|
|
32
|
+
.build();
|
|
33
|
+
|
|
34
|
+
const executor = new FlowChartExecutor(chart);
|
|
35
|
+
await executor.run({ input: { orderId: 'ORD-123' } });
|
|
36
|
+
|
|
37
|
+
console.log(executor.getNarrative());
|
|
38
|
+
// Stage 1: The process began with ReceiveOrder.
|
|
39
|
+
// Step 1: Write orderId = "ORD-123"
|
|
40
|
+
// Step 2: Write amount = 49.99
|
|
41
|
+
// Stage 2: Next, it moved on to ProcessPayment.
|
|
42
|
+
// Step 1: Read amount = 49.99
|
|
43
|
+
// Step 2: Write paymentStatus = "approved"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## FlowChartBuilder API
|
|
49
|
+
|
|
50
|
+
Always chain from `flowChart()` or `new FlowChartBuilder()`.
|
|
51
|
+
|
|
52
|
+
### Linear Stages
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { flowChart } from 'footprintjs';
|
|
56
|
+
|
|
57
|
+
const chart = flowChart('StageA', fnA, 'stage-a', undefined, 'Description of A')
|
|
58
|
+
.addFunction('StageB', fnB, 'stage-b', 'Description of B')
|
|
59
|
+
.addFunction('StageC', fnC, 'stage-c', 'Description of C')
|
|
60
|
+
.build();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Parameters:** `(name: string, fn: PipelineStageFunction, id: string, description?: string)`
|
|
64
|
+
|
|
65
|
+
- `name` — human-readable label (used in narrative)
|
|
66
|
+
- `fn` — the stage function
|
|
67
|
+
- `id` — stable identifier (used for branching, visualization, loop targets)
|
|
68
|
+
- `description` — optional, appears in narrative and auto-generated tool descriptions
|
|
69
|
+
|
|
70
|
+
### Stage Function Signature
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
type PipelineStageFunction = (
|
|
74
|
+
scope: ScopeFacade, // read/write transactional state
|
|
75
|
+
breakPipeline: () => void, // call to stop execution early
|
|
76
|
+
streamCallback?: StreamCallback, // for streaming stages
|
|
77
|
+
) => Promise<void> | void;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### ScopeFacade — State Access
|
|
81
|
+
|
|
82
|
+
Every stage receives a `ScopeFacade` that tracks all reads and writes:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const myStage = (scope: ScopeFacade) => {
|
|
86
|
+
// Read (tracked — appears in narrative)
|
|
87
|
+
const name = scope.getValue('applicantName') as string;
|
|
88
|
+
|
|
89
|
+
// Write (tracked — appears in narrative)
|
|
90
|
+
scope.setValue('greeting', `Hello, ${name}!`);
|
|
91
|
+
|
|
92
|
+
// Update (deep merge for objects)
|
|
93
|
+
scope.updateValue('profile', { verified: true });
|
|
94
|
+
|
|
95
|
+
// Delete
|
|
96
|
+
scope.deleteValue('tempData');
|
|
97
|
+
|
|
98
|
+
// Readonly input (frozen, not tracked in narrative)
|
|
99
|
+
const config = scope.getArgs<{ maxRetries: number }>();
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**IMPORTANT:** `getValue`/`setValue` are tracked and produce narrative. `getArgs()` returns frozen readonly input and is NOT tracked.
|
|
104
|
+
|
|
105
|
+
### Decider Branches (Single-Choice Conditional)
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const chart = flowChart('Intake', intakeFn, 'intake')
|
|
109
|
+
.addDeciderFunction('AssessRisk', (scope) => {
|
|
110
|
+
const score = scope.getValue('riskScore') as number;
|
|
111
|
+
// Return the branch ID to take
|
|
112
|
+
return score > 70 ? 'high-risk' : 'low-risk';
|
|
113
|
+
}, 'assess-risk', 'Evaluate risk and route accordingly')
|
|
114
|
+
.addFunctionBranch('high-risk', 'RejectApplication', rejectFn, 'Reject due to high risk')
|
|
115
|
+
.addFunctionBranch('low-risk', 'ApproveApplication', approveFn, 'Approve the application')
|
|
116
|
+
.setDefault('high-risk') // fallback if branch ID doesn't match
|
|
117
|
+
.end()
|
|
118
|
+
.build();
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The decider function **returns a branch ID string**. The engine matches it to a child and executes that branch. The decision is recorded in the narrative.
|
|
122
|
+
|
|
123
|
+
### Selector Branches (Multi-Choice Fan-Out)
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const chart = flowChart('Intake', intakeFn, 'intake')
|
|
127
|
+
.addSelectorFunction('SelectChecks', (scope) => {
|
|
128
|
+
const checks = [];
|
|
129
|
+
if (scope.getValue('needsCredit')) checks.push('credit-check');
|
|
130
|
+
if (scope.getValue('needsIdentity')) checks.push('identity-check');
|
|
131
|
+
// Return array of branch IDs to execute in parallel
|
|
132
|
+
return checks;
|
|
133
|
+
}, 'select-checks')
|
|
134
|
+
.addFunctionBranch('credit-check', 'CreditCheck', creditFn)
|
|
135
|
+
.addFunctionBranch('identity-check', 'IdentityCheck', identityFn)
|
|
136
|
+
.end()
|
|
137
|
+
.build();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Parallel Execution (Fork)
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
builder.addListOfFunction([
|
|
144
|
+
{ id: 'check-a', name: 'CheckA', fn: checkAFn },
|
|
145
|
+
{ id: 'check-b', name: 'CheckB', fn: checkBFn },
|
|
146
|
+
{ id: 'check-c', name: 'CheckC', fn: checkCFn },
|
|
147
|
+
], { failFast: true }); // reject on first error
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Subflows (Nested Flowcharts)
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// Build a reusable sub-pipeline
|
|
154
|
+
const creditSubflow = flowChart('PullReport', pullReportFn, 'pull-report')
|
|
155
|
+
.addFunction('ScoreReport', scoreReportFn, 'score-report')
|
|
156
|
+
.build();
|
|
157
|
+
|
|
158
|
+
// Mount as linear continuation
|
|
159
|
+
builder.addSubFlowChartNext('credit-sub', creditSubflow, 'CreditCheck', {
|
|
160
|
+
inputMapper: (parentScope) => ({ ssn: parentScope.getValue('ssn') }),
|
|
161
|
+
outputMapper: (subOut, parentScope) => ({ creditScore: subOut.score }),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Mount as decider branch
|
|
165
|
+
builder.addDeciderFunction('Route', routerFn, 'route')
|
|
166
|
+
.addSubFlowChartBranch('detailed', creditSubflow, 'DetailedCheck')
|
|
167
|
+
.addFunctionBranch('simple', 'SimpleCheck', simpleFn)
|
|
168
|
+
.end();
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Loops
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
builder
|
|
175
|
+
.addFunction('RetryPayment', async (scope) => {
|
|
176
|
+
const attempts = (scope.getValue('attempts') as number ?? 0) + 1;
|
|
177
|
+
scope.setValue('attempts', attempts);
|
|
178
|
+
if (attempts >= 3) return; // exit loop by not looping
|
|
179
|
+
}, 'retry-payment')
|
|
180
|
+
.loopTo('retry-payment'); // back-edge to this stage's ID
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Configuration
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
builder
|
|
187
|
+
.setEnableNarrative() // enable narrative recording
|
|
188
|
+
.setInputSchema(zodSchema) // validate input (Zod or JSON Schema)
|
|
189
|
+
.setOutputSchema(outputZodSchema) // declare output shape
|
|
190
|
+
.setOutputMapper((state) => ({ // map final state to response
|
|
191
|
+
decision: state.decision,
|
|
192
|
+
reason: state.reason,
|
|
193
|
+
}));
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Output
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
const chart = builder.build(); // FlowChart object (for executor)
|
|
200
|
+
const spec = builder.toSpec(); // JSON-safe structure (for visualization)
|
|
201
|
+
const mermaid = builder.toMermaid(); // Mermaid diagram string
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## FlowChartExecutor API
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { FlowChartExecutor } from 'footprintjs';
|
|
210
|
+
|
|
211
|
+
const executor = new FlowChartExecutor(chart);
|
|
212
|
+
|
|
213
|
+
// Run with input
|
|
214
|
+
const result = await executor.run({
|
|
215
|
+
input: { applicantName: 'Bob', income: 42000 },
|
|
216
|
+
timeoutMs: 5000, // optional auto-abort
|
|
217
|
+
signal: controller.signal, // optional AbortSignal
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Get narrative (combined flow + data operations)
|
|
221
|
+
const narrative: string[] = executor.getNarrative();
|
|
222
|
+
// ["Stage 1: The process began with ReceiveApplication.",
|
|
223
|
+
// " Step 1: Write app = {applicantName, income, ...}",
|
|
224
|
+
// "Stage 2: Next, it moved on to AssessRisk.",
|
|
225
|
+
// " Step 1: Read app = ...",
|
|
226
|
+
// " Step 2: Write riskTier = \"high\"",
|
|
227
|
+
// "[Condition]: A decision was made, path taken was RejectApplication."]
|
|
228
|
+
|
|
229
|
+
// Structured entries (for programmatic access)
|
|
230
|
+
const entries: CombinedNarrativeEntry[] = executor.getNarrativeEntries();
|
|
231
|
+
// [{ type: 'stage', text: '...', depth: 0, stageName: 'ReceiveApplication' },
|
|
232
|
+
// { type: 'step', text: 'Write app = ...', depth: 1, stageName: 'ReceiveApplication', stepNumber: 1 },
|
|
233
|
+
// { type: 'condition', text: '...', depth: 0 }]
|
|
234
|
+
|
|
235
|
+
// Full memory snapshot
|
|
236
|
+
const snapshot = executor.getSnapshot();
|
|
237
|
+
// { sharedState: { ... }, commitLog: [...], subflowResults: Map }
|
|
238
|
+
|
|
239
|
+
// Flow-only narrative (no data operations)
|
|
240
|
+
const flowOnly: string[] = executor.getFlowNarrative();
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Recorder System — Collect During Traversal
|
|
246
|
+
|
|
247
|
+
**The core innovation.** Two observer layers fire during the single DFS pass:
|
|
248
|
+
|
|
249
|
+
### Scope Recorders (data operations)
|
|
250
|
+
|
|
251
|
+
Attached to `ScopeFacade`, fire during `getValue()`/`setValue()`:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { MetricRecorder, DebugRecorder, NarrativeRecorder } from 'footprintjs';
|
|
255
|
+
|
|
256
|
+
// Built-in recorders
|
|
257
|
+
const metrics = new MetricRecorder();
|
|
258
|
+
const debug = new DebugRecorder('verbose'); // or 'minimal'
|
|
259
|
+
|
|
260
|
+
// Attach to scope (usually via scope factory)
|
|
261
|
+
scope.attachRecorder(metrics);
|
|
262
|
+
scope.attachRecorder(debug);
|
|
263
|
+
|
|
264
|
+
// After execution
|
|
265
|
+
metrics.getSummary(); // { totalReads: 12, totalWrites: 8, stages: {...} }
|
|
266
|
+
debug.getEntries(); // [{ type: 'read', key: 'app', value: {...}, stage: 'Intake' }, ...]
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### FlowRecorders (control flow events)
|
|
270
|
+
|
|
271
|
+
Attached to executor, fire after each stage/decision/fork:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import { NarrativeFlowRecorder, AdaptiveNarrativeFlowRecorder } from 'footprintjs';
|
|
275
|
+
|
|
276
|
+
// Attach before run()
|
|
277
|
+
executor.attachFlowRecorder(new NarrativeFlowRecorder());
|
|
278
|
+
|
|
279
|
+
// 8 built-in strategies:
|
|
280
|
+
// NarrativeFlowRecorder — all events as sentences (default)
|
|
281
|
+
// AdaptiveNarrativeFlowRecorder — full detail then sampling for loops
|
|
282
|
+
// WindowedNarrativeFlowRecorder — keep last N iterations only
|
|
283
|
+
// RLENarrativeFlowRecorder — run-length encode repeated loops
|
|
284
|
+
// MilestoneNarrativeFlowRecorder — only decisions, errors, subflows
|
|
285
|
+
// ProgressiveNarrativeFlowRecorder — progress markers for streaming UIs
|
|
286
|
+
// SeparateNarrativeFlowRecorder — dual channels (main + loop detail)
|
|
287
|
+
// ManifestFlowRecorder — subflow tree + spec catalog for LLM exploration
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Custom FlowRecorder
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
import type { FlowRecorder, FlowStageEvent, FlowDecisionEvent } from 'footprintjs';
|
|
294
|
+
|
|
295
|
+
const myRecorder: FlowRecorder = {
|
|
296
|
+
id: 'my-recorder',
|
|
297
|
+
onStageExecuted(event: FlowStageEvent) {
|
|
298
|
+
console.log(`Executed: ${event.stageName}`);
|
|
299
|
+
},
|
|
300
|
+
onDecision(event: FlowDecisionEvent) {
|
|
301
|
+
console.log(`Decision at ${event.stageName}: chose ${event.chosen}`);
|
|
302
|
+
},
|
|
303
|
+
clear() {
|
|
304
|
+
// Reset state before each run
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
executor.attachFlowRecorder(myRecorder);
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### CombinedNarrativeRecorder (the inline dual-channel recorder)
|
|
312
|
+
|
|
313
|
+
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.
|
|
314
|
+
|
|
315
|
+
**You don't need to create this manually.** Calling `.setEnableNarrative()` on the builder auto-attaches it.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Redaction (PII Protection)
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
executor.setRedactionPolicy({
|
|
323
|
+
keys: ['ssn', 'creditCardNumber'], // exact key names
|
|
324
|
+
patterns: [/password/i, /^secret.*/], // regex patterns
|
|
325
|
+
fields: { applicant: ['ssn', 'address.zip'] }, // nested field paths
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
await executor.run({ input: { ... } });
|
|
329
|
+
|
|
330
|
+
// Narrative shows: Write ssn = [REDACTED]
|
|
331
|
+
// Recorders receive scrubbed values
|
|
332
|
+
const report = executor.getRedactionReport();
|
|
333
|
+
// { redactedKeys: ['ssn'], patternsMatched: [...] } — never contains actual values
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Contracts & OpenAPI
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import { defineContract, generateOpenAPI } from 'footprintjs';
|
|
342
|
+
import { z } from 'zod';
|
|
343
|
+
|
|
344
|
+
const contract = defineContract(chart, {
|
|
345
|
+
inputSchema: z.object({
|
|
346
|
+
applicantName: z.string(),
|
|
347
|
+
income: z.number(),
|
|
348
|
+
}),
|
|
349
|
+
outputSchema: z.object({
|
|
350
|
+
decision: z.enum(['approved', 'rejected']),
|
|
351
|
+
reason: z.string(),
|
|
352
|
+
}),
|
|
353
|
+
outputMapper: (state) => ({
|
|
354
|
+
decision: state.decision,
|
|
355
|
+
reason: state.reason,
|
|
356
|
+
}),
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const openApiSpec = generateOpenAPI(contract, {
|
|
360
|
+
title: 'Loan Underwriting API',
|
|
361
|
+
version: '1.0.0',
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Event Ordering (Critical for Understanding)
|
|
368
|
+
|
|
369
|
+
When a stage executes, events fire in this exact order:
|
|
370
|
+
|
|
371
|
+
```
|
|
372
|
+
1. Recorder.onStageStart — stage begins
|
|
373
|
+
2. Recorder.onRead — each getValue() call (DURING execution)
|
|
374
|
+
3. Recorder.onWrite — each setValue() call (DURING execution)
|
|
375
|
+
4. Recorder.onCommit — transaction buffer flushes to shared memory
|
|
376
|
+
5. Recorder.onStageEnd — stage completes
|
|
377
|
+
6. FlowRecorder.onStageExecuted — control flow records the stage
|
|
378
|
+
(CombinedNarrativeRecorder flushes buffered ops here)
|
|
379
|
+
7. FlowRecorder.onNext — moving to next stage
|
|
380
|
+
OR FlowRecorder.onDecision — if this was a decider
|
|
381
|
+
OR FlowRecorder.onFork — if children execute in parallel
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**This ordering is what makes inline collection work.** Scope events buffer during execution, flow events trigger the flush.
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Anti-Patterns to Avoid
|
|
389
|
+
|
|
390
|
+
1. **Never post-process the tree.** Don't walk the spec after execution to collect data. Use recorders.
|
|
391
|
+
2. **Never use `CombinedNarrativeBuilder`** — it's deprecated. Use `CombinedNarrativeRecorder` (auto-attached by `setEnableNarrative()`).
|
|
392
|
+
3. **Don't extract a shared base class** for Recorder and FlowRecorder. They look similar but serve different layers. Two instances = coincidence.
|
|
393
|
+
4. **Don't call `getArgs()` for tracked data.** `getArgs()` returns frozen readonly input. Use `getValue()`/`setValue()` for state that should appear in the narrative.
|
|
394
|
+
5. **Don't create scope recorders manually** unless building a custom recorder. `setEnableNarrative()` handles everything.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Library Structure (for contributors)
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
src/lib/
|
|
402
|
+
├── memory/ → SharedMemory, StageContext, TransactionBuffer, EventLog (foundation)
|
|
403
|
+
├── schema/ → detectSchema, validate, InputValidationError (foundation)
|
|
404
|
+
├── builder/ → FlowChartBuilder, flowChart(), DeciderList, SelectorFnList (standalone)
|
|
405
|
+
├── scope/ → ScopeFacade, recorders/, providers/, protection/ (depends: memory)
|
|
406
|
+
├── engine/ → FlowchartTraverser, handlers/, narrative/ (depends: memory, scope, builder)
|
|
407
|
+
├── runner/ → FlowChartExecutor, ExecutionRuntime (depends: engine, scope, schema)
|
|
408
|
+
└── contract/ → defineContract, generateOpenAPI (depends: schema)
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Two entry points:
|
|
412
|
+
- `import { ... } from 'footprintjs'` — public API
|
|
413
|
+
- `import { ... } from 'footprintjs/advanced'` — internals (memory, traverser, handlers)
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Common Patterns
|
|
418
|
+
|
|
419
|
+
### Pipeline with decision + narrative
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
const chart = flowChart('Receive', receiveFn, 'receive')
|
|
423
|
+
.addFunction('Analyze', analyzeFn, 'analyze')
|
|
424
|
+
.addDeciderFunction('Decide', decideFn, 'decide')
|
|
425
|
+
.addFunctionBranch('approve', 'Approve', approveFn)
|
|
426
|
+
.addFunctionBranch('reject', 'Reject', rejectFn)
|
|
427
|
+
.setDefault('reject')
|
|
428
|
+
.end()
|
|
429
|
+
.setEnableNarrative()
|
|
430
|
+
.build();
|
|
431
|
+
|
|
432
|
+
const executor = new FlowChartExecutor(chart);
|
|
433
|
+
await executor.run({ input: data });
|
|
434
|
+
const trace = executor.getNarrative();
|
|
435
|
+
// Feed trace to LLM for grounded explanations
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Subflow with input/output mapping
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
const subflow = flowChart('SubStart', subStartFn, 'sub-start')
|
|
442
|
+
.addFunction('SubProcess', subProcessFn, 'sub-process')
|
|
443
|
+
.build();
|
|
444
|
+
|
|
445
|
+
const main = flowChart('Main', mainFn, 'main')
|
|
446
|
+
.addSubFlowChartNext('my-subflow', subflow, 'SubflowMount', {
|
|
447
|
+
inputMapper: (scope) => ({ key: scope.getValue('parentKey') }),
|
|
448
|
+
outputMapper: (subOut) => ({ result: subOut.processed }),
|
|
449
|
+
})
|
|
450
|
+
.build();
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Attach multiple recorders
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import { ManifestFlowRecorder, MilestoneNarrativeFlowRecorder } from 'footprintjs';
|
|
457
|
+
|
|
458
|
+
executor.attachFlowRecorder(new ManifestFlowRecorder());
|
|
459
|
+
executor.attachFlowRecorder(new MilestoneNarrativeFlowRecorder());
|
|
460
|
+
|
|
461
|
+
await executor.run({ input: data });
|
|
462
|
+
|
|
463
|
+
const manifest = executor.getSubflowManifest(); // subflow catalog
|
|
464
|
+
const milestones = executor.getFlowNarrative(); // key events only
|
|
465
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# footprint.js — Cline 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
|
+
├── engine/ → DFS traversal + narrative + handlers
|
|
18
|
+
├── runner/ → FlowChartExecutor
|
|
19
|
+
└── contract/ → I/O schema + OpenAPI
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
|
|
23
|
+
|
|
24
|
+
## Builder API
|
|
25
|
+
|
|
26
|
+
```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)
|
|
35
|
+
.setEnableNarrative()
|
|
36
|
+
.build() / .toSpec() / .toMermaid()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Stage Functions
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
(scope: ScopeFacade, breakPipeline: () => void, streamCallback?: StreamCallback) => void | Promise<void>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## ScopeFacade
|
|
46
|
+
|
|
47
|
+
```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)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Executor
|
|
56
|
+
|
|
57
|
+
```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
|
|
62
|
+
executor.getSnapshot() // memory state
|
|
63
|
+
executor.attachFlowRecorder(r) // plug flow observer
|
|
64
|
+
executor.setRedactionPolicy({ keys, patterns, fields })
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Observer Systems
|
|
68
|
+
|
|
69
|
+
- **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
|
|
70
|
+
- **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()`
|
|
73
|
+
|
|
74
|
+
## Rules
|
|
75
|
+
|
|
76
|
+
- 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
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# footprint.js — Copilot Instructions
|
|
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.
|
|
8
|
+
|
|
9
|
+
## Core Principle
|
|
10
|
+
|
|
11
|
+
**Collect during traversal, never post-process.** All data collection happens as side effects of the single DFS traversal. Never walk the tree after execution.
|
|
12
|
+
|
|
13
|
+
## Architecture
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
src/lib/
|
|
17
|
+
├── memory/ → Transactional state (SharedMemory, StageContext, TransactionBuffer)
|
|
18
|
+
├── schema/ → Validation (Zod optional, duck-typed)
|
|
19
|
+
├── builder/ → Fluent DSL (FlowChartBuilder, flowChart())
|
|
20
|
+
├── scope/ → Per-stage facades + recorders + providers
|
|
21
|
+
├── engine/ → DFS traversal + narrative + handlers
|
|
22
|
+
├── runner/ → FlowChartExecutor
|
|
23
|
+
└── contract/ → I/O schema + OpenAPI
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
|
|
27
|
+
|
|
28
|
+
## Key API
|
|
29
|
+
|
|
30
|
+
### Builder Chain
|
|
31
|
+
|
|
32
|
+
```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)
|
|
41
|
+
.setEnableNarrative()
|
|
42
|
+
.setInputSchema(schema) / .setOutputSchema(schema) / .setOutputMapper(fn)
|
|
43
|
+
.build() / .toSpec() / .toMermaid()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Stage Functions
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
(scope: ScopeFacade, breakPipeline: () => void, streamCallback?: StreamCallback) => void | Promise<void>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### ScopeFacade
|
|
53
|
+
|
|
54
|
+
```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)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Executor
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const executor = new FlowChartExecutor(chart);
|
|
66
|
+
await executor.run({ input, timeoutMs?, signal? });
|
|
67
|
+
executor.getNarrative() // string[]
|
|
68
|
+
executor.getNarrativeEntries() // CombinedNarrativeEntry[]
|
|
69
|
+
executor.getSnapshot() // memory state
|
|
70
|
+
executor.attachFlowRecorder(r) // plug observer
|
|
71
|
+
executor.setRedactionPolicy({ keys, patterns, fields })
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Observer Systems
|
|
75
|
+
|
|
76
|
+
- **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
|
|
77
|
+
- **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)
|
|
80
|
+
|
|
81
|
+
## Rules
|
|
82
|
+
|
|
83
|
+
- 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
|
+
- `setEnableNarrative()` is all you need for narrative setup
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
├── engine/ → DFS traversal + narrative + handlers
|
|
18
|
+
├── runner/ → FlowChartExecutor
|
|
19
|
+
└── contract/ → I/O schema + OpenAPI
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
|
|
23
|
+
|
|
24
|
+
## Builder API
|
|
25
|
+
|
|
26
|
+
```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)
|
|
35
|
+
.setEnableNarrative()
|
|
36
|
+
.build() / .toSpec() / .toMermaid()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Stage Function Signature
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
(scope: ScopeFacade, breakPipeline: () => void, streamCallback?: StreamCallback) => void | Promise<void>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## ScopeFacade
|
|
46
|
+
|
|
47
|
+
```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)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Executor
|
|
56
|
+
|
|
57
|
+
```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
|
|
62
|
+
executor.getSnapshot() // memory state
|
|
63
|
+
executor.attachFlowRecorder(r) // plug flow observer
|
|
64
|
+
executor.setRedactionPolicy({ keys, patterns, fields })
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Two Observer Systems (intentionally separate)
|
|
68
|
+
|
|
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()`
|
|
73
|
+
|
|
74
|
+
## Rules
|
|
75
|
+
|
|
76
|
+
- 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
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
├── engine/ → DFS traversal + narrative + handlers
|
|
18
|
+
├── runner/ → FlowChartExecutor
|
|
19
|
+
└── contract/ → I/O schema + OpenAPI
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
|
|
23
|
+
|
|
24
|
+
## Builder API
|
|
25
|
+
|
|
26
|
+
```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)
|
|
35
|
+
.setEnableNarrative()
|
|
36
|
+
.build() / .toSpec() / .toMermaid()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Stage Functions
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
(scope: ScopeFacade, breakPipeline: () => void, streamCallback?: StreamCallback) => void | Promise<void>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## ScopeFacade
|
|
46
|
+
|
|
47
|
+
```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)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Executor
|
|
56
|
+
|
|
57
|
+
```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
|
|
62
|
+
executor.getSnapshot() // memory state
|
|
63
|
+
executor.attachFlowRecorder(r) // plug flow observer
|
|
64
|
+
executor.setRedactionPolicy({ keys, patterns, fields })
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Observer Systems
|
|
68
|
+
|
|
69
|
+
- **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
|
|
70
|
+
- **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()`
|
|
73
|
+
|
|
74
|
+
## Rules
|
|
75
|
+
|
|
76
|
+
- 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
|
|
@@ -0,0 +1,140 @@
|
|
|
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 ""
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# footprint.js — Windsurf 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
|
+
├── engine/ → DFS traversal + narrative + handlers
|
|
18
|
+
├── runner/ → FlowChartExecutor
|
|
19
|
+
└── contract/ → I/O schema + OpenAPI
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Entry points: `footprintjs` (public) and `footprintjs/advanced` (internals).
|
|
23
|
+
|
|
24
|
+
## Builder API
|
|
25
|
+
|
|
26
|
+
```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)
|
|
35
|
+
.setEnableNarrative()
|
|
36
|
+
.build() / .toSpec() / .toMermaid()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Stage Functions
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
(scope: ScopeFacade, breakPipeline: () => void, streamCallback?: StreamCallback) => void | Promise<void>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## ScopeFacade
|
|
46
|
+
|
|
47
|
+
```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)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Executor
|
|
56
|
+
|
|
57
|
+
```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
|
|
62
|
+
executor.getSnapshot() // memory state
|
|
63
|
+
executor.attachFlowRecorder(r) // plug flow observer
|
|
64
|
+
executor.setRedactionPolicy({ keys, patterns, fields })
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Observer Systems
|
|
68
|
+
|
|
69
|
+
- **Scope Recorder**: fires DURING stage (`onRead`, `onWrite`, `onCommit`)
|
|
70
|
+
- **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()`
|
|
73
|
+
|
|
74
|
+
## Rules
|
|
75
|
+
|
|
76
|
+
- 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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "footprintjs",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Turn your whiteboard flowchart into running code — with automatic causal traces for LLM reasoning",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Sanjay Krishna Anbalagan",
|
|
@@ -49,7 +49,10 @@
|
|
|
49
49
|
"dist/**/*",
|
|
50
50
|
"!dist/test",
|
|
51
51
|
"!dist/types/test",
|
|
52
|
-
"!dist/esm/types/test"
|
|
52
|
+
"!dist/esm/types/test",
|
|
53
|
+
"ai-instructions/**/*",
|
|
54
|
+
"CLAUDE.md",
|
|
55
|
+
"AGENTS.md"
|
|
53
56
|
],
|
|
54
57
|
"main": "dist/index.js",
|
|
55
58
|
"module": "dist/esm/index.js",
|
|
@@ -72,6 +75,9 @@
|
|
|
72
75
|
"prettier --config .prettierrc.js --write"
|
|
73
76
|
]
|
|
74
77
|
},
|
|
78
|
+
"bin": {
|
|
79
|
+
"footprintjs-setup": "ai-instructions/setup.sh"
|
|
80
|
+
},
|
|
75
81
|
"sideEffects": false,
|
|
76
82
|
"dependencies": {
|
|
77
83
|
"lodash.get": "^4.4.2",
|