cto-ai-cli 4.0.0 → 5.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/DOCS.md +201 -2
- package/README.md +216 -312
- package/dist/action/index.js +271 -156
- package/dist/api/dashboard.js +271 -156
- package/dist/api/dashboard.js.map +1 -1
- package/dist/api/server.js +276 -155
- package/dist/api/server.js.map +1 -1
- package/dist/cli/gateway.js +298 -183
- package/dist/cli/score.js +1396 -241
- package/dist/cli/v2/index.js +290 -175
- package/dist/cli/v2/index.js.map +1 -1
- package/dist/engine/index.d.ts +121 -1
- package/dist/engine/index.js +1035 -212
- package/dist/engine/index.js.map +1 -1
- package/dist/fsevents-X6WP4TKM.node +0 -0
- package/dist/gateway/index.js +298 -183
- package/dist/gateway/index.js.map +1 -1
- package/dist/interact/index.js +263 -148
- package/dist/interact/index.js.map +1 -1
- package/dist/mcp/v2.js +287 -172
- package/dist/mcp/v2.js.map +1 -1
- package/package.json +8 -22
|
Binary file
|
package/dist/gateway/index.js
CHANGED
|
@@ -478,10 +478,7 @@ function deduplicateFindings(findings) {
|
|
|
478
478
|
import { createHash as createHash2 } from "crypto";
|
|
479
479
|
|
|
480
480
|
// src/engine/pruner.ts
|
|
481
|
-
import { Project, SyntaxKind } from "ts-morph";
|
|
482
481
|
import { readFile as readFile3 } from "fs/promises";
|
|
483
|
-
import { existsSync as existsSync2 } from "fs";
|
|
484
|
-
import { join as join2 } from "path";
|
|
485
482
|
|
|
486
483
|
// src/engine/tokenizer.ts
|
|
487
484
|
import { encodingForModel } from "js-tiktoken";
|
|
@@ -536,23 +533,7 @@ async function pruneTypeScript(file, level) {
|
|
|
536
533
|
} catch {
|
|
537
534
|
return emptyResult(file, level);
|
|
538
535
|
}
|
|
539
|
-
|
|
540
|
-
try {
|
|
541
|
-
const tsConfigPath = findTsConfig(file.path);
|
|
542
|
-
project = new Project({
|
|
543
|
-
tsConfigFilePath: tsConfigPath,
|
|
544
|
-
skipAddingFilesFromTsConfig: true,
|
|
545
|
-
compilerOptions: tsConfigPath ? void 0 : { allowJs: true, esModuleInterop: true }
|
|
546
|
-
});
|
|
547
|
-
project.createSourceFile(file.path, content, { overwrite: true });
|
|
548
|
-
} catch {
|
|
549
|
-
return pruneGenericFromContent(file, content, level);
|
|
550
|
-
}
|
|
551
|
-
const sourceFile = project.getSourceFiles()[0];
|
|
552
|
-
if (!sourceFile) {
|
|
553
|
-
return pruneGenericFromContent(file, content, level);
|
|
554
|
-
}
|
|
555
|
-
const prunedContent = level === "signatures" ? extractSignaturesAST(sourceFile) : extractSkeletonAST(sourceFile);
|
|
536
|
+
const prunedContent = level === "signatures" ? extractSignaturesRegex(content) : extractSkeletonRegex(content);
|
|
556
537
|
const prunedTokens = countTokensChars4(Buffer.byteLength(prunedContent, "utf-8"));
|
|
557
538
|
const savingsPercent = file.tokens > 0 ? (file.tokens - prunedTokens) / file.tokens * 100 : 0;
|
|
558
539
|
return {
|
|
@@ -564,131 +545,281 @@ async function pruneTypeScript(file, level) {
|
|
|
564
545
|
savingsPercent: Math.max(0, savingsPercent)
|
|
565
546
|
};
|
|
566
547
|
}
|
|
567
|
-
function
|
|
548
|
+
function extractSignaturesRegex(content) {
|
|
549
|
+
const lines = content.split("\n");
|
|
568
550
|
const parts = [];
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
551
|
+
let i = 0;
|
|
552
|
+
while (i < lines.length) {
|
|
553
|
+
const line = lines[i];
|
|
554
|
+
const trimmed = line.trim();
|
|
555
|
+
if (trimmed === "") {
|
|
556
|
+
i++;
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (trimmed.startsWith("/**")) {
|
|
560
|
+
const docLines = [];
|
|
561
|
+
while (i < lines.length) {
|
|
562
|
+
docLines.push(lines[i]);
|
|
563
|
+
if (lines[i].includes("*/")) {
|
|
564
|
+
i++;
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
i++;
|
|
568
|
+
}
|
|
569
|
+
parts.push(docLines.join("\n"));
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
if (trimmed.startsWith("//")) {
|
|
573
|
+
parts.push(line);
|
|
574
|
+
i++;
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (/^\s*(import|export)\s/.test(line) && (trimmed.includes(" from ") || trimmed.startsWith("import "))) {
|
|
578
|
+
const block = collectBracedLine(lines, i);
|
|
579
|
+
parts.push(block.text);
|
|
580
|
+
i = block.nextIndex;
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (/^\s*export\s*(\{|\*)/.test(trimmed)) {
|
|
584
|
+
const block = collectBracedLine(lines, i);
|
|
585
|
+
parts.push(block.text);
|
|
586
|
+
i = block.nextIndex;
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
if (/^\s*(export\s+)?type\s+\w/.test(trimmed) && !trimmed.startsWith("typeof")) {
|
|
590
|
+
const block = collectBalanced(lines, i);
|
|
591
|
+
parts.push(block.text);
|
|
592
|
+
i = block.nextIndex;
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (/^\s*(export\s+)?interface\s+\w/.test(trimmed)) {
|
|
596
|
+
const block = collectBalanced(lines, i);
|
|
597
|
+
parts.push(block.text);
|
|
598
|
+
i = block.nextIndex;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (/^\s*(export\s+)?(const\s+)?enum\s+\w/.test(trimmed)) {
|
|
602
|
+
const block = collectBalanced(lines, i);
|
|
603
|
+
parts.push(block.text);
|
|
604
|
+
i = block.nextIndex;
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
const fnMatch = trimmed.match(/^(export\s+)?(async\s+)?function\s+(\w+)/);
|
|
608
|
+
if (fnMatch) {
|
|
609
|
+
const sig = extractFnSignature(lines, i);
|
|
610
|
+
parts.push(`${sig} { /* ... */ }`);
|
|
611
|
+
i = skipBlock(lines, i);
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
const arrowMatch = trimmed.match(/^(export\s+)?(const|let|var)\s+(\w+)/);
|
|
615
|
+
if (arrowMatch && looksLikeFunctionDecl(lines, i)) {
|
|
616
|
+
const prefix = trimmed.match(/^((?:export\s+)?(?:const|let|var)\s+\w+[^=]*=)/)?.[1];
|
|
617
|
+
if (prefix) {
|
|
618
|
+
parts.push(`${prefix} /* ... */;`);
|
|
612
619
|
}
|
|
620
|
+
i = skipBlock(lines, i);
|
|
621
|
+
continue;
|
|
613
622
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
for (const prop of cls.getProperties()) {
|
|
628
|
-
parts.push(` ${prop.getText()}`);
|
|
629
|
-
}
|
|
630
|
-
const ctor = cls.getConstructors()[0];
|
|
631
|
-
if (ctor) {
|
|
632
|
-
const ctorParams = ctor.getParameters().map((p) => p.getText()).join(", ");
|
|
633
|
-
parts.push(` constructor(${ctorParams}) { /* ... */ }`);
|
|
634
|
-
}
|
|
635
|
-
for (const method of cls.getMethods()) {
|
|
636
|
-
const isStatic = method.isStatic();
|
|
637
|
-
const isAsync = method.isAsync();
|
|
638
|
-
const methodName = method.getName();
|
|
639
|
-
const methodParams = method.getParameters().map((p) => p.getText()).join(", ");
|
|
640
|
-
const returnType = method.getReturnTypeNode()?.getText();
|
|
641
|
-
const returnStr = returnType ? `: ${returnType}` : "";
|
|
642
|
-
const staticStr = isStatic ? "static " : "";
|
|
643
|
-
const asyncStr = isAsync ? "async " : "";
|
|
644
|
-
parts.push(` ${staticStr}${asyncStr}${methodName}(${methodParams})${returnStr} { /* ... */ }`);
|
|
645
|
-
}
|
|
646
|
-
parts.push("}");
|
|
647
|
-
}
|
|
648
|
-
for (const exp of sf.getExportDeclarations()) {
|
|
649
|
-
parts.push(exp.getText());
|
|
650
|
-
}
|
|
651
|
-
for (const exp of sf.getExportAssignments()) {
|
|
652
|
-
parts.push(exp.getText());
|
|
623
|
+
if (arrowMatch) {
|
|
624
|
+
const block = collectStatement(lines, i);
|
|
625
|
+
parts.push(block.text);
|
|
626
|
+
i = block.nextIndex;
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
if (/^\s*(export\s+)?(abstract\s+)?class\s+\w/.test(trimmed)) {
|
|
630
|
+
const classOutline = extractClassOutline(lines, i);
|
|
631
|
+
parts.push(classOutline.text);
|
|
632
|
+
i = classOutline.nextIndex;
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
i++;
|
|
653
636
|
}
|
|
654
637
|
return parts.join("\n");
|
|
655
638
|
}
|
|
656
|
-
function
|
|
639
|
+
function extractSkeletonRegex(content) {
|
|
640
|
+
const lines = content.split("\n");
|
|
657
641
|
const parts = [];
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
642
|
+
let i = 0;
|
|
643
|
+
while (i < lines.length) {
|
|
644
|
+
const trimmed = lines[i].trim();
|
|
645
|
+
if (/^import\s/.test(trimmed)) {
|
|
646
|
+
const block = collectBracedLine(lines, i);
|
|
647
|
+
parts.push(block.text);
|
|
648
|
+
i = block.nextIndex;
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (/^export\s+(type|interface)\s+\w/.test(trimmed)) {
|
|
652
|
+
const block = collectBalanced(lines, i);
|
|
653
|
+
parts.push(block.text);
|
|
654
|
+
i = block.nextIndex;
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if (/^export\s+(const\s+)?enum\s+\w/.test(trimmed)) {
|
|
658
|
+
const block = collectBalanced(lines, i);
|
|
659
|
+
parts.push(block.text);
|
|
660
|
+
i = block.nextIndex;
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (/^export\s+(async\s+)?function\s+\w/.test(trimmed)) {
|
|
664
|
+
const sig = extractFnSignature(lines, i);
|
|
665
|
+
parts.push(`${sig};`);
|
|
666
|
+
i = skipBlock(lines, i);
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (/^export\s+(abstract\s+)?class\s+/.test(trimmed)) {
|
|
670
|
+
const nameMatch = trimmed.match(/class\s+(\w+)/);
|
|
671
|
+
const name = nameMatch?.[1] ?? "Unknown";
|
|
672
|
+
const end = skipBlock(lines, i);
|
|
673
|
+
const methods = [];
|
|
674
|
+
for (let j = i + 1; j < end; j++) {
|
|
675
|
+
const mt = lines[j].trim();
|
|
676
|
+
const mm = mt.match(/^(?:static\s+)?(?:async\s+)?(\w+)\s*\(/);
|
|
677
|
+
if (mm && mm[1] !== "constructor") methods.push(mm[1]);
|
|
678
|
+
}
|
|
679
|
+
parts.push(`export class ${name} { /* methods: ${methods.join(", ")} */ }`);
|
|
680
|
+
i = end;
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (/^export\s*(\{|\*)/.test(trimmed)) {
|
|
684
|
+
const block = collectBracedLine(lines, i);
|
|
685
|
+
parts.push(block.text);
|
|
686
|
+
i = block.nextIndex;
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
i++;
|
|
689
690
|
}
|
|
690
691
|
return parts.join("\n");
|
|
691
692
|
}
|
|
693
|
+
function collectBracedLine(lines, start) {
|
|
694
|
+
let text = lines[start];
|
|
695
|
+
let i = start + 1;
|
|
696
|
+
while (i < lines.length && !text.includes(";") && !text.trimEnd().endsWith("'") && !text.trimEnd().endsWith('"')) {
|
|
697
|
+
text += "\n" + lines[i];
|
|
698
|
+
i++;
|
|
699
|
+
}
|
|
700
|
+
return { text, nextIndex: i };
|
|
701
|
+
}
|
|
702
|
+
function collectBalanced(lines, start) {
|
|
703
|
+
let depth = 0;
|
|
704
|
+
let text = "";
|
|
705
|
+
let i = start;
|
|
706
|
+
let started = false;
|
|
707
|
+
while (i < lines.length) {
|
|
708
|
+
const line = lines[i];
|
|
709
|
+
text += (text ? "\n" : "") + line;
|
|
710
|
+
for (const ch of line) {
|
|
711
|
+
if (ch === "{" || ch === "(") {
|
|
712
|
+
depth++;
|
|
713
|
+
started = true;
|
|
714
|
+
}
|
|
715
|
+
if (ch === "}" || ch === ")") depth--;
|
|
716
|
+
}
|
|
717
|
+
i++;
|
|
718
|
+
if (started && depth <= 0) break;
|
|
719
|
+
if (!started && line.includes(";")) break;
|
|
720
|
+
}
|
|
721
|
+
return { text, nextIndex: i };
|
|
722
|
+
}
|
|
723
|
+
function collectStatement(lines, start) {
|
|
724
|
+
let text = lines[start];
|
|
725
|
+
let i = start + 1;
|
|
726
|
+
if (text.includes(";")) return { text, nextIndex: i };
|
|
727
|
+
let depth = 0;
|
|
728
|
+
for (const ch of text) {
|
|
729
|
+
if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
730
|
+
if (ch === "}" || ch === ")" || ch === "]") depth--;
|
|
731
|
+
}
|
|
732
|
+
while (i < lines.length && depth > 0) {
|
|
733
|
+
text += "\n" + lines[i];
|
|
734
|
+
for (const ch of lines[i]) {
|
|
735
|
+
if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
736
|
+
if (ch === "}" || ch === ")" || ch === "]") depth--;
|
|
737
|
+
}
|
|
738
|
+
i++;
|
|
739
|
+
}
|
|
740
|
+
return { text, nextIndex: i };
|
|
741
|
+
}
|
|
742
|
+
function extractFnSignature(lines, start) {
|
|
743
|
+
let sig = "";
|
|
744
|
+
let i = start;
|
|
745
|
+
while (i < lines.length) {
|
|
746
|
+
const line = lines[i].trim();
|
|
747
|
+
sig += (sig ? " " : "") + line;
|
|
748
|
+
if (line.includes("{")) {
|
|
749
|
+
sig = sig.replace(/\s*\{[^]*$/, "").trim();
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
i++;
|
|
753
|
+
}
|
|
754
|
+
return sig;
|
|
755
|
+
}
|
|
756
|
+
function skipBlock(lines, start) {
|
|
757
|
+
let depth = 0;
|
|
758
|
+
let i = start;
|
|
759
|
+
let foundBrace = false;
|
|
760
|
+
while (i < lines.length) {
|
|
761
|
+
for (const ch of lines[i]) {
|
|
762
|
+
if (ch === "{") {
|
|
763
|
+
depth++;
|
|
764
|
+
foundBrace = true;
|
|
765
|
+
}
|
|
766
|
+
if (ch === "}") depth--;
|
|
767
|
+
}
|
|
768
|
+
i++;
|
|
769
|
+
if (foundBrace && depth <= 0) break;
|
|
770
|
+
if (!foundBrace && lines[i - 1].includes(";")) break;
|
|
771
|
+
}
|
|
772
|
+
return i;
|
|
773
|
+
}
|
|
774
|
+
function looksLikeFunctionDecl(lines, start) {
|
|
775
|
+
const chunk = lines.slice(start, Math.min(start + 5, lines.length)).join(" ");
|
|
776
|
+
return /=>/.test(chunk) || /=\s*function/.test(chunk);
|
|
777
|
+
}
|
|
778
|
+
function extractClassOutline(lines, start) {
|
|
779
|
+
const header = lines[start].trim();
|
|
780
|
+
let headerText = header;
|
|
781
|
+
let i = start + 1;
|
|
782
|
+
if (!header.includes("{")) {
|
|
783
|
+
while (i < lines.length) {
|
|
784
|
+
headerText += " " + lines[i].trim();
|
|
785
|
+
if (lines[i].includes("{")) {
|
|
786
|
+
i++;
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
i++;
|
|
790
|
+
}
|
|
791
|
+
} else {
|
|
792
|
+
i = start + 1;
|
|
793
|
+
}
|
|
794
|
+
const bodyParts = [headerText.replace(/\{[^]*$/, "{").trim()];
|
|
795
|
+
let depth = 1;
|
|
796
|
+
while (i < lines.length && depth > 0) {
|
|
797
|
+
const line = lines[i];
|
|
798
|
+
const trimmed = line.trim();
|
|
799
|
+
for (const ch of line) {
|
|
800
|
+
if (ch === "{") depth++;
|
|
801
|
+
if (ch === "}") depth--;
|
|
802
|
+
}
|
|
803
|
+
if (depth <= 0) {
|
|
804
|
+
i++;
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
if (depth === 1) {
|
|
808
|
+
if (/^(private|protected|public|readonly|static|#)/.test(trimmed) && !trimmed.includes("(")) {
|
|
809
|
+
bodyParts.push(` ${trimmed}`);
|
|
810
|
+
} else if (/^constructor\s*\(/.test(trimmed)) {
|
|
811
|
+
const sig = extractFnSignature(lines, i);
|
|
812
|
+
bodyParts.push(` ${sig} { /* ... */ }`);
|
|
813
|
+
} else if (/^(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?\w+\s*[(<]/.test(trimmed) && !trimmed.startsWith("//")) {
|
|
814
|
+
const sig = extractFnSignature(lines, i);
|
|
815
|
+
bodyParts.push(` ${sig} { /* ... */ }`);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
i++;
|
|
819
|
+
}
|
|
820
|
+
bodyParts.push("}");
|
|
821
|
+
return { text: bodyParts.join("\n"), nextIndex: i };
|
|
822
|
+
}
|
|
692
823
|
async function pruneGeneric(file, level) {
|
|
693
824
|
let content;
|
|
694
825
|
try {
|
|
@@ -749,22 +880,6 @@ function emptyResult(file, level) {
|
|
|
749
880
|
savingsPercent: 100
|
|
750
881
|
};
|
|
751
882
|
}
|
|
752
|
-
function addJSDoc(node, parts) {
|
|
753
|
-
if (!node.getJsDocs) return;
|
|
754
|
-
const docs = node.getJsDocs();
|
|
755
|
-
if (docs.length > 0) {
|
|
756
|
-
parts.push(docs[0].getText());
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
function findTsConfig(filePath) {
|
|
760
|
-
let dir = filePath;
|
|
761
|
-
for (let i = 0; i < 10; i++) {
|
|
762
|
-
dir = join2(dir, "..");
|
|
763
|
-
const candidate = join2(dir, "tsconfig.json");
|
|
764
|
-
if (existsSync2(candidate)) return candidate;
|
|
765
|
-
}
|
|
766
|
-
return void 0;
|
|
767
|
-
}
|
|
768
883
|
|
|
769
884
|
// src/engine/graph-utils.ts
|
|
770
885
|
function buildAdjacencyList(edges) {
|
|
@@ -1287,8 +1402,8 @@ async function optimizeContext(messages, analysis, config) {
|
|
|
1287
1402
|
}
|
|
1288
1403
|
|
|
1289
1404
|
// src/gateway/tracker.ts
|
|
1290
|
-
import { mkdirSync as mkdirSync2, appendFileSync, readFileSync as readFileSync3, readdirSync, existsSync as
|
|
1291
|
-
import { join as
|
|
1405
|
+
import { mkdirSync as mkdirSync2, appendFileSync, readFileSync as readFileSync3, readdirSync, existsSync as existsSync2 } from "fs";
|
|
1406
|
+
import { join as join2 } from "path";
|
|
1292
1407
|
import { randomUUID } from "crypto";
|
|
1293
1408
|
var UsageTracker = class {
|
|
1294
1409
|
logDir;
|
|
@@ -1300,7 +1415,7 @@ var UsageTracker = class {
|
|
|
1300
1415
|
memRecords = [];
|
|
1301
1416
|
constructor(config) {
|
|
1302
1417
|
this.config = config;
|
|
1303
|
-
this.logDir =
|
|
1418
|
+
this.logDir = join2(config.logDir, "usage");
|
|
1304
1419
|
mkdirSync2(this.logDir, { recursive: true });
|
|
1305
1420
|
}
|
|
1306
1421
|
// ===== EVENT SYSTEM =====
|
|
@@ -1323,7 +1438,7 @@ var UsageTracker = class {
|
|
|
1323
1438
|
...params
|
|
1324
1439
|
};
|
|
1325
1440
|
const monthKey = this.getMonthKey(record.timestamp);
|
|
1326
|
-
const logFile =
|
|
1441
|
+
const logFile = join2(this.logDir, `${monthKey}.jsonl`);
|
|
1327
1442
|
const line = JSON.stringify({
|
|
1328
1443
|
...record,
|
|
1329
1444
|
timestamp: record.timestamp.toISOString()
|
|
@@ -1457,8 +1572,8 @@ var UsageTracker = class {
|
|
|
1457
1572
|
return date.toISOString().slice(0, 7);
|
|
1458
1573
|
}
|
|
1459
1574
|
getMonthRecordsByKey(monthKey) {
|
|
1460
|
-
const filePath =
|
|
1461
|
-
if (!
|
|
1575
|
+
const filePath = join2(this.logDir, `${monthKey}.jsonl`);
|
|
1576
|
+
if (!existsSync2(filePath)) return [];
|
|
1462
1577
|
return readFileSync3(filePath, "utf-8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
1463
1578
|
try {
|
|
1464
1579
|
const parsed = JSON.parse(line);
|
|
@@ -1472,8 +1587,8 @@ var UsageTracker = class {
|
|
|
1472
1587
|
getMonthRecords(date) {
|
|
1473
1588
|
const monthKey = this.getMonthKey(date);
|
|
1474
1589
|
if (this.cache && this.cacheMonth === monthKey) return this.cache;
|
|
1475
|
-
const filePath =
|
|
1476
|
-
if (!
|
|
1590
|
+
const filePath = join2(this.logDir, `${monthKey}.jsonl`);
|
|
1591
|
+
if (!existsSync2(filePath)) return [];
|
|
1477
1592
|
const records = readFileSync3(filePath, "utf-8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
1478
1593
|
try {
|
|
1479
1594
|
const parsed = JSON.parse(line);
|
|
@@ -1488,11 +1603,11 @@ var UsageTracker = class {
|
|
|
1488
1603
|
return records;
|
|
1489
1604
|
}
|
|
1490
1605
|
getAllRecords() {
|
|
1491
|
-
if (!
|
|
1606
|
+
if (!existsSync2(this.logDir)) return [];
|
|
1492
1607
|
const files = readdirSync(this.logDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1493
1608
|
const allRecords = [];
|
|
1494
1609
|
for (const file of files) {
|
|
1495
|
-
const content = readFileSync3(
|
|
1610
|
+
const content = readFileSync3(join2(this.logDir, file), "utf-8");
|
|
1496
1611
|
const records = content.split("\n").filter((line) => line.trim()).map((line) => {
|
|
1497
1612
|
try {
|
|
1498
1613
|
const parsed = JSON.parse(line);
|
|
@@ -1510,7 +1625,7 @@ var UsageTracker = class {
|
|
|
1510
1625
|
|
|
1511
1626
|
// src/engine/analyzer.ts
|
|
1512
1627
|
import { readFile as readFile4, readdir, stat as stat2 } from "fs/promises";
|
|
1513
|
-
import { join as
|
|
1628
|
+
import { join as join4, extname, relative as relative3, resolve as resolve4, basename as basename2 } from "path";
|
|
1514
1629
|
import { createHash as createHash3 } from "crypto";
|
|
1515
1630
|
|
|
1516
1631
|
// src/types/engine.ts
|
|
@@ -1563,14 +1678,14 @@ var DEFAULT_CONFIG = {
|
|
|
1563
1678
|
};
|
|
1564
1679
|
|
|
1565
1680
|
// src/engine/graph.ts
|
|
1566
|
-
import { Project
|
|
1567
|
-
import { resolve as resolve3, relative as relative2, dirname as dirname2, join as
|
|
1568
|
-
import { existsSync as
|
|
1681
|
+
import { Project, SyntaxKind } from "ts-morph";
|
|
1682
|
+
import { resolve as resolve3, relative as relative2, dirname as dirname2, join as join3 } from "path";
|
|
1683
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1569
1684
|
var TS_EXTENSIONS2 = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mts", "mjs", "cts", "cjs"]);
|
|
1570
1685
|
function createProject(projectPath, filePaths) {
|
|
1571
|
-
const tsConfigPath =
|
|
1572
|
-
const hasTsConfig =
|
|
1573
|
-
const project = new
|
|
1686
|
+
const tsConfigPath = join3(projectPath, "tsconfig.json");
|
|
1687
|
+
const hasTsConfig = existsSync3(tsConfigPath);
|
|
1688
|
+
const project = new Project({
|
|
1574
1689
|
tsConfigFilePath: hasTsConfig ? tsConfigPath : void 0,
|
|
1575
1690
|
skipAddingFilesFromTsConfig: true,
|
|
1576
1691
|
compilerOptions: hasTsConfig ? void 0 : {
|
|
@@ -1770,7 +1885,7 @@ function enrichComplexity(project, absPath, files) {
|
|
|
1770
1885
|
}
|
|
1771
1886
|
for (const varDecl of sourceFile.getVariableDeclarations()) {
|
|
1772
1887
|
const init = varDecl.getInitializer();
|
|
1773
|
-
if (init && (init.getKind() ===
|
|
1888
|
+
if (init && (init.getKind() === SyntaxKind.ArrowFunction || init.getKind() === SyntaxKind.FunctionExpression)) {
|
|
1774
1889
|
totalComplexity += calculateCyclomaticComplexity(init);
|
|
1775
1890
|
}
|
|
1776
1891
|
}
|
|
@@ -1781,22 +1896,22 @@ function calculateCyclomaticComplexity(node) {
|
|
|
1781
1896
|
let complexity = 1;
|
|
1782
1897
|
node.forEachDescendant((descendant) => {
|
|
1783
1898
|
switch (descendant.getKind()) {
|
|
1784
|
-
case
|
|
1785
|
-
case
|
|
1786
|
-
case
|
|
1787
|
-
case
|
|
1788
|
-
case
|
|
1789
|
-
case
|
|
1790
|
-
case
|
|
1791
|
-
case
|
|
1792
|
-
case
|
|
1899
|
+
case SyntaxKind.IfStatement:
|
|
1900
|
+
case SyntaxKind.ConditionalExpression:
|
|
1901
|
+
case SyntaxKind.ForStatement:
|
|
1902
|
+
case SyntaxKind.ForInStatement:
|
|
1903
|
+
case SyntaxKind.ForOfStatement:
|
|
1904
|
+
case SyntaxKind.WhileStatement:
|
|
1905
|
+
case SyntaxKind.DoStatement:
|
|
1906
|
+
case SyntaxKind.CaseClause:
|
|
1907
|
+
case SyntaxKind.CatchClause:
|
|
1793
1908
|
complexity++;
|
|
1794
1909
|
break;
|
|
1795
|
-
case
|
|
1910
|
+
case SyntaxKind.BinaryExpression: {
|
|
1796
1911
|
const opToken = descendant.getOperatorToken?.();
|
|
1797
1912
|
if (opToken) {
|
|
1798
1913
|
const kind = opToken.getKind();
|
|
1799
|
-
if (kind ===
|
|
1914
|
+
if (kind === SyntaxKind.AmpersandAmpersandToken || kind === SyntaxKind.BarBarToken || kind === SyntaxKind.QuestionQuestionToken) {
|
|
1800
1915
|
complexity++;
|
|
1801
1916
|
}
|
|
1802
1917
|
}
|
|
@@ -1813,14 +1928,14 @@ function resolveImport(sourceFile, moduleSpecifier, projectRoot) {
|
|
|
1813
1928
|
const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
|
|
1814
1929
|
for (const ext of extensions) {
|
|
1815
1930
|
const candidate = basePath.endsWith(ext) ? basePath : basePath + ext;
|
|
1816
|
-
if (
|
|
1931
|
+
if (existsSync3(candidate)) {
|
|
1817
1932
|
const rel = relative2(projectRoot, candidate);
|
|
1818
1933
|
if (!rel.startsWith("..")) return rel;
|
|
1819
1934
|
}
|
|
1820
1935
|
}
|
|
1821
1936
|
if (moduleSpecifier.endsWith(".js")) {
|
|
1822
1937
|
const tsPath = basePath.replace(/\.js$/, ".ts");
|
|
1823
|
-
if (
|
|
1938
|
+
if (existsSync3(tsPath)) {
|
|
1824
1939
|
const rel = relative2(projectRoot, tsPath);
|
|
1825
1940
|
if (!rel.startsWith("..")) return rel;
|
|
1826
1941
|
}
|
|
@@ -1981,7 +2096,7 @@ async function walkProject(rootPath, options) {
|
|
|
1981
2096
|
}
|
|
1982
2097
|
const promises = [];
|
|
1983
2098
|
for (const entry of entries) {
|
|
1984
|
-
const fullPath =
|
|
2099
|
+
const fullPath = join4(dir, entry.name);
|
|
1985
2100
|
if (entry.isDirectory()) {
|
|
1986
2101
|
if (!ignoreDirSet.has(entry.name) && !entry.name.startsWith(".")) {
|
|
1987
2102
|
promises.push(walk(fullPath, depth + 1));
|