cto-ai-cli 4.0.0 → 5.0.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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/api/dashboard.ts
4
- import { resolve as resolve5, join as join6 } from "path";
4
+ import { resolve as resolve5, join as join5 } from "path";
5
5
  import { writeFile as writeFile2, mkdir, readFile as readFile5 } from "fs/promises";
6
6
 
7
7
  // src/engine/analyzer.ts
@@ -1005,10 +1005,7 @@ function deduplicateFindings(findings) {
1005
1005
  }
1006
1006
 
1007
1007
  // src/engine/pruner.ts
1008
- import { Project as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
1009
1008
  import { readFile as readFile4 } from "fs/promises";
1010
- import { existsSync as existsSync3 } from "fs";
1011
- import { join as join5 } from "path";
1012
1009
  var TS_EXTENSIONS2 = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mts", "mjs"]);
1013
1010
  async function pruneFile(file, level) {
1014
1011
  if (level === "excluded") {
@@ -1031,23 +1028,7 @@ async function pruneTypeScript(file, level) {
1031
1028
  } catch {
1032
1029
  return emptyResult(file, level);
1033
1030
  }
1034
- let project;
1035
- try {
1036
- const tsConfigPath = findTsConfig(file.path);
1037
- project = new Project2({
1038
- tsConfigFilePath: tsConfigPath,
1039
- skipAddingFilesFromTsConfig: true,
1040
- compilerOptions: tsConfigPath ? void 0 : { allowJs: true, esModuleInterop: true }
1041
- });
1042
- project.createSourceFile(file.path, content, { overwrite: true });
1043
- } catch {
1044
- return pruneGenericFromContent(file, content, level);
1045
- }
1046
- const sourceFile = project.getSourceFiles()[0];
1047
- if (!sourceFile) {
1048
- return pruneGenericFromContent(file, content, level);
1049
- }
1050
- const prunedContent = level === "signatures" ? extractSignaturesAST(sourceFile) : extractSkeletonAST(sourceFile);
1031
+ const prunedContent = level === "signatures" ? extractSignaturesRegex(content) : extractSkeletonRegex(content);
1051
1032
  const prunedTokens = countTokensChars4(Buffer.byteLength(prunedContent, "utf-8"));
1052
1033
  const savingsPercent = file.tokens > 0 ? (file.tokens - prunedTokens) / file.tokens * 100 : 0;
1053
1034
  return {
@@ -1059,131 +1040,281 @@ async function pruneTypeScript(file, level) {
1059
1040
  savingsPercent: Math.max(0, savingsPercent)
1060
1041
  };
1061
1042
  }
1062
- function extractSignaturesAST(sf) {
1043
+ function extractSignaturesRegex(content) {
1044
+ const lines = content.split("\n");
1063
1045
  const parts = [];
1064
- for (const imp of sf.getImportDeclarations()) {
1065
- parts.push(imp.getText());
1066
- }
1067
- if (parts.length > 0) parts.push("");
1068
- for (const ta of sf.getTypeAliases()) {
1069
- addJSDoc(ta, parts);
1070
- parts.push(ta.getText());
1071
- }
1072
- for (const iface of sf.getInterfaces()) {
1073
- addJSDoc(iface, parts);
1074
- parts.push(iface.getText());
1075
- }
1076
- for (const en of sf.getEnums()) {
1077
- addJSDoc(en, parts);
1078
- parts.push(en.getText());
1079
- }
1080
- for (const fn of sf.getFunctions()) {
1081
- addJSDoc(fn, parts);
1082
- const isExported = fn.isExported();
1083
- const isAsync = fn.isAsync();
1084
- const name = fn.getName() ?? "<anonymous>";
1085
- const params = fn.getParameters().map((p) => p.getText()).join(", ");
1086
- const returnType = fn.getReturnTypeNode()?.getText();
1087
- const returnStr = returnType ? `: ${returnType}` : "";
1088
- const prefix = isExported ? "export " : "";
1089
- const asyncStr = isAsync ? "async " : "";
1090
- parts.push(`${prefix}${asyncStr}function ${name}(${params})${returnStr} { /* ... */ }`);
1091
- }
1092
- for (const stmt of sf.getVariableStatements()) {
1093
- for (const decl of stmt.getDeclarations()) {
1094
- const init = decl.getInitializer();
1095
- if (init && (init.getKind() === SyntaxKind2.ArrowFunction || init.getKind() === SyntaxKind2.FunctionExpression)) {
1096
- addJSDoc(stmt, parts);
1097
- const isExported = stmt.isExported();
1098
- const prefix = isExported ? "export " : "";
1099
- const kind = stmt.getDeclarationKind();
1100
- const name = decl.getName();
1101
- const typeNode = decl.getTypeNode()?.getText();
1102
- const typeStr = typeNode ? `: ${typeNode}` : "";
1103
- parts.push(`${prefix}${kind} ${name}${typeStr} = /* ... */;`);
1104
- } else {
1105
- addJSDoc(stmt, parts);
1106
- parts.push(stmt.getText());
1046
+ let i = 0;
1047
+ while (i < lines.length) {
1048
+ const line = lines[i];
1049
+ const trimmed = line.trim();
1050
+ if (trimmed === "") {
1051
+ i++;
1052
+ continue;
1053
+ }
1054
+ if (trimmed.startsWith("/**")) {
1055
+ const docLines = [];
1056
+ while (i < lines.length) {
1057
+ docLines.push(lines[i]);
1058
+ if (lines[i].includes("*/")) {
1059
+ i++;
1060
+ break;
1061
+ }
1062
+ i++;
1107
1063
  }
1064
+ parts.push(docLines.join("\n"));
1065
+ continue;
1108
1066
  }
1109
- }
1110
- for (const cls of sf.getClasses()) {
1111
- addJSDoc(cls, parts);
1112
- const isExported = cls.isExported();
1113
- const prefix = isExported ? "export " : "";
1114
- const name = cls.getName() ?? "<anonymous>";
1115
- const ext = cls.getExtends()?.getText();
1116
- const impl = cls.getImplements().map((i) => i.getText()).join(", ");
1117
- let header = `${prefix}class ${name}`;
1118
- if (ext) header += ` extends ${ext}`;
1119
- if (impl) header += ` implements ${impl}`;
1120
- header += " {";
1121
- parts.push(header);
1122
- for (const prop of cls.getProperties()) {
1123
- parts.push(` ${prop.getText()}`);
1124
- }
1125
- const ctor = cls.getConstructors()[0];
1126
- if (ctor) {
1127
- const ctorParams = ctor.getParameters().map((p) => p.getText()).join(", ");
1128
- parts.push(` constructor(${ctorParams}) { /* ... */ }`);
1129
- }
1130
- for (const method of cls.getMethods()) {
1131
- const isStatic = method.isStatic();
1132
- const isAsync = method.isAsync();
1133
- const methodName = method.getName();
1134
- const methodParams = method.getParameters().map((p) => p.getText()).join(", ");
1135
- const returnType = method.getReturnTypeNode()?.getText();
1136
- const returnStr = returnType ? `: ${returnType}` : "";
1137
- const staticStr = isStatic ? "static " : "";
1138
- const asyncStr = isAsync ? "async " : "";
1139
- parts.push(` ${staticStr}${asyncStr}${methodName}(${methodParams})${returnStr} { /* ... */ }`);
1140
- }
1141
- parts.push("}");
1142
- }
1143
- for (const exp of sf.getExportDeclarations()) {
1144
- parts.push(exp.getText());
1145
- }
1146
- for (const exp of sf.getExportAssignments()) {
1147
- parts.push(exp.getText());
1067
+ if (trimmed.startsWith("//")) {
1068
+ parts.push(line);
1069
+ i++;
1070
+ continue;
1071
+ }
1072
+ if (/^\s*(import|export)\s/.test(line) && (trimmed.includes(" from ") || trimmed.startsWith("import "))) {
1073
+ const block = collectBracedLine(lines, i);
1074
+ parts.push(block.text);
1075
+ i = block.nextIndex;
1076
+ continue;
1077
+ }
1078
+ if (/^\s*export\s*(\{|\*)/.test(trimmed)) {
1079
+ const block = collectBracedLine(lines, i);
1080
+ parts.push(block.text);
1081
+ i = block.nextIndex;
1082
+ continue;
1083
+ }
1084
+ if (/^\s*(export\s+)?type\s+\w/.test(trimmed) && !trimmed.startsWith("typeof")) {
1085
+ const block = collectBalanced(lines, i);
1086
+ parts.push(block.text);
1087
+ i = block.nextIndex;
1088
+ continue;
1089
+ }
1090
+ if (/^\s*(export\s+)?interface\s+\w/.test(trimmed)) {
1091
+ const block = collectBalanced(lines, i);
1092
+ parts.push(block.text);
1093
+ i = block.nextIndex;
1094
+ continue;
1095
+ }
1096
+ if (/^\s*(export\s+)?(const\s+)?enum\s+\w/.test(trimmed)) {
1097
+ const block = collectBalanced(lines, i);
1098
+ parts.push(block.text);
1099
+ i = block.nextIndex;
1100
+ continue;
1101
+ }
1102
+ const fnMatch = trimmed.match(/^(export\s+)?(async\s+)?function\s+(\w+)/);
1103
+ if (fnMatch) {
1104
+ const sig = extractFnSignature(lines, i);
1105
+ parts.push(`${sig} { /* ... */ }`);
1106
+ i = skipBlock(lines, i);
1107
+ continue;
1108
+ }
1109
+ const arrowMatch = trimmed.match(/^(export\s+)?(const|let|var)\s+(\w+)/);
1110
+ if (arrowMatch && looksLikeFunctionDecl(lines, i)) {
1111
+ const prefix = trimmed.match(/^((?:export\s+)?(?:const|let|var)\s+\w+[^=]*=)/)?.[1];
1112
+ if (prefix) {
1113
+ parts.push(`${prefix} /* ... */;`);
1114
+ }
1115
+ i = skipBlock(lines, i);
1116
+ continue;
1117
+ }
1118
+ if (arrowMatch) {
1119
+ const block = collectStatement(lines, i);
1120
+ parts.push(block.text);
1121
+ i = block.nextIndex;
1122
+ continue;
1123
+ }
1124
+ if (/^\s*(export\s+)?(abstract\s+)?class\s+\w/.test(trimmed)) {
1125
+ const classOutline = extractClassOutline(lines, i);
1126
+ parts.push(classOutline.text);
1127
+ i = classOutline.nextIndex;
1128
+ continue;
1129
+ }
1130
+ i++;
1148
1131
  }
1149
1132
  return parts.join("\n");
1150
1133
  }
1151
- function extractSkeletonAST(sf) {
1134
+ function extractSkeletonRegex(content) {
1135
+ const lines = content.split("\n");
1152
1136
  const parts = [];
1153
- for (const imp of sf.getImportDeclarations()) {
1154
- parts.push(imp.getText());
1155
- }
1156
- if (parts.length > 0) parts.push("");
1157
- for (const ta of sf.getTypeAliases()) {
1158
- if (ta.isExported()) parts.push(ta.getText());
1159
- }
1160
- for (const iface of sf.getInterfaces()) {
1161
- if (!iface.isExported()) continue;
1162
- const ext = iface.getExtends().map((e) => e.getText());
1163
- const extStr = ext.length > 0 ? ` extends ${ext.join(", ")}` : "";
1164
- parts.push(`export interface ${iface.getName()}${extStr} { /* ${iface.getProperties().length} props */ }`);
1165
- }
1166
- for (const en of sf.getEnums()) {
1167
- if (!en.isExported()) continue;
1168
- const members = en.getMembers().map((m) => m.getName());
1169
- parts.push(`export enum ${en.getName()} { ${members.join(", ")} }`);
1170
- }
1171
- for (const fn of sf.getFunctions()) {
1172
- if (!fn.isExported()) continue;
1173
- const name = fn.getName() ?? "<anonymous>";
1174
- const params = fn.getParameters().map((p) => p.getText()).join(", ");
1175
- parts.push(`export function ${name}(${params});`);
1176
- }
1177
- for (const cls of sf.getClasses()) {
1178
- if (!cls.isExported()) continue;
1179
- const methods = cls.getMethods().map((m) => m.getName());
1180
- parts.push(`export class ${cls.getName()} { /* methods: ${methods.join(", ")} */ }`);
1181
- }
1182
- for (const exp of sf.getExportDeclarations()) {
1183
- parts.push(exp.getText());
1137
+ let i = 0;
1138
+ while (i < lines.length) {
1139
+ const trimmed = lines[i].trim();
1140
+ if (/^import\s/.test(trimmed)) {
1141
+ const block = collectBracedLine(lines, i);
1142
+ parts.push(block.text);
1143
+ i = block.nextIndex;
1144
+ continue;
1145
+ }
1146
+ if (/^export\s+(type|interface)\s+\w/.test(trimmed)) {
1147
+ const block = collectBalanced(lines, i);
1148
+ parts.push(block.text);
1149
+ i = block.nextIndex;
1150
+ continue;
1151
+ }
1152
+ if (/^export\s+(const\s+)?enum\s+\w/.test(trimmed)) {
1153
+ const block = collectBalanced(lines, i);
1154
+ parts.push(block.text);
1155
+ i = block.nextIndex;
1156
+ continue;
1157
+ }
1158
+ if (/^export\s+(async\s+)?function\s+\w/.test(trimmed)) {
1159
+ const sig = extractFnSignature(lines, i);
1160
+ parts.push(`${sig};`);
1161
+ i = skipBlock(lines, i);
1162
+ continue;
1163
+ }
1164
+ if (/^export\s+(abstract\s+)?class\s+/.test(trimmed)) {
1165
+ const nameMatch = trimmed.match(/class\s+(\w+)/);
1166
+ const name = nameMatch?.[1] ?? "Unknown";
1167
+ const end = skipBlock(lines, i);
1168
+ const methods = [];
1169
+ for (let j = i + 1; j < end; j++) {
1170
+ const mt = lines[j].trim();
1171
+ const mm = mt.match(/^(?:static\s+)?(?:async\s+)?(\w+)\s*\(/);
1172
+ if (mm && mm[1] !== "constructor") methods.push(mm[1]);
1173
+ }
1174
+ parts.push(`export class ${name} { /* methods: ${methods.join(", ")} */ }`);
1175
+ i = end;
1176
+ continue;
1177
+ }
1178
+ if (/^export\s*(\{|\*)/.test(trimmed)) {
1179
+ const block = collectBracedLine(lines, i);
1180
+ parts.push(block.text);
1181
+ i = block.nextIndex;
1182
+ continue;
1183
+ }
1184
+ i++;
1184
1185
  }
1185
1186
  return parts.join("\n");
1186
1187
  }
1188
+ function collectBracedLine(lines, start) {
1189
+ let text = lines[start];
1190
+ let i = start + 1;
1191
+ while (i < lines.length && !text.includes(";") && !text.trimEnd().endsWith("'") && !text.trimEnd().endsWith('"')) {
1192
+ text += "\n" + lines[i];
1193
+ i++;
1194
+ }
1195
+ return { text, nextIndex: i };
1196
+ }
1197
+ function collectBalanced(lines, start) {
1198
+ let depth = 0;
1199
+ let text = "";
1200
+ let i = start;
1201
+ let started = false;
1202
+ while (i < lines.length) {
1203
+ const line = lines[i];
1204
+ text += (text ? "\n" : "") + line;
1205
+ for (const ch of line) {
1206
+ if (ch === "{" || ch === "(") {
1207
+ depth++;
1208
+ started = true;
1209
+ }
1210
+ if (ch === "}" || ch === ")") depth--;
1211
+ }
1212
+ i++;
1213
+ if (started && depth <= 0) break;
1214
+ if (!started && line.includes(";")) break;
1215
+ }
1216
+ return { text, nextIndex: i };
1217
+ }
1218
+ function collectStatement(lines, start) {
1219
+ let text = lines[start];
1220
+ let i = start + 1;
1221
+ if (text.includes(";")) return { text, nextIndex: i };
1222
+ let depth = 0;
1223
+ for (const ch of text) {
1224
+ if (ch === "{" || ch === "(" || ch === "[") depth++;
1225
+ if (ch === "}" || ch === ")" || ch === "]") depth--;
1226
+ }
1227
+ while (i < lines.length && depth > 0) {
1228
+ text += "\n" + lines[i];
1229
+ for (const ch of lines[i]) {
1230
+ if (ch === "{" || ch === "(" || ch === "[") depth++;
1231
+ if (ch === "}" || ch === ")" || ch === "]") depth--;
1232
+ }
1233
+ i++;
1234
+ }
1235
+ return { text, nextIndex: i };
1236
+ }
1237
+ function extractFnSignature(lines, start) {
1238
+ let sig = "";
1239
+ let i = start;
1240
+ while (i < lines.length) {
1241
+ const line = lines[i].trim();
1242
+ sig += (sig ? " " : "") + line;
1243
+ if (line.includes("{")) {
1244
+ sig = sig.replace(/\s*\{[^]*$/, "").trim();
1245
+ break;
1246
+ }
1247
+ i++;
1248
+ }
1249
+ return sig;
1250
+ }
1251
+ function skipBlock(lines, start) {
1252
+ let depth = 0;
1253
+ let i = start;
1254
+ let foundBrace = false;
1255
+ while (i < lines.length) {
1256
+ for (const ch of lines[i]) {
1257
+ if (ch === "{") {
1258
+ depth++;
1259
+ foundBrace = true;
1260
+ }
1261
+ if (ch === "}") depth--;
1262
+ }
1263
+ i++;
1264
+ if (foundBrace && depth <= 0) break;
1265
+ if (!foundBrace && lines[i - 1].includes(";")) break;
1266
+ }
1267
+ return i;
1268
+ }
1269
+ function looksLikeFunctionDecl(lines, start) {
1270
+ const chunk = lines.slice(start, Math.min(start + 5, lines.length)).join(" ");
1271
+ return /=>/.test(chunk) || /=\s*function/.test(chunk);
1272
+ }
1273
+ function extractClassOutline(lines, start) {
1274
+ const header = lines[start].trim();
1275
+ let headerText = header;
1276
+ let i = start + 1;
1277
+ if (!header.includes("{")) {
1278
+ while (i < lines.length) {
1279
+ headerText += " " + lines[i].trim();
1280
+ if (lines[i].includes("{")) {
1281
+ i++;
1282
+ break;
1283
+ }
1284
+ i++;
1285
+ }
1286
+ } else {
1287
+ i = start + 1;
1288
+ }
1289
+ const bodyParts = [headerText.replace(/\{[^]*$/, "{").trim()];
1290
+ let depth = 1;
1291
+ while (i < lines.length && depth > 0) {
1292
+ const line = lines[i];
1293
+ const trimmed = line.trim();
1294
+ for (const ch of line) {
1295
+ if (ch === "{") depth++;
1296
+ if (ch === "}") depth--;
1297
+ }
1298
+ if (depth <= 0) {
1299
+ i++;
1300
+ break;
1301
+ }
1302
+ if (depth === 1) {
1303
+ if (/^(private|protected|public|readonly|static|#)/.test(trimmed) && !trimmed.includes("(")) {
1304
+ bodyParts.push(` ${trimmed}`);
1305
+ } else if (/^constructor\s*\(/.test(trimmed)) {
1306
+ const sig = extractFnSignature(lines, i);
1307
+ bodyParts.push(` ${sig} { /* ... */ }`);
1308
+ } else if (/^(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?\w+\s*[(<]/.test(trimmed) && !trimmed.startsWith("//")) {
1309
+ const sig = extractFnSignature(lines, i);
1310
+ bodyParts.push(` ${sig} { /* ... */ }`);
1311
+ }
1312
+ }
1313
+ i++;
1314
+ }
1315
+ bodyParts.push("}");
1316
+ return { text: bodyParts.join("\n"), nextIndex: i };
1317
+ }
1187
1318
  async function pruneGeneric(file, level) {
1188
1319
  let content;
1189
1320
  try {
@@ -1244,22 +1375,6 @@ function emptyResult(file, level) {
1244
1375
  savingsPercent: 100
1245
1376
  };
1246
1377
  }
1247
- function addJSDoc(node, parts) {
1248
- if (!node.getJsDocs) return;
1249
- const docs = node.getJsDocs();
1250
- if (docs.length > 0) {
1251
- parts.push(docs[0].getText());
1252
- }
1253
- }
1254
- function findTsConfig(filePath) {
1255
- let dir = filePath;
1256
- for (let i = 0; i < 10; i++) {
1257
- dir = join5(dir, "..");
1258
- const candidate = join5(dir, "tsconfig.json");
1259
- if (existsSync3(candidate)) return candidate;
1260
- }
1261
- return void 0;
1262
- }
1263
1378
 
1264
1379
  // src/engine/graph-utils.ts
1265
1380
  function buildAdjacencyList(edges) {
@@ -1952,7 +2067,7 @@ function computeStrategyScore(strategy, budget) {
1952
2067
  // src/api/dashboard.ts
1953
2068
  async function loadHistory(ctoDir) {
1954
2069
  try {
1955
- const raw = await readFile5(join6(ctoDir, "history.json"), "utf-8");
2070
+ const raw = await readFile5(join5(ctoDir, "history.json"), "utf-8");
1956
2071
  return JSON.parse(raw);
1957
2072
  } catch {
1958
2073
  return [];
@@ -1960,11 +2075,11 @@ async function loadHistory(ctoDir) {
1960
2075
  }
1961
2076
  async function saveHistory(ctoDir, history) {
1962
2077
  await mkdir(ctoDir, { recursive: true });
1963
- await writeFile2(join6(ctoDir, "history.json"), JSON.stringify(history, null, 2));
2078
+ await writeFile2(join5(ctoDir, "history.json"), JSON.stringify(history, null, 2));
1964
2079
  }
1965
2080
  async function generateDashboard(projectPath, task = "general code review and refactoring", budget = 5e4) {
1966
2081
  const absPath = resolve5(projectPath);
1967
- const ctoDir = join6(absPath, ".cto");
2082
+ const ctoDir = join5(absPath, ".cto");
1968
2083
  const analysis = await getCachedAnalysis(absPath);
1969
2084
  const score = await computeContextScore(analysis, task, budget);
1970
2085
  const benchmark = await runBenchmark(analysis, task, budget);
@@ -1994,7 +2109,7 @@ async function generateDashboard(projectPath, task = "general code review and re
1994
2109
  generatedAt: /* @__PURE__ */ new Date()
1995
2110
  };
1996
2111
  const html = renderDashboardHTML(data);
1997
- const htmlPath = join6(ctoDir, "dashboard.html");
2112
+ const htmlPath = join5(ctoDir, "dashboard.html");
1998
2113
  await mkdir(ctoDir, { recursive: true });
1999
2114
  await writeFile2(htmlPath, html, "utf-8");
2000
2115
  return { htmlPath, data };