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
package/dist/cli/gateway.js
CHANGED
|
@@ -483,10 +483,7 @@ function deduplicateFindings(findings) {
|
|
|
483
483
|
import { createHash as createHash2 } from "crypto";
|
|
484
484
|
|
|
485
485
|
// src/engine/pruner.ts
|
|
486
|
-
import { Project, SyntaxKind } from "ts-morph";
|
|
487
486
|
import { readFile as readFile3 } from "fs/promises";
|
|
488
|
-
import { existsSync as existsSync2 } from "fs";
|
|
489
|
-
import { join as join2 } from "path";
|
|
490
487
|
|
|
491
488
|
// src/engine/tokenizer.ts
|
|
492
489
|
import { encodingForModel } from "js-tiktoken";
|
|
@@ -541,23 +538,7 @@ async function pruneTypeScript(file, level) {
|
|
|
541
538
|
} catch {
|
|
542
539
|
return emptyResult(file, level);
|
|
543
540
|
}
|
|
544
|
-
|
|
545
|
-
try {
|
|
546
|
-
const tsConfigPath = findTsConfig(file.path);
|
|
547
|
-
project = new Project({
|
|
548
|
-
tsConfigFilePath: tsConfigPath,
|
|
549
|
-
skipAddingFilesFromTsConfig: true,
|
|
550
|
-
compilerOptions: tsConfigPath ? void 0 : { allowJs: true, esModuleInterop: true }
|
|
551
|
-
});
|
|
552
|
-
project.createSourceFile(file.path, content, { overwrite: true });
|
|
553
|
-
} catch {
|
|
554
|
-
return pruneGenericFromContent(file, content, level);
|
|
555
|
-
}
|
|
556
|
-
const sourceFile = project.getSourceFiles()[0];
|
|
557
|
-
if (!sourceFile) {
|
|
558
|
-
return pruneGenericFromContent(file, content, level);
|
|
559
|
-
}
|
|
560
|
-
const prunedContent = level === "signatures" ? extractSignaturesAST(sourceFile) : extractSkeletonAST(sourceFile);
|
|
541
|
+
const prunedContent = level === "signatures" ? extractSignaturesRegex(content) : extractSkeletonRegex(content);
|
|
561
542
|
const prunedTokens = countTokensChars4(Buffer.byteLength(prunedContent, "utf-8"));
|
|
562
543
|
const savingsPercent = file.tokens > 0 ? (file.tokens - prunedTokens) / file.tokens * 100 : 0;
|
|
563
544
|
return {
|
|
@@ -569,131 +550,281 @@ async function pruneTypeScript(file, level) {
|
|
|
569
550
|
savingsPercent: Math.max(0, savingsPercent)
|
|
570
551
|
};
|
|
571
552
|
}
|
|
572
|
-
function
|
|
553
|
+
function extractSignaturesRegex(content) {
|
|
554
|
+
const lines = content.split("\n");
|
|
573
555
|
const parts = [];
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
556
|
+
let i = 0;
|
|
557
|
+
while (i < lines.length) {
|
|
558
|
+
const line = lines[i];
|
|
559
|
+
const trimmed = line.trim();
|
|
560
|
+
if (trimmed === "") {
|
|
561
|
+
i++;
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
if (trimmed.startsWith("/**")) {
|
|
565
|
+
const docLines = [];
|
|
566
|
+
while (i < lines.length) {
|
|
567
|
+
docLines.push(lines[i]);
|
|
568
|
+
if (lines[i].includes("*/")) {
|
|
569
|
+
i++;
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
i++;
|
|
573
|
+
}
|
|
574
|
+
parts.push(docLines.join("\n"));
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (trimmed.startsWith("//")) {
|
|
578
|
+
parts.push(line);
|
|
579
|
+
i++;
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
if (/^\s*(import|export)\s/.test(line) && (trimmed.includes(" from ") || trimmed.startsWith("import "))) {
|
|
583
|
+
const block = collectBracedLine(lines, i);
|
|
584
|
+
parts.push(block.text);
|
|
585
|
+
i = block.nextIndex;
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (/^\s*export\s*(\{|\*)/.test(trimmed)) {
|
|
589
|
+
const block = collectBracedLine(lines, i);
|
|
590
|
+
parts.push(block.text);
|
|
591
|
+
i = block.nextIndex;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (/^\s*(export\s+)?type\s+\w/.test(trimmed) && !trimmed.startsWith("typeof")) {
|
|
595
|
+
const block = collectBalanced(lines, i);
|
|
596
|
+
parts.push(block.text);
|
|
597
|
+
i = block.nextIndex;
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
if (/^\s*(export\s+)?interface\s+\w/.test(trimmed)) {
|
|
601
|
+
const block = collectBalanced(lines, i);
|
|
602
|
+
parts.push(block.text);
|
|
603
|
+
i = block.nextIndex;
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (/^\s*(export\s+)?(const\s+)?enum\s+\w/.test(trimmed)) {
|
|
607
|
+
const block = collectBalanced(lines, i);
|
|
608
|
+
parts.push(block.text);
|
|
609
|
+
i = block.nextIndex;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
const fnMatch = trimmed.match(/^(export\s+)?(async\s+)?function\s+(\w+)/);
|
|
613
|
+
if (fnMatch) {
|
|
614
|
+
const sig = extractFnSignature(lines, i);
|
|
615
|
+
parts.push(`${sig} { /* ... */ }`);
|
|
616
|
+
i = skipBlock(lines, i);
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
const arrowMatch = trimmed.match(/^(export\s+)?(const|let|var)\s+(\w+)/);
|
|
620
|
+
if (arrowMatch && looksLikeFunctionDecl(lines, i)) {
|
|
621
|
+
const prefix = trimmed.match(/^((?:export\s+)?(?:const|let|var)\s+\w+[^=]*=)/)?.[1];
|
|
622
|
+
if (prefix) {
|
|
623
|
+
parts.push(`${prefix} /* ... */;`);
|
|
617
624
|
}
|
|
625
|
+
i = skipBlock(lines, i);
|
|
626
|
+
continue;
|
|
618
627
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
for (const prop of cls.getProperties()) {
|
|
633
|
-
parts.push(` ${prop.getText()}`);
|
|
634
|
-
}
|
|
635
|
-
const ctor = cls.getConstructors()[0];
|
|
636
|
-
if (ctor) {
|
|
637
|
-
const ctorParams = ctor.getParameters().map((p) => p.getText()).join(", ");
|
|
638
|
-
parts.push(` constructor(${ctorParams}) { /* ... */ }`);
|
|
639
|
-
}
|
|
640
|
-
for (const method of cls.getMethods()) {
|
|
641
|
-
const isStatic = method.isStatic();
|
|
642
|
-
const isAsync = method.isAsync();
|
|
643
|
-
const methodName = method.getName();
|
|
644
|
-
const methodParams = method.getParameters().map((p) => p.getText()).join(", ");
|
|
645
|
-
const returnType = method.getReturnTypeNode()?.getText();
|
|
646
|
-
const returnStr = returnType ? `: ${returnType}` : "";
|
|
647
|
-
const staticStr = isStatic ? "static " : "";
|
|
648
|
-
const asyncStr = isAsync ? "async " : "";
|
|
649
|
-
parts.push(` ${staticStr}${asyncStr}${methodName}(${methodParams})${returnStr} { /* ... */ }`);
|
|
650
|
-
}
|
|
651
|
-
parts.push("}");
|
|
652
|
-
}
|
|
653
|
-
for (const exp of sf.getExportDeclarations()) {
|
|
654
|
-
parts.push(exp.getText());
|
|
655
|
-
}
|
|
656
|
-
for (const exp of sf.getExportAssignments()) {
|
|
657
|
-
parts.push(exp.getText());
|
|
628
|
+
if (arrowMatch) {
|
|
629
|
+
const block = collectStatement(lines, i);
|
|
630
|
+
parts.push(block.text);
|
|
631
|
+
i = block.nextIndex;
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (/^\s*(export\s+)?(abstract\s+)?class\s+\w/.test(trimmed)) {
|
|
635
|
+
const classOutline = extractClassOutline(lines, i);
|
|
636
|
+
parts.push(classOutline.text);
|
|
637
|
+
i = classOutline.nextIndex;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
i++;
|
|
658
641
|
}
|
|
659
642
|
return parts.join("\n");
|
|
660
643
|
}
|
|
661
|
-
function
|
|
644
|
+
function extractSkeletonRegex(content) {
|
|
645
|
+
const lines = content.split("\n");
|
|
662
646
|
const parts = [];
|
|
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
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
647
|
+
let i = 0;
|
|
648
|
+
while (i < lines.length) {
|
|
649
|
+
const trimmed = lines[i].trim();
|
|
650
|
+
if (/^import\s/.test(trimmed)) {
|
|
651
|
+
const block = collectBracedLine(lines, i);
|
|
652
|
+
parts.push(block.text);
|
|
653
|
+
i = block.nextIndex;
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
if (/^export\s+(type|interface)\s+\w/.test(trimmed)) {
|
|
657
|
+
const block = collectBalanced(lines, i);
|
|
658
|
+
parts.push(block.text);
|
|
659
|
+
i = block.nextIndex;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (/^export\s+(const\s+)?enum\s+\w/.test(trimmed)) {
|
|
663
|
+
const block = collectBalanced(lines, i);
|
|
664
|
+
parts.push(block.text);
|
|
665
|
+
i = block.nextIndex;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (/^export\s+(async\s+)?function\s+\w/.test(trimmed)) {
|
|
669
|
+
const sig = extractFnSignature(lines, i);
|
|
670
|
+
parts.push(`${sig};`);
|
|
671
|
+
i = skipBlock(lines, i);
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (/^export\s+(abstract\s+)?class\s+/.test(trimmed)) {
|
|
675
|
+
const nameMatch = trimmed.match(/class\s+(\w+)/);
|
|
676
|
+
const name = nameMatch?.[1] ?? "Unknown";
|
|
677
|
+
const end = skipBlock(lines, i);
|
|
678
|
+
const methods = [];
|
|
679
|
+
for (let j = i + 1; j < end; j++) {
|
|
680
|
+
const mt = lines[j].trim();
|
|
681
|
+
const mm = mt.match(/^(?:static\s+)?(?:async\s+)?(\w+)\s*\(/);
|
|
682
|
+
if (mm && mm[1] !== "constructor") methods.push(mm[1]);
|
|
683
|
+
}
|
|
684
|
+
parts.push(`export class ${name} { /* methods: ${methods.join(", ")} */ }`);
|
|
685
|
+
i = end;
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (/^export\s*(\{|\*)/.test(trimmed)) {
|
|
689
|
+
const block = collectBracedLine(lines, i);
|
|
690
|
+
parts.push(block.text);
|
|
691
|
+
i = block.nextIndex;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
i++;
|
|
694
695
|
}
|
|
695
696
|
return parts.join("\n");
|
|
696
697
|
}
|
|
698
|
+
function collectBracedLine(lines, start) {
|
|
699
|
+
let text = lines[start];
|
|
700
|
+
let i = start + 1;
|
|
701
|
+
while (i < lines.length && !text.includes(";") && !text.trimEnd().endsWith("'") && !text.trimEnd().endsWith('"')) {
|
|
702
|
+
text += "\n" + lines[i];
|
|
703
|
+
i++;
|
|
704
|
+
}
|
|
705
|
+
return { text, nextIndex: i };
|
|
706
|
+
}
|
|
707
|
+
function collectBalanced(lines, start) {
|
|
708
|
+
let depth = 0;
|
|
709
|
+
let text = "";
|
|
710
|
+
let i = start;
|
|
711
|
+
let started = false;
|
|
712
|
+
while (i < lines.length) {
|
|
713
|
+
const line = lines[i];
|
|
714
|
+
text += (text ? "\n" : "") + line;
|
|
715
|
+
for (const ch of line) {
|
|
716
|
+
if (ch === "{" || ch === "(") {
|
|
717
|
+
depth++;
|
|
718
|
+
started = true;
|
|
719
|
+
}
|
|
720
|
+
if (ch === "}" || ch === ")") depth--;
|
|
721
|
+
}
|
|
722
|
+
i++;
|
|
723
|
+
if (started && depth <= 0) break;
|
|
724
|
+
if (!started && line.includes(";")) break;
|
|
725
|
+
}
|
|
726
|
+
return { text, nextIndex: i };
|
|
727
|
+
}
|
|
728
|
+
function collectStatement(lines, start) {
|
|
729
|
+
let text = lines[start];
|
|
730
|
+
let i = start + 1;
|
|
731
|
+
if (text.includes(";")) return { text, nextIndex: i };
|
|
732
|
+
let depth = 0;
|
|
733
|
+
for (const ch of text) {
|
|
734
|
+
if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
735
|
+
if (ch === "}" || ch === ")" || ch === "]") depth--;
|
|
736
|
+
}
|
|
737
|
+
while (i < lines.length && depth > 0) {
|
|
738
|
+
text += "\n" + lines[i];
|
|
739
|
+
for (const ch of lines[i]) {
|
|
740
|
+
if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
741
|
+
if (ch === "}" || ch === ")" || ch === "]") depth--;
|
|
742
|
+
}
|
|
743
|
+
i++;
|
|
744
|
+
}
|
|
745
|
+
return { text, nextIndex: i };
|
|
746
|
+
}
|
|
747
|
+
function extractFnSignature(lines, start) {
|
|
748
|
+
let sig = "";
|
|
749
|
+
let i = start;
|
|
750
|
+
while (i < lines.length) {
|
|
751
|
+
const line = lines[i].trim();
|
|
752
|
+
sig += (sig ? " " : "") + line;
|
|
753
|
+
if (line.includes("{")) {
|
|
754
|
+
sig = sig.replace(/\s*\{[^]*$/, "").trim();
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
i++;
|
|
758
|
+
}
|
|
759
|
+
return sig;
|
|
760
|
+
}
|
|
761
|
+
function skipBlock(lines, start) {
|
|
762
|
+
let depth = 0;
|
|
763
|
+
let i = start;
|
|
764
|
+
let foundBrace = false;
|
|
765
|
+
while (i < lines.length) {
|
|
766
|
+
for (const ch of lines[i]) {
|
|
767
|
+
if (ch === "{") {
|
|
768
|
+
depth++;
|
|
769
|
+
foundBrace = true;
|
|
770
|
+
}
|
|
771
|
+
if (ch === "}") depth--;
|
|
772
|
+
}
|
|
773
|
+
i++;
|
|
774
|
+
if (foundBrace && depth <= 0) break;
|
|
775
|
+
if (!foundBrace && lines[i - 1].includes(";")) break;
|
|
776
|
+
}
|
|
777
|
+
return i;
|
|
778
|
+
}
|
|
779
|
+
function looksLikeFunctionDecl(lines, start) {
|
|
780
|
+
const chunk = lines.slice(start, Math.min(start + 5, lines.length)).join(" ");
|
|
781
|
+
return /=>/.test(chunk) || /=\s*function/.test(chunk);
|
|
782
|
+
}
|
|
783
|
+
function extractClassOutline(lines, start) {
|
|
784
|
+
const header = lines[start].trim();
|
|
785
|
+
let headerText = header;
|
|
786
|
+
let i = start + 1;
|
|
787
|
+
if (!header.includes("{")) {
|
|
788
|
+
while (i < lines.length) {
|
|
789
|
+
headerText += " " + lines[i].trim();
|
|
790
|
+
if (lines[i].includes("{")) {
|
|
791
|
+
i++;
|
|
792
|
+
break;
|
|
793
|
+
}
|
|
794
|
+
i++;
|
|
795
|
+
}
|
|
796
|
+
} else {
|
|
797
|
+
i = start + 1;
|
|
798
|
+
}
|
|
799
|
+
const bodyParts = [headerText.replace(/\{[^]*$/, "{").trim()];
|
|
800
|
+
let depth = 1;
|
|
801
|
+
while (i < lines.length && depth > 0) {
|
|
802
|
+
const line = lines[i];
|
|
803
|
+
const trimmed = line.trim();
|
|
804
|
+
for (const ch of line) {
|
|
805
|
+
if (ch === "{") depth++;
|
|
806
|
+
if (ch === "}") depth--;
|
|
807
|
+
}
|
|
808
|
+
if (depth <= 0) {
|
|
809
|
+
i++;
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
if (depth === 1) {
|
|
813
|
+
if (/^(private|protected|public|readonly|static|#)/.test(trimmed) && !trimmed.includes("(")) {
|
|
814
|
+
bodyParts.push(` ${trimmed}`);
|
|
815
|
+
} else if (/^constructor\s*\(/.test(trimmed)) {
|
|
816
|
+
const sig = extractFnSignature(lines, i);
|
|
817
|
+
bodyParts.push(` ${sig} { /* ... */ }`);
|
|
818
|
+
} else if (/^(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?\w+\s*[(<]/.test(trimmed) && !trimmed.startsWith("//")) {
|
|
819
|
+
const sig = extractFnSignature(lines, i);
|
|
820
|
+
bodyParts.push(` ${sig} { /* ... */ }`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
i++;
|
|
824
|
+
}
|
|
825
|
+
bodyParts.push("}");
|
|
826
|
+
return { text: bodyParts.join("\n"), nextIndex: i };
|
|
827
|
+
}
|
|
697
828
|
async function pruneGeneric(file, level) {
|
|
698
829
|
let content;
|
|
699
830
|
try {
|
|
@@ -754,22 +885,6 @@ function emptyResult(file, level) {
|
|
|
754
885
|
savingsPercent: 100
|
|
755
886
|
};
|
|
756
887
|
}
|
|
757
|
-
function addJSDoc(node, parts) {
|
|
758
|
-
if (!node.getJsDocs) return;
|
|
759
|
-
const docs = node.getJsDocs();
|
|
760
|
-
if (docs.length > 0) {
|
|
761
|
-
parts.push(docs[0].getText());
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
function findTsConfig(filePath) {
|
|
765
|
-
let dir = filePath;
|
|
766
|
-
for (let i = 0; i < 10; i++) {
|
|
767
|
-
dir = join2(dir, "..");
|
|
768
|
-
const candidate = join2(dir, "tsconfig.json");
|
|
769
|
-
if (existsSync2(candidate)) return candidate;
|
|
770
|
-
}
|
|
771
|
-
return void 0;
|
|
772
|
-
}
|
|
773
888
|
|
|
774
889
|
// src/engine/graph-utils.ts
|
|
775
890
|
function buildAdjacencyList(edges) {
|
|
@@ -1292,8 +1407,8 @@ async function optimizeContext(messages, analysis, config) {
|
|
|
1292
1407
|
}
|
|
1293
1408
|
|
|
1294
1409
|
// src/gateway/tracker.ts
|
|
1295
|
-
import { mkdirSync as mkdirSync2, appendFileSync, readFileSync as readFileSync3, readdirSync, existsSync as
|
|
1296
|
-
import { join as
|
|
1410
|
+
import { mkdirSync as mkdirSync2, appendFileSync, readFileSync as readFileSync3, readdirSync, existsSync as existsSync2 } from "fs";
|
|
1411
|
+
import { join as join2 } from "path";
|
|
1297
1412
|
import { randomUUID } from "crypto";
|
|
1298
1413
|
var UsageTracker = class {
|
|
1299
1414
|
logDir;
|
|
@@ -1305,7 +1420,7 @@ var UsageTracker = class {
|
|
|
1305
1420
|
memRecords = [];
|
|
1306
1421
|
constructor(config) {
|
|
1307
1422
|
this.config = config;
|
|
1308
|
-
this.logDir =
|
|
1423
|
+
this.logDir = join2(config.logDir, "usage");
|
|
1309
1424
|
mkdirSync2(this.logDir, { recursive: true });
|
|
1310
1425
|
}
|
|
1311
1426
|
// ===== EVENT SYSTEM =====
|
|
@@ -1328,7 +1443,7 @@ var UsageTracker = class {
|
|
|
1328
1443
|
...params
|
|
1329
1444
|
};
|
|
1330
1445
|
const monthKey = this.getMonthKey(record.timestamp);
|
|
1331
|
-
const logFile =
|
|
1446
|
+
const logFile = join2(this.logDir, `${monthKey}.jsonl`);
|
|
1332
1447
|
const line = JSON.stringify({
|
|
1333
1448
|
...record,
|
|
1334
1449
|
timestamp: record.timestamp.toISOString()
|
|
@@ -1462,8 +1577,8 @@ var UsageTracker = class {
|
|
|
1462
1577
|
return date.toISOString().slice(0, 7);
|
|
1463
1578
|
}
|
|
1464
1579
|
getMonthRecordsByKey(monthKey) {
|
|
1465
|
-
const filePath =
|
|
1466
|
-
if (!
|
|
1580
|
+
const filePath = join2(this.logDir, `${monthKey}.jsonl`);
|
|
1581
|
+
if (!existsSync2(filePath)) return [];
|
|
1467
1582
|
return readFileSync3(filePath, "utf-8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
1468
1583
|
try {
|
|
1469
1584
|
const parsed = JSON.parse(line);
|
|
@@ -1477,8 +1592,8 @@ var UsageTracker = class {
|
|
|
1477
1592
|
getMonthRecords(date) {
|
|
1478
1593
|
const monthKey = this.getMonthKey(date);
|
|
1479
1594
|
if (this.cache && this.cacheMonth === monthKey) return this.cache;
|
|
1480
|
-
const filePath =
|
|
1481
|
-
if (!
|
|
1595
|
+
const filePath = join2(this.logDir, `${monthKey}.jsonl`);
|
|
1596
|
+
if (!existsSync2(filePath)) return [];
|
|
1482
1597
|
const records = readFileSync3(filePath, "utf-8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
1483
1598
|
try {
|
|
1484
1599
|
const parsed = JSON.parse(line);
|
|
@@ -1493,11 +1608,11 @@ var UsageTracker = class {
|
|
|
1493
1608
|
return records;
|
|
1494
1609
|
}
|
|
1495
1610
|
getAllRecords() {
|
|
1496
|
-
if (!
|
|
1611
|
+
if (!existsSync2(this.logDir)) return [];
|
|
1497
1612
|
const files = readdirSync(this.logDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1498
1613
|
const allRecords = [];
|
|
1499
1614
|
for (const file of files) {
|
|
1500
|
-
const content = readFileSync3(
|
|
1615
|
+
const content = readFileSync3(join2(this.logDir, file), "utf-8");
|
|
1501
1616
|
const records = content.split("\n").filter((line) => line.trim()).map((line) => {
|
|
1502
1617
|
try {
|
|
1503
1618
|
const parsed = JSON.parse(line);
|
|
@@ -1515,7 +1630,7 @@ var UsageTracker = class {
|
|
|
1515
1630
|
|
|
1516
1631
|
// src/engine/analyzer.ts
|
|
1517
1632
|
import { readFile as readFile4, readdir, stat as stat2 } from "fs/promises";
|
|
1518
|
-
import { join as
|
|
1633
|
+
import { join as join4, extname, relative as relative3, resolve as resolve4, basename as basename2 } from "path";
|
|
1519
1634
|
import { createHash as createHash3 } from "crypto";
|
|
1520
1635
|
|
|
1521
1636
|
// src/types/engine.ts
|
|
@@ -1568,14 +1683,14 @@ var DEFAULT_CONFIG = {
|
|
|
1568
1683
|
};
|
|
1569
1684
|
|
|
1570
1685
|
// src/engine/graph.ts
|
|
1571
|
-
import { Project
|
|
1572
|
-
import { resolve as resolve3, relative as relative2, dirname as dirname2, join as
|
|
1573
|
-
import { existsSync as
|
|
1686
|
+
import { Project, SyntaxKind } from "ts-morph";
|
|
1687
|
+
import { resolve as resolve3, relative as relative2, dirname as dirname2, join as join3 } from "path";
|
|
1688
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1574
1689
|
var TS_EXTENSIONS2 = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mts", "mjs", "cts", "cjs"]);
|
|
1575
1690
|
function createProject(projectPath, filePaths) {
|
|
1576
|
-
const tsConfigPath =
|
|
1577
|
-
const hasTsConfig =
|
|
1578
|
-
const project = new
|
|
1691
|
+
const tsConfigPath = join3(projectPath, "tsconfig.json");
|
|
1692
|
+
const hasTsConfig = existsSync3(tsConfigPath);
|
|
1693
|
+
const project = new Project({
|
|
1579
1694
|
tsConfigFilePath: hasTsConfig ? tsConfigPath : void 0,
|
|
1580
1695
|
skipAddingFilesFromTsConfig: true,
|
|
1581
1696
|
compilerOptions: hasTsConfig ? void 0 : {
|
|
@@ -1775,7 +1890,7 @@ function enrichComplexity(project, absPath, files) {
|
|
|
1775
1890
|
}
|
|
1776
1891
|
for (const varDecl of sourceFile.getVariableDeclarations()) {
|
|
1777
1892
|
const init = varDecl.getInitializer();
|
|
1778
|
-
if (init && (init.getKind() ===
|
|
1893
|
+
if (init && (init.getKind() === SyntaxKind.ArrowFunction || init.getKind() === SyntaxKind.FunctionExpression)) {
|
|
1779
1894
|
totalComplexity += calculateCyclomaticComplexity(init);
|
|
1780
1895
|
}
|
|
1781
1896
|
}
|
|
@@ -1786,22 +1901,22 @@ function calculateCyclomaticComplexity(node) {
|
|
|
1786
1901
|
let complexity = 1;
|
|
1787
1902
|
node.forEachDescendant((descendant) => {
|
|
1788
1903
|
switch (descendant.getKind()) {
|
|
1789
|
-
case
|
|
1790
|
-
case
|
|
1791
|
-
case
|
|
1792
|
-
case
|
|
1793
|
-
case
|
|
1794
|
-
case
|
|
1795
|
-
case
|
|
1796
|
-
case
|
|
1797
|
-
case
|
|
1904
|
+
case SyntaxKind.IfStatement:
|
|
1905
|
+
case SyntaxKind.ConditionalExpression:
|
|
1906
|
+
case SyntaxKind.ForStatement:
|
|
1907
|
+
case SyntaxKind.ForInStatement:
|
|
1908
|
+
case SyntaxKind.ForOfStatement:
|
|
1909
|
+
case SyntaxKind.WhileStatement:
|
|
1910
|
+
case SyntaxKind.DoStatement:
|
|
1911
|
+
case SyntaxKind.CaseClause:
|
|
1912
|
+
case SyntaxKind.CatchClause:
|
|
1798
1913
|
complexity++;
|
|
1799
1914
|
break;
|
|
1800
|
-
case
|
|
1915
|
+
case SyntaxKind.BinaryExpression: {
|
|
1801
1916
|
const opToken = descendant.getOperatorToken?.();
|
|
1802
1917
|
if (opToken) {
|
|
1803
1918
|
const kind = opToken.getKind();
|
|
1804
|
-
if (kind ===
|
|
1919
|
+
if (kind === SyntaxKind.AmpersandAmpersandToken || kind === SyntaxKind.BarBarToken || kind === SyntaxKind.QuestionQuestionToken) {
|
|
1805
1920
|
complexity++;
|
|
1806
1921
|
}
|
|
1807
1922
|
}
|
|
@@ -1818,14 +1933,14 @@ function resolveImport(sourceFile, moduleSpecifier, projectRoot) {
|
|
|
1818
1933
|
const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
|
|
1819
1934
|
for (const ext of extensions) {
|
|
1820
1935
|
const candidate = basePath.endsWith(ext) ? basePath : basePath + ext;
|
|
1821
|
-
if (
|
|
1936
|
+
if (existsSync3(candidate)) {
|
|
1822
1937
|
const rel = relative2(projectRoot, candidate);
|
|
1823
1938
|
if (!rel.startsWith("..")) return rel;
|
|
1824
1939
|
}
|
|
1825
1940
|
}
|
|
1826
1941
|
if (moduleSpecifier.endsWith(".js")) {
|
|
1827
1942
|
const tsPath = basePath.replace(/\.js$/, ".ts");
|
|
1828
|
-
if (
|
|
1943
|
+
if (existsSync3(tsPath)) {
|
|
1829
1944
|
const rel = relative2(projectRoot, tsPath);
|
|
1830
1945
|
if (!rel.startsWith("..")) return rel;
|
|
1831
1946
|
}
|
|
@@ -1986,7 +2101,7 @@ async function walkProject(rootPath, options) {
|
|
|
1986
2101
|
}
|
|
1987
2102
|
const promises = [];
|
|
1988
2103
|
for (const entry of entries) {
|
|
1989
|
-
const fullPath =
|
|
2104
|
+
const fullPath = join4(dir, entry.name);
|
|
1990
2105
|
if (entry.isDirectory()) {
|
|
1991
2106
|
if (!ignoreDirSet.has(entry.name) && !entry.name.startsWith(".")) {
|
|
1992
2107
|
promises.push(walk(fullPath, depth + 1));
|