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.
@@ -207,10 +207,7 @@ function deduplicateFindings(findings) {
207
207
  }
208
208
 
209
209
  // src/engine/pruner.ts
210
- import { Project, SyntaxKind } from "ts-morph";
211
210
  import { readFile as readFile3 } from "fs/promises";
212
- import { existsSync as existsSync2 } from "fs";
213
- import { join as join2 } from "path";
214
211
 
215
212
  // src/engine/tokenizer.ts
216
213
  import { encodingForModel } from "js-tiktoken";
@@ -243,23 +240,7 @@ async function pruneTypeScript(file, level) {
243
240
  } catch {
244
241
  return emptyResult(file, level);
245
242
  }
246
- let project;
247
- try {
248
- const tsConfigPath = findTsConfig(file.path);
249
- project = new Project({
250
- tsConfigFilePath: tsConfigPath,
251
- skipAddingFilesFromTsConfig: true,
252
- compilerOptions: tsConfigPath ? void 0 : { allowJs: true, esModuleInterop: true }
253
- });
254
- project.createSourceFile(file.path, content, { overwrite: true });
255
- } catch {
256
- return pruneGenericFromContent(file, content, level);
257
- }
258
- const sourceFile = project.getSourceFiles()[0];
259
- if (!sourceFile) {
260
- return pruneGenericFromContent(file, content, level);
261
- }
262
- const prunedContent = level === "signatures" ? extractSignaturesAST(sourceFile) : extractSkeletonAST(sourceFile);
243
+ const prunedContent = level === "signatures" ? extractSignaturesRegex(content) : extractSkeletonRegex(content);
263
244
  const prunedTokens = countTokensChars4(Buffer.byteLength(prunedContent, "utf-8"));
264
245
  const savingsPercent = file.tokens > 0 ? (file.tokens - prunedTokens) / file.tokens * 100 : 0;
265
246
  return {
@@ -271,130 +252,280 @@ async function pruneTypeScript(file, level) {
271
252
  savingsPercent: Math.max(0, savingsPercent)
272
253
  };
273
254
  }
