@jsleekr/graft 5.7.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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -0
  3. package/dist/analyzer/estimator.d.ts +33 -0
  4. package/dist/analyzer/estimator.js +273 -0
  5. package/dist/analyzer/graph-checker.d.ts +13 -0
  6. package/dist/analyzer/graph-checker.js +153 -0
  7. package/dist/analyzer/scope.d.ts +21 -0
  8. package/dist/analyzer/scope.js +324 -0
  9. package/dist/analyzer/types.d.ts +17 -0
  10. package/dist/analyzer/types.js +323 -0
  11. package/dist/codegen/agents.d.ts +2 -0
  12. package/dist/codegen/agents.js +109 -0
  13. package/dist/codegen/backend.d.ts +16 -0
  14. package/dist/codegen/backend.js +1 -0
  15. package/dist/codegen/claude-backend.d.ts +9 -0
  16. package/dist/codegen/claude-backend.js +47 -0
  17. package/dist/codegen/codegen.d.ts +10 -0
  18. package/dist/codegen/codegen.js +57 -0
  19. package/dist/codegen/hooks.d.ts +2 -0
  20. package/dist/codegen/hooks.js +165 -0
  21. package/dist/codegen/orchestration.d.ts +3 -0
  22. package/dist/codegen/orchestration.js +250 -0
  23. package/dist/codegen/settings.d.ts +36 -0
  24. package/dist/codegen/settings.js +87 -0
  25. package/dist/compiler.d.ts +21 -0
  26. package/dist/compiler.js +101 -0
  27. package/dist/constants.d.ts +9 -0
  28. package/dist/constants.js +13 -0
  29. package/dist/errors/diagnostics.d.ts +21 -0
  30. package/dist/errors/diagnostics.js +25 -0
  31. package/dist/format.d.ts +12 -0
  32. package/dist/format.js +46 -0
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +181 -0
  35. package/dist/lexer/lexer.d.ts +23 -0
  36. package/dist/lexer/lexer.js +268 -0
  37. package/dist/lexer/tokens.d.ts +96 -0
  38. package/dist/lexer/tokens.js +150 -0
  39. package/dist/lsp/features/code-actions.d.ts +7 -0
  40. package/dist/lsp/features/code-actions.js +58 -0
  41. package/dist/lsp/features/completions.d.ts +7 -0
  42. package/dist/lsp/features/completions.js +271 -0
  43. package/dist/lsp/features/definition.d.ts +3 -0
  44. package/dist/lsp/features/definition.js +32 -0
  45. package/dist/lsp/features/diagnostics.d.ts +4 -0
  46. package/dist/lsp/features/diagnostics.js +33 -0
  47. package/dist/lsp/features/hover.d.ts +7 -0
  48. package/dist/lsp/features/hover.js +88 -0
  49. package/dist/lsp/features/index.d.ts +9 -0
  50. package/dist/lsp/features/index.js +9 -0
  51. package/dist/lsp/features/references.d.ts +7 -0
  52. package/dist/lsp/features/references.js +53 -0
  53. package/dist/lsp/features/rename.d.ts +17 -0
  54. package/dist/lsp/features/rename.js +198 -0
  55. package/dist/lsp/features/symbols.d.ts +7 -0
  56. package/dist/lsp/features/symbols.js +74 -0
  57. package/dist/lsp/features/utils.d.ts +3 -0
  58. package/dist/lsp/features/utils.js +65 -0
  59. package/dist/lsp/features.d.ts +20 -0
  60. package/dist/lsp/features.js +513 -0
  61. package/dist/lsp/server.d.ts +2 -0
  62. package/dist/lsp/server.js +327 -0
  63. package/dist/parser/ast.d.ts +244 -0
  64. package/dist/parser/ast.js +10 -0
  65. package/dist/parser/parser.d.ts +95 -0
  66. package/dist/parser/parser.js +1175 -0
  67. package/dist/program-index.d.ts +21 -0
  68. package/dist/program-index.js +74 -0
  69. package/dist/resolver/resolver.d.ts +9 -0
  70. package/dist/resolver/resolver.js +136 -0
  71. package/dist/runner.d.ts +13 -0
  72. package/dist/runner.js +41 -0
  73. package/dist/runtime/executor.d.ts +56 -0
  74. package/dist/runtime/executor.js +285 -0
  75. package/dist/runtime/expr-eval.d.ts +3 -0
  76. package/dist/runtime/expr-eval.js +138 -0
  77. package/dist/runtime/flow-runner.d.ts +21 -0
  78. package/dist/runtime/flow-runner.js +230 -0
  79. package/dist/runtime/memory.d.ts +5 -0
  80. package/dist/runtime/memory.js +41 -0
  81. package/dist/runtime/prompt-builder.d.ts +12 -0
  82. package/dist/runtime/prompt-builder.js +66 -0
  83. package/dist/runtime/subprocess.d.ts +20 -0
  84. package/dist/runtime/subprocess.js +99 -0
  85. package/dist/runtime/token-tracker.d.ts +36 -0
  86. package/dist/runtime/token-tracker.js +56 -0
  87. package/dist/runtime/transforms.d.ts +2 -0
  88. package/dist/runtime/transforms.js +104 -0
  89. package/dist/types.d.ts +10 -0
  90. package/dist/types.js +1 -0
  91. package/dist/utils.d.ts +3 -0
  92. package/dist/utils.js +35 -0
  93. package/dist/version.d.ts +1 -0
  94. package/dist/version.js +11 -0
  95. package/package.json +70 -0
