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.
@@ -24179,8 +24179,8 @@ async function analyzeProject(projectPath, config) {
24179
24179
  maxDepth: mergedConfig.analysis.maxDepth
24180
24180
  });
24181
24181
  const tokenMethod = mergedConfig.tokens.method;
24182
- const files = [];
24183
- for (const entry of walkEntries) {
24182
+ const BATCH_SIZE = 50;
24183
+ async function estimateFileTokens(entry) {
24184
24184
  let tokens;
24185
24185
  if (tokenMethod === "tiktoken") {
24186
24186
  try {
@@ -24192,7 +24192,7 @@ async function analyzeProject(projectPath, config) {
24192
24192
  } else {
24193
24193
  tokens = countTokensChars4(entry.size);
24194
24194
  }
24195
- files.push({
24195
+ return {
24196
24196
  path: entry.path,
24197
24197
  relativePath: entry.relativePath,
24198
24198
  extension: entry.extension,
@@ -24201,16 +24201,20 @@ async function analyzeProject(projectPath, config) {
24201
24201
  lines: entry.lines,
24202
24202
  lastModified: entry.lastModified,
24203
24203
  kind: classifyFileKind(entry.relativePath),
24204
- // Graph data — populated by graph analysis
24205
24204
  imports: [],
24206
24205
  importedBy: [],
24207
24206
  isHub: false,
24208
24207
  complexity: 0,
24209
- // Risk data — populated by risk analysis
24210
24208
  riskScore: 0,
24211
24209
  riskFactors: [],
24212
24210
  exclusionImpact: "none"
24213
- });
24211
+ };
24212
+ }
24213
+ const files = [];
24214
+ for (let i = 0; i < walkEntries.length; i += BATCH_SIZE) {
24215
+ const batch = walkEntries.slice(i, i + BATCH_SIZE);
24216
+ const results = await Promise.all(batch.map(estimateFileTokens));
24217
+ files.push(...results);
24214
24218
  }
24215
24219
  const graph = buildProjectGraph(absPath, files);
24216
24220
  for (const file of files) {
@@ -24769,10 +24773,7 @@ async function auditProject(projectPath, filePaths, options = {}) {
24769
24773
  }
24770
24774
 
24771
24775
  // src/engine/pruner.ts
24772
- import { Project as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
24773
24776
  import { readFile as readFile4 } from "fs/promises";
24774
- import { existsSync as existsSync5 } from "fs";
24775
- import { join as join4 } from "path";
24776
24777
  var TS_EXTENSIONS2 = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mts", "mjs"]);
24777
24778
  async function pruneFile(file, level) {
24778
24779
  if (level === "excluded") {
@@ -24795,23 +24796,7 @@ async function pruneTypeScript(file, level) {
24795
24796
  } catch {
24796
24797
  return emptyResult(file, level);
24797
24798
  }
24798
- let project;
24799
- try {
24800
- const tsConfigPath = findTsConfig(file.path);
24801
- project = new Project2({
24802
- tsConfigFilePath: tsConfigPath,
24803
- skipAddingFilesFromTsConfig: true,
24804
- compilerOptions: tsConfigPath ? void 0 : { allowJs: true, esModuleInterop: true }
24805
- });
24806
- project.createSourceFile(file.path, content, { overwrite: true });
24807
- } catch {
24808
- return pruneGenericFromContent(file, content, level);
24809
- }
24810
- const sourceFile = project.getSourceFiles()[0];
24811
- if (!sourceFile) {
24812
- return pruneGenericFromContent(file, content, level);
24813
- }
24814
- const prunedContent = level === "signatures" ? extractSignaturesAST(sourceFile) : extractSkeletonAST(sourceFile);
24799
+ const prunedContent = level === "signatures" ? extractSignaturesRegex(content) : extractSkeletonRegex(content);
24815
24800
  const prunedTokens = countTokensChars4(Buffer.byteLength(prunedContent, "utf-8"));
24816
24801
  const savingsPercent = file.tokens > 0 ? (file.tokens - prunedTokens) / file.tokens * 100 : 0;
24817
24802
  return {
@@ -24823,131 +24808,281 @@ async function pruneTypeScript(file, level) {
24823
24808
  savingsPercent: Math.max(0, savingsPercent)
24824
24809
  };
24825
24810
  }
24826
- function extractSignaturesAST(sf) {
24811
+ function extractSignaturesRegex(content) {
24812
+ const lines = content.split("\n");
24827
24813
  const parts = [];
24828
- for (const imp of sf.getImportDeclarations()) {
24829
- parts.push(imp.getText());
24830
- }
24831
- if (parts.length > 0) parts.push("");
24832
- for (const ta of sf.getTypeAliases()) {
24833
- addJSDoc(ta, parts);
24834
- parts.push(ta.getText());
24835
- }
24836
- for (const iface of sf.getInterfaces()) {
24837
- addJSDoc(iface, parts);
24838
- parts.push(iface.getText());
24839
- }
24840
- for (const en of sf.getEnums()) {
24841
- addJSDoc(en, parts);
24842
- parts.push(en.getText());
24843
- }
24844
- for (const fn of sf.getFunctions()) {
24845
- addJSDoc(fn, parts);
24846
- const isExported = fn.isExported();
24847
- const isAsync = fn.isAsync();
24848
- const name = fn.getName() ?? "<anonymous>";
24849
- const params = fn.getParameters().map((p) => p.getText()).join(", ");
24850
- const returnType = fn.getReturnTypeNode()?.getText();
24851
- const returnStr = returnType ? `: ${returnType}` : "";
24852
- const prefix = isExported ? "export " : "";
24853
- const asyncStr = isAsync ? "async " : "";
24854
- parts.push(`${prefix}${asyncStr}function ${name}(${params})${returnStr} { /* ... */ }`);
24855
- }
24856
- for (const stmt of sf.getVariableStatements()) {
24857
- for (const decl of stmt.getDeclarations()) {
24858
- const init = decl.getInitializer();
24859
- if (init && (init.getKind() === SyntaxKind2.ArrowFunction || init.getKind() === SyntaxKind2.FunctionExpression)) {
24860
- addJSDoc(stmt, parts);
24861
- const isExported = stmt.isExported();
24862
- const prefix = isExported ? "export " : "";
24863
- const kind = stmt.getDeclarationKind();
24864
- const name = decl.getName();
24865
- const typeNode = decl.getTypeNode()?.getText();
24866
- const typeStr = typeNode ? `: ${typeNode}` : "";
24867
- parts.push(`${prefix}${kind} ${name}${typeStr} = /* ... */;`);
24868
- } else {
24869
- addJSDoc(stmt, parts);
24870
- parts.push(stmt.getText());
24871
- }
24872
- }
24873
- }
24874
- for (const cls of sf.getClasses()) {
24875
- addJSDoc(cls, parts);
24876
- const isExported = cls.isExported();
24877
- const prefix = isExported ? "export " : "";
24878
- const name = cls.getName() ?? "<anonymous>";
24879
- const ext = cls.getExtends()?.getText();
24880
- const impl = cls.getImplements().map((i) => i.getText()).join(", ");
24881
- let header = `${prefix}class ${name}`;
24882
- if (ext) header += ` extends ${ext}`;
24883
- if (impl) header += ` implements ${impl}`;
24884
- header += " {";
24885
- parts.push(header);
24886
- for (const prop of cls.getProperties()) {
24887
- parts.push(` ${prop.getText()}`);
24888
- }
24889
- const ctor = cls.getConstructors()[0];
24890
- if (ctor) {
24891
- const ctorParams = ctor.getParameters().map((p) => p.getText()).join(", ");
24892
- parts.push(` constructor(${ctorParams}) { /* ... */ }`);
24893
- }
24894
- for (const method of cls.getMethods()) {
24895
- const isStatic = method.isStatic();
24896
- const isAsync = method.isAsync();
24897
- const methodName = method.getName();
24898
- const methodParams = method.getParameters().map((p) => p.getText()).join(", ");
24899
- const returnType = method.getReturnTypeNode()?.getText();
24900
- const returnStr = returnType ? `: ${returnType}` : "";
24901
- const staticStr = isStatic ? "static " : "";
24902
- const asyncStr = isAsync ? "async " : "";
24903
- parts.push(` ${staticStr}${asyncStr}${methodName}(${methodParams})${returnStr} { /* ... */ }`);
24904
- }
24905
- parts.push("}");
24906
- }
24907
- for (const exp of sf.getExportDeclarations()) {
24908
- parts.push(exp.getText());
24909
- }
24910
- for (const exp of sf.getExportAssignments()) {
24911
- parts.push(exp.getText());
24814
+ let i = 0;
24815
+ while (i < lines.length) {
24816
+ const line = lines[i];
24817
+ const trimmed = line.trim();
24818
+ if (trimmed === "") {
24819
+ i++;
24820
+ continue;
24821
+ }
24822
+ if (trimmed.startsWith("/**")) {
24823
+ const docLines = [];
24824
+ while (i < lines.length) {
24825
+ docLines.push(lines[i]);
24826
+ if (lines[i].includes("*/")) {
24827
+ i++;
24828
+ break;
24829
+ }
24830
+ i++;
24831
+ }
24832
+ parts.push(docLines.join("\n"));
24833
+ continue;
24834
+ }
24835
+ if (trimmed.startsWith("//")) {
24836
+ parts.push(line);
24837
+ i++;
24838
+ continue;
24839
+ }
24840
+ if (/^\s*(import|export)\s/.test(line) && (trimmed.includes(" from ") || trimmed.startsWith("import "))) {
24841
+ const block = collectBracedLine(lines, i);
24842
+ parts.push(block.text);
24843
+ i = block.nextIndex;
24844
+ continue;
24845
+ }
24846
+ if (/^\s*export\s*(\{|\*)/.test(trimmed)) {
24847
+ const block = collectBracedLine(lines, i);
24848
+ parts.push(block.text);
24849
+ i = block.nextIndex;
24850
+ continue;
24851
+ }
24852
+ if (/^\s*(export\s+)?type\s+\w/.test(trimmed) && !trimmed.startsWith("typeof")) {
24853
+ const block = collectBalanced(lines, i);
24854
+ parts.push(block.text);
24855
+ i = block.nextIndex;
24856
+ continue;
24857
+ }
24858
+ if (/^\s*(export\s+)?interface\s+\w/.test(trimmed)) {
24859
+ const block = collectBalanced(lines, i);
24860
+ parts.push(block.text);
24861
+ i = block.nextIndex;
24862
+ continue;
24863
+ }
24864
+ if (/^\s*(export\s+)?(const\s+)?enum\s+\w/.test(trimmed)) {
24865
+ const block = collectBalanced(lines, i);
24866
+ parts.push(block.text);
24867
+ i = block.nextIndex;
24868
+ continue;
24869
+ }
24870
+ const fnMatch = trimmed.match(/^(export\s+)?(async\s+)?function\s+(\w+)/);
24871
+ if (fnMatch) {
24872
+ const sig = extractFnSignature(lines, i);
24873
+ parts.push(`${sig} { /* ... */ }`);
24874
+ i = skipBlock(lines, i);
24875
+ continue;
24876
+ }
24877
+ const arrowMatch = trimmed.match(/^(export\s+)?(const|let|var)\s+(\w+)/);
24878
+ if (arrowMatch && looksLikeFunctionDecl(lines, i)) {
24879
+ const prefix = trimmed.match(/^((?:export\s+)?(?:const|let|var)\s+\w+[^=]*=)/)?.[1];
24880
+ if (prefix) {
24881
+ parts.push(`${prefix} /* ... */;`);
24882
+ }
24883
+ i = skipBlock(lines, i);
24884
+ continue;
24885
+ }
24886
+ if (arrowMatch) {
24887
+ const block = collectStatement(lines, i);
24888
+ parts.push(block.text);
24889
+ i = block.nextIndex;
24890
+ continue;
24891
+ }
24892
+ if (/^\s*(export\s+)?(abstract\s+)?class\s+\w/.test(trimmed)) {
24893
+ const classOutline = extractClassOutline(lines, i);
24894
+ parts.push(classOutline.text);
24895
+ i = classOutline.nextIndex;
24896
+ continue;
24897
+ }
24898
+ i++;
24912
24899
  }
24913
24900
  return parts.join("\n");
24914
24901
  }
24915
- function extractSkeletonAST(sf) {
24902
+ function extractSkeletonRegex(content) {
24903
+ const lines = content.split("\n");
24916
24904
  const parts = [];
24917
- for (const imp of sf.getImportDeclarations()) {
24918
- parts.push(imp.getText());
24919
- }
24920
- if (parts.length > 0) parts.push("");
24921
- for (const ta of sf.getTypeAliases()) {
24922
- if (ta.isExported()) parts.push(ta.getText());
24923
- }
24924
- for (const iface of sf.getInterfaces()) {
24925
- if (!iface.isExported()) continue;
24926
- const ext = iface.getExtends().map((e) => e.getText());
24927
- const extStr = ext.length > 0 ? ` extends ${ext.join(", ")}` : "";
24928
- parts.push(`export interface ${iface.getName()}${extStr} { /* ${iface.getProperties().length} props */ }`);
24929
- }
24930
- for (const en of sf.getEnums()) {
24931
- if (!en.isExported()) continue;
24932
- const members = en.getMembers().map((m) => m.getName());
24933
- parts.push(`export enum ${en.getName()} { ${members.join(", ")} }`);
24934
- }
24935
- for (const fn of sf.getFunctions()) {
24936
- if (!fn.isExported()) continue;
24937
- const name = fn.getName() ?? "<anonymous>";
24938
- const params = fn.getParameters().map((p) => p.getText()).join(", ");
24939
- parts.push(`export function ${name}(${params});`);
24940
- }
24941
- for (const cls of sf.getClasses()) {
24942
- if (!cls.isExported()) continue;
24943
- const methods = cls.getMethods().map((m) => m.getName());
24944
- parts.push(`export class ${cls.getName()} { /* methods: ${methods.join(", ")} */ }`);
24945
- }
24946
- for (const exp of sf.getExportDeclarations()) {
24947
- parts.push(exp.getText());
24905
+ let i = 0;
24906
+ while (i < lines.length) {
24907
+ const trimmed = lines[i].trim();
24908
+ if (/^import\s/.test(trimmed)) {
24909
+ const block = collectBracedLine(lines, i);
24910
+ parts.push(block.text);
24911
+ i = block.nextIndex;
24912
+ continue;
24913
+ }
24914
+ if (/^export\s+(type|interface)\s+\w/.test(trimmed)) {
24915
+ const block = collectBalanced(lines, i);
24916
+ parts.push(block.text);
24917
+ i = block.nextIndex;
24918
+ continue;
24919
+ }
24920
+ if (/^export\s+(const\s+)?enum\s+\w/.test(trimmed)) {
24921
+ const block = collectBalanced(lines, i);
24922
+ parts.push(block.text);
24923
+ i = block.nextIndex;
24924
+ continue;
24925
+ }
24926
+ if (/^export\s+(async\s+)?function\s+\w/.test(trimmed)) {
24927
+ const sig = extractFnSignature(lines, i);
24928
+ parts.push(`${sig};`);
24929
+ i = skipBlock(lines, i);
24930
+ continue;
24931
+ }
24932
+ if (/^export\s+(abstract\s+)?class\s+/.test(trimmed)) {
24933
+ const nameMatch = trimmed.match(/class\s+(\w+)/);
24934
+ const name = nameMatch?.[1] ?? "Unknown";
24935
+ const end = skipBlock(lines, i);
24936
+ const methods = [];
24937
+ for (let j = i + 1; j < end; j++) {
24938
+ const mt = lines[j].trim();
24939
+ const mm = mt.match(/^(?:static\s+)?(?:async\s+)?(\w+)\s*\(/);
24940
+ if (mm && mm[1] !== "constructor") methods.push(mm[1]);
24941
+ }
24942
+ parts.push(`export class ${name} { /* methods: ${methods.join(", ")} */ }`);
24943
+ i = end;
24944
+ continue;
24945
+ }
24946
+ if (/^export\s*(\{|\*)/.test(trimmed)) {
24947
+ const block = collectBracedLine(lines, i);
24948
+ parts.push(block.text);
24949
+ i = block.nextIndex;
24950
+ continue;
24951
+ }
24952
+ i++;
24948
24953
  }
24949
24954
  return parts.join("\n");
24950
24955
  }
24956
+ function collectBracedLine(lines, start) {
24957
+ let text = lines[start];
24958
+ let i = start + 1;
24959
+ while (i < lines.length && !text.includes(";") && !text.trimEnd().endsWith("'") && !text.trimEnd().endsWith('"')) {
24960
+ text += "\n" + lines[i];
24961
+ i++;
24962
+ }
24963
+ return { text, nextIndex: i };
24964
+ }
24965
+ function collectBalanced(lines, start) {
24966
+ let depth = 0;
24967
+ let text = "";
24968
+ let i = start;
24969
+ let started = false;
24970
+ while (i < lines.length) {
24971
+ const line = lines[i];
24972
+ text += (text ? "\n" : "") + line;
24973
+ for (const ch of line) {
24974
+ if (ch === "{" || ch === "(") {
24975
+ depth++;
24976
+ started = true;
24977
+ }
24978
+ if (ch === "}" || ch === ")") depth--;
24979
+ }
24980
+ i++;
24981
+ if (started && depth <= 0) break;
24982
+ if (!started && line.includes(";")) break;
24983
+ }
24984
+ return { text, nextIndex: i };
24985
+ }
24986
+ function collectStatement(lines, start) {
24987
+ let text = lines[start];
24988
+ let i = start + 1;
24989
+ if (text.includes(";")) return { text, nextIndex: i };
24990
+ let depth = 0;
24991
+ for (const ch of text) {
24992
+ if (ch === "{" || ch === "(" || ch === "[") depth++;
24993
+ if (ch === "}" || ch === ")" || ch === "]") depth--;
24994
+ }
24995
+ while (i < lines.length && depth > 0) {
24996
+ text += "\n" + lines[i];
24997
+ for (const ch of lines[i]) {
24998
+ if (ch === "{" || ch === "(" || ch === "[") depth++;
24999
+ if (ch === "}" || ch === ")" || ch === "]") depth--;
25000
+ }
25001
+ i++;
25002
+ }
25003
+ return { text, nextIndex: i };
25004
+ }
25005
+ function extractFnSignature(lines, start) {
25006
+ let sig = "";
25007
+ let i = start;
25008
+ while (i < lines.length) {
25009
+ const line = lines[i].trim();
25010
+ sig += (sig ? " " : "") + line;
25011
+ if (line.includes("{")) {
25012
+ sig = sig.replace(/\s*\{[^]*$/, "").trim();
25013
+ break;
25014
+ }
25015
+ i++;
25016
+ }
25017
+ return sig;
25018
+ }
25019
+ function skipBlock(lines, start) {
25020
+ let depth = 0;
25021
+ let i = start;
25022
+ let foundBrace = false;
25023
+ while (i < lines.length) {
25024
+ for (const ch of lines[i]) {
25025
+ if (ch === "{") {
25026
+ depth++;
25027
+ foundBrace = true;
25028
+ }
25029
+ if (ch === "}") depth--;
25030
+ }
25031
+ i++;
25032
+ if (foundBrace && depth <= 0) break;
25033
+ if (!foundBrace && lines[i - 1].includes(";")) break;
25034
+ }
25035
+ return i;
25036
+ }
25037
+ function looksLikeFunctionDecl(lines, start) {
25038
+ const chunk = lines.slice(start, Math.min(start + 5, lines.length)).join(" ");
25039
+ return /=>/.test(chunk) || /=\s*function/.test(chunk);
25040
+ }
25041
+ function extractClassOutline(lines, start) {
25042
+ const header = lines[start].trim();
25043
+ let headerText = header;
25044
+ let i = start + 1;
25045
+ if (!header.includes("{")) {
25046
+ while (i < lines.length) {
25047
+ headerText += " " + lines[i].trim();
25048
+ if (lines[i].includes("{")) {
25049
+ i++;
25050
+ break;
25051
+ }
25052
+ i++;
25053
+ }
25054
+ } else {
25055
+ i = start + 1;
25056
+ }
25057
+ const bodyParts = [headerText.replace(/\{[^]*$/, "{").trim()];
25058
+ let depth = 1;
25059
+ while (i < lines.length && depth > 0) {
25060
+ const line = lines[i];
25061
+ const trimmed = line.trim();
25062
+ for (const ch of line) {
25063
+ if (ch === "{") depth++;
25064
+ if (ch === "}") depth--;
25065
+ }
25066
+ if (depth <= 0) {
25067
+ i++;
25068
+ break;
25069
+ }
25070
+ if (depth === 1) {
25071
+ if (/^(private|protected|public|readonly|static|#)/.test(trimmed) && !trimmed.includes("(")) {
25072
+ bodyParts.push(` ${trimmed}`);
25073
+ } else if (/^constructor\s*\(/.test(trimmed)) {
25074
+ const sig = extractFnSignature(lines, i);
25075
+ bodyParts.push(` ${sig} { /* ... */ }`);
25076
+ } else if (/^(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?\w+\s*[(<]/.test(trimmed) && !trimmed.startsWith("//")) {
25077
+ const sig = extractFnSignature(lines, i);
25078
+ bodyParts.push(` ${sig} { /* ... */ }`);
25079
+ }
25080
+ }
25081
+ i++;
25082
+ }
25083
+ bodyParts.push("}");
25084
+ return { text: bodyParts.join("\n"), nextIndex: i };
25085
+ }
24951
25086
  async function pruneGeneric(file, level) {
24952
25087
  let content;
24953
25088
  try {
@@ -25008,22 +25143,6 @@ function emptyResult(file, level) {
25008
25143
  savingsPercent: 100
25009
25144
  };
25010
25145
  }
25011
- function addJSDoc(node, parts) {
25012
- if (!node.getJsDocs) return;
25013
- const docs = node.getJsDocs();
25014
- if (docs.length > 0) {
25015
- parts.push(docs[0].getText());
25016
- }
25017
- }
25018
- function findTsConfig(filePath) {
25019
- let dir = filePath;
25020
- for (let i = 0; i < 10; i++) {
25021
- dir = join4(dir, "..");
25022
- const candidate = join4(dir, "tsconfig.json");
25023
- if (existsSync5(candidate)) return candidate;
25024
- }
25025
- return void 0;
25026
- }
25027
25146
 
25028
25147
  // src/engine/graph-utils.ts
25029
25148
  function buildAdjacencyList(edges) {
@@ -25934,7 +26053,7 @@ function emptyResult2(baseBranch) {
25934
26053
  // src/engine/quality-gate.ts
25935
26054
  import { readFile as readFile5, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
25936
26055
  import { resolve as resolve5 } from "path";
25937
- import { existsSync as existsSync6 } from "fs";
26056
+ import { existsSync as existsSync5 } from "fs";
25938
26057
  var DEFAULT_GATE_CONFIG = {
25939
26058
  threshold: 70,
25940
26059
  failOnSecrets: true,
@@ -25945,7 +26064,7 @@ var DEFAULT_GATE_CONFIG = {
25945
26064
  };
25946
26065
  async function loadBaseline(projectPath, baselinePath) {
25947
26066
  const filePath = resolve5(projectPath, baselinePath || ".cto/baseline.json");
25948
- if (!existsSync6(filePath)) return null;
26067
+ if (!existsSync5(filePath)) return null;
25949
26068
  try {
25950
26069
  const content = await readFile5(filePath, "utf-8");
25951
26070
  return JSON.parse(content);
@@ -25955,7 +26074,7 @@ async function loadBaseline(projectPath, baselinePath) {
25955
26074
  }
25956
26075
  async function saveBaseline(projectPath, score, commit, branch, baselinePath) {
25957
26076
  const dir = resolve5(projectPath, ".cto");
25958
- if (!existsSync6(dir)) await mkdir2(dir, { recursive: true });
26077
+ if (!existsSync5(dir)) await mkdir2(dir, { recursive: true });
25959
26078
  const baseline = {
25960
26079
  score: score.overall,
25961
26080
  grade: score.grade,