@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.
- package/README.md +71 -14
- package/dist/cli/help.js +3 -2
- package/dist/cli/init-summary.d.ts +1 -1
- package/dist/cli/init-summary.js +28 -8
- package/dist/config/config.js +41 -0
- package/dist/scanner/broad-symbol-extractor.d.ts +3 -0
- package/dist/scanner/broad-symbol-extractor.js +292 -0
- package/dist/scanner/extraction-context.d.ts +23 -0
- package/dist/scanner/extraction-context.js +77 -0
- package/dist/scanner/file-classifier.js +15 -2
- package/dist/scanner/php-symbol-extractor.d.ts +2 -0
- package/dist/scanner/php-symbol-extractor.js +79 -0
- package/dist/scanner/repo-scanner.js +35 -1
- package/dist/scanner/ruby-symbol-extractor.d.ts +2 -0
- package/dist/scanner/ruby-symbol-extractor.js +75 -0
- package/dist/scanner/shell-symbol-extractor.d.ts +2 -0
- package/dist/scanner/shell-symbol-extractor.js +78 -0
- package/dist/scanner/sql-symbol-extractor.d.ts +2 -0
- package/dist/scanner/sql-symbol-extractor.js +166 -0
- package/dist/scanner/tree-sitter-parser.d.ts +1 -2
- package/dist/scanner/tree-sitter-parser.js +14 -0
- package/media/logo.svg +29 -0
- package/package.json +13 -1
package/README.md
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="media/logo.svg" alt="KGraph Atom Core Logo" width="140" height="140">
|
|
4
|
+
|
|
5
|
+
# KGraph: Persistent repository intelligence for AI coding tools.
|
|
6
|
+
|
|
7
|
+
<strong>atoms · evidence · context packs</strong>
|
|
8
|
+
|
|
9
|
+
<p>
|
|
10
|
+
<a href="https://www.npmjs.com/package/@kentwynn/kgraph">
|
|
11
|
+
<img src="https://img.shields.io/npm/v/@kentwynn/kgraph?label=npm" alt="npm version">
|
|
12
|
+
</a>
|
|
13
|
+
<a href="https://github.com/kentwynn/KGraph/releases/latest">
|
|
14
|
+
<img src="https://img.shields.io/github/v/release/kentwynn/KGraph?label=release" alt="Latest release">
|
|
15
|
+
</a>
|
|
16
|
+
<a href="https://github.com/kentwynn/KGraph/actions/workflows/ci.yml">
|
|
17
|
+
<img src="https://img.shields.io/github/actions/workflow/status/kentwynn/KGraph/ci.yml?branch=main&label=ci" alt="CI">
|
|
18
|
+
</a>
|
|
19
|
+
<a href="https://github.com/kentwynn/KGraph/blob/main/LICENSE">
|
|
20
|
+
<img src="https://img.shields.io/github/license/kentwynn/KGraph" alt="License">
|
|
21
|
+
</a>
|
|
22
|
+
<a href="https://nodejs.org/">
|
|
23
|
+
<img src="https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white" alt="Node.js >=20">
|
|
24
|
+
</a>
|
|
25
|
+
<a href="https://github.com/kentwynn/KGraph/security/policy">
|
|
26
|
+
<img src="https://img.shields.io/badge/security-policy-0f766e" alt="Security policy">
|
|
27
|
+
</a>
|
|
28
|
+
<a href="https://github.com/kentwynn/KGraph/wiki">
|
|
29
|
+
<img src="https://img.shields.io/badge/docs-wiki-blue" alt="Documentation">
|
|
30
|
+
</a>
|
|
31
|
+
<a href="https://github.com/kentwynn/KGraph/stargazers">
|
|
32
|
+
<img src="https://img.shields.io/github/stars/kentwynn/KGraph?style=social" alt="GitHub stars">
|
|
33
|
+
</a>
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
</div>
|
|
4
37
|
|
|
5
38
|
KGraph gives Codex, GitHub Copilot, Cursor, Claude Code, Gemini CLI, Windsurf, and Cline a local knowledge layer for your repo: file maps, symbols, imports, relationships, and durable knowledge atoms from previous AI sessions. The goal is simple: your assistant should not spend every session re-learning the same codebase.
|
|
6
39
|
|
|
@@ -8,22 +41,33 @@ The CLI presents this as **Atom Core**: lightweight local atoms plus determinist
|
|
|
8
41
|
|
|
9
42
|
## The Workflow
|
|
10
43
|
|
|
11
|
-
Use KGraph
|
|
44
|
+
Use KGraph with one setup command and one normal daily command:
|
|
12
45
|
|
|
13
46
|
```bash
|
|
14
|
-
# Required once per repository
|
|
15
|
-
kgraph
|
|
47
|
+
# Required once per repository.
|
|
48
|
+
# Creates .kgraph/, runs the first scan, and detects likely AI tools.
|
|
49
|
+
kgraph init
|
|
16
50
|
|
|
17
|
-
# Normal daily command
|
|
51
|
+
# Normal daily command.
|
|
52
|
+
# Refreshes maps, processes pending capture notes, and returns focused context.
|
|
18
53
|
kgraph "auth token refresh"
|
|
19
54
|
```
|
|
20
55
|
|
|
21
|
-
|
|
56
|
+
AI tool integrations are optional. During `kgraph init`, KGraph detects likely local tools such as Codex, Copilot, Claude Code, and Gemini, then prompts or prints a suggested `kgraph integrate add ...` command. You can accept the recommendation, choose custom integrations, skip the step, or configure them later.
|
|
57
|
+
|
|
58
|
+
If you already know exactly which integrations you want, you can pass them during init:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
kgraph init --integrations codex,copilot,cursor,claude-code,gemini,windsurf,cline
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The daily command runs the full practical workflow:
|
|
22
65
|
|
|
23
66
|
1. Refreshes the repository scan.
|
|
24
67
|
2. Updates file, symbol, import, and relationship maps.
|
|
25
68
|
3. Processes any Markdown capture notes waiting in `.kgraph/inbox/` into knowledge atoms.
|
|
26
|
-
4.
|
|
69
|
+
4. Reports memory health and actionable next steps.
|
|
70
|
+
5. Returns compact context for the topic you asked about.
|
|
27
71
|
|
|
28
72
|
You can also run just:
|
|
29
73
|
|
|
@@ -101,10 +145,11 @@ KGraph's core functionality is free and local-first. It does not require account
|
|
|
101
145
|
From the root of a repository:
|
|
102
146
|
|
|
103
147
|
```bash
|
|
104
|
-
# 1. Create the local KGraph workspace
|
|
148
|
+
# 1. Create the local KGraph workspace and run the first scan
|
|
105
149
|
kgraph init
|
|
106
150
|
|
|
107
|
-
# 2. Optional:
|
|
151
|
+
# 2. Optional: accept detected AI tool recommendations during init,
|
|
152
|
+
# or add integrations later when you want KGraph-managed instructions
|
|
108
153
|
kgraph integrate add codex copilot cursor claude-code gemini windsurf cline
|
|
109
154
|
|
|
110
155
|
# 3. Run the normal workflow for a topic
|
|
@@ -114,7 +159,7 @@ kgraph "auth token refresh"
|
|
|
114
159
|
kgraph doctor
|
|
115
160
|
```
|
|
116
161
|
|
|
117
|
-
`kgraph init`
|
|
162
|
+
`kgraph init` scans once, prints repo language coverage, detects likely local AI tools, and recommends matching integrations. Integrations are still optional: they only write local instruction files so tools know when to run KGraph. They do not start background agents or call AI providers.
|
|
118
163
|
|
|
119
164
|
After useful AI work, assistants save durable runtime-capture notes into `.kgraph/inbox/`. These notes are not project documentation; they are KGraph input files that the next `kgraph` run processes automatically. You can also process them directly with `kgraph update`.
|
|
120
165
|
|
|
@@ -150,13 +195,13 @@ This is optional. Claude Code can use generated hook scripts for automatic captu
|
|
|
150
195
|
kgraph init
|
|
151
196
|
```
|
|
152
197
|
|
|
153
|
-
Required once per repo. Creates `.kgraph/`, writes the local config, runs the first scan, and prints suggested next actions based on
|
|
198
|
+
Required once per repo. Creates `.kgraph/`, writes the local config, initializes the knowledge store, runs the first scan, detects likely local AI tools, and prints suggested next actions based on repo languages and detected tools.
|
|
154
199
|
|
|
155
200
|
```bash
|
|
156
201
|
kgraph init --integrations codex,copilot,cursor,claude-code,gemini,windsurf,cline
|
|
157
202
|
```
|
|
158
203
|
|
|
159
|
-
Initializes KGraph and writes local instruction files for
|
|
204
|
+
Initializes KGraph and immediately writes local instruction files for the named AI tools. This is optional; plain `kgraph init` can detect likely tools and recommend or prompt for integrations instead.
|
|
160
205
|
|
|
161
206
|
```bash
|
|
162
207
|
kgraph "some topic"
|
|
@@ -323,6 +368,10 @@ Show processed capture history. Add a query to find historical work by title, su
|
|
|
323
368
|
|
|
324
369
|
KGraph integrations are local files. They do not start background agents, call AI providers, or send data anywhere.
|
|
325
370
|
|
|
371
|
+
You do not need integrations to use KGraph manually. They are useful when you want Codex, Copilot, Cursor, Claude Code, Gemini, Windsurf, or Cline to see repo-local KGraph workflow instructions automatically.
|
|
372
|
+
|
|
373
|
+
`kgraph init` detects likely local tools and recommends integrations when possible. You can also manage them explicitly:
|
|
374
|
+
|
|
326
375
|
```bash
|
|
327
376
|
kgraph integrate add codex copilot cursor claude-code gemini windsurf cline
|
|
328
377
|
kgraph integrate add copilot --mode smart
|
|
@@ -391,9 +440,17 @@ KGraph deeply scans:
|
|
|
391
440
|
- Java and Kotlin
|
|
392
441
|
- C and C++
|
|
393
442
|
- C#
|
|
443
|
+
- PHP
|
|
444
|
+
- Ruby
|
|
445
|
+
- Shell
|
|
446
|
+
- SQL
|
|
394
447
|
|
|
395
448
|
Other languages keep practical file, import, and symbol depth without full call graph analysis. Common file types still appear in the file map with generic metadata, so context queries can still point to docs, config, SQL, CSS, HTML, YAML, and similar files.
|
|
396
449
|
|
|
450
|
+
KGraph also extracts basic symbols/imports for Swift, Terraform/HCL,
|
|
451
|
+
GraphQL, Protocol Buffers, Lua, Dart, Elixir, Scala, and R. Structured file extraction covers
|
|
452
|
+
YAML, JSON, TOML, Dockerfile/Containerfile, Markdown/MDX, HTML, CSS, SCSS, Sass, Less, and XML.
|
|
453
|
+
|
|
397
454
|
## Visualization
|
|
398
455
|
|
|
399
456
|
```bash
|
package/dist/cli/help.js
CHANGED
|
@@ -20,7 +20,7 @@ export function renderRootHelp(useColor = supportsColor()) {
|
|
|
20
20
|
'',
|
|
21
21
|
sectionTitle(theme, `${accent} Start`),
|
|
22
22
|
command('init', 'Required once: create .kgraph/ workspace'),
|
|
23
|
-
command('init --integrations codex,gemini', '
|
|
23
|
+
command('init --integrations codex,gemini', 'Optional: initialize and connect named AI tools'),
|
|
24
24
|
'',
|
|
25
25
|
sectionTitle(theme, `${accent} Daily workflow`),
|
|
26
26
|
command('kgraph', 'Refresh scan maps and process pending capture notes'),
|
|
@@ -68,7 +68,8 @@ export function renderRootHelp(useColor = supportsColor()) {
|
|
|
68
68
|
command('--capture-symbol <name>', 'Attach symbol evidence to root capture'),
|
|
69
69
|
'',
|
|
70
70
|
sectionTitle(theme, `${accent} Examples`),
|
|
71
|
-
' kgraph init
|
|
71
|
+
' kgraph init',
|
|
72
|
+
' kgraph integrate add codex copilot cursor claude-code gemini windsurf cline',
|
|
72
73
|
' kgraph "blog admin token usage"',
|
|
73
74
|
' kgraph "blog admin token usage" --final',
|
|
74
75
|
' kgraph "blog admin token usage" --capture "Author filter now uses display names" --capture-file www/app/blog/page.tsx',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IntegrationConfig } from '../types/config.js';
|
|
2
2
|
import type { RepositoryFile } from '../types/maps.js';
|
|
3
3
|
import { type InitIntegrationRecommendation } from './init-recommendations.js';
|
|
4
|
-
type CoverageLevel = 'deep' | 'basic' | 'generic';
|
|
4
|
+
type CoverageLevel = 'deep' | 'basic' | 'structured' | 'generic';
|
|
5
5
|
export interface InitLanguageSummary {
|
|
6
6
|
language: string;
|
|
7
7
|
label: string;
|
package/dist/cli/init-summary.js
CHANGED
|
@@ -12,15 +12,32 @@ const LANGUAGE_PRESENTATION = {
|
|
|
12
12
|
c: { label: 'C', coverage: 'deep' },
|
|
13
13
|
cpp: { label: 'C++', coverage: 'deep' },
|
|
14
14
|
csharp: { label: 'C#', coverage: 'deep' },
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
php: { label: 'PHP', coverage: 'deep' },
|
|
16
|
+
swift: { label: 'Swift', coverage: 'basic' },
|
|
17
|
+
ruby: { label: 'Ruby', coverage: 'deep' },
|
|
18
|
+
shell: { label: 'Shell', coverage: 'deep' },
|
|
19
|
+
lua: { label: 'Lua', coverage: 'basic' },
|
|
20
|
+
dart: { label: 'Dart', coverage: 'basic' },
|
|
21
|
+
elixir: { label: 'Elixir', coverage: 'basic' },
|
|
22
|
+
scala: { label: 'Scala', coverage: 'basic' },
|
|
23
|
+
r: { label: 'R', coverage: 'basic' },
|
|
24
|
+
sql: { label: 'SQL', coverage: 'deep' },
|
|
25
|
+
terraform: { label: 'Terraform/HCL', coverage: 'basic' },
|
|
26
|
+
graphql: { label: 'GraphQL', coverage: 'basic' },
|
|
27
|
+
protobuf: { label: 'Protocol Buffers', coverage: 'basic' },
|
|
28
|
+
yaml: { label: 'YAML', coverage: 'structured' },
|
|
29
|
+
json: { label: 'JSON', coverage: 'structured' },
|
|
30
|
+
toml: { label: 'TOML', coverage: 'structured' },
|
|
31
|
+
dockerfile: { label: 'Dockerfile', coverage: 'structured' },
|
|
32
|
+
markdown: { label: 'Markdown', coverage: 'structured' },
|
|
33
|
+
html: { label: 'HTML', coverage: 'structured' },
|
|
34
|
+
css: { label: 'CSS', coverage: 'structured' },
|
|
35
|
+
scss: { label: 'SCSS', coverage: 'structured' },
|
|
36
|
+
sass: { label: 'Sass', coverage: 'structured' },
|
|
37
|
+
less: { label: 'Less', coverage: 'structured' },
|
|
38
|
+
xml: { label: 'XML', coverage: 'structured' },
|
|
22
39
|
};
|
|
23
|
-
const EXCLUDED_LANGUAGES = new Set(['unknown', '
|
|
40
|
+
const EXCLUDED_LANGUAGES = new Set(['unknown', 'restructuredtext']);
|
|
24
41
|
export function summarizeInitLanguages(files) {
|
|
25
42
|
const byLabel = new Map();
|
|
26
43
|
for (const file of files) {
|
|
@@ -103,6 +120,7 @@ function moreDetailedCoverage(left, right) {
|
|
|
103
120
|
const rank = {
|
|
104
121
|
deep: 3,
|
|
105
122
|
basic: 2,
|
|
123
|
+
structured: 2,
|
|
106
124
|
generic: 1,
|
|
107
125
|
};
|
|
108
126
|
return rank[left] >= rank[right] ? left : right;
|
|
@@ -113,6 +131,8 @@ function coverageDescription(coverage) {
|
|
|
113
131
|
return 'deep built-in extraction';
|
|
114
132
|
case 'basic':
|
|
115
133
|
return 'basic built-in extraction';
|
|
134
|
+
case 'structured':
|
|
135
|
+
return 'structured file extraction';
|
|
116
136
|
default:
|
|
117
137
|
return 'generic file coverage';
|
|
118
138
|
}
|
package/dist/config/config.js
CHANGED
|
@@ -67,6 +67,47 @@ export const DEFAULT_CONFIG = {
|
|
|
67
67
|
'.hxx',
|
|
68
68
|
// C#
|
|
69
69
|
'.cs',
|
|
70
|
+
// PHP / Swift / Ruby
|
|
71
|
+
'.php',
|
|
72
|
+
'.swift',
|
|
73
|
+
'.rb',
|
|
74
|
+
'.rake',
|
|
75
|
+
// Shell
|
|
76
|
+
'.sh',
|
|
77
|
+
'.bash',
|
|
78
|
+
'.zsh',
|
|
79
|
+
'.fish',
|
|
80
|
+
// Web / styles
|
|
81
|
+
'.html',
|
|
82
|
+
'.htm',
|
|
83
|
+
'.css',
|
|
84
|
+
'.scss',
|
|
85
|
+
'.sass',
|
|
86
|
+
'.less',
|
|
87
|
+
// Data / config / schema
|
|
88
|
+
'.json',
|
|
89
|
+
'.jsonc',
|
|
90
|
+
'.yaml',
|
|
91
|
+
'.yml',
|
|
92
|
+
'.toml',
|
|
93
|
+
'.dockerfile',
|
|
94
|
+
'.xml',
|
|
95
|
+
'.graphql',
|
|
96
|
+
'.gql',
|
|
97
|
+
'.sql',
|
|
98
|
+
'.tf',
|
|
99
|
+
'.proto',
|
|
100
|
+
// Docs
|
|
101
|
+
'.md',
|
|
102
|
+
'.mdx',
|
|
103
|
+
// Additional app languages
|
|
104
|
+
'.lua',
|
|
105
|
+
'.r',
|
|
106
|
+
'.R',
|
|
107
|
+
'.dart',
|
|
108
|
+
'.ex',
|
|
109
|
+
'.exs',
|
|
110
|
+
'.scala',
|
|
70
111
|
],
|
|
71
112
|
},
|
|
72
113
|
maxContextItems: 8,
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { SymbolExtractionResult } from './ts-symbol-extractor.js';
|
|
2
|
+
export declare function extractBroadSymbols(sourceText: string, filePath: string, language: string): Promise<SymbolExtractionResult>;
|
|
3
|
+
export declare function supportsBroadExtraction(language: string): boolean;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { parseSource } from './tree-sitter-parser.js';
|
|
2
|
+
const TREE_SITTER_GRAMMAR_BY_LANGUAGE = {
|
|
3
|
+
yaml: 'yaml',
|
|
4
|
+
json: 'json',
|
|
5
|
+
html: 'html',
|
|
6
|
+
css: 'css',
|
|
7
|
+
lua: 'lua',
|
|
8
|
+
dart: 'dart',
|
|
9
|
+
elixir: 'elixir',
|
|
10
|
+
scala: 'scala',
|
|
11
|
+
};
|
|
12
|
+
export async function extractBroadSymbols(sourceText, filePath, language) {
|
|
13
|
+
const result = createExtractionResult();
|
|
14
|
+
if (!sourceText.trim()) {
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
const broadLanguage = language;
|
|
18
|
+
const grammar = TREE_SITTER_GRAMMAR_BY_LANGUAGE[broadLanguage];
|
|
19
|
+
if (grammar) {
|
|
20
|
+
try {
|
|
21
|
+
const tree = await parseSource(sourceText, grammar);
|
|
22
|
+
tree.delete();
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
result.warnings.push(`tree-sitter ${grammar} parse failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const lines = sourceText.split(/\r?\n/);
|
|
29
|
+
switch (broadLanguage) {
|
|
30
|
+
case 'swift':
|
|
31
|
+
collectSwift(lines, filePath, result);
|
|
32
|
+
break;
|
|
33
|
+
case 'terraform':
|
|
34
|
+
collectTerraform(lines, filePath, result);
|
|
35
|
+
break;
|
|
36
|
+
case 'graphql':
|
|
37
|
+
collectGraphql(lines, filePath, result);
|
|
38
|
+
break;
|
|
39
|
+
case 'protobuf':
|
|
40
|
+
collectProtobuf(lines, filePath, result);
|
|
41
|
+
break;
|
|
42
|
+
case 'lua':
|
|
43
|
+
collectLua(lines, filePath, result);
|
|
44
|
+
break;
|
|
45
|
+
case 'dart':
|
|
46
|
+
collectDart(lines, filePath, result);
|
|
47
|
+
break;
|
|
48
|
+
case 'elixir':
|
|
49
|
+
collectElixir(lines, filePath, result);
|
|
50
|
+
break;
|
|
51
|
+
case 'scala':
|
|
52
|
+
collectScala(lines, filePath, result);
|
|
53
|
+
break;
|
|
54
|
+
case 'r':
|
|
55
|
+
collectR(lines, filePath, result);
|
|
56
|
+
break;
|
|
57
|
+
case 'yaml':
|
|
58
|
+
case 'json':
|
|
59
|
+
case 'toml':
|
|
60
|
+
case 'dockerfile':
|
|
61
|
+
collectConfig(lines, filePath, result, broadLanguage);
|
|
62
|
+
break;
|
|
63
|
+
case 'markdown':
|
|
64
|
+
collectMarkdown(lines, filePath, result);
|
|
65
|
+
break;
|
|
66
|
+
case 'html':
|
|
67
|
+
case 'xml':
|
|
68
|
+
collectMarkup(lines, filePath, result);
|
|
69
|
+
break;
|
|
70
|
+
case 'css':
|
|
71
|
+
case 'scss':
|
|
72
|
+
case 'sass':
|
|
73
|
+
case 'less':
|
|
74
|
+
collectStylesheet(lines, filePath, result);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
export function supportsBroadExtraction(language) {
|
|
80
|
+
return [
|
|
81
|
+
'swift',
|
|
82
|
+
'terraform',
|
|
83
|
+
'graphql',
|
|
84
|
+
'protobuf',
|
|
85
|
+
'lua',
|
|
86
|
+
'dart',
|
|
87
|
+
'elixir',
|
|
88
|
+
'scala',
|
|
89
|
+
'r',
|
|
90
|
+
'yaml',
|
|
91
|
+
'json',
|
|
92
|
+
'toml',
|
|
93
|
+
'dockerfile',
|
|
94
|
+
'markdown',
|
|
95
|
+
'html',
|
|
96
|
+
'css',
|
|
97
|
+
'scss',
|
|
98
|
+
'sass',
|
|
99
|
+
'less',
|
|
100
|
+
'xml',
|
|
101
|
+
].includes(language);
|
|
102
|
+
}
|
|
103
|
+
function createExtractionResult() {
|
|
104
|
+
return { symbols: [], dependencies: [], relationships: [], warnings: [] };
|
|
105
|
+
}
|
|
106
|
+
function addSymbol(result, filePath, name, kind, line, exported = false, parentName) {
|
|
107
|
+
const id = [filePath, kind, parentName, name, line].filter(Boolean).join('#');
|
|
108
|
+
const symbol = {
|
|
109
|
+
id,
|
|
110
|
+
name,
|
|
111
|
+
kind,
|
|
112
|
+
filePath,
|
|
113
|
+
startLine: line,
|
|
114
|
+
endLine: line,
|
|
115
|
+
exported,
|
|
116
|
+
parentName,
|
|
117
|
+
};
|
|
118
|
+
result.symbols.push(symbol);
|
|
119
|
+
result.relationships.push({
|
|
120
|
+
sourceType: 'file',
|
|
121
|
+
sourceId: filePath,
|
|
122
|
+
targetType: 'symbol',
|
|
123
|
+
targetId: id,
|
|
124
|
+
relationshipType: 'contains',
|
|
125
|
+
confidence: 'high',
|
|
126
|
+
});
|
|
127
|
+
return symbol;
|
|
128
|
+
}
|
|
129
|
+
function addDependency(result, filePath, specifier, kind = specifier.startsWith('.') ? 'local' : 'package') {
|
|
130
|
+
result.dependencies.push({ fromFile: filePath, specifier, kind });
|
|
131
|
+
}
|
|
132
|
+
function collectSwift(lines, filePath, result) {
|
|
133
|
+
forEachLine(lines, (line, lineNumber) => {
|
|
134
|
+
const importMatch = line.match(/^\s*import\s+([A-Za-z_][\w.]*)/);
|
|
135
|
+
if (importMatch?.[1])
|
|
136
|
+
addDependency(result, filePath, importMatch[1]);
|
|
137
|
+
const typeMatch = line.match(/^\s*(?:public|private|internal|open|final|\s)*(class|struct|enum|protocol)\s+([A-Za-z_][\w]*)/);
|
|
138
|
+
if (typeMatch?.[2]) {
|
|
139
|
+
addSymbol(result, filePath, typeMatch[2], typeMatch[1] === 'protocol' ? 'interface' : 'class', lineNumber, true);
|
|
140
|
+
}
|
|
141
|
+
const functionMatch = line.match(/^\s*(?:public|private|internal|open|static|\s)*func\s+([A-Za-z_][\w]*)/);
|
|
142
|
+
if (functionMatch?.[1]) {
|
|
143
|
+
addSymbol(result, filePath, functionMatch[1], 'function', lineNumber, true);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function collectTerraform(lines, filePath, result) {
|
|
148
|
+
forEachLine(lines, (line, lineNumber) => {
|
|
149
|
+
const blockMatch = line.match(/^\s*(resource|data|module|variable|output|provider)\s+"([^"]+)"(?:\s+"([^"]+)")?/);
|
|
150
|
+
if (blockMatch?.[1] && blockMatch[2]) {
|
|
151
|
+
addSymbol(result, filePath, [blockMatch[1], blockMatch[2], blockMatch[3]].filter(Boolean).join('.'), 'type', lineNumber, true);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function collectGraphql(lines, filePath, result) {
|
|
156
|
+
forEachLine(lines, (line, lineNumber) => {
|
|
157
|
+
const typeMatch = line.match(/^\s*(type|interface|enum|input|union|scalar)\s+([A-Za-z_][\w]*)/);
|
|
158
|
+
if (typeMatch?.[2]) {
|
|
159
|
+
addSymbol(result, filePath, typeMatch[2], typeMatch[1] === 'interface' ? 'interface' : 'type', lineNumber, true);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function collectProtobuf(lines, filePath, result) {
|
|
164
|
+
forEachLine(lines, (line, lineNumber) => {
|
|
165
|
+
const importMatch = line.match(/^\s*import\s+"([^"]+)"/);
|
|
166
|
+
if (importMatch?.[1])
|
|
167
|
+
addDependency(result, filePath, importMatch[1], 'local');
|
|
168
|
+
const typeMatch = line.match(/^\s*(message|service|enum)\s+([A-Za-z_][\w]*)/);
|
|
169
|
+
if (typeMatch?.[2]) {
|
|
170
|
+
addSymbol(result, filePath, typeMatch[2], 'type', lineNumber, true);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function collectLua(lines, filePath, result) {
|
|
175
|
+
forEachLine(lines, (line, lineNumber) => {
|
|
176
|
+
const requireMatch = line.match(/require\s*\(?\s*['"]([^'"]+)['"]/);
|
|
177
|
+
if (requireMatch?.[1])
|
|
178
|
+
addDependency(result, filePath, requireMatch[1]);
|
|
179
|
+
const functionMatch = line.match(/^\s*(?:local\s+)?function\s+([A-Za-z_][\w.:]*)/);
|
|
180
|
+
if (functionMatch?.[1]) {
|
|
181
|
+
addSymbol(result, filePath, functionMatch[1], 'function', lineNumber, true);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function collectDart(lines, filePath, result) {
|
|
186
|
+
forEachLine(lines, (line, lineNumber) => {
|
|
187
|
+
const importMatch = line.match(/^\s*import\s+['"]([^'"]+)['"]/);
|
|
188
|
+
if (importMatch?.[1])
|
|
189
|
+
addDependency(result, filePath, importMatch[1]);
|
|
190
|
+
const classMatch = line.match(/^\s*(?:abstract\s+)?class\s+([A-Za-z_][\w]*)/);
|
|
191
|
+
if (classMatch?.[1])
|
|
192
|
+
addSymbol(result, filePath, classMatch[1], 'class', lineNumber, true);
|
|
193
|
+
const functionMatch = line.match(/^\s*(?:[A-Za-z_<>,?]+\s+)+([A-Za-z_][\w]*)\s*\([^;]*\)\s*(?:async\s*)?\{/);
|
|
194
|
+
if (functionMatch?.[1] && !['if', 'for', 'while', 'switch'].includes(functionMatch[1])) {
|
|
195
|
+
addSymbol(result, filePath, functionMatch[1], 'function', lineNumber, true);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function collectElixir(lines, filePath, result) {
|
|
200
|
+
forEachLine(lines, (line, lineNumber) => {
|
|
201
|
+
const moduleMatch = line.match(/^\s*defmodule\s+([A-Z][\w.]+)/);
|
|
202
|
+
if (moduleMatch?.[1])
|
|
203
|
+
addSymbol(result, filePath, moduleMatch[1], 'class', lineNumber, true);
|
|
204
|
+
const functionMatch = line.match(/^\s*defp?\s+([a-z_][\w!?]*)/);
|
|
205
|
+
if (functionMatch?.[1])
|
|
206
|
+
addSymbol(result, filePath, functionMatch[1], 'function', lineNumber, true);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function collectScala(lines, filePath, result) {
|
|
210
|
+
forEachLine(lines, (line, lineNumber) => {
|
|
211
|
+
const importMatch = line.match(/^\s*import\s+(.+)/);
|
|
212
|
+
if (importMatch?.[1])
|
|
213
|
+
addDependency(result, filePath, importMatch[1].trim());
|
|
214
|
+
const typeMatch = line.match(/^\s*(?:case\s+)?(class|object|trait|enum)\s+([A-Za-z_][\w]*)/);
|
|
215
|
+
if (typeMatch?.[2]) {
|
|
216
|
+
addSymbol(result, filePath, typeMatch[2], typeMatch[1] === 'trait' ? 'interface' : 'class', lineNumber, true);
|
|
217
|
+
}
|
|
218
|
+
const functionMatch = line.match(/^\s*def\s+([A-Za-z_][\w]*)/);
|
|
219
|
+
if (functionMatch?.[1])
|
|
220
|
+
addSymbol(result, filePath, functionMatch[1], 'function', lineNumber, true);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function collectR(lines, filePath, result) {
|
|
224
|
+
forEachLine(lines, (line, lineNumber) => {
|
|
225
|
+
const libraryMatch = line.match(/^\s*(?:library|require)\s*\(\s*([A-Za-z.][\w.]*)/);
|
|
226
|
+
if (libraryMatch?.[1])
|
|
227
|
+
addDependency(result, filePath, libraryMatch[1]);
|
|
228
|
+
const functionMatch = line.match(/^\s*([A-Za-z.][\w.]*)\s*(?:<-|=)\s*function\s*\(/);
|
|
229
|
+
if (functionMatch?.[1])
|
|
230
|
+
addSymbol(result, filePath, functionMatch[1], 'function', lineNumber, true);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function collectConfig(lines, filePath, result, language) {
|
|
234
|
+
forEachLine(lines, (line, lineNumber) => {
|
|
235
|
+
if (language === 'dockerfile') {
|
|
236
|
+
const stageMatch = line.match(/^\s*FROM\s+\S+(?:\s+AS\s+([A-Za-z_][\w-]*))?/i);
|
|
237
|
+
if (stageMatch?.[1])
|
|
238
|
+
addSymbol(result, filePath, stageMatch[1], 'type', lineNumber, true);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const keyMatch = language === 'json'
|
|
242
|
+
? line.match(/^\s*"([^"]+)"\s*:/)
|
|
243
|
+
: line.match(/^\s*([A-Za-z_][\w.-]*)\s*[:=]/);
|
|
244
|
+
if (keyMatch?.[1]) {
|
|
245
|
+
addSymbol(result, filePath, keyMatch[1], 'type', lineNumber);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
function collectMarkdown(lines, filePath, result) {
|
|
250
|
+
forEachLine(lines, (line, lineNumber) => {
|
|
251
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)/);
|
|
252
|
+
if (headingMatch?.[2]) {
|
|
253
|
+
addSymbol(result, filePath, headingMatch[2].trim(), 'type', lineNumber);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function collectMarkup(lines, filePath, result) {
|
|
258
|
+
forEachLine(lines, (line, lineNumber) => {
|
|
259
|
+
for (const idMatch of line.matchAll(/\bid=["']([^"']+)["']/g)) {
|
|
260
|
+
if (idMatch[1])
|
|
261
|
+
addSymbol(result, filePath, `#${idMatch[1]}`, 'type', lineNumber);
|
|
262
|
+
}
|
|
263
|
+
for (const classMatch of line.matchAll(/\bclass=["']([^"']+)["']/g)) {
|
|
264
|
+
for (const className of classMatch[1]?.split(/\s+/) ?? []) {
|
|
265
|
+
if (className)
|
|
266
|
+
addSymbol(result, filePath, `.${className}`, 'type', lineNumber);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
function collectStylesheet(lines, filePath, result) {
|
|
272
|
+
forEachLine(lines, (line, lineNumber) => {
|
|
273
|
+
const importMatch = line.match(/^\s*@(import|use|forward)\s+["']([^"']+)["']/);
|
|
274
|
+
if (importMatch?.[2])
|
|
275
|
+
addDependency(result, filePath, importMatch[2]);
|
|
276
|
+
for (const variableMatch of line.matchAll(/(--[A-Za-z_][\w-]*|\$[A-Za-z_][\w-]*)\s*:/g)) {
|
|
277
|
+
if (variableMatch[1])
|
|
278
|
+
addSymbol(result, filePath, variableMatch[1], 'type', lineNumber);
|
|
279
|
+
}
|
|
280
|
+
const mixinMatch = line.match(/^\s*@(mixin|function|keyframes)\s+([A-Za-z_][\w-]*)/);
|
|
281
|
+
if (mixinMatch?.[2]) {
|
|
282
|
+
addSymbol(result, filePath, mixinMatch[2], mixinMatch[1] === 'function' ? 'function' : 'type', lineNumber);
|
|
283
|
+
}
|
|
284
|
+
for (const selectorMatch of line.matchAll(/([.#][A-Za-z_][\w-]*)/g)) {
|
|
285
|
+
if (selectorMatch[1])
|
|
286
|
+
addSymbol(result, filePath, selectorMatch[1], 'type', lineNumber);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
function forEachLine(lines, callback) {
|
|
291
|
+
lines.forEach((line, index) => callback(line, index + 1));
|
|
292
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CodeSymbol, Dependency, Relationship } from '../types/maps.js';
|
|
2
|
+
import type { SymbolExtractionResult } from './ts-symbol-extractor.js';
|
|
3
|
+
export declare class ExtractionContext {
|
|
4
|
+
private readonly filePath;
|
|
5
|
+
readonly symbols: CodeSymbol[];
|
|
6
|
+
readonly dependencies: Dependency[];
|
|
7
|
+
readonly relationships: Relationship[];
|
|
8
|
+
readonly warnings: string[];
|
|
9
|
+
constructor(filePath: string);
|
|
10
|
+
addSymbol(options: {
|
|
11
|
+
name: string;
|
|
12
|
+
kind: CodeSymbol['kind'];
|
|
13
|
+
startLine: number;
|
|
14
|
+
endLine?: number;
|
|
15
|
+
exported?: boolean;
|
|
16
|
+
parentName?: string;
|
|
17
|
+
}): CodeSymbol;
|
|
18
|
+
addDependency(specifier: string, kind?: Dependency['kind'], confidence?: Relationship['confidence']): void;
|
|
19
|
+
addSymbolContains(parent: CodeSymbol, child: CodeSymbol): void;
|
|
20
|
+
addWarning(message: string): void;
|
|
21
|
+
toResult(): SymbolExtractionResult;
|
|
22
|
+
}
|
|
23
|
+
export declare function emptyExtractionResult(): SymbolExtractionResult;
|