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.
@@ -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 messages = (body.messages || []).map((m) => ({
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, streaming) {
113
- if (streaming) {
114
- return {
115
- model: body.model || "unknown",
116
- inputTokens: body.usage?.prompt_tokens || 0,
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: body.usage?.prompt_tokens || 0,
125
- outputTokens: body.usage?.completion_tokens || 0,
126
- content: body.choices?.[0]?.message?.content || "",
127
- finishReason: body.choices?.[0]?.finish_reason || "stop"
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
- for (const m of body.messages || []) {
136
- messages.push({
137
- role: m.role || "user",
138
- content: typeof m.content === "string" ? m.content : m.content?.map((b) => b.text || "").join("\n") || ""
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: body.usage?.input_tokens || 0,
153
- outputTokens: body.usage?.output_tokens || 0,
154
- content: body.content?.map((b) => b.text || "").join("\n") || "",
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
- if (body.systemInstruction?.parts) {
158
+ const sysInst = body.systemInstruction;
159
+ if (sysInst && Array.isArray(sysInst.parts)) {
161
160
  messages.push({
162
161
  role: "system",
163
- content: body.systemInstruction.parts.map((p) => p.text || "").join("\n")
162
+ content: sysInst.parts.map((p) => String(p.text ?? "")).join("\n")
164
163
  });
165
164
  }
166
- for (const item of body.contents || []) {
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 content = item.parts?.map((p) => p.text || "").join("\n") || "";
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: body.generationConfig?.maxOutputTokens,
177
- temperature: body.generationConfig?.temperature
178
+ maxTokens: genConfig?.maxOutputTokens,
179
+ temperature: genConfig?.temperature
178
180
  };
179
181
  }
180
182
  function parseGoogleResponse(body, _streaming) {
181
- const candidate = body.candidates?.[0];
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: body.usageMetadata?.promptTokenCount || 0,
185
- outputTokens: body.usageMetadata?.candidatesTokenCount || 0,
186
- content: candidate?.content?.parts?.map((p) => p.text || "").join("\n") || "",
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
- let project;
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 extractSignaturesAST(sf) {
559
+ function extractSignaturesRegex(content) {
560
+ const lines = content.split("\n");
573
561
  const parts = [];
574
- for (const imp of sf.getImportDeclarations()) {
575
- parts.push(imp.getText());
576
- }
577
- if (parts.length > 0) parts.push("");
578
- for (const ta of sf.getTypeAliases()) {
579
- addJSDoc(ta, parts);
580
- parts.push(ta.getText());
581
- }
582
- for (const iface of sf.getInterfaces()) {
583
- addJSDoc(iface, parts);
584
- parts.push(iface.getText());
585
- }
586
- for (const en of sf.getEnums()) {
587
- addJSDoc(en, parts);
588
- parts.push(en.getText());
589
- }
590
- for (const fn of sf.getFunctions()) {
591
- addJSDoc(fn, parts);
592
- const isExported = fn.isExported();
593
- const isAsync = fn.isAsync();
594
- const name = fn.getName() ?? "<anonymous>";
595
- const params = fn.getParameters().map((p) => p.getText()).join(", ");
596
- const returnType = fn.getReturnTypeNode()?.getText();
597
- const returnStr = returnType ? `: ${returnType}` : "";
598
- const prefix = isExported ? "export " : "";
599
- const asyncStr = isAsync ? "async " : "";
600
- parts.push(`${prefix}${asyncStr}function ${name}(${params})${returnStr} { /* ... */ }`);
601
- }
602
- for (const stmt of sf.getVariableStatements()) {
603
- for (const decl of stmt.getDeclarations()) {
604
- const init = decl.getInitializer();
605
- if (init && (init.getKind() === SyntaxKind.ArrowFunction || init.getKind() === SyntaxKind.FunctionExpression)) {
606
- addJSDoc(stmt, parts);
607
- const isExported = stmt.isExported();
608
- const prefix = isExported ? "export " : "";
609
- const kind = stmt.getDeclarationKind();
610
- const name = decl.getName();
611
- const typeNode = decl.getTypeNode()?.getText();
612
- const typeStr = typeNode ? `: ${typeNode}` : "";
613
- parts.push(`${prefix}${kind} ${name}${typeStr} = /* ... */;`);
614
- } else {
615
- addJSDoc(stmt, parts);
616
- parts.push(stmt.getText());
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
- for (const cls of sf.getClasses()) {
621
- addJSDoc(cls, parts);
622
- const isExported = cls.isExported();
623
- const prefix = isExported ? "export " : "";
624
- const name = cls.getName() ?? "<anonymous>";
625
- const ext = cls.getExtends()?.getText();
626
- const impl = cls.getImplements().map((i) => i.getText()).join(", ");
627
- let header = `${prefix}class ${name}`;
628
- if (ext) header += ` extends ${ext}`;
629
- if (impl) header += ` implements ${impl}`;
630
- header += " {";
631
- parts.push(header);
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 extractSkeletonAST(sf) {
650
+ function extractSkeletonRegex(content) {
651
+ const lines = content.split("\n");
662
652
  const parts = [];
663
- for (const imp of sf.getImportDeclarations()) {
664
- parts.push(imp.getText());
665
- }
666
- if (parts.length > 0) parts.push("");
667
- for (const ta of sf.getTypeAliases()) {
668
- if (ta.isExported()) parts.push(ta.getText());
669
- }
670
- for (const iface of sf.getInterfaces()) {
671
- if (!iface.isExported()) continue;
672
- const ext = iface.getExtends().map((e) => e.getText());
673
- const extStr = ext.length > 0 ? ` extends ${ext.join(", ")}` : "";
674
- parts.push(`export interface ${iface.getName()}${extStr} { /* ${iface.getProperties().length} props */ }`);
675
- }
676
- for (const en of sf.getEnums()) {
677
- if (!en.isExported()) continue;
678
- const members = en.getMembers().map((m) => m.getName());
679
- parts.push(`export enum ${en.getName()} { ${members.join(", ")} }`);
680
- }
681
- for (const fn of sf.getFunctions()) {
682
- if (!fn.isExported()) continue;
683
- const name = fn.getName() ?? "<anonymous>";
684
- const params = fn.getParameters().map((p) => p.getText()).join(", ");
685
- parts.push(`export function ${name}(${params});`);
686
- }
687
- for (const cls of sf.getClasses()) {
688
- if (!cls.isExported()) continue;
689
- const methods = cls.getMethods().map((m) => m.getName());
690
- parts.push(`export class ${cls.getName()} { /* methods: ${methods.join(", ")} */ }`);
691
- }
692
- for (const exp of sf.getExportDeclarations()) {
693
- parts.push(exp.getText());
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
- optimizeDecisions.push(`Context optimization failed: ${err.message}`);
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 existsSync3 } from "fs";
1296
- import { join as join3 } from "path";
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 = join3(config.logDir, "usage");
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 = join3(this.logDir, `${monthKey}.jsonl`);
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 = join3(this.logDir, `${monthKey}.jsonl`);
1466
- if (!existsSync3(filePath)) return [];
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 = join3(this.logDir, `${monthKey}.jsonl`);
1481
- if (!existsSync3(filePath)) return [];
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 (!existsSync3(this.logDir)) return [];
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(join3(this.logDir, file), "utf-8");
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 join5, extname, relative as relative3, resolve as resolve4, basename as basename2 } from "path";
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 as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
1572
- import { resolve as resolve3, relative as relative2, dirname as dirname2, join as join4 } from "path";
1573
- import { existsSync as existsSync4 } from "fs";
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 = join4(projectPath, "tsconfig.json");
1577
- const hasTsConfig = existsSync4(tsConfigPath);
1578
- const project = new Project2({
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() === SyntaxKind2.ArrowFunction || init.getKind() === SyntaxKind2.FunctionExpression)) {
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 SyntaxKind2.IfStatement:
1790
- case SyntaxKind2.ConditionalExpression:
1791
- case SyntaxKind2.ForStatement:
1792
- case SyntaxKind2.ForInStatement:
1793
- case SyntaxKind2.ForOfStatement:
1794
- case SyntaxKind2.WhileStatement:
1795
- case SyntaxKind2.DoStatement:
1796
- case SyntaxKind2.CaseClause:
1797
- case SyntaxKind2.CatchClause:
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 SyntaxKind2.BinaryExpression: {
1922
+ case SyntaxKind.BinaryExpression: {
1801
1923
  const opToken = descendant.getOperatorToken?.();
1802
1924
  if (opToken) {
1803
1925
  const kind = opToken.getKind();
1804
- if (kind === SyntaxKind2.AmpersandAmpersandToken || kind === SyntaxKind2.BarBarToken || kind === SyntaxKind2.QuestionQuestionToken) {
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 (existsSync4(candidate)) {
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 (existsSync4(tsPath)) {
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 = join5(dir, entry.name);
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 files = [];
2075
- for (const entry of walkEntries) {
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
- files.push({
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
- this.emit({ type: "error", message: `Analysis failed: ${err.message}`, error: err });
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 status = err.message === "body-too-large" ? 413 : 400;
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 = err.message === "upstream-timeout" ? 504 : 502;
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: ${err.message}` }));
2531
+ res.end(JSON.stringify({ error: status === 504 ? "Upstream provider timeout" : `Proxy error: ${errMsg}` }));
2403
2532
  }
2404
- this.emit({ type: "error", message: `Proxy error: ${err.message}`, error: err });
2533
+ this.emit({ type: "error", message: `Proxy error: ${errMsg}`, error: err instanceof Error ? err : void 0 });
2405
2534
  }
2406
2535
  }
2407
2536
  // ===== PROXY =====