@@ -0,0 +1,153 @@
1
+ import { BUILTIN_FUNCTIONS } from '../parser/ast.js';
2
+ import { GraftError } from '../errors/diagnostics.js';
3
+ export function checkVarCollision(name, graphName, location, index, errors) {
4
+ if (index.nodeMap.has(name)) {
5
+ errors.push(new GraftError(`Variable '${name}' in graph '${graphName}' collides with declared node '${name}'`, location, 'error', 'SCOPE_VAR_COLLISION'));
6
+ }
7
+ else if (index.contextMap.has(name)) {
8
+ errors.push(new GraftError(`Variable '${name}' in graph '${graphName}' collides with declared context '${name}'`, location, 'error', 'SCOPE_VAR_COLLISION'));
9
+ }
10
+ else if (index.memoryMap.has(name)) {
11
+ errors.push(new GraftError(`Variable '${name}' in graph '${graphName}' collides with declared memory '${name}'`, location, 'error', 'SCOPE_VAR_COLLISION'));
12
+ }
13
+ else if (index.graphMap.has(name)) {
14
+ errors.push(new GraftError(`Variable '${name}' in graph '${graphName}' collides with declared graph '${name}'`, location, 'error', 'SCOPE_VAR_COLLISION'));
15
+ }
16
+ }
17
+ export function checkExprSources(expr, seenNodes, declaredVars, graphName, errors) {
18
+ switch (expr.kind) {
19
+ case 'literal':
20
+ break;
21
+ case 'field_access': {
22
+ const first = expr.segments[0];
23
+ if (expr.segments.length === 1) {
24
+ if (!declaredVars.has(first) && !seenNodes.has(first)) {
25
+ errors.push(new GraftError(`Variable or node '${first}' referenced before declaration in graph '${graphName}'`, expr.location, 'error', 'SCOPE_VAR_ORDER'));
26
+ }
27
+ }
28
+ else {
29
+ if (!seenNodes.has(first) && !declaredVars.has(first)) {
30
+ errors.push(new GraftError(`Node '${first}' referenced before appearance in graph '${graphName}' flow`, expr.location, 'error', 'SCOPE_VAR_ORDER'));
31
+ }
32
+ }
33
+ break;
34
+ }
35
+ case 'binary':
36
+ checkExprSources(expr.left, seenNodes, declaredVars, graphName, errors);
37
+ checkExprSources(expr.right, seenNodes, declaredVars, graphName, errors);
38
+ break;
39
+ case 'unary':
40
+ checkExprSources(expr.operand, seenNodes, declaredVars, graphName, errors);
41
+ break;
42
+ case 'group':
43
+ checkExprSources(expr.inner, seenNodes, declaredVars, graphName, errors);
44
+ break;
45
+ case 'call':
46
+ if (!(expr.name in BUILTIN_FUNCTIONS)) {
47
+ errors.push(new GraftError(`Unknown function '${expr.name}' in graph '${graphName}'`, expr.location, 'error', 'SCOPE_UNKNOWN_FUNCTION'));
48
+ }
49
+ for (const arg of expr.args) {
50
+ checkExprSources(arg, seenNodes, declaredVars, graphName, errors);
51
+ }
52
+ break;
53
+ case 'template':
54
+ for (const part of expr.parts) {
55
+ if (part.kind === 'expr') {
56
+ checkExprSources(part.value, seenNodes, declaredVars, graphName, errors);
57
+ }
58
+ }
59
+ break;
60
+ case 'conditional':
61
+ checkExprSources(expr.condition, seenNodes, declaredVars, graphName, errors);
62
+ checkExprSources(expr.consequent, seenNodes, declaredVars, graphName, errors);
63
+ checkExprSources(expr.alternate, seenNodes, declaredVars, graphName, errors);
64
+ break;
65
+ default: {
66
+ const _exhaustive = expr;
67
+ throw new Error(`Unhandled expression kind: ${_exhaustive.kind}`);
68
+ }
69
+ }
70
+ }
71
+ export function checkGraphCallArgs(args, graphDecl, seenNodes, declaredVars, graphName, location, index, errors) {
72
+ const paramMap = new Map(graphDecl.params.map(p => [p.name, p]));
73
+ const providedNames = new Set();
74
+ for (const arg of args) {
75
+ providedNames.add(arg.name);
76
+ const param = paramMap.get(arg.name);
77
+ if (!param) {
78
+ errors.push(new GraftError(`Unknown parameter '${arg.name}' in call to graph '${graphDecl.name}'`, arg.location, 'error', 'SCOPE_GRAPH_PARAM_TYPE'));
79
+ continue;
80
+ }
81
+ if (param.type === 'Node') {
82
+ if (arg.value.kind !== 'field_access' || arg.value.segments.length !== 1) {
83
+ errors.push(new GraftError(`Parameter '${arg.name}' of type Node requires a node name, not an expression`, arg.location, 'error', 'SCOPE_GRAPH_PARAM_TYPE'));
84
+ }
85
+ else if (!index.nodeMap.has(arg.value.segments[0])) {
86
+ errors.push(new GraftError(`Parameter '${arg.name}' references undeclared node '${arg.value.segments[0]}'`, arg.location, 'error', 'SCOPE_UNDEFINED_REF'));
87
+ }
88
+ }
89
+ else {
90
+ checkExprSources(arg.value, seenNodes, declaredVars, graphName, errors);
91
+ if (arg.value.kind === 'literal') {
92
+ if (!checkLiteralParamType(arg.value.value, param.type)) {
93
+ errors.push(new GraftError(`Parameter '${arg.name}' expects type ${param.type}, got ${typeof arg.value.value}`, arg.location, 'error', 'SCOPE_GRAPH_PARAM_TYPE'));
94
+ }
95
+ }
96
+ }
97
+ }
98
+ for (const param of graphDecl.params) {
99
+ if (!providedNames.has(param.name) && param.default === undefined) {
100
+ errors.push(new GraftError(`Missing required parameter '${param.name}' in call to graph '${graphDecl.name}'`, location, 'error', 'SCOPE_GRAPH_PARAM_MISSING'));
101
+ }
102
+ }
103
+ }
104
+ export function checkGraphRecursion(graphs, index, errors) {
105
+ const callGraph = new Map();
106
+ for (const graph of graphs) {
107
+ const calls = new Set();
108
+ collectGraphCalls(graph.flow, calls);
109
+ callGraph.set(graph.name, calls);
110
+ }
111
+ const visited = new Set();
112
+ const inStack = new Set();
113
+ for (const graphName of callGraph.keys()) {
114
+ if (visited.has(graphName))
115
+ continue;
116
+ dfsGraphCycles(graphName, callGraph, visited, inStack, index, errors);
117
+ }
118
+ }
119
+ export function collectGraphCalls(nodes, calls) {
120
+ for (const step of nodes) {
121
+ if (step.kind === 'graph_call') {
122
+ calls.add(step.name);
123
+ }
124
+ else if (step.kind === 'foreach') {
125
+ collectGraphCalls(step.body, calls);
126
+ }
127
+ }
128
+ }
129
+ function dfsGraphCycles(current, callGraph, visited, inStack, index, errors) {
130
+ visited.add(current);
131
+ inStack.add(current);
132
+ const calls = callGraph.get(current);
133
+ if (calls) {
134
+ for (const callee of calls) {
135
+ if (inStack.has(callee)) {
136
+ const graph = index.graphMap.get(current);
137
+ errors.push(new GraftError(`Recursive graph call detected: '${current}' calls '${callee}' which creates a cycle`, graph?.location ?? { line: 0, column: 0, offset: 0 }, 'error', 'SCOPE_GRAPH_RECURSION'));
138
+ }
139
+ else if (!visited.has(callee)) {
140
+ dfsGraphCycles(callee, callGraph, visited, inStack, index, errors);
141
+ }
142
+ }
143
+ }
144
+ inStack.delete(current);
145
+ }
146
+ export function checkLiteralParamType(value, type) {
147
+ switch (type) {
148
+ case 'Int': return typeof value === 'number';
149
+ case 'String': return typeof value === 'string';
150
+ case 'Bool': return typeof value === 'boolean';
151
+ default: return false;
152
+ }
153
+ }
@@ -0,0 +1,21 @@
1
+ import { Program } from '../parser/ast.js';
2
+ import { GraftError } from '../errors/diagnostics.js';
3
+ import { ProgramIndex } from '../program-index.js';
4
+ export declare class ScopeChecker {
5
+ private program;
6
+ private nodeWritesMap;
7
+ private index;
8
+ constructor(program: Program, index?: ProgramIndex);
9
+ check(): GraftError[];
10
+ private checkDuplicateNames;
11
+ private checkMaxTokens;
12
+ private checkNodeReads;
13
+ private checkNodeWrites;
14
+ private checkEdges;
15
+ private checkMultipleGraphs;
16
+ private checkGraphFlow;
17
+ private walkFlowNodes;
18
+ private checkFailureStrategies;
19
+ private checkFallbackCycles;
20
+ private checkParallelWrites;
21
+ }
@@ -0,0 +1,324 @@
1
+ import { GraftError } from '../errors/diagnostics.js';
2
+ import { ProgramIndex } from '../program-index.js';
3
+ import { checkVarCollision, checkExprSources, checkGraphCallArgs, checkGraphRecursion, } from './graph-checker.js';
4
+ export class ScopeChecker {
5
+ program;
6
+ nodeWritesMap; // node name -> writes targets
7
+ index;
8
+ constructor(program, index) {
9
+ this.program = program;
10
+ this.index = index ?? new ProgramIndex(program);
11
+ this.nodeWritesMap = new Map();
12
+ for (const node of program.nodes) {
13
+ this.nodeWritesMap.set(node.name, node.writes);
14
+ }
15
+ }
16
+ check() {
17
+ const errors = [];
18
+ this.checkDuplicateNames(errors);
19
+ this.checkMaxTokens(errors);
20
+ this.checkNodeReads(errors);
21
+ this.checkNodeWrites(errors);
22
+ this.checkEdges(errors);
23
+ this.checkMultipleGraphs(errors);
24
+ this.checkGraphFlow(errors);
25
+ checkGraphRecursion(this.program.graphs, this.index, errors);
26
+ this.checkFailureStrategies(errors);
27
+ return errors;
28
+ }
29
+ checkDuplicateNames(errors) {
30
+ for (const mem of this.program.memories) {
31
+ if (this.index.contextMap.has(mem.name)) {
32
+ errors.push(new GraftError(`Name '${mem.name}' is declared as both a context and a memory`, mem.location, 'error', 'SCOPE_DUPLICATE_NAME'));
33
+ }
34
+ if (this.index.producesFieldsMap.has(mem.name)) {
35
+ errors.push(new GraftError(`Name '${mem.name}' conflicts with a produces declaration`, mem.location, 'error', 'SCOPE_DUPLICATE_NAME'));
36
+ }
37
+ }
38
+ }
39
+ checkMaxTokens(errors) {
40
+ for (const ctx of this.program.contexts) {
41
+ if (ctx.maxTokens <= 0) {
42
+ errors.push(new GraftError(`Context '${ctx.name}' has invalid max_tokens: ${ctx.maxTokens} (must be > 0)`, ctx.location, 'error', 'SCOPE_MAX_TOKENS_INVALID'));
43
+ }
44
+ }
45
+ for (const mem of this.program.memories) {
46
+ if (mem.maxTokens <= 0) {
47
+ errors.push(new GraftError(`Memory '${mem.name}' has invalid max_tokens: ${mem.maxTokens} (must be > 0)`, mem.location, 'error', 'SCOPE_MAX_TOKENS_INVALID'));
48
+ }
49
+ }
50
+ }
51
+ checkNodeReads(errors) {
52
+ for (const node of this.program.nodes) {
53
+ for (const ref of node.reads) {
54
+ // ref.context could be a context name, produces name, or memory name
55
+ const isContext = this.index.contextMap.has(ref.context);
56
+ const isProduces = this.index.producesFieldsMap.has(ref.context);
57
+ const isMemory = this.index.memoryMap.has(ref.context);
58
+ if (!isContext && !isProduces && !isMemory) {
59
+ errors.push(new GraftError(`'${ref.context}' is not declared as a context, produces output, or memory`, ref.location, 'error', 'SCOPE_UNDEFINED_REF'));
60
+ continue;
61
+ }
62
+ // Check partial reference fields
63
+ if (ref.field) {
64
+ if (isContext) {
65
+ const ctx = this.index.contextMap.get(ref.context);
66
+ const fieldNames = new Set(ctx.fields.map(f => f.name));
67
+ for (const f of ref.field) {
68
+ if (!fieldNames.has(f)) {
69
+ errors.push(new GraftError(`Field '${f}' does not exist in context '${ref.context}'`, ref.location, 'error', 'SCOPE_FIELD_NOT_FOUND'));
70
+ }
71
+ }
72
+ }
73
+ else if (isProduces) {
74
+ const fields = this.index.producesFieldsMap.get(ref.context);
75
+ for (const f of ref.field) {
76
+ if (!fields.has(f)) {
77
+ errors.push(new GraftError(`Field '${f}' does not exist in produces '${ref.context}'`, ref.location, 'error', 'SCOPE_FIELD_NOT_FOUND'));
78
+ }
79
+ }
80
+ }
81
+ else if (isMemory) {
82
+ const fields = this.index.memoryFieldsMap.get(ref.context);
83
+ for (const f of ref.field) {
84
+ if (!fields.has(f)) {
85
+ errors.push(new GraftError(`Field '${f}' does not exist in memory '${ref.context}'`, ref.location, 'error', 'SCOPE_FIELD_NOT_FOUND'));
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+ checkNodeWrites(errors) {
94
+ for (const node of this.program.nodes) {
95
+ for (const writeRef of node.writes) {
96
+ if (!this.index.memoryMap.has(writeRef.memory)) {
97
+ errors.push(new GraftError(`writes target '${writeRef.memory}' is not a declared memory`, writeRef.location, 'error', 'SCOPE_INVALID_WRITES'));
98
+ }
99
+ else if (writeRef.field) {
100
+ const fields = this.index.memoryFieldsMap.get(writeRef.memory);
101
+ if (!fields.has(writeRef.field)) {
102
+ errors.push(new GraftError(`Field '${writeRef.field}' does not exist in memory '${writeRef.memory}'`, writeRef.location, 'error', 'SCOPE_FIELD_NOT_FOUND'));
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ checkEdges(errors) {
109
+ for (const edge of this.program.edges) {
110
+ if (!this.index.nodeMap.has(edge.source)) {
111
+ errors.push(new GraftError(`Edge source '${edge.source}' is not a declared node`, edge.location, 'error', 'SCOPE_UNDEFINED_REF'));
112
+ }
113
+ if (edge.target.kind === 'direct') {
114
+ if (!this.index.nodeMap.has(edge.target.node)) {
115
+ errors.push(new GraftError(`Edge target '${edge.target.node}' is not a declared node`, edge.location, 'error', 'SCOPE_UNDEFINED_REF'));
116
+ }
117
+ }
118
+ else {
119
+ for (const branch of edge.target.branches) {
120
+ if (branch.target !== 'done' && !this.index.nodeMap.has(branch.target)) {
121
+ errors.push(new GraftError(`Edge target '${branch.target}' is not a declared node`, edge.location, 'error', 'SCOPE_UNDEFINED_REF'));
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ checkMultipleGraphs(errors) {
128
+ if (this.program.graphs.length > 1) {
129
+ errors.push(new GraftError(`Multiple graphs declared; only the first graph '${this.program.graphs[0].name}' will be executed`, this.program.graphs[1].location, 'warning', 'GRAPH_MULTIPLE'));
130
+ }
131
+ }
132
+ checkGraphFlow(errors) {
133
+ for (const graph of this.program.graphs) {
134
+ // Validate graph input references a declared context
135
+ if (!this.index.contextMap.has(graph.input)) {
136
+ errors.push(new GraftError(`Graph input '${graph.input}' is not a declared context`, graph.location, 'error', 'SCOPE_UNDEFINED_REF'));
137
+ }
138
+ // Validate graph output references a declared produces type
139
+ if (!this.index.producesFieldsMap.has(graph.output)) {
140
+ errors.push(new GraftError(`Graph output '${graph.output}' is not a declared produces type`, graph.location, 'error', 'SCOPE_UNDEFINED_REF'));
141
+ }
142
+ // Pre-populate seenNodes with Node-type graph params (they alias real nodes)
143
+ const paramNodes = new Set();
144
+ for (const param of graph.params) {
145
+ if (param.type === 'Node') {
146
+ paramNodes.add(param.name);
147
+ }
148
+ }
149
+ // Walk FlowNode tree with variable tracking
150
+ this.walkFlowNodes(graph.flow, graph.location, errors, graph.name, undefined, paramNodes);
151
+ }
152
+ }
153
+ walkFlowNodes(nodes, location, errors, graphName, declaredVars, seenNodes) {
154
+ const vars = declaredVars ?? new Set();
155
+ const seen = seenNodes ?? new Set();
156
+ for (const step of nodes) {
157
+ switch (step.kind) {
158
+ case 'node':
159
+ if (!this.index.nodeMap.has(step.name) && !seen.has(step.name)) {
160
+ errors.push(new GraftError(`Node '${step.name}' in graph flow is not declared`, step.location ?? location, 'error', 'SCOPE_UNDEFINED_REF'));
161
+ }
162
+ else {
163
+ seen.add(step.name);
164
+ }
165
+ break;
166
+ case 'parallel':
167
+ for (const branch of step.branches) {
168
+ if (!this.index.nodeMap.has(branch)) {
169
+ errors.push(new GraftError(`Node '${branch}' in parallel block is not declared`, step.location ?? location, 'error', 'SCOPE_UNDEFINED_REF'));
170
+ }
171
+ else {
172
+ seen.add(branch);
173
+ }
174
+ }
175
+ this.checkParallelWrites(step.branches, step.location ?? location, errors);
176
+ break;
177
+ case 'foreach': {
178
+ // Validate source node exists
179
+ if (!this.index.nodeMap.has(step.source)) {
180
+ errors.push(new GraftError(`Foreach source node '${step.source}' is not declared`, step.location ?? location, 'error', 'SCOPE_UNDEFINED_REF'));
181
+ }
182
+ // Validate source node produces the referenced field
183
+ const sourceNode = this.index.nodeMap.get(step.source);
184
+ if (sourceNode) {
185
+ const fieldNames = new Set(sourceNode.produces.fields.map(f => f.name));
186
+ if (!fieldNames.has(step.field)) {
187
+ errors.push(new GraftError(`Field '${step.field}' does not exist in '${step.source}' produces output`, step.location ?? location, 'error', 'SCOPE_FIELD_NOT_FOUND'));
188
+ }
189
+ }
190
+ if (step.maxIterations < 1) {
191
+ errors.push(new GraftError('foreach max_iterations must be at least 1', step.location ?? location, 'error', 'SCOPE_INVALID_FOREACH'));
192
+ }
193
+ // C-01: Foreach binding name collision detection
194
+ const binding = step.binding;
195
+ if (this.index.nodeMap.has(binding)) {
196
+ errors.push(new GraftError(`Foreach binding '${binding}' collides with declared node '${binding}'`, step.location ?? location, 'warning', 'SCOPE_BINDING_COLLISION'));
197
+ }
198
+ else if (this.index.producesFieldsMap.has(binding)) {
199
+ errors.push(new GraftError(`Foreach binding '${binding}' collides with produces declaration '${binding}'`, step.location ?? location, 'warning', 'SCOPE_BINDING_COLLISION'));
200
+ }
201
+ else if (this.index.contextMap.has(binding)) {
202
+ errors.push(new GraftError(`Foreach binding '${binding}' collides with declared context '${binding}'`, step.location ?? location, 'warning', 'SCOPE_BINDING_COLLISION'));
203
+ }
204
+ else if (this.index.memoryMap.has(binding)) {
205
+ errors.push(new GraftError(`Foreach binding '${binding}' collides with declared memory '${binding}'`, step.location ?? location, 'warning', 'SCOPE_BINDING_COLLISION'));
206
+ }
207
+ // Clone vars for foreach body (body vars don't leak out)
208
+ const bodyVars = new Set(vars);
209
+ bodyVars.add(binding);
210
+ this.walkFlowNodes(step.body, step.location ?? location, errors, graphName, bodyVars, seen);
211
+ break;
212
+ }
213
+ case 'let': {
214
+ const loc = step.location ?? location;
215
+ // Variable-variable collision
216
+ if (vars.has(step.name)) {
217
+ errors.push(new GraftError(`Variable '${step.name}' is already declared in graph '${graphName}'`, loc, 'error', 'SCOPE_VAR_COLLISION'));
218
+ }
219
+ // Variable vs top-level name collision
220
+ checkVarCollision(step.name, graphName, loc, this.index, errors);
221
+ // Variable order: validate expression sources are declared
222
+ checkExprSources(step.value, seen, vars, graphName, errors);
223
+ vars.add(step.name);
224
+ break;
225
+ }
226
+ case 'graph_call': {
227
+ const loc = step.location ?? location;
228
+ const graphDecl = this.index.graphMap.get(step.name);
229
+ if (!graphDecl) {
230
+ errors.push(new GraftError(`Graph '${step.name}' in graph call is not declared`, loc, 'error', 'SCOPE_UNDEFINED_REF'));
231
+ }
232
+ else {
233
+ checkGraphCallArgs(step.args, graphDecl, seen, vars, graphName, loc, this.index, errors);
234
+ }
235
+ break;
236
+ }
237
+ default: {
238
+ const _exhaustive = step;
239
+ throw new Error(`Unhandled FlowNode kind: ${_exhaustive.kind}`);
240
+ }
241
+ }
242
+ }
243
+ }
244
+ checkFailureStrategies(errors) {
245
+ for (const node of this.program.nodes) {
246
+ if (!node.onFailure)
247
+ continue;
248
+ const strategy = node.onFailure;
249
+ if (strategy.type === 'fallback' || strategy.type === 'retry_then_fallback') {
250
+ if (!this.index.nodeMap.has(strategy.node)) {
251
+ errors.push(new GraftError(`Fallback node '${strategy.node}' in '${node.name}' on_failure is not a declared node`, node.location, 'error', 'SCOPE_INVALID_FALLBACK'));
252
+ }
253
+ }
254
+ }
255
+ this.checkFallbackCycles(errors);
256
+ }
257
+ checkFallbackCycles(errors) {
258
+ // Build directed graph: node name -> fallback target
259
+ const fallbackEdges = new Map();
260
+ const nodeLocationMap = new Map();
261
+ for (const node of this.program.nodes) {
262
+ nodeLocationMap.set(node.name, node.location);
263
+ if (!node.onFailure)
264
+ continue;
265
+ const strategy = node.onFailure;
266
+ if (strategy.type === 'fallback' || strategy.type === 'retry_then_fallback') {
267
+ fallbackEdges.set(node.name, strategy.node);
268
+ }
269
+ }
270
+ // DFS cycle detection with visited + in-stack
271
+ const visited = new Set();
272
+ const inStack = new Set();
273
+ for (const start of fallbackEdges.keys()) {
274
+ if (visited.has(start))
275
+ continue;
276
+ const stack = [start];
277
+ while (stack.length > 0) {
278
+ const current = stack[stack.length - 1];
279
+ if (!inStack.has(current)) {
280
+ // First visit: mark in-stack
281
+ inStack.add(current);
282
+ visited.add(current);
283
+ const target = fallbackEdges.get(current);
284
+ if (target) {
285
+ if (inStack.has(target)) {
286
+ // Cycle detected
287
+ errors.push(new GraftError(`Fallback cycle detected: '${current}' falls back to '${target}' which creates a cycle`, nodeLocationMap.get(current), 'error', 'SCOPE_FALLBACK_CYCLE'));
288
+ }
289
+ else if (!visited.has(target)) {
290
+ stack.push(target);
291
+ continue;
292
+ }
293
+ }
294
+ }
295
+ // Backtrack
296
+ stack.pop();
297
+ inStack.delete(current);
298
+ }
299
+ }
300
+ }
301
+ checkParallelWrites(branches, location, errors) {
302
+ const memoryWriters = new Map();
303
+ for (const branch of branches) {
304
+ const writes = this.nodeWritesMap.get(branch);
305
+ if (!writes)
306
+ continue;
307
+ for (const writeRef of writes) {
308
+ const memName = writeRef.memory;
309
+ const writers = memoryWriters.get(memName);
310
+ if (writers) {
311
+ writers.push(branch);
312
+ }
313
+ else {
314
+ memoryWriters.set(memName, [branch]);
315
+ }
316
+ }
317
+ }
318
+ for (const [memName, writers] of memoryWriters) {
319
+ if (writers.length > 1) {
320
+ errors.push(new GraftError(`Nodes ${writers.map(w => `'${w}'`).join(' and ')} both write to memory '${memName}' in parallel`, location, 'warning', 'SCOPE_PARALLEL_WRITES'));
321
+ }
322
+ }
323
+ }
324
+ }
@@ -0,0 +1,17 @@
1
+ import { Program } from '../parser/ast.js';
2
+ import { GraftError } from '../errors/diagnostics.js';
3
+ import { ProgramIndex } from '../program-index.js';
4
+ export declare class TypeChecker {
5
+ private program;
6
+ private index;
7
+ constructor(program: Program, index?: ProgramIndex);
8
+ check(): GraftError[];
9
+ private checkWritesSchemaOverlap;
10
+ private checkEdgeTransforms;
11
+ private checkConditionTypes;
12
+ private checkExprTypes;
13
+ private walkFlowForTypes;
14
+ private inferExprType;
15
+ private checkExprTypeErrors;
16
+ private checkVarConditionTypes;
17
+ }