@jsleekr/graft 5.8.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -130
- package/dist/codegen/generic-backend.d.ts +17 -0
- package/dist/codegen/generic-backend.js +156 -0
- package/dist/compiler.d.ts +4 -3
- package/dist/compiler.js +6 -6
- package/dist/formatter.d.ts +5 -0
- package/dist/formatter.js +170 -0
- package/dist/generator.d.ts +28 -0
- package/dist/generator.js +255 -0
- package/dist/index.js +220 -20
- package/dist/runner.d.ts +3 -0
- package/dist/runner.js +7 -0
- package/dist/runtime/feedback.d.ts +20 -0
- package/dist/runtime/feedback.js +152 -0
- package/dist/runtime/result-formatter.d.ts +13 -0
- package/dist/runtime/result-formatter.js +142 -0
- package/dist/runtime/result-validator.d.ts +28 -0
- package/dist/runtime/result-validator.js +129 -0
- package/dist/test-runner.d.ts +33 -0
- package/dist/test-runner.js +223 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,104 +4,54 @@
|
|
|
4
4
|
|
|
5
5
|
# Graft
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Infrastructure as Code for Claude Code multi-agent pipelines.**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Write `.gft` files to define multi-agent pipelines. The compiler generates `.claude/` harness structures — agents, hooks, orchestration plans, settings — with compile-time token budget analysis.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**[Documentation](https://jsleekr.github.io/graft/)** | **[User Guide](docs/guide.md)** | **[Examples](examples/)**
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm install -g @jsleekr/graft
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Requires Node.js 20+.
|
|
13
|
+
---
|
|
20
14
|
|
|
21
|
-
|
|
15
|
+
## Getting Started (5 minutes)
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
graft init my-pipeline
|
|
25
|
-
cd my-pipeline
|
|
26
|
-
```
|
|
17
|
+
### Prerequisites
|
|
27
18
|
|
|
28
|
-
|
|
19
|
+
- [Node.js 20+](https://nodejs.org)
|
|
20
|
+
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
|
|
29
21
|
|
|
30
|
-
###
|
|
22
|
+
### Step 1: Install Graft
|
|
31
23
|
|
|
32
24
|
```bash
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Output:
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
✓ Parse OK
|
|
40
|
-
✓ Scope check OK
|
|
41
|
-
✓ Type check OK
|
|
42
|
-
✓ Token analysis:
|
|
43
|
-
Analyst in ~ 500 out ~ 2,000
|
|
44
|
-
Reviewer in ~ 840 out ~ 1,000
|
|
45
|
-
Best path: 4,340 tokens ✓ within budget (10,000)
|
|
46
|
-
|
|
47
|
-
Generated:
|
|
48
|
-
.claude/agents/analyst.md ← agent definition
|
|
49
|
-
.claude/agents/reviewer.md ← agent definition
|
|
50
|
-
.claude/hooks/analyst-to-reviewer.js ← edge transform
|
|
51
|
-
.claude/CLAUDE.md ← orchestration plan
|
|
52
|
-
.claude/settings.json ← model routing + hooks
|
|
25
|
+
npm install -g @jsleekr/graft
|
|
53
26
|
```
|
|
54
27
|
|
|
55
|
-
###
|
|
28
|
+
### Step 2: Create a project
|
|
56
29
|
|
|
57
30
|
```bash
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# Open in Claude Code
|
|
62
|
-
claude
|
|
31
|
+
graft init my-pipeline
|
|
32
|
+
cd my-pipeline
|
|
63
33
|
```
|
|
64
34
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Claude Code reads the generated `.claude/` structure and runs the pipeline automatically.
|
|
35
|
+
This creates:
|
|
36
|
+
- `pipeline.gft` — a starter two-node pipeline
|
|
37
|
+
- `.claude/CLAUDE.md` — the .gft language spec, so Claude Code natively understands Graft
|
|
70
38
|
|
|
71
|
-
###
|
|
39
|
+
### Step 3: Open Claude Code and just talk
|
|
72
40
|
|
|
73
41
|
```bash
|
|
74
|
-
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
That's it. You have a working multi-agent pipeline.
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
## How It Works
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
.gft Source → Graft Compiler → .claude/ output → Claude Code runs it
|
|
42
|
+
claude
|
|
85
43
|
```
|
|
86
44
|
|
|
87
|
-
|
|
88
|
-
|-------------|-----------------|---------|
|
|
89
|
-
| `node` | `.claude/agents/*.md` | Agent with model, tools, output schema |
|
|
90
|
-
| `edge \| transform` | `.claude/hooks/*.js` | Node.js data transform between nodes |
|
|
91
|
-
| `graph` | `.claude/CLAUDE.md` | Step-by-step orchestration plan |
|
|
92
|
-
| `memory` | `.graft/memory/*.json` | Persistent state across runs |
|
|
93
|
-
| config | `.claude/settings.json` | Model routing, budget, hook registration |
|
|
94
|
-
|
|
95
|
-
## Why Graft?
|
|
45
|
+
Then say:
|
|
96
46
|
|
|
97
|
-
|
|
47
|
+
> "I want a code review pipeline where security, logic, and performance reviewers run in parallel, then a senior reviewer synthesizes everything."
|
|
98
48
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
49
|
+
**Claude Code already knows .gft syntax** (from `.claude/CLAUDE.md`). It will:
|
|
50
|
+
1. Write a `.gft` file for you
|
|
51
|
+
2. Run `graft compile` to generate the harness
|
|
52
|
+
3. You're done
|
|
103
53
|
|
|
104
|
-
|
|
54
|
+
### Step 4: Or write .gft yourself
|
|
105
55
|
|
|
106
56
|
```graft
|
|
107
57
|
context PullRequest(max_tokens: 2k) {
|
|
@@ -113,7 +63,7 @@ node SecurityReviewer(model: sonnet, budget: 4k/2k) {
|
|
|
113
63
|
reads: [PullRequest]
|
|
114
64
|
produces SecurityAnalysis {
|
|
115
65
|
vulnerabilities: List<String>
|
|
116
|
-
risk_level:
|
|
66
|
+
risk_level: String
|
|
117
67
|
}
|
|
118
68
|
}
|
|
119
69
|
|
|
@@ -143,9 +93,62 @@ graph CodeReview(input: PullRequest, output: FinalReview, budget: 25k) {
|
|
|
143
93
|
}
|
|
144
94
|
```
|
|
145
95
|
|
|
146
|
-
|
|
96
|
+
Compile it:
|
|
147
97
|
|
|
148
|
-
|
|
98
|
+
```bash
|
|
99
|
+
graft compile code-review.gft
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The compiler generates agents, hooks, orchestration plan, and settings — ready for Claude Code.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## How It Works
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
You describe what you want (natural language or .gft)
|
|
110
|
+
↓
|
|
111
|
+
Claude Code writes/edits .gft files (it knows the syntax from CLAUDE.md)
|
|
112
|
+
↓
|
|
113
|
+
graft compile → .claude/ output (agents, hooks, settings)
|
|
114
|
+
↓
|
|
115
|
+
Claude Code reads the .claude/ structure and runs the pipeline
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
| Graft Source | Generated Output | Purpose |
|
|
119
|
+
|-------------|-----------------|---------|
|
|
120
|
+
| `node` | `.claude/agents/*.md` | Agent with model, tools, output schema |
|
|
121
|
+
| `edge \| transform` | `.claude/hooks/*.js` | Data transform between nodes |
|
|
122
|
+
| `graph` | `.claude/CLAUDE.md` | Step-by-step orchestration plan |
|
|
123
|
+
| `memory` | `.graft/memory/*.json` | Persistent state across runs |
|
|
124
|
+
| config | `.claude/settings.json` | Model routing, budget, hook registration |
|
|
125
|
+
|
|
126
|
+
## Why Graft?
|
|
127
|
+
|
|
128
|
+
**For humans**: Write 72 lines of `.gft` instead of manually maintaining 9 generated files (13KB+). ~8x compression ratio.
|
|
129
|
+
|
|
130
|
+
**For LLMs**: Claude Code reads 400 tokens of `.gft` instead of 3,300 tokens of scattered config. Modifications are single-file edits with compiler-guaranteed consistency.
|
|
131
|
+
|
|
132
|
+
- **Edge transforms** — extract only what the next agent needs (`select`, `drop`, `compact`, `filter`)
|
|
133
|
+
- **Compile-time token analysis** — catches budget overruns before you spend API credits
|
|
134
|
+
- **Typed output schemas** — enforce structured JSON between agents
|
|
135
|
+
- **Scope checking** — the compiler verifies every `reads` reference at compile time
|
|
136
|
+
|
|
137
|
+
## CLI
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
graft init <name> # Scaffold project + inject CLAUDE.md spec
|
|
141
|
+
graft compile <file.gft> [--out-dir <dir>] # Compile to .claude/ harness
|
|
142
|
+
graft check <file.gft> # Parse + analyze only
|
|
143
|
+
graft run <file.gft> --input <json> # Compile and execute
|
|
144
|
+
graft test <file.gft> [--input <json>] # Test with mock data
|
|
145
|
+
graft fmt <file.gft> [-w] # Format .gft source
|
|
146
|
+
graft generate <desc> [--output <file>] # Generate .gft via Claude Code CLI
|
|
147
|
+
graft watch <file.gft> # Watch and recompile on changes
|
|
148
|
+
graft visualize <file.gft> # Pipeline DAG as Mermaid diagram
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Language Reference
|
|
149
152
|
|
|
150
153
|
### Contexts and Nodes
|
|
151
154
|
|
|
@@ -175,6 +178,16 @@ edge Analyzer -> Reviewer
|
|
|
175
178
|
| compact
|
|
176
179
|
```
|
|
177
180
|
|
|
181
|
+
### Conditional Routing
|
|
182
|
+
|
|
183
|
+
```graft
|
|
184
|
+
edge RiskAssessor -> {
|
|
185
|
+
when risk_score > 0.7 -> DetailedReviewer
|
|
186
|
+
when risk_score > 0.3 -> StandardReviewer
|
|
187
|
+
else -> AutoApprove
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
178
191
|
### Flow Control
|
|
179
192
|
|
|
180
193
|
```graft
|
|
@@ -185,72 +198,35 @@ graph Pipeline(input: TaskSpec, output: Report, budget: 35k) {
|
|
|
185
198
|
}
|
|
186
199
|
```
|
|
187
200
|
|
|
188
|
-
Also supports: `foreach`, `let` variables with expressions, parameterized sub-graphs,
|
|
201
|
+
Also supports: `foreach`, `let` variables with expressions, parameterized sub-graphs, `import`.
|
|
189
202
|
|
|
190
|
-
###
|
|
203
|
+
### Memory
|
|
191
204
|
|
|
192
205
|
```graft
|
|
193
|
-
import { UserMessage, SystemConfig } from "./shared.gft"
|
|
194
|
-
|
|
195
206
|
memory ConversationLog(max_tokens: 2k, storage: file) {
|
|
196
207
|
turns: List<Turn { role: String, content: String }>
|
|
197
208
|
summary: Optional<String>
|
|
198
209
|
}
|
|
199
|
-
|
|
200
|
-
node Responder(model: sonnet, budget: 4k/2k) {
|
|
201
|
-
reads: [UserMessage, SystemConfig, ConversationLog]
|
|
202
|
-
writes: [ConversationLog.turns]
|
|
203
|
-
produces Response { reply: String }
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### Expressions
|
|
208
|
-
|
|
209
|
-
```graft
|
|
210
|
-
A -> let score = A.risk_score * 2
|
|
211
|
-
-> let label = "Found ${len(A.items)} items"
|
|
212
|
-
-> let safe = score >= 50 && score <= 100
|
|
213
|
-
-> let fallback = A.name ?? "unknown"
|
|
214
|
-
-> B -> done
|
|
215
210
|
```
|
|
216
211
|
|
|
217
|
-
|
|
212
|
+
## Execution Model
|
|
218
213
|
|
|
219
|
-
|
|
220
|
-
String, Int, Float, Float(0..1), Bool // primitives
|
|
221
|
-
List<T>, Map<K, V>, Optional<T> // collections
|
|
222
|
-
TokenBounded<String, 100> // token-bounded types
|
|
223
|
-
enum(low, medium, high) // inline enums
|
|
224
|
-
Issue { file: FilePath, severity: ... } // inline structs
|
|
225
|
-
```
|
|
214
|
+
Graft is a **compiler**, not a runtime orchestrator.
|
|
226
215
|
|
|
227
|
-
|
|
216
|
+
1. **`.claude/CLAUDE.md`** — natural-language execution plan. Claude Code reads it as instructions.
|
|
217
|
+
2. **`.claude/hooks/*.js`** — PostToolUse hooks that fire automatically. Edge transforms run deterministically.
|
|
218
|
+
3. **`.claude/settings.json`** — model routing and hook registration.
|
|
228
219
|
|
|
229
|
-
|
|
230
|
-
graft compile <file.gft> [--out-dir <dir>] # Compile to .claude/ structure
|
|
231
|
-
graft check <file.gft> # Parse + analyze only
|
|
232
|
-
graft run <file.gft> --input <json> [--dry-run] [--verbose] # Compile and execute
|
|
233
|
-
graft init <name> # Scaffold a new project
|
|
234
|
-
graft watch <file.gft> [--out-dir <dir>] # Watch and recompile on changes
|
|
235
|
-
graft visualize <file.gft> # Output pipeline DAG as Mermaid diagram
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
## Programmatic API
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
import { compile } from '@jsleekr/graft/compiler';
|
|
242
|
-
import { Executor } from '@jsleekr/graft/runtime';
|
|
243
|
-
import type { Program } from '@jsleekr/graft/types';
|
|
220
|
+
`graft run` spawns Claude Code subprocesses per node. The orchestration depends on Claude Code's instruction-following — unlike LangGraph or CrewAI which use deterministic state machines.
|
|
244
221
|
|
|
245
|
-
|
|
246
|
-
if (result.success) {
|
|
247
|
-
console.log(`Parsed ${result.program.nodes.length} nodes`);
|
|
248
|
-
}
|
|
249
|
-
```
|
|
222
|
+
## Limitations
|
|
250
223
|
|
|
251
|
-
|
|
224
|
+
- **Non-deterministic orchestration** — `CLAUDE.md` is an LLM prompt, not a state machine
|
|
225
|
+
- **Claude Code dependency** — generates `.claude/` structures only
|
|
226
|
+
- **Single provider** — Anthropic models only (multi-provider planned)
|
|
227
|
+
- **Memory** — JSON file storage only (other backends planned)
|
|
252
228
|
|
|
253
|
-
|
|
229
|
+
See `SPECIFICATION.md` for planned features.
|
|
254
230
|
|
|
255
231
|
## Development
|
|
256
232
|
|
|
@@ -258,7 +234,7 @@ The [Graft VS Code extension](editors/vscode/) provides syntax highlighting, rea
|
|
|
258
234
|
git clone https://github.com/JSLEEKR/graft.git
|
|
259
235
|
cd graft && npm install
|
|
260
236
|
npm run build # Compile TypeScript
|
|
261
|
-
npm test # Run all 1,
|
|
237
|
+
npm test # Run all 1,712 tests
|
|
262
238
|
```
|
|
263
239
|
|
|
264
240
|
## License
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NodeDecl, EdgeDecl } from '../parser/ast.js';
|
|
2
|
+
import { CodegenBackend, CodegenContext } from './backend.js';
|
|
3
|
+
/**
|
|
4
|
+
* GenericBackend — a tool-agnostic codegen backend.
|
|
5
|
+
*
|
|
6
|
+
* Produces markdown agent files, JS hook stubs, and JSON settings
|
|
7
|
+
* that are not tied to any specific AI coding assistant.
|
|
8
|
+
* This is groundwork for future Cursor / Windsurf / Copilot support.
|
|
9
|
+
*/
|
|
10
|
+
export declare class GenericBackend implements CodegenBackend {
|
|
11
|
+
readonly name = "generic";
|
|
12
|
+
generateAgent(node: NodeDecl, memoryNames: Set<string>, ctx: CodegenContext): string;
|
|
13
|
+
generateHook(edge: EdgeDecl, _ctx: CodegenContext): string | null;
|
|
14
|
+
generateConditionalHook(edge: EdgeDecl, _ctx: CodegenContext): string | null;
|
|
15
|
+
generateOrchestration(ctx: CodegenContext): string;
|
|
16
|
+
generateSettings(ctx: CodegenContext): Record<string, unknown>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { fieldsToJsonExample } from '../utils.js';
|
|
2
|
+
import { MODEL_MAP } from '../constants.js';
|
|
3
|
+
import { VERSION } from '../version.js';
|
|
4
|
+
/**
|
|
5
|
+
* GenericBackend — a tool-agnostic codegen backend.
|
|
6
|
+
*
|
|
7
|
+
* Produces markdown agent files, JS hook stubs, and JSON settings
|
|
8
|
+
* that are not tied to any specific AI coding assistant.
|
|
9
|
+
* This is groundwork for future Cursor / Windsurf / Copilot support.
|
|
10
|
+
*/
|
|
11
|
+
export class GenericBackend {
|
|
12
|
+
name = 'generic';
|
|
13
|
+
generateAgent(node, memoryNames, ctx) {
|
|
14
|
+
const name = node.name.toLowerCase();
|
|
15
|
+
const resolvedModel = MODEL_MAP[node.model] || node.model;
|
|
16
|
+
const jsonSchema = fieldsToJsonExample(node.produces.fields);
|
|
17
|
+
const readsList = node.reads
|
|
18
|
+
.map(r => `- ${r.context}${r.field && r.field.length > 0 ? ` (fields: ${r.field.join(', ')})` : ''}`)
|
|
19
|
+
.join('\n');
|
|
20
|
+
const toolsList = node.tools.length > 0
|
|
21
|
+
? `\n## Tools\n${node.tools.map(t => `- ${t}`).join('\n')}\n`
|
|
22
|
+
: '';
|
|
23
|
+
return `# ${node.name} Agent
|
|
24
|
+
|
|
25
|
+
## Metadata
|
|
26
|
+
- **name**: ${name}
|
|
27
|
+
- **model**: ${resolvedModel}
|
|
28
|
+
- **produces**: ${node.produces.name}
|
|
29
|
+
|
|
30
|
+
## Reads
|
|
31
|
+
${readsList || '(none)'}
|
|
32
|
+
${toolsList}
|
|
33
|
+
## Output Schema
|
|
34
|
+
\`\`\`json
|
|
35
|
+
${JSON.stringify(jsonSchema, null, 2)}
|
|
36
|
+
\`\`\`
|
|
37
|
+
|
|
38
|
+
## Instructions
|
|
39
|
+
Execute the ${node.name} step of the pipeline.
|
|
40
|
+
Read the specified inputs, perform your task, and produce output matching the schema above.
|
|
41
|
+
Write your output as JSON to: .graft/session/node_outputs/${name}.json
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
generateHook(edge, _ctx) {
|
|
45
|
+
if (edge.transforms.length === 0)
|
|
46
|
+
return null;
|
|
47
|
+
if (edge.target.kind !== 'direct')
|
|
48
|
+
return null;
|
|
49
|
+
const source = edge.source.toLowerCase();
|
|
50
|
+
const target = edge.target.node.toLowerCase();
|
|
51
|
+
const transformNames = edge.transforms.map(t => t.type).join(', ');
|
|
52
|
+
return `// Hook: ${edge.source} -> ${edge.target.node}
|
|
53
|
+
// Transforms: ${transformNames}
|
|
54
|
+
// Generated by Graft (generic backend)
|
|
55
|
+
|
|
56
|
+
import * as fs from 'node:fs';
|
|
57
|
+
|
|
58
|
+
const inputPath = '.graft/session/node_outputs/${source}.json';
|
|
59
|
+
const outputPath = '.graft/session/node_outputs/${source}_to_${target}.json';
|
|
60
|
+
|
|
61
|
+
const raw = JSON.parse(fs.readFileSync(inputPath, 'utf-8'));
|
|
62
|
+
|
|
63
|
+
// TODO: Implement transforms: ${transformNames}
|
|
64
|
+
const result = raw;
|
|
65
|
+
|
|
66
|
+
fs.writeFileSync(outputPath, JSON.stringify(result, null, 2));
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
generateConditionalHook(edge, _ctx) {
|
|
70
|
+
if (edge.target.kind !== 'conditional')
|
|
71
|
+
return null;
|
|
72
|
+
const source = edge.source.toLowerCase();
|
|
73
|
+
const branches = edge.target.branches;
|
|
74
|
+
const branchLines = branches.map(b => {
|
|
75
|
+
const condition = b.condition ? `/* condition */` : `/* else */`;
|
|
76
|
+
return ` ${condition} '${b.target}'`;
|
|
77
|
+
}).join(',\n');
|
|
78
|
+
return `// Router: ${edge.source} -> conditional
|
|
79
|
+
// Generated by Graft (generic backend)
|
|
80
|
+
|
|
81
|
+
import * as fs from 'node:fs';
|
|
82
|
+
|
|
83
|
+
const inputPath = '.graft/session/node_outputs/${source}.json';
|
|
84
|
+
const routingPath = '.graft/session/routing/${source}_route.json';
|
|
85
|
+
|
|
86
|
+
const data = JSON.parse(fs.readFileSync(inputPath, 'utf-8'));
|
|
87
|
+
|
|
88
|
+
// TODO: Implement conditional routing logic
|
|
89
|
+
const routes = [
|
|
90
|
+
${branchLines}
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const selectedRoute = routes[routes.length - 1]; // default: last (else) branch
|
|
94
|
+
fs.writeFileSync(routingPath, JSON.stringify({ route: selectedRoute }, null, 2));
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
generateOrchestration(ctx) {
|
|
98
|
+
const graph = ctx.program.graphs[0];
|
|
99
|
+
const graphName = graph?.name ?? 'pipeline';
|
|
100
|
+
const nodes = ctx.program.nodes.map(n => n.name);
|
|
101
|
+
const flowSteps = graph?.flow.map(step => {
|
|
102
|
+
if (step.kind === 'node')
|
|
103
|
+
return `1. Run **${step.name}**`;
|
|
104
|
+
if (step.kind === 'parallel')
|
|
105
|
+
return `1. Run in parallel: ${step.branches.join(', ')}`;
|
|
106
|
+
return '';
|
|
107
|
+
}).filter(Boolean).join('\n') ?? '';
|
|
108
|
+
return `# ${graphName} — Orchestration
|
|
109
|
+
|
|
110
|
+
> Generated by Graft v${VERSION} (generic backend)
|
|
111
|
+
> Source: ${ctx.sourceFile}
|
|
112
|
+
|
|
113
|
+
## Pipeline Flow
|
|
114
|
+
${flowSteps}
|
|
115
|
+
|
|
116
|
+
## Nodes
|
|
117
|
+
${nodes.map(n => `- ${n}`).join('\n')}
|
|
118
|
+
|
|
119
|
+
## Runtime
|
|
120
|
+
This pipeline was compiled with the generic backend.
|
|
121
|
+
To execute, run each node in order according to the flow above,
|
|
122
|
+
passing outputs between nodes via .graft/session/node_outputs/.
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
generateSettings(ctx) {
|
|
126
|
+
const graph = ctx.program.graphs[0];
|
|
127
|
+
const firstNode = ctx.program.nodes[0];
|
|
128
|
+
const defaultModel = firstNode
|
|
129
|
+
? (MODEL_MAP[firstNode.model] || firstNode.model)
|
|
130
|
+
: 'default';
|
|
131
|
+
const overrides = {};
|
|
132
|
+
for (const node of ctx.program.nodes) {
|
|
133
|
+
overrides[node.name] = MODEL_MAP[node.model] || node.model;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
graft: {
|
|
137
|
+
version: VERSION,
|
|
138
|
+
backend: 'generic',
|
|
139
|
+
source: ctx.sourceFile,
|
|
140
|
+
compiled_at: new Date().toISOString(),
|
|
141
|
+
budget: {
|
|
142
|
+
total: graph?.budget ?? 0,
|
|
143
|
+
},
|
|
144
|
+
model_routing: {
|
|
145
|
+
default: defaultModel,
|
|
146
|
+
overrides,
|
|
147
|
+
},
|
|
148
|
+
nodes: ctx.program.nodes.map(n => n.name),
|
|
149
|
+
edges: ctx.program.edges.map(e => ({
|
|
150
|
+
source: e.source,
|
|
151
|
+
target: e.target.kind === 'direct' ? e.target.node : 'conditional',
|
|
152
|
+
})),
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
package/dist/compiler.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { GeneratedFile } from './codegen/codegen.js';
|
|
|
3
3
|
import { GraftError } from './errors/diagnostics.js';
|
|
4
4
|
import { Program } from './parser/ast.js';
|
|
5
5
|
import { ProgramIndex } from './program-index.js';
|
|
6
|
+
import { CodegenBackend } from './codegen/backend.js';
|
|
6
7
|
export interface ProgramResult {
|
|
7
8
|
success: boolean;
|
|
8
9
|
program?: Program;
|
|
@@ -15,7 +16,7 @@ export interface CompileResult extends ProgramResult {
|
|
|
15
16
|
files?: GeneratedFile[];
|
|
16
17
|
}
|
|
17
18
|
export declare function compileToProgram(source: string, sourceFile: string): ProgramResult;
|
|
18
|
-
export declare function compileAndGenerate(source: string, sourceFile: string): CompileResult;
|
|
19
|
+
export declare function compileAndGenerate(source: string, sourceFile: string, backend?: CodegenBackend): CompileResult;
|
|
19
20
|
/** Backward-compatible alias for compileAndGenerate */
|
|
20
|
-
export declare function compile(source: string, sourceFile: string): CompileResult;
|
|
21
|
-
export declare function compileAndWrite(source: string, sourceFile: string, outDir: string): CompileResult;
|
|
21
|
+
export declare function compile(source: string, sourceFile: string, backend?: CodegenBackend): CompileResult;
|
|
22
|
+
export declare function compileAndWrite(source: string, sourceFile: string, outDir: string, backend?: CodegenBackend): CompileResult;
|
package/dist/compiler.js
CHANGED
|
@@ -68,7 +68,7 @@ export function compileToProgram(source, sourceFile) {
|
|
|
68
68
|
warnings.push(...report.warnings);
|
|
69
69
|
return { success: true, program, index, report, errors, warnings };
|
|
70
70
|
}
|
|
71
|
-
export function compileAndGenerate(source, sourceFile) {
|
|
71
|
+
export function compileAndGenerate(source, sourceFile, backend) {
|
|
72
72
|
const result = compileToProgram(source, sourceFile);
|
|
73
73
|
if (!result.success || !result.program) {
|
|
74
74
|
return result;
|
|
@@ -85,15 +85,15 @@ export function compileAndGenerate(source, sourceFile) {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
// Generate
|
|
88
|
-
const files = generate(result.program, result.report, sourceFile, result.index);
|
|
88
|
+
const files = generate(result.program, result.report, sourceFile, result.index, backend);
|
|
89
89
|
return { ...result, files };
|
|
90
90
|
}
|
|
91
91
|
/** Backward-compatible alias for compileAndGenerate */
|
|
92
|
-
export function compile(source, sourceFile) {
|
|
93
|
-
return compileAndGenerate(source, sourceFile);
|
|
92
|
+
export function compile(source, sourceFile, backend) {
|
|
93
|
+
return compileAndGenerate(source, sourceFile, backend);
|
|
94
94
|
}
|
|
95
|
-
export function compileAndWrite(source, sourceFile, outDir) {
|
|
96
|
-
const result = compile(source, sourceFile);
|
|
95
|
+
export function compileAndWrite(source, sourceFile, outDir, backend) {
|
|
96
|
+
const result = compile(source, sourceFile, backend);
|
|
97
97
|
if (result.success && result.files) {
|
|
98
98
|
writeFiles(result.files, outDir);
|
|
99
99
|
}
|