@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,96 @@
1
+ import { SourceLocation } from '../errors/diagnostics.js';
2
+ export declare enum TokenType {
3
+ Node = "Node",
4
+ Edge = "Edge",
5
+ Graph = "Graph",
6
+ Context = "Context",
7
+ Reads = "Reads",
8
+ Produces = "Produces",
9
+ Tools = "Tools",
10
+ Budget = "Budget",
11
+ Model = "Model",
12
+ Select = "Select",
13
+ Filter = "Filter",
14
+ Drop = "Drop",
15
+ Compact = "Compact",
16
+ Truncate = "Truncate",
17
+ When = "When",
18
+ Else = "Else",
19
+ Done = "Done",
20
+ OnFailure = "OnFailure",
21
+ Retry = "Retry",
22
+ Fallback = "Fallback",
23
+ Skip = "Skip",
24
+ Abort = "Abort",
25
+ Input = "Input",
26
+ Output = "Output",
27
+ MaxTokens = "MaxTokens",
28
+ Enum = "Enum",
29
+ True = "True",
30
+ False = "False",
31
+ Parallel = "Parallel",
32
+ Foreach = "Foreach",
33
+ As = "As",
34
+ MaxIterations = "MaxIterations",
35
+ Import = "Import",
36
+ From = "From",
37
+ Memory = "Memory",
38
+ Writes = "Writes",
39
+ Storage = "Storage",
40
+ Let = "Let",
41
+ Then = "Then",
42
+ If = "If",
43
+ String = "String",
44
+ Int = "Int",
45
+ Float = "Float",
46
+ Bool = "Bool",
47
+ List = "List",
48
+ Map = "Map",
49
+ Optional = "Optional",
50
+ TokenBounded = "TokenBounded",
51
+ FilePath = "FilePath",
52
+ FileDiff = "FileDiff",
53
+ TestFile = "TestFile",
54
+ IssueRef = "IssueRef",
55
+ IntegerLiteral = "IntegerLiteral",
56
+ KIntegerLiteral = "KIntegerLiteral",
57
+ FloatLiteral = "FloatLiteral",
58
+ StringLiteral = "StringLiteral",
59
+ TemplateString = "TemplateString",
60
+ Identifier = "Identifier",
61
+ LBrace = "LBrace",
62
+ RBrace = "RBrace",
63
+ LParen = "LParen",
64
+ RParen = "RParen",
65
+ LBracket = "LBracket",
66
+ RBracket = "RBracket",
67
+ Colon = "Colon",
68
+ Comma = "Comma",
69
+ Dot = "Dot",
70
+ Arrow = "Arrow",
71
+ Pipe = "Pipe",
72
+ Slash = "Slash",
73
+ Star = "Star",
74
+ Percent = "Percent",
75
+ Plus = "Plus",
76
+ Minus = "Minus",
77
+ Bang = "Bang",
78
+ Equals = "Equals",
79
+ DotDot = "DotDot",
80
+ GreaterEqual = "GreaterEqual",
81
+ Greater = "Greater",
82
+ LessEqual = "LessEqual",
83
+ Less = "Less",
84
+ EqualEqual = "EqualEqual",
85
+ BangEqual = "BangEqual",
86
+ AmpAmp = "AmpAmp",
87
+ PipePipe = "PipePipe",
88
+ QuestionQuestion = "QuestionQuestion",
89
+ EOF = "EOF"
90
+ }
91
+ export interface Token {
92
+ type: TokenType;
93
+ value: string;
94
+ location: SourceLocation;
95
+ }
96
+ export declare const KEYWORDS: Record<string, TokenType>;
@@ -0,0 +1,150 @@
1
+ export var TokenType;
2
+ (function (TokenType) {
3
+ // Keywords
4
+ TokenType["Node"] = "Node";
5
+ TokenType["Edge"] = "Edge";
6
+ TokenType["Graph"] = "Graph";
7
+ TokenType["Context"] = "Context";
8
+ TokenType["Reads"] = "Reads";
9
+ TokenType["Produces"] = "Produces";
10
+ TokenType["Tools"] = "Tools";
11
+ TokenType["Budget"] = "Budget";
12
+ TokenType["Model"] = "Model";
13
+ TokenType["Select"] = "Select";
14
+ TokenType["Filter"] = "Filter";
15
+ TokenType["Drop"] = "Drop";
16
+ TokenType["Compact"] = "Compact";
17
+ TokenType["Truncate"] = "Truncate";
18
+ TokenType["When"] = "When";
19
+ TokenType["Else"] = "Else";
20
+ TokenType["Done"] = "Done";
21
+ TokenType["OnFailure"] = "OnFailure";
22
+ TokenType["Retry"] = "Retry";
23
+ TokenType["Fallback"] = "Fallback";
24
+ TokenType["Skip"] = "Skip";
25
+ TokenType["Abort"] = "Abort";
26
+ TokenType["Input"] = "Input";
27
+ TokenType["Output"] = "Output";
28
+ TokenType["MaxTokens"] = "MaxTokens";
29
+ TokenType["Enum"] = "Enum";
30
+ TokenType["True"] = "True";
31
+ TokenType["False"] = "False";
32
+ TokenType["Parallel"] = "Parallel";
33
+ TokenType["Foreach"] = "Foreach";
34
+ TokenType["As"] = "As";
35
+ TokenType["MaxIterations"] = "MaxIterations";
36
+ TokenType["Import"] = "Import";
37
+ TokenType["From"] = "From";
38
+ TokenType["Memory"] = "Memory";
39
+ TokenType["Writes"] = "Writes";
40
+ TokenType["Storage"] = "Storage";
41
+ TokenType["Let"] = "Let";
42
+ TokenType["Then"] = "Then";
43
+ TokenType["If"] = "If";
44
+ // Type keywords
45
+ TokenType["String"] = "String";
46
+ TokenType["Int"] = "Int";
47
+ TokenType["Float"] = "Float";
48
+ TokenType["Bool"] = "Bool";
49
+ TokenType["List"] = "List";
50
+ TokenType["Map"] = "Map";
51
+ TokenType["Optional"] = "Optional";
52
+ TokenType["TokenBounded"] = "TokenBounded";
53
+ TokenType["FilePath"] = "FilePath";
54
+ TokenType["FileDiff"] = "FileDiff";
55
+ TokenType["TestFile"] = "TestFile";
56
+ TokenType["IssueRef"] = "IssueRef";
57
+ // Literals
58
+ TokenType["IntegerLiteral"] = "IntegerLiteral";
59
+ TokenType["KIntegerLiteral"] = "KIntegerLiteral";
60
+ TokenType["FloatLiteral"] = "FloatLiteral";
61
+ TokenType["StringLiteral"] = "StringLiteral";
62
+ TokenType["TemplateString"] = "TemplateString";
63
+ // Identifiers
64
+ TokenType["Identifier"] = "Identifier";
65
+ // Symbols
66
+ TokenType["LBrace"] = "LBrace";
67
+ TokenType["RBrace"] = "RBrace";
68
+ TokenType["LParen"] = "LParen";
69
+ TokenType["RParen"] = "RParen";
70
+ TokenType["LBracket"] = "LBracket";
71
+ TokenType["RBracket"] = "RBracket";
72
+ TokenType["Colon"] = "Colon";
73
+ TokenType["Comma"] = "Comma";
74
+ TokenType["Dot"] = "Dot";
75
+ TokenType["Arrow"] = "Arrow";
76
+ TokenType["Pipe"] = "Pipe";
77
+ TokenType["Slash"] = "Slash";
78
+ TokenType["Star"] = "Star";
79
+ TokenType["Percent"] = "Percent";
80
+ TokenType["Plus"] = "Plus";
81
+ TokenType["Minus"] = "Minus";
82
+ TokenType["Bang"] = "Bang";
83
+ TokenType["Equals"] = "Equals";
84
+ TokenType["DotDot"] = "DotDot";
85
+ TokenType["GreaterEqual"] = "GreaterEqual";
86
+ TokenType["Greater"] = "Greater";
87
+ TokenType["LessEqual"] = "LessEqual";
88
+ TokenType["Less"] = "Less";
89
+ TokenType["EqualEqual"] = "EqualEqual";
90
+ TokenType["BangEqual"] = "BangEqual";
91
+ TokenType["AmpAmp"] = "AmpAmp";
92
+ TokenType["PipePipe"] = "PipePipe";
93
+ TokenType["QuestionQuestion"] = "QuestionQuestion";
94
+ // Special
95
+ TokenType["EOF"] = "EOF";
96
+ })(TokenType || (TokenType = {}));
97
+ export const KEYWORDS = {
98
+ node: TokenType.Node,
99
+ edge: TokenType.Edge,
100
+ graph: TokenType.Graph,
101
+ context: TokenType.Context,
102
+ reads: TokenType.Reads,
103
+ produces: TokenType.Produces,
104
+ tools: TokenType.Tools,
105
+ budget: TokenType.Budget,
106
+ model: TokenType.Model,
107
+ select: TokenType.Select,
108
+ filter: TokenType.Filter,
109
+ drop: TokenType.Drop,
110
+ compact: TokenType.Compact,
111
+ truncate: TokenType.Truncate,
112
+ when: TokenType.When,
113
+ else: TokenType.Else,
114
+ done: TokenType.Done,
115
+ on_failure: TokenType.OnFailure,
116
+ retry: TokenType.Retry,
117
+ fallback: TokenType.Fallback,
118
+ skip: TokenType.Skip,
119
+ abort: TokenType.Abort,
120
+ input: TokenType.Input,
121
+ output: TokenType.Output,
122
+ max_tokens: TokenType.MaxTokens,
123
+ enum: TokenType.Enum,
124
+ true: TokenType.True,
125
+ false: TokenType.False,
126
+ parallel: TokenType.Parallel,
127
+ foreach: TokenType.Foreach,
128
+ as: TokenType.As,
129
+ max_iterations: TokenType.MaxIterations,
130
+ import: TokenType.Import,
131
+ from: TokenType.From,
132
+ memory: TokenType.Memory,
133
+ writes: TokenType.Writes,
134
+ storage: TokenType.Storage,
135
+ let: TokenType.Let,
136
+ then: TokenType.Then,
137
+ if: TokenType.If,
138
+ String: TokenType.String,
139
+ Int: TokenType.Int,
140
+ Float: TokenType.Float,
141
+ Bool: TokenType.Bool,
142
+ List: TokenType.List,
143
+ Map: TokenType.Map,
144
+ Optional: TokenType.Optional,
145
+ TokenBounded: TokenType.TokenBounded,
146
+ FilePath: TokenType.FilePath,
147
+ FileDiff: TokenType.FileDiff,
148
+ TestFile: TokenType.TestFile,
149
+ IssueRef: TokenType.IssueRef,
150
+ };
@@ -0,0 +1,7 @@
1
+ import type { CodeAction, Diagnostic } from 'vscode-languageserver/node';
2
+ export declare function buildAutoImportActions(docText: string, docUri: string, currentFilePath: string, diagnostics: Diagnostic[], workspaceExports: Map<string, string[]>): CodeAction[];
3
+ export declare function buildAutoImportEdit(name: string, fromPath: string, docText: string): {
4
+ insertLine: number;
5
+ newText: string;
6
+ };
7
+ export declare function computeRelativeImportPath(fromFile: string, toFile: string): string;
@@ -0,0 +1,58 @@
1
+ import * as path from 'node:path';
2
+ import { CodeActionKind } from 'vscode-languageserver/node';
3
+ import { extractUndefinedName } from './diagnostics.js';
4
+ export function buildAutoImportActions(docText, docUri, currentFilePath, diagnostics, workspaceExports) {
5
+ const actions = [];
6
+ // Collect already-imported names
7
+ const importedNames = new Set();
8
+ for (const line of docText.split('\n')) {
9
+ const m = line.match(/^\s*import\s+\{([^}]+)\}/);
10
+ if (m) {
11
+ for (const n of m[1].split(','))
12
+ importedNames.add(n.trim());
13
+ }
14
+ }
15
+ for (const diag of diagnostics) {
16
+ if (diag.code !== 'SCOPE_UNDEFINED_REF')
17
+ continue;
18
+ const name = extractUndefinedName(diag.message, docText, diag.range.start.line, diag.range.start.character);
19
+ if (!name || importedNames.has(name))
20
+ continue;
21
+ for (const [filePath, exports] of workspaceExports) {
22
+ if (filePath === currentFilePath || !exports.includes(name))
23
+ continue;
24
+ const relPath = computeRelativeImportPath(currentFilePath, filePath);
25
+ const edit = buildAutoImportEdit(name, relPath, docText);
26
+ actions.push({
27
+ title: `Import '${name}' from "${relPath}"`,
28
+ kind: CodeActionKind.QuickFix,
29
+ edit: {
30
+ changes: {
31
+ [docUri]: [{
32
+ range: { start: { line: edit.insertLine, character: 0 }, end: { line: edit.insertLine, character: 0 } },
33
+ newText: edit.newText,
34
+ }],
35
+ },
36
+ },
37
+ });
38
+ }
39
+ }
40
+ return actions;
41
+ }
42
+ export function buildAutoImportEdit(name, fromPath, docText) {
43
+ const lines = docText.split('\n');
44
+ let insertLine = 0;
45
+ for (let i = 0; i < lines.length; i++) {
46
+ if (/^\s*import\s+\{/.test(lines[i])) {
47
+ insertLine = i + 1;
48
+ }
49
+ }
50
+ return { insertLine, newText: `import { ${name} } from "${fromPath}"\n` };
51
+ }
52
+ export function computeRelativeImportPath(fromFile, toFile) {
53
+ let rel = path.relative(path.dirname(fromFile), toFile);
54
+ rel = rel.replace(/\\/g, '/'); // Windows path normalization
55
+ if (!rel.startsWith('.'))
56
+ rel = './' + rel;
57
+ return rel;
58
+ }
@@ -0,0 +1,7 @@
1
+ import type { CompletionItem } from 'vscode-languageserver/node';
2
+ import { type Program } from '../../parser/ast.js';
3
+ import type { ProgramIndex } from '../../program-index.js';
4
+ export declare function getCompletions(text: string, line: number, character: number, cache: {
5
+ program: Program;
6
+ index: ProgramIndex;
7
+ } | null, resolveImportNames?: (importPath: string) => string[]): CompletionItem[];
@@ -0,0 +1,271 @@
1
+ import { CompletionItemKind, InsertTextFormat } from 'vscode-languageserver/node';
2
+ import { BUILTIN_FUNCTIONS } from '../../parser/ast.js';
3
+ import { MODEL_MAP } from '../../constants.js';
4
+ import { formatType } from './hover.js';
5
+ import { isInComment, isInString } from './utils.js';
6
+ export function getCompletions(text, line, character, cache, resolveImportNames) {
7
+ const lines = text.split('\n');
8
+ if (line < 0 || line >= lines.length)
9
+ return [];
10
+ const lineText = lines[line].replace(/\r$/, '');
11
+ const before = lineText.slice(0, character);
12
+ // Suppress completions inside comments
13
+ if (isInComment(lines, line, character))
14
+ return [];
15
+ // Suppress completions inside string literals
16
+ if (isInString(lineText, character))
17
+ return [];
18
+ // After `storage:` -> storage types
19
+ if (/storage\s*:\s*\w*$/.test(before)) {
20
+ return [{ label: 'file', kind: CompletionItemKind.EnumMember, detail: 'File-based storage' }];
21
+ }
22
+ // After `model:` -> model aliases
23
+ if (/model\s*:\s*\w*$/.test(before)) {
24
+ return Object.entries(MODEL_MAP).map(([alias, full]) => ({
25
+ label: alias,
26
+ kind: CompletionItemKind.EnumMember,
27
+ detail: full,
28
+ }));
29
+ }
30
+ // After `on_failure:` -> strategy keywords
31
+ if (/on_failure\s*:\s*\w*$/.test(before)) {
32
+ return [
33
+ { label: 'retry', kind: CompletionItemKind.Keyword,
34
+ insertText: 'retry(${1:3})', insertTextFormat: InsertTextFormat.Snippet },
35
+ { label: 'fallback', kind: CompletionItemKind.Keyword,
36
+ insertText: 'fallback(${1:NodeName})', insertTextFormat: InsertTextFormat.Snippet },
37
+ { label: 'skip', kind: CompletionItemKind.Keyword },
38
+ { label: 'abort', kind: CompletionItemKind.Keyword },
39
+ { label: 'retry_then_fallback', kind: CompletionItemKind.Keyword,
40
+ insertText: 'retry(${1:3}, fallback(${2:NodeName}))',
41
+ insertTextFormat: InsertTextFormat.Snippet,
42
+ detail: 'Retry N times, then fallback to node' },
43
+ ];
44
+ }
45
+ // Inside `fallback(` -> node names
46
+ if (/fallback\s*\(\s*\w*$/.test(before)) {
47
+ if (!cache)
48
+ return [];
49
+ return [...cache.index.nodeMap.keys()].map(name => ({
50
+ label: name,
51
+ kind: CompletionItemKind.Class,
52
+ detail: 'node',
53
+ }));
54
+ }
55
+ // Inside `import { }` -> names from resolver
56
+ if (isInsideImportBraces(lines, line, character)) {
57
+ if (!resolveImportNames)
58
+ return [];
59
+ const importPath = extractImportPath(lines, line) ?? '';
60
+ const names = resolveImportNames(importPath);
61
+ return names.map(name => ({
62
+ label: name,
63
+ kind: CompletionItemKind.Class,
64
+ detail: 'importable',
65
+ }));
66
+ }
67
+ // After `Name.` -> field completions (also handles multi-field brace `Name.{f1, `)
68
+ const dotMatch = before.match(/([A-Za-z_]\w*)\.\s*(?:\{[^}]*)?\s*\w*$/);
69
+ if (dotMatch) {
70
+ return getFieldCompletions(dotMatch[1], cache?.index ?? null);
71
+ }
72
+ // Inside `reads: [` -> context + memory + produces names
73
+ if (isInsideBracketAfter(lines, line, character, 'reads')) {
74
+ if (!cache)
75
+ return [];
76
+ const items = [];
77
+ for (const name of cache.index.contextMap.keys()) {
78
+ items.push({ label: name, kind: CompletionItemKind.Class, detail: 'context' });
79
+ }
80
+ for (const name of cache.index.producesNodeMap.keys()) {
81
+ items.push({ label: name, kind: CompletionItemKind.Struct, detail: 'produces' });
82
+ }
83
+ for (const name of cache.index.memoryMap.keys()) {
84
+ items.push({ label: name, kind: CompletionItemKind.Variable, detail: 'memory' });
85
+ }
86
+ return items;
87
+ }
88
+ // Inside `writes: [` -> memory names only
89
+ if (isInsideBracketAfter(lines, line, character, 'writes')) {
90
+ if (!cache)
91
+ return [];
92
+ return [...cache.index.memoryMap.keys()].map(name => ({
93
+ label: name,
94
+ kind: CompletionItemKind.Variable,
95
+ detail: 'memory',
96
+ }));
97
+ }
98
+ // Inside graph flow -> node names + done
99
+ if (isInsideBlock(lines, line, 'graph')) {
100
+ const items = [
101
+ { label: 'done', kind: CompletionItemKind.Keyword, detail: 'Terminal node' },
102
+ { label: 'let', kind: CompletionItemKind.Keyword, detail: 'Variable binding' },
103
+ ];
104
+ if (cache) {
105
+ for (const name of cache.index.nodeMap.keys()) {
106
+ items.push({ label: name, kind: CompletionItemKind.Class, detail: 'node' });
107
+ }
108
+ for (const name of cache.index.graphMap.keys()) {
109
+ items.push({ label: name, kind: CompletionItemKind.Module, detail: 'graph call' });
110
+ }
111
+ }
112
+ // Built-in expression functions
113
+ for (const [name, info] of Object.entries(BUILTIN_FUNCTIONS)) {
114
+ items.push({ label: name, kind: CompletionItemKind.Function, detail: `builtin (${info.arity} arg${info.arity !== 1 ? 's' : ''})` });
115
+ }
116
+ // Expression keywords
117
+ items.push({ label: 'if', kind: CompletionItemKind.Keyword, detail: 'Conditional expression' });
118
+ items.push({ label: 'true', kind: CompletionItemKind.Keyword, detail: 'Boolean literal' });
119
+ items.push({ label: 'false', kind: CompletionItemKind.Keyword, detail: 'Boolean literal' });
120
+ // Variable names from let bindings
121
+ if (cache) {
122
+ for (const [name, binding] of cache.index.letBindingMap) {
123
+ items.push({ label: name, kind: CompletionItemKind.Variable, detail: `variable (graph ${binding.graphName})` });
124
+ }
125
+ }
126
+ return items;
127
+ }
128
+ // Top-level -> keyword completions + snippets
129
+ if (/^\s*\w*$/.test(before)) {
130
+ return topLevelCompletions();
131
+ }
132
+ return [];
133
+ }
134
+ function getFieldCompletions(name, index) {
135
+ if (!index)
136
+ return [];
137
+ const ctx = index.contextMap.get(name);
138
+ if (ctx) {
139
+ return ctx.fields.map(f => ({
140
+ label: f.name,
141
+ kind: CompletionItemKind.Field,
142
+ detail: formatType(f.type),
143
+ }));
144
+ }
145
+ const pf = index.producesFieldsMap.get(name);
146
+ if (pf) {
147
+ return [...pf.entries()].map(([fieldName, type]) => ({
148
+ label: fieldName,
149
+ kind: CompletionItemKind.Field,
150
+ detail: formatType(type),
151
+ }));
152
+ }
153
+ const mf = index.memoryFieldsMap.get(name);
154
+ if (mf) {
155
+ return [...mf.entries()].map(([fieldName, type]) => ({
156
+ label: fieldName,
157
+ kind: CompletionItemKind.Field,
158
+ detail: formatType(type),
159
+ }));
160
+ }
161
+ return [];
162
+ }
163
+ function isInsideBracketAfter(lines, currentLine, character, keyword) {
164
+ let depth = 0;
165
+ for (let i = currentLine; i >= 0; i--) {
166
+ const l = (lines[i] ?? '').replace(/\r$/, '');
167
+ const end = i === currentLine ? character : l.length;
168
+ for (let j = end - 1; j >= 0; j--) {
169
+ if (l[j] === ']')
170
+ depth++;
171
+ if (l[j] === '[') {
172
+ if (depth > 0) {
173
+ depth--;
174
+ }
175
+ else {
176
+ const prefix = l.slice(0, j).trimEnd();
177
+ return prefix.endsWith(keyword + ':') || prefix.endsWith(keyword);
178
+ }
179
+ }
180
+ }
181
+ }
182
+ return false;
183
+ }
184
+ function isInsideImportBraces(lines, currentLine, character) {
185
+ let depth = 0;
186
+ for (let i = currentLine; i >= 0; i--) {
187
+ const l = (lines[i] ?? '').replace(/\r$/, '');
188
+ const end = i === currentLine ? character : l.length;
189
+ for (let j = end - 1; j >= 0; j--) {
190
+ if (l[j] === '}')
191
+ depth++;
192
+ if (l[j] === '{') {
193
+ if (depth > 0) {
194
+ depth--;
195
+ }
196
+ else {
197
+ const prefix = l.slice(0, j).trimEnd();
198
+ return /\bimport\s*$/.test(prefix);
199
+ }
200
+ }
201
+ }
202
+ }
203
+ return false;
204
+ }
205
+ function extractImportPath(lines, startLine) {
206
+ for (let i = startLine; i < Math.min(startLine + 3, lines.length); i++) {
207
+ const match = lines[i].match(/from\s+"([^"]+)"/);
208
+ if (match)
209
+ return match[1];
210
+ }
211
+ return null;
212
+ }
213
+ function isInsideBlock(lines, currentLine, keyword) {
214
+ let depth = 0;
215
+ for (let i = currentLine; i >= 0; i--) {
216
+ const l = (lines[i] ?? '').replace(/\r$/, '');
217
+ for (let j = l.length - 1; j >= 0; j--) {
218
+ if (l[j] === '}')
219
+ depth++;
220
+ if (l[j] === '{') {
221
+ if (depth > 0) {
222
+ depth--;
223
+ }
224
+ else {
225
+ return new RegExp(`^\\s*${keyword}\\s`).test(l);
226
+ }
227
+ }
228
+ }
229
+ }
230
+ return false;
231
+ }
232
+ function topLevelCompletions() {
233
+ return [
234
+ {
235
+ label: 'context',
236
+ kind: CompletionItemKind.Keyword,
237
+ detail: 'Declare a context schema',
238
+ insertText: 'context ${1:Name}(max_tokens: ${2:1k}) {\n ${3:field}: ${4:String}\n}',
239
+ insertTextFormat: InsertTextFormat.Snippet,
240
+ },
241
+ {
242
+ label: 'node',
243
+ kind: CompletionItemKind.Keyword,
244
+ detail: 'Declare a processing node',
245
+ insertText: 'node ${1:Name}(model: ${2:sonnet}, budget: ${3:5k}/${4:2k}) {\n reads: [${5}]\n produces ${6:Output} {\n ${7:field}: ${8:String}\n }\n}',
246
+ insertTextFormat: InsertTextFormat.Snippet,
247
+ },
248
+ {
249
+ label: 'memory',
250
+ kind: CompletionItemKind.Keyword,
251
+ detail: 'Declare persistent memory',
252
+ insertText: 'memory ${1:Name}(max_tokens: ${2:2k}, storage: file) {\n ${3:field}: ${4:String}\n}',
253
+ insertTextFormat: InsertTextFormat.Snippet,
254
+ },
255
+ {
256
+ label: 'graph',
257
+ kind: CompletionItemKind.Keyword,
258
+ detail: 'Declare execution graph',
259
+ insertText: 'graph ${1:Name}(input: ${2:Input}, output: ${3:Output}, budget: ${4:10k}) {\n ${5:Start} -> done\n}',
260
+ insertTextFormat: InsertTextFormat.Snippet,
261
+ },
262
+ { label: 'edge', kind: CompletionItemKind.Keyword, detail: 'Declare edge transform' },
263
+ {
264
+ label: 'import',
265
+ kind: CompletionItemKind.Keyword,
266
+ detail: 'Import from another file',
267
+ insertText: 'import { ${1:Name} } from "${2:./lib.gft}"',
268
+ insertTextFormat: InsertTextFormat.Snippet,
269
+ },
270
+ ];
271
+ }
@@ -0,0 +1,3 @@
1
+ import type { Location } from 'vscode-languageserver/node';
2
+ import type { ProgramIndex } from '../../program-index.js';
3
+ export declare function getDefinitionLocation(word: string, index: ProgramIndex, currentUri: string): Location | null;
@@ -0,0 +1,32 @@
1
+ import { pathToFileURL } from 'node:url';
2
+ export function getDefinitionLocation(word, index, currentUri) {
3
+ const ctx = index.contextMap.get(word);
4
+ if (ctx)
5
+ return declLocation(ctx.location, ctx.sourceFile, word.length, currentUri);
6
+ const node = index.nodeMap.get(word);
7
+ if (node)
8
+ return declLocation(node.location, node.sourceFile, word.length, currentUri);
9
+ const producerNode = index.producesNodeMap.get(word);
10
+ if (producerNode) {
11
+ return declLocation(producerNode.produces.location, producerNode.sourceFile, word.length, currentUri);
12
+ }
13
+ const mem = index.memoryMap.get(word);
14
+ if (mem)
15
+ return declLocation(mem.location, undefined, word.length, currentUri);
16
+ const letBinding = index.letBindingMap.get(word);
17
+ if (letBinding?.location)
18
+ return declLocation(letBinding.location, undefined, word.length, currentUri);
19
+ return null;
20
+ }
21
+ function declLocation(loc, sourceFile, nameLength, currentUri) {
22
+ const uri = sourceFile ? pathToFileURL(sourceFile).toString() : currentUri;
23
+ const line = Math.max(0, loc.line - 1);
24
+ const character = Math.max(0, loc.column - 1);
25
+ return {
26
+ uri,
27
+ range: {
28
+ start: { line, character },
29
+ end: { line, character: character + nameLength },
30
+ },
31
+ };
32
+ }
@@ -0,0 +1,4 @@
1
+ import type { Diagnostic } from 'vscode-languageserver/node';
2
+ import type { GraftError } from '../../errors/diagnostics.js';
3
+ export declare function toDiagnostics(errors: GraftError[], warnings: GraftError[]): Diagnostic[];
4
+ export declare function extractUndefinedName(message: string, docText: string, line: number, character: number): string | null;
@@ -0,0 +1,33 @@
1
+ import { DiagnosticSeverity } from 'vscode-languageserver/node';
2
+ import { getWordAtPosition } from './utils.js';
3
+ export function toDiagnostics(errors, warnings) {
4
+ const result = [];
5
+ for (const e of errors) {
6
+ result.push(makeDiagnostic(e, DiagnosticSeverity.Error));
7
+ }
8
+ for (const w of warnings) {
9
+ result.push(makeDiagnostic(w, DiagnosticSeverity.Warning));
10
+ }
11
+ return result;
12
+ }
13
+ function makeDiagnostic(e, severity) {
14
+ const line = Math.max(0, e.location.line - 1);
15
+ const character = Math.max(0, e.location.column - 1);
16
+ const endCharacter = character + (e.location.length ?? 1);
17
+ return {
18
+ range: {
19
+ start: { line, character },
20
+ end: { line, character: endCharacter },
21
+ },
22
+ severity,
23
+ message: e.message,
24
+ source: 'graft',
25
+ ...(e.code ? { code: e.code } : {}),
26
+ };
27
+ }
28
+ export function extractUndefinedName(message, docText, line, character) {
29
+ const msgMatch = message.match(/'([^']+)'/);
30
+ if (msgMatch)
31
+ return msgMatch[1];
32
+ return getWordAtPosition(docText, line, character);
33
+ }
@@ -0,0 +1,7 @@
1
+ import type { Hover } from 'vscode-languageserver/node';
2
+ import { type TypeExpr } from '../../parser/ast.js';
3
+ import type { ProgramIndex } from '../../program-index.js';
4
+ export declare const KEYWORD_DOCS: Record<string, string>;
5
+ export declare function getHoverInfo(word: string, index: ProgramIndex): Hover | null;
6
+ export declare function formatType(type: TypeExpr): string;
7
+ export { formatExpr } from '../../format.js';