@corbat-tech/coco 1.1.0 → 1.2.2

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
@@ -4,7 +4,7 @@ import { homedir } from 'os';
4
4
  import * as path20 from 'path';
5
5
  import path20__default, { join, dirname, basename } from 'path';
6
6
  import * as fs19 from 'fs';
7
- import fs19__default, { readFileSync, existsSync, constants } from 'fs';
7
+ import fs19__default, { readFileSync, 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';
@@ -21,6 +21,7 @@ import * as http from 'http';
21
21
  import { execFile, exec, execSync, execFileSync, spawn } from 'child_process';
22
22
  import { promisify } from 'util';
23
23
  import { GoogleGenerativeAI, FunctionCallingMode } from '@google/generative-ai';
24
+ import stringWidth from 'string-width';
24
25
  import ansiEscapes3 from 'ansi-escapes';
25
26
  import * as readline from 'readline';
26
27
  import { execa } from 'execa';
@@ -279,8 +280,8 @@ __export(trust_store_exports, {
279
280
  saveTrustStore: () => saveTrustStore,
280
281
  updateLastAccessed: () => updateLastAccessed
281
282
  });
282
- async function ensureDir(path37) {
283
- await mkdir(dirname(path37), { recursive: true });
283
+ async function ensureDir(path36) {
284
+ await mkdir(dirname(path36), { recursive: true });
284
285
  }
285
286
  async function loadTrustStore(storePath = TRUST_STORE_PATH) {
286
287
  try {
@@ -358,8 +359,8 @@ function canPerformOperation(store, projectPath, operation) {
358
359
  };
359
360
  return permissions[level]?.includes(operation) ?? false;
360
361
  }
361
- function normalizePath(path37) {
362
- return join(path37);
362
+ function normalizePath(path36) {
363
+ return join(path36);
363
364
  }
364
365
  function createTrustStore(storePath = TRUST_STORE_PATH) {
365
366
  let store = null;
@@ -609,8 +610,8 @@ Generated by Corbat-Coco v0.1.0
609
610
 
610
611
  // src/cli/commands/init.ts
611
612
  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 (path37, options) => {
613
- await runInit(path37, options);
613
+ 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 (path36, options) => {
614
+ await runInit(path36, options);
614
615
  });
615
616
  }
616
617
  async function runInit(projectPath, options) {
@@ -689,18 +690,18 @@ async function gatherProjectInfo() {
689
690
  language
690
691
  };
691
692
  }
692
- function getDefaultProjectInfo(path37) {
693
- const name = path37 === "." ? "my-project" : path37.split("/").pop() || "my-project";
693
+ function getDefaultProjectInfo(path36) {
694
+ const name = path36 === "." ? "my-project" : path36.split("/").pop() || "my-project";
694
695
  return {
695
696
  name,
696
697
  description: "",
697
698
  language: "typescript"
698
699
  };
699
700
  }
700
- async function checkExistingProject(path37) {
701
+ async function checkExistingProject(path36) {
701
702
  try {
702
703
  const fs39 = await import('fs/promises');
703
- await fs39.access(`${path37}/.coco`);
704
+ await fs39.access(`${path36}/.coco`);
704
705
  return true;
705
706
  } catch {
706
707
  return false;
@@ -724,9 +725,6 @@ var QualityConfigSchema = z.object({
724
725
  }).refine((data) => data.minIterations <= data.maxIterations, {
725
726
  message: "minIterations must be <= maxIterations",
726
727
  path: ["minIterations"]
727
- }).refine((data) => data.convergenceThreshold < data.minScore, {
728
- message: "convergenceThreshold must be < minScore",
729
- path: ["convergenceThreshold"]
730
728
  });
731
729
  var PersistenceConfigSchema = z.object({
732
730
  checkpointInterval: z.number().min(6e4).default(3e5),
@@ -1072,7 +1070,17 @@ function deepMergeConfig(base, override) {
1072
1070
  project: { ...base.project, ...override.project },
1073
1071
  provider: { ...base.provider, ...override.provider },
1074
1072
  quality: { ...base.quality, ...override.quality },
1075
- persistence: { ...base.persistence, ...override.persistence }
1073
+ persistence: { ...base.persistence, ...override.persistence },
1074
+ // Merge optional sections only if present in either base or override
1075
+ ...base.stack || override.stack ? { stack: { ...base.stack, ...override.stack } } : {},
1076
+ ...base.integrations || override.integrations ? {
1077
+ integrations: {
1078
+ ...base.integrations,
1079
+ ...override.integrations
1080
+ }
1081
+ } : {},
1082
+ ...base.mcp || override.mcp ? { mcp: { ...base.mcp, ...override.mcp } } : {},
1083
+ ...base.tools || override.tools ? { tools: { ...base.tools, ...override.tools } } : {}
1076
1084
  };
1077
1085
  }
1078
1086
  function getProjectConfigPath() {
@@ -4778,6 +4786,9 @@ var AnthropicProvider = class {
4778
4786
  try {
4779
4787
  currentToolCall.input = currentToolInputJson ? JSON.parse(currentToolInputJson) : {};
4780
4788
  } catch {
4789
+ console.warn(
4790
+ `[Anthropic] Failed to parse tool call arguments: ${currentToolInputJson?.slice(0, 100)}`
4791
+ );
4781
4792
  currentToolCall.input = {};
4782
4793
  }
4783
4794
  yield {
@@ -5310,6 +5321,9 @@ var OpenAIProvider = class {
5310
5321
  try {
5311
5322
  input = builder.arguments ? JSON.parse(builder.arguments) : {};
5312
5323
  } catch {
5324
+ console.warn(
5325
+ `[OpenAI] Failed to parse tool call arguments: ${builder.arguments?.slice(0, 100)}`
5326
+ );
5313
5327
  }
5314
5328
  yield {
5315
5329
  type: "tool_use_end",
@@ -5598,7 +5612,16 @@ var OpenAIProvider = class {
5598
5612
  ).map((tc) => ({
5599
5613
  id: tc.id,
5600
5614
  name: tc.function.name,
5601
- input: JSON.parse(tc.function.arguments || "{}")
5615
+ input: (() => {
5616
+ try {
5617
+ return JSON.parse(tc.function.arguments || "{}");
5618
+ } catch {
5619
+ console.warn(
5620
+ `[OpenAI] Failed to parse tool call arguments: ${tc.function.arguments?.slice(0, 100)}`
5621
+ );
5622
+ return {};
5623
+ }
5624
+ })()
5602
5625
  }));
5603
5626
  }
5604
5627
  /**
@@ -7246,7 +7269,10 @@ var GeminiProvider = class {
7246
7269
  for (const part of candidate.content.parts) {
7247
7270
  if ("functionCall" in part && part.functionCall) {
7248
7271
  const funcCall = part.functionCall;
7249
- const callKey = `${funcCall.name}-${JSON.stringify(funcCall.args)}`;
7272
+ const sortedArgs = funcCall.args ? Object.keys(funcCall.args).sort().map(
7273
+ (k) => `${k}:${JSON.stringify(funcCall.args[k])}`
7274
+ ).join(",") : "";
7275
+ const callKey = `${funcCall.name}-${sortedArgs}`;
7250
7276
  if (!emittedToolCalls.has(callKey)) {
7251
7277
  emittedToolCalls.add(callKey);
7252
7278
  const toolCall = {
@@ -7278,9 +7304,18 @@ var GeminiProvider = class {
7278
7304
  }
7279
7305
  /**
7280
7306
  * Count tokens (approximate)
7307
+ *
7308
+ * Gemini uses a SentencePiece tokenizer. The average ratio varies:
7309
+ * - English text: ~4 characters per token
7310
+ * - Code: ~3.2 characters per token
7311
+ * - Mixed content: ~3.5 characters per token
7312
+ *
7313
+ * Using 3.5 as the default provides a better estimate for typical
7314
+ * coding agent workloads which mix code and natural language.
7281
7315
  */
7282
7316
  countTokens(text9) {
7283
- return Math.ceil(text9.length / 4);
7317
+ if (!text9) return 0;
7318
+ return Math.ceil(text9.length / 3.5);
7284
7319
  }
7285
7320
  /**
7286
7321
  * Get context window size
@@ -7614,20 +7649,20 @@ async function createCliPhaseContext(projectPath, _onUserInput) {
7614
7649
  },
7615
7650
  tools: {
7616
7651
  file: {
7617
- async read(path37) {
7652
+ async read(path36) {
7618
7653
  const fs39 = await import('fs/promises');
7619
- return fs39.readFile(path37, "utf-8");
7654
+ return fs39.readFile(path36, "utf-8");
7620
7655
  },
7621
- async write(path37, content) {
7656
+ async write(path36, content) {
7622
7657
  const fs39 = await import('fs/promises');
7623
7658
  const nodePath = await import('path');
7624
- await fs39.mkdir(nodePath.dirname(path37), { recursive: true });
7625
- await fs39.writeFile(path37, content, "utf-8");
7659
+ await fs39.mkdir(nodePath.dirname(path36), { recursive: true });
7660
+ await fs39.writeFile(path36, content, "utf-8");
7626
7661
  },
7627
- async exists(path37) {
7662
+ async exists(path36) {
7628
7663
  const fs39 = await import('fs/promises');
7629
7664
  try {
7630
- await fs39.access(path37);
7665
+ await fs39.access(path36);
7631
7666
  return true;
7632
7667
  } catch {
7633
7668
  return false;
@@ -7980,10 +8015,10 @@ function getPhaseStatusForPhase(phase) {
7980
8015
  }
7981
8016
  async function loadProjectState(cwd, config) {
7982
8017
  const fs39 = await import('fs/promises');
7983
- const path37 = await import('path');
7984
- const statePath = path37.join(cwd, ".coco", "state.json");
7985
- const backlogPath = path37.join(cwd, ".coco", "planning", "backlog.json");
7986
- const checkpointDir = path37.join(cwd, ".coco", "checkpoints");
8018
+ const path36 = await import('path');
8019
+ const statePath = path36.join(cwd, ".coco", "state.json");
8020
+ const backlogPath = path36.join(cwd, ".coco", "planning", "backlog.json");
8021
+ const checkpointDir = path36.join(cwd, ".coco", "checkpoints");
7987
8022
  let currentPhase = "idle";
7988
8023
  let metrics;
7989
8024
  let sprint;
@@ -8698,8 +8733,8 @@ async function saveConfig(config) {
8698
8733
  await fs39.mkdir(".coco", { recursive: true });
8699
8734
  await fs39.writeFile(".coco/config.json", JSON.stringify(config, null, 2));
8700
8735
  }
8701
- function getNestedValue(obj, path37) {
8702
- const keys = path37.split(".");
8736
+ function getNestedValue(obj, path36) {
8737
+ const keys = path36.split(".");
8703
8738
  let current = obj;
8704
8739
  for (const key of keys) {
8705
8740
  if (current === null || current === void 0 || typeof current !== "object") {
@@ -8709,8 +8744,8 @@ function getNestedValue(obj, path37) {
8709
8744
  }
8710
8745
  return current;
8711
8746
  }
8712
- function setNestedValue(obj, path37, value) {
8713
- const keys = path37.split(".");
8747
+ function setNestedValue(obj, path36, value) {
8748
+ const keys = path36.split(".");
8714
8749
  let current = obj;
8715
8750
  for (let i = 0; i < keys.length - 1; i++) {
8716
8751
  const key = keys[i];
@@ -8931,8 +8966,8 @@ var MCPRegistryImpl = class {
8931
8966
  /**
8932
8967
  * Ensure directory exists
8933
8968
  */
8934
- async ensureDir(path37) {
8935
- await mkdir(dirname(path37), { recursive: true });
8969
+ async ensureDir(path36) {
8970
+ await mkdir(dirname(path36), { recursive: true });
8936
8971
  }
8937
8972
  };
8938
8973
  function createMCPRegistry(registryPath) {
@@ -9451,9 +9486,9 @@ function createEmptyMemoryContext() {
9451
9486
  errors: []
9452
9487
  };
9453
9488
  }
9454
- function createMissingMemoryFile(path37, level) {
9489
+ function createMissingMemoryFile(path36, level) {
9455
9490
  return {
9456
- path: path37,
9491
+ path: path36,
9457
9492
  level,
9458
9493
  content: "",
9459
9494
  sections: [],
@@ -9658,7 +9693,9 @@ async function saveTrustSettings(settings) {
9658
9693
  await fs22__default.mkdir(TRUST_SETTINGS_DIR, { recursive: true });
9659
9694
  settings.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9660
9695
  await fs22__default.writeFile(TRUST_SETTINGS_FILE, JSON.stringify(settings, null, 2), "utf-8");
9661
- } catch {
9696
+ } catch (error) {
9697
+ const msg = error instanceof Error ? error.message : String(error);
9698
+ console.warn(`[Trust] Failed to save trust settings: ${msg}`);
9662
9699
  }
9663
9700
  }
9664
9701
  async function loadTrustedTools(projectPath) {
@@ -12142,8 +12179,8 @@ async function listTrustedProjects2(trustStore) {
12142
12179
  p9.log.message("");
12143
12180
  for (const project of projects) {
12144
12181
  const level = project.approvalLevel.toUpperCase().padEnd(5);
12145
- const path37 = project.path.length > 50 ? "..." + project.path.slice(-47) : project.path;
12146
- p9.log.message(` [${level}] ${path37}`);
12182
+ const path36 = project.path.length > 50 ? "..." + project.path.slice(-47) : project.path;
12183
+ p9.log.message(` [${level}] ${path36}`);
12147
12184
  p9.log.message(` Last accessed: ${new Date(project.lastAccessed).toLocaleString()}`);
12148
12185
  }
12149
12186
  p9.log.message("");
@@ -13403,8 +13440,8 @@ function displayRewindResult(result) {
13403
13440
  const fileName = filePath.split("/").pop() ?? filePath;
13404
13441
  console.log(`${chalk12.green(String.fromCodePoint(10003))} Restored: ${fileName}`);
13405
13442
  }
13406
- for (const { path: path37, error } of result.filesFailed) {
13407
- const fileName = path37.split("/").pop() ?? path37;
13443
+ for (const { path: path36, error } of result.filesFailed) {
13444
+ const fileName = path36.split("/").pop() ?? path36;
13408
13445
  console.log(`${chalk12.red(String.fromCodePoint(10007))} Failed: ${fileName} (${error})`);
13409
13446
  }
13410
13447
  if (result.conversationRestored) {
@@ -14369,7 +14406,11 @@ function flushLineBuffer() {
14369
14406
  }
14370
14407
  if (inCodeBlock && codeBlockLines.length > 0) {
14371
14408
  stopStreamingIndicator();
14372
- renderCodeBlock(codeBlockLang, codeBlockLines);
14409
+ try {
14410
+ renderCodeBlock(codeBlockLang, codeBlockLines);
14411
+ } finally {
14412
+ stopStreamingIndicator();
14413
+ }
14373
14414
  inCodeBlock = false;
14374
14415
  codeBlockLang = "";
14375
14416
  codeBlockLines = [];
@@ -14725,6 +14766,7 @@ function formatInlineMarkdown(text9) {
14725
14766
  return text9;
14726
14767
  }
14727
14768
  function wrapText(text9, maxWidth) {
14769
+ if (maxWidth <= 0) return [text9];
14728
14770
  const plainText = stripAnsi(text9);
14729
14771
  if (plainText.length <= maxWidth) {
14730
14772
  return [text9];
@@ -14732,14 +14774,37 @@ function wrapText(text9, maxWidth) {
14732
14774
  const lines = [];
14733
14775
  let remaining = text9;
14734
14776
  while (stripAnsi(remaining).length > maxWidth) {
14735
- let breakPoint = maxWidth;
14736
14777
  const plain = stripAnsi(remaining);
14778
+ let breakPoint = maxWidth;
14737
14779
  const lastSpace = plain.lastIndexOf(" ", maxWidth);
14738
14780
  if (lastSpace > maxWidth * 0.5) {
14739
14781
  breakPoint = lastSpace;
14740
14782
  }
14741
- lines.push(remaining.slice(0, breakPoint));
14742
- remaining = remaining.slice(breakPoint).trimStart();
14783
+ const ansiRegex = /\x1b\[[0-9;]*m/g;
14784
+ let match;
14785
+ const ansiPositions = [];
14786
+ ansiRegex.lastIndex = 0;
14787
+ while ((match = ansiRegex.exec(remaining)) !== null) {
14788
+ ansiPositions.push({ start: match.index, end: match.index + match[0].length });
14789
+ }
14790
+ let rawPos = 0;
14791
+ let visualPos = 0;
14792
+ let ansiIdx = 0;
14793
+ while (visualPos < breakPoint && rawPos < remaining.length) {
14794
+ while (ansiIdx < ansiPositions.length && ansiPositions[ansiIdx].start === rawPos) {
14795
+ rawPos = ansiPositions[ansiIdx].end;
14796
+ ansiIdx++;
14797
+ }
14798
+ if (rawPos >= remaining.length) break;
14799
+ rawPos++;
14800
+ visualPos++;
14801
+ }
14802
+ while (ansiIdx < ansiPositions.length && ansiPositions[ansiIdx].start === rawPos) {
14803
+ rawPos = ansiPositions[ansiIdx].end;
14804
+ ansiIdx++;
14805
+ }
14806
+ lines.push(remaining.slice(0, rawPos));
14807
+ remaining = remaining.slice(rawPos).trimStart();
14743
14808
  }
14744
14809
  if (remaining) {
14745
14810
  lines.push(remaining);
@@ -14809,8 +14874,8 @@ function formatToolSummary(toolName, input) {
14809
14874
  return String(input.path || ".");
14810
14875
  case "search_files": {
14811
14876
  const pattern = String(input.pattern || "");
14812
- const path37 = input.path ? ` in ${input.path}` : "";
14813
- return `"${pattern}"${path37}`;
14877
+ const path36 = input.path ? ` in ${input.path}` : "";
14878
+ return `"${pattern}"${path36}`;
14814
14879
  }
14815
14880
  case "bash_exec": {
14816
14881
  const cmd = String(input.command || "");
@@ -16188,6 +16253,9 @@ function createInputHandler(_session) {
16188
16253
  let historyIndex = -1;
16189
16254
  let tempLine = "";
16190
16255
  let lastMenuLines = 0;
16256
+ let lastCursorRow = 0;
16257
+ let lastContentRows = 1;
16258
+ let isFirstRender = true;
16191
16259
  let isPasting = false;
16192
16260
  let pasteBuffer = "";
16193
16261
  let isReadingClipboard = false;
@@ -16219,11 +16287,13 @@ function createInputHandler(_session) {
16219
16287
  function render() {
16220
16288
  const termCols = process.stdout.columns || 80;
16221
16289
  const prompt = getPrompt();
16222
- const totalVisualLen = prompt.visualLen + currentLine.length;
16223
- const wrappedLines = Math.max(0, Math.ceil(totalVisualLen / termCols) - 1);
16224
- if (wrappedLines > 0) {
16225
- process.stdout.write(ansiEscapes3.cursorUp(wrappedLines));
16290
+ if (!isFirstRender) {
16291
+ const linesToGoUp = lastCursorRow + 1;
16292
+ if (linesToGoUp > 0) {
16293
+ process.stdout.write(ansiEscapes3.cursorUp(linesToGoUp));
16294
+ }
16226
16295
  }
16296
+ isFirstRender = false;
16227
16297
  process.stdout.write("\r" + ansiEscapes3.eraseDown);
16228
16298
  const separator = chalk12.dim("\u2500".repeat(termCols));
16229
16299
  let output = separator + "\n";
@@ -16236,7 +16306,12 @@ function createInputHandler(_session) {
16236
16306
  output += chalk12.dim.gray(ghost);
16237
16307
  }
16238
16308
  }
16309
+ const totalContentLen = prompt.visualLen + currentLine.length;
16310
+ const contentRows = totalContentLen === 0 ? 1 : Math.ceil(totalContentLen / termCols);
16311
+ const contentExactFill = totalContentLen > 0 && totalContentLen % termCols === 0;
16312
+ output += (contentExactFill ? "" : "\n") + separator;
16239
16313
  const showMenu = completions.length > 0 && currentLine.startsWith("/") && currentLine.length >= 1;
16314
+ let extraLinesBelow = 0;
16240
16315
  if (showMenu) {
16241
16316
  const cols = getColumnCount();
16242
16317
  const maxVisibleItems = MAX_ROWS * cols;
@@ -16255,9 +16330,11 @@ function createInputHandler(_session) {
16255
16330
  output += "\n";
16256
16331
  output += chalk12.dim(` \u2191 ${startIndex} more above`);
16257
16332
  lastMenuLines++;
16333
+ extraLinesBelow++;
16258
16334
  }
16259
16335
  for (let row = 0; row < rowCount; row++) {
16260
16336
  output += "\n";
16337
+ extraLinesBelow++;
16261
16338
  for (let col = 0; col < cols; col++) {
16262
16339
  const itemIndex = row * cols + col;
16263
16340
  if (itemIndex >= visibleItems.length) break;
@@ -16276,22 +16353,22 @@ function createInputHandler(_session) {
16276
16353
  output += "\n";
16277
16354
  output += chalk12.dim(` \u2193 ${completions.length - endIndex} more below`);
16278
16355
  lastMenuLines++;
16356
+ extraLinesBelow++;
16279
16357
  }
16280
- for (let i = 0; i < BOTTOM_MARGIN; i++) {
16281
- output += "\n";
16282
- }
16283
- output += ansiEscapes3.cursorUp(lastMenuLines + BOTTOM_MARGIN + 1);
16284
16358
  } else {
16285
16359
  lastMenuLines = 0;
16286
- for (let i = 0; i < BOTTOM_MARGIN; i++) {
16287
- output += "\n";
16288
- }
16289
- output += ansiEscapes3.cursorUp(BOTTOM_MARGIN + 1);
16290
16360
  }
16361
+ for (let i = 0; i < BOTTOM_MARGIN; i++) {
16362
+ output += "\n";
16363
+ extraLinesBelow++;
16364
+ }
16365
+ const totalUp = extraLinesBelow + 1 + contentRows;
16366
+ output += ansiEscapes3.cursorUp(totalUp);
16291
16367
  output += ansiEscapes3.cursorDown(1);
16292
16368
  const cursorAbsolutePos = prompt.visualLen + cursorPos;
16293
- const finalLine = Math.floor(cursorAbsolutePos / termCols);
16294
- const finalCol = cursorAbsolutePos % termCols;
16369
+ const onExactBoundary = cursorAbsolutePos > 0 && cursorAbsolutePos % termCols === 0;
16370
+ const finalLine = onExactBoundary ? cursorAbsolutePos / termCols - 1 : Math.floor(cursorAbsolutePos / termCols);
16371
+ const finalCol = onExactBoundary ? 0 : cursorAbsolutePos % termCols;
16295
16372
  output += "\r";
16296
16373
  if (finalLine > 0) {
16297
16374
  output += ansiEscapes3.cursorDown(finalLine);
@@ -16299,10 +16376,16 @@ function createInputHandler(_session) {
16299
16376
  if (finalCol > 0) {
16300
16377
  output += ansiEscapes3.cursorForward(finalCol);
16301
16378
  }
16379
+ lastCursorRow = finalLine;
16380
+ lastContentRows = contentRows;
16302
16381
  process.stdout.write(output);
16303
16382
  }
16304
16383
  function clearMenu() {
16305
- process.stdout.write(ansiEscapes3.eraseDown);
16384
+ const rowsDown = lastContentRows - lastCursorRow + 1;
16385
+ if (rowsDown > 0) {
16386
+ process.stdout.write(ansiEscapes3.cursorDown(rowsDown));
16387
+ }
16388
+ process.stdout.write("\r" + ansiEscapes3.eraseDown);
16306
16389
  lastMenuLines = 0;
16307
16390
  }
16308
16391
  function insertTextAtCursor(text9) {
@@ -16325,15 +16408,21 @@ function createInputHandler(_session) {
16325
16408
  historyIndex = -1;
16326
16409
  tempLine = "";
16327
16410
  lastMenuLines = 0;
16411
+ lastCursorRow = 0;
16412
+ lastContentRows = 1;
16413
+ isFirstRender = true;
16328
16414
  render();
16329
16415
  if (process.stdin.isTTY) {
16330
16416
  process.stdin.setRawMode(true);
16331
16417
  }
16332
16418
  process.stdin.resume();
16333
16419
  process.stdout.write("\x1B[?2004h");
16420
+ const onResize = () => render();
16421
+ process.stdout.on("resize", onResize);
16334
16422
  const cleanup = () => {
16335
16423
  process.stdout.write("\x1B[?2004l");
16336
16424
  process.stdin.removeListener("data", onData);
16425
+ process.stdout.removeListener("resize", onResize);
16337
16426
  if (process.stdin.isTTY) {
16338
16427
  process.stdin.setRawMode(false);
16339
16428
  }
@@ -16463,9 +16552,7 @@ function createInputHandler(_session) {
16463
16552
  currentLine = completions[selectedCompletion].cmd;
16464
16553
  }
16465
16554
  cleanup();
16466
- const termWidth = process.stdout.columns || 80;
16467
16555
  console.log();
16468
- console.log(chalk12.dim("\u2500".repeat(termWidth)));
16469
16556
  const result = currentLine.trim();
16470
16557
  if (result) {
16471
16558
  sessionHistory.push(result);
@@ -17504,7 +17591,8 @@ async function promptEditCommand(originalCommand) {
17504
17591
  console.log();
17505
17592
  console.log(chalk12.dim(" Edit command (or press Enter to cancel):"));
17506
17593
  console.log(chalk12.cyan(` Current: ${originalCommand}`));
17507
- if (process.stdin.isTTY && process.stdin.isRaw) {
17594
+ const wasRaw = process.stdin.isTTY ? process.stdin.isRaw : false;
17595
+ if (process.stdin.isTTY && wasRaw) {
17508
17596
  process.stdin.setRawMode(false);
17509
17597
  }
17510
17598
  const rl = readline2.createInterface({
@@ -17517,6 +17605,9 @@ async function promptEditCommand(originalCommand) {
17517
17605
  return trimmed || null;
17518
17606
  } finally {
17519
17607
  rl.close();
17608
+ if (process.stdin.isTTY && wasRaw) {
17609
+ process.stdin.setRawMode(true);
17610
+ }
17520
17611
  }
17521
17612
  }
17522
17613
  async function confirmToolExecution(toolCall) {
@@ -20228,10 +20319,10 @@ var CoverageAnalyzer = class {
20228
20319
  join(this.projectPath, ".coverage", "coverage-summary.json"),
20229
20320
  join(this.projectPath, "coverage", "lcov-report", "coverage-summary.json")
20230
20321
  ];
20231
- for (const path37 of possiblePaths) {
20322
+ for (const path36 of possiblePaths) {
20232
20323
  try {
20233
- await access(path37, constants.R_OK);
20234
- const content = await readFile(path37, "utf-8");
20324
+ await access(path36, constants.R_OK);
20325
+ const content = await readFile(path36, "utf-8");
20235
20326
  const report = JSON.parse(content);
20236
20327
  return parseCoverageSummary(report);
20237
20328
  } catch {
@@ -22452,17 +22543,17 @@ var QualityEvaluator = class {
22452
22543
  maintainabilityResult
22453
22544
  ] = await Promise.all([
22454
22545
  this.coverageAnalyzer.analyze().catch(() => null),
22455
- this.securityScanner.scan(fileContents),
22456
- this.complexityAnalyzer.analyze(targetFiles),
22457
- this.duplicationAnalyzer.analyze(targetFiles),
22546
+ this.securityScanner.scan(fileContents).catch(() => ({ score: 0, vulnerabilities: [] })),
22547
+ this.complexityAnalyzer.analyze(targetFiles).catch(() => ({ score: 0, files: [] })),
22548
+ this.duplicationAnalyzer.analyze(targetFiles).catch(() => ({ score: 0, percentage: 0 })),
22458
22549
  this.correctnessAnalyzer.analyze().catch(() => ({ score: 0 })),
22459
22550
  this.completenessAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
22460
22551
  this.robustnessAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
22461
22552
  this.testQualityAnalyzer.analyze().catch(() => ({ score: 0 })),
22462
22553
  this.documentationAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
22463
- this.styleAnalyzer.analyze().catch(() => ({ score: 50 })),
22464
- this.readabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 50 })),
22465
- this.maintainabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 50 }))
22554
+ this.styleAnalyzer.analyze().catch(() => ({ score: 0 })),
22555
+ this.readabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
22556
+ this.maintainabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 }))
22466
22557
  ]);
22467
22558
  const dimensions = {
22468
22559
  testCoverage: coverageResult?.lines.percentage ?? 0,
@@ -22976,7 +23067,17 @@ Examples:
22976
23067
  regexPattern = `\\b${pattern}\\b`;
22977
23068
  }
22978
23069
  const flags = caseSensitive ? "g" : "gi";
22979
- const regex = new RegExp(regexPattern, flags);
23070
+ let regex;
23071
+ try {
23072
+ regex = new RegExp(regexPattern, flags);
23073
+ } catch {
23074
+ throw new ToolError(`Invalid regex pattern: ${pattern}`, { tool: "grep" });
23075
+ }
23076
+ if (/(\.\*|\.\+|\[.*\][*+])\s*(\.\*|\.\+|\[.*\][*+])/.test(regexPattern)) {
23077
+ throw new ToolError(`Regex pattern rejected: nested quantifiers may cause slow matching`, {
23078
+ tool: "grep"
23079
+ });
23080
+ }
22980
23081
  const stats = await fs22__default.stat(targetPath);
22981
23082
  let filesToSearch;
22982
23083
  if (stats.isFile()) {
@@ -24114,9 +24215,11 @@ function parseDiff(raw) {
24114
24215
  function parseFileBlock(lines, start) {
24115
24216
  const diffLine = lines[start];
24116
24217
  let i = start + 1;
24117
- const pathMatch = diffLine.match(/^diff --git a\/(.+?) b\/(.+)$/);
24118
- const oldPath = pathMatch?.[1] ?? "";
24119
- const newPath = pathMatch?.[2] ?? oldPath;
24218
+ const gitPrefix = "diff --git a/";
24219
+ const pathPart = diffLine.slice(gitPrefix.length);
24220
+ const lastBSlash = pathPart.lastIndexOf(" b/");
24221
+ const oldPath = lastBSlash >= 0 ? pathPart.slice(0, lastBSlash) : pathPart;
24222
+ const newPath = lastBSlash >= 0 ? pathPart.slice(lastBSlash + 3) : oldPath;
24120
24223
  let fileType = "modified";
24121
24224
  while (i < lines.length && !lines[i].startsWith("diff --git ")) {
24122
24225
  const current = lines[i];
@@ -24262,7 +24365,7 @@ function renderDiff(diff, options) {
24262
24365
  function renderFileBlock(file, opts) {
24263
24366
  const { maxWidth, showLineNumbers, compact } = opts;
24264
24367
  const lang = detectLanguage(file.path);
24265
- const contentWidth = maxWidth - 4;
24368
+ const contentWidth = Math.max(1, maxWidth - 4);
24266
24369
  const typeLabel = file.type === "modified" ? "modified" : file.type === "added" ? "new file" : file.type === "deleted" ? "deleted" : `renamed from ${file.oldPath}`;
24267
24370
  const statsLabel = ` +${file.additions} -${file.deletions}`;
24268
24371
  const title = ` ${file.path} (${typeLabel}${statsLabel}) `;
@@ -24309,12 +24412,8 @@ function renderFileBlock(file, opts) {
24309
24412
  }
24310
24413
  function formatLineNo(line, show) {
24311
24414
  if (!show) return "";
24312
- if (line.type === "add") {
24313
- return chalk12.dim(`${String(line.newLineNo ?? "").padStart(4)} `);
24314
- } else if (line.type === "delete") {
24315
- return chalk12.dim(`${String(line.oldLineNo ?? "").padStart(4)} `);
24316
- }
24317
- return chalk12.dim(`${String(line.newLineNo ?? "").padStart(4)} `);
24415
+ const lineNo = line.type === "delete" ? line.oldLineNo : line.newLineNo;
24416
+ return chalk12.dim(`${String(lineNo ?? "").padStart(5)} `);
24318
24417
  }
24319
24418
  function getChangedLines(diff) {
24320
24419
  const result = /* @__PURE__ */ new Map();
@@ -24405,9 +24504,9 @@ Examples:
24405
24504
  }
24406
24505
  });
24407
24506
  var diffTools = [showDiffTool];
24408
- async function fileExists(path37) {
24507
+ async function fileExists(path36) {
24409
24508
  try {
24410
- await access(path37);
24509
+ await access(path36);
24411
24510
  return true;
24412
24511
  } catch {
24413
24512
  return false;
@@ -26471,6 +26570,13 @@ Examples:
26471
26570
  const startTime = performance.now();
26472
26571
  const effectivePrompt = prompt ?? "Describe this image in detail. If it's code or a UI, identify the key elements.";
26473
26572
  const absPath = path31.resolve(filePath);
26573
+ const cwd = process.cwd();
26574
+ if (!absPath.startsWith(cwd + path31.sep) && absPath !== cwd) {
26575
+ throw new ToolError(
26576
+ `Path traversal denied: '${filePath}' resolves outside the project directory`,
26577
+ { tool: "read_image" }
26578
+ );
26579
+ }
26474
26580
  const ext = path31.extname(absPath).toLowerCase();
26475
26581
  if (!SUPPORTED_FORMATS.has(ext)) {
26476
26582
  throw new ToolError(
@@ -28531,24 +28637,6 @@ var pc = chalk12;
28531
28637
  });
28532
28638
 
28533
28639
  // src/cli/repl/index.ts
28534
- function visualWidth(str) {
28535
- const stripped = str.replace(/\x1b\[[0-9;]*m/g, "");
28536
- let width = 0;
28537
- for (const char of stripped) {
28538
- const cp = char.codePointAt(0) || 0;
28539
- if (cp > 128512 || cp >= 9728 && cp <= 10175 || cp >= 127744 && cp <= 129750 || cp >= 129280 && cp <= 129535 || cp >= 9986 && cp <= 10160 || cp >= 65024 && cp <= 65039 || cp === 8205) {
28540
- width += 2;
28541
- } else if (
28542
- // CJK Unified Ideographs and other wide characters
28543
- cp >= 4352 && cp <= 4447 || cp >= 11904 && cp <= 12350 || cp >= 12352 && cp <= 13247 || cp >= 19968 && cp <= 40959 || cp >= 63744 && cp <= 64255 || cp >= 65072 && cp <= 65135 || cp >= 65281 && cp <= 65376 || cp >= 65504 && cp <= 65510
28544
- ) {
28545
- width += 2;
28546
- } else {
28547
- width += 1;
28548
- }
28549
- }
28550
- return width;
28551
- }
28552
28640
  async function startRepl(options = {}) {
28553
28641
  const projectPath = options.projectPath ?? process.cwd();
28554
28642
  const session = createSession(projectPath, options.config);
@@ -28598,6 +28686,17 @@ async function startRepl(options = {}) {
28598
28686
  const inputHandler = createInputHandler();
28599
28687
  const intentRecognizer = createIntentRecognizer();
28600
28688
  await printWelcome(session);
28689
+ const cleanupTerminal = () => {
28690
+ process.stdout.write("\x1B[?2004l");
28691
+ if (process.stdin.isTTY && process.stdin.isRaw) {
28692
+ process.stdin.setRawMode(false);
28693
+ }
28694
+ };
28695
+ process.on("exit", cleanupTerminal);
28696
+ process.on("SIGTERM", () => {
28697
+ cleanupTerminal();
28698
+ process.exit(0);
28699
+ });
28601
28700
  while (true) {
28602
28701
  const input = await inputHandler.prompt();
28603
28702
  if (input === null && !hasPendingImage()) {
@@ -28677,6 +28776,14 @@ async function startRepl(options = {}) {
28677
28776
  activeSpinner.start();
28678
28777
  }
28679
28778
  };
28779
+ const abortController = new AbortController();
28780
+ let wasAborted = false;
28781
+ const sigintHandler = () => {
28782
+ wasAborted = true;
28783
+ abortController.abort();
28784
+ clearSpinner();
28785
+ renderInfo("\nOperation cancelled");
28786
+ };
28680
28787
  try {
28681
28788
  if (typeof agentMessage === "string" && !isCocoMode() && !wasHintShown() && looksLikeFeatureRequest(agentMessage)) {
28682
28789
  markHintShown();
@@ -28689,14 +28796,6 @@ async function startRepl(options = {}) {
28689
28796
  session.config.agent.systemPrompt = originalSystemPrompt + "\n" + getCocoModeSystemPrompt();
28690
28797
  }
28691
28798
  inputHandler.pause();
28692
- const abortController = new AbortController();
28693
- let wasAborted = false;
28694
- const sigintHandler = () => {
28695
- wasAborted = true;
28696
- abortController.abort();
28697
- clearSpinner();
28698
- renderInfo("\nOperation cancelled");
28699
- };
28700
28799
  process.once("SIGINT", sigintHandler);
28701
28800
  const result = await executeAgentTurn(session, agentMessage, provider, toolRegistry, {
28702
28801
  onStream: renderStreamChunk,
@@ -28785,6 +28884,7 @@ async function startRepl(options = {}) {
28785
28884
  console.log();
28786
28885
  } catch (error) {
28787
28886
  clearSpinner();
28887
+ process.off("SIGINT", sigintHandler);
28788
28888
  if (error instanceof Error && error.name === "AbortError") {
28789
28889
  continue;
28790
28890
  }
@@ -28815,40 +28915,30 @@ async function startRepl(options = {}) {
28815
28915
  inputHandler.close();
28816
28916
  }
28817
28917
  async function printWelcome(session) {
28818
- const providerType = session.config.provider.type;
28819
- const model = session.config.provider.model || "default";
28820
- const isReturningUser = existsSync(path20__default.join(session.projectPath, ".coco"));
28821
- if (isReturningUser) {
28822
- const versionStr = chalk12.dim(`v${VERSION}`);
28823
- const providerStr = chalk12.dim(`${providerType}/${model}`);
28824
- console.log(
28825
- `
28826
- \u{1F965} Coco ${versionStr} ${chalk12.dim("\u2502")} ${providerStr} ${chalk12.dim("\u2502")} ${chalk12.yellow("/help")}
28827
- `
28828
- );
28829
- return;
28830
- }
28831
28918
  const trustStore = createTrustStore();
28832
28919
  await trustStore.init();
28833
28920
  const trustLevel = trustStore.getLevel(session.projectPath);
28834
28921
  const boxWidth = 41;
28835
- const innerWidth = boxWidth - 4;
28836
- const titleContent = "\u{1F965} CORBAT-COCO";
28922
+ const innerWidth = boxWidth - 2;
28837
28923
  const versionText = `v${VERSION}`;
28838
- const titleContentVisualWidth = visualWidth(titleContent);
28839
- const versionVisualWidth = visualWidth(versionText);
28840
- const titlePadding = innerWidth - titleContentVisualWidth - versionVisualWidth;
28841
28924
  const subtitleText = "open source \u2022 corbat.tech";
28842
- const subtitleVisualWidth = visualWidth(subtitleText);
28843
- const subtitlePadding = innerWidth - subtitleVisualWidth;
28925
+ const boxLine = (content) => {
28926
+ const pad = Math.max(0, innerWidth - stringWidth(content));
28927
+ return chalk12.magenta("\u2502") + content + " ".repeat(pad) + chalk12.magenta("\u2502");
28928
+ };
28929
+ const titleLeftRaw = " COCO";
28930
+ const titleRightRaw = versionText + " ";
28931
+ const titleLeftStyled = " " + chalk12.bold.white("COCO");
28932
+ const titleGap = Math.max(1, innerWidth - stringWidth(titleLeftRaw) - stringWidth(titleRightRaw));
28933
+ const titleContent = titleLeftStyled + " ".repeat(titleGap) + chalk12.dim(titleRightRaw);
28934
+ const taglineText = "code that converges to quality";
28935
+ const taglineContent = " " + chalk12.magenta(taglineText) + " ";
28936
+ const subtitleContent = " " + chalk12.dim(subtitleText) + " ";
28844
28937
  console.log();
28845
28938
  console.log(chalk12.magenta(" \u256D" + "\u2500".repeat(boxWidth - 2) + "\u256E"));
28846
- console.log(
28847
- chalk12.magenta(" \u2502 ") + "\u{1F965} " + chalk12.bold.white("CORBAT-COCO") + " ".repeat(Math.max(0, titlePadding)) + chalk12.dim(versionText) + chalk12.magenta(" \u2502")
28848
- );
28849
- console.log(
28850
- chalk12.magenta(" \u2502 ") + chalk12.dim(subtitleText) + " ".repeat(Math.max(0, subtitlePadding)) + chalk12.magenta(" \u2502")
28851
- );
28939
+ console.log(" " + boxLine(titleContent));
28940
+ console.log(" " + boxLine(taglineContent));
28941
+ console.log(" " + boxLine(subtitleContent));
28852
28942
  console.log(chalk12.magenta(" \u2570" + "\u2500".repeat(boxWidth - 2) + "\u256F"));
28853
28943
  const updateInfo = await checkForUpdates();
28854
28944
  if (updateInfo) {
@@ -28889,7 +28979,7 @@ async function checkProjectTrust(projectPath) {
28889
28979
  return true;
28890
28980
  }
28891
28981
  console.log();
28892
- console.log(chalk12.cyan.bold(" \u{1F965} Corbat-Coco") + chalk12.dim(` v${VERSION}`));
28982
+ console.log(chalk12.cyan.bold(" \u{1F965} Coco") + chalk12.dim(` v${VERSION}`));
28893
28983
  console.log(chalk12.dim(` \u{1F4C1} ${projectPath}`));
28894
28984
  console.log();
28895
28985
  console.log(chalk12.yellow(" \u26A0 First time accessing this directory"));