@kentwynn/kgraph 0.2.21 → 0.2.23

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.
@@ -0,0 +1,77 @@
1
+ export class ExtractionContext {
2
+ filePath;
3
+ symbols = [];
4
+ dependencies = [];
5
+ relationships = [];
6
+ warnings = [];
7
+ constructor(filePath) {
8
+ this.filePath = filePath;
9
+ }
10
+ addSymbol(options) {
11
+ const id = [
12
+ this.filePath,
13
+ options.kind,
14
+ options.parentName,
15
+ options.name,
16
+ options.startLine,
17
+ options.endLine ?? options.startLine,
18
+ ]
19
+ .filter(Boolean)
20
+ .join('#');
21
+ const symbol = {
22
+ id,
23
+ name: options.name,
24
+ kind: options.kind,
25
+ filePath: this.filePath,
26
+ startLine: options.startLine,
27
+ endLine: options.endLine ?? options.startLine,
28
+ exported: options.exported ?? false,
29
+ parentName: options.parentName,
30
+ };
31
+ this.symbols.push(symbol);
32
+ this.relationships.push({
33
+ sourceType: 'file',
34
+ sourceId: this.filePath,
35
+ targetType: 'symbol',
36
+ targetId: id,
37
+ relationshipType: 'contains',
38
+ confidence: 'high',
39
+ });
40
+ return symbol;
41
+ }
42
+ addDependency(specifier, kind = specifier.startsWith('.') ? 'local' : 'package', confidence = 'high') {
43
+ this.dependencies.push({ fromFile: this.filePath, specifier, kind });
44
+ this.relationships.push({
45
+ sourceType: 'file',
46
+ sourceId: this.filePath,
47
+ targetType: kind === 'local' ? 'file' : 'package',
48
+ targetId: specifier,
49
+ relationshipType: 'import',
50
+ confidence,
51
+ });
52
+ }
53
+ addSymbolContains(parent, child) {
54
+ this.relationships.push({
55
+ sourceType: 'symbol',
56
+ sourceId: parent.id,
57
+ targetType: 'symbol',
58
+ targetId: child.id,
59
+ relationshipType: 'symbol-contains',
60
+ confidence: 'high',
61
+ });
62
+ }
63
+ addWarning(message) {
64
+ this.warnings.push(message);
65
+ }
66
+ toResult() {
67
+ return {
68
+ symbols: this.symbols,
69
+ dependencies: this.dependencies,
70
+ relationships: this.relationships,
71
+ warnings: this.warnings,
72
+ };
73
+ }
74
+ }
75
+ export function emptyExtractionResult() {
76
+ return { symbols: [], dependencies: [], relationships: [], warnings: [] };
77
+ }
@@ -53,6 +53,7 @@ const LANGUAGE_BY_EXTENSION = {
53
53
  '.scss': 'scss',
54
54
  '.sass': 'sass',
55
55
  '.less': 'less',
56
+ '.dockerfile': 'dockerfile',
56
57
  '.vue': 'vue',
57
58
  '.svelte': 'svelte',
58
59
  // Data / Config
@@ -84,6 +85,13 @@ const LANGUAGE_BY_EXTENSION = {
84
85
  '.proto': 'protobuf',
85
86
  '.sql': 'sql',
86
87
  };
88
+ const LANGUAGE_BY_BASENAME = {
89
+ Dockerfile: 'dockerfile',
90
+ Containerfile: 'dockerfile',
91
+ Makefile: 'shell',
92
+ Rakefile: 'ruby',
93
+ Gemfile: 'ruby',
94
+ };
87
95
  export function shouldExclude(repoPath, config) {
88
96
  const normalizedPath = normalizeRepoPath(repoPath);
89
97
  return config.exclude.some((pattern) => matchesExcludePattern(normalizedPath, pattern));
@@ -120,10 +128,15 @@ export async function readGitignorePatterns(rootPath) {
120
128
  }
121
129
  }
122
130
  export function detectLanguage(filePath) {
123
- return LANGUAGE_BY_EXTENSION[path.extname(filePath)] ?? 'unknown';
131
+ const basename = path.basename(filePath);
132
+ return (LANGUAGE_BY_BASENAME[basename] ??
133
+ LANGUAGE_BY_EXTENSION[path.extname(filePath)] ??
134
+ 'unknown');
124
135
  }
125
136
  export function isPreciseLanguage(filePath, config) {
126
- return config.languages.precise.includes(path.extname(filePath));
137
+ const basename = path.basename(filePath);
138
+ return (config.languages.precise.includes(path.extname(filePath)) ||
139
+ Object.hasOwn(LANGUAGE_BY_BASENAME, basename));
127
140
  }
128
141
  function matchesExcludePattern(repoPath, pattern) {
129
142
  const normalized = normalizeRepoPath(pattern).replace(/\/$/, '');
@@ -0,0 +1,2 @@
1
+ import type { SymbolExtractionResult } from './ts-symbol-extractor.js';
2
+ export declare function extractPhpSymbols(sourceText: string, filePath: string): Promise<SymbolExtractionResult>;
@@ -0,0 +1,79 @@
1
+ import { emptyExtractionResult, ExtractionContext, } from './extraction-context.js';
2
+ import { parseSource } from './tree-sitter-parser.js';
3
+ export async function extractPhpSymbols(sourceText, filePath) {
4
+ if (!sourceText.trim()) {
5
+ return emptyExtractionResult();
6
+ }
7
+ const tree = await parseSource(sourceText, 'php');
8
+ const context = new ExtractionContext(filePath);
9
+ function addNamedSymbol(node, kind, parentName) {
10
+ const nameNode = findNameNode(node);
11
+ if (!nameNode)
12
+ return undefined;
13
+ return context.addSymbol({
14
+ name: nameNode.text,
15
+ kind,
16
+ startLine: node.startPosition.row + 1,
17
+ endLine: node.endPosition.row + 1,
18
+ exported: true,
19
+ parentName,
20
+ });
21
+ }
22
+ function walk(node, parentClassName) {
23
+ switch (node.type) {
24
+ case 'namespace_definition':
25
+ case 'namespace_name': {
26
+ if (node.type === 'namespace_name' && !parentClassName) {
27
+ context.addSymbol({
28
+ name: node.text,
29
+ kind: 'type',
30
+ startLine: node.startPosition.row + 1,
31
+ endLine: node.endPosition.row + 1,
32
+ exported: true,
33
+ });
34
+ return;
35
+ }
36
+ break;
37
+ }
38
+ case 'namespace_use_declaration': {
39
+ for (const nameNode of node.descendantsOfType('qualified_name')) {
40
+ context.addDependency(nameNode.text, 'package');
41
+ }
42
+ return;
43
+ }
44
+ case 'class_declaration':
45
+ case 'interface_declaration':
46
+ case 'trait_declaration':
47
+ case 'enum_declaration': {
48
+ const classSymbol = addNamedSymbol(node, node.type === 'interface_declaration' ? 'interface' : 'class', parentClassName);
49
+ const className = classSymbol?.name ?? parentClassName;
50
+ for (const child of node.namedChildren) {
51
+ walk(child, className);
52
+ }
53
+ return;
54
+ }
55
+ case 'function_definition':
56
+ case 'method_declaration': {
57
+ const symbol = addNamedSymbol(node, parentClassName ? 'method' : 'function', parentClassName);
58
+ if (symbol && parentClassName) {
59
+ const parent = context.symbols.find((candidate) => candidate.name === parentClassName && candidate.kind === 'class');
60
+ if (parent)
61
+ context.addSymbolContains(parent, symbol);
62
+ }
63
+ return;
64
+ }
65
+ }
66
+ for (const child of node.namedChildren) {
67
+ walk(child, parentClassName);
68
+ }
69
+ }
70
+ walk(tree.rootNode);
71
+ tree.delete();
72
+ return context.toResult();
73
+ }
74
+ function findNameNode(node) {
75
+ return (node.childForFieldName('name') ??
76
+ node.namedChildren.find((child) => child.type === 'name') ??
77
+ node.namedChildren.find((child) => child.type === 'variable_name') ??
78
+ node.namedChildren.find((child) => child.type === 'identifier'));
79
+ }
@@ -3,22 +3,44 @@ import crypto from 'node:crypto';
3
3
  import { readFile, stat } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import { estimateTokens } from '../session/token-estimator.js';
6
+ import { extractBroadSymbols, supportsBroadExtraction, } from './broad-symbol-extractor.js';
6
7
  import { extractCSymbols } from './c-symbol-extractor.js';
7
8
  import { extractCSharpSymbols } from './csharp-symbol-extractor.js';
8
9
  import { buildFastGlobIgnore, detectLanguage, isPreciseLanguage, readGitignorePatterns, shouldExclude, } from './file-classifier.js';
9
10
  import { getChangedFilesSince, isGitRepo } from './git-utils.js';
10
11
  import { extractGoSymbols } from './go-symbol-extractor.js';
11
12
  import { extractJvmSymbols } from './jvm-symbol-extractor.js';
13
+ import { extractPhpSymbols } from './php-symbol-extractor.js';
12
14
  import { extractPythonSymbols } from './python-symbol-extractor.js';
15
+ import { extractRubySymbols } from './ruby-symbol-extractor.js';
13
16
  import { extractRustSymbols } from './rust-symbol-extractor.js';
17
+ import { extractShellSymbols } from './shell-symbol-extractor.js';
18
+ import { extractSqlSymbols } from './sql-symbol-extractor.js';
14
19
  import { extractTsSymbols } from './ts-symbol-extractor.js';
15
20
  const C_EXTS = new Set(['.c', '.h', '.cpp', '.cc', '.cxx', '.hpp', '.hxx']);
16
21
  const JVM_EXTS = new Set(['.java', '.kt', '.kts']);
22
+ const TS_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts']);
17
23
  async function extractSymbols(text, repoPath) {
18
24
  const ext = path.extname(repoPath);
25
+ const language = detectLanguage(repoPath);
26
+ if (supportsBroadExtraction(language)) {
27
+ return extractBroadSymbols(text, repoPath, language);
28
+ }
19
29
  if (ext === '.py' || ext === '.pyw' || ext === '.pyi') {
20
30
  return extractPythonSymbols(text, repoPath);
21
31
  }
32
+ if (ext === '.php') {
33
+ return extractPhpSymbols(text, repoPath);
34
+ }
35
+ if (ext === '.rb' || ext === '.rake' || language === 'ruby') {
36
+ return extractRubySymbols(text, repoPath);
37
+ }
38
+ if (['.sh', '.bash', '.zsh', '.fish'].includes(ext) || language === 'shell') {
39
+ return extractShellSymbols(text, repoPath);
40
+ }
41
+ if (ext === '.sql') {
42
+ return extractSqlSymbols(text, repoPath);
43
+ }
22
44
  if (ext === '.go') {
23
45
  return extractGoSymbols(text, repoPath);
24
46
  }
@@ -34,7 +56,10 @@ async function extractSymbols(text, repoPath) {
34
56
  if (ext === '.cs') {
35
57
  return extractCSharpSymbols(text, repoPath);
36
58
  }
37
- return extractTsSymbols(text, repoPath);
59
+ if (TS_EXTS.has(ext)) {
60
+ return extractTsSymbols(text, repoPath);
61
+ }
62
+ return { symbols: [], dependencies: [], relationships: [], warnings: [] };
38
63
  }
39
64
  export async function scanRepository(rootPath, config, previous) {
40
65
  const gitignorePatterns = await readGitignorePatterns(rootPath);
@@ -223,6 +248,15 @@ const SOURCE_EXTENSIONS = [
223
248
  '.hpp',
224
249
  '.hxx',
225
250
  '.cs',
251
+ '.php',
252
+ '.swift',
253
+ '.rb',
254
+ '.rake',
255
+ '.lua',
256
+ '.dart',
257
+ '.ex',
258
+ '.exs',
259
+ '.scala',
226
260
  ];
227
261
  function resolveLocalDependencies(dependencies, files) {
228
262
  const filePaths = new Set(files.map((file) => file.path));
@@ -0,0 +1,2 @@
1
+ import type { SymbolExtractionResult } from './ts-symbol-extractor.js';
2
+ export declare function extractRubySymbols(sourceText: string, filePath: string): Promise<SymbolExtractionResult>;
@@ -0,0 +1,75 @@
1
+ import { emptyExtractionResult, ExtractionContext, } from './extraction-context.js';
2
+ import { parseSource } from './tree-sitter-parser.js';
3
+ export async function extractRubySymbols(sourceText, filePath) {
4
+ if (!sourceText.trim()) {
5
+ return emptyExtractionResult();
6
+ }
7
+ const tree = await parseSource(sourceText, 'ruby');
8
+ const context = new ExtractionContext(filePath);
9
+ function addNamedSymbol(node, kind, parentName) {
10
+ const nameNode = findNameNode(node);
11
+ if (!nameNode)
12
+ return undefined;
13
+ return context.addSymbol({
14
+ name: nameNode.text,
15
+ kind,
16
+ startLine: node.startPosition.row + 1,
17
+ endLine: node.endPosition.row + 1,
18
+ exported: true,
19
+ parentName,
20
+ });
21
+ }
22
+ function walk(node, parentName) {
23
+ switch (node.type) {
24
+ case 'call': {
25
+ const methodName = findCallMethodName(node);
26
+ if (methodName === 'require' || methodName === 'require_relative') {
27
+ const argument = findFirstStringContent(node);
28
+ if (argument) {
29
+ context.addDependency(argument, methodName === 'require_relative' ? 'local' : 'package');
30
+ }
31
+ return;
32
+ }
33
+ break;
34
+ }
35
+ case 'class':
36
+ case 'module': {
37
+ const symbol = addNamedSymbol(node, node.type === 'class' ? 'class' : 'type', parentName);
38
+ const nextParent = symbol?.name ?? parentName;
39
+ for (const child of node.namedChildren) {
40
+ walk(child, nextParent);
41
+ }
42
+ return;
43
+ }
44
+ case 'method':
45
+ case 'singleton_method': {
46
+ const symbol = addNamedSymbol(node, parentName ? 'method' : 'function', parentName);
47
+ if (symbol && parentName) {
48
+ const parent = context.symbols.find((candidate) => candidate.name === parentName &&
49
+ (candidate.kind === 'class' || candidate.kind === 'type'));
50
+ if (parent)
51
+ context.addSymbolContains(parent, symbol);
52
+ }
53
+ return;
54
+ }
55
+ }
56
+ for (const child of node.namedChildren) {
57
+ walk(child, parentName);
58
+ }
59
+ }
60
+ walk(tree.rootNode);
61
+ tree.delete();
62
+ return context.toResult();
63
+ }
64
+ function findNameNode(node) {
65
+ return (node.childForFieldName('name') ??
66
+ node.namedChildren.find((child) => child.type === 'constant') ??
67
+ node.namedChildren.find((child) => child.type === 'identifier'));
68
+ }
69
+ function findCallMethodName(node) {
70
+ return (node.childForFieldName('method')?.text ??
71
+ node.namedChildren.find((child) => child.type === 'identifier')?.text);
72
+ }
73
+ function findFirstStringContent(node) {
74
+ return node.descendantsOfType('string_content')[0]?.text;
75
+ }
@@ -0,0 +1,2 @@
1
+ import type { SymbolExtractionResult } from './ts-symbol-extractor.js';
2
+ export declare function extractShellSymbols(sourceText: string, filePath: string): Promise<SymbolExtractionResult>;
@@ -0,0 +1,78 @@
1
+ import { emptyExtractionResult, ExtractionContext, } from './extraction-context.js';
2
+ import { parseSource } from './tree-sitter-parser.js';
3
+ export async function extractShellSymbols(sourceText, filePath) {
4
+ if (!sourceText.trim()) {
5
+ return emptyExtractionResult();
6
+ }
7
+ const tree = await parseSource(sourceText, 'bash');
8
+ const context = new ExtractionContext(filePath);
9
+ function walk(node, currentFunctionId) {
10
+ switch (node.type) {
11
+ case 'function_definition': {
12
+ const name = findFunctionName(node);
13
+ if (name) {
14
+ const symbol = context.addSymbol({
15
+ name,
16
+ kind: 'function',
17
+ startLine: node.startPosition.row + 1,
18
+ endLine: node.endPosition.row + 1,
19
+ exported: true,
20
+ });
21
+ for (const child of node.namedChildren) {
22
+ walk(child, symbol.id);
23
+ }
24
+ return;
25
+ }
26
+ break;
27
+ }
28
+ case 'command': {
29
+ const commandName = findCommandName(node);
30
+ const firstArgument = findFirstCommandArgument(node);
31
+ if ((commandName === 'source' || commandName === '.') &&
32
+ firstArgument) {
33
+ context.addDependency(firstArgument, 'local');
34
+ return;
35
+ }
36
+ if (currentFunctionId && commandName && isLocalScriptReference(commandName)) {
37
+ context.relationships.push({
38
+ sourceType: 'symbol',
39
+ sourceId: currentFunctionId,
40
+ targetType: 'file',
41
+ targetId: commandName,
42
+ relationshipType: 'calls',
43
+ confidence: 'low',
44
+ });
45
+ }
46
+ break;
47
+ }
48
+ }
49
+ for (const child of node.namedChildren) {
50
+ walk(child, currentFunctionId);
51
+ }
52
+ }
53
+ walk(tree.rootNode);
54
+ tree.delete();
55
+ return context.toResult();
56
+ }
57
+ function findFunctionName(node) {
58
+ return (node.childForFieldName('name')?.text ??
59
+ node.namedChildren.find((child) => child.type === 'word')?.text);
60
+ }
61
+ function findCommandName(node) {
62
+ return (node.childForFieldName('name')?.text ??
63
+ node.namedChildren
64
+ .find((child) => child.type === 'command_name')
65
+ ?.namedChildren.find((child) => child.type === 'word')?.text ??
66
+ node.namedChildren.find((child) => child.type === 'command_name')?.text ??
67
+ node.namedChildren.find((child) => child.type === 'word')?.text);
68
+ }
69
+ function findFirstCommandArgument(node) {
70
+ return node.namedChildren.find((child) => child.type === 'word')?.text;
71
+ }
72
+ function isLocalScriptReference(value) {
73
+ return (value.startsWith('./') ||
74
+ value.startsWith('../') ||
75
+ value.endsWith('.sh') ||
76
+ value.endsWith('.bash') ||
77
+ value.endsWith('.zsh'));
78
+ }
@@ -0,0 +1,2 @@
1
+ import type { SymbolExtractionResult } from './ts-symbol-extractor.js';
2
+ export declare function extractSqlSymbols(sourceText: string, filePath: string): Promise<SymbolExtractionResult>;
@@ -0,0 +1,166 @@
1
+ import { emptyExtractionResult, ExtractionContext, } from './extraction-context.js';
2
+ export async function extractSqlSymbols(sourceText, filePath) {
3
+ if (!sourceText.trim()) {
4
+ return emptyExtractionResult();
5
+ }
6
+ const context = new ExtractionContext(filePath);
7
+ const withoutComments = stripLineComments(sourceText);
8
+ const statements = splitStatements(withoutComments);
9
+ for (const statement of statements) {
10
+ collectCreateStatement(statement, context);
11
+ collectAlterStatement(statement, context);
12
+ collectQueryReferences(statement, context);
13
+ }
14
+ return context.toResult();
15
+ }
16
+ function collectCreateStatement(statement, context) {
17
+ const normalized = compact(statement.text);
18
+ const createMatch = normalized.match(/^CREATE\s+(?:OR\s+REPLACE\s+)?(?:(?:UNIQUE|MATERIALIZED|TEMP|TEMPORARY)\s+)*(TABLE|VIEW|INDEX|FUNCTION|PROCEDURE|TRIGGER|TYPE)\s+(?:IF\s+NOT\s+EXISTS\s+)?("?[\w.]+"?)/i);
19
+ if (!createMatch?.[1] || !createMatch[2]) {
20
+ return;
21
+ }
22
+ const objectKind = createMatch[1].toLowerCase();
23
+ const name = normalizeSqlIdentifier(createMatch[2]);
24
+ const symbol = context.addSymbol({
25
+ name,
26
+ kind: objectKind === 'function' || objectKind === 'procedure'
27
+ ? 'function'
28
+ : 'type',
29
+ startLine: statement.startLine,
30
+ endLine: statement.endLine,
31
+ exported: true,
32
+ parentName: objectKind,
33
+ });
34
+ const tableForTrigger = normalized.match(/\bON\s+("?[\w.]+"?)/i)?.[1];
35
+ if (objectKind === 'trigger' && tableForTrigger) {
36
+ context.relationships.push({
37
+ sourceType: 'symbol',
38
+ sourceId: symbol.id,
39
+ targetType: 'symbol',
40
+ targetId: normalizeSqlIdentifier(tableForTrigger),
41
+ relationshipType: 'mentions',
42
+ confidence: 'high',
43
+ });
44
+ }
45
+ for (const referencedTable of referencedTables(normalized)) {
46
+ context.relationships.push({
47
+ sourceType: 'symbol',
48
+ sourceId: symbol.id,
49
+ targetType: 'symbol',
50
+ targetId: referencedTable,
51
+ relationshipType: 'mentions',
52
+ confidence: 'medium',
53
+ });
54
+ }
55
+ }
56
+ function collectAlterStatement(statement, context) {
57
+ const normalized = compact(statement.text);
58
+ const alterMatch = normalized.match(/^ALTER\s+TABLE\s+(?:IF\s+EXISTS\s+)?("?[\w.]+"?)/i);
59
+ if (!alterMatch?.[1]) {
60
+ return;
61
+ }
62
+ const tableName = normalizeSqlIdentifier(alterMatch[1]);
63
+ const symbol = context.addSymbol({
64
+ name: `alter ${tableName}`,
65
+ kind: 'type',
66
+ startLine: statement.startLine,
67
+ endLine: statement.endLine,
68
+ exported: true,
69
+ parentName: 'table',
70
+ });
71
+ context.relationships.push({
72
+ sourceType: 'symbol',
73
+ sourceId: symbol.id,
74
+ targetType: 'symbol',
75
+ targetId: tableName,
76
+ relationshipType: 'mentions',
77
+ confidence: 'high',
78
+ });
79
+ for (const referencedTable of referencedTables(normalized)) {
80
+ context.relationships.push({
81
+ sourceType: 'symbol',
82
+ sourceId: symbol.id,
83
+ targetType: 'symbol',
84
+ targetId: referencedTable,
85
+ relationshipType: 'mentions',
86
+ confidence: 'medium',
87
+ });
88
+ }
89
+ }
90
+ function collectQueryReferences(statement, context) {
91
+ const normalized = compact(statement.text);
92
+ const queryMatch = normalized.match(/^(SELECT|INSERT|UPDATE|DELETE)\b/i);
93
+ if (!queryMatch?.[1]) {
94
+ return;
95
+ }
96
+ const symbol = context.addSymbol({
97
+ name: `${queryMatch[1].toLowerCase()} statement ${statement.startLine}`,
98
+ kind: 'type',
99
+ startLine: statement.startLine,
100
+ endLine: statement.endLine,
101
+ });
102
+ for (const table of referencedTables(normalized)) {
103
+ context.relationships.push({
104
+ sourceType: 'symbol',
105
+ sourceId: symbol.id,
106
+ targetType: 'symbol',
107
+ targetId: table,
108
+ relationshipType: 'mentions',
109
+ confidence: 'medium',
110
+ });
111
+ }
112
+ }
113
+ function stripLineComments(sourceText) {
114
+ return sourceText
115
+ .split(/\r?\n/)
116
+ .map((line) => line.replace(/--.*$/, ''))
117
+ .join('\n');
118
+ }
119
+ function splitStatements(sourceText) {
120
+ const statements = [];
121
+ let current = '';
122
+ let startLine = 1;
123
+ let lineNumber = 1;
124
+ for (const line of sourceText.split(/\r?\n/)) {
125
+ if (!current.trim()) {
126
+ startLine = lineNumber;
127
+ }
128
+ current += `${line}\n`;
129
+ if (line.includes(';')) {
130
+ statements.push({
131
+ text: current,
132
+ startLine,
133
+ endLine: lineNumber,
134
+ });
135
+ current = '';
136
+ }
137
+ lineNumber += 1;
138
+ }
139
+ if (current.trim()) {
140
+ statements.push({ text: current, startLine, endLine: lineNumber - 1 });
141
+ }
142
+ return statements;
143
+ }
144
+ function compact(value) {
145
+ return value.replace(/\s+/g, ' ').trim();
146
+ }
147
+ function normalizeSqlIdentifier(value) {
148
+ return value.replace(/^"+|"+$/g, '').replace(/[;,)]$/, '');
149
+ }
150
+ function referencedTables(statement) {
151
+ const names = new Set();
152
+ const patterns = [
153
+ /\bREFERENCES\s+("?[\w.]+"?)/gi,
154
+ /\bFROM\s+("?[\w.]+"?)/gi,
155
+ /\bJOIN\s+("?[\w.]+"?)/gi,
156
+ /\bUPDATE\s+("?[\w.]+"?)/gi,
157
+ /\bINTO\s+("?[\w.]+"?)/gi,
158
+ ];
159
+ for (const pattern of patterns) {
160
+ for (const match of statement.matchAll(pattern)) {
161
+ if (match[1])
162
+ names.add(normalizeSqlIdentifier(match[1]));
163
+ }
164
+ }
165
+ return [...names];
166
+ }
@@ -1,5 +1,4 @@
1
1
  import { Language, Tree } from 'web-tree-sitter';
2
- type GrammarKey = 'python' | 'java' | 'kotlin' | 'go' | 'rust' | 'c' | 'cpp' | 'c_sharp';
2
+ export type GrammarKey = 'python' | 'java' | 'kotlin' | 'go' | 'rust' | 'c' | 'cpp' | 'c_sharp' | 'php' | 'ruby' | 'bash' | 'yaml' | 'json' | 'html' | 'css' | 'lua' | 'dart' | 'elixir' | 'scala';
3
3
  export declare function loadLanguage(grammarKey: GrammarKey): Promise<Language>;
4
4
  export declare function parseSource(sourceText: string, grammarKey: GrammarKey): Promise<Tree>;
5
- export {};
@@ -19,6 +19,20 @@ const GRAMMAR_PACKAGES = {
19
19
  pkg: 'tree-sitter-c-sharp',
20
20
  wasm: 'tree-sitter-c_sharp.wasm',
21
21
  },
22
+ php: { pkg: 'tree-sitter-php', wasm: 'tree-sitter-php.wasm' },
23
+ ruby: { pkg: 'tree-sitter-ruby', wasm: 'tree-sitter-ruby.wasm' },
24
+ bash: { pkg: 'tree-sitter-bash', wasm: 'tree-sitter-bash.wasm' },
25
+ yaml: {
26
+ pkg: '@tree-sitter-grammars/tree-sitter-yaml',
27
+ wasm: 'tree-sitter-yaml.wasm',
28
+ },
29
+ json: { pkg: 'tree-sitter-json', wasm: 'tree-sitter-json.wasm' },
30
+ html: { pkg: 'tree-sitter-html', wasm: 'tree-sitter-html.wasm' },
31
+ css: { pkg: 'tree-sitter-css', wasm: 'tree-sitter-css.wasm' },
32
+ lua: { pkg: 'tree-sitter-lua', wasm: 'tree-sitter-lua.wasm' },
33
+ dart: { pkg: 'tree-sitter-dart', wasm: 'tree-sitter-dart.wasm' },
34
+ elixir: { pkg: 'tree-sitter-elixir', wasm: 'tree-sitter-elixir.wasm' },
35
+ scala: { pkg: 'tree-sitter-scala', wasm: 'tree-sitter-scala.wasm' },
22
36
  };
23
37
  async function ensureInit() {
24
38
  if (!initPromise) {
package/media/logo.svg ADDED
@@ -0,0 +1,29 @@
1
+ <svg width="220" height="220" viewBox="0 0 220 220" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
2
+ <title id="title">KGraph Atom Core Logo</title>
3
+ <desc id="desc">Atom Core logo for KGraph, persistent repository intelligence for AI coding tools.</desc>
4
+ <defs>
5
+ <radialGradient id="core" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(110 104) rotate(90) scale(88)">
6
+ <stop offset="0" stop-color="#22D3EE"/>
7
+ <stop offset="0.58" stop-color="#38BDF8"/>
8
+ <stop offset="1" stop-color="#A78BFA"/>
9
+ </radialGradient>
10
+ <linearGradient id="orbit" x1="36" y1="56" x2="184" y2="164" gradientUnits="userSpaceOnUse">
11
+ <stop stop-color="#22D3EE"/>
12
+ <stop offset="1" stop-color="#C084FC"/>
13
+ </linearGradient>
14
+ <filter id="glow" x="12" y="12" width="196" height="196" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
15
+ <feGaussianBlur stdDeviation="6" result="blur"/>
16
+ <feColorMatrix in="blur" type="matrix" values="0 0 0 0 0.133 0 0 0 0 0.827 0 0 0 0 0.933 0 0 0 0.42 0"/>
17
+ <feBlend in="SourceGraphic"/>
18
+ </filter>
19
+ </defs>
20
+ <circle cx="110" cy="110" r="78" fill="#0F172A" fill-opacity="0.82" stroke="#1E293B" stroke-width="2"/>
21
+ <g filter="url(#glow)" stroke="url(#orbit)" stroke-width="6" stroke-linecap="round">
22
+ <ellipse cx="110" cy="110" rx="76" ry="28" transform="rotate(-23 110 110)"/>
23
+ <ellipse cx="110" cy="110" rx="76" ry="28" transform="rotate(23 110 110)"/>
24
+ <ellipse cx="110" cy="110" rx="28" ry="76"/>
25
+ </g>
26
+ <circle cx="110" cy="110" r="19" fill="url(#core)"/>
27
+ <circle cx="164" cy="70" r="7" fill="#22D3EE"/>
28
+ <circle cx="62" cy="148" r="6" fill="#C084FC"/>
29
+ </svg>