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/cli/gateway.js
CHANGED
|
@@ -97,7 +97,8 @@ var GOOGLE_MODELS = [
|
|
|
97
97
|
{ id: "gemini-1.5-pro", contextWindow: 2e6, costPerMInput: 1.25, costPerMOutput: 5, maxOutput: 8192 }
|
|
98
98
|
];
|
|
99
99
|
function parseOpenAIRequest(body) {
|
|
100
|
-
const
|
|
100
|
+
const rawMessages = Array.isArray(body.messages) ? body.messages : [];
|
|
101
|
+
const messages = rawMessages.map((m) => ({
|
|
101
102
|
role: m.role || "user",
|
|
102
103
|
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
103
104
|
}));
|
|
@@ -109,34 +110,29 @@ function parseOpenAIRequest(body) {
|
|
|
109
110
|
temperature: body.temperature
|
|
110
111
|
};
|
|
111
112
|
}
|
|
112
|
-
function parseOpenAIResponse(body,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
outputTokens: body.usage?.completion_tokens || 0,
|
|
118
|
-
content: body.choices?.[0]?.message?.content || "",
|
|
119
|
-
finishReason: body.choices?.[0]?.finish_reason || "stop"
|
|
120
|
-
};
|
|
121
|
-
}
|
|
113
|
+
function parseOpenAIResponse(body, _streaming) {
|
|
114
|
+
const usage = body.usage;
|
|
115
|
+
const choices = Array.isArray(body.choices) ? body.choices : [];
|
|
116
|
+
const first = choices[0];
|
|
117
|
+
const msg = first?.message;
|
|
122
118
|
return {
|
|
123
119
|
model: body.model || "unknown",
|
|
124
|
-
inputTokens:
|
|
125
|
-
outputTokens:
|
|
126
|
-
content:
|
|
127
|
-
finishReason:
|
|
120
|
+
inputTokens: usage?.prompt_tokens || 0,
|
|
121
|
+
outputTokens: usage?.completion_tokens || 0,
|
|
122
|
+
content: msg?.content || "",
|
|
123
|
+
finishReason: first?.finish_reason || "stop"
|
|
128
124
|
};
|
|
129
125
|
}
|
|
130
126
|
function parseAnthropicRequest(body) {
|
|
131
127
|
const messages = [];
|
|
132
128
|
if (body.system) {
|
|
133
|
-
messages.push({ role: "system", content: body.system });
|
|
129
|
+
messages.push({ role: "system", content: String(body.system) });
|
|
134
130
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
});
|
|
131
|
+
const rawMessages = Array.isArray(body.messages) ? body.messages : [];
|
|
132
|
+
for (const m of rawMessages) {
|
|
133
|
+
const content = typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((b) => String(b.text ?? "")).join("\n") : "";
|
|
134
|
+
const role = m.role || "user";
|
|
135
|
+
messages.push({ role, content });
|
|
140
136
|
}
|
|
141
137
|
return {
|
|
142
138
|
model: body.model || "unknown",
|
|
@@ -147,43 +143,53 @@ function parseAnthropicRequest(body) {
|
|
|
147
143
|
};
|
|
148
144
|
}
|
|
149
145
|
function parseAnthropicResponse(body, _streaming) {
|
|
146
|
+
const usage = body.usage;
|
|
147
|
+
const contentBlocks = Array.isArray(body.content) ? body.content : [];
|
|
150
148
|
return {
|
|
151
149
|
model: body.model || "unknown",
|
|
152
|
-
inputTokens:
|
|
153
|
-
outputTokens:
|
|
154
|
-
content:
|
|
150
|
+
inputTokens: usage?.input_tokens || 0,
|
|
151
|
+
outputTokens: usage?.output_tokens || 0,
|
|
152
|
+
content: contentBlocks.map((b) => String(b.text ?? "")).join("\n"),
|
|
155
153
|
finishReason: body.stop_reason || "end_turn"
|
|
156
154
|
};
|
|
157
155
|
}
|
|
158
156
|
function parseGoogleRequest(body) {
|
|
159
157
|
const messages = [];
|
|
160
|
-
|
|
158
|
+
const sysInst = body.systemInstruction;
|
|
159
|
+
if (sysInst && Array.isArray(sysInst.parts)) {
|
|
161
160
|
messages.push({
|
|
162
161
|
role: "system",
|
|
163
|
-
content:
|
|
162
|
+
content: sysInst.parts.map((p) => String(p.text ?? "")).join("\n")
|
|
164
163
|
});
|
|
165
164
|
}
|
|
166
|
-
|
|
165
|
+
const contents = Array.isArray(body.contents) ? body.contents : [];
|
|
166
|
+
for (const item of contents) {
|
|
167
167
|
const role = item.role === "model" ? "assistant" : "user";
|
|
168
|
-
const
|
|
168
|
+
const parts = Array.isArray(item.parts) ? item.parts : [];
|
|
169
|
+
const content = parts.map((p) => String(p.text ?? "")).join("\n");
|
|
169
170
|
messages.push({ role, content });
|
|
170
171
|
}
|
|
171
172
|
const model = body.model || body.modelId || "gemini-2.0-flash";
|
|
173
|
+
const genConfig = body.generationConfig;
|
|
172
174
|
return {
|
|
173
175
|
model,
|
|
174
176
|
messages,
|
|
175
177
|
stream: body.stream === true,
|
|
176
|
-
maxTokens:
|
|
177
|
-
temperature:
|
|
178
|
+
maxTokens: genConfig?.maxOutputTokens,
|
|
179
|
+
temperature: genConfig?.temperature
|
|
178
180
|
};
|
|
179
181
|
}
|
|
180
182
|
function parseGoogleResponse(body, _streaming) {
|
|
181
|
-
const
|
|
183
|
+
const candidates = Array.isArray(body.candidates) ? body.candidates : [];
|
|
184
|
+
const candidate = candidates[0];
|
|
185
|
+
const usage = body.usageMetadata;
|
|
186
|
+
const candidateContent = candidate?.content;
|
|
187
|
+
const parts = Array.isArray(candidateContent?.parts) ? candidateContent.parts : [];
|
|
182
188
|
return {
|
|
183
189
|
model: body.modelVersion || body.model || "gemini-2.0-flash",
|
|
184
|
-
inputTokens:
|
|
185
|
-
outputTokens:
|
|
186
|
-
content:
|
|
190
|
+
inputTokens: usage?.promptTokenCount || 0,
|
|
191
|
+
outputTokens: usage?.candidatesTokenCount || 0,
|
|
192
|
+
content: parts.map((p) => String(p.text ?? "")).join("\n"),
|
|
187
193
|
finishReason: candidate?.finishReason || "STOP"
|
|
188
194
|
};
|
|
189
195
|
}
|
|
@@ -483,10 +489,7 @@ function deduplicateFindings(findings) {
|
|
|
483
489
|
import { createHash as createHash2 } from "crypto";
|
|
484
490
|
|
|
485
491
|
// src/engine/pruner.ts
|
|
486
|
-
import { Project, SyntaxKind } from "ts-morph";
|
|
487
492
|
import { readFile as readFile3 } from "fs/promises";
|
|
488
|
-
import { existsSync as existsSync2 } from "fs";
|
|
489
|
-
import { join as join2 } from "path";
|
|
490
493
|
|
|
491
494
|
// src/engine/tokenizer.ts
|
|
492
495
|
import { encodingForModel } from "js-tiktoken";
|
|
@@ -541,23 +544,7 @@ async function pruneTypeScript(file, level) {
|
|
|
541
544
|
} catch {
|
|
542
545
|
return emptyResult(file, level);
|
|
543
546
|
}
|
|
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);
|
|
547
|
+
const prunedContent = level === "signatures" ? extractSignaturesRegex(content) : extractSkeletonRegex(content);
|
|
561
548
|
const prunedTokens = countTokensChars4(Buffer.byteLength(prunedContent, "utf-8"));
|
|
562
549
|
const savingsPercent = file.tokens > 0 ? (file.tokens - prunedTokens) / file.tokens * 100 : 0;
|
|
563
550
|
return {
|
|
@@ -569,131 +556,281 @@ async function pruneTypeScript(file, level) {
|
|
|
569
556
|
savingsPercent: Math.max(0, savingsPercent)
|
|
570
557
|
};
|
|
571
558
|
}
|
|
572
|
-
function
|
|
559
|
+
function extractSignaturesRegex(content) {
|
|
560
|
+
const lines = content.split("\n");
|
|
573
561
|
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
|
-
|
|
562
|
+
let i = 0;
|
|
563
|
+
while (i < lines.length) {
|
|
564
|
+
const line = lines[i];
|
|
565
|
+
const trimmed = line.trim();
|
|
566
|
+
if (trimmed === "") {
|
|
567
|
+
i++;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (trimmed.startsWith("/**")) {
|
|
571
|
+
const docLines = [];
|
|
572
|
+
while (i < lines.length) {
|
|
573
|
+
docLines.push(lines[i]);
|
|
574
|
+
if (lines[i].includes("*/")) {
|
|
575
|
+
i++;
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
i++;
|
|
579
|
+
}
|
|
580
|
+
parts.push(docLines.join("\n"));
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (trimmed.startsWith("//")) {
|
|
584
|
+
parts.push(line);
|
|
585
|
+
i++;
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (/^\s*(import|export)\s/.test(line) && (trimmed.includes(" from ") || trimmed.startsWith("import "))) {
|
|
589
|
+
const block = collectBracedLine(lines, i);
|
|
590
|
+
parts.push(block.text);
|
|
591
|
+
i = block.nextIndex;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (/^\s*export\s*(\{|\*)/.test(trimmed)) {
|
|
595
|
+
const block = collectBracedLine(lines, i);
|
|
596
|
+
parts.push(block.text);
|
|
597
|
+
i = block.nextIndex;
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
if (/^\s*(export\s+)?type\s+\w/.test(trimmed) && !trimmed.startsWith("typeof")) {
|
|
601
|
+
const block = collectBalanced(lines, i);
|
|
602
|
+
parts.push(block.text);
|
|
603
|
+
i = block.nextIndex;
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (/^\s*(export\s+)?interface\s+\w/.test(trimmed)) {
|
|
607
|
+
const block = collectBalanced(lines, i);
|
|
608
|
+
parts.push(block.text);
|
|
609
|
+
i = block.nextIndex;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (/^\s*(export\s+)?(const\s+)?enum\s+\w/.test(trimmed)) {
|
|
613
|
+
const block = collectBalanced(lines, i);
|
|
614
|
+
parts.push(block.text);
|
|
615
|
+
i = block.nextIndex;
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
const fnMatch = trimmed.match(/^(export\s+)?(async\s+)?function\s+(\w+)/);
|
|
619
|
+
if (fnMatch) {
|
|
620
|
+
const sig = extractFnSignature(lines, i);
|
|
621
|
+
parts.push(`${sig} { /* ... */ }`);
|
|
622
|
+
i = skipBlock(lines, i);
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
const arrowMatch = trimmed.match(/^(export\s+)?(const|let|var)\s+(\w+)/);
|
|
626
|
+
if (arrowMatch && looksLikeFunctionDecl(lines, i)) {
|
|
627
|
+
const prefix = trimmed.match(/^((?:export\s+)?(?:const|let|var)\s+\w+[^=]*=)/)?.[1];
|
|
628
|
+
if (prefix) {
|
|
629
|
+
parts.push(`${prefix} /* ... */;`);
|
|
617
630
|
}
|
|
631
|
+
i = skipBlock(lines, i);
|
|
632
|
+
continue;
|
|
618
633
|
}
|
|
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());
|
|
634
|
+
if (arrowMatch) {
|
|
635
|
+
const block = collectStatement(lines, i);
|
|
636
|
+
parts.push(block.text);
|
|
637
|
+
i = block.nextIndex;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if (/^\s*(export\s+)?(abstract\s+)?class\s+\w/.test(trimmed)) {
|
|
641
|
+
const classOutline = extractClassOutline(lines, i);
|
|
642
|
+
parts.push(classOutline.text);
|
|
643
|
+
i = classOutline.nextIndex;
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
i++;
|
|
658
647
|
}
|
|
659
648
|
return parts.join("\n");
|
|
660
649
|
}
|
|
661
|
-
function
|
|
650
|
+
function extractSkeletonRegex(content) {
|
|
651
|
+
const lines = content.split("\n");
|
|
662
652
|
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
|
-
|
|
653
|
+
let i = 0;
|
|
654
|
+
while (i < lines.length) {
|
|
655
|
+
const trimmed = lines[i].trim();
|
|
656
|
+
if (/^import\s/.test(trimmed)) {
|
|
657
|
+
const block = collectBracedLine(lines, i);
|
|
658
|
+
parts.push(block.text);
|
|
659
|
+
i = block.nextIndex;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (/^export\s+(type|interface)\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+(const\s+)?enum\s+\w/.test(trimmed)) {
|
|
669
|
+
const block = collectBalanced(lines, i);
|
|
670
|
+
parts.push(block.text);
|
|
671
|
+
i = block.nextIndex;
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (/^export\s+(async\s+)?function\s+\w/.test(trimmed)) {
|
|
675
|
+
const sig = extractFnSignature(lines, i);
|
|
676
|
+
parts.push(`${sig};`);
|
|
677
|
+
i = skipBlock(lines, i);
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (/^export\s+(abstract\s+)?class\s+/.test(trimmed)) {
|
|
681
|
+
const nameMatch = trimmed.match(/class\s+(\w+)/);
|
|
682
|
+
const name = nameMatch?.[1] ?? "Unknown";
|
|
683
|
+
const end = skipBlock(lines, i);
|
|
684
|
+
const methods = [];
|
|
685
|
+
for (let j = i + 1; j < end; j++) {
|
|
686
|
+
const mt = lines[j].trim();
|
|
687
|
+
const mm = mt.match(/^(?:static\s+)?(?:async\s+)?(\w+)\s*\(/);
|
|
688
|
+
if (mm && mm[1] !== "constructor") methods.push(mm[1]);
|
|
689
|
+
}
|
|
690
|
+
parts.push(`export class ${name} { /* methods: ${methods.join(", ")} */ }`);
|
|
691
|
+
i = end;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
if (/^export\s*(\{|\*)/.test(trimmed)) {
|
|
695
|
+
const block = collectBracedLine(lines, i);
|
|
696
|
+
parts.push(block.text);
|
|
697
|
+
i = block.nextIndex;
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
i++;
|
|
694
701
|
}
|
|
695
702
|
return parts.join("\n");
|
|
696
703
|
}
|
|
704
|
+
function collectBracedLine(lines, start) {
|
|
705
|
+
let text = lines[start];
|
|
706
|
+
let i = start + 1;
|
|
707
|
+
while (i < lines.length && !text.includes(";") && !text.trimEnd().endsWith("'") && !text.trimEnd().endsWith('"')) {
|
|
708
|
+
text += "\n" + lines[i];
|
|
709
|
+
i++;
|
|
710
|
+
}
|
|
711
|
+
return { text, nextIndex: i };
|
|
712
|
+
}
|
|
713
|
+
function collectBalanced(lines, start) {
|
|
714
|
+
let depth = 0;
|
|
715
|
+
let text = "";
|
|
716
|
+
let i = start;
|
|
717
|
+
let started = false;
|
|
718
|
+
while (i < lines.length) {
|
|
719
|
+
const line = lines[i];
|
|
720
|
+
text += (text ? "\n" : "") + line;
|
|
721
|
+
for (const ch of line) {
|
|
722
|
+
if (ch === "{" || ch === "(") {
|
|
723
|
+
depth++;
|
|
724
|
+
started = true;
|
|
725
|
+
}
|
|
726
|
+
if (ch === "}" || ch === ")") depth--;
|
|
727
|
+
}
|
|
728
|
+
i++;
|
|
729
|
+
if (started && depth <= 0) break;
|
|
730
|
+
if (!started && line.includes(";")) break;
|
|
731
|
+
}
|
|
732
|
+
return { text, nextIndex: i };
|
|
733
|
+
}
|
|
734
|
+
function collectStatement(lines, start) {
|
|
735
|
+
let text = lines[start];
|
|
736
|
+
let i = start + 1;
|
|
737
|
+
if (text.includes(";")) return { text, nextIndex: i };
|
|
738
|
+
let depth = 0;
|
|
739
|
+
for (const ch of text) {
|
|
740
|
+
if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
741
|
+
if (ch === "}" || ch === ")" || ch === "]") depth--;
|
|
742
|
+
}
|
|
743
|
+
while (i < lines.length && depth > 0) {
|
|
744
|
+
text += "\n" + lines[i];
|
|
745
|
+
for (const ch of lines[i]) {
|
|
746
|
+
if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
747
|
+
if (ch === "}" || ch === ")" || ch === "]") depth--;
|
|
748
|
+
}
|
|
749
|
+
i++;
|
|
750
|
+
}
|
|
751
|
+
return { text, nextIndex: i };
|
|
752
|
+
}
|
|
753
|
+
function extractFnSignature(lines, start) {
|
|
754
|
+
let sig = "";
|
|
755
|
+
let i = start;
|
|
756
|
+
while (i < lines.length) {
|
|
757
|
+
const line = lines[i].trim();
|
|
758
|
+
sig += (sig ? " " : "") + line;
|
|
759
|
+
if (line.includes("{")) {
|
|
760
|
+
sig = sig.replace(/\s*\{[^]*$/, "").trim();
|
|
761
|
+
break;
|
|
762
|
+
}
|
|
763
|
+
i++;
|
|
764
|
+
}
|
|
765
|
+
return sig;
|
|
766
|
+
}
|
|
767
|
+
function skipBlock(lines, start) {
|
|
768
|
+
let depth = 0;
|
|
769
|
+
let i = start;
|
|
770
|
+
let foundBrace = false;
|
|
771
|
+
while (i < lines.length) {
|
|
772
|
+
for (const ch of lines[i]) {
|
|
773
|
+
if (ch === "{") {
|
|
774
|
+
depth++;
|
|
775
|
+
foundBrace = true;
|
|
776
|
+
}
|
|
777
|
+
if (ch === "}") depth--;
|
|
778
|
+
}
|
|
779
|
+
i++;
|
|
780
|
+
if (foundBrace && depth <= 0) break;
|
|
781
|
+
if (!foundBrace && lines[i - 1].includes(";")) break;
|
|
782
|
+
}
|
|
783
|
+
return i;
|
|
784
|
+
}
|
|
785
|
+
function looksLikeFunctionDecl(lines, start) {
|
|
786
|
+
const chunk = lines.slice(start, Math.min(start + 5, lines.length)).join(" ");
|
|
787
|
+
return /=>/.test(chunk) || /=\s*function/.test(chunk);
|
|
788
|
+
}
|
|
789
|
+
function extractClassOutline(lines, start) {
|
|
790
|
+
const header = lines[start].trim();
|
|
791
|
+
let headerText = header;
|
|
792
|
+
let i = start + 1;
|
|
793
|
+
if (!header.includes("{")) {
|
|
794
|
+
while (i < lines.length) {
|
|
795
|
+
headerText += " " + lines[i].trim();
|
|
796
|
+
if (lines[i].includes("{")) {
|
|
797
|
+
i++;
|
|
798
|
+
break;
|
|
799
|
+
}
|
|
800
|
+
i++;
|
|
801
|
+
}
|
|
802
|
+
} else {
|
|
803
|
+
i = start + 1;
|
|
804
|
+
}
|
|
805
|
+
const bodyParts = [headerText.replace(/\{[^]*$/, "{").trim()];
|
|
806
|
+
let depth = 1;
|
|
807
|
+
while (i < lines.length && depth > 0) {
|
|
808
|
+
const line = lines[i];
|
|
809
|
+
const trimmed = line.trim();
|
|
810
|
+
for (const ch of line) {
|
|
811
|
+
if (ch === "{") depth++;
|
|
812
|
+
if (ch === "}") depth--;
|
|
813
|
+
}
|
|
814
|
+
if (depth <= 0) {
|
|
815
|
+
i++;
|
|
816
|
+
break;
|
|
817
|
+
}
|
|
818
|
+
if (depth === 1) {
|
|
819
|
+
if (/^(private|protected|public|readonly|static|#)/.test(trimmed) && !trimmed.includes("(")) {
|
|
820
|
+
bodyParts.push(` ${trimmed}`);
|
|
821
|
+
} else if (/^constructor\s*\(/.test(trimmed)) {
|
|
822
|
+
const sig = extractFnSignature(lines, i);
|
|
823
|
+
bodyParts.push(` ${sig} { /* ... */ }`);
|
|
824
|
+
} else if (/^(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?\w+\s*[(<]/.test(trimmed) && !trimmed.startsWith("//")) {
|
|
825
|
+
const sig = extractFnSignature(lines, i);
|
|
826
|
+
bodyParts.push(` ${sig} { /* ... */ }`);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
i++;
|
|
830
|
+
}
|
|
831
|
+
bodyParts.push("}");
|
|
832
|
+
return { text: bodyParts.join("\n"), nextIndex: i };
|
|
833
|
+
}
|
|
697
834
|
async function pruneGeneric(file, level) {
|
|
698
835
|
let content;
|
|
699
836
|
try {
|
|
@@ -754,22 +891,6 @@ function emptyResult(file, level) {
|
|
|
754
891
|
savingsPercent: 100
|
|
755
892
|
};
|
|
756
893
|
}
|
|
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
894
|
|
|
774
895
|
// src/engine/graph-utils.ts
|
|
775
896
|
function buildAdjacencyList(edges) {
|
|
@@ -1286,14 +1407,15 @@ async function optimizeContext(messages, analysis, config) {
|
|
|
1286
1407
|
);
|
|
1287
1408
|
return { messages: optimizedMessages, injected: true, optimizeDecisions };
|
|
1288
1409
|
} catch (err) {
|
|
1289
|
-
|
|
1410
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1411
|
+
optimizeDecisions.push(`Context optimization failed: ${errMsg}`);
|
|
1290
1412
|
return { messages, injected: false, optimizeDecisions };
|
|
1291
1413
|
}
|
|
1292
1414
|
}
|
|
1293
1415
|
|
|
1294
1416
|
// src/gateway/tracker.ts
|
|
1295
|
-
import { mkdirSync as mkdirSync2, appendFileSync, readFileSync as readFileSync3, readdirSync, existsSync as
|
|
1296
|
-
import { join as
|
|
1417
|
+
import { mkdirSync as mkdirSync2, appendFileSync, readFileSync as readFileSync3, readdirSync, existsSync as existsSync2 } from "fs";
|
|
1418
|
+
import { join as join2 } from "path";
|
|
1297
1419
|
import { randomUUID } from "crypto";
|
|
1298
1420
|
var UsageTracker = class {
|
|
1299
1421
|
logDir;
|
|
@@ -1305,7 +1427,7 @@ var UsageTracker = class {
|
|
|
1305
1427
|
memRecords = [];
|
|
1306
1428
|
constructor(config) {
|
|
1307
1429
|
this.config = config;
|
|
1308
|
-
this.logDir =
|
|
1430
|
+
this.logDir = join2(config.logDir, "usage");
|
|
1309
1431
|
mkdirSync2(this.logDir, { recursive: true });
|
|
1310
1432
|
}
|
|
1311
1433
|
// ===== EVENT SYSTEM =====
|
|
@@ -1328,7 +1450,7 @@ var UsageTracker = class {
|
|
|
1328
1450
|
...params
|
|
1329
1451
|
};
|
|
1330
1452
|
const monthKey = this.getMonthKey(record.timestamp);
|
|
1331
|
-
const logFile =
|
|
1453
|
+
const logFile = join2(this.logDir, `${monthKey}.jsonl`);
|
|
1332
1454
|
const line = JSON.stringify({
|
|
1333
1455
|
...record,
|
|
1334
1456
|
timestamp: record.timestamp.toISOString()
|
|
@@ -1462,8 +1584,8 @@ var UsageTracker = class {
|
|
|
1462
1584
|
return date.toISOString().slice(0, 7);
|
|
1463
1585
|
}
|
|
1464
1586
|
getMonthRecordsByKey(monthKey) {
|
|
1465
|
-
const filePath =
|
|
1466
|
-
if (!
|
|
1587
|
+
const filePath = join2(this.logDir, `${monthKey}.jsonl`);
|
|
1588
|
+
if (!existsSync2(filePath)) return [];
|
|
1467
1589
|
return readFileSync3(filePath, "utf-8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
1468
1590
|
try {
|
|
1469
1591
|
const parsed = JSON.parse(line);
|
|
@@ -1477,8 +1599,8 @@ var UsageTracker = class {
|
|
|
1477
1599
|
getMonthRecords(date) {
|
|
1478
1600
|
const monthKey = this.getMonthKey(date);
|
|
1479
1601
|
if (this.cache && this.cacheMonth === monthKey) return this.cache;
|
|
1480
|
-
const filePath =
|
|
1481
|
-
if (!
|
|
1602
|
+
const filePath = join2(this.logDir, `${monthKey}.jsonl`);
|
|
1603
|
+
if (!existsSync2(filePath)) return [];
|
|
1482
1604
|
const records = readFileSync3(filePath, "utf-8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
1483
1605
|
try {
|
|
1484
1606
|
const parsed = JSON.parse(line);
|
|
@@ -1493,11 +1615,11 @@ var UsageTracker = class {
|
|
|
1493
1615
|
return records;
|
|
1494
1616
|
}
|
|
1495
1617
|
getAllRecords() {
|
|
1496
|
-
if (!
|
|
1618
|
+
if (!existsSync2(this.logDir)) return [];
|
|
1497
1619
|
const files = readdirSync(this.logDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1498
1620
|
const allRecords = [];
|
|
1499
1621
|
for (const file of files) {
|
|
1500
|
-
const content = readFileSync3(
|
|
1622
|
+
const content = readFileSync3(join2(this.logDir, file), "utf-8");
|
|
1501
1623
|
const records = content.split("\n").filter((line) => line.trim()).map((line) => {
|
|
1502
1624
|
try {
|
|
1503
1625
|
const parsed = JSON.parse(line);
|
|
@@ -1515,7 +1637,7 @@ var UsageTracker = class {
|
|
|
1515
1637
|
|
|
1516
1638
|
// src/engine/analyzer.ts
|
|
1517
1639
|
import { readFile as readFile4, readdir, stat as stat2 } from "fs/promises";
|
|
1518
|
-
import { join as
|
|
1640
|
+
import { join as join4, extname, relative as relative3, resolve as resolve4, basename as basename2 } from "path";
|
|
1519
1641
|
import { createHash as createHash3 } from "crypto";
|
|
1520
1642
|
|
|
1521
1643
|
// src/types/engine.ts
|
|
@@ -1568,14 +1690,14 @@ var DEFAULT_CONFIG = {
|
|
|
1568
1690
|
};
|
|
1569
1691
|
|
|
1570
1692
|
// src/engine/graph.ts
|
|
1571
|
-
import { Project
|
|
1572
|
-
import { resolve as resolve3, relative as relative2, dirname as dirname2, join as
|
|
1573
|
-
import { existsSync as
|
|
1693
|
+
import { Project, SyntaxKind } from "ts-morph";
|
|
1694
|
+
import { resolve as resolve3, relative as relative2, dirname as dirname2, join as join3 } from "path";
|
|
1695
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1574
1696
|
var TS_EXTENSIONS2 = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mts", "mjs", "cts", "cjs"]);
|
|
1575
1697
|
function createProject(projectPath, filePaths) {
|
|
1576
|
-
const tsConfigPath =
|
|
1577
|
-
const hasTsConfig =
|
|
1578
|
-
const project = new
|
|
1698
|
+
const tsConfigPath = join3(projectPath, "tsconfig.json");
|
|
1699
|
+
const hasTsConfig = existsSync3(tsConfigPath);
|
|
1700
|
+
const project = new Project({
|
|
1579
1701
|
tsConfigFilePath: hasTsConfig ? tsConfigPath : void 0,
|
|
1580
1702
|
skipAddingFilesFromTsConfig: true,
|
|
1581
1703
|
compilerOptions: hasTsConfig ? void 0 : {
|
|
@@ -1775,7 +1897,7 @@ function enrichComplexity(project, absPath, files) {
|
|
|
1775
1897
|
}
|
|
1776
1898
|
for (const varDecl of sourceFile.getVariableDeclarations()) {
|
|
1777
1899
|
const init = varDecl.getInitializer();
|
|
1778
|
-
if (init && (init.getKind() ===
|
|
1900
|
+
if (init && (init.getKind() === SyntaxKind.ArrowFunction || init.getKind() === SyntaxKind.FunctionExpression)) {
|
|
1779
1901
|
totalComplexity += calculateCyclomaticComplexity(init);
|
|
1780
1902
|
}
|
|
1781
1903
|
}
|
|
@@ -1786,22 +1908,22 @@ function calculateCyclomaticComplexity(node) {
|
|
|
1786
1908
|
let complexity = 1;
|
|
1787
1909
|
node.forEachDescendant((descendant) => {
|
|
1788
1910
|
switch (descendant.getKind()) {
|
|
1789
|
-
case
|
|
1790
|
-
case
|
|
1791
|
-
case
|
|
1792
|
-
case
|
|
1793
|
-
case
|
|
1794
|
-
case
|
|
1795
|
-
case
|
|
1796
|
-
case
|
|
1797
|
-
case
|
|
1911
|
+
case SyntaxKind.IfStatement:
|
|
1912
|
+
case SyntaxKind.ConditionalExpression:
|
|
1913
|
+
case SyntaxKind.ForStatement:
|
|
1914
|
+
case SyntaxKind.ForInStatement:
|
|
1915
|
+
case SyntaxKind.ForOfStatement:
|
|
1916
|
+
case SyntaxKind.WhileStatement:
|
|
1917
|
+
case SyntaxKind.DoStatement:
|
|
1918
|
+
case SyntaxKind.CaseClause:
|
|
1919
|
+
case SyntaxKind.CatchClause:
|
|
1798
1920
|
complexity++;
|
|
1799
1921
|
break;
|
|
1800
|
-
case
|
|
1922
|
+
case SyntaxKind.BinaryExpression: {
|
|
1801
1923
|
const opToken = descendant.getOperatorToken?.();
|
|
1802
1924
|
if (opToken) {
|
|
1803
1925
|
const kind = opToken.getKind();
|
|
1804
|
-
if (kind ===
|
|
1926
|
+
if (kind === SyntaxKind.AmpersandAmpersandToken || kind === SyntaxKind.BarBarToken || kind === SyntaxKind.QuestionQuestionToken) {
|
|
1805
1927
|
complexity++;
|
|
1806
1928
|
}
|
|
1807
1929
|
}
|
|
@@ -1818,14 +1940,14 @@ function resolveImport(sourceFile, moduleSpecifier, projectRoot) {
|
|
|
1818
1940
|
const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
|
|
1819
1941
|
for (const ext of extensions) {
|
|
1820
1942
|
const candidate = basePath.endsWith(ext) ? basePath : basePath + ext;
|
|
1821
|
-
if (
|
|
1943
|
+
if (existsSync3(candidate)) {
|
|
1822
1944
|
const rel = relative2(projectRoot, candidate);
|
|
1823
1945
|
if (!rel.startsWith("..")) return rel;
|
|
1824
1946
|
}
|
|
1825
1947
|
}
|
|
1826
1948
|
if (moduleSpecifier.endsWith(".js")) {
|
|
1827
1949
|
const tsPath = basePath.replace(/\.js$/, ".ts");
|
|
1828
|
-
if (
|
|
1950
|
+
if (existsSync3(tsPath)) {
|
|
1829
1951
|
const rel = relative2(projectRoot, tsPath);
|
|
1830
1952
|
if (!rel.startsWith("..")) return rel;
|
|
1831
1953
|
}
|
|
@@ -1986,7 +2108,7 @@ async function walkProject(rootPath, options) {
|
|
|
1986
2108
|
}
|
|
1987
2109
|
const promises = [];
|
|
1988
2110
|
for (const entry of entries) {
|
|
1989
|
-
const fullPath =
|
|
2111
|
+
const fullPath = join4(dir, entry.name);
|
|
1990
2112
|
if (entry.isDirectory()) {
|
|
1991
2113
|
if (!ignoreDirSet.has(entry.name) && !entry.name.startsWith(".")) {
|
|
1992
2114
|
promises.push(walk(fullPath, depth + 1));
|
|
@@ -2071,8 +2193,8 @@ async function analyzeProject(projectPath, config) {
|
|
|
2071
2193
|
maxDepth: mergedConfig.analysis.maxDepth
|
|
2072
2194
|
});
|
|
2073
2195
|
const tokenMethod = mergedConfig.tokens.method;
|
|
2074
|
-
const
|
|
2075
|
-
|
|
2196
|
+
const BATCH_SIZE = 50;
|
|
2197
|
+
async function estimateFileTokens(entry) {
|
|
2076
2198
|
let tokens;
|
|
2077
2199
|
if (tokenMethod === "tiktoken") {
|
|
2078
2200
|
try {
|
|
@@ -2084,7 +2206,7 @@ async function analyzeProject(projectPath, config) {
|
|
|
2084
2206
|
} else {
|
|
2085
2207
|
tokens = countTokensChars4(entry.size);
|
|
2086
2208
|
}
|
|
2087
|
-
|
|
2209
|
+
return {
|
|
2088
2210
|
path: entry.path,
|
|
2089
2211
|
relativePath: entry.relativePath,
|
|
2090
2212
|
extension: entry.extension,
|
|
@@ -2093,16 +2215,20 @@ async function analyzeProject(projectPath, config) {
|
|
|
2093
2215
|
lines: entry.lines,
|
|
2094
2216
|
lastModified: entry.lastModified,
|
|
2095
2217
|
kind: classifyFileKind(entry.relativePath),
|
|
2096
|
-
// Graph data — populated by graph analysis
|
|
2097
2218
|
imports: [],
|
|
2098
2219
|
importedBy: [],
|
|
2099
2220
|
isHub: false,
|
|
2100
2221
|
complexity: 0,
|
|
2101
|
-
// Risk data — populated by risk analysis
|
|
2102
2222
|
riskScore: 0,
|
|
2103
2223
|
riskFactors: [],
|
|
2104
2224
|
exclusionImpact: "none"
|
|
2105
|
-
}
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
const files = [];
|
|
2228
|
+
for (let i = 0; i < walkEntries.length; i += BATCH_SIZE) {
|
|
2229
|
+
const batch = walkEntries.slice(i, i + BATCH_SIZE);
|
|
2230
|
+
const results = await Promise.all(batch.map(estimateFileTokens));
|
|
2231
|
+
files.push(...results);
|
|
2106
2232
|
}
|
|
2107
2233
|
const graph = buildProjectGraph(absPath, files);
|
|
2108
2234
|
for (const file of files) {
|
|
@@ -2248,7 +2374,8 @@ var ContextGateway = class {
|
|
|
2248
2374
|
this.analysis = analysis;
|
|
2249
2375
|
return analysis;
|
|
2250
2376
|
} catch (err) {
|
|
2251
|
-
|
|
2377
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2378
|
+
this.emit({ type: "error", message: `Analysis failed: ${message}`, error: err instanceof Error ? err : void 0 });
|
|
2252
2379
|
throw err;
|
|
2253
2380
|
}
|
|
2254
2381
|
}
|
|
@@ -2285,7 +2412,8 @@ var ContextGateway = class {
|
|
|
2285
2412
|
try {
|
|
2286
2413
|
body = await readBody(req, this.config.maxBodyBytes);
|
|
2287
2414
|
} catch (err) {
|
|
2288
|
-
const
|
|
2415
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2416
|
+
const status = errMsg === "body-too-large" ? 413 : 400;
|
|
2289
2417
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
2290
2418
|
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" }));
|
|
2291
2419
|
return;
|
|
@@ -2396,12 +2524,13 @@ var ContextGateway = class {
|
|
|
2396
2524
|
try {
|
|
2397
2525
|
await this.proxyRequest(targetUrl, req, res, modifiedBody, provider, parsed, interceptResult, startTime);
|
|
2398
2526
|
} catch (err) {
|
|
2527
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2399
2528
|
if (!res.headersSent) {
|
|
2400
|
-
const status =
|
|
2529
|
+
const status = errMsg === "upstream-timeout" ? 504 : 502;
|
|
2401
2530
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
2402
|
-
res.end(JSON.stringify({ error: status === 504 ? "Upstream provider timeout" : `Proxy error: ${
|
|
2531
|
+
res.end(JSON.stringify({ error: status === 504 ? "Upstream provider timeout" : `Proxy error: ${errMsg}` }));
|
|
2403
2532
|
}
|
|
2404
|
-
this.emit({ type: "error", message: `Proxy error: ${
|
|
2533
|
+
this.emit({ type: "error", message: `Proxy error: ${errMsg}`, error: err instanceof Error ? err : void 0 });
|
|
2405
2534
|
}
|
|
2406
2535
|
}
|
|
2407
2536
|
// ===== PROXY =====
|