@corbat-tech/coco 1.0.2 → 1.2.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,9 @@ 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"]
724
727
  });
725
728
  var PersistenceConfigSchema = z.object({
726
729
  checkpointInterval: z.number().min(6e4).default(3e5),
@@ -976,6 +979,35 @@ var TimeoutError = class extends CocoError {
976
979
  this.operation = options.operation;
977
980
  }
978
981
  };
982
+ var ERROR_SUGGESTIONS = {
983
+ PROVIDER_ERROR: "Check your API key and provider configuration. Run 'coco setup' to reconfigure.",
984
+ CONFIG_ERROR: "Check your .coco/config.json or run 'coco setup' to reconfigure.",
985
+ FILESYSTEM_ERROR: "Check that the path exists and you have read/write permissions.",
986
+ VALIDATION_ERROR: "Check the input data format. See 'coco --help' for usage.",
987
+ PHASE_ERROR: "Phase execution failed. Try 'coco resume' to continue from the last checkpoint.",
988
+ TASK_ERROR: "Task execution failed. The task can be retried from the last checkpoint with 'coco resume'.",
989
+ QUALITY_ERROR: "Quality score below threshold. Review the issues listed above and iterate on the code.",
990
+ RECOVERY_ERROR: "Checkpoint may be corrupted. Try 'coco init --force' to start fresh.",
991
+ TOOL_ERROR: "A tool execution failed. Check the error details above and retry.",
992
+ TIMEOUT_ERROR: "Operation timed out. Try increasing the timeout in config or simplifying the request.",
993
+ UNEXPECTED_ERROR: "An unexpected error occurred. Please report at github.com/corbat/corbat-coco/issues."
994
+ };
995
+ function formatError(error) {
996
+ if (error instanceof CocoError) {
997
+ let message = `[${error.code}] ${error.message}`;
998
+ const suggestion = error.suggestion ?? ERROR_SUGGESTIONS[error.code];
999
+ if (suggestion) {
1000
+ message += `
1001
+ Suggestion: ${suggestion}`;
1002
+ }
1003
+ return message;
1004
+ }
1005
+ if (error instanceof Error) {
1006
+ return `${error.message}
1007
+ Suggestion: ${ERROR_SUGGESTIONS["UNEXPECTED_ERROR"]}`;
1008
+ }
1009
+ return String(error);
1010
+ }
979
1011
 
980
1012
  // src/config/loader.ts
981
1013
  init_paths();
@@ -1037,7 +1069,17 @@ function deepMergeConfig(base, override) {
1037
1069
  project: { ...base.project, ...override.project },
1038
1070
  provider: { ...base.provider, ...override.provider },
1039
1071
  quality: { ...base.quality, ...override.quality },
1040
- persistence: { ...base.persistence, ...override.persistence }
1072
+ persistence: { ...base.persistence, ...override.persistence },
1073
+ // Merge optional sections only if present in either base or override
1074
+ ...base.stack || override.stack ? { stack: { ...base.stack, ...override.stack } } : {},
1075
+ ...base.integrations || override.integrations ? {
1076
+ integrations: {
1077
+ ...base.integrations,
1078
+ ...override.integrations
1079
+ }
1080
+ } : {},
1081
+ ...base.mcp || override.mcp ? { mcp: { ...base.mcp, ...override.mcp } } : {},
1082
+ ...base.tools || override.tools ? { tools: { ...base.tools, ...override.tools } } : {}
1041
1083
  };
1042
1084
  }