274
- function extractSignaturesAST(sf) {
255
+ function extractSignaturesRegex(content) {
256
+ const lines = content.split("\n");
275
257
  const parts = [];
276
- for (const imp of sf.getImportDeclarations()) {
277
- parts.push(imp.getText());
278
- }
279
- if (parts.length > 0) parts.push("");
280
- for (const ta of sf.getTypeAliases()) {
281
- addJSDoc(ta, parts);
282
- parts.push(ta.getText());
283
- }
284
- for (const iface of sf.getInterfaces()) {
285
- addJSDoc(iface, parts);
286
- parts.push(iface.getText());
287
- }
288
- for (const en of sf.getEnums()) {
289
- addJSDoc(en, parts);
290
- parts.push(en.getText());
291
- }
292
- for (const fn of sf.getFunctions()) {
293
- addJSDoc(fn, parts);
294
- const isExported = fn.isExported();
295
- const isAsync = fn.isAsync();
296
- const name = fn.getName() ?? "<anonymous>";
297
- const params = fn.getParameters().map((p) => p.getText()).join(", ");
298
- const returnType = fn.getReturnTypeNode()?.getText();
299
- const returnStr = returnType ? `: ${returnType}` : "";
300
- const prefix = isExported ? "export " : "";
301
- const asyncStr = isAsync ? "async " : "";
302
- parts.push(`${prefix}${asyncStr}function ${name}(${params})${returnStr} { /* ... */ }`);
303
- }
304
- for (const stmt of sf.getVariableStatements()) {
305
- for (const decl of stmt.getDeclarations()) {
306
- const init = decl.getInitializer();
307
- if (init && (init.getKind() === SyntaxKind.ArrowFunction || init.getKind() === SyntaxKind.FunctionExpression)) {
308
- addJSDoc(stmt, parts);
309
- const isExported = stmt.isExported();
310
- const prefix = isExported ? "export " : "";
311
- const kind = stmt.getDeclarationKind();
312
- const name = decl.getName();
313
- const typeNode = decl.getTypeNode()?.getText();
314
- const typeStr = typeNode ? `: ${typeNode}` : "";
315
- parts.push(`${prefix}${kind} ${name}${typeStr} = /* ... */;`);
316
- } else {
317
- addJSDoc(stmt, parts);
318
- parts.push(stmt.getText());
258
+ let i = 0;
259
+ while (i < lines.length) {
260
+ const line = lines[i];
261
+ const trimmed = line.trim();
262
+ if (trimmed === "") {
263
+ i++;
264
+ continue;
265
+ }
266
+ if (trimmed.startsWith("/**")) {
267
+ const docLines = [];
268
+ while (i < lines.length) {
269
+ docLines.push(lines[i]);
270
+ if (lines[i].includes("*/")) {
271
+ i++;
272
+ break;
273
+ }
274
+ i++;
319
275
  }
276
+ parts.push(docLines.join("\n"));
277
+ continue;
320
278
  }
321
- }
322
- for (const cls of sf.getClasses()) {
323
- addJSDoc(cls, parts);
324
- const isExported = cls.isExported();
325
- const prefix = isExported ? "export " : "";
326
- const name = cls.getName() ?? "<anonymous>";
327
- const ext = cls.getExtends()?.getText();
328
- const impl = cls.getImplements().map((i) => i.getText()).join(", ");
329
- let header = `${prefix}class ${name}`;
330
- if (ext) header += ` extends ${ext}`;
331
- if (impl) header += ` implements ${impl}`;
332
- header += " {";
333
- parts.push(header);
334
- for (const prop of cls.getProperties()) {
335
- parts.push(` ${prop.getText()}`);
279
+ if (trimmed.startsWith("//")) {
280
+ parts.push(line);
281
+ i++;
282
+ continue;
336
283
  }
337
- const ctor = cls.getConstructors()[0];
338
- if (ctor) {
339
- const ctorParams = ctor.getParameters().map((p) => p.getText()).join(", ");
340
- parts.push(` constructor(${ctorParams}) { /* ... */ }`);
284
+ if (/^\s*(import|export)\s/.test(line) && (trimmed.includes(" from ") || trimmed.startsWith("import "))) {
285
+ const block = collectBracedLine(lines, i);
286
+ parts.push(block.text);
287
+ i = block.nextIndex;
288
+ continue;
341
289
  }
342
- for (const method of cls.getMethods()) {
343
- const isStatic = method.isStatic();
344
- const isAsync = method.isAsync();
345
- const methodName = method.getName();
346
- const methodParams = method.getParameters().map((p) => p.getText()).join(", ");
347
- const returnType = method.getReturnTypeNode()?.getText();
348
- const returnStr = returnType ? `: ${returnType}` : "";
349
- const staticStr = isStatic ? "static " : "";
350
- const asyncStr = isAsync ? "async " : "";
351
- parts.push(` ${staticStr}${asyncStr}${methodName}(${methodParams})${returnStr} { /* ... */ }`);
290
+ if (/^\s*export\s*(\{|\*)/.test(trimmed)) {
291
+ const block = collectBracedLine(lines, i);
292
+ parts.push(block.text);
293
+ i = block.nextIndex;
294
+ continue;
352
295
  }
353
- parts.push("}");
354
- }
355
- for (const exp of sf.getExportDeclarations()) {
356
- parts.push(exp.getText());
357
- }
358
- for (const exp of sf.getExportAssignments()) {
359
- parts.push(exp.getText());
296
+ if (/^\s*(export\s+)?type\s+\w/.test(trimmed) && !trimmed.startsWith("typeof")) {
297
+ const block = collectBalanced(lines, i);
298
+ parts.push(block.text);
299
+ i = block.nextIndex;
300
+ continue;
301
+ }
302
+ if (/^\s*(export\s+)?interface\s+\w/.test(trimmed)) {
303
+ const block = collectBalanced(lines, i);
304
+ parts.push(block.text);
305
+ i = block.nextIndex;
306
+ continue;
307
+ }
308
+ if (/^\s*(export\s+)?(const\s+)?enum\s+\w/.test(trimmed)) {
309
+ const block = collectBalanced(lines, i);
310
+ parts.push(block.text);
311
+ i = block.nextIndex;
312
+ continue;
313
+ }
314
+ const fnMatch = trimmed.match(/^(export\s+)?(async\s+)?function\s+(\w+)/);
315
+ if (fnMatch) {
316
+ const sig = extractFnSignature(lines, i);
317
+ parts.push(`${sig} { /* ... */ }`);
318
+ i = skipBlock(lines, i);
319
+ continue;
320
+ }
321
+ const arrowMatch = trimmed.match(/^(export\s+)?(const|let|var)\s+(\w+)/);
322
+ if (arrowMatch && looksLikeFunctionDecl(lines, i)) {
323
+ const prefix = trimmed.match(/^((?:export\s+)?(?:const|let|var)\s+\w+[^=]*=)/)?.[1];
324
+ if (prefix) {
325
+ parts.push(`${prefix} /* ... */;`);
326
+ }
327
+ i = skipBlock(lines, i);
328
+ continue;
329
+ }
330
+ if (arrowMatch) {
331
+ const block = collectStatement(lines, i);
332
+ parts.push(block.text);
333
+ i = block.nextIndex;
334
+ continue;
335
+ }
336
+ if (/^\s*(export\s+)?(abstract\s+)?class\s+\w/.test(trimmed)) {
337
+ const classOutline = extractClassOutline(lines, i);
338
+ parts.push(classOutline.text);
339
+ i = classOutline.nextIndex;
340
+ continue;
341
+ }
342
+ i++;
360
343
  }
361
344
  return parts.join("\n");
362
345
  }
363
- function extractSkeletonAST(sf) {
346
+ function extractSkeletonRegex(content) {
347
+ const lines = content.split("\n");
364
348
  const parts = [];
365
- for (const imp of sf.getImportDeclarations()) {
366
- parts.push(imp.getText());
367
- }
368
- if (parts.length > 0) parts.push("");
369
- for (const ta of sf.getTypeAliases()) {
370
- if (ta.isExported()) parts.push(ta.getText());
349
+ let i = 0;
350
+ while (i < lines.length) {
351
+ const trimmed = lines[i].trim();
352
+ if (/^import\s/.test(trimmed)) {
353
+ const block = collectBracedLine(lines, i);
354
+ parts.push(block.text);
355
+ i = block.nextIndex;
356
+ continue;
357
+ }
358
+ if (/^export\s+(type|interface)\s+\w/.test(trimmed)) {
359
+ const block = collectBalanced(lines, i);
360
+ parts.push(block.text);
361
+ i = block.nextIndex;
362
+ continue;
363
+ }
364
+ if (/^export\s+(const\s+)?enum\s+\w/.test(trimmed)) {
365
+ const block = collectBalanced(lines, i);
366
+ parts.push(block.text);
367
+ i = block.nextIndex;
368
+ continue;
369
+ }
370
+ if (/^export\s+(async\s+)?function\s+\w/.test(trimmed)) {
371
+ const sig = extractFnSignature(lines, i);
372
+ parts.push(`${sig};`);
373
+ i = skipBlock(lines, i);
374
+ continue;
375
+ }
376
+ if (/^export\s+(abstract\s+)?class\s+/.test(trimmed)) {
377
+ const nameMatch = trimmed.match(/class\s+(\w+)/);
378
+ const name = nameMatch?.[1] ?? "Unknown";
379
+ const end = skipBlock(lines, i);
380
+ const methods = [];
381
+ for (let j = i + 1; j < end; j++) {
382
+ const mt = lines[j].trim();
383
+ const mm = mt.match(/^(?:static\s+)?(?:async\s+)?(\w+)\s*\(/);
384
+ if (mm && mm[1] !== "constructor") methods.push(mm[1]);
385
+ }
386
+ parts.push(`export class ${name} { /* methods: ${methods.join(", ")} */ }`);
387
+ i = end;
388
+ continue;
389
+ }
390
+ if (/^export\s*(\{|\*)/.test(trimmed)) {
391
+ const block = collectBracedLine(lines, i);
392
+ parts.push(block.text);
393
+ i = block.nextIndex;
394
+ continue;
395
+ }
396
+ i++;
371
397
  }
372
- for (const iface of sf.getInterfaces()) {
373
- if (!iface.isExported()) continue;
374
- const ext = iface.getExtends().map((e) => e.getText());
375
- const extStr = ext.length > 0 ? ` extends ${ext.join(", ")}` : "";
376
- parts.push(`export interface ${iface.getName()}${extStr} { /* ${iface.getProperties().length} props */ }`);
398
+ return parts.join("\n");
399
+ }
400
+ function collectBracedLine(lines, start) {
401
+ let text = lines[start];
402
+ let i = start + 1;
403
+ while (i < lines.length && !text.includes(";") && !text.trimEnd().endsWith("'") && !text.trimEnd().endsWith('"')) {
404
+ text += "\n" + lines[i];
405
+ i++;
406
+ }
407
+ return { text, nextIndex: i };
408
+ }
409
+ function collectBalanced(lines, start) {
410
+ let depth = 0;
411
+ let text = "";
412
+ let i = start;
413
+ let started = false;
414
+ while (i < lines.length) {
415
+ const line = lines[i];
416
+ text += (text ? "\n" : "") + line;
417
+ for (const ch of line) {
418
+ if (ch === "{" || ch === "(") {
419
+ depth++;
420
+ started = true;
421
+ }
422
+ if (ch === "}" || ch === ")") depth--;
423
+ }
424
+ i++;
425
+ if (started && depth <= 0) break;
426
+ if (!started && line.includes(";")) break;
377
427
  }
378
- for (const en of sf.getEnums()) {
379
- if (!en.isExported()) continue;
380
- const members = en.getMembers().map((m) => m.getName());
381
- parts.push(`export enum ${en.getName()} { ${members.join(", ")} }`);
428
+ return { text, nextIndex: i };
429
+ }
430
+ function collectStatement(lines, start) {
431
+ let text = lines[start];
432
+ let i = start + 1;
433
+ if (text.includes(";")) return { text, nextIndex: i };
434
+ let depth = 0;
435
+ for (const ch of text) {
436
+ if (ch === "{" || ch === "(" || ch === "[") depth++;
437
+ if (ch === "}" || ch === ")" || ch === "]") depth--;
438
+ }
439
+ while (i < lines.length && depth > 0) {
440
+ text += "\n" + lines[i];
441
+ for (const ch of lines[i]) {
442
+ if (ch === "{" || ch === "(" || ch === "[") depth++;
443
+ if (ch === "}" || ch === ")" || ch === "]") depth--;
444
+ }
445
+ i++;
382
446
  }
383
- for (const fn of sf.getFunctions()) {
384
- if (!fn.isExported()) continue;
385
- const name = fn.getName() ?? "<anonymous>";
386
- const params = fn.getParameters().map((p) => p.getText()).join(", ");
387
- parts.push(`export function ${name}(${params});`);
447
+ return { text, nextIndex: i };
448
+ }
449
+ function extractFnSignature(lines, start) {
450
+ let sig = "";
451
+ let i = start;
452
+ while (i < lines.length) {
453
+ const line = lines[i].trim();
454
+ sig += (sig ? " " : "") + line;
455
+ if (line.includes("{")) {
456
+ sig = sig.replace(/\s*\{[^]*$/, "").trim();
457
+ break;
458
+ }
459
+ i++;
388
460
  }
389
- for (const cls of sf.getClasses()) {
390
- if (!cls.isExported()) continue;
391
- const methods = cls.getMethods().map((m) => m.getName());
392
- parts.push(`export class ${cls.getName()} { /* methods: ${methods.join(", ")} */ }`);
461
+ return sig;
462
+ }
463
+ function skipBlock(lines, start) {
464
+ let depth = 0;
465
+ let i = start;
466
+ let foundBrace = false;
467
+ while (i < lines.length) {
468
+ for (const ch of lines[i]) {
469
+ if (ch === "{") {
470
+ depth++;
471
+ foundBrace = true;
472
+ }
473
+ if (ch === "}") depth--;
474
+ }
475
+ i++;
476
+ if (foundBrace && depth <= 0) break;
477
+ if (!foundBrace && lines[i - 1].includes(";")) break;
393
478
  }
394
- for (const exp of sf.getExportDeclarations()) {
395
- parts.push(exp.getText());
479
+ return i;
480
+ }
481
+ function looksLikeFunctionDecl(lines, start) {
482
+ const chunk = lines.slice(start, Math.min(start + 5, lines.length)).join(" ");
483
+ return /=>/.test(chunk) || /=\s*function/.test(chunk);
484
+ }
485
+ function extractClassOutline(lines, start) {
486
+ const header = lines[start].trim();
487
+ let headerText = header;
488
+ let i = start + 1;
489
+ if (!header.includes("{")) {
490
+ while (i < lines.length) {
491
+ headerText += " " + lines[i].trim();
492
+ if (lines[i].includes("{")) {
493
+ i++;
494
+ break;
495
+ }
496
+ i++;
497
+ }
498
+ } else {
499
+ i = start + 1;
500
+ }
501
+ const bodyParts = [headerText.replace(/\{[^]*$/, "{").trim()];
502
+ let depth = 1;
503
+ while (i < lines.length && depth > 0) {
504
+ const line = lines[i];
505
+ const trimmed = line.trim();
506
+ for (const ch of line) {
507
+ if (ch === "{") depth++;
508
+ if (ch === "}") depth--;
509
+ }
510
+ if (depth <= 0) {
511
+ i++;
512
+ break;
513
+ }
514
+ if (depth === 1) {
515
+ if (/^(private|protected|public|readonly|static|#)/.test(trimmed) && !trimmed.includes("(")) {
516
+ bodyParts.push(` ${trimmed}`);
517
+ } else if (/^constructor\s*\(/.test(trimmed)) {
518
+ const sig = extractFnSignature(lines, i);
519
+ bodyParts.push(` ${sig} { /* ... */ }`);
520
+ } else if (/^(?:static\s+)?(?:async\s+)?(?:get\s+|set\s+)?\w+\s*[(<]/.test(trimmed) && !trimmed.startsWith("//")) {
521
+ const sig = extractFnSignature(lines, i);
522
+ bodyParts.push(` ${sig} { /* ... */ }`);
523
+ }
524
+ }
525
+ i++;
396
526
  }
397
- return parts.join("\n");
527
+ bodyParts.push("}");
528
+ return { text: bodyParts.join("\n"), nextIndex: i };
398
529
  }
399
530
  async function pruneGeneric(file, level) {
400
531
  let content;
@@ -456,22 +587,6 @@ function emptyResult(file, level) {
456
587
  savingsPercent: 100
457
588
  };
458
589
  }
459
- function addJSDoc(node, parts) {
460
- if (!node.getJsDocs) return;
461
- const docs = node.getJsDocs();
462
- if (docs.length > 0) {
463
- parts.push(docs[0].getText());
464
- }
465
- }
466
- function findTsConfig(filePath) {
467
- let dir = filePath;
468
- for (let i = 0; i < 10; i++) {
469
- dir = join2(dir, "..");
470
- const candidate = join2(dir, "tsconfig.json");
471
- if (existsSync2(candidate)) return candidate;
472
- }
473
- return void 0;
474
- }
475
590
 
476
591
  // src/engine/graph-utils.ts
477
592
  function buildAdjacencyList(edges) {
@@ -1230,18 +1345,18 @@ function makeSection(id, role, content) {
1230
1345
  // src/govern/audit.ts
1231
1346
  import { randomUUID, createHash as createHash3 } from "crypto";
1232
1347
  import { readdir, chmod } from "fs/promises";
1233
- import { join as join3 } from "path";
1348
+ import { join as join2 } from "path";
1234
1349
  import { userInfo } from "os";
1235
1350
  import { homedir } from "os";
1236
1351
  var CTO_DIR = ".cto-ai";
1237
1352
  var AUDIT_DIR = "audit";
1238
1353
  var MAX_ENTRIES_PER_FILE = 500;
1239
1354
  function getAuditDir() {
1240
- return join3(homedir(), CTO_DIR, AUDIT_DIR);
1355
+ return join2(homedir(), CTO_DIR, AUDIT_DIR);
1241
1356
  }
1242
1357
  function getCurrentAuditFile() {
1243
1358
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0].replace(/-/g, "");
1244
- return join3(getAuditDir(), `audit_${date}.json`);
1359
+ return join2(getAuditDir(), `audit_${date}.json`);
1245
1360
  }
1246
1361
  function computeIntegrityHash(entry) {
1247
1362
  const payload = JSON.stringify({
@@ -1269,7 +1384,7 @@ async function readJSON(filePath) {
1269
1384
  }
1270
1385
  async function writeJSON(filePath, data) {
1271
1386
  const { writeFile: writeFile2 } = await import("fs/promises");
1272
- await ensureDir(join3(filePath, ".."));
1387
+ await ensureDir(join2(filePath, ".."));
1273
1388
  await writeFile2(filePath, JSON.stringify(data, null, 2), "utf-8");
1274
1389
  }
1275
1390
  async function logAudit(action, projectPath, details = {}) {