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