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.
@@ -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++;
1063
+ }
1064
+ parts.push(docLines.join("\n"));
1065
+ continue;
1066
+ }
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} /* ... */;`);
1107
1114
  }
1115
+ i = skipBlock(lines, i);
1116
+ continue;
1108
1117
  }
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());
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) {
@@ -2922,18 +3037,18 @@ function formatCost(cost) {
2922
3037
  // src/govern/audit.ts
2923
3038
  import { randomUUID, createHash as createHash5 } from "crypto";
2924
3039
  import { readdir as readdir3, chmod } from "fs/promises";
2925
- import { join as join6 } from "path";
3040
+ import { join as join5 } from "path";
2926
3041
  import { userInfo } from "os";
2927
3042
  import { homedir } from "os";
2928
3043
  var CTO_DIR = ".cto-ai";
2929
3044
  var AUDIT_DIR = "audit";
2930
3045
  var MAX_ENTRIES_PER_FILE = 500;
2931
3046
  function getAuditDir() {
2932
- return join6(homedir(), CTO_DIR, AUDIT_DIR);
3047
+ return join5(homedir(), CTO_DIR, AUDIT_DIR);
2933
3048
  }
2934
3049
  function getCurrentAuditFile() {
2935
3050
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0].replace(/-/g, "");
2936
- return join6(getAuditDir(), `audit_${date}.json`);
3051
+ return join5(getAuditDir(), `audit_${date}.json`);
2937
3052
  }
2938
3053
  function computeIntegrityHash(entry) {
2939
3054
  const payload = JSON.stringify({
@@ -2961,7 +3076,7 @@ async function readJSON(filePath) {
2961
3076
  }
2962
3077
  async function writeJSON(filePath, data) {
2963
3078
  const { writeFile: writeFile2 } = await import("fs/promises");
2964
- await ensureDir(join6(filePath, ".."));
3079
+ await ensureDir(join5(filePath, ".."));
2965
3080
  await writeFile2(filePath, JSON.stringify(data, null, 2), "utf-8");
2966
3081
  }
2967
3082
  async function logAudit(action, projectPath, details = {}) {
@@ -3134,6 +3249,12 @@ function checkRateLimit(ip) {
3134
3249
  entry.count++;
3135
3250
  return true;
3136
3251
  }
3252
+ setInterval(() => {
3253
+ const now = Date.now();
3254
+ for (const [ip, entry] of requestCounts) {
3255
+ if (now > entry.reset) requestCounts.delete(ip);
3256
+ }
3257
+ }, 5 * 6e4).unref();
3137
3258
  function getIP(req) {
3138
3259
  return req.headers["x-forwarded-for"]?.split(",")[0]?.trim() ?? req.socket.remoteAddress ?? "unknown";
3139
3260
  }