cto-ai-cli 4.0.0 → 5.1.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 +217 -312
- package/dist/action/index.js +281 -162
- package/dist/api/dashboard.js +281 -162
- package/dist/api/dashboard.js.map +1 -1
- package/dist/api/server.js +362 -184
- package/dist/api/server.js.map +1 -1
- package/dist/cli/gateway.js +358 -229
- package/dist/cli/score.js +2426 -1225
- package/dist/cli/v2/index.js +290 -175
- package/dist/cli/v2/index.js.map +1 -1
- package/dist/engine/index.d.ts +150 -1
- package/dist/engine/index.js +1130 -219
- package/dist/engine/index.js.map +1 -1
- package/dist/fsevents-X6WP4TKM.node +0 -0
- package/dist/gateway/index.d.ts +2 -2
- package/dist/gateway/index.js +358 -229
- 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 +297 -178
- package/dist/mcp/v2.js.map +1 -1
- package/package.json +8 -22
- package/dist/core/index.d.ts +0 -717
- package/dist/core/index.js +0 -4446
- package/dist/core/index.js.map +0 -1
package/dist/gateway/index.js
CHANGED
|
@@ -92,7 +92,8 @@ var GOOGLE_MODELS = [
|
|
|
92
92
|
{ id: "gemini-1.5-pro", contextWindow: 2e6, costPerMInput: 1.25, costPerMOutput: 5, maxOutput: 8192 }
|
|
93
93
|
];
|
|
94
94
|
function parseOpenAIRequest(body) {
|
|
95
|
-
const
|
|
95
|
+
const rawMessages = Array.isArray(body.messages) ? body.messages : [];
|
|
96
|
+
const messages = rawMessages.map((m) => ({
|
|
96
97
|
role: m.role || "user",
|
|
97
98
|
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
98
99
|
}));
|
|
@@ -104,34 +105,29 @@ function parseOpenAIRequest(body) {
|
|
|
104
105
|
temperature: body.temperature
|
|
105
106
|
};
|
|
106
107
|
}
|
|
107
|
-
function parseOpenAIResponse(body,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
outputTokens: body.usage?.completion_tokens || 0,
|
|
113
|
-
content: body.choices?.[0]?.message?.content || "",
|
|
114
|
-
finishReason: body.choices?.[0]?.finish_reason || "stop"
|
|
115
|
-
};
|
|
116
|
-
}
|
|
108
|
+
function parseOpenAIResponse(body, _streaming) {
|
|
109
|
+
const usage = body.usage;
|
|
110
|
+
const choices = Array.isArray(body.choices) ? body.choices : [];
|
|
111
|
+
const first = choices[0];
|
|
112
|
+
const msg = first?.message;
|
|
117
113
|
return {
|
|
118
114
|
model: body.model || "unknown",
|
|
119
|
-
inputTokens:
|
|
120
|
-
outputTokens:
|
|
121
|
-
content:
|
|
122
|
-
finishReason:
|
|
115
|
+
inputTokens: usage?.prompt_tokens || 0,
|
|
116
|
+
outputTokens: usage?.completion_tokens || 0,
|
|
117
|
+
content: msg?.content || "",
|
|
118
|
+
finishReason: first?.finish_reason || "stop"
|
|
123
119
|
};
|
|
124
120
|
}
|
|
125
121
|
function parseAnthropicRequest(body) {
|
|
126
122
|
const messages = [];
|
|
127
123
|
if (body.system) {
|
|
128
|
-
messages.push({ role: "system", content: body.system });
|
|
124
|
+
messages.push({ role: "system", content: String(body.system) });
|
|
129
125
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
});
|
|
126
|
+
const rawMessages = Array.isArray(body.messages) ? body.messages : [];
|
|
127
|
+
for (const m of rawMessages) {
|
|
128
|
+
const content = typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((b) => String(b.text ?? "")).join("\n") : "";
|
|
129
|
+
const role = m.role || "user";
|
|
130
|
+
messages.push({ role, content });
|
|
135
131
|
}
|
|
136
132
|
return {
|
|
137
133
|
model: body.model || "unknown",
|
|
@@ -142,43 +138,53 @@ function parseAnthropicRequest(body) {
|
|
|
142
138
|
};
|
|
143
139
|
}
|
|
144
140
|
function parseAnthropicResponse(body, _streaming) {
|
|
141
|
+
const usage = body.usage;
|
|
142
|
+
const contentBlocks = Array.isArray(body.content) ? body.content : [];
|
|
145
143
|
return {
|
|
146
144
|
model: body.model || "unknown",
|
|
147
|
-
inputTokens:
|
|
148
|
-
outputTokens:
|
|
149
|
-
content:
|
|
145
|
+
inputTokens: usage?.input_tokens || 0,
|
|
146
|
+
outputTokens: usage?.output_tokens || 0,
|
|
147
|
+
content: contentBlocks.map((b) => String(b.text ?? "")).join("\n"),
|
|
150
148
|
finishReason: body.stop_reason || "end_turn"
|
|
151
149
|
};
|
|
152
150
|
}
|
|
153
151
|
function parseGoogleRequest(body) {
|
|
154
152
|
const messages = [];
|
|
155
|
-
|
|
153
|
+
const sysInst = body.systemInstruction;
|
|
154
|
+
if (sysInst && Array.isArray(sysInst.parts)) {
|
|
156
155
|
messages.push({
|
|
157
156
|
role: "system",
|
|
158
|
-
content:
|
|
157
|
+
content: sysInst.parts.map((p) => String(p.text ?? "")).join("\n")
|
|
159
158
|
});
|
|
160
159
|
}
|
|
161
|
-
|
|
160
|
+
const contents = Array.isArray(body.contents) ? body.contents : [];
|
|
161
|
+
for (const item of contents) {
|
|
162
162
|
const role = item.role === "model" ? "assistant" : "user";
|
|
163
|
-
const
|
|
163
|
+
const parts = Array.isArray(item.parts) ? item.parts : [];
|
|
164
|
+
const content = parts.map((p) => String(p.text ?? "")).join("\n");
|
|
164
165
|
messages.push({ role, content });
|
|
165
166
|
}
|
|
166
167
|
const model = body.model || body.modelId || "gemini-2.0-flash";
|
|
168
|
+
const genConfig = body.generationConfig;
|
|
167
169
|
return {
|
|
168
170
|
model,
|
|
169
171
|
messages,
|
|
170
172
|
stream: body.stream === true,
|
|
171
|
-
maxTokens:
|
|
172
|
-
temperature:
|
|
173
|
+
maxTokens: genConfig?.maxOutputTokens,
|
|
174
|
+
temperature: genConfig?.temperature
|
|
173
175
|
};
|
|
174
176
|
}
|
|
175
177
|
function parseGoogleResponse(body, _streaming) {
|
|
176
|
-
const
|
|
178
|
+
const candidates = Array.isArray(body.candidates) ? body.candidates : [];
|
|
179
|
+
const candidate = candidates[0];
|
|
180
|
+
const usage = body.usageMetadata;
|
|
181
|
+
const candidateContent = candidate?.content;
|
|
182
|
+
const parts = Array.isArray(candidateContent?.parts) ? candidateContent.parts : [];
|
|
177
183
|
return {
|
|
178
184
|
model: body.modelVersion || body.model || "gemini-2.0-flash",
|
|
179
|
-
inputTokens:
|
|
180
|
-
outputTokens:
|
|
181
|
-
content:
|
|
185
|
+
inputTokens: usage?.promptTokenCount || 0,
|
|
186
|
+
outputTokens: usage?.candidatesTokenCount || 0,
|
|
187
|
+
content: parts.map((p) => String(p.text ?? "")).join("\n"),
|
|
182
188
|
finishReason: candidate?.finishReason || "STOP"
|
|
183
189
|
};
|
|
184
190
|
}
|
|
@@ -478,10 +484,7 @@ function deduplicateFindings(findings) {
|
|
|
478
484
|
import { createHash as createHash2 } from "crypto";
|
|
479
485
|
|
|
480
486
|
// src/engine/pruner.ts
|
|
481
|
-
import { Project, SyntaxKind } from "ts-morph";
|
|
482
487
|
import { readFile as readFile3 } from "fs/promises";
|
|
483
|
-
import { existsSync as existsSync2 } from "fs";
|
|
484
|
-
import { join as join2 } from "path";
|
|
485
488
|
|
|
486
489
|
// src/engine/tokenizer.ts
|
|
487
490
|
import { encodingForModel } from "js-tiktoken";
|
|
@@ -536,23 +539,7 @@ async function pruneTypeScript(file, level) {
|
|
|
536
539
|
} catch {
|
|
537
540
|
return emptyResult(file, level);
|
|
538
541
|
}
|
|
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);
|
|
542
|
+
const prunedContent = level === "signatures" ? extractSignaturesRegex(content) : extractSkeletonRegex(content);
|
|
556
543
|
const prunedTokens = countTokensChars4(Buffer.byteLength(prunedContent, "utf-8"));
|
|
557
544
|
const savingsPercent = file.tokens > 0 ? (file.tokens - prunedTokens) / file.tokens * 100 : 0;
|
|
558
545
|
return {
|
|
@@ -564,131 +551,281 @@ async function pruneTypeScript(file, level) {
|
|
|
564
551
|
savingsPercent: Math.max(0, savingsPercent)
|
|
565
552
|
};
|
|
566
553
|
}
|
|
567
|
-
function
|
|
554
|
+
function extractSignaturesRegex(content) {
|
|
555
|
+
const lines = content.split("\n");
|
|
568
556
|
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
|
-
|
|
557
|
+
let i = 0;
|
|
558
|
+
while (i < lines.length) {
|
|
559
|
+
const line = lines[i];
|
|
560
|
+
const trimmed = line.trim();
|
|
561
|
+
if (trimmed === "") {
|
|
562
|
+
i++;
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
if (trimmed.startsWith("/**")) {
|
|
566
|
+
const docLines = [];
|
|
567
|
+
while (i < lines.length) {
|
|
568
|
+
docLines.push(lines[i]);
|
|
569
|
+
if (lines[i].includes("*/")) {
|
|
570
|
+
i++;
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
i++;
|
|
574
|
+
}
|
|
575
|
+
parts.push(docLines.join("\n"));
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
if (trimmed.startsWith("//")) {
|
|
579
|
+
parts.push(line);
|
|
580
|
+
i++;
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (/^\s*(import|export)\s/.test(line) && (trimmed.includes(" from ") || trimmed.startsWith("import "))) {
|
|
584
|
+
const block = collectBracedLine(lines, i);
|
|
585
|
+
parts.push(block.text);
|
|
586
|
+
i = block.nextIndex;
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
if (/^\s*export\s*(\{|\*)/.test(trimmed)) {
|
|
590
|
+
const block = collectBracedLine(lines, i);
|
|
591
|
+
parts.push(block.text);
|
|
592
|
+
i = block.nextIndex;
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (/^\s*(export\s+)?type\s+\w/.test(trimmed) && !trimmed.startsWith("typeof")) {
|
|
596
|
+
const block = collectBalanced(lines, i);
|
|
597
|
+
parts.push(block.text);
|
|
598
|
+
i = block.nextIndex;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (/^\s*(export\s+)?interface\s+\w/.test(trimmed)) {
|
|
602
|
+
const block = collectBalanced(lines, i);
|
|
603
|
+
parts.push(block.text);
|
|
604
|
+
i = block.nextIndex;
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
if (/^\s*(export\s+)?(const\s+)?enum\s+\w/.test(trimmed)) {
|
|
608
|
+
const block = collectBalanced(lines, i);
|
|
609
|
+
parts.push(block.text);
|
|
610
|
+
i = block.nextIndex;
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
const fnMatch = trimmed.match(/^(export\s+)?(async\s+)?function\s+(\w+)/);
|
|
614
|
+
if (fnMatch) {
|
|
615
|
+
const sig = extractFnSignature(lines, i);
|
|
616
|
+
parts.push(`${sig} { /* ... */ }`);
|
|
617
|
+
i = skipBlock(lines, i);
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
const arrowMatch = trimmed.match(/^(export\s+)?(const|let|var)\s+(\w+)/);
|
|
621
|
+
if (arrowMatch && looksLikeFunctionDecl(lines, i)) {
|
|
622
|
+
const prefix = trimmed.match(/^((?:export\s+)?(?:const|let|var)\s+\w+[^=]*=)/)?.[1];
|
|
623
|
+
if (prefix) {
|
|
624
|
+
parts.push(`${prefix} /* ... */;`);
|
|
612
625
|
}
|
|
626
|
+
i = skipBlock(lines, i);
|
|
627
|
+
continue;
|
|
613
628
|
}
|
|
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());
|
|
629
|
+
if (arrowMatch) {
|
|
630
|
+
const block = collectStatement(lines, i);
|
|
631
|
+
parts.push(block.text);
|
|
632
|
+
i = block.nextIndex;
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
if (/^\s*(export\s+)?(abstract\s+)?class\s+\w/.test(trimmed)) {
|
|
636
|
+
const classOutline = extractClassOutline(lines, i);
|
|
637
|
+
parts.push(classOutline.text);
|
|
638
|
+
i = classOutline.nextIndex;
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
i++;
|
|
653
642
|
}
|
|
654
643
|
return parts.join("\n");
|
|
655
644
|
}
|
|
656
|
-
function
|
|
645
|
+
function extractSkeletonRegex(content) {
|
|
646
|
+
const lines = content.split("\n");
|
|
657
647
|
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
|
-
|
|
648
|
+
let i = 0;
|
|
649
|
+
while (i < lines.length) {
|
|
650
|
+
const trimmed = lines[i].trim();
|
|
651
|
+
if (/^import\s/.test(trimmed)) {
|
|
652
|
+
const block = collectBracedLine(lines, i);
|
|
653
|
+
parts.push(block.text);
|
|
654
|
+
i = block.nextIndex;
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if (/^export\s+(type|interface)\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+(const\s+)?enum\s+\w/.test(trimmed)) {
|
|
664
|
+
const block = collectBalanced(lines, i);
|
|
665
|
+
parts.push(block.text);
|
|
666
|
+
i = block.nextIndex;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (/^export\s+(async\s+)?function\s+\w/.test(trimmed)) {
|
|
670
|
+
const sig = extractFnSignature(lines, i);
|
|
671
|
+
parts.push(`${sig};`);
|
|
672
|
+
i = skipBlock(lines, i);
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (/^export\s+(abstract\s+)?class\s+/.test(trimmed)) {
|
|
676
|
+
const nameMatch = trimmed.match(/class\s+(\w+)/);
|
|
677
|
+
const name = nameMatch?.[1] ?? "Unknown";
|
|
678
|
+
const end = skipBlock(lines, i);
|
|
679
|
+
const methods = [];
|
|
680
|
+
for (let j = i + 1; j < end; j++) {
|
|
681
|
+
const mt = lines[j].trim();
|
|
682
|
+
const mm = mt.match(/^(?:static\s+)?(?:async\s+)?(\w+)\s*\(/);
|
|
683
|
+
if (mm && mm[1] !== "constructor") methods.push(mm[1]);
|
|
684
|
+
}
|
|
685
|
+
parts.push(`export class ${name} { /* methods: ${methods.join(", ")} */ }`);
|
|
686
|
+
i = end;
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (/^export\s*(\{|\*)/.test(trimmed)) {
|
|
690
|
+
const block = collectBracedLine(lines, i);
|
|
691
|
+
parts.push(block.text);
|
|
692
|
+
i = block.nextIndex;
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
i++;
|
|
689
696
|
}
|
|
690
697
|
return parts.join("\n");
|
|
691
698
|
}
|
|
699
|
+
function collectBracedLine(lines, start) {
|
|
700
|
+
let text = lines[start];
|
|
701
|
+
let i = start + 1;
|
|
702
|
+
while (i < lines.length && !text.includes(";") && !text.trimEnd().endsWith("'") && !text.trimEnd().endsWith('"')) {
|
|
703
|
+
text += "\n" + lines[i];
|
|
704
|
+
i++;
|
|
705
|
+
}
|
|
706
|
+
return { text, nextIndex: i };
|
|
707
|
+
}
|
|
708
|
+
function collectBalanced(lines, start) {
|
|
709
|
+
let depth = 0;
|
|
710
|
+
let text = "";
|
|
711
|
+
let i = start;
|
|
712
|
+
let started = false;
|
|
713
|
+
while (i < lines.length) {
|
|
714
|
+
const line = lines[i];
|
|
715
|
+
text += (text ? "\n" : "") + line;
|
|
716
|
+
for (const ch of line) {
|
|
717
|
+
if (ch === "{" || ch === "(") {
|
|
718
|
+
depth++;
|
|
719
|
+
started = true;
|
|
720
|
+
}
|
|
721
|
+
if (ch === "}" || ch === ")") depth--;
|
|
722
|
+
}
|
|
723
|
+
i++;
|
|
724
|
+
if (started && depth <= 0) break;
|
|
725
|
+
if (!started && line.includes(";")) break;
|
|
726
|
+
}
|
|
727
|
+
return { text, nextIndex: i };
|
|
728
|
+
}
|
|
729
|
+
function collectStatement(lines, start) {
|
|
730
|
+
let text = lines[start];
|
|
731
|
+
let i = start + 1;
|
|
732
|
+
if (text.includes(";")) return { text, nextIndex: i };
|
|
733
|
+
let depth = 0;
|
|
734
|
+
for (const ch of text) {
|
|
735
|
+
if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
736
|
+
if (ch === "}" || ch === ")" || ch === "]") depth--;
|
|
737
|
+
}
|
|
738
|
+
while (i < lines.length && depth > 0) {
|
|
739
|
+
text += "\n" + lines[i];
|
|
740
|
+
for (const ch of lines[i]) {
|
|
741
|
+
if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
742
|
+
if (ch === "}" || ch === ")" || ch === "]") depth--;
|
|
743
|
+
}
|
|
744
|
+
i++;
|
|
745
|
+
}
|
|
746
|
+
return { text, nextIndex: i };
|
|
747
|
+
}
|
|
748
|
+
function extractFnSignature(lines, start) {
|
|
749
|
+
let sig = "";
|
|
750
|
+
let i = start;
|
|
751
|
+
while (i < lines.length) {
|
|
752
|
+
const line = lines[i].trim();
|
|
753
|
+
sig += (sig ? " " : "") + line;
|
|
754
|
+
if (line.includes("{")) {
|
|
755
|
+
sig = sig.replace(/\s*\{[^]*$/, "").trim();
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
i++;
|
|
759
|
+
}
|
|
760
|
+
return sig;
|
|
761
|
+
}
|
|
762
|
+
function skipBlock(lines, start) {
|
|
763
|
+
let depth = 0;
|
|
764
|
+
let i = start;
|
|
765
|
+
let foundBrace = false;
|
|
766
|
+
while (i < lines.length) {
|
|
767
|
+
for (const ch of lines[i]) {
|
|
768
|
+
if (ch === "{") {
|
|
769
|
+
depth++;
|
|
770
|
+
foundBrace = true;
|
|
771
|
+
}
|
|
772
|
+
if (ch === "}") depth--;
|
|
773
|
+
}
|
|
774
|
+
i++;
|
|
775
|
+
if (foundBrace && depth <= 0) break;
|
|
776
|
+
if (!foundBrace && lines[i - 1].includes(";")) break;
|
|
777
|
+
}
|
|
778
|
+
return i;
|
|
779
|
+
}
|
|
780
|
+
function looksLikeFunctionDecl(lines, start) {
|
|
781
|
+
const chunk = lines.slice(start, Math.min(start + 5, lines.length)).join(" ");
|
|
782
|
+
return /=>/.test(chunk) || /=\s*function/.test(chunk);
|
|
783
|
+
}
|
|
784
|
+
function extractClassOutline(lines, start) {
|
|
785
|
+
const header = lines[start].trim();
|
|
786
|
+
let headerText = header;
|
|
787
|
+
let i = start + 1;
|
|
788
|
+
if (!header.includes("{")) {
|
|
789
|
+
while (i < lines.length) {
|
|
790
|
+
headerText += " " + lines[i].trim();
|
|
791
|
+
if (lines[i].includes("{")) {
|
|
792
|
+
i++;
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
i++;
|
|
796
|
+
}
|
|
797
|
+
} else {
|
|
798
|
+
i = start + 1;
|
|
799
|
+
}
|
|
800
|
+
const bodyParts = [headerText.replace(/\{[^]*$/, "{").trim()];
|
|
801
|
+
let depth = 1;
|
|
802
|
+
while (i < lines.length && depth > 0) {
|
|
803
|
+
const line = lines[i];
|
|
804
|
+
const trimmed = line.trim();
|
|
805
|
+
for (const ch of line) {
|
|
806
|
+
if (ch === "{") depth++;
|
|
807
|
+
if (ch === "}") depth--;
|
|
808
|
+
}
|
|
809
|
+
if (depth <= 0) {
|
|
810
|
+
i++;
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
if (depth === 1) {
|
|
814
|
+
if (/^(private|protected|public|readonly|static|#)/.test(trimmed) && !trimmed.includes("(")) {
|
|
815
|
+
bodyParts.push(` ${trimmed}`);
|
|
816
|
+
} else if (/^constructor\s*\(/.test(trimmed)) {
|
|
817
|
+
const sig = extractFnSignature(lines, i);
|
|
818
|
+
bodyParts.push(` ${sig} { /* ... */ }`);
|
|
819
|
+
} else if (/^(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?\w+\s*[(<]/.test(trimmed) && !trimmed.startsWith("//")) {
|
|
820
|
+
const sig = extractFnSignature(lines, i);
|
|
821
|
+
bodyParts.push(` ${sig} { /* ... */ }`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
i++;
|
|
825
|
+
}
|
|
826
|
+
bodyParts.push("}");
|
|
827
|
+
return { text: bodyParts.join("\n"), nextIndex: i };
|
|
828
|
+
}
|
|
692
829
|
async function pruneGeneric(file, level) {
|
|
693
830
|
let content;
|
|
694
831
|
try {
|
|
@@ -749,22 +886,6 @@ function emptyResult(file, level) {
|
|
|
749
886
|
savingsPercent: 100
|
|
750
887
|
};
|
|
751
888
|
}
|
|
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
889
|
|
|
769
890
|
// src/engine/graph-utils.ts
|
|
770
891
|
function buildAdjacencyList(edges) {
|
|
@@ -1281,14 +1402,15 @@ async function optimizeContext(messages, analysis, config) {
|
|
|
1281
1402
|
);
|
|
1282
1403
|
return { messages: optimizedMessages, injected: true, optimizeDecisions };
|
|
1283
1404
|
} catch (err) {
|
|
1284
|
-
|
|
1405
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1406
|
+
optimizeDecisions.push(`Context optimization failed: ${errMsg}`);
|
|
1285
1407
|
return { messages, injected: false, optimizeDecisions };
|
|
1286
1408
|
}
|
|
1287
1409
|
}
|
|
1288
1410
|
|
|
1289
1411
|
// src/gateway/tracker.ts
|
|
1290
|
-
import { mkdirSync as mkdirSync2, appendFileSync, readFileSync as readFileSync3, readdirSync, existsSync as
|
|
1291
|
-
import { join as
|
|
1412
|
+
import { mkdirSync as mkdirSync2, appendFileSync, readFileSync as readFileSync3, readdirSync, existsSync as existsSync2 } from "fs";
|
|
1413
|
+
import { join as join2 } from "path";
|
|
1292
1414
|
import { randomUUID } from "crypto";
|
|
1293
1415
|
var UsageTracker = class {
|
|
1294
1416
|
logDir;
|
|
@@ -1300,7 +1422,7 @@ var UsageTracker = class {
|
|
|
1300
1422
|
memRecords = [];
|
|
1301
1423
|
constructor(config) {
|
|
1302
1424
|
this.config = config;
|
|
1303
|
-
this.logDir =
|
|
1425
|
+
this.logDir = join2(config.logDir, "usage");
|
|
1304
1426
|
mkdirSync2(this.logDir, { recursive: true });
|
|
1305
1427
|
}
|
|
1306
1428
|
// ===== EVENT SYSTEM =====
|
|
@@ -1323,7 +1445,7 @@ var UsageTracker = class {
|
|
|
1323
1445
|
...params
|
|
1324
1446
|
};
|
|
1325
1447
|
const monthKey = this.getMonthKey(record.timestamp);
|
|
1326
|
-
const logFile =
|
|
1448
|
+
const logFile = join2(this.logDir, `${monthKey}.jsonl`);
|
|
1327
1449
|
const line = JSON.stringify({
|
|
1328
1450
|
...record,
|
|
1329
1451
|
timestamp: record.timestamp.toISOString()
|
|
@@ -1457,8 +1579,8 @@ var UsageTracker = class {
|
|
|
1457
1579
|
return date.toISOString().slice(0, 7);
|
|
1458
1580
|
}
|
|
1459
1581
|
getMonthRecordsByKey(monthKey) {
|
|
1460
|
-
const filePath =
|
|
1461
|
-
if (!
|
|
1582
|
+
const filePath = join2(this.logDir, `${monthKey}.jsonl`);
|
|
1583
|
+
if (!existsSync2(filePath)) return [];
|
|
1462
1584
|
return readFileSync3(filePath, "utf-8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
1463
1585
|
try {
|
|
1464
1586
|
const parsed = JSON.parse(line);
|
|
@@ -1472,8 +1594,8 @@ var UsageTracker = class {
|
|
|
1472
1594
|
getMonthRecords(date) {
|
|
1473
1595
|
const monthKey = this.getMonthKey(date);
|
|
1474
1596
|
if (this.cache && this.cacheMonth === monthKey) return this.cache;
|
|
1475
|
-
const filePath =
|
|
1476
|
-
if (!
|
|
1597
|
+
const filePath = join2(this.logDir, `${monthKey}.jsonl`);
|
|
1598
|
+
if (!existsSync2(filePath)) return [];
|
|
1477
1599
|
const records = readFileSync3(filePath, "utf-8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
1478
1600
|
try {
|
|
1479
1601
|
const parsed = JSON.parse(line);
|
|
@@ -1488,11 +1610,11 @@ var UsageTracker = class {
|
|
|
1488
1610
|
return records;
|
|
1489
1611
|
}
|
|
1490
1612
|
getAllRecords() {
|
|
1491
|
-
if (!
|
|
1613
|
+
if (!existsSync2(this.logDir)) return [];
|
|
1492
1614
|
const files = readdirSync(this.logDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1493
1615
|
const allRecords = [];
|
|
1494
1616
|
for (const file of files) {
|
|
1495
|
-
const content = readFileSync3(
|
|
1617
|
+
const content = readFileSync3(join2(this.logDir, file), "utf-8");
|
|
1496
1618
|
const records = content.split("\n").filter((line) => line.trim()).map((line) => {
|
|
1497
1619
|
try {
|
|
1498
1620
|
const parsed = JSON.parse(line);
|
|
@@ -1510,7 +1632,7 @@ var UsageTracker = class {
|
|
|
1510
1632
|
|
|
1511
1633
|
// src/engine/analyzer.ts
|
|
1512
1634
|
import { readFile as readFile4, readdir, stat as stat2 } from "fs/promises";
|
|
1513
|
-
import { join as
|
|
1635
|
+
import { join as join4, extname, relative as relative3, resolve as resolve4, basename as basename2 } from "path";
|
|
1514
1636
|
import { createHash as createHash3 } from "crypto";
|
|
1515
1637
|
|
|
1516
1638
|
// src/types/engine.ts
|
|
@@ -1563,14 +1685,14 @@ var DEFAULT_CONFIG = {
|
|
|
1563
1685
|
};
|
|
1564
1686
|
|
|
1565
1687
|
// src/engine/graph.ts
|
|
1566
|
-
import { Project
|
|
1567
|
-
import { resolve as resolve3, relative as relative2, dirname as dirname2, join as
|
|
1568
|
-
import { existsSync as
|
|
1688
|
+
import { Project, SyntaxKind } from "ts-morph";
|
|
1689
|
+
import { resolve as resolve3, relative as relative2, dirname as dirname2, join as join3 } from "path";
|
|
1690
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1569
1691
|
var TS_EXTENSIONS2 = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mts", "mjs", "cts", "cjs"]);
|
|
1570
1692
|
function createProject(projectPath, filePaths) {
|
|
1571
|
-
const tsConfigPath =
|
|
1572
|
-
const hasTsConfig =
|
|
1573
|
-
const project = new
|
|
1693
|
+
const tsConfigPath = join3(projectPath, "tsconfig.json");
|
|
1694
|
+
const hasTsConfig = existsSync3(tsConfigPath);
|
|
1695
|
+
const project = new Project({
|
|
1574
1696
|
tsConfigFilePath: hasTsConfig ? tsConfigPath : void 0,
|
|
1575
1697
|
skipAddingFilesFromTsConfig: true,
|
|
1576
1698
|
compilerOptions: hasTsConfig ? void 0 : {
|
|
@@ -1770,7 +1892,7 @@ function enrichComplexity(project, absPath, files) {
|
|
|
1770
1892
|
}
|
|
1771
1893
|
for (const varDecl of sourceFile.getVariableDeclarations()) {
|
|
1772
1894
|
const init = varDecl.getInitializer();
|
|
1773
|
-
if (init && (init.getKind() ===
|
|
1895
|
+
if (init && (init.getKind() === SyntaxKind.ArrowFunction || init.getKind() === SyntaxKind.FunctionExpression)) {
|
|
1774
1896
|
totalComplexity += calculateCyclomaticComplexity(init);
|
|
1775
1897
|
}
|
|
1776
1898
|
}
|
|
@@ -1781,22 +1903,22 @@ function calculateCyclomaticComplexity(node) {
|
|
|
1781
1903
|
let complexity = 1;
|
|
1782
1904
|
node.forEachDescendant((descendant) => {
|
|
1783
1905
|
switch (descendant.getKind()) {
|
|
1784
|
-
case
|
|
1785
|
-
case
|
|
1786
|
-
case
|
|
1787
|
-
case
|
|
1788
|
-
case
|
|
1789
|
-
case
|
|
1790
|
-
case
|
|
1791
|
-
case
|
|
1792
|
-
case
|
|
1906
|
+
case SyntaxKind.IfStatement:
|
|
1907
|
+
case SyntaxKind.ConditionalExpression:
|
|
1908
|
+
case SyntaxKind.ForStatement:
|
|
1909
|
+
case SyntaxKind.ForInStatement:
|
|
1910
|
+
case SyntaxKind.ForOfStatement:
|
|
1911
|
+
case SyntaxKind.WhileStatement:
|
|
1912
|
+
case SyntaxKind.DoStatement:
|
|
1913
|
+
case SyntaxKind.CaseClause:
|
|
1914
|
+
case SyntaxKind.CatchClause:
|
|
1793
1915
|
complexity++;
|
|
1794
1916
|
break;
|
|
1795
|
-
case
|
|
1917
|
+
case SyntaxKind.BinaryExpression: {
|
|
1796
1918
|
const opToken = descendant.getOperatorToken?.();
|
|
1797
1919
|
if (opToken) {
|
|
1798
1920
|
const kind = opToken.getKind();
|
|
1799
|
-
if (kind ===
|
|
1921
|
+
if (kind === SyntaxKind.AmpersandAmpersandToken || kind === SyntaxKind.BarBarToken || kind === SyntaxKind.QuestionQuestionToken) {
|
|
1800
1922
|
complexity++;
|
|
1801
1923
|
}
|
|
1802
1924
|
}
|
|
@@ -1813,14 +1935,14 @@ function resolveImport(sourceFile, moduleSpecifier, projectRoot) {
|
|
|
1813
1935
|
const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
|
|
1814
1936
|
for (const ext of extensions) {
|
|
1815
1937
|
const candidate = basePath.endsWith(ext) ? basePath : basePath + ext;
|
|
1816
|
-
if (
|
|
1938
|
+
if (existsSync3(candidate)) {
|
|
1817
1939
|
const rel = relative2(projectRoot, candidate);
|
|
1818
1940
|
if (!rel.startsWith("..")) return rel;
|
|
1819
1941
|
}
|
|
1820
1942
|
}
|
|
1821
1943
|
if (moduleSpecifier.endsWith(".js")) {
|
|
1822
1944
|
const tsPath = basePath.replace(/\.js$/, ".ts");
|
|
1823
|
-
if (
|
|
1945
|
+
if (existsSync3(tsPath)) {
|
|
1824
1946
|
const rel = relative2(projectRoot, tsPath);
|
|
1825
1947
|
if (!rel.startsWith("..")) return rel;
|
|
1826
1948
|
}
|
|
@@ -1981,7 +2103,7 @@ async function walkProject(rootPath, options) {
|
|
|
1981
2103
|
}
|
|
1982
2104
|
const promises = [];
|
|
1983
2105
|
for (const entry of entries) {
|
|
1984
|
-
const fullPath =
|
|
2106
|
+
const fullPath = join4(dir, entry.name);
|
|
1985
2107
|
if (entry.isDirectory()) {
|
|
1986
2108
|
if (!ignoreDirSet.has(entry.name) && !entry.name.startsWith(".")) {
|
|
1987
2109
|
promises.push(walk(fullPath, depth + 1));
|
|
@@ -2066,8 +2188,8 @@ async function analyzeProject(projectPath, config) {
|
|
|
2066
2188
|
maxDepth: mergedConfig.analysis.maxDepth
|
|
2067
2189
|
});
|
|
2068
2190
|
const tokenMethod = mergedConfig.tokens.method;
|
|
2069
|
-
const
|
|
2070
|
-
|
|
2191
|
+
const BATCH_SIZE = 50;
|
|
2192
|
+
async function estimateFileTokens(entry) {
|
|
2071
2193
|
let tokens;
|
|
2072
2194
|
if (tokenMethod === "tiktoken") {
|
|
2073
2195
|
try {
|
|
@@ -2079,7 +2201,7 @@ async function analyzeProject(projectPath, config) {
|
|
|
2079
2201
|
} else {
|
|
2080
2202
|
tokens = countTokensChars4(entry.size);
|
|
2081
2203
|
}
|
|
2082
|
-
|
|
2204
|
+
return {
|
|
2083
2205
|
path: entry.path,
|
|
2084
2206
|
relativePath: entry.relativePath,
|
|
2085
2207
|
extension: entry.extension,
|
|
@@ -2088,16 +2210,20 @@ async function analyzeProject(projectPath, config) {
|
|
|
2088
2210
|
lines: entry.lines,
|
|
2089
2211
|
lastModified: entry.lastModified,
|
|
2090
2212
|
kind: classifyFileKind(entry.relativePath),
|
|
2091
|
-
// Graph data — populated by graph analysis
|
|
2092
2213
|
imports: [],
|
|
2093
2214
|
importedBy: [],
|
|
2094
2215
|
isHub: false,
|
|
2095
2216
|
complexity: 0,
|
|
2096
|
-
// Risk data — populated by risk analysis
|
|
2097
2217
|
riskScore: 0,
|
|
2098
2218
|
riskFactors: [],
|
|
2099
2219
|
exclusionImpact: "none"
|
|
2100
|
-
}
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
const files = [];
|
|
2223
|
+
for (let i = 0; i < walkEntries.length; i += BATCH_SIZE) {
|
|
2224
|
+
const batch = walkEntries.slice(i, i + BATCH_SIZE);
|
|
2225
|
+
const results = await Promise.all(batch.map(estimateFileTokens));
|
|
2226
|
+
files.push(...results);
|
|
2101
2227
|
}
|
|
2102
2228
|
const graph = buildProjectGraph(absPath, files);
|
|
2103
2229
|
for (const file of files) {
|
|
@@ -2243,7 +2369,8 @@ var ContextGateway = class {
|
|
|
2243
2369
|
this.analysis = analysis;
|
|
2244
2370
|
return analysis;
|
|
2245
2371
|
} catch (err) {
|
|
2246
|
-
|
|
2372
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2373
|
+
this.emit({ type: "error", message: `Analysis failed: ${message}`, error: err instanceof Error ? err : void 0 });
|
|
2247
2374
|
throw err;
|
|
2248
2375
|
}
|
|
2249
2376
|
}
|
|
@@ -2280,7 +2407,8 @@ var ContextGateway = class {
|
|
|
2280
2407
|
try {
|
|
2281
2408
|
body = await readBody(req, this.config.maxBodyBytes);
|
|
2282
2409
|
} catch (err) {
|
|
2283
|
-
const
|
|
2410
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2411
|
+
const status = errMsg === "body-too-large" ? 413 : 400;
|
|
2284
2412
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
2285
2413
|
res.end(JSON.stringify({ error: status === 413 ? `Request body too large. Max: ${Math.round(this.config.maxBodyBytes / 1024 / 1024)}MB` : "Failed to read request body" }));
|
|
2286
2414
|
return;
|
|
@@ -2391,12 +2519,13 @@ var ContextGateway = class {
|
|
|
2391
2519
|
try {
|
|
2392
2520
|
await this.proxyRequest(targetUrl, req, res, modifiedBody, provider, parsed, interceptResult, startTime);
|
|
2393
2521
|
} catch (err) {
|
|
2522
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2394
2523
|
if (!res.headersSent) {
|
|
2395
|
-
const status =
|
|
2524
|
+
const status = errMsg === "upstream-timeout" ? 504 : 502;
|
|
2396
2525
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
2397
|
-
res.end(JSON.stringify({ error: status === 504 ? "Upstream provider timeout" : `Proxy error: ${
|
|
2526
|
+
res.end(JSON.stringify({ error: status === 504 ? "Upstream provider timeout" : `Proxy error: ${errMsg}` }));
|
|
2398
2527
|
}
|
|
2399
|
-
this.emit({ type: "error", message: `Proxy error: ${
|
|
2528
|
+
this.emit({ type: "error", message: `Proxy error: ${errMsg}`, error: err instanceof Error ? err : void 0 });
|
|
2400
2529
|
}
|
|
2401
2530
|
}
|
|
2402
2531
|
// ===== PROXY =====
|