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.
@@ -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 messages = (body.messages || []).map((m) => ({
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, streaming) {
108
- if (streaming) {
109
- return {
110
- model: body.model || "unknown",
111
- inputTokens: body.usage?.prompt_tokens || 0,
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: body.usage?.prompt_tokens || 0,
120
- outputTokens: body.usage?.completion_tokens || 0,
121
- content: body.choices?.[0]?.message?.content || "",
122
- finishReason: body.choices?.[0]?.finish_reason || "stop"
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
- for (const m of body.messages || []) {
131
- messages.push({
132
- role: m.role || "user",
133
- content: typeof m.content === "string" ? m.content : m.content?.map((b) => b.text || "").join("\n") || ""
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: body.usage?.input_tokens || 0,
148
- outputTokens: body.usage?.output_tokens || 0,
149
- content: body.content?.map((b) => b.text || "").join("\n") || "",
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
- if (body.systemInstruction?.parts) {
153
+ const sysInst = body.systemInstruction;
154
+ if (sysInst && Array.isArray(sysInst.parts)) {
156
155
  messages.push({
157
156
  role: "system",
158
- content: body.systemInstruction.parts.map((p) => p.text || "").join("\n")
157
+ content: sysInst.parts.map((p) => String(p.text ?? "")).join("\n")
159
158
  });
160
159
  }
161
- for (const item of body.contents || []) {
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 content = item.parts?.map((p) => p.text || "").join("\n") || "";
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: body.generationConfig?.maxOutputTokens,
172
- temperature: body.generationConfig?.temperature
173
+ maxTokens: genConfig?.maxOutputTokens,
174
+ temperature: genConfig?.temperature
173
175
  };
174
176
  }
175
177
  function parseGoogleResponse(body, _streaming) {
176
- const candidate = body.candidates?.[0];
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: body.usageMetadata?.promptTokenCount || 0,
180
- outputTokens: body.usageMetadata?.candidatesTokenCount || 0,
181
- content: candidate?.content?.parts?.map((p) => p.text || "").join("\n") || "",
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
- let project;
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 extractSignaturesAST(sf) {
554
+ function extractSignaturesRegex(content) {
555
+ const lines = content.split("\n");
568
556
  const parts = [];
569
- for (const imp of sf.getImportDeclarations()) {
570
- parts.push(imp.getText());
571
- }
572
- if (parts.length > 0) parts.push("");
573
- for (const ta of sf.getTypeAliases()) {
574
- addJSDoc(ta, parts);
575
- parts.push(ta.getText());
576
- }
577
- for (const iface of sf.getInterfaces()) {
578
- addJSDoc(iface, parts);
579
- parts.push(iface.getText());
580
- }
581
- for (const en of sf.getEnums()) {
582
- addJSDoc(en, parts);
583
- parts.push(en.getText());
584
- }
585
- for (const fn of sf.getFunctions()) {
586
- addJSDoc(fn, parts);
587
- const isExported = fn.isExported();
588
- const isAsync = fn.isAsync();
589
- const name = fn.getName() ?? "<anonymous>";
590
- const params = fn.getParameters().map((p) => p.getText()).join(", ");
591
- const returnType = fn.getReturnTypeNode()?.getText();
592
- const returnStr = returnType ? `: ${returnType}` : "";
593
- const prefix = isExported ? "export " : "";
594
- const asyncStr = isAsync ? "async " : "";
595
- parts.push(`${prefix}${asyncStr}function ${name}(${params})${returnStr} { /* ... */ }`);
596
- }
597
- for (const stmt of sf.getVariableStatements()) {
598
- for (const decl of stmt.getDeclarations()) {
599
- const init = decl.getInitializer();
600
- if (init && (init.getKind() === SyntaxKind.ArrowFunction || init.getKind() === SyntaxKind.FunctionExpression)) {
601
- addJSDoc(stmt, parts);
602
- const isExported = stmt.isExported();
603
- const prefix = isExported ? "export " : "";
604
- const kind = stmt.getDeclarationKind();
605
- const name = decl.getName();
606
- const typeNode = decl.getTypeNode()?.getText();
607
- const typeStr = typeNode ? `: ${typeNode}` : "";
608
- parts.push(`${prefix}${kind} ${name}${typeStr} = /* ... */;`);
609
- } else {
610
- addJSDoc(stmt, parts);
611
- parts.push(stmt.getText());
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
- for (const cls of sf.getClasses()) {
616
- addJSDoc(cls, parts);
617
- const isExported = cls.isExported();
618
- const prefix = isExported ? "export " : "";
619
- const name = cls.getName() ?? "<anonymous>";
620
- const ext = cls.getExtends()?.getText();
621
- const impl = cls.getImplements().map((i) => i.getText()).join(", ");
622
- let header = `${prefix}class ${name}`;
623
- if (ext) header += ` extends ${ext}`;
624
- if (impl) header += ` implements ${impl}`;
625
- header += " {";
626
- parts.push(header);
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 extractSkeletonAST(sf) {
645
+ function extractSkeletonRegex(content) {
646
+ const lines = content.split("\n");
657
647
  const parts = [];
658
- for (const imp of sf.getImportDeclarations()) {
659
- parts.push(imp.getText());
660
- }
661
- if (parts.length > 0) parts.push("");
662
- for (const ta of sf.getTypeAliases()) {
663
- if (ta.isExported()) parts.push(ta.getText());
664
- }
665
- for (const iface of sf.getInterfaces()) {
666
- if (!iface.isExported()) continue;
667
- const ext = iface.getExtends().map((e) => e.getText());
668
- const extStr = ext.length > 0 ? ` extends ${ext.join(", ")}` : "";
669
- parts.push(`export interface ${iface.getName()}${extStr} { /* ${iface.getProperties().length} props */ }`);
670
- }
671
- for (const en of sf.getEnums()) {
672
- if (!en.isExported()) continue;
673
- const members = en.getMembers().map((m) => m.getName());
674
- parts.push(`export enum ${en.getName()} { ${members.join(", ")} }`);
675
- }
676
- for (const fn of sf.getFunctions()) {
677
- if (!fn.isExported()) continue;
678
- const name = fn.getName() ?? "<anonymous>";
679
- const params = fn.getParameters().map((p) => p.getText()).join(", ");
680
- parts.push(`export function ${name}(${params});`);
681
- }
682
- for (const cls of sf.getClasses()) {
683
- if (!cls.isExported()) continue;
684
- const methods = cls.getMethods().map((m) => m.getName());
685
- parts.push(`export class ${cls.getName()} { /* methods: ${methods.join(", ")} */ }`);
686
- }
687
- for (const exp of sf.getExportDeclarations()) {
688
- parts.push(exp.getText());
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
- optimizeDecisions.push(`Context optimization failed: ${err.message}`);
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 existsSync3 } from "fs";
1291
- import { join as join3 } from "path";
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 = join3(config.logDir, "usage");
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 = join3(this.logDir, `${monthKey}.jsonl`);
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 = join3(this.logDir, `${monthKey}.jsonl`);
1461
- if (!existsSync3(filePath)) return [];
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 = join3(this.logDir, `${monthKey}.jsonl`);
1476
- if (!existsSync3(filePath)) return [];
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 (!existsSync3(this.logDir)) return [];
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(join3(this.logDir, file), "utf-8");
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 join5, extname, relative as relative3, resolve as resolve4, basename as basename2 } from "path";
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 as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
1567
- import { resolve as resolve3, relative as relative2, dirname as dirname2, join as join4 } from "path";
1568
- import { existsSync as existsSync4 } from "fs";
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 = join4(projectPath, "tsconfig.json");
1572
- const hasTsConfig = existsSync4(tsConfigPath);
1573
- const project = new Project2({
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() === SyntaxKind2.ArrowFunction || init.getKind() === SyntaxKind2.FunctionExpression)) {
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 SyntaxKind2.IfStatement:
1785
- case SyntaxKind2.ConditionalExpression:
1786
- case SyntaxKind2.ForStatement:
1787
- case SyntaxKind2.ForInStatement:
1788
- case SyntaxKind2.ForOfStatement:
1789
- case SyntaxKind2.WhileStatement:
1790
- case SyntaxKind2.DoStatement:
1791
- case SyntaxKind2.CaseClause:
1792
- case SyntaxKind2.CatchClause:
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 SyntaxKind2.BinaryExpression: {
1917
+ case SyntaxKind.BinaryExpression: {
1796
1918
  const opToken = descendant.getOperatorToken?.();
1797
1919
  if (opToken) {
1798
1920
  const kind = opToken.getKind();
1799
- if (kind === SyntaxKind2.AmpersandAmpersandToken || kind === SyntaxKind2.BarBarToken || kind === SyntaxKind2.QuestionQuestionToken) {
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 (existsSync4(candidate)) {
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 (existsSync4(tsPath)) {
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 = join5(dir, entry.name);
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 files = [];
2070
- for (const entry of walkEntries) {
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
- files.push({
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
- this.emit({ type: "error", message: `Analysis failed: ${err.message}`, error: err });
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 status = err.message === "body-too-large" ? 413 : 400;
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 = err.message === "upstream-timeout" ? 504 : 502;
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: ${err.message}` }));
2526
+ res.end(JSON.stringify({ error: status === 504 ? "Upstream provider timeout" : `Proxy error: ${errMsg}` }));
2398
2527
  }
2399
- this.emit({ type: "error", message: `Proxy error: ${err.message}`, error: err });
2528
+ this.emit({ type: "error", message: `Proxy error: ${errMsg}`, error: err instanceof Error ? err : void 0 });
2400
2529
  }
2401
2530
  }
2402
2531
  // ===== PROXY =====