codemap-ai 0.2.0 → 3.0.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.
- package/LICENSE +21 -0
- package/README.md +257 -94
- package/dist/chunk-GX7ZMBC2.js +730 -0
- package/dist/chunk-GX7ZMBC2.js.map +1 -0
- package/dist/chunk-XNA2HNUR.js +733 -0
- package/dist/chunk-XNA2HNUR.js.map +1 -0
- package/dist/cli.js +636 -688
- package/dist/cli.js.map +1 -1
- package/dist/flow-server-QO7IWYLE.js +246 -0
- package/dist/flow-server-QO7IWYLE.js.map +1 -0
- package/dist/index.d.ts +167 -190
- package/dist/index.js +597 -485
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +144 -265
- package/dist/mcp-server.js.map +1 -1
- package/package.json +13 -17
- package/dist/chunk-5ONPBEWJ.js +0 -350
- package/dist/chunk-5ONPBEWJ.js.map +0 -1
- package/dist/chunk-FLUWKIEM.js +0 -347
- package/dist/chunk-FLUWKIEM.js.map +0 -1
- package/dist/server-3W7ZN3LX.js +0 -103
- package/dist/server-3W7ZN3LX.js.map +0 -1
- package/dist/server-TBIVIIUJ.js +0 -367
- package/dist/server-TBIVIIUJ.js.map +0 -1
- package/web/index.html +0 -639
package/dist/index.js
CHANGED
|
@@ -1,26 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./chunk-
|
|
2
|
+
FlowStorage
|
|
3
|
+
} from "./chunk-GX7ZMBC2.js";
|
|
4
4
|
|
|
5
|
-
// src/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
exclude: [
|
|
9
|
-
"**/node_modules/**",
|
|
10
|
-
"**/dist/**",
|
|
11
|
-
"**/build/**",
|
|
12
|
-
"**/.git/**",
|
|
13
|
-
"**/venv/**",
|
|
14
|
-
"**/__pycache__/**",
|
|
15
|
-
"**/.next/**"
|
|
16
|
-
],
|
|
17
|
-
languages: ["typescript", "javascript", "python"]
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
// src/graph/builder.ts
|
|
21
|
-
import { readFileSync } from "fs";
|
|
5
|
+
// src/flow/builder.ts
|
|
6
|
+
import { readFileSync, statSync } from "fs";
|
|
7
|
+
import { createHash } from "crypto";
|
|
22
8
|
import { glob } from "glob";
|
|
23
|
-
import { relative,
|
|
9
|
+
import { resolve, relative, extname } from "path";
|
|
24
10
|
|
|
25
11
|
// src/parsers/base.ts
|
|
26
12
|
var BaseParser = class {
|
|
@@ -108,13 +94,40 @@ var TypeScriptParser = class extends BaseParser {
|
|
|
108
94
|
}
|
|
109
95
|
parse(filePath, content) {
|
|
110
96
|
const analysis = this.createEmptyAnalysis(filePath);
|
|
97
|
+
const sourceCode = typeof content === "string" ? content : String(content);
|
|
98
|
+
if (sourceCode.length > 5e5) {
|
|
99
|
+
const lines2 = sourceCode.split("\n");
|
|
100
|
+
const fileNode2 = this.createNode(
|
|
101
|
+
"file",
|
|
102
|
+
filePath.split("/").pop() || filePath,
|
|
103
|
+
filePath,
|
|
104
|
+
1,
|
|
105
|
+
lines2.length
|
|
106
|
+
);
|
|
107
|
+
analysis.nodes.push(fileNode2);
|
|
108
|
+
return analysis;
|
|
109
|
+
}
|
|
111
110
|
if (filePath.endsWith(".tsx")) {
|
|
112
111
|
this.parser.setLanguage(TypeScript.tsx);
|
|
113
112
|
} else {
|
|
114
113
|
this.parser.setLanguage(TypeScript.typescript);
|
|
115
114
|
}
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
let tree;
|
|
116
|
+
try {
|
|
117
|
+
tree = this.parser.parse(sourceCode);
|
|
118
|
+
} catch {
|
|
119
|
+
const lines2 = sourceCode.split("\n");
|
|
120
|
+
const fileNode2 = this.createNode(
|
|
121
|
+
"file",
|
|
122
|
+
filePath.split("/").pop() || filePath,
|
|
123
|
+
filePath,
|
|
124
|
+
1,
|
|
125
|
+
lines2.length
|
|
126
|
+
);
|
|
127
|
+
analysis.nodes.push(fileNode2);
|
|
128
|
+
return analysis;
|
|
129
|
+
}
|
|
130
|
+
const lines = sourceCode.split("\n");
|
|
118
131
|
const fileNode = this.createNode(
|
|
119
132
|
"file",
|
|
120
133
|
filePath.split("/").pop() || filePath,
|
|
@@ -357,7 +370,13 @@ var JavaScriptParser = class extends TypeScriptParser {
|
|
|
357
370
|
this.parser.setLanguage(JavaScript);
|
|
358
371
|
}
|
|
359
372
|
parse(filePath, content) {
|
|
360
|
-
const
|
|
373
|
+
const sourceCode = typeof content === "string" ? content : String(content);
|
|
374
|
+
if (filePath.endsWith(".jsx")) {
|
|
375
|
+
this.parser.setLanguage(TypeScript.tsx);
|
|
376
|
+
} else {
|
|
377
|
+
this.parser.setLanguage(JavaScript);
|
|
378
|
+
}
|
|
379
|
+
const analysis = super.parse(filePath, sourceCode);
|
|
361
380
|
analysis.language = "javascript";
|
|
362
381
|
for (const node of analysis.nodes) {
|
|
363
382
|
node.language = "javascript";
|
|
@@ -380,8 +399,41 @@ var PythonParser = class extends BaseParser {
|
|
|
380
399
|
}
|
|
381
400
|
parse(filePath, content) {
|
|
382
401
|
const analysis = this.createEmptyAnalysis(filePath);
|
|
383
|
-
const
|
|
384
|
-
|
|
402
|
+
const sourceCode = typeof content === "string" ? content : String(content);
|
|
403
|
+
if (sourceCode.length > 5e5) {
|
|
404
|
+
const lines2 = sourceCode.split("\n");
|
|
405
|
+
const fileNode2 = this.createNode(
|
|
406
|
+
"file",
|
|
407
|
+
filePath.split("/").pop() || filePath,
|
|
408
|
+
filePath,
|
|
409
|
+
1,
|
|
410
|
+
lines2.length
|
|
411
|
+
);
|
|
412
|
+
analysis.nodes.push(fileNode2);
|
|
413
|
+
return analysis;
|
|
414
|
+
}
|
|
415
|
+
let tree;
|
|
416
|
+
try {
|
|
417
|
+
tree = this.parser.parse((index, position) => {
|
|
418
|
+
if (index >= sourceCode.length) {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
const endIndex = Math.min(index + 1024, sourceCode.length);
|
|
422
|
+
return sourceCode.substring(index, endIndex);
|
|
423
|
+
});
|
|
424
|
+
} catch (error) {
|
|
425
|
+
const lines2 = sourceCode.split("\n");
|
|
426
|
+
const fileNode2 = this.createNode(
|
|
427
|
+
"file",
|
|
428
|
+
filePath.split("/").pop() || filePath,
|
|
429
|
+
filePath,
|
|
430
|
+
1,
|
|
431
|
+
lines2.length
|
|
432
|
+
);
|
|
433
|
+
analysis.nodes.push(fileNode2);
|
|
434
|
+
return analysis;
|
|
435
|
+
}
|
|
436
|
+
const lines = sourceCode.split("\n");
|
|
385
437
|
const fileNode = this.createNode(
|
|
386
438
|
"file",
|
|
387
439
|
filePath.split("/").pop() || filePath,
|
|
@@ -486,17 +538,66 @@ var PythonParser = class extends BaseParser {
|
|
|
486
538
|
if (!nameNode) return;
|
|
487
539
|
const name = nameNode.text;
|
|
488
540
|
const decorators = [];
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const
|
|
492
|
-
(
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
541
|
+
const decoratorDetails = [];
|
|
542
|
+
if (node.parent?.type === "decorated_definition") {
|
|
543
|
+
for (const sibling of node.parent.children) {
|
|
544
|
+
if (sibling.type === "decorator") {
|
|
545
|
+
const decoratorCall = sibling.children.find((c) => c.type === "call");
|
|
546
|
+
const decoratorAttr = sibling.children.find((c) => c.type === "attribute" || c.type === "identifier");
|
|
547
|
+
if (decoratorCall) {
|
|
548
|
+
const funcName = decoratorCall.children.find((c) => c.type === "attribute" || c.type === "identifier");
|
|
549
|
+
if (funcName) {
|
|
550
|
+
const fullDecorator = funcName.text;
|
|
551
|
+
decorators.push(fullDecorator);
|
|
552
|
+
const args = [];
|
|
553
|
+
const kwargs = {};
|
|
554
|
+
const argList = decoratorCall.children.find((c) => c.type === "argument_list");
|
|
555
|
+
if (argList) {
|
|
556
|
+
for (const arg of argList.children) {
|
|
557
|
+
if (arg.type === "string") {
|
|
558
|
+
const stringContent = arg.children.find((c) => c.type === "string_content");
|
|
559
|
+
const argText = stringContent ? stringContent.text : arg.text.replace(/^["']|["']$/g, "");
|
|
560
|
+
args.push(argText);
|
|
561
|
+
} else if (arg.type === "keyword_argument") {
|
|
562
|
+
const keyNode = arg.childForFieldName("name");
|
|
563
|
+
const valueNode = arg.childForFieldName("value");
|
|
564
|
+
if (keyNode && valueNode) {
|
|
565
|
+
const key = keyNode.text;
|
|
566
|
+
let value = valueNode.text;
|
|
567
|
+
if (valueNode.type === "list") {
|
|
568
|
+
const items = [];
|
|
569
|
+
for (const child of valueNode.children) {
|
|
570
|
+
if (child.type === "string") {
|
|
571
|
+
const stringContent = child.children.find((c) => c.type === "string_content");
|
|
572
|
+
items.push(stringContent ? stringContent.text : child.text.replace(/^["']|["']$/g, ""));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
value = items.length > 0 ? items.join(",") : value;
|
|
576
|
+
}
|
|
577
|
+
kwargs[key] = value;
|
|
578
|
+
}
|
|
579
|
+
} else if (arg.type === "identifier") {
|
|
580
|
+
args.push(arg.text);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
decoratorDetails.push({ name: fullDecorator, args, kwargs });
|
|
585
|
+
}
|
|
586
|
+
} else if (decoratorAttr) {
|
|
587
|
+
const fullDecorator = decoratorAttr.text;
|
|
588
|
+
decorators.push(fullDecorator);
|
|
589
|
+
decoratorDetails.push({ name: fullDecorator, args: [] });
|
|
590
|
+
}
|
|
591
|
+
}
|
|
496
592
|
}
|
|
497
|
-
currentNode = currentNode.previousSibling;
|
|
498
593
|
}
|
|
499
594
|
const isAsync = node.children.some((c) => c.type === "async");
|
|
595
|
+
let returnType;
|
|
596
|
+
const returnTypeNode = node.childForFieldName("return_type");
|
|
597
|
+
if (returnTypeNode) {
|
|
598
|
+
returnType = returnTypeNode.text;
|
|
599
|
+
returnType = returnType.replace(/["']/g, "").split("[")[0].trim();
|
|
600
|
+
}
|
|
500
601
|
const funcNode = this.createNode(
|
|
501
602
|
"function",
|
|
502
603
|
name,
|
|
@@ -506,12 +607,20 @@ var PythonParser = class extends BaseParser {
|
|
|
506
607
|
{
|
|
507
608
|
async: isAsync,
|
|
508
609
|
decorators,
|
|
610
|
+
decoratorDetails: JSON.stringify(decoratorDetails),
|
|
509
611
|
isPrivate: name.startsWith("_"),
|
|
510
|
-
isDunder: name.startsWith("__") && name.endsWith("__")
|
|
612
|
+
isDunder: name.startsWith("__") && name.endsWith("__"),
|
|
613
|
+
returnType
|
|
511
614
|
}
|
|
512
615
|
);
|
|
513
616
|
analysis.nodes.push(funcNode);
|
|
514
617
|
analysis.edges.push(this.createEdge("contains", parentId, funcNode.id));
|
|
618
|
+
this.detectFastAPIRoute(decoratorDetails, funcNode, analysis);
|
|
619
|
+
const parameters = node.childForFieldName("parameters");
|
|
620
|
+
if (parameters) {
|
|
621
|
+
this.detectDependsPattern(parameters, funcNode, analysis);
|
|
622
|
+
this.extractParameterTypes(parameters, funcNode.id, filePath, analysis);
|
|
623
|
+
}
|
|
515
624
|
const body = node.childForFieldName("body");
|
|
516
625
|
if (body) {
|
|
517
626
|
this.parseBodyForCalls(body, filePath, analysis, funcNode.id);
|
|
@@ -524,6 +633,95 @@ var PythonParser = class extends BaseParser {
|
|
|
524
633
|
});
|
|
525
634
|
}
|
|
526
635
|
}
|
|
636
|
+
/**
|
|
637
|
+
* Detect FastAPI route decorators like @router.post("/chat")
|
|
638
|
+
*/
|
|
639
|
+
detectFastAPIRoute(decoratorDetails, funcNode, analysis) {
|
|
640
|
+
for (const dec of decoratorDetails) {
|
|
641
|
+
const routeMatch = dec.name.match(/^(router|app)\.(get|post|put|patch|delete|options|head)$/);
|
|
642
|
+
if (routeMatch && dec.args.length > 0) {
|
|
643
|
+
const method = routeMatch[2].toUpperCase();
|
|
644
|
+
const path = dec.args[0];
|
|
645
|
+
if (!funcNode.metadata) funcNode.metadata = {};
|
|
646
|
+
funcNode.metadata.httpEndpoint = {
|
|
647
|
+
method,
|
|
648
|
+
path
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Detect Depends() pattern in function parameters
|
|
655
|
+
* Example: user: User = Depends(authenticate_user)
|
|
656
|
+
*/
|
|
657
|
+
detectDependsPattern(parameters, funcNode, analysis) {
|
|
658
|
+
for (const param of parameters.children) {
|
|
659
|
+
if (param.type === "typed_parameter" || param.type === "default_parameter") {
|
|
660
|
+
const defaultValue = param.children.find((c) => c.type === "call");
|
|
661
|
+
if (defaultValue) {
|
|
662
|
+
const funcCall = defaultValue.childForFieldName("function");
|
|
663
|
+
if (funcCall && funcCall.text === "Depends") {
|
|
664
|
+
const argList = defaultValue.childForFieldName("arguments");
|
|
665
|
+
if (argList) {
|
|
666
|
+
for (const arg of argList.children) {
|
|
667
|
+
if (arg.type === "identifier") {
|
|
668
|
+
const dependencyName = arg.text;
|
|
669
|
+
const paramName = param.children.find(
|
|
670
|
+
(c) => c.type === "identifier"
|
|
671
|
+
)?.text;
|
|
672
|
+
if (!funcNode.metadata) funcNode.metadata = {};
|
|
673
|
+
if (!funcNode.metadata.depends) funcNode.metadata.depends = [];
|
|
674
|
+
funcNode.metadata.depends.push({
|
|
675
|
+
parameterName: paramName,
|
|
676
|
+
dependencyName,
|
|
677
|
+
line: param.startPosition.row + 1
|
|
678
|
+
});
|
|
679
|
+
analysis.edges.push(
|
|
680
|
+
this.createEdge("depends", funcNode.id, `ref:${dependencyName}`, {
|
|
681
|
+
unresolvedName: dependencyName,
|
|
682
|
+
parameterName: paramName,
|
|
683
|
+
line: param.startPosition.row + 1
|
|
684
|
+
})
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Extract parameter type hints and register as variable types
|
|
696
|
+
* Example: def process(user: User, config: Config)
|
|
697
|
+
*/
|
|
698
|
+
extractParameterTypes(parameters, scopeId, filePath, analysis) {
|
|
699
|
+
for (const param of parameters.children) {
|
|
700
|
+
if (param.type === "typed_parameter") {
|
|
701
|
+
const nameNode = param.children.find((c) => c.type === "identifier");
|
|
702
|
+
if (!nameNode) continue;
|
|
703
|
+
const paramName = nameNode.text;
|
|
704
|
+
if (paramName === "self" || paramName === "cls") continue;
|
|
705
|
+
const typeNode = param.children.find((c) => c.type === "type");
|
|
706
|
+
if (!typeNode) continue;
|
|
707
|
+
let typeName = typeNode.text;
|
|
708
|
+
typeName = typeName.replace(/["']/g, "").trim();
|
|
709
|
+
const genericMatch = typeName.match(/(?:Optional|List|Dict|Set)\[([^\]]+)\]/);
|
|
710
|
+
if (genericMatch) {
|
|
711
|
+
typeName = genericMatch[1].trim();
|
|
712
|
+
}
|
|
713
|
+
if (!analysis.variableTypes) {
|
|
714
|
+
analysis.variableTypes = [];
|
|
715
|
+
}
|
|
716
|
+
analysis.variableTypes.push({
|
|
717
|
+
variableName: paramName,
|
|
718
|
+
typeName,
|
|
719
|
+
scopeId,
|
|
720
|
+
line: param.startPosition.row + 1
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
527
725
|
handleClass(node, filePath, analysis, parentId) {
|
|
528
726
|
const nameNode = node.childForFieldName("name");
|
|
529
727
|
if (!nameNode) return;
|
|
@@ -562,6 +760,11 @@ var PythonParser = class extends BaseParser {
|
|
|
562
760
|
for (const child of body.children) {
|
|
563
761
|
if (child.type === "function_definition") {
|
|
564
762
|
this.handleMethod(child, filePath, analysis, classNode.id);
|
|
763
|
+
} else if (child.type === "decorated_definition") {
|
|
764
|
+
const funcNode = child.children.find((c) => c.type === "function_definition");
|
|
765
|
+
if (funcNode) {
|
|
766
|
+
this.handleMethod(funcNode, filePath, analysis, classNode.id);
|
|
767
|
+
}
|
|
565
768
|
}
|
|
566
769
|
}
|
|
567
770
|
}
|
|
@@ -578,15 +781,15 @@ var PythonParser = class extends BaseParser {
|
|
|
578
781
|
if (!nameNode) return;
|
|
579
782
|
const name = nameNode.text;
|
|
580
783
|
const decorators = [];
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
784
|
+
if (node.parent?.type === "decorated_definition") {
|
|
785
|
+
for (const sibling of node.parent.children) {
|
|
786
|
+
if (sibling.type === "decorator") {
|
|
787
|
+
const decoratorAttr = sibling.children.find((c) => c.type === "attribute" || c.type === "identifier");
|
|
788
|
+
if (decoratorAttr) {
|
|
789
|
+
decorators.push(decoratorAttr.text);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
588
792
|
}
|
|
589
|
-
currentNode = currentNode.previousSibling;
|
|
590
793
|
}
|
|
591
794
|
const isStatic = decorators.includes("staticmethod");
|
|
592
795
|
const isClassMethod = decorators.includes("classmethod");
|
|
@@ -624,19 +827,63 @@ var PythonParser = class extends BaseParser {
|
|
|
624
827
|
} else if (funcNode.type === "attribute") {
|
|
625
828
|
calledName = funcNode.text;
|
|
626
829
|
}
|
|
627
|
-
if (calledName
|
|
830
|
+
if (calledName) {
|
|
628
831
|
analysis.edges.push(
|
|
629
832
|
this.createEdge("calls", parentId, `ref:${calledName}`, {
|
|
630
833
|
unresolvedName: calledName,
|
|
631
834
|
line: node.startPosition.row + 1
|
|
632
835
|
})
|
|
633
836
|
);
|
|
837
|
+
this.detectDatabaseOperation(calledName, node, parentId, analysis);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Detect database operations (MongoDB, SQL, etc.)
|
|
842
|
+
* Examples: collection.find_one(), db.users.insert_one(), collection.update_many()
|
|
843
|
+
*/
|
|
844
|
+
detectDatabaseOperation(calledName, node, parentId, analysis) {
|
|
845
|
+
const mongoOps = [
|
|
846
|
+
"find_one",
|
|
847
|
+
"find",
|
|
848
|
+
"insert_one",
|
|
849
|
+
"insert_many",
|
|
850
|
+
"update_one",
|
|
851
|
+
"update_many",
|
|
852
|
+
"delete_one",
|
|
853
|
+
"delete_many",
|
|
854
|
+
"aggregate",
|
|
855
|
+
"count_documents",
|
|
856
|
+
"replace_one"
|
|
857
|
+
];
|
|
858
|
+
const parts = calledName.split(".");
|
|
859
|
+
const operation = parts[parts.length - 1];
|
|
860
|
+
if (mongoOps.includes(operation)) {
|
|
861
|
+
let collection = "unknown";
|
|
862
|
+
if (parts.length >= 2) {
|
|
863
|
+
collection = parts[parts.length - 2];
|
|
864
|
+
}
|
|
865
|
+
if (!analysis.databaseOperations) {
|
|
866
|
+
analysis.databaseOperations = [];
|
|
867
|
+
}
|
|
868
|
+
analysis.databaseOperations.push({
|
|
869
|
+
nodeId: parentId,
|
|
870
|
+
operation,
|
|
871
|
+
collection,
|
|
872
|
+
databaseType: "mongodb",
|
|
873
|
+
line: node.startPosition.row + 1
|
|
874
|
+
});
|
|
634
875
|
}
|
|
635
876
|
}
|
|
636
877
|
parseBodyForCalls(body, filePath, analysis, parentId) {
|
|
637
878
|
const walkForCalls = (node) => {
|
|
638
879
|
if (node.type === "call") {
|
|
639
880
|
this.handleCall(node, filePath, analysis, parentId);
|
|
881
|
+
} else if (node.type === "import_statement") {
|
|
882
|
+
this.handleImport(node, analysis);
|
|
883
|
+
} else if (node.type === "import_from_statement") {
|
|
884
|
+
this.handleFromImport(node, analysis);
|
|
885
|
+
} else if (node.type === "assignment") {
|
|
886
|
+
this.handleAssignment(node, filePath, analysis, parentId);
|
|
640
887
|
}
|
|
641
888
|
for (const child of node.children) {
|
|
642
889
|
walkForCalls(child);
|
|
@@ -644,6 +891,56 @@ var PythonParser = class extends BaseParser {
|
|
|
644
891
|
};
|
|
645
892
|
walkForCalls(body);
|
|
646
893
|
}
|
|
894
|
+
/**
|
|
895
|
+
* Handle variable assignments to track types
|
|
896
|
+
* Examples:
|
|
897
|
+
* user = get_user(id) # Track if get_user has type hint
|
|
898
|
+
* handler = UserHandler(db) # Track: handler -> UserHandler
|
|
899
|
+
* org_handler = OrgHandler.get_instance() # Track: org_handler -> OrgHandler
|
|
900
|
+
*/
|
|
901
|
+
handleAssignment(node, filePath, analysis, scopeId) {
|
|
902
|
+
const leftNode = node.childForFieldName("left");
|
|
903
|
+
if (!leftNode || leftNode.type !== "identifier") return;
|
|
904
|
+
const variableName = leftNode.text;
|
|
905
|
+
const rightNode = node.childForFieldName("right");
|
|
906
|
+
if (!rightNode) return;
|
|
907
|
+
let typeName = null;
|
|
908
|
+
if (rightNode.type === "call") {
|
|
909
|
+
const funcNode = rightNode.childForFieldName("function");
|
|
910
|
+
if (funcNode) {
|
|
911
|
+
if (funcNode.type === "identifier") {
|
|
912
|
+
const funcName = funcNode.text;
|
|
913
|
+
if (funcName[0] === funcName[0].toUpperCase()) {
|
|
914
|
+
typeName = funcName;
|
|
915
|
+
} else {
|
|
916
|
+
typeName = `@call:${funcName}`;
|
|
917
|
+
}
|
|
918
|
+
} else if (funcNode.type === "attribute") {
|
|
919
|
+
const parts = funcNode.text.split(".");
|
|
920
|
+
if (parts.length >= 2) {
|
|
921
|
+
const className = parts[0];
|
|
922
|
+
const methodName = parts[1];
|
|
923
|
+
if (className[0] === className[0].toUpperCase()) {
|
|
924
|
+
typeName = className;
|
|
925
|
+
} else {
|
|
926
|
+
typeName = `@call:${funcNode.text}`;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
if (typeName && !analysis.variableTypes) {
|
|
933
|
+
analysis.variableTypes = [];
|
|
934
|
+
}
|
|
935
|
+
if (typeName) {
|
|
936
|
+
analysis.variableTypes.push({
|
|
937
|
+
variableName,
|
|
938
|
+
typeName,
|
|
939
|
+
scopeId,
|
|
940
|
+
line: node.startPosition.row + 1
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
}
|
|
647
944
|
};
|
|
648
945
|
|
|
649
946
|
// src/parsers/index.ts
|
|
@@ -654,11 +951,14 @@ function registerAllParsers() {
|
|
|
654
951
|
}
|
|
655
952
|
registerAllParsers();
|
|
656
953
|
|
|
657
|
-
// src/
|
|
658
|
-
var
|
|
954
|
+
// src/flow/builder.ts
|
|
955
|
+
var FlowBuilder = class {
|
|
659
956
|
storage;
|
|
660
957
|
config;
|
|
661
958
|
onProgress;
|
|
959
|
+
moduleMap = /* @__PURE__ */ new Map();
|
|
960
|
+
// file path → exports
|
|
961
|
+
errors = [];
|
|
662
962
|
constructor(storage, config) {
|
|
663
963
|
this.storage = storage;
|
|
664
964
|
this.config = config;
|
|
@@ -666,498 +966,310 @@ var GraphBuilder = class {
|
|
|
666
966
|
setProgressCallback(callback) {
|
|
667
967
|
this.onProgress = callback;
|
|
668
968
|
}
|
|
669
|
-
async
|
|
969
|
+
async build() {
|
|
670
970
|
const rootPath = resolve(this.config.rootPath);
|
|
671
|
-
this.emitProgress({
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
971
|
+
this.emitProgress({
|
|
972
|
+
phase: "discovering",
|
|
973
|
+
total: 0,
|
|
974
|
+
current: 0,
|
|
975
|
+
currentFile: ""
|
|
976
|
+
});
|
|
977
|
+
const allFiles = await this.discoverFiles(rootPath);
|
|
978
|
+
const filesToIndex = [];
|
|
979
|
+
let skipped = 0;
|
|
980
|
+
for (const filePath of allFiles) {
|
|
981
|
+
const content = readFileSync(filePath, "utf-8");
|
|
982
|
+
const hash = this.hashContent(content);
|
|
983
|
+
if (!this.config.forceReindex) {
|
|
984
|
+
const existingHash = this.storage.getFileHash(filePath);
|
|
985
|
+
if (existingHash === hash) {
|
|
986
|
+
skipped++;
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
filesToIndex.push({ path: filePath, hash });
|
|
991
|
+
}
|
|
992
|
+
let indexed = 0;
|
|
993
|
+
for (const file of filesToIndex) {
|
|
677
994
|
this.emitProgress({
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
995
|
+
phase: "parsing",
|
|
996
|
+
total: filesToIndex.length,
|
|
997
|
+
current: indexed + 1,
|
|
998
|
+
currentFile: relative(rootPath, file.path)
|
|
682
999
|
});
|
|
683
1000
|
try {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
fileAnalyses.push(analysis);
|
|
687
|
-
this.storage.insertFileAnalysis(analysis);
|
|
688
|
-
}
|
|
1001
|
+
await this.parseAndStore(file.path, file.hash, rootPath);
|
|
1002
|
+
indexed++;
|
|
689
1003
|
} catch (error) {
|
|
690
|
-
|
|
1004
|
+
this.errors.push(`${relative(rootPath, file.path)}: ${error}`);
|
|
691
1005
|
}
|
|
692
1006
|
}
|
|
693
1007
|
this.emitProgress({
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1008
|
+
phase: "resolving",
|
|
1009
|
+
total: filesToIndex.length,
|
|
1010
|
+
current: filesToIndex.length,
|
|
1011
|
+
currentFile: ""
|
|
698
1012
|
});
|
|
699
|
-
this.
|
|
700
|
-
const languages = {};
|
|
701
|
-
for (const analysis of fileAnalyses) {
|
|
702
|
-
languages[analysis.language] = (languages[analysis.language] || 0) + 1;
|
|
703
|
-
}
|
|
1013
|
+
this.resolveAllCalls();
|
|
704
1014
|
const stats = this.storage.getStats();
|
|
705
|
-
this.storage.setMeta("rootPath", rootPath);
|
|
706
|
-
this.storage.setMeta("analyzedAt", (/* @__PURE__ */ new Date()).toISOString());
|
|
707
|
-
this.storage.setMeta("totalFiles", String(files.length));
|
|
708
1015
|
this.emitProgress({
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
1016
|
+
phase: "complete",
|
|
1017
|
+
total: filesToIndex.length,
|
|
1018
|
+
current: filesToIndex.length,
|
|
1019
|
+
currentFile: ""
|
|
713
1020
|
});
|
|
714
1021
|
return {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
analyzedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1022
|
+
indexed,
|
|
1023
|
+
skipped,
|
|
1024
|
+
resolved: stats.resolvedCalls,
|
|
1025
|
+
unresolved: stats.unresolvedCalls,
|
|
1026
|
+
errors: this.errors
|
|
721
1027
|
};
|
|
722
1028
|
}
|
|
723
1029
|
async discoverFiles(rootPath) {
|
|
724
|
-
const patterns = this.config.include;
|
|
725
|
-
const ignorePatterns = this.config.exclude;
|
|
726
1030
|
const allFiles = [];
|
|
727
|
-
for (const pattern of
|
|
1031
|
+
for (const pattern of this.config.include) {
|
|
728
1032
|
const matches = await glob(pattern, {
|
|
729
1033
|
cwd: rootPath,
|
|
730
1034
|
absolute: true,
|
|
731
|
-
ignore:
|
|
1035
|
+
ignore: this.config.exclude,
|
|
732
1036
|
nodir: true
|
|
733
1037
|
});
|
|
734
1038
|
allFiles.push(...matches);
|
|
735
1039
|
}
|
|
736
|
-
const supportedExts = new Set(
|
|
1040
|
+
const supportedExts = /* @__PURE__ */ new Set([".py", ".ts", ".tsx", ".js", ".jsx"]);
|
|
737
1041
|
const uniqueFiles = [...new Set(allFiles)].filter((f) => {
|
|
738
|
-
const ext =
|
|
739
|
-
|
|
1042
|
+
const ext = extname(f);
|
|
1043
|
+
if (!supportedExts.has(ext)) return false;
|
|
1044
|
+
try {
|
|
1045
|
+
const stats = statSync(f);
|
|
1046
|
+
if (stats.size > 2e5) return false;
|
|
1047
|
+
} catch {
|
|
1048
|
+
return false;
|
|
1049
|
+
}
|
|
1050
|
+
return true;
|
|
740
1051
|
});
|
|
741
1052
|
return uniqueFiles.sort();
|
|
742
1053
|
}
|
|
743
|
-
|
|
744
|
-
|
|
1054
|
+
hashContent(content) {
|
|
1055
|
+
return createHash("sha256").update(content).digest("hex").substring(0, 16);
|
|
1056
|
+
}
|
|
1057
|
+
async parseAndStore(filePath, hash, rootPath) {
|
|
1058
|
+
const language = this.getLanguage(filePath);
|
|
1059
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1060
|
+
const parser = parserRegistry.getByLanguage(language);
|
|
745
1061
|
if (!parser) {
|
|
746
|
-
|
|
1062
|
+
throw new Error(`No parser found for language: ${language}`);
|
|
747
1063
|
}
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1064
|
+
const analysis = parser.parse(filePath, content);
|
|
1065
|
+
this.storage.deleteFileData(filePath);
|
|
1066
|
+
const fileExports = {};
|
|
1067
|
+
for (const node of analysis.nodes) {
|
|
1068
|
+
const nodeHash = this.hashContent(`${node.name}:${node.startLine}:${node.endLine}`);
|
|
1069
|
+
const nodeWithHash = { ...node, hash: nodeHash };
|
|
1070
|
+
this.storage.insertNode(nodeWithHash);
|
|
1071
|
+
if (node.type === "function" || node.type === "class") {
|
|
1072
|
+
fileExports[node.name] = node.id;
|
|
1073
|
+
this.storage.registerExport(filePath, node.name, node.id);
|
|
1074
|
+
}
|
|
759
1075
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
1076
|
+
this.moduleMap.set(filePath, fileExports);
|
|
1077
|
+
for (const edge of analysis.edges) {
|
|
1078
|
+
this.storage.insertEdge(edge);
|
|
1079
|
+
}
|
|
1080
|
+
for (const imp of analysis.imports) {
|
|
1081
|
+
for (const spec of imp.specifiers) {
|
|
1082
|
+
this.storage.registerImport(filePath, spec, imp.source, imp.line);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
if (analysis.variableTypes) {
|
|
1086
|
+
for (const varType of analysis.variableTypes) {
|
|
1087
|
+
this.storage.registerVariableType(
|
|
1088
|
+
varType.variableName,
|
|
1089
|
+
varType.typeName,
|
|
1090
|
+
varType.scopeId,
|
|
1091
|
+
filePath,
|
|
1092
|
+
varType.line
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
if (analysis.databaseOperations) {
|
|
1097
|
+
for (const dbOp of analysis.databaseOperations) {
|
|
1098
|
+
this.storage.insertDatabaseOperation({
|
|
1099
|
+
id: `${dbOp.nodeId}:db:${dbOp.line}`,
|
|
1100
|
+
node_id: dbOp.nodeId,
|
|
1101
|
+
database_type: dbOp.databaseType,
|
|
1102
|
+
operation: dbOp.operation,
|
|
1103
|
+
collection: dbOp.collection,
|
|
1104
|
+
line: dbOp.line
|
|
1105
|
+
});
|
|
766
1106
|
}
|
|
767
1107
|
}
|
|
1108
|
+
this.storage.upsertFile(filePath, language, hash);
|
|
1109
|
+
this.processFrameworkPatterns(analysis.nodes, filePath);
|
|
768
1110
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
1111
|
+
/**
|
|
1112
|
+
* Process framework-specific patterns (FastAPI routes, Depends, etc.)
|
|
1113
|
+
*/
|
|
1114
|
+
processFrameworkPatterns(nodes, filePath) {
|
|
1115
|
+
for (const node of nodes) {
|
|
1116
|
+
if (node.type === "function" && node.metadata) {
|
|
1117
|
+
const httpEndpoint = node.metadata.httpEndpoint;
|
|
1118
|
+
if (httpEndpoint) {
|
|
1119
|
+
const endpointId = `${httpEndpoint.method}:${httpEndpoint.path}`;
|
|
1120
|
+
this.storage.insertHttpEndpoint({
|
|
1121
|
+
id: endpointId,
|
|
1122
|
+
method: httpEndpoint.method,
|
|
1123
|
+
path: httpEndpoint.path,
|
|
1124
|
+
handler_node_id: node.id,
|
|
1125
|
+
container_id: void 0,
|
|
1126
|
+
// Will be set when docker-compose is parsed
|
|
1127
|
+
metadata: {
|
|
1128
|
+
filePath,
|
|
1129
|
+
functionName: node.name,
|
|
1130
|
+
line: node.startLine
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
const depends = node.metadata.depends;
|
|
1135
|
+
if (depends) {
|
|
1136
|
+
for (const dep of depends) {
|
|
1137
|
+
const depId = `${node.id}:depends:${dep.parameterName}`;
|
|
1138
|
+
this.storage.insertFrameworkDependency({
|
|
1139
|
+
id: depId,
|
|
1140
|
+
source_node_id: node.id,
|
|
1141
|
+
target_node_id: void 0,
|
|
1142
|
+
// Will be resolved later
|
|
1143
|
+
framework: "fastapi",
|
|
1144
|
+
pattern: "depends",
|
|
1145
|
+
parameter_name: dep.parameterName,
|
|
1146
|
+
line: dep.line,
|
|
1147
|
+
unresolved_name: dep.dependencyName,
|
|
1148
|
+
metadata: {
|
|
1149
|
+
filePath,
|
|
1150
|
+
functionName: node.name
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
772
1156
|
}
|
|
773
1157
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1158
|
+
resolveAllCalls() {
|
|
1159
|
+
const unresolvedEdges = this.storage.db.prepare(`SELECT * FROM edges WHERE target_id LIKE 'ref:%'`).all();
|
|
1160
|
+
for (const edge of unresolvedEdges) {
|
|
1161
|
+
const refName = edge.target_id.replace("ref:", "");
|
|
1162
|
+
const sourceNode = this.storage.getNode(edge.source_id);
|
|
1163
|
+
if (!sourceNode) continue;
|
|
1164
|
+
const resolvedId = this.resolveReference(sourceNode.filePath, refName, edge.source_id);
|
|
1165
|
+
if (resolvedId) {
|
|
1166
|
+
this.storage.insertEdge({
|
|
1167
|
+
id: edge.id,
|
|
1168
|
+
type: edge.type,
|
|
1169
|
+
sourceId: edge.source_id,
|
|
1170
|
+
targetId: resolvedId,
|
|
1171
|
+
metadata: edge.metadata ? JSON.parse(edge.metadata) : void 0
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
this.resolveFrameworkDependencies();
|
|
785
1176
|
}
|
|
786
1177
|
/**
|
|
787
|
-
*
|
|
1178
|
+
* Resolve framework dependencies (e.g., FastAPI Depends())
|
|
788
1179
|
*/
|
|
789
|
-
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
1180
|
+
resolveFrameworkDependencies() {
|
|
1181
|
+
const unresolvedDeps = this.storage.getAllUnresolvedDependencies();
|
|
1182
|
+
for (const dep of unresolvedDeps) {
|
|
1183
|
+
const sourceNode = this.storage.getNode(dep.source_node_id);
|
|
1184
|
+
if (!sourceNode) continue;
|
|
1185
|
+
const resolvedId = this.resolveReference(sourceNode.filePath, dep.unresolved_name);
|
|
1186
|
+
if (resolvedId) {
|
|
1187
|
+
this.storage.updateFrameworkDependencyTarget(dep.id, resolvedId);
|
|
1188
|
+
this.storage.insertEdge({
|
|
1189
|
+
id: `${dep.id}:edge`,
|
|
1190
|
+
type: "calls",
|
|
1191
|
+
sourceId: dep.source_node_id,
|
|
1192
|
+
targetId: resolvedId,
|
|
1193
|
+
metadata: {
|
|
1194
|
+
framework: "fastapi",
|
|
1195
|
+
pattern: "depends",
|
|
1196
|
+
line: dep.line
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
793
1200
|
}
|
|
794
|
-
|
|
795
|
-
|
|
1201
|
+
}
|
|
1202
|
+
resolveReference(sourceFilePath, refName, scopeNodeId) {
|
|
1203
|
+
if (refName.startsWith("super().") && scopeNodeId) {
|
|
1204
|
+
const methodName = refName.replace("super().", "");
|
|
1205
|
+
const resolvedSuper = this.storage.resolveSuperMethod(methodName, scopeNodeId);
|
|
1206
|
+
if (resolvedSuper) {
|
|
1207
|
+
return resolvedSuper;
|
|
1208
|
+
}
|
|
796
1209
|
}
|
|
797
|
-
if (
|
|
798
|
-
|
|
1210
|
+
if (refName.includes(".")) {
|
|
1211
|
+
const parts = refName.split(".");
|
|
1212
|
+
if (parts.length === 2) {
|
|
1213
|
+
const [objectName, methodName] = parts;
|
|
1214
|
+
if (scopeNodeId) {
|
|
1215
|
+
const resolvedVarMethod = this.storage.resolveVariableMethod(objectName, methodName, scopeNodeId);
|
|
1216
|
+
if (resolvedVarMethod) {
|
|
1217
|
+
return resolvedVarMethod;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
const resolvedClassMethod = this.storage.resolveClassMethod(objectName, methodName, sourceFilePath);
|
|
1221
|
+
if (resolvedClassMethod) {
|
|
1222
|
+
return resolvedClassMethod;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
799
1225
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
}
|
|
805
|
-
generateArchitectureSkill() {
|
|
806
|
-
const stats = this.storage.getStats();
|
|
807
|
-
const rootPath = this.storage.getMeta("rootPath") || "";
|
|
808
|
-
const allNodes = this.storage.getNodesByType("file");
|
|
809
|
-
const directories = /* @__PURE__ */ new Map();
|
|
810
|
-
for (const node of allNodes) {
|
|
811
|
-
const relativePath = node.filePath.replace(rootPath, "").replace(/^\//, "");
|
|
812
|
-
const parts = relativePath.split("/");
|
|
813
|
-
if (parts.length > 1) {
|
|
814
|
-
const topDir = parts[0];
|
|
815
|
-
directories.set(topDir, (directories.get(topDir) || 0) + 1);
|
|
1226
|
+
const nodesInFile = this.storage.getNodesByFile(sourceFilePath);
|
|
1227
|
+
for (const node of nodesInFile) {
|
|
1228
|
+
if (node.name === refName) {
|
|
1229
|
+
return node.id;
|
|
816
1230
|
}
|
|
817
1231
|
}
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
const content = `---
|
|
822
|
-
name: architecture
|
|
823
|
-
description: Overview of ${this.config.projectName} architecture and structure
|
|
824
|
-
user-invocable: true
|
|
825
|
-
---
|
|
826
|
-
|
|
827
|
-
# ${this.config.projectName} Architecture
|
|
828
|
-
|
|
829
|
-
## Project Statistics
|
|
830
|
-
- **Total files**: ${stats.totalFiles}
|
|
831
|
-
- **Functions/Methods**: ${stats.nodesByType.function || 0} functions, ${stats.nodesByType.method || 0} methods
|
|
832
|
-
- **Classes**: ${stats.nodesByType.class || 0}
|
|
833
|
-
- **Import relationships**: ${stats.edgesByType.imports || 0}
|
|
834
|
-
- **Call relationships**: ${stats.edgesByType.calls || 0}
|
|
835
|
-
|
|
836
|
-
## Languages
|
|
837
|
-
${langList}
|
|
838
|
-
|
|
839
|
-
## Directory Structure
|
|
840
|
-
${dirList}
|
|
841
|
-
|
|
842
|
-
## Key Patterns
|
|
843
|
-
When working with this codebase:
|
|
844
|
-
1. Check the main directories above for feature organization
|
|
845
|
-
2. Use the \`/impact\` skill to understand changes
|
|
846
|
-
3. Use the \`/navigate\` skill to find specific code
|
|
847
|
-
|
|
848
|
-
## Getting Started
|
|
849
|
-
To understand a specific area, ask questions like:
|
|
850
|
-
- "What does the ${sortedDirs[0]?.[0] || "src"} directory contain?"
|
|
851
|
-
- "How is authentication handled?"
|
|
852
|
-
- "What are the main entry points?"
|
|
853
|
-
`;
|
|
854
|
-
return {
|
|
855
|
-
name: "architecture",
|
|
856
|
-
description: `Overview of ${this.config.projectName} architecture`,
|
|
857
|
-
content,
|
|
858
|
-
filePath: join2(this.config.outputDir, "architecture", "SKILL.md")
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
generatePatternsSkill() {
|
|
862
|
-
const stats = this.storage.getStats();
|
|
863
|
-
const classes = this.storage.getNodesByType("class");
|
|
864
|
-
const functions = this.storage.getNodesByType("function");
|
|
865
|
-
const classDetails = classes.map((cls) => {
|
|
866
|
-
const methods = this.storage.getEdgesFrom(cls.id).filter((e) => e.type === "contains");
|
|
867
|
-
return { ...cls, methodCount: methods.length };
|
|
868
|
-
}).sort((a, b) => b.methodCount - a.methodCount);
|
|
869
|
-
const topClasses = classDetails.slice(0, 5);
|
|
870
|
-
const classList = topClasses.map((c) => `- \`${c.name}\` (${c.methodCount} methods) - [${basename(c.filePath)}](${c.filePath}#L${c.startLine})`).join("\n");
|
|
871
|
-
const callEdges = this.storage.getEdgesByType("calls");
|
|
872
|
-
const callCounts = /* @__PURE__ */ new Map();
|
|
873
|
-
for (const edge of callEdges) {
|
|
874
|
-
const target = edge.targetId.replace("ref:", "");
|
|
875
|
-
callCounts.set(target, (callCounts.get(target) || 0) + 1);
|
|
1232
|
+
const resolvedFromImport = this.storage.resolveImport(sourceFilePath, refName);
|
|
1233
|
+
if (resolvedFromImport) {
|
|
1234
|
+
return resolvedFromImport;
|
|
876
1235
|
}
|
|
877
|
-
const
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
---
|
|
883
|
-
|
|
884
|
-
# Codebase Patterns
|
|
885
|
-
|
|
886
|
-
## Key Classes
|
|
887
|
-
These are the most substantial classes in the codebase:
|
|
888
|
-
${classList || "No classes found"}
|
|
889
|
-
|
|
890
|
-
## Most Used Functions
|
|
891
|
-
These functions are called most frequently:
|
|
892
|
-
${calledList || "No call data available"}
|
|
893
|
-
|
|
894
|
-
## Conventions Detected
|
|
895
|
-
|
|
896
|
-
### File Organization
|
|
897
|
-
${stats.nodesByType.file ? `- ${stats.totalFiles} files organized across the project` : ""}
|
|
898
|
-
${stats.languages.typescript ? "- TypeScript is used for type safety" : ""}
|
|
899
|
-
${stats.languages.python ? "- Python follows standard module patterns" : ""}
|
|
900
|
-
|
|
901
|
-
### Code Structure
|
|
902
|
-
- Functions: ${stats.nodesByType.function || 0}
|
|
903
|
-
- Classes: ${stats.nodesByType.class || 0}
|
|
904
|
-
- Methods: ${stats.nodesByType.method || 0}
|
|
905
|
-
|
|
906
|
-
## When Making Changes
|
|
907
|
-
1. Follow existing naming conventions
|
|
908
|
-
2. Check similar files for patterns
|
|
909
|
-
3. Use \`/impact\` to verify affected areas
|
|
910
|
-
`;
|
|
911
|
-
return {
|
|
912
|
-
name: "patterns",
|
|
913
|
-
description: `Common patterns in ${this.config.projectName}`,
|
|
914
|
-
content,
|
|
915
|
-
filePath: join2(this.config.outputDir, "patterns", "SKILL.md")
|
|
916
|
-
};
|
|
917
|
-
}
|
|
918
|
-
generateDependencySkill() {
|
|
919
|
-
const content = `---
|
|
920
|
-
name: dependencies
|
|
921
|
-
description: Explore dependencies and imports in ${this.config.projectName}
|
|
922
|
-
context: fork
|
|
923
|
-
agent: Explore
|
|
924
|
-
---
|
|
925
|
-
|
|
926
|
-
# Dependency Explorer
|
|
927
|
-
|
|
928
|
-
When analyzing dependencies for $ARGUMENTS:
|
|
929
|
-
|
|
930
|
-
## Steps
|
|
931
|
-
1. **Find the target file/module**
|
|
932
|
-
- Search for files matching the query
|
|
933
|
-
- Identify the main module or component
|
|
934
|
-
|
|
935
|
-
2. **Analyze imports**
|
|
936
|
-
- What does this file import?
|
|
937
|
-
- Are there circular dependencies?
|
|
938
|
-
|
|
939
|
-
3. **Analyze dependents**
|
|
940
|
-
- What files import this module?
|
|
941
|
-
- How deep is the dependency chain?
|
|
942
|
-
|
|
943
|
-
4. **Provide summary**
|
|
944
|
-
- List direct dependencies
|
|
945
|
-
- List files that depend on this
|
|
946
|
-
- Highlight any concerns (circular deps, too many dependents)
|
|
947
|
-
|
|
948
|
-
## Query the CodeMap database
|
|
949
|
-
If the codemap MCP server is available, use these queries:
|
|
950
|
-
- Get imports: Query the imports table for the file
|
|
951
|
-
- Get dependents: Find files that import this module
|
|
952
|
-
- Get call graph: Find functions that call into this module
|
|
953
|
-
|
|
954
|
-
## Output Format
|
|
955
|
-
\`\`\`
|
|
956
|
-
Dependencies for [target]:
|
|
957
|
-
|
|
958
|
-
IMPORTS (what this uses):
|
|
959
|
-
- module1 (from ./path)
|
|
960
|
-
- module2 (from package)
|
|
961
|
-
|
|
962
|
-
IMPORTED BY (what uses this):
|
|
963
|
-
- file1.ts (lines X-Y)
|
|
964
|
-
- file2.ts (lines X-Y)
|
|
965
|
-
|
|
966
|
-
DEPENDENCY DEPTH: N levels
|
|
967
|
-
\`\`\`
|
|
968
|
-
`;
|
|
969
|
-
return {
|
|
970
|
-
name: "dependencies",
|
|
971
|
-
description: `Explore dependencies in ${this.config.projectName}`,
|
|
972
|
-
content,
|
|
973
|
-
filePath: join2(this.config.outputDir, "dependencies", "SKILL.md")
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
|
-
generateImpactAnalysisSkill() {
|
|
977
|
-
const content = `---
|
|
978
|
-
name: impact
|
|
979
|
-
description: Analyze the impact of changes to a file or function
|
|
980
|
-
disable-model-invocation: true
|
|
981
|
-
---
|
|
982
|
-
|
|
983
|
-
# Impact Analysis
|
|
984
|
-
|
|
985
|
-
Analyze what would be affected by changes to $ARGUMENTS.
|
|
986
|
-
|
|
987
|
-
## Analysis Steps
|
|
988
|
-
|
|
989
|
-
1. **Identify the target**
|
|
990
|
-
- Find the file, function, or class being modified
|
|
991
|
-
- Note its current state and purpose
|
|
992
|
-
|
|
993
|
-
2. **Find direct dependents**
|
|
994
|
-
- What files import this module?
|
|
995
|
-
- What functions call this function?
|
|
996
|
-
- What classes extend this class?
|
|
997
|
-
|
|
998
|
-
3. **Find indirect dependents**
|
|
999
|
-
- Follow the chain: if A depends on B, and B is our target, A is affected
|
|
1000
|
-
- Go up to 3 levels deep
|
|
1001
|
-
|
|
1002
|
-
4. **Categorize impact**
|
|
1003
|
-
- **Breaking changes**: Signature changes, removed exports
|
|
1004
|
-
- **Behavioral changes**: Logic changes that may affect callers
|
|
1005
|
-
- **Safe changes**: Internal refactors, added functionality
|
|
1006
|
-
|
|
1007
|
-
5. **Generate checklist**
|
|
1008
|
-
|
|
1009
|
-
## Output Format
|
|
1010
|
-
|
|
1011
|
-
\`\`\`markdown
|
|
1012
|
-
# Impact Analysis: [target]
|
|
1013
|
-
|
|
1014
|
-
## Direct Impact
|
|
1015
|
-
- [ ] file1.ts - uses [specific function/import]
|
|
1016
|
-
- [ ] file2.ts - extends [class]
|
|
1017
|
-
|
|
1018
|
-
## Indirect Impact
|
|
1019
|
-
- [ ] file3.ts - imports file1.ts which uses [target]
|
|
1020
|
-
|
|
1021
|
-
## Recommended Actions
|
|
1022
|
-
1. Update tests in [test files]
|
|
1023
|
-
2. Check [specific callers] for compatibility
|
|
1024
|
-
3. Update documentation in [docs]
|
|
1025
|
-
|
|
1026
|
-
## Risk Level: [LOW/MEDIUM/HIGH]
|
|
1027
|
-
[Explanation of risk assessment]
|
|
1028
|
-
\`\`\`
|
|
1029
|
-
`;
|
|
1030
|
-
return {
|
|
1031
|
-
name: "impact",
|
|
1032
|
-
description: "Analyze impact of code changes",
|
|
1033
|
-
content,
|
|
1034
|
-
filePath: join2(this.config.outputDir, "impact", "SKILL.md")
|
|
1035
|
-
};
|
|
1236
|
+
const matches = this.storage.searchNodesByName(refName);
|
|
1237
|
+
if (matches.length === 1) {
|
|
1238
|
+
return matches[0].id;
|
|
1239
|
+
}
|
|
1240
|
+
return null;
|
|
1036
1241
|
}
|
|
1037
|
-
|
|
1038
|
-
const
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
# Code Navigation
|
|
1045
|
-
|
|
1046
|
-
Find and navigate to code matching $ARGUMENTS.
|
|
1047
|
-
|
|
1048
|
-
## Available Queries
|
|
1049
|
-
|
|
1050
|
-
### By Type
|
|
1051
|
-
- "find class [name]" - Locate a class definition
|
|
1052
|
-
- "find function [name]" - Locate a function
|
|
1053
|
-
- "find file [pattern]" - Find files matching pattern
|
|
1054
|
-
- "find imports of [module]" - Find all imports of a module
|
|
1055
|
-
|
|
1056
|
-
### By Relationship
|
|
1057
|
-
- "callers of [function]" - Who calls this function?
|
|
1058
|
-
- "callees of [function]" - What does this function call?
|
|
1059
|
-
- "subclasses of [class]" - Classes that extend this
|
|
1060
|
-
- "implementers of [interface]" - Classes implementing interface
|
|
1061
|
-
|
|
1062
|
-
### By Location
|
|
1063
|
-
- "in [directory]" - Filter to specific directory
|
|
1064
|
-
- "in [file]" - Show contents of file
|
|
1065
|
-
|
|
1066
|
-
## Codebase Stats
|
|
1067
|
-
- ${stats.totalFiles} files to search
|
|
1068
|
-
- ${stats.nodesByType.function || 0} functions
|
|
1069
|
-
- ${stats.nodesByType.class || 0} classes
|
|
1070
|
-
- ${stats.nodesByType.method || 0} methods
|
|
1071
|
-
|
|
1072
|
-
## Output Format
|
|
1073
|
-
For each match, provide:
|
|
1074
|
-
- File path with line number link
|
|
1075
|
-
- Brief description of what it does
|
|
1076
|
-
- Related code (callers/callees if relevant)
|
|
1077
|
-
`;
|
|
1078
|
-
return {
|
|
1079
|
-
name: "navigate",
|
|
1080
|
-
description: `Navigate ${this.config.projectName} codebase`,
|
|
1081
|
-
content,
|
|
1082
|
-
filePath: join2(this.config.outputDir, "navigate", "SKILL.md")
|
|
1083
|
-
};
|
|
1242
|
+
getLanguage(filePath) {
|
|
1243
|
+
const ext = extname(filePath);
|
|
1244
|
+
if (ext === ".py") return "python";
|
|
1245
|
+
if (ext === ".ts" || ext === ".tsx") return "typescript";
|
|
1246
|
+
if (ext === ".js" || ext === ".jsx") return "javascript";
|
|
1247
|
+
return "unknown";
|
|
1084
1248
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
if (!existsSync(dir)) {
|
|
1089
|
-
mkdirSync(dir, { recursive: true });
|
|
1090
|
-
}
|
|
1091
|
-
writeFileSync(skill.filePath, skill.content, "utf-8");
|
|
1249
|
+
emitProgress(progress) {
|
|
1250
|
+
if (this.onProgress) {
|
|
1251
|
+
this.onProgress(progress);
|
|
1092
1252
|
}
|
|
1093
1253
|
}
|
|
1094
1254
|
};
|
|
1095
1255
|
|
|
1096
|
-
// src/
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
exclude: options.exclude || DEFAULT_CONFIG.exclude,
|
|
1111
|
-
languages: options.languages || DEFAULT_CONFIG.languages,
|
|
1112
|
-
dbPath,
|
|
1113
|
-
skillsOutputDir: options.skillsOutputDir || join3(rootPath, ".claude", "skills", "codemap")
|
|
1114
|
-
};
|
|
1115
|
-
const builder = new GraphBuilder(storage, config);
|
|
1116
|
-
const analysis = await builder.scan();
|
|
1117
|
-
storage.close();
|
|
1118
|
-
return { analysis, dbPath };
|
|
1119
|
-
}
|
|
1120
|
-
function generateSkills(projectPath, projectName) {
|
|
1121
|
-
const rootPath = resolve2(projectPath);
|
|
1122
|
-
const dbPath = join3(rootPath, ".codemap", "graph.db");
|
|
1123
|
-
if (!existsSync2(dbPath)) {
|
|
1124
|
-
throw new Error("No graph database found. Run scan() first.");
|
|
1125
|
-
}
|
|
1126
|
-
const outputDir = join3(rootPath, ".claude", "skills", "codemap");
|
|
1127
|
-
const storage = new GraphStorage(dbPath);
|
|
1128
|
-
const generator = new SkillGenerator(storage, {
|
|
1129
|
-
projectName: projectName || rootPath.split("/").pop() || "project",
|
|
1130
|
-
outputDir,
|
|
1131
|
-
includeArchitecture: true,
|
|
1132
|
-
includePatterns: true,
|
|
1133
|
-
includeDependencies: true
|
|
1134
|
-
});
|
|
1135
|
-
const skills = generator.generateAll();
|
|
1136
|
-
storage.close();
|
|
1137
|
-
return skills;
|
|
1138
|
-
}
|
|
1139
|
-
async function scanAndGenerate(projectPath, projectName, options = {}) {
|
|
1140
|
-
const scanResult = await scan(projectPath, options);
|
|
1141
|
-
const skills = generateSkills(projectPath, projectName);
|
|
1142
|
-
return {
|
|
1143
|
-
...scanResult,
|
|
1144
|
-
skills
|
|
1145
|
-
};
|
|
1146
|
-
}
|
|
1256
|
+
// src/types.ts
|
|
1257
|
+
var DEFAULT_CONFIG = {
|
|
1258
|
+
include: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.py"],
|
|
1259
|
+
exclude: [
|
|
1260
|
+
"**/node_modules/**",
|
|
1261
|
+
"**/dist/**",
|
|
1262
|
+
"**/build/**",
|
|
1263
|
+
"**/.git/**",
|
|
1264
|
+
"**/venv/**",
|
|
1265
|
+
"**/__pycache__/**",
|
|
1266
|
+
"**/.next/**"
|
|
1267
|
+
],
|
|
1268
|
+
languages: ["typescript", "javascript", "python"]
|
|
1269
|
+
};
|
|
1147
1270
|
export {
|
|
1148
|
-
BaseParser,
|
|
1149
1271
|
DEFAULT_CONFIG,
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
JavaScriptParser,
|
|
1153
|
-
ParserRegistry,
|
|
1154
|
-
PythonParser,
|
|
1155
|
-
SkillGenerator,
|
|
1156
|
-
TypeScriptParser,
|
|
1157
|
-
generateSkills,
|
|
1158
|
-
parserRegistry,
|
|
1159
|
-
registerAllParsers,
|
|
1160
|
-
scan,
|
|
1161
|
-
scanAndGenerate
|
|
1272
|
+
FlowBuilder,
|
|
1273
|
+
FlowStorage
|
|
1162
1274
|
};
|
|
1163
1275
|
//# sourceMappingURL=index.js.map
|