@kentwynn/kgraph 0.1.20 → 0.1.21
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
CHANGED
|
@@ -51,6 +51,7 @@ KGraph stores the reusable parts locally:
|
|
|
51
51
|
- What files exist and what language they use.
|
|
52
52
|
- What symbols each source file defines.
|
|
53
53
|
- Which files import each other.
|
|
54
|
+
- Which TypeScript/JavaScript functions and methods directly call each other when KGraph can infer it cheaply.
|
|
54
55
|
- Which notes, decisions, debugging findings, and gotchas were captured from prior sessions.
|
|
55
56
|
- Which cognition references are current, mixed, stale, or unresolved after code moves.
|
|
56
57
|
|
|
@@ -240,7 +241,7 @@ The files are local, inspectable, and human-readable. There is no database, tele
|
|
|
240
241
|
|
|
241
242
|
KGraph deeply scans:
|
|
242
243
|
|
|
243
|
-
- TypeScript and JavaScript
|
|
244
|
+
- TypeScript and JavaScript, including lightweight function/method call relationships
|
|
244
245
|
- Python
|
|
245
246
|
- Go
|
|
246
247
|
- Rust
|
|
@@ -248,7 +249,7 @@ KGraph deeply scans:
|
|
|
248
249
|
- C and C++
|
|
249
250
|
- C#
|
|
250
251
|
|
|
251
|
-
Other
|
|
252
|
+
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.
|
|
252
253
|
|
|
253
254
|
## Visualization
|
|
254
255
|
|
|
@@ -256,7 +257,7 @@ Other common file types still appear in the file map with generic metadata, so c
|
|
|
256
257
|
kgraph visualize
|
|
257
258
|
```
|
|
258
259
|
|
|
259
|
-
The graph shows files, symbols, imports, cognition notes, and relationship edges. Cognition notes are colored by reference health:
|
|
260
|
+
The graph shows files, symbols, imports, TypeScript/JavaScript call edges, ownership edges, cognition notes, and relationship edges. Cognition notes are colored by reference health:
|
|
260
261
|
|
|
261
262
|
- current
|
|
262
263
|
- mixed
|
|
@@ -39,6 +39,13 @@ export async function queryContext(workspace, config, maps, query) {
|
|
|
39
39
|
...domain.item.symbols,
|
|
40
40
|
]),
|
|
41
41
|
]);
|
|
42
|
+
for (const relationship of maps.relationshipMap.relationships) {
|
|
43
|
+
if (relatedIds.has(relationship.sourceId) ||
|
|
44
|
+
relatedIds.has(relationship.targetId)) {
|
|
45
|
+
relatedIds.add(relationship.sourceId);
|
|
46
|
+
relatedIds.add(relationship.targetId);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
42
49
|
const rankedRelationships = rankByFields(query, maps.relationshipMap.relationships, [
|
|
43
50
|
{ name: 'source', value: (relationship) => relationship.sourceId },
|
|
44
51
|
{ name: 'target', value: (relationship) => relationship.targetId },
|
|
@@ -6,11 +6,14 @@ export function extractTsSymbols(sourceText, filePath) {
|
|
|
6
6
|
const dependencies = [];
|
|
7
7
|
const relationships = [];
|
|
8
8
|
const warnings = [];
|
|
9
|
+
const symbolIdsByNode = new Map();
|
|
10
|
+
const symbolsByName = new Map();
|
|
11
|
+
const importedBindings = new Map();
|
|
9
12
|
const addSymbol = (name, kind, node, exported = false, parentName) => {
|
|
10
13
|
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
11
14
|
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
12
15
|
const id = [filePath, kind, parentName, name, start.line + 1, end.line + 1].filter(Boolean).join("#");
|
|
13
|
-
|
|
16
|
+
const symbol = {
|
|
14
17
|
id,
|
|
15
18
|
name,
|
|
16
19
|
kind,
|
|
@@ -19,7 +22,12 @@ export function extractTsSymbols(sourceText, filePath) {
|
|
|
19
22
|
endLine: end.line + 1,
|
|
20
23
|
exported,
|
|
21
24
|
parentName
|
|
22
|
-
}
|
|
25
|
+
};
|
|
26
|
+
symbols.push(symbol);
|
|
27
|
+
symbolIdsByNode.set(node, id);
|
|
28
|
+
const byName = symbolsByName.get(name) ?? [];
|
|
29
|
+
byName.push(symbol);
|
|
30
|
+
symbolsByName.set(name, byName);
|
|
23
31
|
relationships.push({
|
|
24
32
|
sourceType: "file",
|
|
25
33
|
sourceId: filePath,
|
|
@@ -28,8 +36,9 @@ export function extractTsSymbols(sourceText, filePath) {
|
|
|
28
36
|
relationshipType: "contains",
|
|
29
37
|
confidence: "high"
|
|
30
38
|
});
|
|
39
|
+
return symbol;
|
|
31
40
|
};
|
|
32
|
-
const
|
|
41
|
+
const collectSymbols = (node, parentName) => {
|
|
33
42
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
34
43
|
const specifier = node.moduleSpecifier.text;
|
|
35
44
|
const dependency = {
|
|
@@ -48,6 +57,7 @@ export function extractTsSymbols(sourceText, filePath) {
|
|
|
48
57
|
relationshipType: "import",
|
|
49
58
|
confidence: dependency.resolvedFile ? "high" : "medium"
|
|
50
59
|
});
|
|
60
|
+
collectImportedBindings(node, specifier, dependency.resolvedFile, importedBindings);
|
|
51
61
|
}
|
|
52
62
|
if (ts.isExportDeclaration(node)) {
|
|
53
63
|
const name = node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : "export";
|
|
@@ -66,10 +76,18 @@ export function extractTsSymbols(sourceText, filePath) {
|
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
78
|
if (ts.isClassDeclaration(node) && node.name) {
|
|
69
|
-
addSymbol(node.name.text, "class", node, isExported(node), parentName);
|
|
79
|
+
const classSymbol = addSymbol(node.name.text, "class", node, isExported(node), parentName);
|
|
70
80
|
node.members.forEach((member) => {
|
|
71
81
|
if (ts.isMethodDeclaration(member) && member.name && ts.isIdentifier(member.name)) {
|
|
72
|
-
addSymbol(member.name.text, "method", member, false, node.name?.text);
|
|
82
|
+
const methodSymbol = addSymbol(member.name.text, "method", member, false, node.name?.text);
|
|
83
|
+
relationships.push({
|
|
84
|
+
sourceType: "symbol",
|
|
85
|
+
sourceId: classSymbol.id,
|
|
86
|
+
targetType: "symbol",
|
|
87
|
+
targetId: methodSymbol.id,
|
|
88
|
+
relationshipType: "symbol-contains",
|
|
89
|
+
confidence: "high"
|
|
90
|
+
});
|
|
73
91
|
}
|
|
74
92
|
});
|
|
75
93
|
}
|
|
@@ -79,16 +97,85 @@ export function extractTsSymbols(sourceText, filePath) {
|
|
|
79
97
|
if (ts.isTypeAliasDeclaration(node)) {
|
|
80
98
|
addSymbol(node.name.text, "type", node, isExported(node), parentName);
|
|
81
99
|
}
|
|
82
|
-
ts.forEachChild(node, (child) =>
|
|
100
|
+
ts.forEachChild(node, (child) => collectSymbols(child, parentName));
|
|
101
|
+
};
|
|
102
|
+
const collectCalls = (node, currentSymbolId) => {
|
|
103
|
+
const nextSymbolId = symbolIdsByNode.get(node) ?? currentSymbolId;
|
|
104
|
+
if (ts.isCallExpression(node) && nextSymbolId) {
|
|
105
|
+
const target = resolveCallTarget(node, symbolsByName, importedBindings);
|
|
106
|
+
if (target) {
|
|
107
|
+
relationships.push({
|
|
108
|
+
sourceType: "symbol",
|
|
109
|
+
sourceId: nextSymbolId,
|
|
110
|
+
targetType: target.targetType,
|
|
111
|
+
targetId: target.targetId,
|
|
112
|
+
relationshipType: "calls",
|
|
113
|
+
confidence: target.confidence
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
ts.forEachChild(node, (child) => collectCalls(child, nextSymbolId));
|
|
83
118
|
};
|
|
84
119
|
try {
|
|
85
|
-
|
|
120
|
+
collectSymbols(sourceFile);
|
|
121
|
+
collectCalls(sourceFile);
|
|
86
122
|
}
|
|
87
123
|
catch (error) {
|
|
88
124
|
warnings.push(error instanceof Error ? error.message : String(error));
|
|
89
125
|
}
|
|
90
126
|
return { symbols, dependencies, relationships, warnings };
|
|
91
127
|
}
|
|
128
|
+
function collectImportedBindings(node, specifier, resolvedFile, importedBindings) {
|
|
129
|
+
const clause = node.importClause;
|
|
130
|
+
if (!clause) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (clause.name) {
|
|
134
|
+
importedBindings.set(clause.name.text, { specifier, resolvedFile });
|
|
135
|
+
}
|
|
136
|
+
const bindings = clause.namedBindings;
|
|
137
|
+
if (bindings && ts.isNamedImports(bindings)) {
|
|
138
|
+
for (const element of bindings.elements) {
|
|
139
|
+
importedBindings.set(element.name.text, { specifier, resolvedFile });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function resolveCallTarget(node, symbolsByName, importedBindings) {
|
|
144
|
+
const expression = node.expression;
|
|
145
|
+
if (ts.isIdentifier(expression)) {
|
|
146
|
+
const localSymbols = symbolsByName
|
|
147
|
+
.get(expression.text)
|
|
148
|
+
?.filter((symbol) => symbol.kind === "function" || symbol.kind === "method");
|
|
149
|
+
if (localSymbols?.[0]) {
|
|
150
|
+
return {
|
|
151
|
+
targetType: "symbol",
|
|
152
|
+
targetId: localSymbols[0].id,
|
|
153
|
+
confidence: "high"
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const imported = importedBindings.get(expression.text);
|
|
157
|
+
if (imported) {
|
|
158
|
+
return {
|
|
159
|
+
targetType: "symbol",
|
|
160
|
+
targetId: imported.resolvedFile ? `${imported.resolvedFile}#${expression.text}` : expression.text,
|
|
161
|
+
confidence: imported.resolvedFile ? "medium" : "low"
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
targetType: "symbol",
|
|
166
|
+
targetId: expression.text,
|
|
167
|
+
confidence: "low"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (ts.isPropertyAccessExpression(expression)) {
|
|
171
|
+
return {
|
|
172
|
+
targetType: "symbol",
|
|
173
|
+
targetId: expression.getText(),
|
|
174
|
+
confidence: "low"
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
92
179
|
function isExported(node) {
|
|
93
180
|
return ts.canHaveModifiers(node) && Boolean(ts.getModifiers(node)?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword));
|
|
94
181
|
}
|
package/dist/types/maps.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type ScanStatus = "mapped" | "generic" | "failed";
|
|
2
2
|
export type DependencyKind = "local" | "package" | "unknown";
|
|
3
3
|
export type SymbolKind = "function" | "class" | "method" | "type" | "interface" | "export" | "import";
|
|
4
|
-
export type RelationshipType = "import" | "contains" | "mentions" | "belongs-to-domain" | "stale-reference" | "moved-from";
|
|
4
|
+
export type RelationshipType = "import" | "contains" | "symbol-contains" | "calls" | "mentions" | "belongs-to-domain" | "stale-reference" | "moved-from";
|
|
5
5
|
export interface RepositoryFile {
|
|
6
6
|
id: string;
|
|
7
7
|
path: string;
|