1043
1085
  function getProjectConfigPath() {
@@ -4743,6 +4785,9 @@ var AnthropicProvider = class {
4743
4785
  try {
4744
4786
  currentToolCall.input = currentToolInputJson ? JSON.parse(currentToolInputJson) : {};
4745
4787
  } catch {
4788
+ console.warn(
4789
+ `[Anthropic] Failed to parse tool call arguments: ${currentToolInputJson?.slice(0, 100)}`
4790
+ );
4746
4791
  currentToolCall.input = {};
4747
4792
  }
4748
4793
  yield {
@@ -5275,6 +5320,9 @@ var OpenAIProvider = class {
5275
5320
  try {
5276
5321
  input = builder.arguments ? JSON.parse(builder.arguments) : {};
5277
5322
  } catch {
5323
+ console.warn(
5324
+ `[OpenAI] Failed to parse tool call arguments: ${builder.arguments?.slice(0, 100)}`
5325
+ );
5278
5326
  }
5279
5327
  yield {
5280
5328
  type: "tool_use_end",
@@ -5563,7 +5611,16 @@ var OpenAIProvider = class {
5563
5611
  ).map((tc) => ({
5564
5612
  id: tc.id,
5565
5613
  name: tc.function.name,
5566
- input: JSON.parse(tc.function.arguments || "{}")
5614
+ input: (() => {
5615
+ try {
5616
+ return JSON.parse(tc.function.arguments || "{}");
5617
+ } catch {
5618
+ console.warn(
5619
+ `[OpenAI] Failed to parse tool call arguments: ${tc.function.arguments?.slice(0, 100)}`
5620
+ );
5621
+ return {};
5622
+ }
5623
+ })()
5567
5624
  }));
5568
5625
  }
5569
5626
  /**
@@ -7211,7 +7268,10 @@ var GeminiProvider = class {
7211
7268
  for (const part of candidate.content.parts) {
7212
7269
  if ("functionCall" in part && part.functionCall) {
7213
7270
  const funcCall = part.functionCall;
7214
- const callKey = `${funcCall.name}-${JSON.stringify(funcCall.args)}`;
7271
+ const sortedArgs = funcCall.args ? Object.keys(funcCall.args).sort().map(
7272
+ (k) => `${k}:${JSON.stringify(funcCall.args[k])}`
7273
+ ).join(",") : "";
7274
+ const callKey = `${funcCall.name}-${sortedArgs}`;
7215
7275
  if (!emittedToolCalls.has(callKey)) {
7216
7276
  emittedToolCalls.add(callKey);
7217
7277
  const toolCall = {
@@ -7243,9 +7303,18 @@ var GeminiProvider = class {
7243
7303
  }
7244
7304
  /**
7245
7305
  * Count tokens (approximate)
7306
+ *
7307
+ * Gemini uses a SentencePiece tokenizer. The average ratio varies:
7308
+ * - English text: ~4 characters per token
7309
+ * - Code: ~3.2 characters per token
7310
+ * - Mixed content: ~3.5 characters per token
7311
+ *
7312
+ * Using 3.5 as the default provides a better estimate for typical
7313
+ * coding agent workloads which mix code and natural language.
7246
7314
  */
7247
7315
  countTokens(text9) {
7248
- return Math.ceil(text9.length / 4);
7316
+ if (!text9) return 0;
7317
+ return Math.ceil(text9.length / 3.5);
7249
7318
  }
7250
7319
  /**
7251
7320
  * Get context window size
@@ -7579,35 +7648,35 @@ async function createCliPhaseContext(projectPath, _onUserInput) {
7579
7648
  },
7580
7649
  tools: {
7581
7650
  file: {
7582
- async read(path35) {
7583
- const fs38 = await import('fs/promises');
7584
- return fs38.readFile(path35, "utf-8");
7651
+ async read(path37) {
7652
+ const fs39 = await import('fs/promises');
7653
+ return fs39.readFile(path37, "utf-8");
7585
7654
  },
7586
- async write(path35, content) {
7587
- const fs38 = await import('fs/promises');
7655
+ async write(path37, content) {
7656
+ const fs39 = await import('fs/promises');
7588
7657
  const nodePath = await import('path');
7589
- await fs38.mkdir(nodePath.dirname(path35), { recursive: true });
7590
- await fs38.writeFile(path35, content, "utf-8");
7658
+ await fs39.mkdir(nodePath.dirname(path37), { recursive: true });
7659
+ await fs39.writeFile(path37, content, "utf-8");
7591
7660
  },
7592
- async exists(path35) {
7593
- const fs38 = await import('fs/promises');
7661
+ async exists(path37) {
7662
+ const fs39 = await import('fs/promises');
7594
7663
  try {
7595
- await fs38.access(path35);
7664
+ await fs39.access(path37);
7596
7665
  return true;
7597
7666
  } catch {
7598
7667
  return false;
7599
7668
  }
7600
7669
  },
7601
7670
  async glob(pattern) {
7602
- const { glob: glob9 } = await import('glob');
7603
- return glob9(pattern, { cwd: projectPath });
7671
+ const { glob: glob15 } = await import('glob');
7672
+ return glob15(pattern, { cwd: projectPath });
7604
7673
  }
7605
7674
  },
7606
7675
  bash: {
7607
7676
  async exec(command, options = {}) {
7608
- const { execa: execa8 } = await import('execa');
7677
+ const { execa: execa10 } = await import('execa');
7609
7678
  try {
7610
- const result = await execa8(command, {
7679
+ const result = await execa10(command, {
7611
7680
  shell: true,
7612
7681
  cwd: options.cwd || projectPath,
7613
7682
  timeout: options.timeout,
@@ -7831,16 +7900,16 @@ async function loadTasks(_options) {
7831
7900
  ];
7832
7901
  }
7833
7902
  async function checkProjectState() {
7834
- const fs38 = await import('fs/promises');
7903
+ const fs39 = await import('fs/promises');
7835
7904
  let hasProject = false;
7836
7905
  let hasPlan = false;
7837
7906
  try {
7838
- await fs38.access(".coco");
7907
+ await fs39.access(".coco");
7839
7908
  hasProject = true;
7840
7909
  } catch {
7841
7910
  }
7842
7911
  try {
7843
- await fs38.access(".coco/planning/backlog.json");
7912
+ await fs39.access(".coco/planning/backlog.json");
7844
7913
  hasPlan = true;
7845
7914
  } catch {
7846
7915
  }
@@ -7944,24 +8013,24 @@ function getPhaseStatusForPhase(phase) {
7944
8013
  return "in_progress";
7945
8014
  }
7946
8015
  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");
8016
+ const fs39 = await import('fs/promises');
8017
+ const path37 = await import('path');
8018
+ const statePath = path37.join(cwd, ".coco", "state.json");
8019
+ const backlogPath = path37.join(cwd, ".coco", "planning", "backlog.json");
8020
+ const checkpointDir = path37.join(cwd, ".coco", "checkpoints");
7952
8021
  let currentPhase = "idle";
7953
8022
  let metrics;
7954
8023
  let sprint;
7955
8024
  let checkpoints = [];
7956
8025
  try {
7957
- const stateContent = await fs38.readFile(statePath, "utf-8");
8026
+ const stateContent = await fs39.readFile(statePath, "utf-8");
7958
8027
  const stateData = JSON.parse(stateContent);
7959
8028
  currentPhase = stateData.currentPhase || "idle";
7960
8029
  metrics = stateData.metrics;
7961
8030
  } catch {
7962
8031
  }
7963
8032
  try {
7964
- const backlogContent = await fs38.readFile(backlogPath, "utf-8");
8033
+ const backlogContent = await fs39.readFile(backlogPath, "utf-8");
7965
8034
  const backlogData = JSON.parse(backlogContent);
7966
8035
  if (backlogData.currentSprint) {
7967
8036
  const tasks = backlogData.tasks || [];
@@ -7983,7 +8052,7 @@ async function loadProjectState(cwd, config) {
7983
8052
  } catch {
7984
8053
  }
7985
8054
  try {
7986
- const files = await fs38.readdir(checkpointDir);
8055
+ const files = await fs39.readdir(checkpointDir);
7987
8056
  checkpoints = files.filter((f) => f.endsWith(".json")).sort().reverse();
7988
8057
  } catch {
7989
8058
  }
@@ -8119,8 +8188,8 @@ async function restoreFromCheckpoint(_checkpoint) {
8119
8188
  }
8120
8189
  async function checkProjectExists() {
8121
8190
  try {
8122
- const fs38 = await import('fs/promises');
8123
- await fs38.access(".coco");
8191
+ const fs39 = await import('fs/promises');
8192
+ await fs39.access(".coco");
8124
8193
  return true;
8125
8194
  } catch {
8126
8195
  return false;
@@ -8659,12 +8728,12 @@ async function loadConfig2() {
8659
8728
  };
8660
8729
  }
8661
8730
  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));
8731
+ const fs39 = await import('fs/promises');
8732
+ await fs39.mkdir(".coco", { recursive: true });
8733
+ await fs39.writeFile(".coco/config.json", JSON.stringify(config, null, 2));
8665
8734
  }
8666
- function getNestedValue(obj, path35) {
8667
- const keys = path35.split(".");
8735
+ function getNestedValue(obj, path37) {
8736
+ const keys = path37.split(".");
8668
8737
  let current = obj;
8669
8738
  for (const key of keys) {
8670
8739
  if (current === null || current === void 0 || typeof current !== "object") {
@@ -8674,8 +8743,8 @@ function getNestedValue(obj, path35) {
8674
8743
  }
8675
8744
  return current;
8676
8745
  }
8677
- function setNestedValue(obj, path35, value) {
8678
- const keys = path35.split(".");
8746
+ function setNestedValue(obj, path37, value) {
8747
+ const keys = path37.split(".");
8679
8748
  let current = obj;
8680
8749
  for (let i = 0; i < keys.length - 1; i++) {
8681
8750
  const key = keys[i];
@@ -8896,8 +8965,8 @@ var MCPRegistryImpl = class {
8896
8965
  /**
8897
8966
  * Ensure directory exists
8898
8967
  */
8899
- async ensureDir(path35) {
8900
- await mkdir(dirname(path35), { recursive: true });
8968
+ async ensureDir(path37) {
8969
+ await mkdir(dirname(path37), { recursive: true });
8901
8970
  }
8902
8971
  };
8903
8972
  function createMCPRegistry(registryPath) {
@@ -9416,9 +9485,9 @@ function createEmptyMemoryContext() {
9416
9485
  errors: []
9417
9486
  };
9418
9487
  }
9419
- function createMissingMemoryFile(path35, level) {
9488
+ function createMissingMemoryFile(path37, level) {
9420
9489
  return {
9421
- path: path35,
9490
+ path: path37,
9422
9491
  level,
9423
9492
  content: "",
9424
9493
  sections: [],
@@ -9623,7 +9692,9 @@ async function saveTrustSettings(settings) {
9623
9692
  await fs22__default.mkdir(TRUST_SETTINGS_DIR, { recursive: true });
9624
9693
  settings.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9625
9694
  await fs22__default.writeFile(TRUST_SETTINGS_FILE, JSON.stringify(settings, null, 2), "utf-8");
9626
- } catch {
9695
+ } catch (error) {
9696
+ const msg = error instanceof Error ? error.message : String(error);
9697
+ console.warn(`[Trust] Failed to save trust settings: ${msg}`);
9627
9698
  }
9628
9699
  }
9629
9700
  async function loadTrustedTools(projectPath) {
@@ -9784,6 +9855,7 @@ var helpCommand = {
9784
9855
  commands: [
9785
9856
  { cmd: "/help, /?", desc: "Show this help message" },
9786
9857
  { cmd: "/help tools", desc: "Show available agent tools" },
9858
+ { cmd: "/tutorial, /tut", desc: "Quick guide to using Coco" },
9787
9859
  { cmd: "/clear, /c", desc: "Clear conversation history" },
9788
9860
  { cmd: "/exit, /quit, /q", desc: "Exit the REPL" }
9789
9861
  ]
@@ -10329,11 +10401,11 @@ async function setupGcloudADC(provider) {
10329
10401
  console.log(chalk12.dim(" Opening browser for Google sign-in..."));
10330
10402
  console.log(chalk12.dim(" (Complete the sign-in in your browser, then return here)"));
10331
10403
  console.log();
10332
- const { exec: exec2 } = await import('child_process');
10333
- const { promisify: promisify3 } = await import('util');
10334
- const execAsync2 = promisify3(exec2);
10404
+ const { exec: exec3 } = await import('child_process');
10405
+ const { promisify: promisify4 } = await import('util');
10406
+ const execAsync3 = promisify4(exec3);
10335
10407
  try {
10336
- await execAsync2("gcloud auth application-default login", {
10408
+ await execAsync3("gcloud auth application-default login", {
10337
10409
  timeout: 12e4
10338
10410
  // 2 minute timeout
10339
10411
  });
@@ -11417,11 +11489,11 @@ async function setupGcloudADCForProvider(_provider) {
11417
11489
  if (p9.isCancel(runNow) || !runNow) {
11418
11490
  return false;
11419
11491
  }
11420
- const { exec: exec2 } = await import('child_process');
11421
- const { promisify: promisify3 } = await import('util');
11422
- const execAsync2 = promisify3(exec2);
11492
+ const { exec: exec3 } = await import('child_process');
11493
+ const { promisify: promisify4 } = await import('util');
11494
+ const execAsync3 = promisify4(exec3);
11423
11495
  try {
11424
- await execAsync2("gcloud auth application-default login", { timeout: 12e4 });
11496
+ await execAsync3("gcloud auth application-default login", { timeout: 12e4 });
11425
11497
  const token = await getADCAccessToken();
11426
11498
  if (token) {
11427
11499
  console.log(chalk12.green("\n \u2713 Authentication successful!\n"));
@@ -12106,8 +12178,8 @@ async function listTrustedProjects2(trustStore) {
12106
12178
  p9.log.message("");
12107
12179
  for (const project of projects) {
12108
12180
  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}`);
12181
+ const path37 = project.path.length > 50 ? "..." + project.path.slice(-47) : project.path;
12182
+ p9.log.message(` [${level}] ${path37}`);
12111
12183
  p9.log.message(` Last accessed: ${new Date(project.lastAccessed).toLocaleString()}`);
12112
12184
  }
12113
12185
  p9.log.message("");
@@ -13277,7 +13349,7 @@ async function getCheckpoint(session, checkpointId) {
13277
13349
  return store.checkpoints.find((cp) => cp.id === checkpointId) ?? null;
13278
13350
  }
13279
13351
  async function restoreFiles(checkpoint, excludeFiles) {
13280
- const fs38 = await import('fs/promises');
13352
+ const fs39 = await import('fs/promises');
13281
13353
  const restored = [];
13282
13354
  const failed = [];
13283
13355
  for (const fileCheckpoint of checkpoint.files) {
@@ -13285,7 +13357,7 @@ async function restoreFiles(checkpoint, excludeFiles) {
13285
13357
  continue;
13286
13358
  }
13287
13359
  try {
13288
- await fs38.writeFile(fileCheckpoint.filePath, fileCheckpoint.originalContent, "utf-8");
13360
+ await fs39.writeFile(fileCheckpoint.filePath, fileCheckpoint.originalContent, "utf-8");
13289
13361
  restored.push(fileCheckpoint.filePath);
13290
13362
  } catch (error) {
13291
13363
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -13367,8 +13439,8 @@ function displayRewindResult(result) {
13367
13439
  const fileName = filePath.split("/").pop() ?? filePath;
13368
13440
  console.log(`${chalk12.green(String.fromCodePoint(10003))} Restored: ${fileName}`);
13369
13441
  }
13370
- for (const { path: path35, error } of result.filesFailed) {
13371
- const fileName = path35.split("/").pop() ?? path35;
13442
+ for (const { path: path37, error } of result.filesFailed) {
13443
+ const fileName = path37.split("/").pop() ?? path37;
13372
13444
  console.log(`${chalk12.red(String.fromCodePoint(10007))} Failed: ${fileName} (${error})`);
13373
13445
  }
13374
13446
  if (result.conversationRestored) {
@@ -14333,7 +14405,11 @@ function flushLineBuffer() {
14333
14405
  }
14334
14406
  if (inCodeBlock && codeBlockLines.length > 0) {
14335
14407
  stopStreamingIndicator();
14336
- renderCodeBlock(codeBlockLang, codeBlockLines);
14408
+ try {
14409
+ renderCodeBlock(codeBlockLang, codeBlockLines);
14410
+ } finally {
14411
+ stopStreamingIndicator();
14412
+ }
14337
14413
  inCodeBlock = false;
14338
14414
  codeBlockLang = "";
14339
14415
  codeBlockLines = [];
@@ -14689,6 +14765,7 @@ function formatInlineMarkdown(text9) {
14689
14765
  return text9;
14690
14766
  }
14691
14767
  function wrapText(text9, maxWidth) {
14768
+ if (maxWidth <= 0) return [text9];
14692
14769
  const plainText = stripAnsi(text9);
14693
14770
  if (plainText.length <= maxWidth) {
14694
14771
  return [text9];
@@ -14696,14 +14773,37 @@ function wrapText(text9, maxWidth) {
14696
14773
  const lines = [];
14697
14774
  let remaining = text9;
14698
14775
  while (stripAnsi(remaining).length > maxWidth) {
14699
- let breakPoint = maxWidth;
14700
14776
  const plain = stripAnsi(remaining);
14777
+ let breakPoint = maxWidth;
14701
14778
  const lastSpace = plain.lastIndexOf(" ", maxWidth);
14702
14779
  if (lastSpace > maxWidth * 0.5) {
14703
14780
  breakPoint = lastSpace;
14704
14781
  }
14705
- lines.push(remaining.slice(0, breakPoint));
14706
- remaining = remaining.slice(breakPoint).trimStart();
14782
+ const ansiRegex = /\x1b\[[0-9;]*m/g;
14783
+ let match;
14784
+ const ansiPositions = [];
14785
+ ansiRegex.lastIndex = 0;
14786
+ while ((match = ansiRegex.exec(remaining)) !== null) {
14787
+ ansiPositions.push({ start: match.index, end: match.index + match[0].length });
14788
+ }
14789
+ let rawPos = 0;
14790
+ let visualPos = 0;
14791
+ let ansiIdx = 0;
14792
+ while (visualPos < breakPoint && rawPos < remaining.length) {
14793
+ while (ansiIdx < ansiPositions.length && ansiPositions[ansiIdx].start === rawPos) {
14794
+ rawPos = ansiPositions[ansiIdx].end;
14795
+ ansiIdx++;
14796
+ }
14797
+ if (rawPos >= remaining.length) break;
14798
+ rawPos++;
14799
+ visualPos++;
14800
+ }
14801
+ while (ansiIdx < ansiPositions.length && ansiPositions[ansiIdx].start === rawPos) {
14802
+ rawPos = ansiPositions[ansiIdx].end;
14803
+ ansiIdx++;
14804
+ }
14805
+ lines.push(remaining.slice(0, rawPos));
14806
+ remaining = remaining.slice(rawPos).trimStart();
14707
14807
  }
14708
14808
  if (remaining) {
14709
14809
  lines.push(remaining);
@@ -14773,8 +14873,8 @@ function formatToolSummary(toolName, input) {
14773
14873
  return String(input.path || ".");
14774
14874
  case "search_files": {
14775
14875
  const pattern = String(input.pattern || "");
14776
- const path35 = input.path ? ` in ${input.path}` : "";
14777
- return `"${pattern}"${path35}`;
14876
+ const path37 = input.path ? ` in ${input.path}` : "";
14877
+ return `"${pattern}"${path37}`;
14778
14878
  }
14779
14879
  case "bash_exec": {
14780
14880
  const cmd = String(input.command || "");
@@ -15985,6 +16085,56 @@ var imageCommand = {
15985
16085
  return false;
15986
16086
  }
15987
16087
  };
16088
+ var tutorialCommand = {
16089
+ name: "tutorial",
16090
+ aliases: ["tut", "learn"],
16091
+ description: "Quick guide to using Coco",
16092
+ usage: "/tutorial",
16093
+ async execute() {
16094
+ console.log(chalk12.cyan.bold("\n\u2550\u2550\u2550 Coco Quick Tutorial \u2550\u2550\u2550\n"));
16095
+ const steps = [
16096
+ {
16097
+ step: "1",
16098
+ title: "Ask Coco anything",
16099
+ desc: 'Just type what you need: "Create a REST API with authentication"'
16100
+ },
16101
+ {
16102
+ step: "2",
16103
+ title: "Coco works autonomously",
16104
+ desc: "Reads your project, writes code, runs tests, and iterates until quality passes"
16105
+ },
16106
+ {
16107
+ step: "3",
16108
+ title: "Enable quality mode",
16109
+ desc: "Type /coco to enable auto-iteration: test \u2192 analyze \u2192 fix \u2192 repeat until score \u2265 85"
16110
+ },
16111
+ {
16112
+ step: "4",
16113
+ title: "Review changes",
16114
+ desc: "Use /diff to see what changed, /status for project state"
16115
+ },
16116
+ {
16117
+ step: "5",
16118
+ title: "Save your work",
16119
+ desc: "Use /commit to commit changes with a descriptive message"
16120
+ }
16121
+ ];
16122
+ for (const { step, title, desc } of steps) {
16123
+ console.log(` ${chalk12.magenta.bold(step)}. ${chalk12.bold(title)}`);
16124
+ console.log(` ${chalk12.dim(desc)}`);
16125
+ console.log();
16126
+ }
16127
+ console.log(chalk12.bold("Useful commands:"));
16128
+ console.log(
16129
+ ` ${chalk12.yellow("/coco")} ${chalk12.dim("Toggle quality mode (auto-iteration)")}`
16130
+ );
16131
+ console.log(` ${chalk12.yellow("/init")} ${chalk12.dim("Initialize a new project")}`);
16132
+ console.log(` ${chalk12.yellow("/help")} ${chalk12.dim("See all available commands")}`);
16133
+ console.log(` ${chalk12.yellow("/help tools")} ${chalk12.dim("See available agent tools")}`);
16134
+ console.log();
16135
+ return false;
16136
+ }
16137
+ };
15988
16138
 
15989
16139
  // src/cli/repl/commands/index.ts
15990
16140
  var commands = [
@@ -16014,7 +16164,8 @@ var commands = [
16014
16164
  allowPathCommand,
16015
16165
  permissionsCommand,
16016
16166
  cocoCommand,
16017
- imageCommand
16167
+ imageCommand,
16168
+ tutorialCommand
16018
16169
  ];
16019
16170
  function isSlashCommand(input) {
16020
16171
  return input.startsWith("/");
@@ -16101,21 +16252,25 @@ function createInputHandler(_session) {
16101
16252
  let historyIndex = -1;
16102
16253
  let tempLine = "";
16103
16254
  let lastMenuLines = 0;
16255
+ let lastCursorRow = 0;
16256
+ let isFirstRender = true;
16104
16257
  let isPasting = false;
16105
16258
  let pasteBuffer = "";
16106
16259
  let isReadingClipboard = false;
16107
16260
  const getPrompt = () => {
16261
+ const imageIndicator = hasPendingImage() ? chalk12.cyan(" \u{1F4CE} 1 image") : "";
16262
+ const imageIndicatorLen = hasPendingImage() ? 10 : 0;
16108
16263
  if (isCocoMode()) {
16109
16264
  return {
16110
- str: "\u{1F965} " + chalk12.magenta("[coco]") + " \u203A ",
16111
- // 🥥=2 + space=1 + [coco]=6 + space=1 + ›=1 + space=1 = 12
16112
- visualLen: 12
16265
+ str: "\u{1F965} " + chalk12.magenta("[coco]") + " \u203A " + imageIndicator,
16266
+ // 🥥=2 + space=1 + [coco]=6 + space=1 + ›=1 + space=1 = 12 + image indicator
16267
+ visualLen: 12 + imageIndicatorLen
16113
16268
  };
16114
16269
  }
16115
16270
  return {
16116
- str: chalk12.green("\u{1F965} \u203A "),
16117
- // 🥥=2 + space=1 + ›=1 + space=1 = 5
16118
- visualLen: 5
16271
+ str: chalk12.green("\u{1F965} \u203A ") + imageIndicator,
16272
+ // 🥥=2 + space=1 + ›=1 + space=1 = 5 + image indicator
16273
+ visualLen: 5 + imageIndicatorLen
16119
16274
  };
16120
16275
  };
16121
16276
  const MAX_ROWS = 8;
@@ -16128,9 +16283,19 @@ function createInputHandler(_session) {
16128
16283
  return Math.max(1, Math.min(3, cols));
16129
16284
  }
16130
16285
  function render() {
16131
- process.stdout.write("\r" + ansiEscapes3.eraseDown);
16286
+ const termCols = process.stdout.columns || 80;
16132
16287
  const prompt = getPrompt();
16133
- let output = prompt.str + currentLine;
16288
+ if (!isFirstRender) {
16289
+ const linesToGoUp = lastCursorRow + 1;
16290
+ if (linesToGoUp > 0) {
16291
+ process.stdout.write(ansiEscapes3.cursorUp(linesToGoUp));
16292
+ }
16293
+ }
16294
+ isFirstRender = false;
16295
+ process.stdout.write("\r" + ansiEscapes3.eraseDown);
16296
+ const separator = chalk12.dim("\u2500".repeat(termCols));
16297
+ let output = separator + "\n";
16298
+ output += prompt.str + currentLine;
16134
16299
  completions = findCompletions(currentLine);
16135
16300
  selectedCompletion = Math.min(selectedCompletion, Math.max(0, completions.length - 1));
16136
16301
  if (cursorPos === currentLine.length && completions.length > 0 && completions[selectedCompletion]) {
@@ -16183,15 +16348,26 @@ function createInputHandler(_session) {
16183
16348
  for (let i = 0; i < BOTTOM_MARGIN; i++) {
16184
16349
  output += "\n";
16185
16350
  }
16186
- output += ansiEscapes3.cursorUp(lastMenuLines + BOTTOM_MARGIN);
16351
+ output += ansiEscapes3.cursorUp(lastMenuLines + BOTTOM_MARGIN + 1);
16187
16352
  } else {
16188
16353
  lastMenuLines = 0;
16189
16354
  for (let i = 0; i < BOTTOM_MARGIN; i++) {
16190
16355
  output += "\n";
16191
16356
  }
16192
- output += ansiEscapes3.cursorUp(BOTTOM_MARGIN);
16357
+ output += ansiEscapes3.cursorUp(BOTTOM_MARGIN + 1);
16358
+ }
16359
+ output += ansiEscapes3.cursorDown(1);
16360
+ const cursorAbsolutePos = prompt.visualLen + cursorPos;
16361
+ const finalLine = Math.floor(cursorAbsolutePos / termCols);
16362
+ const finalCol = cursorAbsolutePos % termCols;
16363
+ output += "\r";
16364
+ if (finalLine > 0) {
16365
+ output += ansiEscapes3.cursorDown(finalLine);
16193
16366
  }
16194
- output += `\r${ansiEscapes3.cursorForward(prompt.visualLen + cursorPos)}`;
16367
+ if (finalCol > 0) {
16368
+ output += ansiEscapes3.cursorForward(finalCol);
16369
+ }
16370
+ lastCursorRow = finalLine;
16195
16371
  process.stdout.write(output);
16196
16372
  }
16197
16373
  function clearMenu() {
@@ -16218,15 +16394,20 @@ function createInputHandler(_session) {
16218
16394
  historyIndex = -1;
16219
16395
  tempLine = "";
16220
16396
  lastMenuLines = 0;
16397
+ lastCursorRow = 0;
16398
+ isFirstRender = true;
16221
16399
  render();
16222
16400
  if (process.stdin.isTTY) {
16223
16401
  process.stdin.setRawMode(true);
16224
16402
  }
16225
16403
  process.stdin.resume();
16226
16404
  process.stdout.write("\x1B[?2004h");
16405
+ const onResize = () => render();
16406
+ process.stdout.on("resize", onResize);
16227
16407
  const cleanup = () => {
16228
16408
  process.stdout.write("\x1B[?2004l");
16229
16409
  process.stdin.removeListener("data", onData);
16410
+ process.stdout.removeListener("resize", onResize);
16230
16411
  if (process.stdin.isTTY) {
16231
16412
  process.stdin.setRawMode(false);
16232
16413
  }
@@ -16339,15 +16520,14 @@ function createInputHandler(_session) {
16339
16520
  process.stdout.write(
16340
16521
  chalk12.green(" \u2713 Image captured") + chalk12.dim(` (${sizeKB} KB)`) + chalk12.dim(` \u2014 "${truncatedPrompt}"`)
16341
16522
  );
16342
- cleanup();
16343
- console.log();
16344
- const result = currentLine.trim();
16345
- if (result) {
16346
- sessionHistory.push(result);
16347
- }
16348
- resolve2(result || "");
16349
- }).catch(() => {
16523
+ setTimeout(() => render(), 1200);
16524
+ }).catch((err) => {
16350
16525
  isReadingClipboard = false;
16526
+ const msg = err instanceof Error ? err.message : String(err);
16527
+ if (msg.includes("color space")) {
16528
+ render();
16529
+ return;
16530
+ }
16351
16531
  render();
16352
16532
  });
16353
16533
  return;
@@ -16357,7 +16537,9 @@ function createInputHandler(_session) {
16357
16537
  currentLine = completions[selectedCompletion].cmd;
16358
16538
  }
16359
16539
  cleanup();
16540
+ const termWidth = process.stdout.columns || 80;
16360
16541
  console.log();
16542
+ console.log(chalk12.dim("\u2500".repeat(termWidth)));
16361
16543
  const result = currentLine.trim();
16362
16544
  if (result) {
16363
16545
  sessionHistory.push(result);
@@ -17396,7 +17578,8 @@ async function promptEditCommand(originalCommand) {
17396
17578
  console.log();
17397
17579
  console.log(chalk12.dim(" Edit command (or press Enter to cancel):"));
17398
17580
  console.log(chalk12.cyan(` Current: ${originalCommand}`));
17399
- if (process.stdin.isTTY && process.stdin.isRaw) {
17581
+ const wasRaw = process.stdin.isTTY ? process.stdin.isRaw : false;
17582
+ if (process.stdin.isTTY && wasRaw) {
17400
17583
  process.stdin.setRawMode(false);
17401
17584
  }
17402
17585
  const rl = readline2.createInterface({
@@ -17409,6 +17592,9 @@ async function promptEditCommand(originalCommand) {
17409
17592
  return trimmed || null;
17410
17593
  } finally {
17411
17594
  rl.close();
17595
+ if (process.stdin.isTTY && wasRaw) {
17596
+ process.stdin.setRawMode(true);
17597
+ }
17412
17598
  }
17413
17599
  }
17414
17600
  async function confirmToolExecution(toolCall) {
@@ -18190,8 +18376,8 @@ function isPathAllowed(filePath, operation) {
18190
18376
  if (absolute.startsWith(normalizedHome) && !absolute.startsWith(normalizedCwd)) {
18191
18377
  if (isWithinAllowedPath(absolute, operation)) ; else if (operation === "read") {
18192
18378
  const allowedHomeReads = [".gitconfig", ".zshrc", ".bashrc"];
18193
- const basename2 = path20__default.basename(absolute);
18194
- if (!allowedHomeReads.includes(basename2)) {
18379
+ const basename3 = path20__default.basename(absolute);
18380
+ if (!allowedHomeReads.includes(basename3)) {
18195
18381
  const targetDir = path20__default.dirname(absolute);
18196
18382
  return {
18197
18383
  allowed: false,
@@ -18208,12 +18394,12 @@ function isPathAllowed(filePath, operation) {
18208
18394
  }
18209
18395
  }
18210
18396
  if (operation === "write" || operation === "delete") {
18211
- const basename2 = path20__default.basename(absolute);
18397
+ const basename3 = path20__default.basename(absolute);
18212
18398
  for (const pattern of SENSITIVE_PATTERNS) {
18213
- if (pattern.test(basename2)) {
18399
+ if (pattern.test(basename3)) {
18214
18400
  return {
18215
18401
  allowed: false,
18216
- reason: `Operation on sensitive file '${basename2}' requires explicit confirmation`
18402
+ reason: `Operation on sensitive file '${basename3}' requires explicit confirmation`
18217
18403
  };
18218
18404
  }
18219
18405
  }
@@ -19471,59 +19657,322 @@ var simpleAutoCommitTool = defineTool({
19471
19657
  }
19472
19658
  });
19473
19659
  var gitSimpleTools = [checkProtectedBranchTool, simpleAutoCommitTool];
19660
+
19661
+ // src/agents/provider-bridge.ts
19662
+ var agentProvider = null;
19663
+ var agentToolRegistry = null;
19664
+ function setAgentProvider(provider) {
19665
+ agentProvider = provider;
19666
+ }
19667
+ function getAgentProvider() {
19668
+ return agentProvider;
19669
+ }
19670
+ function setAgentToolRegistry(registry) {
19671
+ agentToolRegistry = registry;
19672
+ }
19673
+ function getAgentToolRegistry() {
19674
+ return agentToolRegistry;
19675
+ }
19676
+
19677
+ // src/agents/executor.ts
19678
+ var AgentExecutor = class {
19679
+ constructor(provider, toolRegistry) {
19680
+ this.provider = provider;
19681
+ this.toolRegistry = toolRegistry;
19682
+ }
19683
+ /**
19684
+ * Execute an agent on a task with multi-turn tool use
19685
+ */
19686
+ async execute(agent, task) {
19687
+ const startTime = Date.now();
19688
+ const toolsUsed = /* @__PURE__ */ new Set();
19689
+ const messages = [
19690
+ {
19691
+ role: "user",
19692
+ content: this.buildTaskPrompt(task)
19693
+ }
19694
+ ];
19695
+ const agentToolDefs = this.getToolDefinitionsForAgent(agent.allowedTools);
19696
+ let turn = 0;
19697
+ let totalTokens = 0;
19698
+ while (turn < agent.maxTurns) {
19699
+ turn++;
19700
+ try {
19701
+ const response = await this.provider.chatWithTools(messages, {
19702
+ tools: agentToolDefs,
19703
+ system: agent.systemPrompt
19704
+ });
19705
+ const usage = response.usage;
19706
+ totalTokens += (usage?.inputTokens || 0) + (usage?.outputTokens || 0);
19707
+ if (response.stopReason !== "tool_use" || response.toolCalls.length === 0) {
19708
+ return {
19709
+ output: response.content,
19710
+ success: true,
19711
+ turns: turn,
19712
+ toolsUsed: Array.from(toolsUsed),
19713
+ tokensUsed: totalTokens,
19714
+ duration: Date.now() - startTime
19715
+ };
19716
+ }
19717
+ const assistantContent = [];
19718
+ if (response.content) {
19719
+ assistantContent.push({
19720
+ type: "text",
19721
+ text: response.content
19722
+ });
19723
+ }
19724
+ for (const toolCall of response.toolCalls) {
19725
+ assistantContent.push({
19726
+ type: "tool_use",
19727
+ id: toolCall.id,
19728
+ name: toolCall.name,
19729
+ input: toolCall.input
19730
+ });
19731
+ }
19732
+ messages.push({
19733
+ role: "assistant",
19734
+ content: assistantContent
19735
+ });
19736
+ const toolResults = [];
19737
+ for (const toolCall of response.toolCalls) {
19738
+ toolsUsed.add(toolCall.name);
19739
+ try {
19740
+ const result = await this.toolRegistry.execute(toolCall.name, toolCall.input);
19741
+ toolResults.push({
19742
+ type: "tool_result",
19743
+ tool_use_id: toolCall.id,
19744
+ content: result.success ? JSON.stringify(result.data) : `Error: ${result.error}`,
19745
+ is_error: !result.success
19746
+ });
19747
+ } catch (error) {
19748
+ toolResults.push({
19749
+ type: "tool_result",
19750
+ tool_use_id: toolCall.id,
19751
+ content: `Tool execution error: ${error instanceof Error ? error.message : String(error)}`,
19752
+ is_error: true
19753
+ });
19754
+ }
19755
+ }
19756
+ messages.push({
19757
+ role: "user",
19758
+ content: toolResults
19759
+ });
19760
+ } catch (error) {
19761
+ return {
19762
+ output: `Agent error on turn ${turn}: ${error instanceof Error ? error.message : String(error)}`,
19763
+ success: false,
19764
+ turns: turn,
19765
+ toolsUsed: Array.from(toolsUsed),
19766
+ tokensUsed: totalTokens,
19767
+ duration: Date.now() - startTime
19768
+ };
19769
+ }
19770
+ }
19771
+ return {
19772
+ output: "Agent reached maximum turns without completing task",
19773
+ success: false,
19774
+ turns: turn,
19775
+ toolsUsed: Array.from(toolsUsed),
19776
+ tokensUsed: totalTokens,
19777
+ duration: Date.now() - startTime
19778
+ };
19779
+ }
19780
+ /**
19781
+ * Build task prompt with context
19782
+ */
19783
+ buildTaskPrompt(task) {
19784
+ let prompt = `Task: ${task.description}
19785
+ `;
19786
+ if (task.context && Object.keys(task.context).length > 0) {
19787
+ prompt += `
19788
+ Context:
19789
+ ${JSON.stringify(task.context, null, 2)}
19790
+ `;
19791
+ }
19792
+ prompt += `
19793
+ Complete this task autonomously using the available tools. When done, provide a summary of what you accomplished.`;
19794
+ return prompt;
19795
+ }
19796
+ /**
19797
+ * Get tool definitions filtered for this agent's allowed tools
19798
+ */
19799
+ getToolDefinitionsForAgent(allowedToolNames) {
19800
+ const allDefs = this.toolRegistry.getToolDefinitionsForLLM();
19801
+ if (allowedToolNames.length === 0) return allDefs;
19802
+ return allDefs.filter((def) => allowedToolNames.includes(def.name));
19803
+ }
19804
+ };
19805
+ var AGENT_ROLES = {
19806
+ researcher: {
19807
+ role: "researcher",
19808
+ systemPrompt: `You are a code researcher agent. Your role is to:
19809
+ - Explore and understand existing codebases
19810
+ - Find relevant code patterns and examples
19811
+ - Identify dependencies and relationships
19812
+ - Document your findings clearly
19813
+
19814
+ Use tools to search, read files, and analyze code structure.`,
19815
+ allowedTools: ["read_file", "grep", "find_in_file", "glob", "codebase_map"]
19816
+ },
19817
+ coder: {
19818
+ role: "coder",
19819
+ systemPrompt: `You are a code generation agent. Your role is to:
19820
+ - Write high-quality, production-ready code
19821
+ - Follow best practices and coding standards
19822
+ - Ensure code is syntactically valid
19823
+ - Write clean, maintainable code
19824
+
19825
+ Use tools to read existing code, write new files, and validate syntax.`,
19826
+ allowedTools: ["read_file", "write_file", "edit_file", "bash_exec", "validateCode"]
19827
+ },
19828
+ tester: {
19829
+ role: "tester",
19830
+ systemPrompt: `You are a test generation agent. Your role is to:
19831
+ - Write comprehensive test suites
19832
+ - Achieve high code coverage
19833
+ - Test edge cases and error conditions
19834
+ - Ensure tests are reliable and maintainable
19835
+
19836
+ Use tools to read code, write tests, and run them.`,
19837
+ allowedTools: ["read_file", "write_file", "run_tests", "get_coverage", "run_test_file"]
19838
+ },
19839
+ reviewer: {
19840
+ role: "reviewer",
19841
+ systemPrompt: `You are a code review agent. Your role is to:
19842
+ - Identify code quality issues
19843
+ - Check for security vulnerabilities
19844
+ - Ensure best practices are followed
19845
+ - Provide actionable feedback
19846
+
19847
+ Use tools to read and analyze code quality.`,
19848
+ allowedTools: ["read_file", "calculate_quality", "analyze_complexity", "grep"]
19849
+ },
19850
+ optimizer: {
19851
+ role: "optimizer",
19852
+ systemPrompt: `You are a code optimization agent. Your role is to:
19853
+ - Reduce code complexity
19854
+ - Eliminate duplication
19855
+ - Improve performance
19856
+ - Refactor for maintainability
19857
+
19858
+ Use tools to analyze and improve code.`,
19859
+ allowedTools: ["read_file", "write_file", "edit_file", "analyze_complexity", "grep"]
19860
+ },
19861
+ planner: {
19862
+ role: "planner",
19863
+ systemPrompt: `You are a task planning agent. Your role is to:
19864
+ - Break down complex tasks into subtasks
19865
+ - Identify dependencies between tasks
19866
+ - Estimate complexity and effort
19867
+ - Create actionable plans
19868
+
19869
+ Use tools to analyze requirements and explore the codebase.`,
19870
+ allowedTools: ["read_file", "grep", "glob", "codebase_map"]
19871
+ }
19872
+ };
19873
+
19874
+ // src/tools/simple-agent.ts
19474
19875
  var SpawnSimpleAgentSchema = z.object({
19475
19876
  task: z.string().describe("Task description for the sub-agent"),
19476
- context: z.string().optional().describe("Additional context or instructions for the agent")
19877
+ context: z.string().optional().describe("Additional context or instructions for the agent"),
19878
+ role: z.enum(["researcher", "coder", "tester", "reviewer", "optimizer", "planner"]).default("coder").describe("Agent role to use"),
19879
+ maxTurns: z.number().default(10).describe("Maximum tool-use turns for the agent")
19477
19880
  });
19478
19881
  var spawnSimpleAgentTool = defineTool({
19479
19882
  name: "spawnSimpleAgent",
19480
- description: `Spawn a sub-agent to handle a specific task.
19883
+ description: `Spawn a sub-agent to handle a specific task with real LLM tool-use execution.
19481
19884
 
19482
19885
  Use this when you need to:
19483
19886
  - Delegate a focused task to another agent
19484
19887
  - Get a second opinion or alternative approach
19485
19888
  - Handle multiple independent subtasks
19486
19889
 
19487
- The sub-agent will work on the task and return results.
19890
+ The sub-agent will work on the task autonomously using available tools.
19488
19891
 
19489
19892
  Example: "Write unit tests for the authentication module"`,
19490
19893
  category: "build",
19491
19894
  parameters: SpawnSimpleAgentSchema,
19492
19895
  async execute(input) {
19493
19896
  const typedInput = input;
19897
+ const provider = getAgentProvider();
19898
+ const toolRegistry = getAgentToolRegistry();
19899
+ if (!provider || !toolRegistry) {
19900
+ const agentId2 = `agent-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
19901
+ return {
19902
+ stdout: JSON.stringify({
19903
+ agentId: agentId2,
19904
+ status: "unavailable",
19905
+ task: typedInput.task,
19906
+ message: "Agent provider not initialized. Call setAgentProvider() during orchestrator startup.",
19907
+ success: false
19908
+ }),
19909
+ stderr: "",
19910
+ exitCode: 1,
19911
+ duration: 0
19912
+ };
19913
+ }
19494
19914
  const agentId = `agent-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
19915
+ const executor = new AgentExecutor(provider, toolRegistry);
19916
+ const roleDef = AGENT_ROLES[typedInput.role];
19917
+ if (!roleDef) {
19918
+ return {
19919
+ stdout: JSON.stringify({
19920
+ agentId,
19921
+ status: "error",
19922
+ message: `Unknown agent role: ${typedInput.role}`,
19923
+ success: false
19924
+ }),
19925
+ stderr: "",
19926
+ exitCode: 1,
19927
+ duration: 0
19928
+ };
19929
+ }
19930
+ const agentDef = { ...roleDef, maxTurns: typedInput.maxTurns };
19931
+ const result = await executor.execute(agentDef, {
19932
+ id: agentId,
19933
+ description: typedInput.task,
19934
+ context: typedInput.context ? { userContext: typedInput.context } : void 0
19935
+ });
19495
19936
  return {
19496
19937
  stdout: JSON.stringify({
19497
19938
  agentId,
19498
- status: "simulated",
19939
+ status: result.success ? "completed" : "failed",
19499
19940
  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."
19941
+ output: result.output,
19942
+ success: result.success,
19943
+ turns: result.turns,
19944
+ toolsUsed: result.toolsUsed,
19945
+ tokensUsed: result.tokensUsed,
19946
+ duration: result.duration
19503
19947
  }),
19504
19948
  stderr: "",
19505
- exitCode: 0,
19506
- duration: 0
19949
+ exitCode: result.success ? 0 : 1,
19950
+ duration: result.duration
19507
19951
  };
19508
19952
  }
19509
19953
  });
19510
19954
  var checkAgentCapabilityTool = defineTool({
19511
19955
  name: "checkAgentCapability",
19512
- description: "Check if multi-agent capability is available",
19956
+ description: "Check if multi-agent capability is available and configured",
19513
19957
  category: "build",
19514
19958
  parameters: z.object({}),
19515
19959
  async execute() {
19960
+ const provider = getAgentProvider();
19961
+ const toolRegistry = getAgentToolRegistry();
19962
+ const isReady = provider !== null && toolRegistry !== null;
19516
19963
  return {
19517
19964
  stdout: JSON.stringify({
19518
19965
  multiAgentSupported: true,
19519
- parallelExecution: "not yet implemented",
19520
- isolatedContexts: "not yet implemented",
19966
+ providerConfigured: provider !== null,
19967
+ toolRegistryConfigured: toolRegistry !== null,
19968
+ ready: isReady,
19969
+ availableRoles: Object.keys(AGENT_ROLES),
19521
19970
  features: {
19522
- taskDelegation: "available",
19523
- parallelSpawn: "requires integration",
19524
- contextForking: "requires integration"
19971
+ taskDelegation: isReady ? "ready" : "requires provider initialization",
19972
+ parallelSpawn: isReady ? "ready" : "requires provider initialization",
19973
+ multiTurnToolUse: isReady ? "ready" : "requires provider initialization"
19525
19974
  },
19526
- status: "Multi-agent foundation exists. Full features require provider integration."
19975
+ status: isReady ? "Multi-agent system is ready with real LLM tool-use execution." : "Provider not initialized. Call setAgentProvider() during startup."
19527
19976
  }),
19528
19977
  stderr: "",
19529
19978
  exitCode: 0,
@@ -19857,10 +20306,10 @@ var CoverageAnalyzer = class {
19857
20306
  join(this.projectPath, ".coverage", "coverage-summary.json"),
19858
20307
  join(this.projectPath, "coverage", "lcov-report", "coverage-summary.json")
19859
20308
  ];
19860
- for (const path35 of possiblePaths) {
20309
+ for (const path37 of possiblePaths) {
19861
20310
  try {
19862
- await access(path35, constants.R_OK);
19863
- const content = await readFile(path35, "utf-8");
20311
+ await access(path37, constants.R_OK);
20312
+ const content = await readFile(path37, "utf-8");
19864
20313
  const report = JSON.parse(content);
19865
20314
  return parseCoverageSummary(report);
19866
20315
  } catch {
@@ -20514,132 +20963,1635 @@ var DuplicationAnalyzer = class {
20514
20963
  });
20515
20964
  }
20516
20965
  };
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) {
20966
+ var execAsync2 = promisify(exec);
20967
+ var BuildVerifier = class {
20968
+ projectPath;
20969
+ constructor(projectPath) {
20546
20970
  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
20971
  }
20552
- coverageAnalyzer;
20553
- securityScanner;
20554
- complexityAnalyzer;
20555
- duplicationAnalyzer;
20556
20972
  /**
20557
- * Evaluate quality across all dimensions
20558
- * Returns QualityScores with 0% hardcoded values (5/12 dimensions are real)
20973
+ * Verify that the project builds successfully
20559
20974
  */
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
- };
20975
+ async verifyBuild() {
20976
+ const startTime = Date.now();
20977
+ try {
20978
+ const buildCommand2 = await this.detectBuildCommand();
20979
+ if (!buildCommand2) {
20980
+ return {
20981
+ success: true,
20982
+ errors: [],
20983
+ warnings: [],
20984
+ duration: Date.now() - startTime,
20985
+ stdout: "No build command detected",
20986
+ stderr: ""
20987
+ };
20988
+ }
20989
+ const { stdout, stderr } = await execAsync2(buildCommand2, {
20990
+ cwd: this.projectPath,
20991
+ timeout: 12e4,
20992
+ // 2 minutes
20993
+ maxBuffer: 10 * 1024 * 1024
20994
+ // 10MB
20995
+ });
20996
+ const errors = this.parseErrors(stdout + stderr);
20997
+ const warnings = this.parseWarnings(stdout + stderr);
20998
+ return {
20999
+ success: errors.length === 0,
21000
+ errors,
21001
+ warnings,
21002
+ duration: Date.now() - startTime,
21003
+ stdout,
21004
+ stderr
21005
+ };
21006
+ } catch (error) {
21007
+ const execError = error;
21008
+ const errors = this.parseErrors(
21009
+ (execError.stdout ?? "") + (execError.stderr ?? "") || (execError.message ?? "")
21010
+ );
21011
+ const warnings = this.parseWarnings(
21012
+ (execError.stdout ?? "") + (execError.stderr ?? "") || ""
21013
+ );
21014
+ return {
21015
+ success: false,
21016
+ errors,
21017
+ warnings,
21018
+ duration: Date.now() - startTime,
21019
+ stdout: execError.stdout || "",
21020
+ stderr: execError.stderr || execError.message || ""
21021
+ };
21022
+ }
20628
21023
  }
20629
21024
  /**
20630
- * Calculate readability from complexity
20631
- * Low complexity = high readability
21025
+ * Run TypeScript type checking only (faster than full build)
20632
21026
  */
20633
- calculateReadability(averageComplexity) {
20634
- if (averageComplexity <= 3) return 100;
20635
- return Math.max(0, 100 - (averageComplexity - 3) / 17 * 100);
21027
+ async verifyTypes() {
21028
+ const startTime = Date.now();
21029
+ try {
21030
+ const hasTsConfig = await this.fileExists(path20.join(this.projectPath, "tsconfig.json"));
21031
+ if (!hasTsConfig) {
21032
+ return {
21033
+ success: true,
21034
+ errors: [],
21035
+ warnings: [],
21036
+ duration: Date.now() - startTime,
21037
+ stdout: "No tsconfig.json found",
21038
+ stderr: ""
21039
+ };
21040
+ }
21041
+ const { stdout, stderr } = await execAsync2("npx tsc --noEmit", {
21042
+ cwd: this.projectPath,
21043
+ timeout: 6e4,
21044
+ // 1 minute
21045
+ maxBuffer: 10 * 1024 * 1024
21046
+ });
21047
+ const errors = this.parseTypeScriptErrors(stdout + stderr);
21048
+ const warnings = this.parseTypeScriptWarnings(stdout + stderr);
21049
+ return {
21050
+ success: errors.length === 0,
21051
+ errors,
21052
+ warnings,
21053
+ duration: Date.now() - startTime,
21054
+ stdout,
21055
+ stderr
21056
+ };
21057
+ } catch (error) {
21058
+ const execError = error;
21059
+ const errors = this.parseTypeScriptErrors(
21060
+ (execError.stdout ?? "") + (execError.stderr ?? "") || (execError.message ?? "")
21061
+ );
21062
+ const warnings = this.parseTypeScriptWarnings(
21063
+ (execError.stdout ?? "") + (execError.stderr ?? "") || ""
21064
+ );
21065
+ return {
21066
+ success: false,
21067
+ errors,
21068
+ warnings,
21069
+ duration: Date.now() - startTime,
21070
+ stdout: execError.stdout || "",
21071
+ stderr: execError.stderr || execError.message || ""
21072
+ };
21073
+ }
20636
21074
  }
20637
21075
  /**
20638
- * Generate quality issues from analyzer results
21076
+ * Detect build command from package.json
20639
21077
  */
20640
- generateIssues(securityVulns, complexityResult, duplicationResult) {
20641
- const issues = [];
20642
- for (const vuln of securityVulns) {
21078
+ async detectBuildCommand() {
21079
+ try {
21080
+ const packageJsonPath = path20.join(this.projectPath, "package.json");
21081
+ const content = await fs22.readFile(packageJsonPath, "utf-8");
21082
+ const packageJson = JSON.parse(content);
21083
+ if (packageJson.scripts?.build) {
21084
+ return "npm run build";
21085
+ }
21086
+ if (packageJson.devDependencies?.typescript || packageJson.dependencies?.typescript) {
21087
+ return "npx tsc --noEmit";
21088
+ }
21089
+ return null;
21090
+ } catch {
21091
+ return null;
21092
+ }
21093
+ }
21094
+ /**
21095
+ * Parse errors from build output
21096
+ */
21097
+ parseErrors(output) {
21098
+ const errors = [];
21099
+ const tsErrorRegex = /(.+?)\((\d+),(\d+)\): error (TS\d+): (.+)/g;
21100
+ let match;
21101
+ while ((match = tsErrorRegex.exec(output)) !== null) {
21102
+ errors.push({
21103
+ file: match[1] || "",
21104
+ line: parseInt(match[2] || "0", 10),
21105
+ column: parseInt(match[3] || "0", 10),
21106
+ code: match[4],
21107
+ message: match[5] || ""
21108
+ });
21109
+ }
21110
+ const eslintErrorRegex = /(.+?):(\d+):(\d+): (.+)/g;
21111
+ while ((match = eslintErrorRegex.exec(output)) !== null) {
21112
+ if (!output.includes("error")) continue;
21113
+ errors.push({
21114
+ file: match[1] || "",
21115
+ line: parseInt(match[2] || "0", 10),
21116
+ column: parseInt(match[3] || "0", 10),
21117
+ message: match[4] || ""
21118
+ });
21119
+ }
21120
+ return errors;
21121
+ }
21122
+ /**
21123
+ * Parse warnings from build output
21124
+ */
21125
+ parseWarnings(output) {
21126
+ const warnings = [];
21127
+ const tsWarningRegex = /(.+?)\((\d+),(\d+)\): warning (TS\d+): (.+)/g;
21128
+ let match;
21129
+ while ((match = tsWarningRegex.exec(output)) !== null) {
21130
+ warnings.push({
21131
+ file: match[1] || "",
21132
+ line: parseInt(match[2] || "0", 10),
21133
+ column: parseInt(match[3] || "0", 10),
21134
+ code: match[4],
21135
+ message: match[5] || ""
21136
+ });
21137
+ }
21138
+ return warnings;
21139
+ }
21140
+ /**
21141
+ * Parse TypeScript-specific errors
21142
+ */
21143
+ parseTypeScriptErrors(output) {
21144
+ return this.parseErrors(output);
21145
+ }
21146
+ /**
21147
+ * Parse TypeScript-specific warnings
21148
+ */
21149
+ parseTypeScriptWarnings(output) {
21150
+ return this.parseWarnings(output);
21151
+ }
21152
+ /**
21153
+ * Check if file exists
21154
+ */
21155
+ async fileExists(filePath) {
21156
+ try {
21157
+ await fs22.access(filePath);
21158
+ return true;
21159
+ } catch {
21160
+ return false;
21161
+ }
21162
+ }
21163
+ };
21164
+
21165
+ // src/quality/analyzers/correctness.ts
21166
+ function parseVitestOutput(stdout) {
21167
+ const testsMatch = stdout.match(
21168
+ /Tests\s+(?:(\d+)\s+passed)?(?:\s*\|\s*(\d+)\s+failed)?(?:\s*\|\s*(\d+)\s+skipped)?/
21169
+ );
21170
+ if (testsMatch) {
21171
+ return {
21172
+ passed: parseInt(testsMatch[1] || "0", 10),
21173
+ failed: parseInt(testsMatch[2] || "0", 10),
21174
+ skipped: parseInt(testsMatch[3] || "0", 10)
21175
+ };
21176
+ }
21177
+ try {
21178
+ const json2 = JSON.parse(stdout);
21179
+ return {
21180
+ passed: json2.numPassedTests ?? 0,
21181
+ failed: json2.numFailedTests ?? 0,
21182
+ skipped: json2.numPendingTests ?? 0
21183
+ };
21184
+ } catch {
21185
+ }
21186
+ return { passed: 0, failed: 0, skipped: 0 };
21187
+ }
21188
+ function parseJestOutput(stdout) {
21189
+ try {
21190
+ const json2 = JSON.parse(stdout);
21191
+ return {
21192
+ passed: json2.numPassedTests ?? 0,
21193
+ failed: json2.numFailedTests ?? 0,
21194
+ skipped: json2.numPendingTests ?? 0
21195
+ };
21196
+ } catch {
21197
+ const match = stdout.match(
21198
+ /Tests:\s+(\d+)\s+passed(?:,\s*(\d+)\s+failed)?(?:,\s*(\d+)\s+skipped)?/
21199
+ );
21200
+ if (match) {
21201
+ return {
21202
+ passed: parseInt(match[1] || "0", 10),
21203
+ failed: parseInt(match[2] || "0", 10),
21204
+ skipped: parseInt(match[3] || "0", 10)
21205
+ };
21206
+ }
21207
+ }
21208
+ return { passed: 0, failed: 0, skipped: 0 };
21209
+ }
21210
+ function buildTestCommand(framework) {
21211
+ switch (framework) {
21212
+ case "vitest":
21213
+ return { command: "npx", args: ["vitest", "run", "--reporter=verbose"] };
21214
+ case "jest":
21215
+ return { command: "npx", args: ["jest", "--json"] };
21216
+ case "mocha":
21217
+ return { command: "npx", args: ["mocha", "--reporter=json"] };
21218
+ default:
21219
+ return null;
21220
+ }
21221
+ }
21222
+ var CorrectnessAnalyzer = class {
21223
+ constructor(projectPath) {
21224
+ this.projectPath = projectPath;
21225
+ this.buildVerifier = new BuildVerifier(projectPath);
21226
+ }
21227
+ buildVerifier;
21228
+ /**
21229
+ * Analyze correctness by running tests and verifying build
21230
+ */
21231
+ async analyze() {
21232
+ const [testResult, buildResult] = await Promise.all([
21233
+ this.runTests(),
21234
+ this.buildVerifier.verifyTypes().catch(() => ({ success: false, errors: [] }))
21235
+ ]);
21236
+ const total = testResult.passed + testResult.failed;
21237
+ const testPassRate = total > 0 ? testResult.passed / total * 100 : 0;
21238
+ const buildSuccess = buildResult.success;
21239
+ let score;
21240
+ if (total === 0 && !buildSuccess) {
21241
+ score = 0;
21242
+ } else if (total === 0) {
21243
+ score = 30;
21244
+ } else {
21245
+ score = testPassRate / 100 * 70 + (buildSuccess ? 30 : 0);
21246
+ }
21247
+ score = Math.round(Math.max(0, Math.min(100, score)));
21248
+ const details = this.buildDetails(testResult, buildSuccess, testPassRate, total);
21249
+ return {
21250
+ score,
21251
+ testPassRate,
21252
+ buildSuccess,
21253
+ testsPassed: testResult.passed,
21254
+ testsFailed: testResult.failed,
21255
+ testsSkipped: testResult.skipped,
21256
+ testsTotal: total,
21257
+ buildErrors: buildResult.errors?.length ?? 0,
21258
+ details
21259
+ };
21260
+ }
21261
+ /**
21262
+ * Run tests and parse results
21263
+ */
21264
+ async runTests() {
21265
+ const framework = await detectTestFramework2(this.projectPath);
21266
+ if (!framework) {
21267
+ return { passed: 0, failed: 0, skipped: 0 };
21268
+ }
21269
+ const cmd = buildTestCommand(framework);
21270
+ if (!cmd) {
21271
+ return { passed: 0, failed: 0, skipped: 0 };
21272
+ }
21273
+ try {
21274
+ const result = await execa(cmd.command, cmd.args, {
21275
+ cwd: this.projectPath,
21276
+ reject: false,
21277
+ timeout: 3e5
21278
+ // 5 minutes
21279
+ });
21280
+ const output = result.stdout + "\n" + result.stderr;
21281
+ switch (framework) {
21282
+ case "vitest":
21283
+ return parseVitestOutput(output);
21284
+ case "jest":
21285
+ return parseJestOutput(result.stdout);
21286
+ case "mocha": {
21287
+ try {
21288
+ const json2 = JSON.parse(result.stdout);
21289
+ return {
21290
+ passed: json2.stats?.passes ?? 0,
21291
+ failed: json2.stats?.failures ?? 0,
21292
+ skipped: json2.stats?.pending ?? 0
21293
+ };
21294
+ } catch {
21295
+ return { passed: 0, failed: 0, skipped: 0 };
21296
+ }
21297
+ }
21298
+ default:
21299
+ return { passed: 0, failed: 0, skipped: 0 };
21300
+ }
21301
+ } catch {
21302
+ return { passed: 0, failed: 0, skipped: 0 };
21303
+ }
21304
+ }
21305
+ buildDetails(testResult, buildSuccess, testPassRate, total) {
21306
+ const parts = [];
21307
+ if (total > 0) {
21308
+ parts.push(`Tests: ${testResult.passed}/${total} passed (${testPassRate.toFixed(1)}%)`);
21309
+ if (testResult.failed > 0) {
21310
+ parts.push(`${testResult.failed} failed`);
21311
+ }
21312
+ } else {
21313
+ parts.push("No tests found");
21314
+ }
21315
+ parts.push(`Build: ${buildSuccess ? "success" : "failed"}`);
21316
+ return parts.join(", ");
21317
+ }
21318
+ };
21319
+ function countExports(ast) {
21320
+ let exportCount = 0;
21321
+ for (const node of ast.body) {
21322
+ switch (node.type) {
21323
+ case "ExportNamedDeclaration":
21324
+ if (node.declaration) {
21325
+ exportCount++;
21326
+ if (node.declaration.type === "VariableDeclaration" && node.declaration.declarations.length > 1) {
21327
+ exportCount += node.declaration.declarations.length - 1;
21328
+ }
21329
+ } else if (node.specifiers.length > 0) {
21330
+ exportCount += node.specifiers.length;
21331
+ }
21332
+ break;
21333
+ case "ExportDefaultDeclaration":
21334
+ exportCount++;
21335
+ break;
21336
+ case "ExportAllDeclaration":
21337
+ exportCount++;
21338
+ break;
21339
+ }
21340
+ }
21341
+ return exportCount;
21342
+ }
21343
+ var CompletenessAnalyzer = class {
21344
+ constructor(projectPath) {
21345
+ this.projectPath = projectPath;
21346
+ }
21347
+ /**
21348
+ * Analyze project completeness
21349
+ */
21350
+ async analyze(files) {
21351
+ const sourceFiles = files ?? await this.findSourceFiles();
21352
+ const testFiles = await this.findTestFiles();
21353
+ const { totalExports, exportDensity } = await this.analyzeExports(sourceFiles);
21354
+ const testFileRatio = this.calculateTestFileRatio(sourceFiles, testFiles);
21355
+ const hasEntryPoint = await this.checkEntryPoint();
21356
+ const exportScore = Math.min(40, exportDensity * 0.4);
21357
+ const testScore = Math.min(40, testFileRatio * 0.4);
21358
+ const entryScore = hasEntryPoint ? 20 : 0;
21359
+ const score = Math.round(Math.max(0, Math.min(100, exportScore + testScore + entryScore)));
21360
+ const details = this.buildDetails(
21361
+ sourceFiles.length,
21362
+ testFiles.length,
21363
+ totalExports,
21364
+ exportDensity,
21365
+ testFileRatio,
21366
+ hasEntryPoint
21367
+ );
21368
+ return {
21369
+ score,
21370
+ exportDensity,
21371
+ testFileRatio,
21372
+ hasEntryPoint,
21373
+ sourceFileCount: sourceFiles.length,
21374
+ testFileCount: testFiles.length,
21375
+ totalExports,
21376
+ details
21377
+ };
21378
+ }
21379
+ /**
21380
+ * Analyze export density across files
21381
+ */
21382
+ async analyzeExports(files) {
21383
+ let totalExports = 0;
21384
+ let filesWithExports = 0;
21385
+ for (const file of files) {
21386
+ try {
21387
+ const content = await readFile(file, "utf-8");
21388
+ const ast = parse(content, {
21389
+ loc: true,
21390
+ range: true,
21391
+ jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
21392
+ });
21393
+ const exports$1 = countExports(ast);
21394
+ totalExports += exports$1;
21395
+ if (exports$1 > 0) filesWithExports++;
21396
+ } catch {
21397
+ }
21398
+ }
21399
+ const exportDensity = files.length > 0 ? filesWithExports / files.length * 100 : 0;
21400
+ return { totalExports, exportDensity };
21401
+ }
21402
+ /**
21403
+ * Calculate ratio of source files that have corresponding test files
21404
+ */
21405
+ calculateTestFileRatio(sourceFiles, testFiles) {
21406
+ if (sourceFiles.length === 0) return 0;
21407
+ const testBaseNames = new Set(
21408
+ testFiles.map((f) => {
21409
+ const name = basename(f);
21410
+ return name.replace(/\.(test|spec)\.(ts|tsx|js|jsx)$/, "");
21411
+ })
21412
+ );
21413
+ let coveredCount = 0;
21414
+ for (const sourceFile of sourceFiles) {
21415
+ const name = basename(sourceFile).replace(/\.(ts|tsx|js|jsx)$/, "");
21416
+ if (testBaseNames.has(name)) {
21417
+ coveredCount++;
21418
+ }
21419
+ }
21420
+ return coveredCount / sourceFiles.length * 100;
21421
+ }
21422
+ /**
21423
+ * Check if project has a clear entry point
21424
+ */
21425
+ async checkEntryPoint() {
21426
+ const entryPoints = [
21427
+ "src/index.ts",
21428
+ "src/index.js",
21429
+ "src/main.ts",
21430
+ "src/main.js",
21431
+ "index.ts",
21432
+ "index.js"
21433
+ ];
21434
+ for (const entry of entryPoints) {
21435
+ try {
21436
+ await access(join(this.projectPath, entry), constants.R_OK);
21437
+ return true;
21438
+ } catch {
21439
+ }
21440
+ }
21441
+ try {
21442
+ const pkgContent = await readFile(join(this.projectPath, "package.json"), "utf-8");
21443
+ const pkg = JSON.parse(pkgContent);
21444
+ if (pkg.main || pkg.exports) return true;
21445
+ } catch {
21446
+ }
21447
+ return false;
21448
+ }
21449
+ buildDetails(sourceCount, testCount, totalExports, exportDensity, testFileRatio, hasEntryPoint) {
21450
+ return [
21451
+ `${sourceCount} source files, ${testCount} test files`,
21452
+ `${totalExports} exports (${exportDensity.toFixed(1)}% files have exports)`,
21453
+ `${testFileRatio.toFixed(1)}% source files have tests`,
21454
+ `Entry point: ${hasEntryPoint ? "found" : "missing"}`
21455
+ ].join(", ");
21456
+ }
21457
+ async findSourceFiles() {
21458
+ return glob("**/*.{ts,js,tsx,jsx}", {
21459
+ cwd: this.projectPath,
21460
+ absolute: true,
21461
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
21462
+ });
21463
+ }
21464
+ async findTestFiles() {
21465
+ return glob("**/*.{test,spec}.{ts,tsx,js,jsx}", {
21466
+ cwd: this.projectPath,
21467
+ absolute: true,
21468
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"]
21469
+ });
21470
+ }
21471
+ };
21472
+ function analyzeRobustnessPatterns(ast) {
21473
+ let functions = 0;
21474
+ let functionsWithTryCatch = 0;
21475
+ let optionalChaining = 0;
21476
+ let nullishCoalescing = 0;
21477
+ let typeGuards = 0;
21478
+ let typeofChecks = 0;
21479
+ let nullChecks = 0;
21480
+ let insideFunction = false;
21481
+ let currentFunctionHasTryCatch = false;
21482
+ function traverse(node) {
21483
+ const isFunctionNode = node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "MethodDefinition";
21484
+ if (isFunctionNode) {
21485
+ if (insideFunction && currentFunctionHasTryCatch) {
21486
+ functionsWithTryCatch++;
21487
+ }
21488
+ functions++;
21489
+ const previousInsideFunction = insideFunction;
21490
+ const previousHasTryCatch = currentFunctionHasTryCatch;
21491
+ insideFunction = true;
21492
+ currentFunctionHasTryCatch = false;
21493
+ traverseChildren(node);
21494
+ if (currentFunctionHasTryCatch) {
21495
+ functionsWithTryCatch++;
21496
+ }
21497
+ if (isFunctionNode) {
21498
+ functions--;
21499
+ functions++;
21500
+ }
21501
+ insideFunction = previousInsideFunction;
21502
+ currentFunctionHasTryCatch = previousHasTryCatch;
21503
+ return;
21504
+ }
21505
+ switch (node.type) {
21506
+ case "TryStatement":
21507
+ if (insideFunction) {
21508
+ currentFunctionHasTryCatch = true;
21509
+ }
21510
+ break;
21511
+ case "ChainExpression":
21512
+ optionalChaining++;
21513
+ break;
21514
+ case "MemberExpression":
21515
+ if (node.optional) {
21516
+ optionalChaining++;
21517
+ }
21518
+ break;
21519
+ case "CallExpression":
21520
+ if (node.optional) {
21521
+ optionalChaining++;
21522
+ }
21523
+ break;
21524
+ case "LogicalExpression":
21525
+ if (node.operator === "??") {
21526
+ nullishCoalescing++;
21527
+ }
21528
+ break;
21529
+ case "TSTypeAliasDeclaration":
21530
+ typeGuards++;
21531
+ break;
21532
+ case "UnaryExpression":
21533
+ if (node.operator === "typeof") {
21534
+ typeofChecks++;
21535
+ }
21536
+ break;
21537
+ case "BinaryExpression":
21538
+ if (node.operator === "===" || node.operator === "!==" || node.operator === "==" || node.operator === "!=") {
21539
+ if (isNullOrUndefined(node.left) || isNullOrUndefined(node.right)) {
21540
+ nullChecks++;
21541
+ }
21542
+ }
21543
+ break;
21544
+ }
21545
+ traverseChildren(node);
21546
+ }
21547
+ function traverseChildren(node) {
21548
+ for (const key of Object.keys(node)) {
21549
+ if (key === "parent") continue;
21550
+ const child = node[key];
21551
+ if (child && typeof child === "object") {
21552
+ if (Array.isArray(child)) {
21553
+ for (const item of child) {
21554
+ if (item && typeof item === "object" && item.type) {
21555
+ traverse(item);
21556
+ }
21557
+ }
21558
+ } else if (child.type) {
21559
+ traverse(child);
21560
+ }
21561
+ }
21562
+ }
21563
+ }
21564
+ traverse(ast);
21565
+ return {
21566
+ functions,
21567
+ functionsWithTryCatch,
21568
+ optionalChaining,
21569
+ nullishCoalescing,
21570
+ typeGuards,
21571
+ typeofChecks,
21572
+ nullChecks
21573
+ };
21574
+ }
21575
+ function isNullOrUndefined(node) {
21576
+ if (node.type === "Literal" && node.value === null) return true;
21577
+ if (node.type === "Identifier" && node.name === "undefined") return true;
21578
+ return false;
21579
+ }
21580
+ var RobustnessAnalyzer = class {
21581
+ constructor(projectPath) {
21582
+ this.projectPath = projectPath;
21583
+ }
21584
+ /**
21585
+ * Analyze robustness of project files
21586
+ */
21587
+ async analyze(files) {
21588
+ const targetFiles = files ?? await this.findSourceFiles();
21589
+ let totalFunctions = 0;
21590
+ let totalFunctionsWithTryCatch = 0;
21591
+ let totalOptionalChaining = 0;
21592
+ let totalNullishCoalescing = 0;
21593
+ let totalTypeGuards = 0;
21594
+ let totalTypeofChecks = 0;
21595
+ let totalNullChecks = 0;
21596
+ for (const file of targetFiles) {
21597
+ try {
21598
+ const content = await readFile(file, "utf-8");
21599
+ const ast = parse(content, {
21600
+ loc: true,
21601
+ range: true,
21602
+ jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
21603
+ });
21604
+ const result = analyzeRobustnessPatterns(ast);
21605
+ totalFunctions += result.functions;
21606
+ totalFunctionsWithTryCatch += result.functionsWithTryCatch;
21607
+ totalOptionalChaining += result.optionalChaining;
21608
+ totalNullishCoalescing += result.nullishCoalescing;
21609
+ totalTypeGuards += result.typeGuards;
21610
+ totalTypeofChecks += result.typeofChecks;
21611
+ totalNullChecks += result.nullChecks;
21612
+ } catch {
21613
+ }
21614
+ }
21615
+ const tryCatchRatio = totalFunctions > 0 ? totalFunctionsWithTryCatch / totalFunctions * 100 : 0;
21616
+ const validationPerFunction = totalFunctions > 0 ? (totalTypeGuards + totalTypeofChecks + totalNullChecks) / totalFunctions : 0;
21617
+ const inputValidationScore = Math.min(100, validationPerFunction * 50);
21618
+ const defensivePerFunction = totalFunctions > 0 ? (totalOptionalChaining + totalNullishCoalescing) / totalFunctions : 0;
21619
+ const defensiveCodingScore = Math.min(100, defensivePerFunction * 40);
21620
+ const score = Math.round(
21621
+ Math.max(
21622
+ 0,
21623
+ Math.min(
21624
+ 100,
21625
+ tryCatchRatio * 0.4 + inputValidationScore * 0.3 + defensiveCodingScore * 0.3
21626
+ )
21627
+ )
21628
+ );
21629
+ const details = [
21630
+ `${totalFunctionsWithTryCatch}/${totalFunctions} functions with error handling`,
21631
+ `${totalOptionalChaining} optional chaining, ${totalNullishCoalescing} nullish coalescing`,
21632
+ `${totalTypeGuards + totalTypeofChecks + totalNullChecks} validation checks`
21633
+ ].join(", ");
21634
+ return {
21635
+ score,
21636
+ tryCatchRatio,
21637
+ inputValidationScore,
21638
+ defensiveCodingScore,
21639
+ functionsAnalyzed: totalFunctions,
21640
+ functionsWithErrorHandling: totalFunctionsWithTryCatch,
21641
+ optionalChainingCount: totalOptionalChaining,
21642
+ nullishCoalescingCount: totalNullishCoalescing,
21643
+ typeGuardCount: totalTypeGuards,
21644
+ details
21645
+ };
21646
+ }
21647
+ async findSourceFiles() {
21648
+ return glob("**/*.{ts,js,tsx,jsx}", {
21649
+ cwd: this.projectPath,
21650
+ absolute: true,
21651
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
21652
+ });
21653
+ }
21654
+ };
21655
+ var TRIVIAL_PATTERNS = [
21656
+ /\.toBeDefined\(\)/,
21657
+ /\.toBeUndefined\(\)/,
21658
+ /\.toBeTruthy\(\)/,
21659
+ /\.toBeFalsy\(\)/,
21660
+ /\.toBe\(true\)/,
21661
+ /\.toBe\(false\)/,
21662
+ /\.not\.toBeNull\(\)/,
21663
+ /\.toBeInstanceOf\(/,
21664
+ /\.toBeTypeOf\(/
21665
+ ];
21666
+ var EDGE_CASE_PATTERNS = [
21667
+ /error/i,
21668
+ /edge/i,
21669
+ /boundary/i,
21670
+ /invalid/i,
21671
+ /empty/i,
21672
+ /null/i,
21673
+ /undefined/i,
21674
+ /negative/i,
21675
+ /overflow/i,
21676
+ /timeout/i,
21677
+ /fail/i,
21678
+ /reject/i,
21679
+ /throw/i,
21680
+ /missing/i,
21681
+ /malformed/i,
21682
+ /corrupt/i
21683
+ ];
21684
+ var MATCHER_PATTERNS = [
21685
+ /\.toBe\(/,
21686
+ /\.toEqual\(/,
21687
+ /\.toStrictEqual\(/,
21688
+ /\.toContain\(/,
21689
+ /\.toMatch\(/,
21690
+ /\.toHaveLength\(/,
21691
+ /\.toHaveProperty\(/,
21692
+ /\.toThrow/,
21693
+ /\.toHaveBeenCalled/,
21694
+ /\.toHaveBeenCalledWith\(/,
21695
+ /\.toHaveBeenCalledTimes\(/,
21696
+ /\.toBeGreaterThan\(/,
21697
+ /\.toBeLessThan\(/,
21698
+ /\.toBeCloseTo\(/,
21699
+ /\.resolves\./,
21700
+ /\.rejects\./
21701
+ ];
21702
+ function analyzeTestFile(content) {
21703
+ const lines = content.split("\n");
21704
+ let totalAssertions = 0;
21705
+ let trivialAssertions = 0;
21706
+ let totalTests = 0;
21707
+ let edgeCaseTests = 0;
21708
+ const matchersUsed = /* @__PURE__ */ new Set();
21709
+ for (const line of lines) {
21710
+ const trimmed = line.trim();
21711
+ if (/^\s*(it|test)\s*\(/.test(trimmed) || /^\s*(it|test)\s*\./.test(trimmed)) {
21712
+ totalTests++;
21713
+ for (const pattern of EDGE_CASE_PATTERNS) {
21714
+ if (pattern.test(trimmed)) {
21715
+ edgeCaseTests++;
21716
+ break;
21717
+ }
21718
+ }
21719
+ }
21720
+ if (/expect\s*\(/.test(trimmed)) {
21721
+ totalAssertions++;
21722
+ for (const pattern of TRIVIAL_PATTERNS) {
21723
+ if (pattern.test(trimmed)) {
21724
+ trivialAssertions++;
21725
+ break;
21726
+ }
21727
+ }
21728
+ for (let i = 0; i < MATCHER_PATTERNS.length; i++) {
21729
+ if (MATCHER_PATTERNS[i].test(trimmed)) {
21730
+ matchersUsed.add(i);
21731
+ }
21732
+ }
21733
+ }
21734
+ }
21735
+ return { totalAssertions, trivialAssertions, totalTests, edgeCaseTests, matchersUsed };
21736
+ }
21737
+ var TestQualityAnalyzer = class {
21738
+ constructor(projectPath) {
21739
+ this.projectPath = projectPath;
21740
+ }
21741
+ /**
21742
+ * Analyze test quality across the project
21743
+ */
21744
+ async analyze(files) {
21745
+ const testFiles = files ?? await this.findTestFiles();
21746
+ let totalAssertions = 0;
21747
+ let trivialAssertions = 0;
21748
+ let totalTests = 0;
21749
+ let edgeCaseTests = 0;
21750
+ const allMatchersUsed = /* @__PURE__ */ new Set();
21751
+ for (const file of testFiles) {
21752
+ try {
21753
+ const content = await readFile(file, "utf-8");
21754
+ const result = analyzeTestFile(content);
21755
+ totalAssertions += result.totalAssertions;
21756
+ trivialAssertions += result.trivialAssertions;
21757
+ totalTests += result.totalTests;
21758
+ edgeCaseTests += result.edgeCaseTests;
21759
+ for (const m of result.matchersUsed) {
21760
+ allMatchersUsed.add(m);
21761
+ }
21762
+ } catch {
21763
+ }
21764
+ }
21765
+ const assertionDensity = totalTests > 0 ? totalAssertions / totalTests : 0;
21766
+ const trivialAssertionRatio = totalAssertions > 0 ? trivialAssertions / totalAssertions * 100 : 0;
21767
+ const edgeCaseRatio = totalTests > 0 ? edgeCaseTests / totalTests * 100 : 0;
21768
+ const assertionDiversity = allMatchersUsed.size / MATCHER_PATTERNS.length * 100;
21769
+ let score = 100;
21770
+ score -= Math.min(40, trivialAssertionRatio * 0.4);
21771
+ if (assertionDensity < 2) {
21772
+ score -= (2 - assertionDensity) * 10;
21773
+ }
21774
+ if (edgeCaseRatio > 10) {
21775
+ score += Math.min(10, (edgeCaseRatio - 10) * 0.5);
21776
+ }
21777
+ if (assertionDiversity > 30) {
21778
+ score += Math.min(10, (assertionDiversity - 30) * 0.15);
21779
+ }
21780
+ if (totalTests === 0) {
21781
+ score = 0;
21782
+ }
21783
+ score = Math.round(Math.max(0, Math.min(100, score)));
21784
+ const details = [
21785
+ `${totalTests} tests, ${totalAssertions} assertions`,
21786
+ `${assertionDensity.toFixed(1)} assertions/test`,
21787
+ `${trivialAssertionRatio.toFixed(1)}% trivial`,
21788
+ `${edgeCaseRatio.toFixed(1)}% edge cases`,
21789
+ `${allMatchersUsed.size}/${MATCHER_PATTERNS.length} matcher types`
21790
+ ].join(", ");
21791
+ return {
21792
+ score,
21793
+ assertionDensity,
21794
+ trivialAssertionRatio,
21795
+ edgeCaseRatio,
21796
+ assertionDiversity,
21797
+ totalTestFiles: testFiles.length,
21798
+ totalAssertions,
21799
+ trivialAssertions,
21800
+ edgeCaseTests,
21801
+ totalTests,
21802
+ details
21803
+ };
21804
+ }
21805
+ async findTestFiles() {
21806
+ return glob("**/*.{test,spec}.{ts,tsx,js,jsx}", {
21807
+ cwd: this.projectPath,
21808
+ absolute: true,
21809
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"]
21810
+ });
21811
+ }
21812
+ };
21813
+ function hasJSDocComment(node, sourceCode) {
21814
+ const startLine = node.loc.start.line;
21815
+ const lines = sourceCode.split("\n");
21816
+ for (let i = startLine - 2; i >= Math.max(0, startLine - 10); i--) {
21817
+ const line = lines[i]?.trim() ?? "";
21818
+ if (line.endsWith("*/")) {
21819
+ for (let j = i; j >= Math.max(0, i - 20); j--) {
21820
+ const commentLine = lines[j]?.trim() ?? "";
21821
+ if (commentLine.startsWith("/**")) return true;
21822
+ if (commentLine.startsWith("/*") && !commentLine.startsWith("/**")) return false;
21823
+ }
21824
+ }
21825
+ if (line && !line.startsWith("*") && !line.startsWith("//") && !line.startsWith("/*")) {
21826
+ break;
21827
+ }
21828
+ }
21829
+ return false;
21830
+ }
21831
+ function analyzeExportDocumentation(ast, sourceCode) {
21832
+ let exported = 0;
21833
+ let documented = 0;
21834
+ for (const node of ast.body) {
21835
+ if (node.type === "ExportNamedDeclaration" && node.declaration) {
21836
+ const decl = node.declaration;
21837
+ switch (decl.type) {
21838
+ case "FunctionDeclaration":
21839
+ exported++;
21840
+ if (hasJSDocComment(node, sourceCode)) documented++;
21841
+ break;
21842
+ case "ClassDeclaration":
21843
+ exported++;
21844
+ if (hasJSDocComment(node, sourceCode)) documented++;
21845
+ break;
21846
+ case "TSInterfaceDeclaration":
21847
+ case "TSTypeAliasDeclaration":
21848
+ case "TSEnumDeclaration":
21849
+ exported++;
21850
+ if (hasJSDocComment(node, sourceCode)) documented++;
21851
+ break;
21852
+ case "VariableDeclaration":
21853
+ for (const _declarator of decl.declarations) {
21854
+ exported++;
21855
+ if (hasJSDocComment(node, sourceCode)) documented++;
21856
+ }
21857
+ break;
21858
+ }
21859
+ } else if (node.type === "ExportDefaultDeclaration") {
21860
+ exported++;
21861
+ if (hasJSDocComment(node, sourceCode)) documented++;
21862
+ }
21863
+ }
21864
+ return { exported, documented };
21865
+ }
21866
+ var DocumentationAnalyzer = class {
21867
+ constructor(projectPath) {
21868
+ this.projectPath = projectPath;
21869
+ }
21870
+ /**
21871
+ * Analyze documentation quality
21872
+ */
21873
+ async analyze(files) {
21874
+ const targetFiles = files ?? await this.findSourceFiles();
21875
+ let totalExported = 0;
21876
+ let totalDocumented = 0;
21877
+ for (const file of targetFiles) {
21878
+ try {
21879
+ const content = await readFile(file, "utf-8");
21880
+ const ast = parse(content, {
21881
+ loc: true,
21882
+ range: true,
21883
+ comment: true,
21884
+ jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
21885
+ });
21886
+ const result = analyzeExportDocumentation(ast, content);
21887
+ totalExported += result.exported;
21888
+ totalDocumented += result.documented;
21889
+ } catch {
21890
+ }
21891
+ }
21892
+ const jsdocCoverage = totalExported > 0 ? totalDocumented / totalExported * 100 : 0;
21893
+ const hasReadme = await this.fileExists("README.md");
21894
+ const hasChangelog = await this.fileExists("CHANGELOG.md") || await this.fileExists("CHANGES.md");
21895
+ const score = Math.round(
21896
+ Math.max(
21897
+ 0,
21898
+ Math.min(100, jsdocCoverage * 0.7 + (hasReadme ? 20 : 0) + (hasChangelog ? 10 : 0))
21899
+ )
21900
+ );
21901
+ const details = [
21902
+ `${totalDocumented}/${totalExported} exports documented (${jsdocCoverage.toFixed(1)}%)`,
21903
+ `README: ${hasReadme ? "yes" : "no"}`,
21904
+ `CHANGELOG: ${hasChangelog ? "yes" : "no"}`
21905
+ ].join(", ");
21906
+ return {
21907
+ score,
21908
+ jsdocCoverage,
21909
+ hasReadme,
21910
+ hasChangelog,
21911
+ exportedDeclarations: totalExported,
21912
+ documentedDeclarations: totalDocumented,
21913
+ details
21914
+ };
21915
+ }
21916
+ async fileExists(relativePath) {
21917
+ try {
21918
+ await access(join(this.projectPath, relativePath), constants.R_OK);
21919
+ return true;
21920
+ } catch {
21921
+ return false;
21922
+ }
21923
+ }
21924
+ async findSourceFiles() {
21925
+ return glob("**/*.{ts,js,tsx,jsx}", {
21926
+ cwd: this.projectPath,
21927
+ absolute: true,
21928
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
21929
+ });
21930
+ }
21931
+ };
21932
+ async function detectLinter(projectPath) {
21933
+ try {
21934
+ const pkgContent = await readFile(join(projectPath, "package.json"), "utf-8");
21935
+ const pkg = JSON.parse(pkgContent);
21936
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
21937
+ if (deps.oxlint) return "oxlint";
21938
+ if (deps["@biomejs/biome"] || deps.biome) return "biome";
21939
+ if (deps.eslint) return "eslint";
21940
+ return null;
21941
+ } catch {
21942
+ return null;
21943
+ }
21944
+ }
21945
+ async function runOxlint(projectPath) {
21946
+ try {
21947
+ const result = await execa("npx", ["oxlint", "src", "--format=json"], {
21948
+ cwd: projectPath,
21949
+ reject: false,
21950
+ timeout: 6e4
21951
+ });
21952
+ try {
21953
+ const output = JSON.parse(result.stdout);
21954
+ const errors = Array.isArray(output) ? output.filter((d) => {
21955
+ const diag = d;
21956
+ return diag.severity === 2 || diag.severity === "error";
21957
+ }).length : 0;
21958
+ const warnings = Array.isArray(output) ? output.filter((d) => {
21959
+ const diag = d;
21960
+ return diag.severity === 1 || diag.severity === "warning";
21961
+ }).length : 0;
21962
+ return { errors, warnings };
21963
+ } catch {
21964
+ const lines = (result.stdout + result.stderr).split("\n");
21965
+ let errors = 0;
21966
+ let warnings = 0;
21967
+ for (const line of lines) {
21968
+ if (/error\[/.test(line) || /✖.*error/.test(line)) errors++;
21969
+ if (/warning\[/.test(line) || /⚠.*warning/.test(line)) warnings++;
21970
+ }
21971
+ return { errors, warnings };
21972
+ }
21973
+ } catch {
21974
+ return { errors: 0, warnings: 0 };
21975
+ }
21976
+ }
21977
+ async function runEslint(projectPath) {
21978
+ try {
21979
+ const result = await execa("npx", ["eslint", "src", "--format=json"], {
21980
+ cwd: projectPath,
21981
+ reject: false,
21982
+ timeout: 12e4
21983
+ });
21984
+ try {
21985
+ const output = JSON.parse(result.stdout);
21986
+ const errors = output.reduce((sum, f) => sum + f.errorCount, 0);
21987
+ const warnings = output.reduce((sum, f) => sum + f.warningCount, 0);
21988
+ return { errors, warnings };
21989
+ } catch {
21990
+ return { errors: 0, warnings: 0 };
21991
+ }
21992
+ } catch {
21993
+ return { errors: 0, warnings: 0 };
21994
+ }
21995
+ }
21996
+ async function runBiome(projectPath) {
21997
+ try {
21998
+ const result = await execa("npx", ["@biomejs/biome", "lint", "src", "--reporter=json"], {
21999
+ cwd: projectPath,
22000
+ reject: false,
22001
+ timeout: 6e4
22002
+ });
22003
+ try {
22004
+ const output = JSON.parse(result.stdout);
22005
+ const diagnostics = output.diagnostics ?? [];
22006
+ const errors = diagnostics.filter(
22007
+ (d) => d.severity === "error"
22008
+ ).length;
22009
+ const warnings = diagnostics.filter(
22010
+ (d) => d.severity === "warning"
22011
+ ).length;
22012
+ return { errors, warnings };
22013
+ } catch {
22014
+ return { errors: 0, warnings: 0 };
22015
+ }
22016
+ } catch {
22017
+ return { errors: 0, warnings: 0 };
22018
+ }
22019
+ }
22020
+ var StyleAnalyzer = class {
22021
+ constructor(projectPath) {
22022
+ this.projectPath = projectPath;
22023
+ }
22024
+ /**
22025
+ * Analyze style/linting quality
22026
+ */
22027
+ async analyze() {
22028
+ const linter = await detectLinter(this.projectPath);
22029
+ if (!linter) {
22030
+ return {
22031
+ score: 50,
22032
+ // Neutral score when no linter is configured
22033
+ errors: 0,
22034
+ warnings: 0,
22035
+ linterUsed: null,
22036
+ details: "No linter configured"
22037
+ };
22038
+ }
22039
+ let result;
22040
+ switch (linter) {
22041
+ case "oxlint":
22042
+ result = await runOxlint(this.projectPath);
22043
+ break;
22044
+ case "eslint":
22045
+ result = await runEslint(this.projectPath);
22046
+ break;
22047
+ case "biome":
22048
+ result = await runBiome(this.projectPath);
22049
+ break;
22050
+ }
22051
+ const score = Math.round(
22052
+ Math.max(0, Math.min(100, 100 - result.errors * 5 - result.warnings * 2))
22053
+ );
22054
+ const details = [
22055
+ `Linter: ${linter}`,
22056
+ `${result.errors} errors, ${result.warnings} warnings`
22057
+ ].join(", ");
22058
+ return {
22059
+ score,
22060
+ errors: result.errors,
22061
+ warnings: result.warnings,
22062
+ linterUsed: linter,
22063
+ details
22064
+ };
22065
+ }
22066
+ };
22067
+ function analyzeReadabilityPatterns(ast) {
22068
+ let goodNames = 0;
22069
+ let totalNames = 0;
22070
+ const functionLengths = [];
22071
+ let maxNestingDepth = 0;
22072
+ const cognitiveComplexities = [];
22073
+ const allowedSingleChar = /* @__PURE__ */ new Set(["i", "j", "k", "_", "e", "x", "y", "t", "n"]);
22074
+ function isGoodName(name) {
22075
+ if (name.length === 1) {
22076
+ return allowedSingleChar.has(name);
22077
+ }
22078
+ return name.length >= 3 && name.length <= 30;
22079
+ }
22080
+ function analyzeFunction(node) {
22081
+ if (node.loc) {
22082
+ const length = node.loc.end.line - node.loc.start.line + 1;
22083
+ functionLengths.push(length);
22084
+ }
22085
+ let localMaxNesting = 0;
22086
+ let cognitiveComplexity = 0;
22087
+ function traverseForComplexity(n, currentNesting) {
22088
+ localMaxNesting = Math.max(localMaxNesting, currentNesting);
22089
+ let nextNesting = currentNesting;
22090
+ let incrementsNesting = false;
22091
+ switch (n.type) {
22092
+ case "IfStatement":
22093
+ cognitiveComplexity += 1;
22094
+ incrementsNesting = true;
22095
+ break;
22096
+ case "ForStatement":
22097
+ case "ForInStatement":
22098
+ case "ForOfStatement":
22099
+ case "WhileStatement":
22100
+ case "DoWhileStatement":
22101
+ cognitiveComplexity += 1 + currentNesting;
22102
+ incrementsNesting = true;
22103
+ break;
22104
+ case "SwitchStatement":
22105
+ cognitiveComplexity += 1 + currentNesting;
22106
+ incrementsNesting = true;
22107
+ break;
22108
+ case "CatchClause":
22109
+ cognitiveComplexity += 1 + currentNesting;
22110
+ incrementsNesting = true;
22111
+ break;
22112
+ case "ConditionalExpression":
22113
+ cognitiveComplexity += 1 + currentNesting;
22114
+ incrementsNesting = true;
22115
+ break;
22116
+ case "LogicalExpression":
22117
+ if (n.operator === "&&" || n.operator === "||") {
22118
+ cognitiveComplexity += 1;
22119
+ }
22120
+ break;
22121
+ case "FunctionExpression":
22122
+ case "ArrowFunctionExpression":
22123
+ incrementsNesting = true;
22124
+ break;
22125
+ }
22126
+ if (incrementsNesting) {
22127
+ nextNesting = currentNesting + 1;
22128
+ }
22129
+ traverseChildren(n, (child) => traverseForComplexity(child, nextNesting));
22130
+ }
22131
+ traverseForComplexity(node, 0);
22132
+ maxNestingDepth = Math.max(maxNestingDepth, localMaxNesting);
22133
+ cognitiveComplexities.push(cognitiveComplexity);
22134
+ }
22135
+ function collectNames(node) {
22136
+ switch (node.type) {
22137
+ case "FunctionDeclaration":
22138
+ if (node.id) {
22139
+ totalNames++;
22140
+ if (isGoodName(node.id.name)) {
22141
+ goodNames++;
22142
+ }
22143
+ }
22144
+ for (const param of node.params) {
22145
+ collectParamNames(param);
22146
+ }
22147
+ break;
22148
+ case "FunctionExpression":
22149
+ case "ArrowFunctionExpression":
22150
+ for (const param of node.params) {
22151
+ collectParamNames(param);
22152
+ }
22153
+ break;
22154
+ case "VariableDeclarator":
22155
+ if (node.id.type === "Identifier") {
22156
+ totalNames++;
22157
+ if (isGoodName(node.id.name)) {
22158
+ goodNames++;
22159
+ }
22160
+ }
22161
+ break;
22162
+ case "MethodDefinition":
22163
+ if (node.key.type === "Identifier") {
22164
+ totalNames++;
22165
+ if (isGoodName(node.key.name)) {
22166
+ goodNames++;
22167
+ }
22168
+ }
22169
+ if (node.value.type === "FunctionExpression") {
22170
+ for (const param of node.value.params) {
22171
+ collectParamNames(param);
22172
+ }
22173
+ }
22174
+ break;
22175
+ }
22176
+ }
22177
+ function collectParamNames(param) {
22178
+ if (param.type === "Identifier") {
22179
+ totalNames++;
22180
+ if (isGoodName(param.name)) {
22181
+ goodNames++;
22182
+ }
22183
+ } else if (param.type === "AssignmentPattern" && param.left.type === "Identifier") {
22184
+ totalNames++;
22185
+ if (isGoodName(param.left.name)) {
22186
+ goodNames++;
22187
+ }
22188
+ } else if (param.type === "RestElement" && param.argument.type === "Identifier") {
22189
+ totalNames++;
22190
+ if (isGoodName(param.argument.name)) {
22191
+ goodNames++;
22192
+ }
22193
+ }
22194
+ }
22195
+ function traverse(node) {
22196
+ const isFunctionNode = node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "MethodDefinition";
22197
+ collectNames(node);
22198
+ if (isFunctionNode) {
22199
+ analyzeFunction(node);
22200
+ }
22201
+ traverseChildren(node, traverse);
22202
+ }
22203
+ function traverseChildren(node, callback) {
22204
+ for (const key of Object.keys(node)) {
22205
+ if (key === "parent") continue;
22206
+ const child = node[key];
22207
+ if (child && typeof child === "object") {
22208
+ if (Array.isArray(child)) {
22209
+ for (const item of child) {
22210
+ if (item && typeof item === "object" && item.type) {
22211
+ callback(item);
22212
+ }
22213
+ }
22214
+ } else if (child.type) {
22215
+ callback(child);
22216
+ }
22217
+ }
22218
+ }
22219
+ }
22220
+ traverse(ast);
22221
+ return {
22222
+ goodNames,
22223
+ totalNames,
22224
+ functionLengths,
22225
+ maxNestingDepth,
22226
+ cognitiveComplexities
22227
+ };
22228
+ }
22229
+ var ReadabilityAnalyzer = class {
22230
+ constructor(projectPath) {
22231
+ this.projectPath = projectPath;
22232
+ }
22233
+ /**
22234
+ * Analyze readability of project files
22235
+ */
22236
+ async analyze(files) {
22237
+ const targetFiles = files ?? await this.findSourceFiles();
22238
+ let totalGoodNames = 0;
22239
+ let totalNames = 0;
22240
+ const allFunctionLengths = [];
22241
+ let globalMaxNestingDepth = 0;
22242
+ const allCognitiveComplexities = [];
22243
+ for (const file of targetFiles) {
22244
+ try {
22245
+ const content = await readFile(file, "utf-8");
22246
+ const ast = parse(content, {
22247
+ loc: true,
22248
+ range: true,
22249
+ jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
22250
+ });
22251
+ const result = analyzeReadabilityPatterns(ast);
22252
+ totalGoodNames += result.goodNames;
22253
+ totalNames += result.totalNames;
22254
+ allFunctionLengths.push(...result.functionLengths);
22255
+ globalMaxNestingDepth = Math.max(globalMaxNestingDepth, result.maxNestingDepth);
22256
+ allCognitiveComplexities.push(...result.cognitiveComplexities);
22257
+ } catch {
22258
+ }
22259
+ }
22260
+ const namingScore = totalNames > 0 ? totalGoodNames / totalNames * 100 : 100;
22261
+ const averageFunctionLength = allFunctionLengths.length > 0 ? allFunctionLengths.reduce((a, b) => a + b, 0) / allFunctionLengths.length : 0;
22262
+ const functionLengthScore = allFunctionLengths.length > 0 ? Math.max(0, Math.min(100, 100 - (averageFunctionLength - 20) * 2.5)) : 100;
22263
+ const maxNestingDepth = globalMaxNestingDepth;
22264
+ const nestingDepthScore = Math.max(0, Math.min(100, 100 - (maxNestingDepth - 2) * 20));
22265
+ const averageCognitiveComplexity = allCognitiveComplexities.length > 0 ? allCognitiveComplexities.reduce((a, b) => a + b, 0) / allCognitiveComplexities.length : 0;
22266
+ const cognitiveComplexityScore = allCognitiveComplexities.length > 0 ? Math.max(0, Math.min(100, 100 - (averageCognitiveComplexity - 5) * 5)) : 100;
22267
+ const score = Math.round(
22268
+ namingScore * 0.25 + functionLengthScore * 0.25 + nestingDepthScore * 0.25 + cognitiveComplexityScore * 0.25
22269
+ );
22270
+ const details = [
22271
+ `${totalGoodNames}/${totalNames} good names`,
22272
+ `avg func ${averageFunctionLength.toFixed(1)} LOC`,
22273
+ `max nesting ${maxNestingDepth}`,
22274
+ `avg weighted complexity ${averageCognitiveComplexity.toFixed(1)}`
22275
+ ].join(", ");
22276
+ return {
22277
+ score,
22278
+ namingScore,
22279
+ functionLengthScore,
22280
+ nestingDepthScore,
22281
+ cognitiveComplexityScore,
22282
+ averageFunctionLength,
22283
+ maxNestingDepth,
22284
+ averageCognitiveComplexity,
22285
+ details
22286
+ };
22287
+ }
22288
+ async findSourceFiles() {
22289
+ return glob("**/*.{ts,js,tsx,jsx}", {
22290
+ cwd: this.projectPath,
22291
+ absolute: true,
22292
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
22293
+ });
22294
+ }
22295
+ };
22296
+ function analyzeMaintainabilityPatterns(ast, filePath) {
22297
+ let functionCount = 0;
22298
+ let importCount = 0;
22299
+ let crossBoundaryImportCount = 0;
22300
+ function traverse(node) {
22301
+ switch (node.type) {
22302
+ case "FunctionDeclaration":
22303
+ case "FunctionExpression":
22304
+ case "ArrowFunctionExpression":
22305
+ case "MethodDefinition":
22306
+ functionCount++;
22307
+ break;
22308
+ case "ImportDeclaration":
22309
+ importCount++;
22310
+ if (isCrossBoundaryImport(node)) {
22311
+ crossBoundaryImportCount++;
22312
+ }
22313
+ break;
22314
+ }
22315
+ traverseChildren(node);
22316
+ }
22317
+ function traverseChildren(node) {
22318
+ for (const key of Object.keys(node)) {
22319
+ if (key === "parent") continue;
22320
+ const child = node[key];
22321
+ if (child && typeof child === "object") {
22322
+ if (Array.isArray(child)) {
22323
+ for (const item of child) {
22324
+ if (item && typeof item === "object" && item.type) {
22325
+ traverse(item);
22326
+ }
22327
+ }
22328
+ } else if (child.type) {
22329
+ traverse(child);
22330
+ }
22331
+ }
22332
+ }
22333
+ }
22334
+ traverse(ast);
22335
+ return {
22336
+ lineCount: 0,
22337
+ // Will be set separately from content
22338
+ functionCount,
22339
+ importCount,
22340
+ crossBoundaryImportCount
22341
+ };
22342
+ }
22343
+ function isCrossBoundaryImport(node, _filePath) {
22344
+ if (node.source.type !== "Literal" || typeof node.source.value !== "string") {
22345
+ return false;
22346
+ }
22347
+ const importPath = node.source.value;
22348
+ if (importPath.startsWith("node:")) {
22349
+ return false;
22350
+ }
22351
+ if (!importPath.startsWith(".")) {
22352
+ return false;
22353
+ }
22354
+ if (importPath.startsWith("../")) {
22355
+ return true;
22356
+ }
22357
+ if (importPath.startsWith("./")) {
22358
+ const relativePath = importPath.slice(2);
22359
+ return relativePath.includes("/");
22360
+ }
22361
+ return false;
22362
+ }
22363
+ function countLines(content) {
22364
+ return content.split("\n").length;
22365
+ }
22366
+ var MaintainabilityAnalyzer = class {
22367
+ constructor(projectPath) {
22368
+ this.projectPath = projectPath;
22369
+ }
22370
+ /**
22371
+ * Analyze maintainability of project files
22372
+ */
22373
+ async analyze(files) {
22374
+ const targetFiles = files ?? await this.findSourceFiles();
22375
+ if (targetFiles.length === 0) {
22376
+ return {
22377
+ score: 100,
22378
+ fileLengthScore: 100,
22379
+ functionCountScore: 100,
22380
+ dependencyCountScore: 100,
22381
+ couplingScore: 100,
22382
+ averageFileLength: 0,
22383
+ averageFunctionsPerFile: 0,
22384
+ averageImportsPerFile: 0,
22385
+ fileCount: 0,
22386
+ details: "No files to analyze"
22387
+ };
22388
+ }
22389
+ let totalLines = 0;
22390
+ let totalFunctions = 0;
22391
+ let totalImports = 0;
22392
+ let totalCrossBoundaryImports = 0;
22393
+ let filesAnalyzed = 0;
22394
+ for (const file of targetFiles) {
22395
+ try {
22396
+ const content = await readFile(file, "utf-8");
22397
+ const lineCount = countLines(content);
22398
+ const ast = parse(content, {
22399
+ loc: true,
22400
+ range: true,
22401
+ jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
22402
+ });
22403
+ const result = analyzeMaintainabilityPatterns(ast, file);
22404
+ totalLines += lineCount;
22405
+ totalFunctions += result.functionCount;
22406
+ totalImports += result.importCount;
22407
+ totalCrossBoundaryImports += result.crossBoundaryImportCount;
22408
+ filesAnalyzed++;
22409
+ } catch {
22410
+ }
22411
+ }
22412
+ const averageFileLength = filesAnalyzed > 0 ? totalLines / filesAnalyzed : 0;
22413
+ const averageFunctionsPerFile = filesAnalyzed > 0 ? totalFunctions / filesAnalyzed : 0;
22414
+ const averageImportsPerFile = filesAnalyzed > 0 ? totalImports / filesAnalyzed : 0;
22415
+ const fileLengthScore = Math.max(0, Math.min(100, 100 - (averageFileLength - 200) * 0.33));
22416
+ const functionCountScore = Math.max(0, Math.min(100, 100 - (averageFunctionsPerFile - 10) * 5));
22417
+ const dependencyCountScore = Math.max(0, Math.min(100, 100 - (averageImportsPerFile - 5) * 5));
22418
+ const crossBoundaryRatio = totalImports > 0 ? totalCrossBoundaryImports / totalImports : 0;
22419
+ const couplingScore = Math.max(0, Math.min(100, 100 - crossBoundaryRatio * 100 * 0.5));
22420
+ const score = Math.round(
22421
+ fileLengthScore * 0.3 + functionCountScore * 0.25 + dependencyCountScore * 0.25 + couplingScore * 0.2
22422
+ );
22423
+ const details = [
22424
+ `${Math.round(averageFileLength)} avg lines/file`,
22425
+ `${averageFunctionsPerFile.toFixed(1)} avg functions/file`,
22426
+ `${averageImportsPerFile.toFixed(1)} avg imports/file`,
22427
+ `${Math.round(crossBoundaryRatio * 100)}% cross-boundary coupling`
22428
+ ].join(", ");
22429
+ return {
22430
+ score,
22431
+ fileLengthScore,
22432
+ functionCountScore,
22433
+ dependencyCountScore,
22434
+ couplingScore,
22435
+ averageFileLength,
22436
+ averageFunctionsPerFile,
22437
+ averageImportsPerFile,
22438
+ fileCount: filesAnalyzed,
22439
+ details
22440
+ };
22441
+ }
22442
+ async findSourceFiles() {
22443
+ return glob("**/*.{ts,js,tsx,jsx}", {
22444
+ cwd: this.projectPath,
22445
+ absolute: true,
22446
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
22447
+ });
22448
+ }
22449
+ };
22450
+
22451
+ // src/quality/types.ts
22452
+ var DEFAULT_QUALITY_WEIGHTS = {
22453
+ correctness: 0.15,
22454
+ completeness: 0.1,
22455
+ robustness: 0.1,
22456
+ readability: 0.1,
22457
+ maintainability: 0.1,
22458
+ complexity: 0.08,
22459
+ duplication: 0.07,
22460
+ testCoverage: 0.1,
22461
+ testQuality: 0.05,
22462
+ security: 0.08,
22463
+ documentation: 0.04,
22464
+ style: 0.03
22465
+ };
22466
+ var DEFAULT_QUALITY_THRESHOLDS = {
22467
+ minimum: {
22468
+ overall: 85,
22469
+ testCoverage: 80,
22470
+ security: 100
22471
+ // No vulnerabilities allowed
22472
+ },
22473
+ target: {
22474
+ overall: 95,
22475
+ testCoverage: 90
22476
+ }};
22477
+ var QualityEvaluator = class {
22478
+ constructor(projectPath, useSnyk = false) {
22479
+ this.projectPath = projectPath;
22480
+ this.coverageAnalyzer = new CoverageAnalyzer(projectPath);
22481
+ this.securityScanner = new CompositeSecurityScanner(projectPath, useSnyk);
22482
+ this.complexityAnalyzer = new ComplexityAnalyzer(projectPath);
22483
+ this.duplicationAnalyzer = new DuplicationAnalyzer(projectPath);
22484
+ this.correctnessAnalyzer = new CorrectnessAnalyzer(projectPath);
22485
+ this.completenessAnalyzer = new CompletenessAnalyzer(projectPath);
22486
+ this.robustnessAnalyzer = new RobustnessAnalyzer(projectPath);
22487
+ this.testQualityAnalyzer = new TestQualityAnalyzer(projectPath);
22488
+ this.documentationAnalyzer = new DocumentationAnalyzer(projectPath);
22489
+ this.styleAnalyzer = new StyleAnalyzer(projectPath);
22490
+ this.readabilityAnalyzer = new ReadabilityAnalyzer(projectPath);
22491
+ this.maintainabilityAnalyzer = new MaintainabilityAnalyzer(projectPath);
22492
+ }
22493
+ coverageAnalyzer;
22494
+ securityScanner;
22495
+ complexityAnalyzer;
22496
+ duplicationAnalyzer;
22497
+ correctnessAnalyzer;
22498
+ completenessAnalyzer;
22499
+ robustnessAnalyzer;
22500
+ testQualityAnalyzer;
22501
+ documentationAnalyzer;
22502
+ styleAnalyzer;
22503
+ readabilityAnalyzer;
22504
+ maintainabilityAnalyzer;
22505
+ /**
22506
+ * Evaluate quality across all 12 dimensions
22507
+ * Every dimension is computed by real static analysis — zero hardcoded values
22508
+ */
22509
+ async evaluate(files) {
22510
+ const startTime = performance.now();
22511
+ const targetFiles = files ?? await this.findSourceFiles();
22512
+ const fileContents = await Promise.all(
22513
+ targetFiles.map(async (file) => ({
22514
+ path: file,
22515
+ content: await readFile(file, "utf-8")
22516
+ }))
22517
+ );
22518
+ const [
22519
+ coverageResult,
22520
+ securityResult,
22521
+ complexityResult,
22522
+ duplicationResult,
22523
+ correctnessResult,
22524
+ completenessResult,
22525
+ robustnessResult,
22526
+ testQualityResult,
22527
+ documentationResult,
22528
+ styleResult,
22529
+ readabilityResult,
22530
+ maintainabilityResult
22531
+ ] = await Promise.all([
22532
+ this.coverageAnalyzer.analyze().catch(() => null),
22533
+ this.securityScanner.scan(fileContents).catch(() => ({ score: 0, vulnerabilities: [] })),
22534
+ this.complexityAnalyzer.analyze(targetFiles).catch(() => ({ score: 0, files: [] })),
22535
+ this.duplicationAnalyzer.analyze(targetFiles).catch(() => ({ score: 0, percentage: 0 })),
22536
+ this.correctnessAnalyzer.analyze().catch(() => ({ score: 0 })),
22537
+ this.completenessAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
22538
+ this.robustnessAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
22539
+ this.testQualityAnalyzer.analyze().catch(() => ({ score: 0 })),
22540
+ this.documentationAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
22541
+ this.styleAnalyzer.analyze().catch(() => ({ score: 0 })),
22542
+ this.readabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
22543
+ this.maintainabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 }))
22544
+ ]);
22545
+ const dimensions = {
22546
+ testCoverage: coverageResult?.lines.percentage ?? 0,
22547
+ security: securityResult.score,
22548
+ complexity: complexityResult.score,
22549
+ duplication: Math.max(0, 100 - duplicationResult.percentage),
22550
+ style: styleResult.score,
22551
+ readability: readabilityResult.score,
22552
+ maintainability: maintainabilityResult.score,
22553
+ correctness: correctnessResult.score,
22554
+ completeness: completenessResult.score,
22555
+ robustness: robustnessResult.score,
22556
+ testQuality: testQualityResult.score,
22557
+ documentation: documentationResult.score
22558
+ };
22559
+ const overall = Object.entries(dimensions).reduce((sum, [key, value]) => {
22560
+ const weight = DEFAULT_QUALITY_WEIGHTS[key] ?? 0;
22561
+ return sum + value * weight;
22562
+ }, 0);
22563
+ const scores = {
22564
+ overall: Math.round(overall),
22565
+ dimensions,
22566
+ evaluatedAt: /* @__PURE__ */ new Date(),
22567
+ evaluationDurationMs: performance.now() - startTime
22568
+ };
22569
+ const issues = this.generateIssues(
22570
+ securityResult.vulnerabilities,
22571
+ complexityResult,
22572
+ duplicationResult,
22573
+ correctnessResult,
22574
+ styleResult,
22575
+ documentationResult
22576
+ );
22577
+ const suggestions = this.generateSuggestions(dimensions);
22578
+ const meetsMinimum = scores.overall >= DEFAULT_QUALITY_THRESHOLDS.minimum.overall && dimensions.testCoverage >= DEFAULT_QUALITY_THRESHOLDS.minimum.testCoverage && dimensions.security >= DEFAULT_QUALITY_THRESHOLDS.minimum.security;
22579
+ const meetsTarget = scores.overall >= DEFAULT_QUALITY_THRESHOLDS.target.overall && dimensions.testCoverage >= DEFAULT_QUALITY_THRESHOLDS.target.testCoverage;
22580
+ return {
22581
+ scores,
22582
+ meetsMinimum,
22583
+ meetsTarget,
22584
+ converged: false,
22585
+ issues,
22586
+ suggestions
22587
+ };
22588
+ }
22589
+ /**
22590
+ * Generate quality issues from analyzer results
22591
+ */
22592
+ generateIssues(securityVulns, complexityResult, duplicationResult, correctnessResult, styleResult, documentationResult) {
22593
+ const issues = [];
22594
+ for (const vuln of securityVulns) {
20643
22595
  issues.push({
20644
22596
  dimension: "security",
20645
22597
  severity: vuln.severity === "critical" ? "critical" : vuln.severity === "high" ? "major" : "minor",
@@ -20670,6 +22622,38 @@ var QualityEvaluator = class {
20670
22622
  suggestion: "Extract common code into reusable functions or modules"
20671
22623
  });
20672
22624
  }
22625
+ if (correctnessResult.testsFailed != null && correctnessResult.testsFailed > 0) {
22626
+ issues.push({
22627
+ dimension: "correctness",
22628
+ severity: "critical",
22629
+ message: `${correctnessResult.testsFailed} tests failing`,
22630
+ suggestion: "Fix failing tests to improve correctness score"
22631
+ });
22632
+ }
22633
+ if (correctnessResult.buildSuccess === false) {
22634
+ issues.push({
22635
+ dimension: "correctness",
22636
+ severity: "critical",
22637
+ message: "Build/type check failed",
22638
+ suggestion: "Fix type errors to pass build verification"
22639
+ });
22640
+ }
22641
+ if (styleResult.errors != null && styleResult.errors > 0) {
22642
+ issues.push({
22643
+ dimension: "style",
22644
+ severity: "minor",
22645
+ message: `${styleResult.errors} linting errors found`,
22646
+ suggestion: "Run linter with --fix to auto-correct style issues"
22647
+ });
22648
+ }
22649
+ if (documentationResult.jsdocCoverage !== void 0 && documentationResult.jsdocCoverage < 50) {
22650
+ issues.push({
22651
+ dimension: "documentation",
22652
+ severity: "minor",
22653
+ message: `Low JSDoc coverage: ${documentationResult.jsdocCoverage?.toFixed(1) ?? 0}%`,
22654
+ suggestion: "Add JSDoc comments to exported functions and classes"
22655
+ });
22656
+ }
20673
22657
  return issues;
20674
22658
  }
20675
22659
  /**
@@ -20693,6 +22677,14 @@ var QualityEvaluator = class {
20693
22677
  estimatedImpact: 100 - dimensions.security
20694
22678
  });
20695
22679
  }
22680
+ if (dimensions.correctness < 85) {
22681
+ suggestions.push({
22682
+ dimension: "correctness",
22683
+ priority: "high",
22684
+ description: "Fix failing tests and build errors",
22685
+ estimatedImpact: 85 - dimensions.correctness
22686
+ });
22687
+ }
20696
22688
  if (dimensions.complexity < 80) {
20697
22689
  suggestions.push({
20698
22690
  dimension: "complexity",
@@ -20701,6 +22693,22 @@ var QualityEvaluator = class {
20701
22693
  estimatedImpact: Math.min(10, 80 - dimensions.complexity)
20702
22694
  });
20703
22695
  }
22696
+ if (dimensions.documentation < 60) {
22697
+ suggestions.push({
22698
+ dimension: "documentation",
22699
+ priority: "medium",
22700
+ description: "Add JSDoc comments to exported declarations",
22701
+ estimatedImpact: Math.min(15, 60 - dimensions.documentation)
22702
+ });
22703
+ }
22704
+ if (dimensions.testQuality < 70) {
22705
+ suggestions.push({
22706
+ dimension: "testQuality",
22707
+ priority: "medium",
22708
+ description: "Replace trivial assertions with meaningful behavioral tests",
22709
+ estimatedImpact: Math.min(10, 70 - dimensions.testQuality)
22710
+ });
22711
+ }
20704
22712
  if (dimensions.duplication < 95) {
20705
22713
  suggestions.push({
20706
22714
  dimension: "duplication",
@@ -20727,7 +22735,7 @@ function createQualityEvaluator(projectPath, useSnyk) {
20727
22735
  }
20728
22736
 
20729
22737
  // src/tools/quality.ts
20730
- async function detectLinter(cwd) {
22738
+ async function detectLinter2(cwd) {
20731
22739
  try {
20732
22740
  const pkgPath = path20__default.join(cwd, "package.json");
20733
22741
  const pkgContent = await fs22__default.readFile(pkgPath, "utf-8");
@@ -20762,7 +22770,7 @@ Examples:
20762
22770
  }),
20763
22771
  async execute({ cwd, files, fix, linter }) {
20764
22772
  const projectDir = cwd ?? process.cwd();
20765
- const detectedLinter = linter ?? await detectLinter(projectDir);
22773
+ const detectedLinter = linter ?? await detectLinter2(projectDir);
20766
22774
  if (!detectedLinter) {
20767
22775
  return {
20768
22776
  errors: 0,
@@ -20917,8 +22925,8 @@ Examples:
20917
22925
  }
20918
22926
  });
20919
22927
  async function findSourceFiles(cwd) {
20920
- const { glob: glob9 } = await import('glob');
20921
- return glob9("src/**/*.{ts,js,tsx,jsx}", {
22928
+ const { glob: glob15 } = await import('glob');
22929
+ return glob15("src/**/*.{ts,js,tsx,jsx}", {
20922
22930
  cwd,
20923
22931
  absolute: true,
20924
22932
  ignore: ["**/*.test.*", "**/*.spec.*", "**/node_modules/**"]
@@ -21046,7 +23054,17 @@ Examples:
21046
23054
  regexPattern = `\\b${pattern}\\b`;
21047
23055
  }
21048
23056
  const flags = caseSensitive ? "g" : "gi";
21049
- const regex = new RegExp(regexPattern, flags);
23057
+ let regex;
23058
+ try {
23059
+ regex = new RegExp(regexPattern, flags);
23060
+ } catch {
23061
+ throw new ToolError(`Invalid regex pattern: ${pattern}`, { tool: "grep" });
23062
+ }
23063
+ if (/(\.\*|\.\+|\[.*\][*+])\s*(\.\*|\.\+|\[.*\][*+])/.test(regexPattern)) {
23064
+ throw new ToolError(`Regex pattern rejected: nested quantifiers may cause slow matching`, {
23065
+ tool: "grep"
23066
+ });
23067
+ }
21050
23068
  const stats = await fs22__default.stat(targetPath);
21051
23069
  let filesToSearch;
21052
23070
  if (stats.isFile()) {
@@ -22184,9 +24202,11 @@ function parseDiff(raw) {
22184
24202
  function parseFileBlock(lines, start) {
22185
24203
  const diffLine = lines[start];
22186
24204
  let i = start + 1;
22187
- const pathMatch = diffLine.match(/^diff --git a\/(.+?) b\/(.+)$/);
22188
- const oldPath = pathMatch?.[1] ?? "";
22189
- const newPath = pathMatch?.[2] ?? oldPath;
24205
+ const gitPrefix = "diff --git a/";
24206
+ const pathPart = diffLine.slice(gitPrefix.length);
24207
+ const lastBSlash = pathPart.lastIndexOf(" b/");
24208
+ const oldPath = lastBSlash >= 0 ? pathPart.slice(0, lastBSlash) : pathPart;
24209
+ const newPath = lastBSlash >= 0 ? pathPart.slice(lastBSlash + 3) : oldPath;
22190
24210
  let fileType = "modified";
22191
24211
  while (i < lines.length && !lines[i].startsWith("diff --git ")) {
22192
24212
  const current = lines[i];
@@ -22332,7 +24352,7 @@ function renderDiff(diff, options) {
22332
24352
  function renderFileBlock(file, opts) {
22333
24353
  const { maxWidth, showLineNumbers, compact } = opts;
22334
24354
  const lang = detectLanguage(file.path);
22335
- const contentWidth = maxWidth - 4;
24355
+ const contentWidth = Math.max(1, maxWidth - 4);
22336
24356
  const typeLabel = file.type === "modified" ? "modified" : file.type === "added" ? "new file" : file.type === "deleted" ? "deleted" : `renamed from ${file.oldPath}`;
22337
24357
  const statsLabel = ` +${file.additions} -${file.deletions}`;
22338
24358
  const title = ` ${file.path} (${typeLabel}${statsLabel}) `;
@@ -22379,12 +24399,8 @@ function renderFileBlock(file, opts) {
22379
24399
  }
22380
24400
  function formatLineNo(line, show) {
22381
24401
  if (!show) return "";
22382
- if (line.type === "add") {
22383
- return chalk12.dim(`${String(line.newLineNo ?? "").padStart(4)} `);
22384
- } else if (line.type === "delete") {
22385
- return chalk12.dim(`${String(line.oldLineNo ?? "").padStart(4)} `);
22386
- }
22387
- return chalk12.dim(`${String(line.newLineNo ?? "").padStart(4)} `);
24402
+ const lineNo = line.type === "delete" ? line.oldLineNo : line.newLineNo;
24403
+ return chalk12.dim(`${String(lineNo ?? "").padStart(5)} `);
22388
24404
  }
22389
24405
  function getChangedLines(diff) {
22390
24406
  const result = /* @__PURE__ */ new Map();
@@ -22475,9 +24491,9 @@ Examples:
22475
24491
  }
22476
24492
  });
22477
24493
  var diffTools = [showDiffTool];
22478
- async function fileExists(path35) {
24494
+ async function fileExists(path37) {
22479
24495
  try {
22480
- await access(path35);
24496
+ await access(path37);
22481
24497
  return true;
22482
24498
  } catch {
22483
24499
  return false;
@@ -22567,7 +24583,7 @@ async function detectMaturity(cwd) {
22567
24583
  if (!hasLintConfig && hasPackageJson) {
22568
24584
  try {
22569
24585
  const pkgRaw = await import('fs/promises').then(
22570
- (fs38) => fs38.readFile(join(cwd, "package.json"), "utf-8")
24586
+ (fs39) => fs39.readFile(join(cwd, "package.json"), "utf-8")
22571
24587
  );
22572
24588
  const pkg = JSON.parse(pkgRaw);
22573
24589
  if (pkg.scripts?.lint || pkg.scripts?.["lint:fix"]) {
@@ -22934,9 +24950,9 @@ Examples:
22934
24950
  }
22935
24951
  });
22936
24952
  var reviewTools = [reviewCodeTool];
22937
- var fs27 = await import('fs/promises');
22938
- var path25 = await import('path');
22939
- var { glob: glob6 } = await import('glob');
24953
+ var fs28 = await import('fs/promises');
24954
+ var path26 = await import('path');
24955
+ var { glob: glob12 } = await import('glob');
22940
24956
  var DEFAULT_MAX_FILES = 200;
22941
24957
  var LANGUAGE_EXTENSIONS = {
22942
24958
  typescript: [".ts", ".tsx", ".mts", ".cts"],
@@ -22961,7 +24977,7 @@ var DEFAULT_EXCLUDES = [
22961
24977
  "**/*.d.ts"
22962
24978
  ];
22963
24979
  function detectLanguage2(filePath) {
22964
- const ext = path25.extname(filePath).toLowerCase();
24980
+ const ext = path26.extname(filePath).toLowerCase();
22965
24981
  for (const [lang, extensions] of Object.entries(LANGUAGE_EXTENSIONS)) {
22966
24982
  if (extensions.includes(ext)) return lang;
22967
24983
  }
@@ -23370,9 +25386,9 @@ Examples:
23370
25386
  }),
23371
25387
  async execute({ path: rootPath, include, exclude, languages, maxFiles, depth }) {
23372
25388
  const startTime = performance.now();
23373
- const absPath = path25.resolve(rootPath);
25389
+ const absPath = path26.resolve(rootPath);
23374
25390
  try {
23375
- const stat2 = await fs27.stat(absPath);
25391
+ const stat2 = await fs28.stat(absPath);
23376
25392
  if (!stat2.isDirectory()) {
23377
25393
  throw new ToolError(`Path is not a directory: ${absPath}`, {
23378
25394
  tool: "codebase_map"
@@ -23397,7 +25413,7 @@ Examples:
23397
25413
  pattern = `**/*{${allExts.join(",")}}`;
23398
25414
  }
23399
25415
  const excludePatterns = [...DEFAULT_EXCLUDES, ...exclude ?? []];
23400
- const files = await glob6(pattern, {
25416
+ const files = await glob12(pattern, {
23401
25417
  cwd: absPath,
23402
25418
  ignore: excludePatterns,
23403
25419
  nodir: true,
@@ -23409,14 +25425,14 @@ Examples:
23409
25425
  let totalDefinitions = 0;
23410
25426
  let exportedSymbols = 0;
23411
25427
  for (const file of limitedFiles) {
23412
- const fullPath = path25.join(absPath, file);
25428
+ const fullPath = path26.join(absPath, file);
23413
25429
  const language = detectLanguage2(file);
23414
25430
  if (!language) continue;
23415
25431
  if (languages && !languages.includes(language)) {
23416
25432
  continue;
23417
25433
  }
23418
25434
  try {
23419
- const content = await fs27.readFile(fullPath, "utf-8");
25435
+ const content = await fs28.readFile(fullPath, "utf-8");
23420
25436
  const lineCount = content.split("\n").length;
23421
25437
  const parsed = parseFile(content, language);
23422
25438
  const definitions = depth === "overview" ? parsed.definitions.filter((d) => d.exported) : parsed.definitions;
@@ -23449,23 +25465,23 @@ Examples:
23449
25465
  });
23450
25466
  var codebaseMapTools = [codebaseMapTool];
23451
25467
  init_paths();
23452
- var fs28 = await import('fs/promises');
23453
- var path26 = await import('path');
25468
+ var fs29 = await import('fs/promises');
25469
+ var path27 = await import('path');
23454
25470
  var crypto2 = await import('crypto');
23455
- var GLOBAL_MEMORIES_DIR = path26.join(COCO_HOME, "memories");
25471
+ var GLOBAL_MEMORIES_DIR = path27.join(COCO_HOME, "memories");
23456
25472
  var PROJECT_MEMORIES_DIR = ".coco/memories";
23457
25473
  var DEFAULT_MAX_MEMORIES = 1e3;
23458
25474
  async function ensureDir2(dirPath) {
23459
- await fs28.mkdir(dirPath, { recursive: true });
25475
+ await fs29.mkdir(dirPath, { recursive: true });
23460
25476
  }
23461
25477
  function getMemoriesDir(scope) {
23462
25478
  return scope === "global" ? GLOBAL_MEMORIES_DIR : PROJECT_MEMORIES_DIR;
23463
25479
  }
23464
25480
  async function loadIndex(scope) {
23465
25481
  const dir = getMemoriesDir(scope);
23466
- const indexPath = path26.join(dir, "index.json");
25482
+ const indexPath = path27.join(dir, "index.json");
23467
25483
  try {
23468
- const content = await fs28.readFile(indexPath, "utf-8");
25484
+ const content = await fs29.readFile(indexPath, "utf-8");
23469
25485
  return JSON.parse(content);
23470
25486
  } catch {
23471
25487
  return [];
@@ -23474,14 +25490,14 @@ async function loadIndex(scope) {
23474
25490
  async function saveIndex(scope, index) {
23475
25491
  const dir = getMemoriesDir(scope);
23476
25492
  await ensureDir2(dir);
23477
- const indexPath = path26.join(dir, "index.json");
23478
- await fs28.writeFile(indexPath, JSON.stringify(index, null, 2), "utf-8");
25493
+ const indexPath = path27.join(dir, "index.json");
25494
+ await fs29.writeFile(indexPath, JSON.stringify(index, null, 2), "utf-8");
23479
25495
  }
23480
25496
  async function loadMemory(scope, id) {
23481
25497
  const dir = getMemoriesDir(scope);
23482
- const memPath = path26.join(dir, `${id}.json`);
25498
+ const memPath = path27.join(dir, `${id}.json`);
23483
25499
  try {
23484
- const content = await fs28.readFile(memPath, "utf-8");
25500
+ const content = await fs29.readFile(memPath, "utf-8");
23485
25501
  return JSON.parse(content);
23486
25502
  } catch {
23487
25503
  return null;
@@ -23490,8 +25506,8 @@ async function loadMemory(scope, id) {
23490
25506
  async function saveMemory(scope, memory) {
23491
25507
  const dir = getMemoriesDir(scope);
23492
25508
  await ensureDir2(dir);
23493
- const memPath = path26.join(dir, `${memory.id}.json`);
23494
- await fs28.writeFile(memPath, JSON.stringify(memory, null, 2), "utf-8");
25509
+ const memPath = path27.join(dir, `${memory.id}.json`);
25510
+ await fs29.writeFile(memPath, JSON.stringify(memory, null, 2), "utf-8");
23495
25511
  }
23496
25512
  var createMemoryTool = defineTool({
23497
25513
  name: "create_memory",
@@ -23643,17 +25659,17 @@ Examples:
23643
25659
  }
23644
25660
  });
23645
25661
  var memoryTools = [createMemoryTool, recallMemoryTool, listMemoriesTool];
23646
- var fs29 = await import('fs/promises');
25662
+ var fs30 = await import('fs/promises');
23647
25663
  var crypto3 = await import('crypto');
23648
25664
  var CHECKPOINT_FILE = ".coco/checkpoints.json";
23649
25665
  var DEFAULT_MAX_CHECKPOINTS = 50;
23650
25666
  var STASH_PREFIX = "coco-cp";
23651
25667
  async function ensureCocoDir() {
23652
- await fs29.mkdir(".coco", { recursive: true });
25668
+ await fs30.mkdir(".coco", { recursive: true });
23653
25669
  }
23654
25670
  async function loadCheckpoints() {
23655
25671
  try {
23656
- const content = await fs29.readFile(CHECKPOINT_FILE, "utf-8");
25672
+ const content = await fs30.readFile(CHECKPOINT_FILE, "utf-8");
23657
25673
  return JSON.parse(content);
23658
25674
  } catch {
23659
25675
  return [];
@@ -23661,7 +25677,7 @@ async function loadCheckpoints() {
23661
25677
  }
23662
25678
  async function saveCheckpoints(checkpoints) {
23663
25679
  await ensureCocoDir();
23664
- await fs29.writeFile(CHECKPOINT_FILE, JSON.stringify(checkpoints, null, 2), "utf-8");
25680
+ await fs30.writeFile(CHECKPOINT_FILE, JSON.stringify(checkpoints, null, 2), "utf-8");
23665
25681
  }
23666
25682
  async function execGit(args) {
23667
25683
  const { execaCommand } = await import('execa');
@@ -23821,9 +25837,9 @@ Examples:
23821
25837
  }
23822
25838
  });
23823
25839
  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');
25840
+ var fs31 = await import('fs/promises');
25841
+ var path28 = await import('path');
25842
+ var { glob: glob13 } = await import('glob');
23827
25843
  var INDEX_DIR = ".coco/search-index";
23828
25844
  var DEFAULT_CHUNK_SIZE = 20;
23829
25845
  var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -23948,20 +25964,20 @@ async function getEmbedding(text9) {
23948
25964
  }
23949
25965
  async function loadIndex2(indexDir) {
23950
25966
  try {
23951
- const indexPath = path27.join(indexDir, "index.json");
23952
- const content = await fs30.readFile(indexPath, "utf-8");
25967
+ const indexPath = path28.join(indexDir, "index.json");
25968
+ const content = await fs31.readFile(indexPath, "utf-8");
23953
25969
  return JSON.parse(content);
23954
25970
  } catch {
23955
25971
  return null;
23956
25972
  }
23957
25973
  }
23958
25974
  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");
25975
+ await fs31.mkdir(indexDir, { recursive: true });
25976
+ const indexPath = path28.join(indexDir, "index.json");
25977
+ await fs31.writeFile(indexPath, JSON.stringify(index), "utf-8");
23962
25978
  }
23963
25979
  function isBinary(filePath) {
23964
- return BINARY_EXTENSIONS.has(path27.extname(filePath).toLowerCase());
25980
+ return BINARY_EXTENSIONS.has(path28.extname(filePath).toLowerCase());
23965
25981
  }
23966
25982
  var semanticSearchTool = defineTool({
23967
25983
  name: "semantic_search",
@@ -23986,12 +26002,12 @@ Examples:
23986
26002
  const effectivePath = rootPath ?? ".";
23987
26003
  const effectiveMaxResults = maxResults ?? 10;
23988
26004
  const effectiveThreshold = threshold ?? 0.3;
23989
- const absPath = path27.resolve(effectivePath);
23990
- const indexDir = path27.join(absPath, INDEX_DIR);
26005
+ const absPath = path28.resolve(effectivePath);
26006
+ const indexDir = path28.join(absPath, INDEX_DIR);
23991
26007
  let index = reindex ? null : await loadIndex2(indexDir);
23992
26008
  if (!index) {
23993
26009
  const pattern = include ?? "**/*";
23994
- const files = await glob7(pattern, {
26010
+ const files = await glob13(pattern, {
23995
26011
  cwd: absPath,
23996
26012
  ignore: DEFAULT_EXCLUDES2,
23997
26013
  nodir: true,
@@ -24000,10 +26016,10 @@ Examples:
24000
26016
  const chunks = [];
24001
26017
  for (const file of files) {
24002
26018
  if (isBinary(file)) continue;
24003
- const fullPath = path27.join(absPath, file);
26019
+ const fullPath = path28.join(absPath, file);
24004
26020
  try {
24005
- const stat2 = await fs30.stat(fullPath);
24006
- const content = await fs30.readFile(fullPath, "utf-8");
26021
+ const stat2 = await fs31.stat(fullPath);
26022
+ const content = await fs31.readFile(fullPath, "utf-8");
24007
26023
  if (content.length > 1e5) continue;
24008
26024
  const fileChunks = chunkContent(content, DEFAULT_CHUNK_SIZE);
24009
26025
  for (const chunk of fileChunks) {
@@ -24062,12 +26078,12 @@ Examples:
24062
26078
  }
24063
26079
  });
24064
26080
  var semanticSearchTools = [semanticSearchTool];
24065
- var fs31 = await import('fs/promises');
24066
- var path28 = await import('path');
24067
- var { glob: glob8 } = await import('glob');
26081
+ var fs32 = await import('fs/promises');
26082
+ var path29 = await import('path');
26083
+ var { glob: glob14 } = await import('glob');
24068
26084
  async function parseClassRelationships(rootPath, include) {
24069
26085
  const pattern = include ?? "**/*.{ts,tsx,js,jsx}";
24070
- const files = await glob8(pattern, {
26086
+ const files = await glob14(pattern, {
24071
26087
  cwd: rootPath,
24072
26088
  ignore: ["**/node_modules/**", "**/dist/**", "**/*.test.*", "**/*.d.ts"],
24073
26089
  nodir: true
@@ -24076,7 +26092,7 @@ async function parseClassRelationships(rootPath, include) {
24076
26092
  const interfaces = [];
24077
26093
  for (const file of files.slice(0, 100)) {
24078
26094
  try {
24079
- const content = await fs31.readFile(path28.join(rootPath, file), "utf-8");
26095
+ const content = await fs32.readFile(path29.join(rootPath, file), "utf-8");
24080
26096
  const lines = content.split("\n");
24081
26097
  for (let i = 0; i < lines.length; i++) {
24082
26098
  const line = lines[i];
@@ -24195,14 +26211,14 @@ async function generateClassDiagram(rootPath, include) {
24195
26211
  };
24196
26212
  }
24197
26213
  async function generateArchitectureDiagram(rootPath) {
24198
- const entries = await fs31.readdir(rootPath, { withFileTypes: true });
26214
+ const entries = await fs32.readdir(rootPath, { withFileTypes: true });
24199
26215
  const dirs = entries.filter(
24200
26216
  (e) => e.isDirectory() && !e.name.startsWith(".") && !["node_modules", "dist", "build", "coverage", "__pycache__", "target"].includes(e.name)
24201
26217
  );
24202
26218
  const lines = ["graph TD"];
24203
26219
  let nodeCount = 0;
24204
26220
  let edgeCount = 0;
24205
- const rootName = path28.basename(rootPath);
26221
+ const rootName = path29.basename(rootPath);
24206
26222
  lines.push(` ROOT["${rootName}"]`);
24207
26223
  nodeCount++;
24208
26224
  for (const dir of dirs) {
@@ -24212,7 +26228,7 @@ async function generateArchitectureDiagram(rootPath) {
24212
26228
  nodeCount++;
24213
26229
  edgeCount++;
24214
26230
  try {
24215
- const subEntries = await fs31.readdir(path28.join(rootPath, dir.name), {
26231
+ const subEntries = await fs32.readdir(path29.join(rootPath, dir.name), {
24216
26232
  withFileTypes: true
24217
26233
  });
24218
26234
  const subDirs = subEntries.filter(
@@ -24335,7 +26351,7 @@ Examples:
24335
26351
  tool: "generate_diagram"
24336
26352
  });
24337
26353
  }
24338
- const absPath = rootPath ? path28.resolve(rootPath) : process.cwd();
26354
+ const absPath = rootPath ? path29.resolve(rootPath) : process.cwd();
24339
26355
  switch (type) {
24340
26356
  case "class":
24341
26357
  return generateClassDiagram(absPath, include);
@@ -24396,8 +26412,8 @@ Examples:
24396
26412
  }
24397
26413
  });
24398
26414
  var diagramTools = [generateDiagramTool];
24399
- var fs32 = await import('fs/promises');
24400
- var path29 = await import('path');
26415
+ var fs33 = await import('fs/promises');
26416
+ var path30 = await import('path');
24401
26417
  var DEFAULT_MAX_PAGES = 20;
24402
26418
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
24403
26419
  function parsePageRange(rangeStr, totalPages) {
@@ -24432,9 +26448,9 @@ Examples:
24432
26448
  }),
24433
26449
  async execute({ path: filePath, pages, maxPages }) {
24434
26450
  const startTime = performance.now();
24435
- const absPath = path29.resolve(filePath);
26451
+ const absPath = path30.resolve(filePath);
24436
26452
  try {
24437
- const stat2 = await fs32.stat(absPath);
26453
+ const stat2 = await fs33.stat(absPath);
24438
26454
  if (!stat2.isFile()) {
24439
26455
  throw new ToolError(`Path is not a file: ${absPath}`, {
24440
26456
  tool: "read_pdf"
@@ -24465,7 +26481,7 @@ Examples:
24465
26481
  }
24466
26482
  try {
24467
26483
  const pdfParse = await import('pdf-parse');
24468
- const dataBuffer = await fs32.readFile(absPath);
26484
+ const dataBuffer = await fs33.readFile(absPath);
24469
26485
  const pdfData = await pdfParse.default(dataBuffer, {
24470
26486
  max: maxPages
24471
26487
  });
@@ -24511,8 +26527,8 @@ Examples:
24511
26527
  }
24512
26528
  });
24513
26529
  var pdfTools = [readPdfTool];
24514
- var fs33 = await import('fs/promises');
24515
- var path30 = await import('path');
26530
+ var fs34 = await import('fs/promises');
26531
+ var path31 = await import('path');
24516
26532
  var SUPPORTED_FORMATS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
24517
26533
  var MAX_IMAGE_SIZE = 20 * 1024 * 1024;
24518
26534
  var MIME_TYPES = {
@@ -24540,8 +26556,15 @@ Examples:
24540
26556
  async execute({ path: filePath, prompt, provider }) {
24541
26557
  const startTime = performance.now();
24542
26558
  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();
26559
+ const absPath = path31.resolve(filePath);
26560
+ const cwd = process.cwd();
26561
+ if (!absPath.startsWith(cwd + path31.sep) && absPath !== cwd) {
26562
+ throw new ToolError(
26563
+ `Path traversal denied: '${filePath}' resolves outside the project directory`,
26564
+ { tool: "read_image" }
26565
+ );
26566
+ }
26567
+ const ext = path31.extname(absPath).toLowerCase();
24545
26568
  if (!SUPPORTED_FORMATS.has(ext)) {
24546
26569
  throw new ToolError(
24547
26570
  `Unsupported image format '${ext}'. Supported: ${Array.from(SUPPORTED_FORMATS).join(", ")}`,
@@ -24549,7 +26572,7 @@ Examples:
24549
26572
  );
24550
26573
  }
24551
26574
  try {
24552
- const stat2 = await fs33.stat(absPath);
26575
+ const stat2 = await fs34.stat(absPath);
24553
26576
  if (!stat2.isFile()) {
24554
26577
  throw new ToolError(`Path is not a file: ${absPath}`, {
24555
26578
  tool: "read_image"
@@ -24570,7 +26593,7 @@ Examples:
24570
26593
  if (error instanceof ToolError) throw error;
24571
26594
  throw error;
24572
26595
  }
24573
- const imageBuffer = await fs33.readFile(absPath);
26596
+ const imageBuffer = await fs34.readFile(absPath);
24574
26597
  const base64 = imageBuffer.toString("base64");
24575
26598
  const mimeType = MIME_TYPES[ext] ?? "image/png";
24576
26599
  const selectedProvider = provider ?? "anthropic";
@@ -24683,7 +26706,7 @@ Examples:
24683
26706
  }
24684
26707
  });
24685
26708
  var imageTools = [readImageTool];
24686
- var path31 = await import('path');
26709
+ var path32 = await import('path');
24687
26710
  var DANGEROUS_PATTERNS2 = [
24688
26711
  /\bDROP\s+(?:TABLE|DATABASE|INDEX|VIEW)\b/i,
24689
26712
  /\bTRUNCATE\b/i,
@@ -24714,7 +26737,7 @@ Examples:
24714
26737
  async execute({ database, query, params, readonly: isReadonlyParam }) {
24715
26738
  const isReadonly = isReadonlyParam ?? true;
24716
26739
  const startTime = performance.now();
24717
- const absPath = path31.resolve(database);
26740
+ const absPath = path32.resolve(database);
24718
26741
  if (isReadonly && isDangerousSql(query)) {
24719
26742
  throw new ToolError(
24720
26743
  "Write operations (INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE) are blocked in readonly mode. Set readonly: false to allow writes.",
@@ -24787,7 +26810,7 @@ Examples:
24787
26810
  }),
24788
26811
  async execute({ database, table }) {
24789
26812
  const startTime = performance.now();
24790
- const absPath = path31.resolve(database);
26813
+ const absPath = path32.resolve(database);
24791
26814
  try {
24792
26815
  const { default: Database } = await import('better-sqlite3');
24793
26816
  const db = new Database(absPath, { readonly: true, fileMustExist: true });
@@ -24958,14 +26981,14 @@ var findMissingImportsTool = defineTool({
24958
26981
  }
24959
26982
  });
24960
26983
  var astValidatorTools = [validateCodeTool, findMissingImportsTool];
24961
- var fs34 = await import('fs/promises');
24962
- var path32 = await import('path');
26984
+ var fs35 = await import('fs/promises');
26985
+ var path33 = await import('path');
24963
26986
  var AnalyzeFileSchema = z.object({
24964
26987
  filePath: z.string().describe("Path to file to analyze"),
24965
26988
  includeAst: z.boolean().default(false).describe("Include AST in result")
24966
26989
  });
24967
26990
  async function analyzeFile(filePath, includeAst = false) {
24968
- const content = await fs34.readFile(filePath, "utf-8");
26991
+ const content = await fs35.readFile(filePath, "utf-8");
24969
26992
  const lines = content.split("\n").length;
24970
26993
  const functions = [];
24971
26994
  const classes = [];
@@ -25056,8 +27079,8 @@ async function analyzeFile(filePath, includeAst = false) {
25056
27079
  };
25057
27080
  }
25058
27081
  async function analyzeDirectory(dirPath) {
25059
- const { glob: glob9 } = await import('glob');
25060
- const files = await glob9("**/*.{ts,tsx,js,jsx}", {
27082
+ const { glob: glob15 } = await import('glob');
27083
+ const files = await glob15("**/*.{ts,tsx,js,jsx}", {
25061
27084
  cwd: dirPath,
25062
27085
  ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
25063
27086
  absolute: true
@@ -25069,10 +27092,10 @@ async function analyzeDirectory(dirPath) {
25069
27092
  try {
25070
27093
  const analysis = await analyzeFile(file, false);
25071
27094
  totalLines += analysis.lines;
25072
- const ext = path32.extname(file);
27095
+ const ext = path33.extname(file);
25073
27096
  filesByType[ext] = (filesByType[ext] || 0) + 1;
25074
27097
  fileStats.push({
25075
- file: path32.relative(dirPath, file),
27098
+ file: path33.relative(dirPath, file),
25076
27099
  lines: analysis.lines,
25077
27100
  complexity: analysis.complexity.cyclomatic
25078
27101
  });
@@ -25132,8 +27155,8 @@ var codeAnalyzerTools = [analyzeFileTool, analyzeDirectoryTool];
25132
27155
  var AgentTaskQueue = class {
25133
27156
  tasks = /* @__PURE__ */ new Map();
25134
27157
  completionOrder = [];
25135
- addTask(task) {
25136
- const id = `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
27158
+ addTask(task, idOverride) {
27159
+ const id = idOverride ?? `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
25137
27160
  this.tasks.set(id, {
25138
27161
  ...task,
25139
27162
  id,
@@ -25179,10 +27202,20 @@ var AgentTaskQueue = class {
25179
27202
  };
25180
27203
  function planExecution(tasks, strategy) {
25181
27204
  const taskGraph = /* @__PURE__ */ new Map();
27205
+ const unresolvedDependencies = [];
25182
27206
  tasks.forEach((task, idx) => {
25183
- const id = `task-${idx}`;
27207
+ const id = task.id ?? `task-${idx}`;
25184
27208
  taskGraph.set(id, task.dependencies || []);
25185
27209
  });
27210
+ const taskIds = new Set(taskGraph.keys());
27211
+ for (const [id, deps] of taskGraph) {
27212
+ const filtered = deps.filter((dep) => {
27213
+ if (taskIds.has(dep)) return true;
27214
+ unresolvedDependencies.push({ taskId: id, dependency: dep });
27215
+ return false;
27216
+ });
27217
+ taskGraph.set(id, filtered);
27218
+ }
25186
27219
  let plan = [];
25187
27220
  let estimatedTime = 0;
25188
27221
  let maxParallelism = 1;
@@ -25200,7 +27233,11 @@ function planExecution(tasks, strategy) {
25200
27233
  break;
25201
27234
  }
25202
27235
  case "priority-based": {
25203
- const sorted = tasks.map((t, idx) => ({ id: `task-${idx}`, len: t.description.length })).sort((a, b) => a.len - b.len);
27236
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
27237
+ const sorted = tasks.map((t, idx) => ({
27238
+ id: t.id ?? `task-${idx}`,
27239
+ priority: t.priority ?? "medium"
27240
+ })).sort((a, b) => (priorityOrder[a.priority] ?? 1) - (priorityOrder[b.priority] ?? 1));
25204
27241
  plan = sorted.map((t) => t.id);
25205
27242
  estimatedTime = tasks.length * 80;
25206
27243
  maxParallelism = 3;
@@ -25229,7 +27266,7 @@ function planExecution(tasks, strategy) {
25229
27266
  break;
25230
27267
  }
25231
27268
  }
25232
- return { plan, estimatedTime, maxParallelism };
27269
+ return { plan, estimatedTime, maxParallelism, unresolvedDependencies };
25233
27270
  }
25234
27271
  var createAgentPlanTool = defineTool({
25235
27272
  name: "createAgentPlan",
@@ -25248,15 +27285,35 @@ var createAgentPlanTool = defineTool({
25248
27285
  async execute(input) {
25249
27286
  const typedInput = input;
25250
27287
  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
- });
27288
+ const indexIdMap = /* @__PURE__ */ new Map();
27289
+ for (let idx = 0; idx < typedInput.tasks.length; idx++) {
27290
+ indexIdMap.set(idx, `task-${idx}`);
27291
+ }
27292
+ const normalizeDependency = (dep) => {
27293
+ if (indexIdMap.has(Number(dep))) {
27294
+ return indexIdMap.get(Number(dep));
27295
+ }
27296
+ return dep;
27297
+ };
27298
+ for (const [idx, task] of typedInput.tasks.entries()) {
27299
+ const id = indexIdMap.get(idx);
27300
+ const normalizedDependencies = (task.dependencies || []).map(normalizeDependency);
27301
+ queue.addTask(
27302
+ {
27303
+ description: task.description,
27304
+ priority: task.priority,
27305
+ estimatedDuration: 100,
27306
+ dependencies: normalizedDependencies
27307
+ },
27308
+ id
27309
+ );
25258
27310
  }
25259
- const executionPlan = planExecution(typedInput.tasks, typedInput.strategy);
27311
+ const plannedTasks = typedInput.tasks.map((task, idx) => ({
27312
+ id: indexIdMap.get(idx),
27313
+ description: task.description,
27314
+ dependencies: (task.dependencies || []).map(normalizeDependency)
27315
+ }));
27316
+ const executionPlan = planExecution(plannedTasks, typedInput.strategy);
25260
27317
  return {
25261
27318
  planId: `plan-${Date.now()}`,
25262
27319
  strategy: typedInput.strategy,
@@ -25264,6 +27321,7 @@ var createAgentPlanTool = defineTool({
25264
27321
  executionOrder: executionPlan.plan,
25265
27322
  estimatedTime: executionPlan.estimatedTime,
25266
27323
  maxParallelism: executionPlan.maxParallelism,
27324
+ unresolvedDependencies: executionPlan.unresolvedDependencies,
25267
27325
  tasks: queue.getTasks().map((t) => ({
25268
27326
  id: t.id,
25269
27327
  description: t.description,
@@ -25276,30 +27334,59 @@ var createAgentPlanTool = defineTool({
25276
27334
  });
25277
27335
  var delegateTaskTool = defineTool({
25278
27336
  name: "delegateTask",
25279
- description: "Delegate a task to a virtual sub-agent (simulated for demonstration)",
27337
+ description: "Delegate a task to a specialized sub-agent with real LLM tool-use execution.",
25280
27338
  category: "build",
25281
27339
  parameters: z.object({
25282
27340
  taskId: z.string(),
27341
+ task: z.string().describe("Description of the task for the agent to execute"),
25283
27342
  agentRole: z.enum(["researcher", "coder", "reviewer", "tester", "optimizer"]).default("coder"),
25284
- context: z.string().optional()
27343
+ context: z.string().optional(),
27344
+ maxTurns: z.number().default(10)
25285
27345
  }),
25286
27346
  async execute(input) {
25287
27347
  const typedInput = input;
27348
+ const provider = getAgentProvider();
27349
+ const toolRegistry = getAgentToolRegistry();
27350
+ if (!provider || !toolRegistry) {
27351
+ return {
27352
+ agentId: `agent-${Date.now()}-${typedInput.agentRole}`,
27353
+ taskId: typedInput.taskId,
27354
+ role: typedInput.agentRole,
27355
+ status: "unavailable",
27356
+ message: "Agent provider not initialized. Call setAgentProvider() during orchestrator startup.",
27357
+ success: false
27358
+ };
27359
+ }
25288
27360
  const agentId = `agent-${Date.now()}-${typedInput.agentRole}`;
27361
+ const executor = new AgentExecutor(provider, toolRegistry);
27362
+ const roleDef = AGENT_ROLES[typedInput.agentRole];
27363
+ if (!roleDef) {
27364
+ return {
27365
+ agentId,
27366
+ taskId: typedInput.taskId,
27367
+ role: typedInput.agentRole,
27368
+ status: "error",
27369
+ message: `Unknown agent role: ${typedInput.agentRole}`,
27370
+ success: false
27371
+ };
27372
+ }
27373
+ const agentDef = { ...roleDef, maxTurns: typedInput.maxTurns };
27374
+ const result = await executor.execute(agentDef, {
27375
+ id: typedInput.taskId,
27376
+ description: typedInput.task,
27377
+ context: typedInput.context ? { userContext: typedInput.context } : void 0
27378
+ });
25289
27379
  return {
25290
27380
  agentId,
25291
27381
  taskId: typedInput.taskId,
25292
27382
  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."
27383
+ status: result.success ? "completed" : "failed",
27384
+ output: result.output,
27385
+ success: result.success,
27386
+ turns: result.turns,
27387
+ toolsUsed: result.toolsUsed,
27388
+ tokensUsed: result.tokensUsed,
27389
+ duration: result.duration
25303
27390
  };
25304
27391
  }
25305
27392
  });
@@ -25335,10 +27422,14 @@ var aggregateResultsTool = defineTool({
25335
27422
  const winner = Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0];
25336
27423
  aggregatedOutput = winner ? winner[0] : "";
25337
27424
  break;
25338
- case "best":
25339
- const longest = completed.sort((a, b) => b.output.length - a.output.length)[0];
25340
- aggregatedOutput = longest ? longest.output : "";
27425
+ case "best": {
27426
+ const successRate = typedInput.results.length > 0 ? completed.length / typedInput.results.length : 0;
27427
+ const bestResult = completed.length > 0 ? completed[0] : void 0;
27428
+ aggregatedOutput = bestResult ? `[Success rate: ${Math.round(successRate * 100)}%]
27429
+
27430
+ ${bestResult.output}` : "";
25341
27431
  break;
27432
+ }
25342
27433
  case "summary":
25343
27434
  aggregatedOutput = `Completed: ${completed.length}, Failed: ${failed.length}
25344
27435
 
@@ -25360,13 +27451,13 @@ ${completed.map((r) => `- ${r.agentId}: Success`).join("\n")}`;
25360
27451
  }
25361
27452
  });
25362
27453
  var agentCoordinatorTools = [createAgentPlanTool, delegateTaskTool, aggregateResultsTool];
25363
- var fs35 = await import('fs/promises');
27454
+ var fs36 = await import('fs/promises');
25364
27455
  var SuggestImprovementsSchema = z.object({
25365
27456
  filePath: z.string().describe("File to analyze for improvement suggestions"),
25366
27457
  context: z.string().optional().describe("Additional context about the code")
25367
27458
  });
25368
27459
  async function analyzeAndSuggest(filePath, _context) {
25369
- const content = await fs35.readFile(filePath, "utf-8");
27460
+ const content = await fs36.readFile(filePath, "utf-8");
25370
27461
  const lines = content.split("\n");
25371
27462
  const suggestions = [];
25372
27463
  for (let i = 0; i < lines.length; i++) {
@@ -25412,7 +27503,8 @@ async function analyzeAndSuggest(filePath, _context) {
25412
27503
  reasoning: "'any' bypasses all type checking and can hide bugs"
25413
27504
  });
25414
27505
  }
25415
- if (line.trim() === "catch (error) {" || line.trim() === "catch (e) {") {
27506
+ const trimmedLine = line.trim();
27507
+ if (trimmedLine.endsWith("catch (error) {") || trimmedLine.endsWith("catch (e) {") || trimmedLine === "catch (error) {" || trimmedLine === "catch (e) {") {
25416
27508
  const nextLine = lines[i + 1];
25417
27509
  if (nextLine && nextLine.trim() === "}") {
25418
27510
  suggestions.push({
@@ -25457,7 +27549,7 @@ async function analyzeAndSuggest(filePath, _context) {
25457
27549
  if (filePath.endsWith(".ts") && !filePath.includes("test") && !filePath.includes(".d.ts") && line.includes("export ")) {
25458
27550
  const testPath = filePath.replace(".ts", ".test.ts");
25459
27551
  try {
25460
- await fs35.access(testPath);
27552
+ await fs36.access(testPath);
25461
27553
  } catch {
25462
27554
  suggestions.push({
25463
27555
  type: "testing",
@@ -25514,7 +27606,7 @@ var calculateCodeScoreTool = defineTool({
25514
27606
  async execute(input) {
25515
27607
  const { filePath } = input;
25516
27608
  const suggestions = await analyzeAndSuggest(filePath);
25517
- const content = await fs35.readFile(filePath, "utf-8");
27609
+ const content = await fs36.readFile(filePath, "utf-8");
25518
27610
  const lines = content.split("\n");
25519
27611
  const nonEmptyLines = lines.filter((l) => l.trim()).length;
25520
27612
  let score = 100;
@@ -25548,8 +27640,8 @@ var calculateCodeScoreTool = defineTool({
25548
27640
  }
25549
27641
  });
25550
27642
  var smartSuggestionsTools = [suggestImprovementsTool, calculateCodeScoreTool];
25551
- var fs36 = await import('fs/promises');
25552
- var path33 = await import('path');
27643
+ var fs37 = await import('fs/promises');
27644
+ var path34 = await import('path');
25553
27645
  var ContextMemoryStore = class {
25554
27646
  items = /* @__PURE__ */ new Map();
25555
27647
  learnings = /* @__PURE__ */ new Map();
@@ -25561,7 +27653,7 @@ var ContextMemoryStore = class {
25561
27653
  }
25562
27654
  async load() {
25563
27655
  try {
25564
- const content = await fs36.readFile(this.storePath, "utf-8");
27656
+ const content = await fs37.readFile(this.storePath, "utf-8");
25565
27657
  const data = JSON.parse(content);
25566
27658
  this.items = new Map(Object.entries(data.items || {}));
25567
27659
  this.learnings = new Map(Object.entries(data.learnings || {}));
@@ -25569,15 +27661,15 @@ var ContextMemoryStore = class {
25569
27661
  }
25570
27662
  }
25571
27663
  async save() {
25572
- const dir = path33.dirname(this.storePath);
25573
- await fs36.mkdir(dir, { recursive: true });
27664
+ const dir = path34.dirname(this.storePath);
27665
+ await fs37.mkdir(dir, { recursive: true });
25574
27666
  const data = {
25575
27667
  sessionId: this.sessionId,
25576
27668
  items: Object.fromEntries(this.items),
25577
27669
  learnings: Object.fromEntries(this.learnings),
25578
27670
  savedAt: Date.now()
25579
27671
  };
25580
- await fs36.writeFile(this.storePath, JSON.stringify(data, null, 2));
27672
+ await fs37.writeFile(this.storePath, JSON.stringify(data, null, 2));
25581
27673
  }
25582
27674
  addContext(id, item) {
25583
27675
  this.items.set(id, item);
@@ -25742,11 +27834,11 @@ var contextEnhancerTools = [
25742
27834
  recordLearningTool,
25743
27835
  getLearnedPatternsTool
25744
27836
  ];
25745
- var fs37 = await import('fs/promises');
25746
- var path34 = await import('path');
27837
+ var fs38 = await import('fs/promises');
27838
+ var path35 = await import('path');
25747
27839
  async function discoverSkills(skillsDir) {
25748
27840
  try {
25749
- const files = await fs37.readdir(skillsDir);
27841
+ const files = await fs38.readdir(skillsDir);
25750
27842
  return files.filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
25751
27843
  } catch {
25752
27844
  return [];
@@ -25754,12 +27846,12 @@ async function discoverSkills(skillsDir) {
25754
27846
  }
25755
27847
  async function loadSkillMetadata(skillPath) {
25756
27848
  try {
25757
- const content = await fs37.readFile(skillPath, "utf-8");
27849
+ const content = await fs38.readFile(skillPath, "utf-8");
25758
27850
  const nameMatch = content.match(/@name\s+(\S+)/);
25759
27851
  const descMatch = content.match(/@description\s+(.+)/);
25760
27852
  const versionMatch = content.match(/@version\s+(\S+)/);
25761
27853
  return {
25762
- name: nameMatch?.[1] || path34.basename(skillPath, path34.extname(skillPath)),
27854
+ name: nameMatch?.[1] || path35.basename(skillPath, path35.extname(skillPath)),
25763
27855
  description: descMatch?.[1] || "No description",
25764
27856
  version: versionMatch?.[1] || "1.0.0",
25765
27857
  dependencies: []
@@ -25803,7 +27895,7 @@ var discoverSkillsTool = defineTool({
25803
27895
  const { skillsDir } = input;
25804
27896
  const skills = await discoverSkills(skillsDir);
25805
27897
  const metadata = await Promise.all(
25806
- skills.map((s) => loadSkillMetadata(path34.join(skillsDir, s)))
27898
+ skills.map((s) => loadSkillMetadata(path35.join(skillsDir, s)))
25807
27899
  );
25808
27900
  return {
25809
27901
  skillsDir,
@@ -26532,6 +28624,24 @@ var pc = chalk12;
26532
28624
  });
26533
28625
 
26534
28626
  // src/cli/repl/index.ts
28627
+ function visualWidth(str) {
28628
+ const stripped = str.replace(/\x1b\[[0-9;]*m/g, "");
28629
+ let width = 0;
28630
+ for (const char of stripped) {
28631
+ const cp = char.codePointAt(0) || 0;
28632
+ 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) {
28633
+ width += 2;
28634
+ } else if (
28635
+ // CJK Unified Ideographs and other wide characters
28636
+ 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
28637
+ ) {
28638
+ width += 2;
28639
+ } else {
28640
+ width += 1;
28641
+ }
28642
+ }
28643
+ return width;
28644
+ }
26535
28645
  async function startRepl(options = {}) {
26536
28646
  const projectPath = options.projectPath ?? process.cwd();
26537
28647
  const session = createSession(projectPath, options.config);
@@ -26576,9 +28686,22 @@ async function startRepl(options = {}) {
26576
28686
  }
26577
28687
  await loadCocoModePreference();
26578
28688
  const toolRegistry = createFullToolRegistry();
28689
+ setAgentProvider(provider);
28690
+ setAgentToolRegistry(toolRegistry);
26579
28691
  const inputHandler = createInputHandler();
26580
28692
  const intentRecognizer = createIntentRecognizer();
26581
28693
  await printWelcome(session);
28694
+ const cleanupTerminal = () => {
28695
+ process.stdout.write("\x1B[?2004l");
28696
+ if (process.stdin.isTTY && process.stdin.isRaw) {
28697
+ process.stdin.setRawMode(false);
28698
+ }
28699
+ };
28700
+ process.on("exit", cleanupTerminal);
28701
+ process.on("SIGTERM", () => {
28702
+ cleanupTerminal();
28703
+ process.exit(0);
28704
+ });
26582
28705
  while (true) {
26583
28706
  const input = await inputHandler.prompt();
26584
28707
  if (input === null && !hasPendingImage()) {
@@ -26658,6 +28781,14 @@ async function startRepl(options = {}) {
26658
28781
  activeSpinner.start();
26659
28782
  }
26660
28783
  };
28784
+ const abortController = new AbortController();
28785
+ let wasAborted = false;
28786
+ const sigintHandler = () => {
28787
+ wasAborted = true;
28788
+ abortController.abort();
28789
+ clearSpinner();
28790
+ renderInfo("\nOperation cancelled");
28791
+ };
26661
28792
  try {
26662
28793
  if (typeof agentMessage === "string" && !isCocoMode() && !wasHintShown() && looksLikeFeatureRequest(agentMessage)) {
26663
28794
  markHintShown();
@@ -26670,14 +28801,6 @@ async function startRepl(options = {}) {
26670
28801
  session.config.agent.systemPrompt = originalSystemPrompt + "\n" + getCocoModeSystemPrompt();
26671
28802
  }
26672
28803
  inputHandler.pause();
26673
- const abortController = new AbortController();
26674
- let wasAborted = false;
26675
- const sigintHandler = () => {
26676
- wasAborted = true;
26677
- abortController.abort();
26678
- clearSpinner();
26679
- renderInfo("\nOperation cancelled");
26680
- };
26681
28804
  process.once("SIGINT", sigintHandler);
26682
28805
  const result = await executeAgentTurn(session, agentMessage, provider, toolRegistry, {
26683
28806
  onStream: renderStreamChunk,
@@ -26766,6 +28889,7 @@ async function startRepl(options = {}) {
26766
28889
  console.log();
26767
28890
  } catch (error) {
26768
28891
  clearSpinner();
28892
+ process.off("SIGINT", sigintHandler);
26769
28893
  if (error instanceof Error && error.name === "AbortError") {
26770
28894
  continue;
26771
28895
  }
@@ -26796,23 +28920,39 @@ async function startRepl(options = {}) {
26796
28920
  inputHandler.close();
26797
28921
  }
26798
28922
  async function printWelcome(session) {
28923
+ const providerType = session.config.provider.type;
28924
+ const model = session.config.provider.model || "default";
28925
+ const isReturningUser = existsSync(path20__default.join(session.projectPath, ".coco"));
28926
+ if (isReturningUser) {
28927
+ const versionStr = chalk12.dim(`v${VERSION}`);
28928
+ const providerStr = chalk12.dim(`${providerType}/${model}`);
28929
+ console.log(
28930
+ `
28931
+ \u{1F965} Coco ${versionStr} ${chalk12.dim("\u2502")} ${providerStr} ${chalk12.dim("\u2502")} ${chalk12.yellow("/help")}
28932
+ `
28933
+ );
28934
+ return;
28935
+ }
26799
28936
  const trustStore = createTrustStore();
26800
28937
  await trustStore.init();
26801
28938
  const trustLevel = trustStore.getLevel(session.projectPath);
26802
28939
  const boxWidth = 41;
26803
28940
  const innerWidth = boxWidth - 4;
26804
- const titleText = "CORBAT-COCO";
28941
+ const titleContent = "\u{1F965} CORBAT-COCO";
26805
28942
  const versionText = `v${VERSION}`;
26806
- const titlePadding = innerWidth - titleText.length - versionText.length - 2;
28943
+ const titleContentVisualWidth = visualWidth(titleContent);
28944
+ const versionVisualWidth = visualWidth(versionText);
28945
+ const titlePadding = innerWidth - titleContentVisualWidth - versionVisualWidth;
26807
28946
  const subtitleText = "open source \u2022 corbat.tech";
26808
- const subtitlePadding = innerWidth - subtitleText.length;
28947
+ const subtitleVisualWidth = visualWidth(subtitleText);
28948
+ const subtitlePadding = innerWidth - subtitleVisualWidth;
26809
28949
  console.log();
26810
28950
  console.log(chalk12.magenta(" \u256D" + "\u2500".repeat(boxWidth - 2) + "\u256E"));
26811
28951
  console.log(
26812
- chalk12.magenta(" \u2502 ") + "\u{1F965} " + chalk12.bold.white(titleText) + " ".repeat(titlePadding) + chalk12.dim(versionText) + chalk12.magenta(" \u2502")
28952
+ chalk12.magenta(" \u2502 ") + "\u{1F965} " + chalk12.bold.white("CORBAT-COCO") + " ".repeat(Math.max(0, titlePadding)) + chalk12.dim(versionText) + chalk12.magenta(" \u2502")
26813
28953
  );
26814
28954
  console.log(
26815
- chalk12.magenta(" \u2502 ") + chalk12.dim(subtitleText) + " ".repeat(subtitlePadding) + chalk12.magenta(" \u2502")
28955
+ chalk12.magenta(" \u2502 ") + chalk12.dim(subtitleText) + " ".repeat(Math.max(0, subtitlePadding)) + chalk12.magenta(" \u2502")
26816
28956
  );
26817
28957
  console.log(chalk12.magenta(" \u2570" + "\u2500".repeat(boxWidth - 2) + "\u256F"));
26818
28958
  const updateInfo = await checkForUpdates();
@@ -27067,7 +29207,7 @@ async function main() {
27067
29207
  await program.parseAsync(process.argv);
27068
29208
  }
27069
29209
  main().catch((error) => {
27070
- console.error("Fatal error:", error);
29210
+ console.error(formatError(error));
27071
29211
  process.exit(1);
27072
29212
  });
27073
29213
  //# sourceMappingURL=index.js.map