@corbat-tech/coco 1.0.2 → 1.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.
package/dist/cli/index.js CHANGED
@@ -2,9 +2,9 @@
2
2
  import * as os2 from 'os';
3
3
  import { homedir } from 'os';
4
4
  import * as path20 from 'path';
5
- import path20__default, { join, dirname } from 'path';
5
+ import path20__default, { join, dirname, basename } from 'path';
6
6
  import * as fs19 from 'fs';
7
- import fs19__default, { readFileSync, constants } from 'fs';
7
+ import fs19__default, { readFileSync, existsSync, constants } from 'fs';
8
8
  import * as fs22 from 'fs/promises';
9
9
  import fs22__default, { writeFile, access, readFile, mkdir, readdir, rm } from 'fs/promises';
10
10
  import { Command } from 'commander';
@@ -279,8 +279,8 @@ __export(trust_store_exports, {
279
279
  saveTrustStore: () => saveTrustStore,
280
280
  updateLastAccessed: () => updateLastAccessed
281
281
  });
282
- async function ensureDir(path35) {
283
- await mkdir(dirname(path35), { recursive: true });
282
+ async function ensureDir(path37) {
283
+ await mkdir(dirname(path37), { recursive: true });
284
284
  }
285
285
  async function loadTrustStore(storePath = TRUST_STORE_PATH) {
286
286
  try {
@@ -358,8 +358,8 @@ function canPerformOperation(store, projectPath, operation) {
358
358
  };
359
359
  return permissions[level]?.includes(operation) ?? false;
360
360
  }
361
- function normalizePath(path35) {
362
- return join(path35);
361
+ function normalizePath(path37) {
362
+ return join(path37);
363
363
  }
364
364
  function createTrustStore(storePath = TRUST_STORE_PATH) {
365
365
  let store = null;
@@ -609,8 +609,8 @@ Generated by Corbat-Coco v0.1.0
609
609
 
610
610
  // src/cli/commands/init.ts
611
611
  function registerInitCommand(program2) {
612
- program2.command("init").description("Initialize a new Corbat-Coco project").argument("[path]", "Project directory path", ".").option("-t, --template <template>", "Project template to use").option("-y, --yes", "Skip prompts and use defaults").option("--skip-discovery", "Skip the discovery phase (use existing spec)").action(async (path35, options) => {
613
- await runInit(path35, options);
612
+ program2.command("init").description("Initialize a new Corbat-Coco project").argument("[path]", "Project directory path", ".").option("-t, --template <template>", "Project template to use").option("-y, --yes", "Skip prompts and use defaults").option("--skip-discovery", "Skip the discovery phase (use existing spec)").action(async (path37, options) => {
613
+ await runInit(path37, options);
614
614
  });
615
615
  }
616
616
  async function runInit(projectPath, options) {
@@ -689,18 +689,18 @@ async function gatherProjectInfo() {
689
689
  language
690
690
  };
691
691
  }
692
- function getDefaultProjectInfo(path35) {
693
- const name = path35 === "." ? "my-project" : path35.split("/").pop() || "my-project";
692
+ function getDefaultProjectInfo(path37) {
693
+ const name = path37 === "." ? "my-project" : path37.split("/").pop() || "my-project";
694
694
  return {
695
695
  name,
696
696
  description: "",
697
697
  language: "typescript"
698
698
  };
699
699
  }
700
- async function checkExistingProject(path35) {
700
+ async function checkExistingProject(path37) {
701
701
  try {
702
- const fs38 = await import('fs/promises');
703
- await fs38.access(`${path35}/.coco`);
702
+ const fs39 = await import('fs/promises');
703
+ await fs39.access(`${path37}/.coco`);
704
704
  return true;
705
705
  } catch {
706
706
  return false;
@@ -721,6 +721,12 @@ var QualityConfigSchema = z.object({
721
721
  minIterations: z.number().min(1).max(10).default(2),
722
722
  convergenceThreshold: z.number().min(0).max(10).default(2),
723
723
  securityThreshold: z.number().min(0).max(100).default(100)
724
+ }).refine((data) => data.minIterations <= data.maxIterations, {
725
+ message: "minIterations must be <= maxIterations",
726
+ path: ["minIterations"]
727
+ }).refine((data) => data.convergenceThreshold < data.minScore, {
728
+ message: "convergenceThreshold must be < minScore",
729
+ path: ["convergenceThreshold"]
724
730
  });
725
731
  var PersistenceConfigSchema = z.object({
726
732
  checkpointInterval: z.number().min(6e4).default(3e5),
@@ -976,6 +982,35 @@ var TimeoutError = class extends CocoError {
976
982
  this.operation = options.operation;
977
983
  }
978
984
  };
985
+ var ERROR_SUGGESTIONS = {
986
+ PROVIDER_ERROR: "Check your API key and provider configuration. Run 'coco setup' to reconfigure.",
987
+ CONFIG_ERROR: "Check your .coco/config.json or run 'coco setup' to reconfigure.",
988
+ FILESYSTEM_ERROR: "Check that the path exists and you have read/write permissions.",
989
+ VALIDATION_ERROR: "Check the input data format. See 'coco --help' for usage.",
990
+ PHASE_ERROR: "Phase execution failed. Try 'coco resume' to continue from the last checkpoint.",
991
+ TASK_ERROR: "Task execution failed. The task can be retried from the last checkpoint with 'coco resume'.",
992
+ QUALITY_ERROR: "Quality score below threshold. Review the issues listed above and iterate on the code.",
993
+ RECOVERY_ERROR: "Checkpoint may be corrupted. Try 'coco init --force' to start fresh.",
994
+ TOOL_ERROR: "A tool execution failed. Check the error details above and retry.",
995
+ TIMEOUT_ERROR: "Operation timed out. Try increasing the timeout in config or simplifying the request.",
996
+ UNEXPECTED_ERROR: "An unexpected error occurred. Please report at github.com/corbat/corbat-coco/issues."
997
+ };
998
+ function formatError(error) {
999
+ if (error instanceof CocoError) {
1000
+ let message = `[${error.code}] ${error.message}`;
1001
+ const suggestion = error.suggestion ?? ERROR_SUGGESTIONS[error.code];
1002
+ if (suggestion) {
1003
+ message += `
1004
+ Suggestion: ${suggestion}`;
1005
+ }
1006
+ return message;
1007
+ }
1008
+ if (error instanceof Error) {
1009
+ return `${error.message}
1010
+ Suggestion: ${ERROR_SUGGESTIONS["UNEXPECTED_ERROR"]}`;
1011
+ }
1012
+ return String(error);
1013
+ }
979
1014
 
980
1015
  // src/config/loader.ts
981
1016
  init_paths();
@@ -7579,35 +7614,35 @@ async function createCliPhaseContext(projectPath, _onUserInput) {
7579
7614
  },
7580
7615
  tools: {
7581
7616
  file: {
7582
- async read(path35) {
7583
- const fs38 = await import('fs/promises');
7584
- return fs38.readFile(path35, "utf-8");
7617
+ async read(path37) {
7618
+ const fs39 = await import('fs/promises');
7619
+ return fs39.readFile(path37, "utf-8");
7585
7620
  },
7586
- async write(path35, content) {
7587
- const fs38 = await import('fs/promises');
7621
+ async write(path37, content) {
7622
+ const fs39 = await import('fs/promises');
7588
7623
  const nodePath = await import('path');
7589
- await fs38.mkdir(nodePath.dirname(path35), { recursive: true });
7590
- await fs38.writeFile(path35, content, "utf-8");
7624
+ await fs39.mkdir(nodePath.dirname(path37), { recursive: true });
7625
+ await fs39.writeFile(path37, content, "utf-8");
7591
7626
  },
7592
- async exists(path35) {
7593
- const fs38 = await import('fs/promises');
7627
+ async exists(path37) {
7628
+ const fs39 = await import('fs/promises');
7594
7629
  try {
7595
- await fs38.access(path35);
7630
+ await fs39.access(path37);
7596
7631
  return true;
7597
7632
  } catch {
7598
7633
  return false;
7599
7634
  }
7600
7635
  },
7601
7636
  async glob(pattern) {
7602
- const { glob: glob9 } = await import('glob');
7603
- return glob9(pattern, { cwd: projectPath });
7637
+ const { glob: glob15 } = await import('glob');
7638
+ return glob15(pattern, { cwd: projectPath });
7604
7639
  }
7605
7640
  },
7606
7641
  bash: {
7607
7642
  async exec(command, options = {}) {
7608
- const { execa: execa8 } = await import('execa');
7643
+ const { execa: execa10 } = await import('execa');
7609
7644
  try {
7610
- const result = await execa8(command, {
7645
+ const result = await execa10(command, {
7611
7646
  shell: true,
7612
7647
  cwd: options.cwd || projectPath,
7613
7648
  timeout: options.timeout,
@@ -7831,16 +7866,16 @@ async function loadTasks(_options) {
7831
7866
  ];
7832
7867
  }
7833
7868
  async function checkProjectState() {
7834
- const fs38 = await import('fs/promises');
7869
+ const fs39 = await import('fs/promises');
7835
7870
  let hasProject = false;
7836
7871
  let hasPlan = false;
7837
7872
  try {
7838
- await fs38.access(".coco");
7873
+ await fs39.access(".coco");
7839
7874
  hasProject = true;
7840
7875
  } catch {
7841
7876
  }
7842
7877
  try {
7843
- await fs38.access(".coco/planning/backlog.json");
7878
+ await fs39.access(".coco/planning/backlog.json");
7844
7879
  hasPlan = true;
7845
7880
  } catch {
7846
7881
  }
@@ -7944,24 +7979,24 @@ function getPhaseStatusForPhase(phase) {
7944
7979
  return "in_progress";
7945
7980
  }
7946
7981
  async function loadProjectState(cwd, config) {
7947
- const fs38 = await import('fs/promises');
7948
- const path35 = await import('path');
7949
- const statePath = path35.join(cwd, ".coco", "state.json");
7950
- const backlogPath = path35.join(cwd, ".coco", "planning", "backlog.json");
7951
- const checkpointDir = path35.join(cwd, ".coco", "checkpoints");
7982
+ const fs39 = await import('fs/promises');
7983
+ const path37 = await import('path');
7984
+ const statePath = path37.join(cwd, ".coco", "state.json");
7985
+ const backlogPath = path37.join(cwd, ".coco", "planning", "backlog.json");
7986
+ const checkpointDir = path37.join(cwd, ".coco", "checkpoints");
7952
7987
  let currentPhase = "idle";
7953
7988
  let metrics;
7954
7989
  let sprint;
7955
7990
  let checkpoints = [];
7956
7991
  try {
7957
- const stateContent = await fs38.readFile(statePath, "utf-8");
7992
+ const stateContent = await fs39.readFile(statePath, "utf-8");
7958
7993
  const stateData = JSON.parse(stateContent);
7959
7994
  currentPhase = stateData.currentPhase || "idle";
7960
7995
  metrics = stateData.metrics;
7961
7996
  } catch {
7962
7997
  }
7963
7998
  try {
7964
- const backlogContent = await fs38.readFile(backlogPath, "utf-8");
7999
+ const backlogContent = await fs39.readFile(backlogPath, "utf-8");
7965
8000
  const backlogData = JSON.parse(backlogContent);
7966
8001
  if (backlogData.currentSprint) {
7967
8002
  const tasks = backlogData.tasks || [];
@@ -7983,7 +8018,7 @@ async function loadProjectState(cwd, config) {
7983
8018
  } catch {
7984
8019
  }
7985
8020
  try {
7986
- const files = await fs38.readdir(checkpointDir);
8021
+ const files = await fs39.readdir(checkpointDir);
7987
8022
  checkpoints = files.filter((f) => f.endsWith(".json")).sort().reverse();
7988
8023
  } catch {
7989
8024
  }
@@ -8119,8 +8154,8 @@ async function restoreFromCheckpoint(_checkpoint) {
8119
8154
  }
8120
8155
  async function checkProjectExists() {
8121
8156
  try {
8122
- const fs38 = await import('fs/promises');
8123
- await fs38.access(".coco");
8157
+ const fs39 = await import('fs/promises');
8158
+ await fs39.access(".coco");
8124
8159
  return true;
8125
8160
  } catch {
8126
8161
  return false;
@@ -8659,12 +8694,12 @@ async function loadConfig2() {
8659
8694
  };
8660
8695
  }
8661
8696
  async function saveConfig(config) {
8662
- const fs38 = await import('fs/promises');
8663
- await fs38.mkdir(".coco", { recursive: true });
8664
- await fs38.writeFile(".coco/config.json", JSON.stringify(config, null, 2));
8697
+ const fs39 = await import('fs/promises');
8698
+ await fs39.mkdir(".coco", { recursive: true });
8699
+ await fs39.writeFile(".coco/config.json", JSON.stringify(config, null, 2));
8665
8700
  }
8666
- function getNestedValue(obj, path35) {
8667
- const keys = path35.split(".");
8701
+ function getNestedValue(obj, path37) {
8702
+ const keys = path37.split(".");
8668
8703
  let current = obj;
8669
8704
  for (const key of keys) {
8670
8705
  if (current === null || current === void 0 || typeof current !== "object") {
@@ -8674,8 +8709,8 @@ function getNestedValue(obj, path35) {
8674
8709
  }
8675
8710
  return current;
8676
8711
  }
8677
- function setNestedValue(obj, path35, value) {
8678
- const keys = path35.split(".");
8712
+ function setNestedValue(obj, path37, value) {
8713
+ const keys = path37.split(".");
8679
8714
  let current = obj;
8680
8715
  for (let i = 0; i < keys.length - 1; i++) {
8681
8716
  const key = keys[i];
@@ -8896,8 +8931,8 @@ var MCPRegistryImpl = class {
8896
8931
  /**
8897
8932
  * Ensure directory exists
8898
8933
  */
8899
- async ensureDir(path35) {
8900
- await mkdir(dirname(path35), { recursive: true });
8934
+ async ensureDir(path37) {
8935
+ await mkdir(dirname(path37), { recursive: true });
8901
8936
  }
8902
8937
  };
8903
8938
  function createMCPRegistry(registryPath) {
@@ -9416,9 +9451,9 @@ function createEmptyMemoryContext() {
9416
9451
  errors: []
9417
9452
  };
9418
9453
  }
9419
- function createMissingMemoryFile(path35, level) {
9454
+ function createMissingMemoryFile(path37, level) {
9420
9455
  return {
9421
- path: path35,
9456
+ path: path37,
9422
9457
  level,
9423
9458
  content: "",
9424
9459
  sections: [],
@@ -9784,6 +9819,7 @@ var helpCommand = {
9784
9819
  commands: [
9785
9820
  { cmd: "/help, /?", desc: "Show this help message" },
9786
9821
  { cmd: "/help tools", desc: "Show available agent tools" },
9822
+ { cmd: "/tutorial, /tut", desc: "Quick guide to using Coco" },
9787
9823
  { cmd: "/clear, /c", desc: "Clear conversation history" },
9788
9824
  { cmd: "/exit, /quit, /q", desc: "Exit the REPL" }
9789
9825
  ]
@@ -10329,11 +10365,11 @@ async function setupGcloudADC(provider) {
10329
10365
  console.log(chalk12.dim(" Opening browser for Google sign-in..."));
10330
10366
  console.log(chalk12.dim(" (Complete the sign-in in your browser, then return here)"));
10331
10367
  console.log();
10332
- const { exec: exec2 } = await import('child_process');
10333
- const { promisify: promisify3 } = await import('util');
10334
- const execAsync2 = promisify3(exec2);
10368
+ const { exec: exec3 } = await import('child_process');
10369
+ const { promisify: promisify4 } = await import('util');
10370
+ const execAsync3 = promisify4(exec3);
10335
10371
  try {
10336
- await execAsync2("gcloud auth application-default login", {
10372
+ await execAsync3("gcloud auth application-default login", {
10337
10373
  timeout: 12e4
10338
10374
  // 2 minute timeout
10339
10375
  });
@@ -11417,11 +11453,11 @@ async function setupGcloudADCForProvider(_provider) {
11417
11453
  if (p9.isCancel(runNow) || !runNow) {
11418
11454
  return false;
11419
11455
  }
11420
- const { exec: exec2 } = await import('child_process');
11421
- const { promisify: promisify3 } = await import('util');
11422
- const execAsync2 = promisify3(exec2);
11456
+ const { exec: exec3 } = await import('child_process');
11457
+ const { promisify: promisify4 } = await import('util');
11458
+ const execAsync3 = promisify4(exec3);
11423
11459
  try {
11424
- await execAsync2("gcloud auth application-default login", { timeout: 12e4 });
11460
+ await execAsync3("gcloud auth application-default login", { timeout: 12e4 });
11425
11461
  const token = await getADCAccessToken();
11426
11462
  if (token) {
11427
11463
  console.log(chalk12.green("\n \u2713 Authentication successful!\n"));
@@ -12106,8 +12142,8 @@ async function listTrustedProjects2(trustStore) {
12106
12142
  p9.log.message("");
12107
12143
  for (const project of projects) {
12108
12144
  const level = project.approvalLevel.toUpperCase().padEnd(5);
12109
- const path35 = project.path.length > 50 ? "..." + project.path.slice(-47) : project.path;
12110
- p9.log.message(` [${level}] ${path35}`);
12145
+ const path37 = project.path.length > 50 ? "..." + project.path.slice(-47) : project.path;
12146
+ p9.log.message(` [${level}] ${path37}`);
12111
12147
  p9.log.message(` Last accessed: ${new Date(project.lastAccessed).toLocaleString()}`);
12112
12148
  }
12113
12149
  p9.log.message("");
@@ -13277,7 +13313,7 @@ async function getCheckpoint(session, checkpointId) {
13277
13313
  return store.checkpoints.find((cp) => cp.id === checkpointId) ?? null;
13278
13314
  }
13279
13315
  async function restoreFiles(checkpoint, excludeFiles) {
13280
- const fs38 = await import('fs/promises');
13316
+ const fs39 = await import('fs/promises');
13281
13317
  const restored = [];
13282
13318
  const failed = [];
13283
13319
  for (const fileCheckpoint of checkpoint.files) {
@@ -13285,7 +13321,7 @@ async function restoreFiles(checkpoint, excludeFiles) {
13285
13321
  continue;
13286
13322
  }
13287
13323
  try {
13288
- await fs38.writeFile(fileCheckpoint.filePath, fileCheckpoint.originalContent, "utf-8");
13324
+ await fs39.writeFile(fileCheckpoint.filePath, fileCheckpoint.originalContent, "utf-8");
13289
13325
  restored.push(fileCheckpoint.filePath);
13290
13326
  } catch (error) {
13291
13327
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -13367,8 +13403,8 @@ function displayRewindResult(result) {
13367
13403
  const fileName = filePath.split("/").pop() ?? filePath;
13368
13404
  console.log(`${chalk12.green(String.fromCodePoint(10003))} Restored: ${fileName}`);
13369
13405
  }
13370
- for (const { path: path35, error } of result.filesFailed) {
13371
- const fileName = path35.split("/").pop() ?? path35;
13406
+ for (const { path: path37, error } of result.filesFailed) {
13407
+ const fileName = path37.split("/").pop() ?? path37;
13372
13408
  console.log(`${chalk12.red(String.fromCodePoint(10007))} Failed: ${fileName} (${error})`);
13373
13409
  }
13374
13410
  if (result.conversationRestored) {
@@ -14773,8 +14809,8 @@ function formatToolSummary(toolName, input) {
14773
14809
  return String(input.path || ".");
14774
14810
  case "search_files": {
14775
14811
  const pattern = String(input.pattern || "");
14776
- const path35 = input.path ? ` in ${input.path}` : "";
14777
- return `"${pattern}"${path35}`;
14812
+ const path37 = input.path ? ` in ${input.path}` : "";
14813
+ return `"${pattern}"${path37}`;
14778
14814
  }
14779
14815
  case "bash_exec": {
14780
14816
  const cmd = String(input.command || "");
@@ -15985,6 +16021,56 @@ var imageCommand = {
15985
16021
  return false;
15986
16022
  }
15987
16023
  };
16024
+ var tutorialCommand = {
16025
+ name: "tutorial",
16026
+ aliases: ["tut", "learn"],
16027
+ description: "Quick guide to using Coco",
16028
+ usage: "/tutorial",
16029
+ async execute() {
16030
+ console.log(chalk12.cyan.bold("\n\u2550\u2550\u2550 Coco Quick Tutorial \u2550\u2550\u2550\n"));
16031
+ const steps = [
16032
+ {
16033
+ step: "1",
16034
+ title: "Ask Coco anything",
16035
+ desc: 'Just type what you need: "Create a REST API with authentication"'
16036
+ },
16037
+ {
16038
+ step: "2",
16039
+ title: "Coco works autonomously",
16040
+ desc: "Reads your project, writes code, runs tests, and iterates until quality passes"
16041
+ },
16042
+ {
16043
+ step: "3",
16044
+ title: "Enable quality mode",
16045
+ desc: "Type /coco to enable auto-iteration: test \u2192 analyze \u2192 fix \u2192 repeat until score \u2265 85"
16046
+ },
16047
+ {
16048
+ step: "4",
16049
+ title: "Review changes",
16050
+ desc: "Use /diff to see what changed, /status for project state"
16051
+ },
16052
+ {
16053
+ step: "5",
16054
+ title: "Save your work",
16055
+ desc: "Use /commit to commit changes with a descriptive message"
16056
+ }
16057
+ ];
16058
+ for (const { step, title, desc } of steps) {
16059
+ console.log(` ${chalk12.magenta.bold(step)}. ${chalk12.bold(title)}`);
16060
+ console.log(` ${chalk12.dim(desc)}`);
16061
+ console.log();
16062
+ }
16063
+ console.log(chalk12.bold("Useful commands:"));
16064
+ console.log(
16065
+ ` ${chalk12.yellow("/coco")} ${chalk12.dim("Toggle quality mode (auto-iteration)")}`
16066
+ );
16067
+ console.log(` ${chalk12.yellow("/init")} ${chalk12.dim("Initialize a new project")}`);
16068
+ console.log(` ${chalk12.yellow("/help")} ${chalk12.dim("See all available commands")}`);
16069
+ console.log(` ${chalk12.yellow("/help tools")} ${chalk12.dim("See available agent tools")}`);
16070
+ console.log();
16071
+ return false;
16072
+ }
16073
+ };
15988
16074
 
15989
16075
  // src/cli/repl/commands/index.ts
15990
16076
  var commands = [
@@ -16014,7 +16100,8 @@ var commands = [
16014
16100
  allowPathCommand,
16015
16101
  permissionsCommand,
16016
16102
  cocoCommand,
16017
- imageCommand
16103
+ imageCommand,
16104
+ tutorialCommand
16018
16105
  ];
16019
16106
  function isSlashCommand(input) {
16020
16107
  return input.startsWith("/");
@@ -16105,17 +16192,19 @@ function createInputHandler(_session) {
16105
16192
  let pasteBuffer = "";
16106
16193
  let isReadingClipboard = false;
16107
16194
  const getPrompt = () => {
16195
+ const imageIndicator = hasPendingImage() ? chalk12.cyan(" \u{1F4CE} 1 image") : "";
16196
+ const imageIndicatorLen = hasPendingImage() ? 10 : 0;
16108
16197
  if (isCocoMode()) {
16109
16198
  return {
16110
- str: "\u{1F965} " + chalk12.magenta("[coco]") + " \u203A ",
16111
- // 🥥=2 + space=1 + [coco]=6 + space=1 + ›=1 + space=1 = 12
16112
- visualLen: 12
16199
+ str: "\u{1F965} " + chalk12.magenta("[coco]") + " \u203A " + imageIndicator,
16200
+ // 🥥=2 + space=1 + [coco]=6 + space=1 + ›=1 + space=1 = 12 + image indicator
16201
+ visualLen: 12 + imageIndicatorLen
16113
16202
  };
16114
16203
  }
16115
16204
  return {
16116
- str: chalk12.green("\u{1F965} \u203A "),
16117
- // 🥥=2 + space=1 + ›=1 + space=1 = 5
16118
- visualLen: 5
16205
+ str: chalk12.green("\u{1F965} \u203A ") + imageIndicator,
16206
+ // 🥥=2 + space=1 + ›=1 + space=1 = 5 + image indicator
16207
+ visualLen: 5 + imageIndicatorLen
16119
16208
  };
16120
16209
  };
16121
16210
  const MAX_ROWS = 8;
@@ -16128,9 +16217,17 @@ function createInputHandler(_session) {
16128
16217
  return Math.max(1, Math.min(3, cols));
16129
16218
  }
16130
16219
  function render() {
16131
- process.stdout.write("\r" + ansiEscapes3.eraseDown);
16220
+ const termCols = process.stdout.columns || 80;
16132
16221
  const prompt = getPrompt();
16133
- let output = prompt.str + currentLine;
16222
+ const totalVisualLen = prompt.visualLen + currentLine.length;
16223
+ const wrappedLines = Math.max(0, Math.ceil(totalVisualLen / termCols) - 1);
16224
+ if (wrappedLines > 0) {
16225
+ process.stdout.write(ansiEscapes3.cursorUp(wrappedLines));
16226
+ }
16227
+ process.stdout.write("\r" + ansiEscapes3.eraseDown);
16228
+ const separator = chalk12.dim("\u2500".repeat(termCols));
16229
+ let output = separator + "\n";
16230
+ output += prompt.str + currentLine;
16134
16231
  completions = findCompletions(currentLine);
16135
16232
  selectedCompletion = Math.min(selectedCompletion, Math.max(0, completions.length - 1));
16136
16233
  if (cursorPos === currentLine.length && completions.length > 0 && completions[selectedCompletion]) {
@@ -16183,15 +16280,25 @@ function createInputHandler(_session) {
16183
16280
  for (let i = 0; i < BOTTOM_MARGIN; i++) {
16184
16281
  output += "\n";
16185
16282
  }
16186
- output += ansiEscapes3.cursorUp(lastMenuLines + BOTTOM_MARGIN);
16283
+ output += ansiEscapes3.cursorUp(lastMenuLines + BOTTOM_MARGIN + 1);
16187
16284
  } else {
16188
16285
  lastMenuLines = 0;
16189
16286
  for (let i = 0; i < BOTTOM_MARGIN; i++) {
16190
16287
  output += "\n";
16191
16288
  }
16192
- output += ansiEscapes3.cursorUp(BOTTOM_MARGIN);
16289
+ output += ansiEscapes3.cursorUp(BOTTOM_MARGIN + 1);
16290
+ }
16291
+ output += ansiEscapes3.cursorDown(1);
16292
+ const cursorAbsolutePos = prompt.visualLen + cursorPos;
16293
+ const finalLine = Math.floor(cursorAbsolutePos / termCols);
16294
+ const finalCol = cursorAbsolutePos % termCols;
16295
+ output += "\r";
16296
+ if (finalLine > 0) {
16297
+ output += ansiEscapes3.cursorDown(finalLine);
16298
+ }
16299
+ if (finalCol > 0) {
16300
+ output += ansiEscapes3.cursorForward(finalCol);
16193
16301
  }
16194
- output += `\r${ansiEscapes3.cursorForward(prompt.visualLen + cursorPos)}`;
16195
16302
  process.stdout.write(output);
16196
16303
  }
16197
16304
  function clearMenu() {
@@ -16339,15 +16446,14 @@ function createInputHandler(_session) {
16339
16446
  process.stdout.write(
16340
16447
  chalk12.green(" \u2713 Image captured") + chalk12.dim(` (${sizeKB} KB)`) + chalk12.dim(` \u2014 "${truncatedPrompt}"`)
16341
16448
  );
16342
- cleanup();
16343
- console.log();
16344
- const result = currentLine.trim();
16345
- if (result) {
16346
- sessionHistory.push(result);
16347
- }
16348
- resolve2(result || "");
16349
- }).catch(() => {
16449
+ setTimeout(() => render(), 1200);
16450
+ }).catch((err) => {
16350
16451
  isReadingClipboard = false;
16452
+ const msg = err instanceof Error ? err.message : String(err);
16453
+ if (msg.includes("color space")) {
16454
+ render();
16455
+ return;
16456
+ }
16351
16457
  render();
16352
16458
  });
16353
16459
  return;
@@ -16357,7 +16463,9 @@ function createInputHandler(_session) {
16357
16463
  currentLine = completions[selectedCompletion].cmd;
16358
16464
  }
16359
16465
  cleanup();
16466
+ const termWidth = process.stdout.columns || 80;
16360
16467
  console.log();
16468
+ console.log(chalk12.dim("\u2500".repeat(termWidth)));
16361
16469
  const result = currentLine.trim();
16362
16470
  if (result) {
16363
16471
  sessionHistory.push(result);
@@ -18190,8 +18298,8 @@ function isPathAllowed(filePath, operation) {
18190
18298
  if (absolute.startsWith(normalizedHome) && !absolute.startsWith(normalizedCwd)) {
18191
18299
  if (isWithinAllowedPath(absolute, operation)) ; else if (operation === "read") {
18192
18300
  const allowedHomeReads = [".gitconfig", ".zshrc", ".bashrc"];
18193
- const basename2 = path20__default.basename(absolute);
18194
- if (!allowedHomeReads.includes(basename2)) {
18301
+ const basename3 = path20__default.basename(absolute);
18302
+ if (!allowedHomeReads.includes(basename3)) {
18195
18303
  const targetDir = path20__default.dirname(absolute);
18196
18304
  return {
18197
18305
  allowed: false,
@@ -18208,12 +18316,12 @@ function isPathAllowed(filePath, operation) {
18208
18316
  }
18209
18317
  }
18210
18318
  if (operation === "write" || operation === "delete") {
18211
- const basename2 = path20__default.basename(absolute);
18319
+ const basename3 = path20__default.basename(absolute);
18212
18320
  for (const pattern of SENSITIVE_PATTERNS) {
18213
- if (pattern.test(basename2)) {
18321
+ if (pattern.test(basename3)) {
18214
18322
  return {
18215
18323
  allowed: false,
18216
- reason: `Operation on sensitive file '${basename2}' requires explicit confirmation`
18324
+ reason: `Operation on sensitive file '${basename3}' requires explicit confirmation`
18217
18325
  };
18218
18326
  }
18219
18327
  }
@@ -19471,59 +19579,322 @@ var simpleAutoCommitTool = defineTool({
19471
19579
  }
19472
19580
  });
19473
19581
  var gitSimpleTools = [checkProtectedBranchTool, simpleAutoCommitTool];
19582
+
19583
+ // src/agents/provider-bridge.ts
19584
+ var agentProvider = null;
19585
+ var agentToolRegistry = null;
19586
+ function setAgentProvider(provider) {
19587
+ agentProvider = provider;
19588
+ }
19589
+ function getAgentProvider() {
19590
+ return agentProvider;
19591
+ }
19592
+ function setAgentToolRegistry(registry) {
19593
+ agentToolRegistry = registry;
19594
+ }
19595
+ function getAgentToolRegistry() {
19596
+ return agentToolRegistry;
19597
+ }
19598
+
19599
+ // src/agents/executor.ts
19600
+ var AgentExecutor = class {
19601
+ constructor(provider, toolRegistry) {
19602
+ this.provider = provider;
19603
+ this.toolRegistry = toolRegistry;
19604
+ }
19605
+ /**
19606
+ * Execute an agent on a task with multi-turn tool use
19607
+ */
19608
+ async execute(agent, task) {
19609
+ const startTime = Date.now();
19610
+ const toolsUsed = /* @__PURE__ */ new Set();
19611
+ const messages = [
19612
+ {
19613
+ role: "user",
19614
+ content: this.buildTaskPrompt(task)
19615
+ }
19616
+ ];
19617
+ const agentToolDefs = this.getToolDefinitionsForAgent(agent.allowedTools);
19618
+ let turn = 0;
19619
+ let totalTokens = 0;
19620
+ while (turn < agent.maxTurns) {
19621
+ turn++;
19622
+ try {
19623
+ const response = await this.provider.chatWithTools(messages, {
19624
+ tools: agentToolDefs,
19625
+ system: agent.systemPrompt
19626
+ });
19627
+ const usage = response.usage;
19628
+ totalTokens += (usage?.inputTokens || 0) + (usage?.outputTokens || 0);
19629
+ if (response.stopReason !== "tool_use" || response.toolCalls.length === 0) {
19630
+ return {
19631
+ output: response.content,
19632
+ success: true,
19633
+ turns: turn,
19634
+ toolsUsed: Array.from(toolsUsed),
19635
+ tokensUsed: totalTokens,
19636
+ duration: Date.now() - startTime
19637
+ };
19638
+ }
19639
+ const assistantContent = [];
19640
+ if (response.content) {
19641
+ assistantContent.push({
19642
+ type: "text",
19643
+ text: response.content
19644
+ });
19645
+ }
19646
+ for (const toolCall of response.toolCalls) {
19647
+ assistantContent.push({
19648
+ type: "tool_use",
19649
+ id: toolCall.id,
19650
+ name: toolCall.name,
19651
+ input: toolCall.input
19652
+ });
19653
+ }
19654
+ messages.push({
19655
+ role: "assistant",
19656
+ content: assistantContent
19657
+ });
19658
+ const toolResults = [];
19659
+ for (const toolCall of response.toolCalls) {
19660
+ toolsUsed.add(toolCall.name);
19661
+ try {
19662
+ const result = await this.toolRegistry.execute(toolCall.name, toolCall.input);
19663
+ toolResults.push({
19664
+ type: "tool_result",
19665
+ tool_use_id: toolCall.id,
19666
+ content: result.success ? JSON.stringify(result.data) : `Error: ${result.error}`,
19667
+ is_error: !result.success
19668
+ });
19669
+ } catch (error) {
19670
+ toolResults.push({
19671
+ type: "tool_result",
19672
+ tool_use_id: toolCall.id,
19673
+ content: `Tool execution error: ${error instanceof Error ? error.message : String(error)}`,
19674
+ is_error: true
19675
+ });
19676
+ }
19677
+ }
19678
+ messages.push({
19679
+ role: "user",
19680
+ content: toolResults
19681
+ });
19682
+ } catch (error) {
19683
+ return {
19684
+ output: `Agent error on turn ${turn}: ${error instanceof Error ? error.message : String(error)}`,
19685
+ success: false,
19686
+ turns: turn,
19687
+ toolsUsed: Array.from(toolsUsed),
19688
+ tokensUsed: totalTokens,
19689
+ duration: Date.now() - startTime
19690
+ };
19691
+ }
19692
+ }
19693
+ return {
19694
+ output: "Agent reached maximum turns without completing task",
19695
+ success: false,
19696
+ turns: turn,
19697
+ toolsUsed: Array.from(toolsUsed),
19698
+ tokensUsed: totalTokens,
19699
+ duration: Date.now() - startTime
19700
+ };
19701
+ }
19702
+ /**
19703
+ * Build task prompt with context
19704
+ */
19705
+ buildTaskPrompt(task) {
19706
+ let prompt = `Task: ${task.description}
19707
+ `;
19708
+ if (task.context && Object.keys(task.context).length > 0) {
19709
+ prompt += `
19710
+ Context:
19711
+ ${JSON.stringify(task.context, null, 2)}
19712
+ `;
19713
+ }
19714
+ prompt += `
19715
+ Complete this task autonomously using the available tools. When done, provide a summary of what you accomplished.`;
19716
+ return prompt;
19717
+ }
19718
+ /**
19719
+ * Get tool definitions filtered for this agent's allowed tools
19720
+ */
19721
+ getToolDefinitionsForAgent(allowedToolNames) {
19722
+ const allDefs = this.toolRegistry.getToolDefinitionsForLLM();
19723
+ if (allowedToolNames.length === 0) return allDefs;
19724
+ return allDefs.filter((def) => allowedToolNames.includes(def.name));
19725
+ }
19726
+ };
19727
+ var AGENT_ROLES = {
19728
+ researcher: {
19729
+ role: "researcher",
19730
+ systemPrompt: `You are a code researcher agent. Your role is to:
19731
+ - Explore and understand existing codebases
19732
+ - Find relevant code patterns and examples
19733
+ - Identify dependencies and relationships
19734
+ - Document your findings clearly
19735
+
19736
+ Use tools to search, read files, and analyze code structure.`,
19737
+ allowedTools: ["read_file", "grep", "find_in_file", "glob", "codebase_map"]
19738
+ },
19739
+ coder: {
19740
+ role: "coder",
19741
+ systemPrompt: `You are a code generation agent. Your role is to:
19742
+ - Write high-quality, production-ready code
19743
+ - Follow best practices and coding standards
19744
+ - Ensure code is syntactically valid
19745
+ - Write clean, maintainable code
19746
+
19747
+ Use tools to read existing code, write new files, and validate syntax.`,
19748
+ allowedTools: ["read_file", "write_file", "edit_file", "bash_exec", "validateCode"]
19749
+ },
19750
+ tester: {
19751
+ role: "tester",
19752
+ systemPrompt: `You are a test generation agent. Your role is to:
19753
+ - Write comprehensive test suites
19754
+ - Achieve high code coverage
19755
+ - Test edge cases and error conditions
19756
+ - Ensure tests are reliable and maintainable
19757
+
19758
+ Use tools to read code, write tests, and run them.`,
19759
+ allowedTools: ["read_file", "write_file", "run_tests", "get_coverage", "run_test_file"]
19760
+ },
19761
+ reviewer: {
19762
+ role: "reviewer",
19763
+ systemPrompt: `You are a code review agent. Your role is to:
19764
+ - Identify code quality issues
19765
+ - Check for security vulnerabilities
19766
+ - Ensure best practices are followed
19767
+ - Provide actionable feedback
19768
+
19769
+ Use tools to read and analyze code quality.`,
19770
+ allowedTools: ["read_file", "calculate_quality", "analyze_complexity", "grep"]
19771
+ },
19772
+ optimizer: {
19773
+ role: "optimizer",
19774
+ systemPrompt: `You are a code optimization agent. Your role is to:
19775
+ - Reduce code complexity
19776
+ - Eliminate duplication
19777
+ - Improve performance
19778
+ - Refactor for maintainability
19779
+
19780
+ Use tools to analyze and improve code.`,
19781
+ allowedTools: ["read_file", "write_file", "edit_file", "analyze_complexity", "grep"]
19782
+ },
19783
+ planner: {
19784
+ role: "planner",
19785
+ systemPrompt: `You are a task planning agent. Your role is to:
19786
+ - Break down complex tasks into subtasks
19787
+ - Identify dependencies between tasks
19788
+ - Estimate complexity and effort
19789
+ - Create actionable plans
19790
+
19791
+ Use tools to analyze requirements and explore the codebase.`,
19792
+ allowedTools: ["read_file", "grep", "glob", "codebase_map"]
19793
+ }
19794
+ };
19795
+
19796
+ // src/tools/simple-agent.ts
19474
19797
  var SpawnSimpleAgentSchema = z.object({
19475
19798
  task: z.string().describe("Task description for the sub-agent"),
19476
- context: z.string().optional().describe("Additional context or instructions for the agent")
19799
+ context: z.string().optional().describe("Additional context or instructions for the agent"),
19800
+ role: z.enum(["researcher", "coder", "tester", "reviewer", "optimizer", "planner"]).default("coder").describe("Agent role to use"),
19801
+ maxTurns: z.number().default(10).describe("Maximum tool-use turns for the agent")
19477
19802
  });
19478
19803
  var spawnSimpleAgentTool = defineTool({
19479
19804
  name: "spawnSimpleAgent",
19480
- description: `Spawn a sub-agent to handle a specific task.
19805
+ description: `Spawn a sub-agent to handle a specific task with real LLM tool-use execution.
19481
19806
 
19482
19807
  Use this when you need to:
19483
19808
  - Delegate a focused task to another agent
19484
19809
  - Get a second opinion or alternative approach
19485
19810
  - Handle multiple independent subtasks
19486
19811
 
19487
- The sub-agent will work on the task and return results.
19812
+ The sub-agent will work on the task autonomously using available tools.
19488
19813
 
19489
19814
  Example: "Write unit tests for the authentication module"`,
19490
19815
  category: "build",
19491
19816
  parameters: SpawnSimpleAgentSchema,
19492
19817
  async execute(input) {
19493
19818
  const typedInput = input;
19819
+ const provider = getAgentProvider();
19820
+ const toolRegistry = getAgentToolRegistry();
19821
+ if (!provider || !toolRegistry) {
19822
+ const agentId2 = `agent-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
19823
+ return {
19824
+ stdout: JSON.stringify({
19825
+ agentId: agentId2,
19826
+ status: "unavailable",
19827
+ task: typedInput.task,
19828
+ message: "Agent provider not initialized. Call setAgentProvider() during orchestrator startup.",
19829
+ success: false
19830
+ }),
19831
+ stderr: "",
19832
+ exitCode: 1,
19833
+ duration: 0
19834
+ };
19835
+ }
19494
19836
  const agentId = `agent-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
19837
+ const executor = new AgentExecutor(provider, toolRegistry);
19838
+ const roleDef = AGENT_ROLES[typedInput.role];
19839
+ if (!roleDef) {
19840
+ return {
19841
+ stdout: JSON.stringify({
19842
+ agentId,
19843
+ status: "error",
19844
+ message: `Unknown agent role: ${typedInput.role}`,
19845
+ success: false
19846
+ }),
19847
+ stderr: "",
19848
+ exitCode: 1,
19849
+ duration: 0
19850
+ };
19851
+ }
19852
+ const agentDef = { ...roleDef, maxTurns: typedInput.maxTurns };
19853
+ const result = await executor.execute(agentDef, {
19854
+ id: agentId,
19855
+ description: typedInput.task,
19856
+ context: typedInput.context ? { userContext: typedInput.context } : void 0
19857
+ });
19495
19858
  return {
19496
19859
  stdout: JSON.stringify({
19497
19860
  agentId,
19498
- status: "simulated",
19861
+ status: result.success ? "completed" : "failed",
19499
19862
  task: typedInput.task,
19500
- context: typedInput.context,
19501
- message: "Sub-agent system is available but requires provider integration to execute.",
19502
- note: "This tool demonstrates the multi-agent capability. Full implementation requires LLM provider integration in the agent loop."
19863
+ output: result.output,
19864
+ success: result.success,
19865
+ turns: result.turns,
19866
+ toolsUsed: result.toolsUsed,
19867
+ tokensUsed: result.tokensUsed,
19868
+ duration: result.duration
19503
19869
  }),
19504
19870
  stderr: "",
19505
- exitCode: 0,
19506
- duration: 0
19871
+ exitCode: result.success ? 0 : 1,
19872
+ duration: result.duration
19507
19873
  };
19508
19874
  }
19509
19875
  });
19510
19876
  var checkAgentCapabilityTool = defineTool({
19511
19877
  name: "checkAgentCapability",
19512
- description: "Check if multi-agent capability is available",
19878
+ description: "Check if multi-agent capability is available and configured",
19513
19879
  category: "build",
19514
19880
  parameters: z.object({}),
19515
19881
  async execute() {
19882
+ const provider = getAgentProvider();
19883
+ const toolRegistry = getAgentToolRegistry();
19884
+ const isReady = provider !== null && toolRegistry !== null;
19516
19885
  return {
19517
19886
  stdout: JSON.stringify({
19518
19887
  multiAgentSupported: true,
19519
- parallelExecution: "not yet implemented",
19520
- isolatedContexts: "not yet implemented",
19888
+ providerConfigured: provider !== null,
19889
+ toolRegistryConfigured: toolRegistry !== null,
19890
+ ready: isReady,
19891
+ availableRoles: Object.keys(AGENT_ROLES),
19521
19892
  features: {
19522
- taskDelegation: "available",
19523
- parallelSpawn: "requires integration",
19524
- contextForking: "requires integration"
19893
+ taskDelegation: isReady ? "ready" : "requires provider initialization",
19894
+ parallelSpawn: isReady ? "ready" : "requires provider initialization",
19895
+ multiTurnToolUse: isReady ? "ready" : "requires provider initialization"
19525
19896
  },
19526
- status: "Multi-agent foundation exists. Full features require provider integration."
19897
+ status: isReady ? "Multi-agent system is ready with real LLM tool-use execution." : "Provider not initialized. Call setAgentProvider() during startup."
19527
19898
  }),
19528
19899
  stderr: "",
19529
19900
  exitCode: 0,
@@ -19857,10 +20228,10 @@ var CoverageAnalyzer = class {
19857
20228
  join(this.projectPath, ".coverage", "coverage-summary.json"),
19858
20229
  join(this.projectPath, "coverage", "lcov-report", "coverage-summary.json")
19859
20230
  ];
19860
- for (const path35 of possiblePaths) {
20231
+ for (const path37 of possiblePaths) {
19861
20232
  try {
19862
- await access(path35, constants.R_OK);
19863
- const content = await readFile(path35, "utf-8");
20233
+ await access(path37, constants.R_OK);
20234
+ const content = await readFile(path37, "utf-8");
19864
20235
  const report = JSON.parse(content);
19865
20236
  return parseCoverageSummary(report);
19866
20237
  } catch {
@@ -20514,147 +20885,1650 @@ var DuplicationAnalyzer = class {
20514
20885
  });
20515
20886
  }
20516
20887
  };
20517
-
20518
- // src/quality/types.ts
20519
- var DEFAULT_QUALITY_WEIGHTS = {
20520
- correctness: 0.15,
20521
- completeness: 0.1,
20522
- robustness: 0.1,
20523
- readability: 0.1,
20524
- maintainability: 0.1,
20525
- complexity: 0.08,
20526
- duplication: 0.07,
20527
- testCoverage: 0.1,
20528
- testQuality: 0.05,
20529
- security: 0.08,
20530
- documentation: 0.04,
20531
- style: 0.03
20532
- };
20533
- var DEFAULT_QUALITY_THRESHOLDS = {
20534
- minimum: {
20535
- overall: 85,
20536
- testCoverage: 80,
20537
- security: 100
20538
- // No vulnerabilities allowed
20539
- },
20540
- target: {
20541
- overall: 95,
20542
- testCoverage: 90
20543
- }};
20544
- var QualityEvaluator = class {
20545
- constructor(projectPath, useSnyk = false) {
20888
+ var execAsync2 = promisify(exec);
20889
+ var BuildVerifier = class {
20890
+ projectPath;
20891
+ constructor(projectPath) {
20546
20892
  this.projectPath = projectPath;
20547
- this.coverageAnalyzer = new CoverageAnalyzer(projectPath);
20548
- this.securityScanner = new CompositeSecurityScanner(projectPath, useSnyk);
20549
- this.complexityAnalyzer = new ComplexityAnalyzer(projectPath);
20550
- this.duplicationAnalyzer = new DuplicationAnalyzer(projectPath);
20551
20893
  }
20552
- coverageAnalyzer;
20553
- securityScanner;
20554
- complexityAnalyzer;
20555
- duplicationAnalyzer;
20556
20894
  /**
20557
- * Evaluate quality across all dimensions
20558
- * Returns QualityScores with 0% hardcoded values (5/12 dimensions are real)
20895
+ * Verify that the project builds successfully
20559
20896
  */
20560
- async evaluate(files) {
20561
- const startTime = performance.now();
20562
- const targetFiles = files ?? await this.findSourceFiles();
20563
- const fileContents = await Promise.all(
20564
- targetFiles.map(async (file) => ({
20565
- path: file,
20566
- content: await readFile(file, "utf-8")
20567
- }))
20568
- );
20569
- const [coverageResult, securityResult, complexityResult, duplicationResult] = await Promise.all(
20570
- [
20571
- this.coverageAnalyzer.analyze().catch(() => null),
20572
- // Coverage may fail
20573
- this.securityScanner.scan(fileContents),
20574
- this.complexityAnalyzer.analyze(targetFiles),
20575
- this.duplicationAnalyzer.analyze(targetFiles)
20576
- ]
20577
- );
20578
- const dimensions = {
20579
- // REAL values (5/12):
20580
- testCoverage: coverageResult?.lines.percentage ?? 0,
20581
- security: securityResult.score,
20582
- complexity: complexityResult.score,
20583
- duplication: Math.max(0, 100 - duplicationResult.percentage),
20584
- style: 100,
20585
- // TODO: integrate linter
20586
- // Derived from real metrics (2/12):
20587
- readability: this.calculateReadability(complexityResult.averageComplexity),
20588
- maintainability: complexityResult.maintainabilityIndex,
20589
- // Still TODO - need test runner integration (5/12):
20590
- correctness: 85,
20591
- // TODO: run tests and check pass rate
20592
- completeness: 80,
20593
- // TODO: requirements tracking
20594
- robustness: 75,
20595
- // TODO: edge case analysis from tests
20596
- testQuality: 70,
20597
- // TODO: test quality analyzer
20598
- documentation: 60
20599
- // TODO: doc coverage analyzer
20600
- };
20601
- const overall = Object.entries(dimensions).reduce((sum, [key, value]) => {
20602
- const weight = DEFAULT_QUALITY_WEIGHTS[key] ?? 0;
20603
- return sum + value * weight;
20604
- }, 0);
20605
- const scores = {
20606
- overall: Math.round(overall),
20607
- dimensions,
20608
- evaluatedAt: /* @__PURE__ */ new Date(),
20609
- evaluationDurationMs: performance.now() - startTime
20610
- };
20611
- const issues = this.generateIssues(
20612
- securityResult.vulnerabilities,
20613
- complexityResult,
20614
- duplicationResult
20615
- );
20616
- const suggestions = this.generateSuggestions(dimensions);
20617
- const meetsMinimum = scores.overall >= DEFAULT_QUALITY_THRESHOLDS.minimum.overall && dimensions.testCoverage >= DEFAULT_QUALITY_THRESHOLDS.minimum.testCoverage && dimensions.security >= DEFAULT_QUALITY_THRESHOLDS.minimum.security;
20618
- const meetsTarget = scores.overall >= DEFAULT_QUALITY_THRESHOLDS.target.overall && dimensions.testCoverage >= DEFAULT_QUALITY_THRESHOLDS.target.testCoverage;
20619
- return {
20620
- scores,
20621
- meetsMinimum,
20622
- meetsTarget,
20623
- converged: false,
20624
- // Determined by iteration loop
20625
- issues,
20626
- suggestions
20627
- };
20897
+ async verifyBuild() {
20898
+ const startTime = Date.now();
20899
+ try {
20900
+ const buildCommand2 = await this.detectBuildCommand();
20901
+ if (!buildCommand2) {
20902
+ return {
20903
+ success: true,
20904
+ errors: [],
20905
+ warnings: [],
20906
+ duration: Date.now() - startTime,
20907
+ stdout: "No build command detected",
20908
+ stderr: ""
20909
+ };
20910
+ }
20911
+ const { stdout, stderr } = await execAsync2(buildCommand2, {
20912
+ cwd: this.projectPath,
20913
+ timeout: 12e4,
20914
+ // 2 minutes
20915
+ maxBuffer: 10 * 1024 * 1024
20916
+ // 10MB
20917
+ });
20918
+ const errors = this.parseErrors(stdout + stderr);
20919
+ const warnings = this.parseWarnings(stdout + stderr);
20920
+ return {
20921
+ success: errors.length === 0,
20922
+ errors,
20923
+ warnings,
20924
+ duration: Date.now() - startTime,
20925
+ stdout,
20926
+ stderr
20927
+ };
20928
+ } catch (error) {
20929
+ const execError = error;
20930
+ const errors = this.parseErrors(
20931
+ (execError.stdout ?? "") + (execError.stderr ?? "") || (execError.message ?? "")
20932
+ );
20933
+ const warnings = this.parseWarnings(
20934
+ (execError.stdout ?? "") + (execError.stderr ?? "") || ""
20935
+ );
20936
+ return {
20937
+ success: false,
20938
+ errors,
20939
+ warnings,
20940
+ duration: Date.now() - startTime,
20941
+ stdout: execError.stdout || "",
20942
+ stderr: execError.stderr || execError.message || ""
20943
+ };
20944
+ }
20945
+ }
20946
+ /**
20947
+ * Run TypeScript type checking only (faster than full build)
20948
+ */
20949
+ async verifyTypes() {
20950
+ const startTime = Date.now();
20951
+ try {
20952
+ const hasTsConfig = await this.fileExists(path20.join(this.projectPath, "tsconfig.json"));
20953
+ if (!hasTsConfig) {
20954
+ return {
20955
+ success: true,
20956
+ errors: [],
20957
+ warnings: [],
20958
+ duration: Date.now() - startTime,
20959
+ stdout: "No tsconfig.json found",
20960
+ stderr: ""
20961
+ };
20962
+ }
20963
+ const { stdout, stderr } = await execAsync2("npx tsc --noEmit", {
20964
+ cwd: this.projectPath,
20965
+ timeout: 6e4,
20966
+ // 1 minute
20967
+ maxBuffer: 10 * 1024 * 1024
20968
+ });
20969
+ const errors = this.parseTypeScriptErrors(stdout + stderr);
20970
+ const warnings = this.parseTypeScriptWarnings(stdout + stderr);
20971
+ return {
20972
+ success: errors.length === 0,
20973
+ errors,
20974
+ warnings,
20975
+ duration: Date.now() - startTime,
20976
+ stdout,
20977
+ stderr
20978
+ };
20979
+ } catch (error) {
20980
+ const execError = error;
20981
+ const errors = this.parseTypeScriptErrors(
20982
+ (execError.stdout ?? "") + (execError.stderr ?? "") || (execError.message ?? "")
20983
+ );
20984
+ const warnings = this.parseTypeScriptWarnings(
20985
+ (execError.stdout ?? "") + (execError.stderr ?? "") || ""
20986
+ );
20987
+ return {
20988
+ success: false,
20989
+ errors,
20990
+ warnings,
20991
+ duration: Date.now() - startTime,
20992
+ stdout: execError.stdout || "",
20993
+ stderr: execError.stderr || execError.message || ""
20994
+ };
20995
+ }
20628
20996
  }
20629
20997
  /**
20630
- * Calculate readability from complexity
20631
- * Low complexity = high readability
20998
+ * Detect build command from package.json
20632
20999
  */
20633
- calculateReadability(averageComplexity) {
20634
- if (averageComplexity <= 3) return 100;
20635
- return Math.max(0, 100 - (averageComplexity - 3) / 17 * 100);
21000
+ async detectBuildCommand() {
21001
+ try {
21002
+ const packageJsonPath = path20.join(this.projectPath, "package.json");
21003
+ const content = await fs22.readFile(packageJsonPath, "utf-8");
21004
+ const packageJson = JSON.parse(content);
21005
+ if (packageJson.scripts?.build) {
21006
+ return "npm run build";
21007
+ }
21008
+ if (packageJson.devDependencies?.typescript || packageJson.dependencies?.typescript) {
21009
+ return "npx tsc --noEmit";
21010
+ }
21011
+ return null;
21012
+ } catch {
21013
+ return null;
21014
+ }
20636
21015
  }
20637
21016
  /**
20638
- * Generate quality issues from analyzer results
21017
+ * Parse errors from build output
20639
21018
  */
20640
- generateIssues(securityVulns, complexityResult, duplicationResult) {
20641
- const issues = [];
20642
- for (const vuln of securityVulns) {
20643
- issues.push({
20644
- dimension: "security",
20645
- severity: vuln.severity === "critical" ? "critical" : vuln.severity === "high" ? "major" : "minor",
20646
- message: `${vuln.type}: ${vuln.description}`,
20647
- file: vuln.location.file,
20648
- line: vuln.location.line
21019
+ parseErrors(output) {
21020
+ const errors = [];
21021
+ const tsErrorRegex = /(.+?)\((\d+),(\d+)\): error (TS\d+): (.+)/g;
21022
+ let match;
21023
+ while ((match = tsErrorRegex.exec(output)) !== null) {
21024
+ errors.push({
21025
+ file: match[1] || "",
21026
+ line: parseInt(match[2] || "0", 10),
21027
+ column: parseInt(match[3] || "0", 10),
21028
+ code: match[4],
21029
+ message: match[5] || ""
20649
21030
  });
20650
21031
  }
20651
- for (const file of complexityResult.files) {
20652
- for (const fn of file.functions) {
20653
- if (fn.complexity > 10) {
20654
- issues.push({
20655
- dimension: "complexity",
20656
- severity: "major",
20657
- message: `Function '${fn.name}' has high complexity (${fn.complexity})`,
21032
+ const eslintErrorRegex = /(.+?):(\d+):(\d+): (.+)/g;
21033
+ while ((match = eslintErrorRegex.exec(output)) !== null) {
21034
+ if (!output.includes("error")) continue;
21035
+ errors.push({
21036
+ file: match[1] || "",
21037
+ line: parseInt(match[2] || "0", 10),
21038
+ column: parseInt(match[3] || "0", 10),
21039
+ message: match[4] || ""
21040
+ });
21041
+ }
21042
+ return errors;
21043
+ }
21044
+ /**
21045
+ * Parse warnings from build output
21046
+ */
21047
+ parseWarnings(output) {
21048
+ const warnings = [];
21049
+ const tsWarningRegex = /(.+?)\((\d+),(\d+)\): warning (TS\d+): (.+)/g;
21050
+ let match;
21051
+ while ((match = tsWarningRegex.exec(output)) !== null) {
21052
+ warnings.push({
21053
+ file: match[1] || "",
21054
+ line: parseInt(match[2] || "0", 10),
21055
+ column: parseInt(match[3] || "0", 10),
21056
+ code: match[4],
21057
+ message: match[5] || ""
21058
+ });
21059
+ }
21060
+ return warnings;
21061
+ }
21062
+ /**
21063
+ * Parse TypeScript-specific errors
21064
+ */
21065
+ parseTypeScriptErrors(output) {
21066
+ return this.parseErrors(output);
21067
+ }
21068
+ /**
21069
+ * Parse TypeScript-specific warnings
21070
+ */
21071
+ parseTypeScriptWarnings(output) {
21072
+ return this.parseWarnings(output);
21073
+ }
21074
+ /**
21075
+ * Check if file exists
21076
+ */
21077
+ async fileExists(filePath) {
21078
+ try {
21079
+ await fs22.access(filePath);
21080
+ return true;
21081
+ } catch {
21082
+ return false;
21083
+ }
21084
+ }
21085
+ };
21086
+
21087
+ // src/quality/analyzers/correctness.ts
21088
+ function parseVitestOutput(stdout) {
21089
+ const testsMatch = stdout.match(
21090
+ /Tests\s+(?:(\d+)\s+passed)?(?:\s*\|\s*(\d+)\s+failed)?(?:\s*\|\s*(\d+)\s+skipped)?/
21091
+ );
21092
+ if (testsMatch) {
21093
+ return {
21094
+ passed: parseInt(testsMatch[1] || "0", 10),
21095
+ failed: parseInt(testsMatch[2] || "0", 10),
21096
+ skipped: parseInt(testsMatch[3] || "0", 10)
21097
+ };
21098
+ }
21099
+ try {
21100
+ const json2 = JSON.parse(stdout);
21101
+ return {
21102
+ passed: json2.numPassedTests ?? 0,
21103
+ failed: json2.numFailedTests ?? 0,
21104
+ skipped: json2.numPendingTests ?? 0
21105
+ };
21106
+ } catch {
21107
+ }
21108
+ return { passed: 0, failed: 0, skipped: 0 };
21109
+ }
21110
+ function parseJestOutput(stdout) {
21111
+ try {
21112
+ const json2 = JSON.parse(stdout);
21113
+ return {
21114
+ passed: json2.numPassedTests ?? 0,
21115
+ failed: json2.numFailedTests ?? 0,
21116
+ skipped: json2.numPendingTests ?? 0
21117
+ };
21118
+ } catch {
21119
+ const match = stdout.match(
21120
+ /Tests:\s+(\d+)\s+passed(?:,\s*(\d+)\s+failed)?(?:,\s*(\d+)\s+skipped)?/
21121
+ );
21122
+ if (match) {
21123
+ return {
21124
+ passed: parseInt(match[1] || "0", 10),
21125
+ failed: parseInt(match[2] || "0", 10),
21126
+ skipped: parseInt(match[3] || "0", 10)
21127
+ };
21128
+ }
21129
+ }
21130
+ return { passed: 0, failed: 0, skipped: 0 };
21131
+ }
21132
+ function buildTestCommand(framework) {
21133
+ switch (framework) {
21134
+ case "vitest":
21135
+ return { command: "npx", args: ["vitest", "run", "--reporter=verbose"] };
21136
+ case "jest":
21137
+ return { command: "npx", args: ["jest", "--json"] };
21138
+ case "mocha":
21139
+ return { command: "npx", args: ["mocha", "--reporter=json"] };
21140
+ default:
21141
+ return null;
21142
+ }
21143
+ }
21144
+ var CorrectnessAnalyzer = class {
21145
+ constructor(projectPath) {
21146
+ this.projectPath = projectPath;
21147
+ this.buildVerifier = new BuildVerifier(projectPath);
21148
+ }
21149
+ buildVerifier;
21150
+ /**
21151
+ * Analyze correctness by running tests and verifying build
21152
+ */
21153
+ async analyze() {
21154
+ const [testResult, buildResult] = await Promise.all([
21155
+ this.runTests(),
21156
+ this.buildVerifier.verifyTypes().catch(() => ({ success: false, errors: [] }))
21157
+ ]);
21158
+ const total = testResult.passed + testResult.failed;
21159
+ const testPassRate = total > 0 ? testResult.passed / total * 100 : 0;
21160
+ const buildSuccess = buildResult.success;
21161
+ let score;
21162
+ if (total === 0 && !buildSuccess) {
21163
+ score = 0;
21164
+ } else if (total === 0) {
21165
+ score = 30;
21166
+ } else {
21167
+ score = testPassRate / 100 * 70 + (buildSuccess ? 30 : 0);
21168
+ }
21169
+ score = Math.round(Math.max(0, Math.min(100, score)));
21170
+ const details = this.buildDetails(testResult, buildSuccess, testPassRate, total);
21171
+ return {
21172
+ score,
21173
+ testPassRate,
21174
+ buildSuccess,
21175
+ testsPassed: testResult.passed,
21176
+ testsFailed: testResult.failed,
21177
+ testsSkipped: testResult.skipped,
21178
+ testsTotal: total,
21179
+ buildErrors: buildResult.errors?.length ?? 0,
21180
+ details
21181
+ };
21182
+ }
21183
+ /**
21184
+ * Run tests and parse results
21185
+ */
21186
+ async runTests() {
21187
+ const framework = await detectTestFramework2(this.projectPath);
21188
+ if (!framework) {
21189
+ return { passed: 0, failed: 0, skipped: 0 };
21190
+ }
21191
+ const cmd = buildTestCommand(framework);
21192
+ if (!cmd) {
21193
+ return { passed: 0, failed: 0, skipped: 0 };
21194
+ }
21195
+ try {
21196
+ const result = await execa(cmd.command, cmd.args, {
21197
+ cwd: this.projectPath,
21198
+ reject: false,
21199
+ timeout: 3e5
21200
+ // 5 minutes
21201
+ });
21202
+ const output = result.stdout + "\n" + result.stderr;
21203
+ switch (framework) {
21204
+ case "vitest":
21205
+ return parseVitestOutput(output);
21206
+ case "jest":
21207
+ return parseJestOutput(result.stdout);
21208
+ case "mocha": {
21209
+ try {
21210
+ const json2 = JSON.parse(result.stdout);
21211
+ return {
21212
+ passed: json2.stats?.passes ?? 0,
21213
+ failed: json2.stats?.failures ?? 0,
21214
+ skipped: json2.stats?.pending ?? 0
21215
+ };
21216
+ } catch {
21217
+ return { passed: 0, failed: 0, skipped: 0 };
21218
+ }
21219
+ }
21220
+ default:
21221
+ return { passed: 0, failed: 0, skipped: 0 };
21222
+ }
21223
+ } catch {
21224
+ return { passed: 0, failed: 0, skipped: 0 };
21225
+ }
21226
+ }
21227
+ buildDetails(testResult, buildSuccess, testPassRate, total) {
21228
+ const parts = [];
21229
+ if (total > 0) {
21230
+ parts.push(`Tests: ${testResult.passed}/${total} passed (${testPassRate.toFixed(1)}%)`);
21231
+ if (testResult.failed > 0) {
21232
+ parts.push(`${testResult.failed} failed`);
21233
+ }
21234
+ } else {
21235
+ parts.push("No tests found");
21236
+ }
21237
+ parts.push(`Build: ${buildSuccess ? "success" : "failed"}`);
21238
+ return parts.join(", ");
21239
+ }
21240
+ };
21241
+ function countExports(ast) {
21242
+ let exportCount = 0;
21243
+ for (const node of ast.body) {
21244
+ switch (node.type) {
21245
+ case "ExportNamedDeclaration":
21246
+ if (node.declaration) {
21247
+ exportCount++;
21248
+ if (node.declaration.type === "VariableDeclaration" && node.declaration.declarations.length > 1) {
21249
+ exportCount += node.declaration.declarations.length - 1;
21250
+ }
21251
+ } else if (node.specifiers.length > 0) {
21252
+ exportCount += node.specifiers.length;
21253
+ }
21254
+ break;
21255
+ case "ExportDefaultDeclaration":
21256
+ exportCount++;
21257
+ break;
21258
+ case "ExportAllDeclaration":
21259
+ exportCount++;
21260
+ break;
21261
+ }
21262
+ }
21263
+ return exportCount;
21264
+ }
21265
+ var CompletenessAnalyzer = class {
21266
+ constructor(projectPath) {
21267
+ this.projectPath = projectPath;
21268
+ }
21269
+ /**
21270
+ * Analyze project completeness
21271
+ */
21272
+ async analyze(files) {
21273
+ const sourceFiles = files ?? await this.findSourceFiles();
21274
+ const testFiles = await this.findTestFiles();
21275
+ const { totalExports, exportDensity } = await this.analyzeExports(sourceFiles);
21276
+ const testFileRatio = this.calculateTestFileRatio(sourceFiles, testFiles);
21277
+ const hasEntryPoint = await this.checkEntryPoint();
21278
+ const exportScore = Math.min(40, exportDensity * 0.4);
21279
+ const testScore = Math.min(40, testFileRatio * 0.4);
21280
+ const entryScore = hasEntryPoint ? 20 : 0;
21281
+ const score = Math.round(Math.max(0, Math.min(100, exportScore + testScore + entryScore)));
21282
+ const details = this.buildDetails(
21283
+ sourceFiles.length,
21284
+ testFiles.length,
21285
+ totalExports,
21286
+ exportDensity,
21287
+ testFileRatio,
21288
+ hasEntryPoint
21289
+ );
21290
+ return {
21291
+ score,
21292
+ exportDensity,
21293
+ testFileRatio,
21294
+ hasEntryPoint,
21295
+ sourceFileCount: sourceFiles.length,
21296
+ testFileCount: testFiles.length,
21297
+ totalExports,
21298
+ details
21299
+ };
21300
+ }
21301
+ /**
21302
+ * Analyze export density across files
21303
+ */
21304
+ async analyzeExports(files) {
21305
+ let totalExports = 0;
21306
+ let filesWithExports = 0;
21307
+ for (const file of files) {
21308
+ try {
21309
+ const content = await readFile(file, "utf-8");
21310
+ const ast = parse(content, {
21311
+ loc: true,
21312
+ range: true,
21313
+ jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
21314
+ });
21315
+ const exports$1 = countExports(ast);
21316
+ totalExports += exports$1;
21317
+ if (exports$1 > 0) filesWithExports++;
21318
+ } catch {
21319
+ }
21320
+ }
21321
+ const exportDensity = files.length > 0 ? filesWithExports / files.length * 100 : 0;
21322
+ return { totalExports, exportDensity };
21323
+ }
21324
+ /**
21325
+ * Calculate ratio of source files that have corresponding test files
21326
+ */
21327
+ calculateTestFileRatio(sourceFiles, testFiles) {
21328
+ if (sourceFiles.length === 0) return 0;
21329
+ const testBaseNames = new Set(
21330
+ testFiles.map((f) => {
21331
+ const name = basename(f);
21332
+ return name.replace(/\.(test|spec)\.(ts|tsx|js|jsx)$/, "");
21333
+ })
21334
+ );
21335
+ let coveredCount = 0;
21336
+ for (const sourceFile of sourceFiles) {
21337
+ const name = basename(sourceFile).replace(/\.(ts|tsx|js|jsx)$/, "");
21338
+ if (testBaseNames.has(name)) {
21339
+ coveredCount++;
21340
+ }
21341
+ }
21342
+ return coveredCount / sourceFiles.length * 100;
21343
+ }
21344
+ /**
21345
+ * Check if project has a clear entry point
21346
+ */
21347
+ async checkEntryPoint() {
21348
+ const entryPoints = [
21349
+ "src/index.ts",
21350
+ "src/index.js",
21351
+ "src/main.ts",
21352
+ "src/main.js",
21353
+ "index.ts",
21354
+ "index.js"
21355
+ ];
21356
+ for (const entry of entryPoints) {
21357
+ try {
21358
+ await access(join(this.projectPath, entry), constants.R_OK);
21359
+ return true;
21360
+ } catch {
21361
+ }
21362
+ }
21363
+ try {
21364
+ const pkgContent = await readFile(join(this.projectPath, "package.json"), "utf-8");
21365
+ const pkg = JSON.parse(pkgContent);
21366
+ if (pkg.main || pkg.exports) return true;
21367
+ } catch {
21368
+ }
21369
+ return false;
21370
+ }
21371
+ buildDetails(sourceCount, testCount, totalExports, exportDensity, testFileRatio, hasEntryPoint) {
21372
+ return [
21373
+ `${sourceCount} source files, ${testCount} test files`,
21374
+ `${totalExports} exports (${exportDensity.toFixed(1)}% files have exports)`,
21375
+ `${testFileRatio.toFixed(1)}% source files have tests`,
21376
+ `Entry point: ${hasEntryPoint ? "found" : "missing"}`
21377
+ ].join(", ");
21378
+ }
21379
+ async findSourceFiles() {
21380
+ return glob("**/*.{ts,js,tsx,jsx}", {
21381
+ cwd: this.projectPath,
21382
+ absolute: true,
21383
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
21384
+ });
21385
+ }
21386
+ async findTestFiles() {
21387
+ return glob("**/*.{test,spec}.{ts,tsx,js,jsx}", {
21388
+ cwd: this.projectPath,
21389
+ absolute: true,
21390
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"]
21391
+ });
21392
+ }
21393
+ };
21394
+ function analyzeRobustnessPatterns(ast) {
21395
+ let functions = 0;
21396
+ let functionsWithTryCatch = 0;
21397
+ let optionalChaining = 0;
21398
+ let nullishCoalescing = 0;
21399
+ let typeGuards = 0;
21400
+ let typeofChecks = 0;
21401
+ let nullChecks = 0;
21402
+ let insideFunction = false;
21403
+ let currentFunctionHasTryCatch = false;
21404
+ function traverse(node) {
21405
+ const isFunctionNode = node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "MethodDefinition";
21406
+ if (isFunctionNode) {
21407
+ if (insideFunction && currentFunctionHasTryCatch) {
21408
+ functionsWithTryCatch++;
21409
+ }
21410
+ functions++;
21411
+ const previousInsideFunction = insideFunction;
21412
+ const previousHasTryCatch = currentFunctionHasTryCatch;
21413
+ insideFunction = true;
21414
+ currentFunctionHasTryCatch = false;
21415
+ traverseChildren(node);
21416
+ if (currentFunctionHasTryCatch) {
21417
+ functionsWithTryCatch++;
21418
+ }
21419
+ if (isFunctionNode) {
21420
+ functions--;
21421
+ functions++;
21422
+ }
21423
+ insideFunction = previousInsideFunction;
21424
+ currentFunctionHasTryCatch = previousHasTryCatch;
21425
+ return;
21426
+ }
21427
+ switch (node.type) {
21428
+ case "TryStatement":
21429
+ if (insideFunction) {
21430
+ currentFunctionHasTryCatch = true;
21431
+ }
21432
+ break;
21433
+ case "ChainExpression":
21434
+ optionalChaining++;
21435
+ break;
21436
+ case "MemberExpression":
21437
+ if (node.optional) {
21438
+ optionalChaining++;
21439
+ }
21440
+ break;
21441
+ case "CallExpression":
21442
+ if (node.optional) {
21443
+ optionalChaining++;
21444
+ }
21445
+ break;
21446
+ case "LogicalExpression":
21447
+ if (node.operator === "??") {
21448
+ nullishCoalescing++;
21449
+ }
21450
+ break;
21451
+ case "TSTypeAliasDeclaration":
21452
+ typeGuards++;
21453
+ break;
21454
+ case "UnaryExpression":
21455
+ if (node.operator === "typeof") {
21456
+ typeofChecks++;
21457
+ }
21458
+ break;
21459
+ case "BinaryExpression":
21460
+ if (node.operator === "===" || node.operator === "!==" || node.operator === "==" || node.operator === "!=") {
21461
+ if (isNullOrUndefined(node.left) || isNullOrUndefined(node.right)) {
21462
+ nullChecks++;
21463
+ }
21464
+ }
21465
+ break;
21466
+ }
21467
+ traverseChildren(node);
21468
+ }
21469
+ function traverseChildren(node) {
21470
+ for (const key of Object.keys(node)) {
21471
+ if (key === "parent") continue;
21472
+ const child = node[key];
21473
+ if (child && typeof child === "object") {
21474
+ if (Array.isArray(child)) {
21475
+ for (const item of child) {
21476
+ if (item && typeof item === "object" && item.type) {
21477
+ traverse(item);
21478
+ }
21479
+ }
21480
+ } else if (child.type) {
21481
+ traverse(child);
21482
+ }
21483
+ }
21484
+ }
21485
+ }
21486
+ traverse(ast);
21487
+ return {
21488
+ functions,
21489
+ functionsWithTryCatch,
21490
+ optionalChaining,
21491
+ nullishCoalescing,
21492
+ typeGuards,
21493
+ typeofChecks,
21494
+ nullChecks
21495
+ };
21496
+ }
21497
+ function isNullOrUndefined(node) {
21498
+ if (node.type === "Literal" && node.value === null) return true;
21499
+ if (node.type === "Identifier" && node.name === "undefined") return true;
21500
+ return false;
21501
+ }
21502
+ var RobustnessAnalyzer = class {
21503
+ constructor(projectPath) {
21504
+ this.projectPath = projectPath;
21505
+ }
21506
+ /**
21507
+ * Analyze robustness of project files
21508
+ */
21509
+ async analyze(files) {
21510
+ const targetFiles = files ?? await this.findSourceFiles();
21511
+ let totalFunctions = 0;
21512
+ let totalFunctionsWithTryCatch = 0;
21513
+ let totalOptionalChaining = 0;
21514
+ let totalNullishCoalescing = 0;
21515
+ let totalTypeGuards = 0;
21516
+ let totalTypeofChecks = 0;
21517
+ let totalNullChecks = 0;
21518
+ for (const file of targetFiles) {
21519
+ try {
21520
+ const content = await readFile(file, "utf-8");
21521
+ const ast = parse(content, {
21522
+ loc: true,
21523
+ range: true,
21524
+ jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
21525
+ });
21526
+ const result = analyzeRobustnessPatterns(ast);
21527
+ totalFunctions += result.functions;
21528
+ totalFunctionsWithTryCatch += result.functionsWithTryCatch;
21529
+ totalOptionalChaining += result.optionalChaining;
21530
+ totalNullishCoalescing += result.nullishCoalescing;
21531
+ totalTypeGuards += result.typeGuards;
21532
+ totalTypeofChecks += result.typeofChecks;
21533
+ totalNullChecks += result.nullChecks;
21534
+ } catch {
21535
+ }
21536
+ }
21537
+ const tryCatchRatio = totalFunctions > 0 ? totalFunctionsWithTryCatch / totalFunctions * 100 : 0;
21538
+ const validationPerFunction = totalFunctions > 0 ? (totalTypeGuards + totalTypeofChecks + totalNullChecks) / totalFunctions : 0;
21539
+ const inputValidationScore = Math.min(100, validationPerFunction * 50);
21540
+ const defensivePerFunction = totalFunctions > 0 ? (totalOptionalChaining + totalNullishCoalescing) / totalFunctions : 0;
21541
+ const defensiveCodingScore = Math.min(100, defensivePerFunction * 40);
21542
+ const score = Math.round(
21543
+ Math.max(
21544
+ 0,
21545
+ Math.min(
21546
+ 100,
21547
+ tryCatchRatio * 0.4 + inputValidationScore * 0.3 + defensiveCodingScore * 0.3
21548
+ )
21549
+ )
21550
+ );
21551
+ const details = [
21552
+ `${totalFunctionsWithTryCatch}/${totalFunctions} functions with error handling`,
21553
+ `${totalOptionalChaining} optional chaining, ${totalNullishCoalescing} nullish coalescing`,
21554
+ `${totalTypeGuards + totalTypeofChecks + totalNullChecks} validation checks`
21555
+ ].join(", ");
21556
+ return {
21557
+ score,
21558
+ tryCatchRatio,
21559
+ inputValidationScore,
21560
+ defensiveCodingScore,
21561
+ functionsAnalyzed: totalFunctions,
21562
+ functionsWithErrorHandling: totalFunctionsWithTryCatch,
21563
+ optionalChainingCount: totalOptionalChaining,
21564
+ nullishCoalescingCount: totalNullishCoalescing,
21565
+ typeGuardCount: totalTypeGuards,
21566
+ details
21567
+ };
21568
+ }
21569
+ async findSourceFiles() {
21570
+ return glob("**/*.{ts,js,tsx,jsx}", {
21571
+ cwd: this.projectPath,
21572
+ absolute: true,
21573
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
21574
+ });
21575
+ }
21576
+ };
21577
+ var TRIVIAL_PATTERNS = [
21578
+ /\.toBeDefined\(\)/,
21579
+ /\.toBeUndefined\(\)/,
21580
+ /\.toBeTruthy\(\)/,
21581
+ /\.toBeFalsy\(\)/,
21582
+ /\.toBe\(true\)/,
21583
+ /\.toBe\(false\)/,
21584
+ /\.not\.toBeNull\(\)/,
21585
+ /\.toBeInstanceOf\(/,
21586
+ /\.toBeTypeOf\(/
21587
+ ];
21588
+ var EDGE_CASE_PATTERNS = [
21589
+ /error/i,
21590
+ /edge/i,
21591
+ /boundary/i,
21592
+ /invalid/i,
21593
+ /empty/i,
21594
+ /null/i,
21595
+ /undefined/i,
21596
+ /negative/i,
21597
+ /overflow/i,
21598
+ /timeout/i,
21599
+ /fail/i,
21600
+ /reject/i,
21601
+ /throw/i,
21602
+ /missing/i,
21603
+ /malformed/i,
21604
+ /corrupt/i
21605
+ ];
21606
+ var MATCHER_PATTERNS = [
21607
+ /\.toBe\(/,
21608
+ /\.toEqual\(/,
21609
+ /\.toStrictEqual\(/,
21610
+ /\.toContain\(/,
21611
+ /\.toMatch\(/,
21612
+ /\.toHaveLength\(/,
21613
+ /\.toHaveProperty\(/,
21614
+ /\.toThrow/,
21615
+ /\.toHaveBeenCalled/,
21616
+ /\.toHaveBeenCalledWith\(/,
21617
+ /\.toHaveBeenCalledTimes\(/,
21618
+ /\.toBeGreaterThan\(/,
21619
+ /\.toBeLessThan\(/,
21620
+ /\.toBeCloseTo\(/,
21621
+ /\.resolves\./,
21622
+ /\.rejects\./
21623
+ ];
21624
+ function analyzeTestFile(content) {
21625
+ const lines = content.split("\n");
21626
+ let totalAssertions = 0;
21627
+ let trivialAssertions = 0;
21628
+ let totalTests = 0;
21629
+ let edgeCaseTests = 0;
21630
+ const matchersUsed = /* @__PURE__ */ new Set();
21631
+ for (const line of lines) {
21632
+ const trimmed = line.trim();
21633
+ if (/^\s*(it|test)\s*\(/.test(trimmed) || /^\s*(it|test)\s*\./.test(trimmed)) {
21634
+ totalTests++;
21635
+ for (const pattern of EDGE_CASE_PATTERNS) {
21636
+ if (pattern.test(trimmed)) {
21637
+ edgeCaseTests++;
21638
+ break;
21639
+ }
21640
+ }
21641
+ }
21642
+ if (/expect\s*\(/.test(trimmed)) {
21643
+ totalAssertions++;
21644
+ for (const pattern of TRIVIAL_PATTERNS) {
21645
+ if (pattern.test(trimmed)) {
21646
+ trivialAssertions++;
21647
+ break;
21648
+ }
21649
+ }
21650
+ for (let i = 0; i < MATCHER_PATTERNS.length; i++) {
21651
+ if (MATCHER_PATTERNS[i].test(trimmed)) {
21652
+ matchersUsed.add(i);
21653
+ }
21654
+ }
21655
+ }
21656
+ }
21657
+ return { totalAssertions, trivialAssertions, totalTests, edgeCaseTests, matchersUsed };
21658
+ }
21659
+ var TestQualityAnalyzer = class {
21660
+ constructor(projectPath) {
21661
+ this.projectPath = projectPath;
21662
+ }
21663
+ /**
21664
+ * Analyze test quality across the project
21665
+ */
21666
+ async analyze(files) {
21667
+ const testFiles = files ?? await this.findTestFiles();
21668
+ let totalAssertions = 0;
21669
+ let trivialAssertions = 0;
21670
+ let totalTests = 0;
21671
+ let edgeCaseTests = 0;
21672
+ const allMatchersUsed = /* @__PURE__ */ new Set();
21673
+ for (const file of testFiles) {
21674
+ try {
21675
+ const content = await readFile(file, "utf-8");
21676
+ const result = analyzeTestFile(content);
21677
+ totalAssertions += result.totalAssertions;
21678
+ trivialAssertions += result.trivialAssertions;
21679
+ totalTests += result.totalTests;
21680
+ edgeCaseTests += result.edgeCaseTests;
21681
+ for (const m of result.matchersUsed) {
21682
+ allMatchersUsed.add(m);
21683
+ }
21684
+ } catch {
21685
+ }
21686
+ }
21687
+ const assertionDensity = totalTests > 0 ? totalAssertions / totalTests : 0;
21688
+ const trivialAssertionRatio = totalAssertions > 0 ? trivialAssertions / totalAssertions * 100 : 0;
21689
+ const edgeCaseRatio = totalTests > 0 ? edgeCaseTests / totalTests * 100 : 0;
21690
+ const assertionDiversity = allMatchersUsed.size / MATCHER_PATTERNS.length * 100;
21691
+ let score = 100;
21692
+ score -= Math.min(40, trivialAssertionRatio * 0.4);
21693
+ if (assertionDensity < 2) {
21694
+ score -= (2 - assertionDensity) * 10;
21695
+ }
21696
+ if (edgeCaseRatio > 10) {
21697
+ score += Math.min(10, (edgeCaseRatio - 10) * 0.5);
21698
+ }
21699
+ if (assertionDiversity > 30) {
21700
+ score += Math.min(10, (assertionDiversity - 30) * 0.15);
21701
+ }
21702
+ if (totalTests === 0) {
21703
+ score = 0;
21704
+ }
21705
+ score = Math.round(Math.max(0, Math.min(100, score)));
21706
+ const details = [
21707
+ `${totalTests} tests, ${totalAssertions} assertions`,
21708
+ `${assertionDensity.toFixed(1)} assertions/test`,
21709
+ `${trivialAssertionRatio.toFixed(1)}% trivial`,
21710
+ `${edgeCaseRatio.toFixed(1)}% edge cases`,
21711
+ `${allMatchersUsed.size}/${MATCHER_PATTERNS.length} matcher types`
21712
+ ].join(", ");
21713
+ return {
21714
+ score,
21715
+ assertionDensity,
21716
+ trivialAssertionRatio,
21717
+ edgeCaseRatio,
21718
+ assertionDiversity,
21719
+ totalTestFiles: testFiles.length,
21720
+ totalAssertions,
21721
+ trivialAssertions,
21722
+ edgeCaseTests,
21723
+ totalTests,
21724
+ details
21725
+ };
21726
+ }
21727
+ async findTestFiles() {
21728
+ return glob("**/*.{test,spec}.{ts,tsx,js,jsx}", {
21729
+ cwd: this.projectPath,
21730
+ absolute: true,
21731
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"]
21732
+ });
21733
+ }
21734
+ };
21735
+ function hasJSDocComment(node, sourceCode) {
21736
+ const startLine = node.loc.start.line;
21737
+ const lines = sourceCode.split("\n");
21738
+ for (let i = startLine - 2; i >= Math.max(0, startLine - 10); i--) {
21739
+ const line = lines[i]?.trim() ?? "";
21740
+ if (line.endsWith("*/")) {
21741
+ for (let j = i; j >= Math.max(0, i - 20); j--) {
21742
+ const commentLine = lines[j]?.trim() ?? "";
21743
+ if (commentLine.startsWith("/**")) return true;
21744
+ if (commentLine.startsWith("/*") && !commentLine.startsWith("/**")) return false;
21745
+ }
21746
+ }
21747
+ if (line && !line.startsWith("*") && !line.startsWith("//") && !line.startsWith("/*")) {
21748
+ break;
21749
+ }
21750
+ }
21751
+ return false;
21752
+ }
21753
+ function analyzeExportDocumentation(ast, sourceCode) {
21754
+ let exported = 0;
21755
+ let documented = 0;
21756
+ for (const node of ast.body) {
21757
+ if (node.type === "ExportNamedDeclaration" && node.declaration) {
21758
+ const decl = node.declaration;
21759
+ switch (decl.type) {
21760
+ case "FunctionDeclaration":
21761
+ exported++;
21762
+ if (hasJSDocComment(node, sourceCode)) documented++;
21763
+ break;
21764
+ case "ClassDeclaration":
21765
+ exported++;
21766
+ if (hasJSDocComment(node, sourceCode)) documented++;
21767
+ break;
21768
+ case "TSInterfaceDeclaration":
21769
+ case "TSTypeAliasDeclaration":
21770
+ case "TSEnumDeclaration":
21771
+ exported++;
21772
+ if (hasJSDocComment(node, sourceCode)) documented++;
21773
+ break;
21774
+ case "VariableDeclaration":
21775
+ for (const _declarator of decl.declarations) {
21776
+ exported++;
21777
+ if (hasJSDocComment(node, sourceCode)) documented++;
21778
+ }
21779
+ break;
21780
+ }
21781
+ } else if (node.type === "ExportDefaultDeclaration") {
21782
+ exported++;
21783
+ if (hasJSDocComment(node, sourceCode)) documented++;
21784
+ }
21785
+ }
21786
+ return { exported, documented };
21787
+ }
21788
+ var DocumentationAnalyzer = class {
21789
+ constructor(projectPath) {
21790
+ this.projectPath = projectPath;
21791
+ }
21792
+ /**
21793
+ * Analyze documentation quality
21794
+ */
21795
+ async analyze(files) {
21796
+ const targetFiles = files ?? await this.findSourceFiles();
21797
+ let totalExported = 0;
21798
+ let totalDocumented = 0;
21799
+ for (const file of targetFiles) {
21800
+ try {
21801
+ const content = await readFile(file, "utf-8");
21802
+ const ast = parse(content, {
21803
+ loc: true,
21804
+ range: true,
21805
+ comment: true,
21806
+ jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
21807
+ });
21808
+ const result = analyzeExportDocumentation(ast, content);
21809
+ totalExported += result.exported;
21810
+ totalDocumented += result.documented;
21811
+ } catch {
21812
+ }
21813
+ }
21814
+ const jsdocCoverage = totalExported > 0 ? totalDocumented / totalExported * 100 : 0;
21815
+ const hasReadme = await this.fileExists("README.md");
21816
+ const hasChangelog = await this.fileExists("CHANGELOG.md") || await this.fileExists("CHANGES.md");
21817
+ const score = Math.round(
21818
+ Math.max(
21819
+ 0,
21820
+ Math.min(100, jsdocCoverage * 0.7 + (hasReadme ? 20 : 0) + (hasChangelog ? 10 : 0))
21821
+ )
21822
+ );
21823
+ const details = [
21824
+ `${totalDocumented}/${totalExported} exports documented (${jsdocCoverage.toFixed(1)}%)`,
21825
+ `README: ${hasReadme ? "yes" : "no"}`,
21826
+ `CHANGELOG: ${hasChangelog ? "yes" : "no"}`
21827
+ ].join(", ");
21828
+ return {
21829
+ score,
21830
+ jsdocCoverage,
21831
+ hasReadme,
21832
+ hasChangelog,
21833
+ exportedDeclarations: totalExported,
21834
+ documentedDeclarations: totalDocumented,
21835
+ details
21836
+ };
21837
+ }
21838
+ async fileExists(relativePath) {
21839
+ try {
21840
+ await access(join(this.projectPath, relativePath), constants.R_OK);
21841
+ return true;
21842
+ } catch {
21843
+ return false;
21844
+ }
21845
+ }
21846
+ async findSourceFiles() {
21847
+ return glob("**/*.{ts,js,tsx,jsx}", {
21848
+ cwd: this.projectPath,
21849
+ absolute: true,
21850
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
21851
+ });
21852
+ }
21853
+ };
21854
+ async function detectLinter(projectPath) {
21855
+ try {
21856
+ const pkgContent = await readFile(join(projectPath, "package.json"), "utf-8");
21857
+ const pkg = JSON.parse(pkgContent);
21858
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
21859
+ if (deps.oxlint) return "oxlint";
21860
+ if (deps["@biomejs/biome"] || deps.biome) return "biome";
21861
+ if (deps.eslint) return "eslint";
21862
+ return null;
21863
+ } catch {
21864
+ return null;
21865
+ }
21866
+ }
21867
+ async function runOxlint(projectPath) {
21868
+ try {
21869
+ const result = await execa("npx", ["oxlint", "src", "--format=json"], {
21870
+ cwd: projectPath,
21871
+ reject: false,
21872
+ timeout: 6e4
21873
+ });
21874
+ try {
21875
+ const output = JSON.parse(result.stdout);
21876
+ const errors = Array.isArray(output) ? output.filter((d) => {
21877
+ const diag = d;
21878
+ return diag.severity === 2 || diag.severity === "error";
21879
+ }).length : 0;
21880
+ const warnings = Array.isArray(output) ? output.filter((d) => {
21881
+ const diag = d;
21882
+ return diag.severity === 1 || diag.severity === "warning";
21883
+ }).length : 0;
21884
+ return { errors, warnings };
21885
+ } catch {
21886
+ const lines = (result.stdout + result.stderr).split("\n");
21887
+ let errors = 0;
21888
+ let warnings = 0;
21889
+ for (const line of lines) {
21890
+ if (/error\[/.test(line) || /✖.*error/.test(line)) errors++;
21891
+ if (/warning\[/.test(line) || /⚠.*warning/.test(line)) warnings++;
21892
+ }
21893
+ return { errors, warnings };
21894
+ }
21895
+ } catch {
21896
+ return { errors: 0, warnings: 0 };
21897
+ }
21898
+ }
21899
+ async function runEslint(projectPath) {
21900
+ try {
21901
+ const result = await execa("npx", ["eslint", "src", "--format=json"], {
21902
+ cwd: projectPath,
21903
+ reject: false,
21904
+ timeout: 12e4
21905
+ });
21906
+ try {
21907
+ const output = JSON.parse(result.stdout);
21908
+ const errors = output.reduce((sum, f) => sum + f.errorCount, 0);
21909
+ const warnings = output.reduce((sum, f) => sum + f.warningCount, 0);
21910
+ return { errors, warnings };
21911
+ } catch {
21912
+ return { errors: 0, warnings: 0 };
21913
+ }
21914
+ } catch {
21915
+ return { errors: 0, warnings: 0 };
21916
+ }
21917
+ }
21918
+ async function runBiome(projectPath) {
21919
+ try {
21920
+ const result = await execa("npx", ["@biomejs/biome", "lint", "src", "--reporter=json"], {
21921
+ cwd: projectPath,
21922
+ reject: false,
21923
+ timeout: 6e4
21924
+ });
21925
+ try {
21926
+ const output = JSON.parse(result.stdout);
21927
+ const diagnostics = output.diagnostics ?? [];
21928
+ const errors = diagnostics.filter(
21929
+ (d) => d.severity === "error"
21930
+ ).length;
21931
+ const warnings = diagnostics.filter(
21932
+ (d) => d.severity === "warning"
21933
+ ).length;
21934
+ return { errors, warnings };
21935
+ } catch {
21936
+ return { errors: 0, warnings: 0 };
21937
+ }
21938
+ } catch {
21939
+ return { errors: 0, warnings: 0 };
21940
+ }
21941
+ }
21942
+ var StyleAnalyzer = class {
21943
+ constructor(projectPath) {
21944
+ this.projectPath = projectPath;
21945
+ }
21946
+ /**
21947
+ * Analyze style/linting quality
21948
+ */
21949
+ async analyze() {
21950
+ const linter = await detectLinter(this.projectPath);
21951
+ if (!linter) {
21952
+ return {
21953
+ score: 50,
21954
+ // Neutral score when no linter is configured
21955
+ errors: 0,
21956
+ warnings: 0,
21957
+ linterUsed: null,
21958
+ details: "No linter configured"
21959
+ };
21960
+ }
21961
+ let result;
21962
+ switch (linter) {
21963
+ case "oxlint":
21964
+ result = await runOxlint(this.projectPath);
21965
+ break;
21966
+ case "eslint":
21967
+ result = await runEslint(this.projectPath);
21968
+ break;
21969
+ case "biome":
21970
+ result = await runBiome(this.projectPath);
21971
+ break;
21972
+ }
21973
+ const score = Math.round(
21974
+ Math.max(0, Math.min(100, 100 - result.errors * 5 - result.warnings * 2))
21975
+ );
21976
+ const details = [
21977
+ `Linter: ${linter}`,
21978
+ `${result.errors} errors, ${result.warnings} warnings`
21979
+ ].join(", ");
21980
+ return {
21981
+ score,
21982
+ errors: result.errors,
21983
+ warnings: result.warnings,
21984
+ linterUsed: linter,
21985
+ details
21986
+ };
21987
+ }
21988
+ };
21989
+ function analyzeReadabilityPatterns(ast) {
21990
+ let goodNames = 0;
21991
+ let totalNames = 0;
21992
+ const functionLengths = [];
21993
+ let maxNestingDepth = 0;
21994
+ const cognitiveComplexities = [];
21995
+ const allowedSingleChar = /* @__PURE__ */ new Set(["i", "j", "k", "_", "e", "x", "y", "t", "n"]);
21996
+ function isGoodName(name) {
21997
+ if (name.length === 1) {
21998
+ return allowedSingleChar.has(name);
21999
+ }
22000
+ return name.length >= 3 && name.length <= 30;
22001
+ }
22002
+ function analyzeFunction(node) {
22003
+ if (node.loc) {
22004
+ const length = node.loc.end.line - node.loc.start.line + 1;
22005
+ functionLengths.push(length);
22006
+ }
22007
+ let localMaxNesting = 0;
22008
+ let cognitiveComplexity = 0;
22009
+ function traverseForComplexity(n, currentNesting) {
22010
+ localMaxNesting = Math.max(localMaxNesting, currentNesting);
22011
+ let nextNesting = currentNesting;
22012
+ let incrementsNesting = false;
22013
+ switch (n.type) {
22014
+ case "IfStatement":
22015
+ cognitiveComplexity += 1;
22016
+ incrementsNesting = true;
22017
+ break;
22018
+ case "ForStatement":
22019
+ case "ForInStatement":
22020
+ case "ForOfStatement":
22021
+ case "WhileStatement":
22022
+ case "DoWhileStatement":
22023
+ cognitiveComplexity += 1 + currentNesting;
22024
+ incrementsNesting = true;
22025
+ break;
22026
+ case "SwitchStatement":
22027
+ cognitiveComplexity += 1 + currentNesting;
22028
+ incrementsNesting = true;
22029
+ break;
22030
+ case "CatchClause":
22031
+ cognitiveComplexity += 1 + currentNesting;
22032
+ incrementsNesting = true;
22033
+ break;
22034
+ case "ConditionalExpression":
22035
+ cognitiveComplexity += 1 + currentNesting;
22036
+ incrementsNesting = true;
22037
+ break;
22038
+ case "LogicalExpression":
22039
+ if (n.operator === "&&" || n.operator === "||") {
22040
+ cognitiveComplexity += 1;
22041
+ }
22042
+ break;
22043
+ case "FunctionExpression":
22044
+ case "ArrowFunctionExpression":
22045
+ incrementsNesting = true;
22046
+ break;
22047
+ }
22048
+ if (incrementsNesting) {
22049
+ nextNesting = currentNesting + 1;
22050
+ }
22051
+ traverseChildren(n, (child) => traverseForComplexity(child, nextNesting));
22052
+ }
22053
+ traverseForComplexity(node, 0);
22054
+ maxNestingDepth = Math.max(maxNestingDepth, localMaxNesting);
22055
+ cognitiveComplexities.push(cognitiveComplexity);
22056
+ }
22057
+ function collectNames(node) {
22058
+ switch (node.type) {
22059
+ case "FunctionDeclaration":
22060
+ if (node.id) {
22061
+ totalNames++;
22062
+ if (isGoodName(node.id.name)) {
22063
+ goodNames++;
22064
+ }
22065
+ }
22066
+ for (const param of node.params) {
22067
+ collectParamNames(param);
22068
+ }
22069
+ break;
22070
+ case "FunctionExpression":
22071
+ case "ArrowFunctionExpression":
22072
+ for (const param of node.params) {
22073
+ collectParamNames(param);
22074
+ }
22075
+ break;
22076
+ case "VariableDeclarator":
22077
+ if (node.id.type === "Identifier") {
22078
+ totalNames++;
22079
+ if (isGoodName(node.id.name)) {
22080
+ goodNames++;
22081
+ }
22082
+ }
22083
+ break;
22084
+ case "MethodDefinition":
22085
+ if (node.key.type === "Identifier") {
22086
+ totalNames++;
22087
+ if (isGoodName(node.key.name)) {
22088
+ goodNames++;
22089
+ }
22090
+ }
22091
+ if (node.value.type === "FunctionExpression") {
22092
+ for (const param of node.value.params) {
22093
+ collectParamNames(param);
22094
+ }
22095
+ }
22096
+ break;
22097
+ }
22098
+ }
22099
+ function collectParamNames(param) {
22100
+ if (param.type === "Identifier") {
22101
+ totalNames++;
22102
+ if (isGoodName(param.name)) {
22103
+ goodNames++;
22104
+ }
22105
+ } else if (param.type === "AssignmentPattern" && param.left.type === "Identifier") {
22106
+ totalNames++;
22107
+ if (isGoodName(param.left.name)) {
22108
+ goodNames++;
22109
+ }
22110
+ } else if (param.type === "RestElement" && param.argument.type === "Identifier") {
22111
+ totalNames++;
22112
+ if (isGoodName(param.argument.name)) {
22113
+ goodNames++;
22114
+ }
22115
+ }
22116
+ }
22117
+ function traverse(node) {
22118
+ const isFunctionNode = node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "MethodDefinition";
22119
+ collectNames(node);
22120
+ if (isFunctionNode) {
22121
+ analyzeFunction(node);
22122
+ }
22123
+ traverseChildren(node, traverse);
22124
+ }
22125
+ function traverseChildren(node, callback) {
22126
+ for (const key of Object.keys(node)) {
22127
+ if (key === "parent") continue;
22128
+ const child = node[key];
22129
+ if (child && typeof child === "object") {
22130
+ if (Array.isArray(child)) {
22131
+ for (const item of child) {
22132
+ if (item && typeof item === "object" && item.type) {
22133
+ callback(item);
22134
+ }
22135
+ }
22136
+ } else if (child.type) {
22137
+ callback(child);
22138
+ }
22139
+ }
22140
+ }
22141
+ }
22142
+ traverse(ast);
22143
+ return {
22144
+ goodNames,
22145
+ totalNames,
22146
+ functionLengths,
22147
+ maxNestingDepth,
22148
+ cognitiveComplexities
22149
+ };
22150
+ }
22151
+ var ReadabilityAnalyzer = class {
22152
+ constructor(projectPath) {
22153
+ this.projectPath = projectPath;
22154
+ }
22155
+ /**
22156
+ * Analyze readability of project files
22157
+ */
22158
+ async analyze(files) {
22159
+ const targetFiles = files ?? await this.findSourceFiles();
22160
+ let totalGoodNames = 0;
22161
+ let totalNames = 0;
22162
+ const allFunctionLengths = [];
22163
+ let globalMaxNestingDepth = 0;
22164
+ const allCognitiveComplexities = [];
22165
+ for (const file of targetFiles) {
22166
+ try {
22167
+ const content = await readFile(file, "utf-8");
22168
+ const ast = parse(content, {
22169
+ loc: true,
22170
+ range: true,
22171
+ jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
22172
+ });
22173
+ const result = analyzeReadabilityPatterns(ast);
22174
+ totalGoodNames += result.goodNames;
22175
+ totalNames += result.totalNames;
22176
+ allFunctionLengths.push(...result.functionLengths);
22177
+ globalMaxNestingDepth = Math.max(globalMaxNestingDepth, result.maxNestingDepth);
22178
+ allCognitiveComplexities.push(...result.cognitiveComplexities);
22179
+ } catch {
22180
+ }
22181
+ }
22182
+ const namingScore = totalNames > 0 ? totalGoodNames / totalNames * 100 : 100;
22183
+ const averageFunctionLength = allFunctionLengths.length > 0 ? allFunctionLengths.reduce((a, b) => a + b, 0) / allFunctionLengths.length : 0;
22184
+ const functionLengthScore = allFunctionLengths.length > 0 ? Math.max(0, Math.min(100, 100 - (averageFunctionLength - 20) * 2.5)) : 100;
22185
+ const maxNestingDepth = globalMaxNestingDepth;
22186
+ const nestingDepthScore = Math.max(0, Math.min(100, 100 - (maxNestingDepth - 2) * 20));
22187
+ const averageCognitiveComplexity = allCognitiveComplexities.length > 0 ? allCognitiveComplexities.reduce((a, b) => a + b, 0) / allCognitiveComplexities.length : 0;
22188
+ const cognitiveComplexityScore = allCognitiveComplexities.length > 0 ? Math.max(0, Math.min(100, 100 - (averageCognitiveComplexity - 5) * 5)) : 100;
22189
+ const score = Math.round(
22190
+ namingScore * 0.25 + functionLengthScore * 0.25 + nestingDepthScore * 0.25 + cognitiveComplexityScore * 0.25
22191
+ );
22192
+ const details = [
22193
+ `${totalGoodNames}/${totalNames} good names`,
22194
+ `avg func ${averageFunctionLength.toFixed(1)} LOC`,
22195
+ `max nesting ${maxNestingDepth}`,
22196
+ `avg weighted complexity ${averageCognitiveComplexity.toFixed(1)}`
22197
+ ].join(", ");
22198
+ return {
22199
+ score,
22200
+ namingScore,
22201
+ functionLengthScore,
22202
+ nestingDepthScore,
22203
+ cognitiveComplexityScore,
22204
+ averageFunctionLength,
22205
+ maxNestingDepth,
22206
+ averageCognitiveComplexity,
22207
+ details
22208
+ };
22209
+ }
22210
+ async findSourceFiles() {
22211
+ return glob("**/*.{ts,js,tsx,jsx}", {
22212
+ cwd: this.projectPath,
22213
+ absolute: true,
22214
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
22215
+ });
22216
+ }
22217
+ };
22218
+ function analyzeMaintainabilityPatterns(ast, filePath) {
22219
+ let functionCount = 0;
22220
+ let importCount = 0;
22221
+ let crossBoundaryImportCount = 0;
22222
+ function traverse(node) {
22223
+ switch (node.type) {
22224
+ case "FunctionDeclaration":
22225
+ case "FunctionExpression":
22226
+ case "ArrowFunctionExpression":
22227
+ case "MethodDefinition":
22228
+ functionCount++;
22229
+ break;
22230
+ case "ImportDeclaration":
22231
+ importCount++;
22232
+ if (isCrossBoundaryImport(node)) {
22233
+ crossBoundaryImportCount++;
22234
+ }
22235
+ break;
22236
+ }
22237
+ traverseChildren(node);
22238
+ }
22239
+ function traverseChildren(node) {
22240
+ for (const key of Object.keys(node)) {
22241
+ if (key === "parent") continue;
22242
+ const child = node[key];
22243
+ if (child && typeof child === "object") {
22244
+ if (Array.isArray(child)) {
22245
+ for (const item of child) {
22246
+ if (item && typeof item === "object" && item.type) {
22247
+ traverse(item);
22248
+ }
22249
+ }
22250
+ } else if (child.type) {
22251
+ traverse(child);
22252
+ }
22253
+ }
22254
+ }
22255
+ }
22256
+ traverse(ast);
22257
+ return {
22258
+ lineCount: 0,
22259
+ // Will be set separately from content
22260
+ functionCount,
22261
+ importCount,
22262
+ crossBoundaryImportCount
22263
+ };
22264
+ }
22265
+ function isCrossBoundaryImport(node, _filePath) {
22266
+ if (node.source.type !== "Literal" || typeof node.source.value !== "string") {
22267
+ return false;
22268
+ }
22269
+ const importPath = node.source.value;
22270
+ if (importPath.startsWith("node:")) {
22271
+ return false;
22272
+ }
22273
+ if (!importPath.startsWith(".")) {
22274
+ return false;
22275
+ }
22276
+ if (importPath.startsWith("../")) {
22277
+ return true;
22278
+ }
22279
+ if (importPath.startsWith("./")) {
22280
+ const relativePath = importPath.slice(2);
22281
+ return relativePath.includes("/");
22282
+ }
22283
+ return false;
22284
+ }
22285
+ function countLines(content) {
22286
+ return content.split("\n").length;
22287
+ }
22288
+ var MaintainabilityAnalyzer = class {
22289
+ constructor(projectPath) {
22290
+ this.projectPath = projectPath;
22291
+ }
22292
+ /**
22293
+ * Analyze maintainability of project files
22294
+ */
22295
+ async analyze(files) {
22296
+ const targetFiles = files ?? await this.findSourceFiles();
22297
+ if (targetFiles.length === 0) {
22298
+ return {
22299
+ score: 100,
22300
+ fileLengthScore: 100,
22301
+ functionCountScore: 100,
22302
+ dependencyCountScore: 100,
22303
+ couplingScore: 100,
22304
+ averageFileLength: 0,
22305
+ averageFunctionsPerFile: 0,
22306
+ averageImportsPerFile: 0,
22307
+ fileCount: 0,
22308
+ details: "No files to analyze"
22309
+ };
22310
+ }
22311
+ let totalLines = 0;
22312
+ let totalFunctions = 0;
22313
+ let totalImports = 0;
22314
+ let totalCrossBoundaryImports = 0;
22315
+ let filesAnalyzed = 0;
22316
+ for (const file of targetFiles) {
22317
+ try {
22318
+ const content = await readFile(file, "utf-8");
22319
+ const lineCount = countLines(content);
22320
+ const ast = parse(content, {
22321
+ loc: true,
22322
+ range: true,
22323
+ jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
22324
+ });
22325
+ const result = analyzeMaintainabilityPatterns(ast, file);
22326
+ totalLines += lineCount;
22327
+ totalFunctions += result.functionCount;
22328
+ totalImports += result.importCount;
22329
+ totalCrossBoundaryImports += result.crossBoundaryImportCount;
22330
+ filesAnalyzed++;
22331
+ } catch {
22332
+ }
22333
+ }
22334
+ const averageFileLength = filesAnalyzed > 0 ? totalLines / filesAnalyzed : 0;
22335
+ const averageFunctionsPerFile = filesAnalyzed > 0 ? totalFunctions / filesAnalyzed : 0;
22336
+ const averageImportsPerFile = filesAnalyzed > 0 ? totalImports / filesAnalyzed : 0;
22337
+ const fileLengthScore = Math.max(0, Math.min(100, 100 - (averageFileLength - 200) * 0.33));
22338
+ const functionCountScore = Math.max(0, Math.min(100, 100 - (averageFunctionsPerFile - 10) * 5));
22339
+ const dependencyCountScore = Math.max(0, Math.min(100, 100 - (averageImportsPerFile - 5) * 5));
22340
+ const crossBoundaryRatio = totalImports > 0 ? totalCrossBoundaryImports / totalImports : 0;
22341
+ const couplingScore = Math.max(0, Math.min(100, 100 - crossBoundaryRatio * 100 * 0.5));
22342
+ const score = Math.round(
22343
+ fileLengthScore * 0.3 + functionCountScore * 0.25 + dependencyCountScore * 0.25 + couplingScore * 0.2
22344
+ );
22345
+ const details = [
22346
+ `${Math.round(averageFileLength)} avg lines/file`,
22347
+ `${averageFunctionsPerFile.toFixed(1)} avg functions/file`,
22348
+ `${averageImportsPerFile.toFixed(1)} avg imports/file`,
22349
+ `${Math.round(crossBoundaryRatio * 100)}% cross-boundary coupling`
22350
+ ].join(", ");
22351
+ return {
22352
+ score,
22353
+ fileLengthScore,
22354
+ functionCountScore,
22355
+ dependencyCountScore,
22356
+ couplingScore,
22357
+ averageFileLength,
22358
+ averageFunctionsPerFile,
22359
+ averageImportsPerFile,
22360
+ fileCount: filesAnalyzed,
22361
+ details
22362
+ };
22363
+ }
22364
+ async findSourceFiles() {
22365
+ return glob("**/*.{ts,js,tsx,jsx}", {
22366
+ cwd: this.projectPath,
22367
+ absolute: true,
22368
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
22369
+ });
22370
+ }
22371
+ };
22372
+
22373
+ // src/quality/types.ts
22374
+ var DEFAULT_QUALITY_WEIGHTS = {
22375
+ correctness: 0.15,
22376
+ completeness: 0.1,
22377
+ robustness: 0.1,
22378
+ readability: 0.1,
22379
+ maintainability: 0.1,
22380
+ complexity: 0.08,
22381
+ duplication: 0.07,
22382
+ testCoverage: 0.1,
22383
+ testQuality: 0.05,
22384
+ security: 0.08,
22385
+ documentation: 0.04,
22386
+ style: 0.03
22387
+ };
22388
+ var DEFAULT_QUALITY_THRESHOLDS = {
22389
+ minimum: {
22390
+ overall: 85,
22391
+ testCoverage: 80,
22392
+ security: 100
22393
+ // No vulnerabilities allowed
22394
+ },
22395
+ target: {
22396
+ overall: 95,
22397
+ testCoverage: 90
22398
+ }};
22399
+ var QualityEvaluator = class {
22400
+ constructor(projectPath, useSnyk = false) {
22401
+ this.projectPath = projectPath;
22402
+ this.coverageAnalyzer = new CoverageAnalyzer(projectPath);
22403
+ this.securityScanner = new CompositeSecurityScanner(projectPath, useSnyk);
22404
+ this.complexityAnalyzer = new ComplexityAnalyzer(projectPath);
22405
+ this.duplicationAnalyzer = new DuplicationAnalyzer(projectPath);
22406
+ this.correctnessAnalyzer = new CorrectnessAnalyzer(projectPath);
22407
+ this.completenessAnalyzer = new CompletenessAnalyzer(projectPath);
22408
+ this.robustnessAnalyzer = new RobustnessAnalyzer(projectPath);
22409
+ this.testQualityAnalyzer = new TestQualityAnalyzer(projectPath);
22410
+ this.documentationAnalyzer = new DocumentationAnalyzer(projectPath);
22411
+ this.styleAnalyzer = new StyleAnalyzer(projectPath);
22412
+ this.readabilityAnalyzer = new ReadabilityAnalyzer(projectPath);
22413
+ this.maintainabilityAnalyzer = new MaintainabilityAnalyzer(projectPath);
22414
+ }
22415
+ coverageAnalyzer;
22416
+ securityScanner;
22417
+ complexityAnalyzer;
22418
+ duplicationAnalyzer;
22419
+ correctnessAnalyzer;
22420
+ completenessAnalyzer;
22421
+ robustnessAnalyzer;
22422
+ testQualityAnalyzer;
22423
+ documentationAnalyzer;
22424
+ styleAnalyzer;
22425
+ readabilityAnalyzer;
22426
+ maintainabilityAnalyzer;
22427
+ /**
22428
+ * Evaluate quality across all 12 dimensions
22429
+ * Every dimension is computed by real static analysis — zero hardcoded values
22430
+ */
22431
+ async evaluate(files) {
22432
+ const startTime = performance.now();
22433
+ const targetFiles = files ?? await this.findSourceFiles();
22434
+ const fileContents = await Promise.all(
22435
+ targetFiles.map(async (file) => ({
22436
+ path: file,
22437
+ content: await readFile(file, "utf-8")
22438
+ }))
22439
+ );
22440
+ const [
22441
+ coverageResult,
22442
+ securityResult,
22443
+ complexityResult,
22444
+ duplicationResult,
22445
+ correctnessResult,
22446
+ completenessResult,
22447
+ robustnessResult,
22448
+ testQualityResult,
22449
+ documentationResult,
22450
+ styleResult,
22451
+ readabilityResult,
22452
+ maintainabilityResult
22453
+ ] = await Promise.all([
22454
+ this.coverageAnalyzer.analyze().catch(() => null),
22455
+ this.securityScanner.scan(fileContents),
22456
+ this.complexityAnalyzer.analyze(targetFiles),
22457
+ this.duplicationAnalyzer.analyze(targetFiles),
22458
+ this.correctnessAnalyzer.analyze().catch(() => ({ score: 0 })),
22459
+ this.completenessAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
22460
+ this.robustnessAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
22461
+ this.testQualityAnalyzer.analyze().catch(() => ({ score: 0 })),
22462
+ this.documentationAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
22463
+ this.styleAnalyzer.analyze().catch(() => ({ score: 50 })),
22464
+ this.readabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 50 })),
22465
+ this.maintainabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 50 }))
22466
+ ]);
22467
+ const dimensions = {
22468
+ testCoverage: coverageResult?.lines.percentage ?? 0,
22469
+ security: securityResult.score,
22470
+ complexity: complexityResult.score,
22471
+ duplication: Math.max(0, 100 - duplicationResult.percentage),
22472
+ style: styleResult.score,
22473
+ readability: readabilityResult.score,
22474
+ maintainability: maintainabilityResult.score,
22475
+ correctness: correctnessResult.score,
22476
+ completeness: completenessResult.score,
22477
+ robustness: robustnessResult.score,
22478
+ testQuality: testQualityResult.score,
22479
+ documentation: documentationResult.score
22480
+ };
22481
+ const overall = Object.entries(dimensions).reduce((sum, [key, value]) => {
22482
+ const weight = DEFAULT_QUALITY_WEIGHTS[key] ?? 0;
22483
+ return sum + value * weight;
22484
+ }, 0);
22485
+ const scores = {
22486
+ overall: Math.round(overall),
22487
+ dimensions,
22488
+ evaluatedAt: /* @__PURE__ */ new Date(),
22489
+ evaluationDurationMs: performance.now() - startTime
22490
+ };
22491
+ const issues = this.generateIssues(
22492
+ securityResult.vulnerabilities,
22493
+ complexityResult,
22494
+ duplicationResult,
22495
+ correctnessResult,
22496
+ styleResult,
22497
+ documentationResult
22498
+ );
22499
+ const suggestions = this.generateSuggestions(dimensions);
22500
+ const meetsMinimum = scores.overall >= DEFAULT_QUALITY_THRESHOLDS.minimum.overall && dimensions.testCoverage >= DEFAULT_QUALITY_THRESHOLDS.minimum.testCoverage && dimensions.security >= DEFAULT_QUALITY_THRESHOLDS.minimum.security;
22501
+ const meetsTarget = scores.overall >= DEFAULT_QUALITY_THRESHOLDS.target.overall && dimensions.testCoverage >= DEFAULT_QUALITY_THRESHOLDS.target.testCoverage;
22502
+ return {
22503
+ scores,
22504
+ meetsMinimum,
22505
+ meetsTarget,
22506
+ converged: false,
22507
+ issues,
22508
+ suggestions
22509
+ };
22510
+ }
22511
+ /**
22512
+ * Generate quality issues from analyzer results
22513
+ */
22514
+ generateIssues(securityVulns, complexityResult, duplicationResult, correctnessResult, styleResult, documentationResult) {
22515
+ const issues = [];
22516
+ for (const vuln of securityVulns) {
22517
+ issues.push({
22518
+ dimension: "security",
22519
+ severity: vuln.severity === "critical" ? "critical" : vuln.severity === "high" ? "major" : "minor",
22520
+ message: `${vuln.type}: ${vuln.description}`,
22521
+ file: vuln.location.file,
22522
+ line: vuln.location.line
22523
+ });
22524
+ }
22525
+ for (const file of complexityResult.files) {
22526
+ for (const fn of file.functions) {
22527
+ if (fn.complexity > 10) {
22528
+ issues.push({
22529
+ dimension: "complexity",
22530
+ severity: "major",
22531
+ message: `Function '${fn.name}' has high complexity (${fn.complexity})`,
20658
22532
  file: file.file,
20659
22533
  line: fn.line,
20660
22534
  suggestion: "Refactor into smaller functions or reduce branching"
@@ -20670,6 +22544,38 @@ var QualityEvaluator = class {
20670
22544
  suggestion: "Extract common code into reusable functions or modules"
20671
22545
  });
20672
22546
  }
22547
+ if (correctnessResult.testsFailed != null && correctnessResult.testsFailed > 0) {
22548
+ issues.push({
22549
+ dimension: "correctness",
22550
+ severity: "critical",
22551
+ message: `${correctnessResult.testsFailed} tests failing`,
22552
+ suggestion: "Fix failing tests to improve correctness score"
22553
+ });
22554
+ }
22555
+ if (correctnessResult.buildSuccess === false) {
22556
+ issues.push({
22557
+ dimension: "correctness",
22558
+ severity: "critical",
22559
+ message: "Build/type check failed",
22560
+ suggestion: "Fix type errors to pass build verification"
22561
+ });
22562
+ }
22563
+ if (styleResult.errors != null && styleResult.errors > 0) {
22564
+ issues.push({
22565
+ dimension: "style",
22566
+ severity: "minor",
22567
+ message: `${styleResult.errors} linting errors found`,
22568
+ suggestion: "Run linter with --fix to auto-correct style issues"
22569
+ });
22570
+ }
22571
+ if (documentationResult.jsdocCoverage !== void 0 && documentationResult.jsdocCoverage < 50) {
22572
+ issues.push({
22573
+ dimension: "documentation",
22574
+ severity: "minor",
22575
+ message: `Low JSDoc coverage: ${documentationResult.jsdocCoverage?.toFixed(1) ?? 0}%`,
22576
+ suggestion: "Add JSDoc comments to exported functions and classes"
22577
+ });
22578
+ }
20673
22579
  return issues;
20674
22580
  }
20675
22581
  /**
@@ -20693,6 +22599,14 @@ var QualityEvaluator = class {
20693
22599
  estimatedImpact: 100 - dimensions.security
20694
22600
  });
20695
22601
  }
22602
+ if (dimensions.correctness < 85) {
22603
+ suggestions.push({
22604
+ dimension: "correctness",
22605
+ priority: "high",
22606
+ description: "Fix failing tests and build errors",
22607
+ estimatedImpact: 85 - dimensions.correctness
22608
+ });
22609
+ }
20696
22610
  if (dimensions.complexity < 80) {
20697
22611
  suggestions.push({
20698
22612
  dimension: "complexity",
@@ -20701,6 +22615,22 @@ var QualityEvaluator = class {
20701
22615
  estimatedImpact: Math.min(10, 80 - dimensions.complexity)
20702
22616
  });
20703
22617
  }
22618
+ if (dimensions.documentation < 60) {
22619
+ suggestions.push({
22620
+ dimension: "documentation",
22621
+ priority: "medium",
22622
+ description: "Add JSDoc comments to exported declarations",
22623
+ estimatedImpact: Math.min(15, 60 - dimensions.documentation)
22624
+ });
22625
+ }
22626
+ if (dimensions.testQuality < 70) {
22627
+ suggestions.push({
22628
+ dimension: "testQuality",
22629
+ priority: "medium",
22630
+ description: "Replace trivial assertions with meaningful behavioral tests",
22631
+ estimatedImpact: Math.min(10, 70 - dimensions.testQuality)
22632
+ });
22633
+ }
20704
22634
  if (dimensions.duplication < 95) {
20705
22635
  suggestions.push({
20706
22636
  dimension: "duplication",
@@ -20727,7 +22657,7 @@ function createQualityEvaluator(projectPath, useSnyk) {
20727
22657
  }
20728
22658
 
20729
22659
  // src/tools/quality.ts
20730
- async function detectLinter(cwd) {
22660
+ async function detectLinter2(cwd) {
20731
22661
  try {
20732
22662
  const pkgPath = path20__default.join(cwd, "package.json");
20733
22663
  const pkgContent = await fs22__default.readFile(pkgPath, "utf-8");
@@ -20762,7 +22692,7 @@ Examples:
20762
22692
  }),
20763
22693
  async execute({ cwd, files, fix, linter }) {
20764
22694
  const projectDir = cwd ?? process.cwd();
20765
- const detectedLinter = linter ?? await detectLinter(projectDir);
22695
+ const detectedLinter = linter ?? await detectLinter2(projectDir);
20766
22696
  if (!detectedLinter) {
20767
22697
  return {
20768
22698
  errors: 0,
@@ -20917,8 +22847,8 @@ Examples:
20917
22847
  }
20918
22848
  });
20919
22849
  async function findSourceFiles(cwd) {
20920
- const { glob: glob9 } = await import('glob');
20921
- return glob9("src/**/*.{ts,js,tsx,jsx}", {
22850
+ const { glob: glob15 } = await import('glob');
22851
+ return glob15("src/**/*.{ts,js,tsx,jsx}", {
20922
22852
  cwd,
20923
22853
  absolute: true,
20924
22854
  ignore: ["**/*.test.*", "**/*.spec.*", "**/node_modules/**"]
@@ -22475,9 +24405,9 @@ Examples:
22475
24405
  }
22476
24406
  });
22477
24407
  var diffTools = [showDiffTool];
22478
- async function fileExists(path35) {
24408
+ async function fileExists(path37) {
22479
24409
  try {
22480
- await access(path35);
24410
+ await access(path37);
22481
24411
  return true;
22482
24412
  } catch {
22483
24413
  return false;
@@ -22567,7 +24497,7 @@ async function detectMaturity(cwd) {
22567
24497
  if (!hasLintConfig && hasPackageJson) {
22568
24498
  try {
22569
24499
  const pkgRaw = await import('fs/promises').then(
22570
- (fs38) => fs38.readFile(join(cwd, "package.json"), "utf-8")
24500
+ (fs39) => fs39.readFile(join(cwd, "package.json"), "utf-8")
22571
24501
  );
22572
24502
  const pkg = JSON.parse(pkgRaw);
22573
24503
  if (pkg.scripts?.lint || pkg.scripts?.["lint:fix"]) {
@@ -22934,9 +24864,9 @@ Examples:
22934
24864
  }
22935
24865
  });
22936
24866
  var reviewTools = [reviewCodeTool];
22937
- var fs27 = await import('fs/promises');
22938
- var path25 = await import('path');
22939
- var { glob: glob6 } = await import('glob');
24867
+ var fs28 = await import('fs/promises');
24868
+ var path26 = await import('path');
24869
+ var { glob: glob12 } = await import('glob');
22940
24870
  var DEFAULT_MAX_FILES = 200;
22941
24871
  var LANGUAGE_EXTENSIONS = {
22942
24872
  typescript: [".ts", ".tsx", ".mts", ".cts"],
@@ -22961,7 +24891,7 @@ var DEFAULT_EXCLUDES = [
22961
24891
  "**/*.d.ts"
22962
24892
  ];
22963
24893
  function detectLanguage2(filePath) {
22964
- const ext = path25.extname(filePath).toLowerCase();
24894
+ const ext = path26.extname(filePath).toLowerCase();
22965
24895
  for (const [lang, extensions] of Object.entries(LANGUAGE_EXTENSIONS)) {
22966
24896
  if (extensions.includes(ext)) return lang;
22967
24897
  }
@@ -23370,9 +25300,9 @@ Examples:
23370
25300
  }),
23371
25301
  async execute({ path: rootPath, include, exclude, languages, maxFiles, depth }) {
23372
25302
  const startTime = performance.now();
23373
- const absPath = path25.resolve(rootPath);
25303
+ const absPath = path26.resolve(rootPath);
23374
25304
  try {
23375
- const stat2 = await fs27.stat(absPath);
25305
+ const stat2 = await fs28.stat(absPath);
23376
25306
  if (!stat2.isDirectory()) {
23377
25307
  throw new ToolError(`Path is not a directory: ${absPath}`, {
23378
25308
  tool: "codebase_map"
@@ -23397,7 +25327,7 @@ Examples:
23397
25327
  pattern = `**/*{${allExts.join(",")}}`;
23398
25328
  }
23399
25329
  const excludePatterns = [...DEFAULT_EXCLUDES, ...exclude ?? []];
23400
- const files = await glob6(pattern, {
25330
+ const files = await glob12(pattern, {
23401
25331
  cwd: absPath,
23402
25332
  ignore: excludePatterns,
23403
25333
  nodir: true,
@@ -23409,14 +25339,14 @@ Examples:
23409
25339
  let totalDefinitions = 0;
23410
25340
  let exportedSymbols = 0;
23411
25341
  for (const file of limitedFiles) {
23412
- const fullPath = path25.join(absPath, file);
25342
+ const fullPath = path26.join(absPath, file);
23413
25343
  const language = detectLanguage2(file);
23414
25344
  if (!language) continue;
23415
25345
  if (languages && !languages.includes(language)) {
23416
25346
  continue;
23417
25347
  }
23418
25348
  try {
23419
- const content = await fs27.readFile(fullPath, "utf-8");
25349
+ const content = await fs28.readFile(fullPath, "utf-8");
23420
25350
  const lineCount = content.split("\n").length;
23421
25351
  const parsed = parseFile(content, language);
23422
25352
  const definitions = depth === "overview" ? parsed.definitions.filter((d) => d.exported) : parsed.definitions;
@@ -23449,23 +25379,23 @@ Examples:
23449
25379
  });
23450
25380
  var codebaseMapTools = [codebaseMapTool];
23451
25381
  init_paths();
23452
- var fs28 = await import('fs/promises');
23453
- var path26 = await import('path');
25382
+ var fs29 = await import('fs/promises');
25383
+ var path27 = await import('path');
23454
25384
  var crypto2 = await import('crypto');
23455
- var GLOBAL_MEMORIES_DIR = path26.join(COCO_HOME, "memories");
25385
+ var GLOBAL_MEMORIES_DIR = path27.join(COCO_HOME, "memories");
23456
25386
  var PROJECT_MEMORIES_DIR = ".coco/memories";
23457
25387
  var DEFAULT_MAX_MEMORIES = 1e3;
23458
25388
  async function ensureDir2(dirPath) {
23459
- await fs28.mkdir(dirPath, { recursive: true });
25389
+ await fs29.mkdir(dirPath, { recursive: true });
23460
25390
  }
23461
25391
  function getMemoriesDir(scope) {
23462
25392
  return scope === "global" ? GLOBAL_MEMORIES_DIR : PROJECT_MEMORIES_DIR;
23463
25393
  }
23464
25394
  async function loadIndex(scope) {
23465
25395
  const dir = getMemoriesDir(scope);
23466
- const indexPath = path26.join(dir, "index.json");
25396
+ const indexPath = path27.join(dir, "index.json");
23467
25397
  try {
23468
- const content = await fs28.readFile(indexPath, "utf-8");
25398
+ const content = await fs29.readFile(indexPath, "utf-8");
23469
25399
  return JSON.parse(content);
23470
25400
  } catch {
23471
25401
  return [];
@@ -23474,14 +25404,14 @@ async function loadIndex(scope) {
23474
25404
  async function saveIndex(scope, index) {
23475
25405
  const dir = getMemoriesDir(scope);
23476
25406
  await ensureDir2(dir);
23477
- const indexPath = path26.join(dir, "index.json");
23478
- await fs28.writeFile(indexPath, JSON.stringify(index, null, 2), "utf-8");
25407
+ const indexPath = path27.join(dir, "index.json");
25408
+ await fs29.writeFile(indexPath, JSON.stringify(index, null, 2), "utf-8");
23479
25409
  }
23480
25410
  async function loadMemory(scope, id) {
23481
25411
  const dir = getMemoriesDir(scope);
23482
- const memPath = path26.join(dir, `${id}.json`);
25412
+ const memPath = path27.join(dir, `${id}.json`);
23483
25413
  try {
23484
- const content = await fs28.readFile(memPath, "utf-8");
25414
+ const content = await fs29.readFile(memPath, "utf-8");
23485
25415
  return JSON.parse(content);
23486
25416
  } catch {
23487
25417
  return null;
@@ -23490,8 +25420,8 @@ async function loadMemory(scope, id) {
23490
25420
  async function saveMemory(scope, memory) {
23491
25421
  const dir = getMemoriesDir(scope);
23492
25422
  await ensureDir2(dir);
23493
- const memPath = path26.join(dir, `${memory.id}.json`);
23494
- await fs28.writeFile(memPath, JSON.stringify(memory, null, 2), "utf-8");
25423
+ const memPath = path27.join(dir, `${memory.id}.json`);
25424
+ await fs29.writeFile(memPath, JSON.stringify(memory, null, 2), "utf-8");
23495
25425
  }
23496
25426
  var createMemoryTool = defineTool({
23497
25427
  name: "create_memory",
@@ -23643,17 +25573,17 @@ Examples:
23643
25573
  }
23644
25574
  });
23645
25575
  var memoryTools = [createMemoryTool, recallMemoryTool, listMemoriesTool];
23646
- var fs29 = await import('fs/promises');
25576
+ var fs30 = await import('fs/promises');
23647
25577
  var crypto3 = await import('crypto');
23648
25578
  var CHECKPOINT_FILE = ".coco/checkpoints.json";
23649
25579
  var DEFAULT_MAX_CHECKPOINTS = 50;
23650
25580
  var STASH_PREFIX = "coco-cp";
23651
25581
  async function ensureCocoDir() {
23652
- await fs29.mkdir(".coco", { recursive: true });
25582
+ await fs30.mkdir(".coco", { recursive: true });
23653
25583
  }
23654
25584
  async function loadCheckpoints() {
23655
25585
  try {
23656
- const content = await fs29.readFile(CHECKPOINT_FILE, "utf-8");
25586
+ const content = await fs30.readFile(CHECKPOINT_FILE, "utf-8");
23657
25587
  return JSON.parse(content);
23658
25588
  } catch {
23659
25589
  return [];
@@ -23661,7 +25591,7 @@ async function loadCheckpoints() {
23661
25591
  }
23662
25592
  async function saveCheckpoints(checkpoints) {
23663
25593
  await ensureCocoDir();
23664
- await fs29.writeFile(CHECKPOINT_FILE, JSON.stringify(checkpoints, null, 2), "utf-8");
25594
+ await fs30.writeFile(CHECKPOINT_FILE, JSON.stringify(checkpoints, null, 2), "utf-8");
23665
25595
  }
23666
25596
  async function execGit(args) {
23667
25597
  const { execaCommand } = await import('execa');
@@ -23821,9 +25751,9 @@ Examples:
23821
25751
  }
23822
25752
  });
23823
25753
  var checkpointTools = [createCheckpointTool, restoreCheckpointTool, listCheckpointsTool];
23824
- var fs30 = await import('fs/promises');
23825
- var path27 = await import('path');
23826
- var { glob: glob7 } = await import('glob');
25754
+ var fs31 = await import('fs/promises');
25755
+ var path28 = await import('path');
25756
+ var { glob: glob13 } = await import('glob');
23827
25757
  var INDEX_DIR = ".coco/search-index";
23828
25758
  var DEFAULT_CHUNK_SIZE = 20;
23829
25759
  var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -23948,20 +25878,20 @@ async function getEmbedding(text9) {
23948
25878
  }
23949
25879
  async function loadIndex2(indexDir) {
23950
25880
  try {
23951
- const indexPath = path27.join(indexDir, "index.json");
23952
- const content = await fs30.readFile(indexPath, "utf-8");
25881
+ const indexPath = path28.join(indexDir, "index.json");
25882
+ const content = await fs31.readFile(indexPath, "utf-8");
23953
25883
  return JSON.parse(content);
23954
25884
  } catch {
23955
25885
  return null;
23956
25886
  }
23957
25887
  }
23958
25888
  async function saveIndex2(indexDir, index) {
23959
- await fs30.mkdir(indexDir, { recursive: true });
23960
- const indexPath = path27.join(indexDir, "index.json");
23961
- await fs30.writeFile(indexPath, JSON.stringify(index), "utf-8");
25889
+ await fs31.mkdir(indexDir, { recursive: true });
25890
+ const indexPath = path28.join(indexDir, "index.json");
25891
+ await fs31.writeFile(indexPath, JSON.stringify(index), "utf-8");
23962
25892
  }
23963
25893
  function isBinary(filePath) {
23964
- return BINARY_EXTENSIONS.has(path27.extname(filePath).toLowerCase());
25894
+ return BINARY_EXTENSIONS.has(path28.extname(filePath).toLowerCase());
23965
25895
  }
23966
25896
  var semanticSearchTool = defineTool({
23967
25897
  name: "semantic_search",
@@ -23986,12 +25916,12 @@ Examples:
23986
25916
  const effectivePath = rootPath ?? ".";
23987
25917
  const effectiveMaxResults = maxResults ?? 10;
23988
25918
  const effectiveThreshold = threshold ?? 0.3;
23989
- const absPath = path27.resolve(effectivePath);
23990
- const indexDir = path27.join(absPath, INDEX_DIR);
25919
+ const absPath = path28.resolve(effectivePath);
25920
+ const indexDir = path28.join(absPath, INDEX_DIR);
23991
25921
  let index = reindex ? null : await loadIndex2(indexDir);
23992
25922
  if (!index) {
23993
25923
  const pattern = include ?? "**/*";
23994
- const files = await glob7(pattern, {
25924
+ const files = await glob13(pattern, {
23995
25925
  cwd: absPath,
23996
25926
  ignore: DEFAULT_EXCLUDES2,
23997
25927
  nodir: true,
@@ -24000,10 +25930,10 @@ Examples:
24000
25930
  const chunks = [];
24001
25931
  for (const file of files) {
24002
25932
  if (isBinary(file)) continue;
24003
- const fullPath = path27.join(absPath, file);
25933
+ const fullPath = path28.join(absPath, file);
24004
25934
  try {
24005
- const stat2 = await fs30.stat(fullPath);
24006
- const content = await fs30.readFile(fullPath, "utf-8");
25935
+ const stat2 = await fs31.stat(fullPath);
25936
+ const content = await fs31.readFile(fullPath, "utf-8");
24007
25937
  if (content.length > 1e5) continue;
24008
25938
  const fileChunks = chunkContent(content, DEFAULT_CHUNK_SIZE);
24009
25939
  for (const chunk of fileChunks) {
@@ -24062,12 +25992,12 @@ Examples:
24062
25992
  }
24063
25993
  });
24064
25994
  var semanticSearchTools = [semanticSearchTool];
24065
- var fs31 = await import('fs/promises');
24066
- var path28 = await import('path');
24067
- var { glob: glob8 } = await import('glob');
25995
+ var fs32 = await import('fs/promises');
25996
+ var path29 = await import('path');
25997
+ var { glob: glob14 } = await import('glob');
24068
25998
  async function parseClassRelationships(rootPath, include) {
24069
25999
  const pattern = include ?? "**/*.{ts,tsx,js,jsx}";
24070
- const files = await glob8(pattern, {
26000
+ const files = await glob14(pattern, {
24071
26001
  cwd: rootPath,
24072
26002
  ignore: ["**/node_modules/**", "**/dist/**", "**/*.test.*", "**/*.d.ts"],
24073
26003
  nodir: true
@@ -24076,7 +26006,7 @@ async function parseClassRelationships(rootPath, include) {
24076
26006
  const interfaces = [];
24077
26007
  for (const file of files.slice(0, 100)) {
24078
26008
  try {
24079
- const content = await fs31.readFile(path28.join(rootPath, file), "utf-8");
26009
+ const content = await fs32.readFile(path29.join(rootPath, file), "utf-8");
24080
26010
  const lines = content.split("\n");
24081
26011
  for (let i = 0; i < lines.length; i++) {
24082
26012
  const line = lines[i];
@@ -24195,14 +26125,14 @@ async function generateClassDiagram(rootPath, include) {
24195
26125
  };
24196
26126
  }
24197
26127
  async function generateArchitectureDiagram(rootPath) {
24198
- const entries = await fs31.readdir(rootPath, { withFileTypes: true });
26128
+ const entries = await fs32.readdir(rootPath, { withFileTypes: true });
24199
26129
  const dirs = entries.filter(
24200
26130
  (e) => e.isDirectory() && !e.name.startsWith(".") && !["node_modules", "dist", "build", "coverage", "__pycache__", "target"].includes(e.name)
24201
26131
  );
24202
26132
  const lines = ["graph TD"];
24203
26133
  let nodeCount = 0;
24204
26134
  let edgeCount = 0;
24205
- const rootName = path28.basename(rootPath);
26135
+ const rootName = path29.basename(rootPath);
24206
26136
  lines.push(` ROOT["${rootName}"]`);
24207
26137
  nodeCount++;
24208
26138
  for (const dir of dirs) {
@@ -24212,7 +26142,7 @@ async function generateArchitectureDiagram(rootPath) {
24212
26142
  nodeCount++;
24213
26143
  edgeCount++;
24214
26144
  try {
24215
- const subEntries = await fs31.readdir(path28.join(rootPath, dir.name), {
26145
+ const subEntries = await fs32.readdir(path29.join(rootPath, dir.name), {
24216
26146
  withFileTypes: true
24217
26147
  });
24218
26148
  const subDirs = subEntries.filter(
@@ -24335,7 +26265,7 @@ Examples:
24335
26265
  tool: "generate_diagram"
24336
26266
  });
24337
26267
  }
24338
- const absPath = rootPath ? path28.resolve(rootPath) : process.cwd();
26268
+ const absPath = rootPath ? path29.resolve(rootPath) : process.cwd();
24339
26269
  switch (type) {
24340
26270
  case "class":
24341
26271
  return generateClassDiagram(absPath, include);
@@ -24396,8 +26326,8 @@ Examples:
24396
26326
  }
24397
26327
  });
24398
26328
  var diagramTools = [generateDiagramTool];
24399
- var fs32 = await import('fs/promises');
24400
- var path29 = await import('path');
26329
+ var fs33 = await import('fs/promises');
26330
+ var path30 = await import('path');
24401
26331
  var DEFAULT_MAX_PAGES = 20;
24402
26332
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
24403
26333
  function parsePageRange(rangeStr, totalPages) {
@@ -24432,9 +26362,9 @@ Examples:
24432
26362
  }),
24433
26363
  async execute({ path: filePath, pages, maxPages }) {
24434
26364
  const startTime = performance.now();
24435
- const absPath = path29.resolve(filePath);
26365
+ const absPath = path30.resolve(filePath);
24436
26366
  try {
24437
- const stat2 = await fs32.stat(absPath);
26367
+ const stat2 = await fs33.stat(absPath);
24438
26368
  if (!stat2.isFile()) {
24439
26369
  throw new ToolError(`Path is not a file: ${absPath}`, {
24440
26370
  tool: "read_pdf"
@@ -24465,7 +26395,7 @@ Examples:
24465
26395
  }
24466
26396
  try {
24467
26397
  const pdfParse = await import('pdf-parse');
24468
- const dataBuffer = await fs32.readFile(absPath);
26398
+ const dataBuffer = await fs33.readFile(absPath);
24469
26399
  const pdfData = await pdfParse.default(dataBuffer, {
24470
26400
  max: maxPages
24471
26401
  });
@@ -24511,8 +26441,8 @@ Examples:
24511
26441
  }
24512
26442
  });
24513
26443
  var pdfTools = [readPdfTool];
24514
- var fs33 = await import('fs/promises');
24515
- var path30 = await import('path');
26444
+ var fs34 = await import('fs/promises');
26445
+ var path31 = await import('path');
24516
26446
  var SUPPORTED_FORMATS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
24517
26447
  var MAX_IMAGE_SIZE = 20 * 1024 * 1024;
24518
26448
  var MIME_TYPES = {
@@ -24540,8 +26470,8 @@ Examples:
24540
26470
  async execute({ path: filePath, prompt, provider }) {
24541
26471
  const startTime = performance.now();
24542
26472
  const effectivePrompt = prompt ?? "Describe this image in detail. If it's code or a UI, identify the key elements.";
24543
- const absPath = path30.resolve(filePath);
24544
- const ext = path30.extname(absPath).toLowerCase();
26473
+ const absPath = path31.resolve(filePath);
26474
+ const ext = path31.extname(absPath).toLowerCase();
24545
26475
  if (!SUPPORTED_FORMATS.has(ext)) {
24546
26476
  throw new ToolError(
24547
26477
  `Unsupported image format '${ext}'. Supported: ${Array.from(SUPPORTED_FORMATS).join(", ")}`,
@@ -24549,7 +26479,7 @@ Examples:
24549
26479
  );
24550
26480
  }
24551
26481
  try {
24552
- const stat2 = await fs33.stat(absPath);
26482
+ const stat2 = await fs34.stat(absPath);
24553
26483
  if (!stat2.isFile()) {
24554
26484
  throw new ToolError(`Path is not a file: ${absPath}`, {
24555
26485
  tool: "read_image"
@@ -24570,7 +26500,7 @@ Examples:
24570
26500
  if (error instanceof ToolError) throw error;
24571
26501
  throw error;
24572
26502
  }
24573
- const imageBuffer = await fs33.readFile(absPath);
26503
+ const imageBuffer = await fs34.readFile(absPath);
24574
26504
  const base64 = imageBuffer.toString("base64");
24575
26505
  const mimeType = MIME_TYPES[ext] ?? "image/png";
24576
26506
  const selectedProvider = provider ?? "anthropic";
@@ -24683,7 +26613,7 @@ Examples:
24683
26613
  }
24684
26614
  });
24685
26615
  var imageTools = [readImageTool];
24686
- var path31 = await import('path');
26616
+ var path32 = await import('path');
24687
26617
  var DANGEROUS_PATTERNS2 = [
24688
26618
  /\bDROP\s+(?:TABLE|DATABASE|INDEX|VIEW)\b/i,
24689
26619
  /\bTRUNCATE\b/i,
@@ -24714,7 +26644,7 @@ Examples:
24714
26644
  async execute({ database, query, params, readonly: isReadonlyParam }) {
24715
26645
  const isReadonly = isReadonlyParam ?? true;
24716
26646
  const startTime = performance.now();
24717
- const absPath = path31.resolve(database);
26647
+ const absPath = path32.resolve(database);
24718
26648
  if (isReadonly && isDangerousSql(query)) {
24719
26649
  throw new ToolError(
24720
26650
  "Write operations (INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE) are blocked in readonly mode. Set readonly: false to allow writes.",
@@ -24787,7 +26717,7 @@ Examples:
24787
26717
  }),
24788
26718
  async execute({ database, table }) {
24789
26719
  const startTime = performance.now();
24790
- const absPath = path31.resolve(database);
26720
+ const absPath = path32.resolve(database);
24791
26721
  try {
24792
26722
  const { default: Database } = await import('better-sqlite3');
24793
26723
  const db = new Database(absPath, { readonly: true, fileMustExist: true });
@@ -24958,14 +26888,14 @@ var findMissingImportsTool = defineTool({
24958
26888
  }
24959
26889
  });
24960
26890
  var astValidatorTools = [validateCodeTool, findMissingImportsTool];
24961
- var fs34 = await import('fs/promises');
24962
- var path32 = await import('path');
26891
+ var fs35 = await import('fs/promises');
26892
+ var path33 = await import('path');
24963
26893
  var AnalyzeFileSchema = z.object({
24964
26894
  filePath: z.string().describe("Path to file to analyze"),
24965
26895
  includeAst: z.boolean().default(false).describe("Include AST in result")
24966
26896
  });
24967
26897
  async function analyzeFile(filePath, includeAst = false) {
24968
- const content = await fs34.readFile(filePath, "utf-8");
26898
+ const content = await fs35.readFile(filePath, "utf-8");
24969
26899
  const lines = content.split("\n").length;
24970
26900
  const functions = [];
24971
26901
  const classes = [];
@@ -25056,8 +26986,8 @@ async function analyzeFile(filePath, includeAst = false) {
25056
26986
  };
25057
26987
  }
25058
26988
  async function analyzeDirectory(dirPath) {
25059
- const { glob: glob9 } = await import('glob');
25060
- const files = await glob9("**/*.{ts,tsx,js,jsx}", {
26989
+ const { glob: glob15 } = await import('glob');
26990
+ const files = await glob15("**/*.{ts,tsx,js,jsx}", {
25061
26991
  cwd: dirPath,
25062
26992
  ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
25063
26993
  absolute: true
@@ -25069,10 +26999,10 @@ async function analyzeDirectory(dirPath) {
25069
26999
  try {
25070
27000
  const analysis = await analyzeFile(file, false);
25071
27001
  totalLines += analysis.lines;
25072
- const ext = path32.extname(file);
27002
+ const ext = path33.extname(file);
25073
27003
  filesByType[ext] = (filesByType[ext] || 0) + 1;
25074
27004
  fileStats.push({
25075
- file: path32.relative(dirPath, file),
27005
+ file: path33.relative(dirPath, file),
25076
27006
  lines: analysis.lines,
25077
27007
  complexity: analysis.complexity.cyclomatic
25078
27008
  });
@@ -25132,8 +27062,8 @@ var codeAnalyzerTools = [analyzeFileTool, analyzeDirectoryTool];
25132
27062
  var AgentTaskQueue = class {
25133
27063
  tasks = /* @__PURE__ */ new Map();
25134
27064
  completionOrder = [];
25135
- addTask(task) {
25136
- const id = `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
27065
+ addTask(task, idOverride) {
27066
+ const id = idOverride ?? `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
25137
27067
  this.tasks.set(id, {
25138
27068
  ...task,
25139
27069
  id,
@@ -25179,10 +27109,20 @@ var AgentTaskQueue = class {
25179
27109
  };
25180
27110
  function planExecution(tasks, strategy) {
25181
27111
  const taskGraph = /* @__PURE__ */ new Map();
27112
+ const unresolvedDependencies = [];
25182
27113
  tasks.forEach((task, idx) => {
25183
- const id = `task-${idx}`;
27114
+ const id = task.id ?? `task-${idx}`;
25184
27115
  taskGraph.set(id, task.dependencies || []);
25185
27116
  });
27117
+ const taskIds = new Set(taskGraph.keys());
27118
+ for (const [id, deps] of taskGraph) {
27119
+ const filtered = deps.filter((dep) => {
27120
+ if (taskIds.has(dep)) return true;
27121
+ unresolvedDependencies.push({ taskId: id, dependency: dep });
27122
+ return false;
27123
+ });
27124
+ taskGraph.set(id, filtered);
27125
+ }
25186
27126
  let plan = [];
25187
27127
  let estimatedTime = 0;
25188
27128
  let maxParallelism = 1;
@@ -25200,7 +27140,11 @@ function planExecution(tasks, strategy) {
25200
27140
  break;
25201
27141
  }
25202
27142
  case "priority-based": {
25203
- const sorted = tasks.map((t, idx) => ({ id: `task-${idx}`, len: t.description.length })).sort((a, b) => a.len - b.len);
27143
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
27144
+ const sorted = tasks.map((t, idx) => ({
27145
+ id: t.id ?? `task-${idx}`,
27146
+ priority: t.priority ?? "medium"
27147
+ })).sort((a, b) => (priorityOrder[a.priority] ?? 1) - (priorityOrder[b.priority] ?? 1));
25204
27148
  plan = sorted.map((t) => t.id);
25205
27149
  estimatedTime = tasks.length * 80;
25206
27150
  maxParallelism = 3;
@@ -25229,7 +27173,7 @@ function planExecution(tasks, strategy) {
25229
27173
  break;
25230
27174
  }
25231
27175
  }
25232
- return { plan, estimatedTime, maxParallelism };
27176
+ return { plan, estimatedTime, maxParallelism, unresolvedDependencies };
25233
27177
  }
25234
27178
  var createAgentPlanTool = defineTool({
25235
27179
  name: "createAgentPlan",
@@ -25248,15 +27192,35 @@ var createAgentPlanTool = defineTool({
25248
27192
  async execute(input) {
25249
27193
  const typedInput = input;
25250
27194
  const queue = new AgentTaskQueue();
25251
- for (const task of typedInput.tasks) {
25252
- queue.addTask({
25253
- description: task.description,
25254
- priority: task.priority,
25255
- estimatedDuration: 100,
25256
- dependencies: task.dependencies
25257
- });
27195
+ const indexIdMap = /* @__PURE__ */ new Map();
27196
+ for (let idx = 0; idx < typedInput.tasks.length; idx++) {
27197
+ indexIdMap.set(idx, `task-${idx}`);
27198
+ }
27199
+ const normalizeDependency = (dep) => {
27200
+ if (indexIdMap.has(Number(dep))) {
27201
+ return indexIdMap.get(Number(dep));
27202
+ }
27203
+ return dep;
27204
+ };
27205
+ for (const [idx, task] of typedInput.tasks.entries()) {
27206
+ const id = indexIdMap.get(idx);
27207
+ const normalizedDependencies = (task.dependencies || []).map(normalizeDependency);
27208
+ queue.addTask(
27209
+ {
27210
+ description: task.description,
27211
+ priority: task.priority,
27212
+ estimatedDuration: 100,
27213
+ dependencies: normalizedDependencies
27214
+ },
27215
+ id
27216
+ );
25258
27217
  }
25259
- const executionPlan = planExecution(typedInput.tasks, typedInput.strategy);
27218
+ const plannedTasks = typedInput.tasks.map((task, idx) => ({
27219
+ id: indexIdMap.get(idx),
27220
+ description: task.description,
27221
+ dependencies: (task.dependencies || []).map(normalizeDependency)
27222
+ }));
27223
+ const executionPlan = planExecution(plannedTasks, typedInput.strategy);
25260
27224
  return {
25261
27225
  planId: `plan-${Date.now()}`,
25262
27226
  strategy: typedInput.strategy,
@@ -25264,6 +27228,7 @@ var createAgentPlanTool = defineTool({
25264
27228
  executionOrder: executionPlan.plan,
25265
27229
  estimatedTime: executionPlan.estimatedTime,
25266
27230
  maxParallelism: executionPlan.maxParallelism,
27231
+ unresolvedDependencies: executionPlan.unresolvedDependencies,
25267
27232
  tasks: queue.getTasks().map((t) => ({
25268
27233
  id: t.id,
25269
27234
  description: t.description,
@@ -25276,30 +27241,59 @@ var createAgentPlanTool = defineTool({
25276
27241
  });
25277
27242
  var delegateTaskTool = defineTool({
25278
27243
  name: "delegateTask",
25279
- description: "Delegate a task to a virtual sub-agent (simulated for demonstration)",
27244
+ description: "Delegate a task to a specialized sub-agent with real LLM tool-use execution.",
25280
27245
  category: "build",
25281
27246
  parameters: z.object({
25282
27247
  taskId: z.string(),
27248
+ task: z.string().describe("Description of the task for the agent to execute"),
25283
27249
  agentRole: z.enum(["researcher", "coder", "reviewer", "tester", "optimizer"]).default("coder"),
25284
- context: z.string().optional()
27250
+ context: z.string().optional(),
27251
+ maxTurns: z.number().default(10)
25285
27252
  }),
25286
27253
  async execute(input) {
25287
27254
  const typedInput = input;
27255
+ const provider = getAgentProvider();
27256
+ const toolRegistry = getAgentToolRegistry();
27257
+ if (!provider || !toolRegistry) {
27258
+ return {
27259
+ agentId: `agent-${Date.now()}-${typedInput.agentRole}`,
27260
+ taskId: typedInput.taskId,
27261
+ role: typedInput.agentRole,
27262
+ status: "unavailable",
27263
+ message: "Agent provider not initialized. Call setAgentProvider() during orchestrator startup.",
27264
+ success: false
27265
+ };
27266
+ }
25288
27267
  const agentId = `agent-${Date.now()}-${typedInput.agentRole}`;
27268
+ const executor = new AgentExecutor(provider, toolRegistry);
27269
+ const roleDef = AGENT_ROLES[typedInput.agentRole];
27270
+ if (!roleDef) {
27271
+ return {
27272
+ agentId,
27273
+ taskId: typedInput.taskId,
27274
+ role: typedInput.agentRole,
27275
+ status: "error",
27276
+ message: `Unknown agent role: ${typedInput.agentRole}`,
27277
+ success: false
27278
+ };
27279
+ }
27280
+ const agentDef = { ...roleDef, maxTurns: typedInput.maxTurns };
27281
+ const result = await executor.execute(agentDef, {
27282
+ id: typedInput.taskId,
27283
+ description: typedInput.task,
27284
+ context: typedInput.context ? { userContext: typedInput.context } : void 0
27285
+ });
25289
27286
  return {
25290
27287
  agentId,
25291
27288
  taskId: typedInput.taskId,
25292
27289
  role: typedInput.agentRole,
25293
- status: "simulated",
25294
- message: `Task delegated to ${typedInput.agentRole} agent`,
25295
- capabilities: {
25296
- researcher: ["web search", "document analysis", "information synthesis"],
25297
- coder: ["code generation", "refactoring", "debugging"],
25298
- reviewer: ["code review", "security analysis", "best practices"],
25299
- tester: ["test generation", "coverage analysis", "bug detection"],
25300
- optimizer: ["performance tuning", "complexity reduction", "resource optimization"]
25301
- }[typedInput.agentRole],
25302
- note: "This is a simulated agent. Full implementation requires provider integration."
27290
+ status: result.success ? "completed" : "failed",
27291
+ output: result.output,
27292
+ success: result.success,
27293
+ turns: result.turns,
27294
+ toolsUsed: result.toolsUsed,
27295
+ tokensUsed: result.tokensUsed,
27296
+ duration: result.duration
25303
27297
  };
25304
27298
  }
25305
27299
  });
@@ -25335,10 +27329,14 @@ var aggregateResultsTool = defineTool({
25335
27329
  const winner = Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0];
25336
27330
  aggregatedOutput = winner ? winner[0] : "";
25337
27331
  break;
25338
- case "best":
25339
- const longest = completed.sort((a, b) => b.output.length - a.output.length)[0];
25340
- aggregatedOutput = longest ? longest.output : "";
27332
+ case "best": {
27333
+ const successRate = typedInput.results.length > 0 ? completed.length / typedInput.results.length : 0;
27334
+ const bestResult = completed.length > 0 ? completed[0] : void 0;
27335
+ aggregatedOutput = bestResult ? `[Success rate: ${Math.round(successRate * 100)}%]
27336
+
27337
+ ${bestResult.output}` : "";
25341
27338
  break;
27339
+ }
25342
27340
  case "summary":
25343
27341
  aggregatedOutput = `Completed: ${completed.length}, Failed: ${failed.length}
25344
27342
 
@@ -25360,13 +27358,13 @@ ${completed.map((r) => `- ${r.agentId}: Success`).join("\n")}`;
25360
27358
  }
25361
27359
  });
25362
27360
  var agentCoordinatorTools = [createAgentPlanTool, delegateTaskTool, aggregateResultsTool];
25363
- var fs35 = await import('fs/promises');
27361
+ var fs36 = await import('fs/promises');
25364
27362
  var SuggestImprovementsSchema = z.object({
25365
27363
  filePath: z.string().describe("File to analyze for improvement suggestions"),
25366
27364
  context: z.string().optional().describe("Additional context about the code")
25367
27365
  });
25368
27366
  async function analyzeAndSuggest(filePath, _context) {
25369
- const content = await fs35.readFile(filePath, "utf-8");
27367
+ const content = await fs36.readFile(filePath, "utf-8");
25370
27368
  const lines = content.split("\n");
25371
27369
  const suggestions = [];
25372
27370
  for (let i = 0; i < lines.length; i++) {
@@ -25412,7 +27410,8 @@ async function analyzeAndSuggest(filePath, _context) {
25412
27410
  reasoning: "'any' bypasses all type checking and can hide bugs"
25413
27411
  });
25414
27412
  }
25415
- if (line.trim() === "catch (error) {" || line.trim() === "catch (e) {") {
27413
+ const trimmedLine = line.trim();
27414
+ if (trimmedLine.endsWith("catch (error) {") || trimmedLine.endsWith("catch (e) {") || trimmedLine === "catch (error) {" || trimmedLine === "catch (e) {") {
25416
27415
  const nextLine = lines[i + 1];
25417
27416
  if (nextLine && nextLine.trim() === "}") {
25418
27417
  suggestions.push({
@@ -25457,7 +27456,7 @@ async function analyzeAndSuggest(filePath, _context) {
25457
27456
  if (filePath.endsWith(".ts") && !filePath.includes("test") && !filePath.includes(".d.ts") && line.includes("export ")) {
25458
27457
  const testPath = filePath.replace(".ts", ".test.ts");
25459
27458
  try {
25460
- await fs35.access(testPath);
27459
+ await fs36.access(testPath);
25461
27460
  } catch {
25462
27461
  suggestions.push({
25463
27462
  type: "testing",
@@ -25514,7 +27513,7 @@ var calculateCodeScoreTool = defineTool({
25514
27513
  async execute(input) {
25515
27514
  const { filePath } = input;
25516
27515
  const suggestions = await analyzeAndSuggest(filePath);
25517
- const content = await fs35.readFile(filePath, "utf-8");
27516
+ const content = await fs36.readFile(filePath, "utf-8");
25518
27517
  const lines = content.split("\n");
25519
27518
  const nonEmptyLines = lines.filter((l) => l.trim()).length;
25520
27519
  let score = 100;
@@ -25548,8 +27547,8 @@ var calculateCodeScoreTool = defineTool({
25548
27547
  }
25549
27548
  });
25550
27549
  var smartSuggestionsTools = [suggestImprovementsTool, calculateCodeScoreTool];
25551
- var fs36 = await import('fs/promises');
25552
- var path33 = await import('path');
27550
+ var fs37 = await import('fs/promises');
27551
+ var path34 = await import('path');
25553
27552
  var ContextMemoryStore = class {
25554
27553
  items = /* @__PURE__ */ new Map();
25555
27554
  learnings = /* @__PURE__ */ new Map();
@@ -25561,7 +27560,7 @@ var ContextMemoryStore = class {
25561
27560
  }
25562
27561
  async load() {
25563
27562
  try {
25564
- const content = await fs36.readFile(this.storePath, "utf-8");
27563
+ const content = await fs37.readFile(this.storePath, "utf-8");
25565
27564
  const data = JSON.parse(content);
25566
27565
  this.items = new Map(Object.entries(data.items || {}));
25567
27566
  this.learnings = new Map(Object.entries(data.learnings || {}));
@@ -25569,15 +27568,15 @@ var ContextMemoryStore = class {
25569
27568
  }
25570
27569
  }
25571
27570
  async save() {
25572
- const dir = path33.dirname(this.storePath);
25573
- await fs36.mkdir(dir, { recursive: true });
27571
+ const dir = path34.dirname(this.storePath);
27572
+ await fs37.mkdir(dir, { recursive: true });
25574
27573
  const data = {
25575
27574
  sessionId: this.sessionId,
25576
27575
  items: Object.fromEntries(this.items),
25577
27576
  learnings: Object.fromEntries(this.learnings),
25578
27577
  savedAt: Date.now()
25579
27578
  };
25580
- await fs36.writeFile(this.storePath, JSON.stringify(data, null, 2));
27579
+ await fs37.writeFile(this.storePath, JSON.stringify(data, null, 2));
25581
27580
  }
25582
27581
  addContext(id, item) {
25583
27582
  this.items.set(id, item);
@@ -25742,11 +27741,11 @@ var contextEnhancerTools = [
25742
27741
  recordLearningTool,
25743
27742
  getLearnedPatternsTool
25744
27743
  ];
25745
- var fs37 = await import('fs/promises');
25746
- var path34 = await import('path');
27744
+ var fs38 = await import('fs/promises');
27745
+ var path35 = await import('path');
25747
27746
  async function discoverSkills(skillsDir) {
25748
27747
  try {
25749
- const files = await fs37.readdir(skillsDir);
27748
+ const files = await fs38.readdir(skillsDir);
25750
27749
  return files.filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
25751
27750
  } catch {
25752
27751
  return [];
@@ -25754,12 +27753,12 @@ async function discoverSkills(skillsDir) {
25754
27753
  }
25755
27754
  async function loadSkillMetadata(skillPath) {
25756
27755
  try {
25757
- const content = await fs37.readFile(skillPath, "utf-8");
27756
+ const content = await fs38.readFile(skillPath, "utf-8");
25758
27757
  const nameMatch = content.match(/@name\s+(\S+)/);
25759
27758
  const descMatch = content.match(/@description\s+(.+)/);
25760
27759
  const versionMatch = content.match(/@version\s+(\S+)/);
25761
27760
  return {
25762
- name: nameMatch?.[1] || path34.basename(skillPath, path34.extname(skillPath)),
27761
+ name: nameMatch?.[1] || path35.basename(skillPath, path35.extname(skillPath)),
25763
27762
  description: descMatch?.[1] || "No description",
25764
27763
  version: versionMatch?.[1] || "1.0.0",
25765
27764
  dependencies: []
@@ -25803,7 +27802,7 @@ var discoverSkillsTool = defineTool({
25803
27802
  const { skillsDir } = input;
25804
27803
  const skills = await discoverSkills(skillsDir);
25805
27804
  const metadata = await Promise.all(
25806
- skills.map((s) => loadSkillMetadata(path34.join(skillsDir, s)))
27805
+ skills.map((s) => loadSkillMetadata(path35.join(skillsDir, s)))
25807
27806
  );
25808
27807
  return {
25809
27808
  skillsDir,
@@ -26532,6 +28531,24 @@ var pc = chalk12;
26532
28531
  });
26533
28532
 
26534
28533
  // src/cli/repl/index.ts
28534
+ function visualWidth(str) {
28535
+ const stripped = str.replace(/\x1b\[[0-9;]*m/g, "");
28536
+ let width = 0;
28537
+ for (const char of stripped) {
28538
+ const cp = char.codePointAt(0) || 0;
28539
+ if (cp > 128512 || cp >= 9728 && cp <= 10175 || cp >= 127744 && cp <= 129750 || cp >= 129280 && cp <= 129535 || cp >= 9986 && cp <= 10160 || cp >= 65024 && cp <= 65039 || cp === 8205) {
28540
+ width += 2;
28541
+ } else if (
28542
+ // CJK Unified Ideographs and other wide characters
28543
+ cp >= 4352 && cp <= 4447 || cp >= 11904 && cp <= 12350 || cp >= 12352 && cp <= 13247 || cp >= 19968 && cp <= 40959 || cp >= 63744 && cp <= 64255 || cp >= 65072 && cp <= 65135 || cp >= 65281 && cp <= 65376 || cp >= 65504 && cp <= 65510
28544
+ ) {
28545
+ width += 2;
28546
+ } else {
28547
+ width += 1;
28548
+ }
28549
+ }
28550
+ return width;
28551
+ }
26535
28552
  async function startRepl(options = {}) {
26536
28553
  const projectPath = options.projectPath ?? process.cwd();
26537
28554
  const session = createSession(projectPath, options.config);
@@ -26576,6 +28593,8 @@ async function startRepl(options = {}) {
26576
28593
  }
26577
28594
  await loadCocoModePreference();
26578
28595
  const toolRegistry = createFullToolRegistry();
28596
+ setAgentProvider(provider);
28597
+ setAgentToolRegistry(toolRegistry);
26579
28598
  const inputHandler = createInputHandler();
26580
28599
  const intentRecognizer = createIntentRecognizer();
26581
28600
  await printWelcome(session);
@@ -26796,23 +28815,39 @@ async function startRepl(options = {}) {
26796
28815
  inputHandler.close();
26797
28816
  }
26798
28817
  async function printWelcome(session) {
28818
+ const providerType = session.config.provider.type;
28819
+ const model = session.config.provider.model || "default";
28820
+ const isReturningUser = existsSync(path20__default.join(session.projectPath, ".coco"));
28821
+ if (isReturningUser) {
28822
+ const versionStr = chalk12.dim(`v${VERSION}`);
28823
+ const providerStr = chalk12.dim(`${providerType}/${model}`);
28824
+ console.log(
28825
+ `
28826
+ \u{1F965} Coco ${versionStr} ${chalk12.dim("\u2502")} ${providerStr} ${chalk12.dim("\u2502")} ${chalk12.yellow("/help")}
28827
+ `
28828
+ );
28829
+ return;
28830
+ }
26799
28831
  const trustStore = createTrustStore();
26800
28832
  await trustStore.init();
26801
28833
  const trustLevel = trustStore.getLevel(session.projectPath);
26802
28834
  const boxWidth = 41;
26803
28835
  const innerWidth = boxWidth - 4;
26804
- const titleText = "CORBAT-COCO";
28836
+ const titleContent = "\u{1F965} CORBAT-COCO";
26805
28837
  const versionText = `v${VERSION}`;
26806
- const titlePadding = innerWidth - titleText.length - versionText.length - 2;
28838
+ const titleContentVisualWidth = visualWidth(titleContent);
28839
+ const versionVisualWidth = visualWidth(versionText);
28840
+ const titlePadding = innerWidth - titleContentVisualWidth - versionVisualWidth;
26807
28841
  const subtitleText = "open source \u2022 corbat.tech";
26808
- const subtitlePadding = innerWidth - subtitleText.length;
28842
+ const subtitleVisualWidth = visualWidth(subtitleText);
28843
+ const subtitlePadding = innerWidth - subtitleVisualWidth;
26809
28844
  console.log();
26810
28845
  console.log(chalk12.magenta(" \u256D" + "\u2500".repeat(boxWidth - 2) + "\u256E"));
26811
28846
  console.log(
26812
- chalk12.magenta(" \u2502 ") + "\u{1F965} " + chalk12.bold.white(titleText) + " ".repeat(titlePadding) + chalk12.dim(versionText) + chalk12.magenta(" \u2502")
28847
+ chalk12.magenta(" \u2502 ") + "\u{1F965} " + chalk12.bold.white("CORBAT-COCO") + " ".repeat(Math.max(0, titlePadding)) + chalk12.dim(versionText) + chalk12.magenta(" \u2502")
26813
28848
  );
26814
28849
  console.log(
26815
- chalk12.magenta(" \u2502 ") + chalk12.dim(subtitleText) + " ".repeat(subtitlePadding) + chalk12.magenta(" \u2502")
28850
+ chalk12.magenta(" \u2502 ") + chalk12.dim(subtitleText) + " ".repeat(Math.max(0, subtitlePadding)) + chalk12.magenta(" \u2502")
26816
28851
  );
26817
28852
  console.log(chalk12.magenta(" \u2570" + "\u2500".repeat(boxWidth - 2) + "\u256F"));
26818
28853
  const updateInfo = await checkForUpdates();
@@ -27067,7 +29102,7 @@ async function main() {
27067
29102
  await program.parseAsync(process.argv);
27068
29103
  }
27069
29104
  main().catch((error) => {
27070
- console.error("Fatal error:", error);
29105
+ console.error(formatError(error));
27071
29106
  process.exit(1);
27072
29107
  });
27073
29108
  //# sourceMappingURL=index.js.map