@jsleekr/graft 6.0.0 → 6.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.js +53 -33
- package/dist/playground-entry.d.ts +26 -0
- package/dist/playground-entry.js +93 -0
- package/dist/runtime/result-formatter.js +2 -1
- package/package.json +13 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
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
|
-
**[Documentation](https://jsleekr.github.io/graft/)** | **[User Guide](docs/guide.md)** | **[Examples](examples/)**
|
|
11
|
+
**[Documentation](https://jsleekr.github.io/graft/)** | **[Playground](https://jsleekr.github.io/graft/playground/)** | **[User Guide](docs/guide.md)** | **[Examples](examples/)**
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -137,7 +137,7 @@ Claude Code reads the .claude/ structure and runs the pipeline
|
|
|
137
137
|
## CLI
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
|
-
graft init
|
|
140
|
+
graft init [name] # New project, or add Graft to current dir
|
|
141
141
|
graft compile <file.gft> [--out-dir <dir>] # Compile to .claude/ harness
|
|
142
142
|
graft check <file.gft> # Parse + analyze only
|
|
143
143
|
graft run <file.gft> --input <json> # Compile and execute
|
package/dist/index.js
CHANGED
|
@@ -137,19 +137,23 @@ program
|
|
|
137
137
|
});
|
|
138
138
|
program
|
|
139
139
|
.command('init')
|
|
140
|
-
.description('Scaffold a new Graft project')
|
|
141
|
-
.argument('
|
|
140
|
+
.description('Scaffold a new Graft project, or add Graft to an existing project')
|
|
141
|
+
.argument('[name]', 'project name (omit to set up current directory)')
|
|
142
142
|
.action(async (name) => {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
const isExisting = !name;
|
|
144
|
+
const dir = name ? path.resolve(name) : process.cwd();
|
|
145
|
+
if (name) {
|
|
146
|
+
if (fs.existsSync(dir)) {
|
|
147
|
+
console.error(`Error: directory '${name}' already exists`);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
147
151
|
}
|
|
148
|
-
|
|
149
|
-
const baseName = path.basename(name);
|
|
152
|
+
const baseName = path.basename(dir);
|
|
150
153
|
const safeName = baseName.replace(/[^a-zA-Z0-9]/g, '_').replace(/^_+|_+$/g, '') || 'pipeline';
|
|
151
|
-
// Generate pipeline.gft starter template
|
|
152
|
-
|
|
154
|
+
// Generate pipeline.gft starter template (only for new projects)
|
|
155
|
+
if (!isExisting) {
|
|
156
|
+
fs.writeFileSync(path.join(dir, 'pipeline.gft'), `// ${safeName} — a simple two-node pipeline
|
|
153
157
|
|
|
154
158
|
context Input(max_tokens: 500) {
|
|
155
159
|
question: String
|
|
@@ -177,23 +181,29 @@ graph ${safeName}(input: Input, output: Output, budget: 10k) {
|
|
|
177
181
|
Analyst -> Reviewer -> done
|
|
178
182
|
}
|
|
179
183
|
`);
|
|
180
|
-
|
|
184
|
+
}
|
|
185
|
+
// Generate or append .claude/CLAUDE.md with .gft spec
|
|
181
186
|
const claudeDir = path.join(dir, '.claude');
|
|
182
187
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
183
188
|
const { buildSystemPrompt } = await import('./generator.js');
|
|
184
189
|
const gftSpec = buildSystemPrompt();
|
|
185
|
-
|
|
190
|
+
const claudeMdPath = path.join(claudeDir, 'CLAUDE.md');
|
|
191
|
+
const existingClaudeMd = fs.existsSync(claudeMdPath) ? fs.readFileSync(claudeMdPath, 'utf-8') : '';
|
|
192
|
+
if (existingClaudeMd && existingClaudeMd.includes('graft compile')) {
|
|
193
|
+
console.log(` .claude/CLAUDE.md already contains Graft config — skipped`);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const graftSection = `
|
|
197
|
+
## Graft — Multi-Agent Pipelines
|
|
186
198
|
|
|
187
199
|
This project uses **Graft** (.gft) for defining multi-agent pipelines.
|
|
188
200
|
|
|
189
|
-
## Working with .gft files
|
|
190
|
-
|
|
191
201
|
When the user asks to create, modify, or manage pipelines:
|
|
192
202
|
1. Write or edit \`.gft\` files using the syntax below
|
|
193
203
|
2. Run \`graft compile <file.gft>\` to generate the \`.claude/\` harness structure
|
|
194
204
|
3. Run \`graft check <file.gft>\` to validate without generating files
|
|
195
205
|
|
|
196
|
-
|
|
206
|
+
### CLI Commands
|
|
197
207
|
|
|
198
208
|
\`\`\`bash
|
|
199
209
|
graft compile <file.gft> [--out-dir <dir>] # Compile to harness structure
|
|
@@ -204,30 +214,40 @@ graft visualize <file.gft> # Output pipeline DAG as Mermaid
|
|
|
204
214
|
graft watch <file.gft> # Watch and recompile on changes
|
|
205
215
|
\`\`\`
|
|
206
216
|
|
|
207
|
-
|
|
217
|
+
### After Pipeline Execution
|
|
208
218
|
|
|
209
|
-
|
|
219
|
+
\`graft run\` automatically:
|
|
210
220
|
1. Shows a formatted result summary (nodes, tokens, timing)
|
|
211
221
|
2. Validates output against the .gft schema (types, ranges, empty fields)
|
|
212
222
|
3. Suggests .gft modifications if quality issues are found
|
|
213
223
|
|
|
214
|
-
If the quality report shows issues:
|
|
215
|
-
- Empty fields → suggest increasing node output budget
|
|
216
|
-
- Budget exhaustion → suggest adding edge transforms (select, compact, truncate)
|
|
217
|
-
- Node failures → suggest adding on_failure: retry(N)
|
|
218
|
-
- Type mismatches → check the node's prompt and produces schema
|
|
219
|
-
|
|
220
|
-
Apply the suggested fixes to the .gft file, then run \`graft compile\` and \`graft run\` again.
|
|
221
|
-
|
|
222
224
|
${gftSpec}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
225
|
+
`;
|
|
226
|
+
if (existingClaudeMd) {
|
|
227
|
+
// Append Graft section to existing CLAUDE.md
|
|
228
|
+
fs.writeFileSync(claudeMdPath, existingClaudeMd.trimEnd() + '\n\n' + graftSection.trim() + '\n');
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
// Create new CLAUDE.md with Graft header
|
|
232
|
+
fs.writeFileSync(claudeMdPath, `# ${safeName}\n` + graftSection);
|
|
233
|
+
}
|
|
234
|
+
} // end else (not already configured)
|
|
235
|
+
if (isExisting) {
|
|
236
|
+
console.log(`\nGraft added to current project.`);
|
|
237
|
+
console.log(` .claude/CLAUDE.md — Graft spec injected`);
|
|
238
|
+
console.log(`\nNext steps:`);
|
|
239
|
+
console.log(` # Write a .gft file, or open Claude Code — it already knows .gft syntax`);
|
|
240
|
+
console.log(` graft compile <file.gft>`);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
console.log(`\nCreated ${name}/`);
|
|
244
|
+
console.log(` pipeline.gft — starter pipeline template`);
|
|
245
|
+
console.log(` .claude/CLAUDE.md — Graft spec for Claude Code`);
|
|
246
|
+
console.log(`\nNext steps:`);
|
|
247
|
+
console.log(` cd ${name}`);
|
|
248
|
+
console.log(` graft compile pipeline.gft`);
|
|
249
|
+
console.log(` # Open in Claude Code — it already knows .gft syntax`);
|
|
250
|
+
}
|
|
231
251
|
console.log('');
|
|
232
252
|
});
|
|
233
253
|
program
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { TokenReport } from './analyzer/estimator.js';
|
|
2
|
+
import { Program } from './parser/ast.js';
|
|
3
|
+
import { GeneratedFile } from './codegen/codegen.js';
|
|
4
|
+
export interface PlaygroundResult {
|
|
5
|
+
success: boolean;
|
|
6
|
+
program?: Program;
|
|
7
|
+
report?: TokenReport;
|
|
8
|
+
files?: GeneratedFile[];
|
|
9
|
+
errors: Array<{
|
|
10
|
+
message: string;
|
|
11
|
+
line: number;
|
|
12
|
+
column: number;
|
|
13
|
+
severity: string;
|
|
14
|
+
code?: string;
|
|
15
|
+
formatted: string;
|
|
16
|
+
}>;
|
|
17
|
+
warnings: Array<{
|
|
18
|
+
message: string;
|
|
19
|
+
line: number;
|
|
20
|
+
column: number;
|
|
21
|
+
severity: string;
|
|
22
|
+
code?: string;
|
|
23
|
+
formatted: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export declare function compilePlayground(source: string, filename?: string): PlaygroundResult;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser entry point for the Graft playground.
|
|
3
|
+
* Exports only the compiler pipeline (no fs, no runtime, no CLI).
|
|
4
|
+
*/
|
|
5
|
+
import { Lexer } from './lexer/lexer.js';
|
|
6
|
+
import { Parser } from './parser/parser.js';
|
|
7
|
+
import { ScopeChecker } from './analyzer/scope.js';
|
|
8
|
+
import { TypeChecker } from './analyzer/types.js';
|
|
9
|
+
import { TokenEstimator } from './analyzer/estimator.js';
|
|
10
|
+
import { GraftError } from './errors/diagnostics.js';
|
|
11
|
+
import { ProgramIndex } from './program-index.js';
|
|
12
|
+
import { generate } from './codegen/codegen.js';
|
|
13
|
+
function serializeDiagnostic(e, source, filename) {
|
|
14
|
+
return {
|
|
15
|
+
message: e.message,
|
|
16
|
+
line: e.location.line,
|
|
17
|
+
column: e.location.column,
|
|
18
|
+
severity: e.severity,
|
|
19
|
+
code: e.code,
|
|
20
|
+
formatted: e.format(source, filename),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function compilePlayground(source, filename = 'playground.gft') {
|
|
24
|
+
const errors = [];
|
|
25
|
+
const warnings = [];
|
|
26
|
+
// Lex
|
|
27
|
+
let tokens;
|
|
28
|
+
try {
|
|
29
|
+
const lexer = new Lexer(source);
|
|
30
|
+
tokens = lexer.tokenize();
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
if (e instanceof GraftError) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
errors: [serializeDiagnostic(e, source, filename)],
|
|
37
|
+
warnings: [],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
42
|
+
// Parse
|
|
43
|
+
const { program, errors: parseErrors } = new Parser(tokens).parse();
|
|
44
|
+
errors.push(...parseErrors);
|
|
45
|
+
if (parseErrors.length > 0) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
program,
|
|
49
|
+
errors: errors.map(e => serializeDiagnostic(e, source, filename)),
|
|
50
|
+
warnings: [],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Set sourceFile (no path.resolve — browser-safe)
|
|
54
|
+
for (const c of program.contexts)
|
|
55
|
+
c.sourceFile = filename;
|
|
56
|
+
for (const n of program.nodes)
|
|
57
|
+
n.sourceFile = filename;
|
|
58
|
+
// Build ProgramIndex
|
|
59
|
+
const index = new ProgramIndex(program);
|
|
60
|
+
// Analyze
|
|
61
|
+
const scopeDiagnostics = new ScopeChecker(program, index).check();
|
|
62
|
+
const typeDiagnostics = new TypeChecker(program, index).check();
|
|
63
|
+
for (const d of [...scopeDiagnostics, ...typeDiagnostics]) {
|
|
64
|
+
if (d.severity === 'warning')
|
|
65
|
+
warnings.push(d);
|
|
66
|
+
else
|
|
67
|
+
errors.push(d);
|
|
68
|
+
}
|
|
69
|
+
if (errors.length > 0) {
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
program,
|
|
73
|
+
errors: errors.map(e => serializeDiagnostic(e, source, filename)),
|
|
74
|
+
warnings: warnings.map(w => serializeDiagnostic(w, source, filename)),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// Token analysis
|
|
78
|
+
const report = new TokenEstimator(program, index).estimate();
|
|
79
|
+
warnings.push(...report.warnings);
|
|
80
|
+
// Generate files (if graph exists)
|
|
81
|
+
let files;
|
|
82
|
+
if (program.graphs.length > 0) {
|
|
83
|
+
files = generate(program, report, filename, index);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
program,
|
|
88
|
+
report,
|
|
89
|
+
files,
|
|
90
|
+
errors: [],
|
|
91
|
+
warnings: warnings.map(w => serializeDiagnostic(w, source, filename)),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -89,7 +89,8 @@ function formatNodeTokens(nr) {
|
|
|
89
89
|
return `${total.toLocaleString()} tok`;
|
|
90
90
|
}
|
|
91
91
|
function renderBar(fraction, width) {
|
|
92
|
-
const
|
|
92
|
+
const clamped = Math.max(0, Math.min(1, fraction));
|
|
93
|
+
const filled = Math.round(clamped * width);
|
|
93
94
|
const empty = width - filled;
|
|
94
95
|
const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(empty);
|
|
95
96
|
return `[${bar}]`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsleekr/graft",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.2",
|
|
4
4
|
"description": "Graft compiler — compile .gft graph DSL to Claude Code harness structures",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,7 +15,17 @@
|
|
|
15
15
|
"dsl",
|
|
16
16
|
"llm",
|
|
17
17
|
"claude",
|
|
18
|
-
"
|
|
18
|
+
"claude-code",
|
|
19
|
+
"multi-agent",
|
|
20
|
+
"pipeline",
|
|
21
|
+
"infrastructure-as-code",
|
|
22
|
+
"ai-agents",
|
|
23
|
+
"token-optimization",
|
|
24
|
+
"orchestration",
|
|
25
|
+
"graph",
|
|
26
|
+
"code-generation",
|
|
27
|
+
"developer-tools",
|
|
28
|
+
"automation"
|
|
19
29
|
],
|
|
20
30
|
"main": "./dist/index.js",
|
|
21
31
|
"types": "./dist/index.d.ts",
|
|
@@ -59,6 +69,7 @@
|
|
|
59
69
|
"test": "vitest run",
|
|
60
70
|
"test:watch": "vitest",
|
|
61
71
|
"check": "tsc --noEmit",
|
|
72
|
+
"build:playground": "npx esbuild src/playground-entry.ts --bundle --format=iife --global-name=GraftCompiler --outfile=docs/playground/graft-compiler.js --platform=browser --alias:node:path=./scripts/playground-shims/node-path.js --alias:node:fs=./scripts/playground-shims/node-fs.js --alias:node:module=./scripts/playground-shims/node-module.js --alias:node:child_process=./scripts/playground-shims/node-fs.js --alias:node:url=./scripts/playground-shims/node-fs.js --footer:js=\"if(typeof window!=='undefined')window.Graft=GraftCompiler;\"",
|
|
62
73
|
"prepublishOnly": "npm run build && npm test"
|
|
63
74
|
},
|
|
64
75
|
"engines": {
|