@corbat-tech/coco 2.27.0 → 2.27.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
@@ -2324,13 +2324,20 @@ async function isADCConfigured() {
2324
2324
  }
2325
2325
  async function runGcloudADCLogin() {
2326
2326
  try {
2327
- await execAsync("gcloud auth application-default login --no-launch-browser", {
2328
- timeout: 12e4
2329
- // 2 minute timeout for manual login
2327
+ await execAsync(ADC_LOGIN_COMMAND, {
2328
+ timeout: 3e5
2329
+ // 5 minutes for interactive auth
2330
2330
  });
2331
2331
  return true;
2332
2332
  } catch {
2333
- return false;
2333
+ try {
2334
+ await execAsync(`${ADC_LOGIN_COMMAND} --no-launch-browser`, {
2335
+ timeout: 3e5
2336
+ });
2337
+ return true;
2338
+ } catch {
2339
+ return false;
2340
+ }
2334
2341
  }
2335
2342
  }
2336
2343
  async function getGeminiADCKey() {
@@ -2504,7 +2511,7 @@ function getApiKey(provider) {
2504
2511
  case "gemini":
2505
2512
  return process.env["GEMINI_API_KEY"] ?? process.env["GOOGLE_API_KEY"];
2506
2513
  case "vertex":
2507
- return void 0;
2514
+ return process.env["VERTEX_API_KEY"] ?? process.env["GOOGLE_API_KEY"];
2508
2515
  case "kimi":
2509
2516
  return process.env["KIMI_API_KEY"] ?? process.env["MOONSHOT_API_KEY"];
2510
2517
  case "kimi-code":
@@ -6117,21 +6124,26 @@ var init_vertex = __esm({
6117
6124
  config = {};
6118
6125
  project = "";
6119
6126
  location = DEFAULT_LOCATION;
6127
+ apiKey;
6120
6128
  retryConfig = DEFAULT_RETRY_CONFIG;
6121
6129
  async initialize(config) {
6122
6130
  this.config = config;
6123
6131
  this.project = config.project ?? process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"] ?? "";
6124
6132
  this.location = config.location ?? process.env["VERTEX_LOCATION"] ?? process.env["GOOGLE_CLOUD_LOCATION"] ?? DEFAULT_LOCATION;
6133
+ this.apiKey = config.apiKey ?? process.env["VERTEX_API_KEY"] ?? process.env["GOOGLE_API_KEY"];
6125
6134
  if (!this.project.trim()) {
6126
6135
  throw new ProviderError(
6127
6136
  "Vertex AI project not configured. Set provider.project, VERTEX_PROJECT, or GOOGLE_CLOUD_PROJECT.",
6128
6137
  { provider: this.id }
6129
6138
  );
6130
6139
  }
6140
+ if (this.apiKey?.trim()) {
6141
+ return;
6142
+ }
6131
6143
  const token = await getCachedADCToken();
6132
6144
  if (!token) {
6133
6145
  throw new ProviderError(
6134
- "Vertex AI ADC is not configured. Run `gcloud auth application-default login` manually, then retry.",
6146
+ "Vertex AI authentication is not configured. Set VERTEX_API_KEY (or GOOGLE_API_KEY), or run `gcloud auth application-default login`.",
6135
6147
  { provider: this.id }
6136
6148
  );
6137
6149
  }
@@ -6250,10 +6262,17 @@ var init_vertex = __esm({
6250
6262
  return `${this.getResolvedBaseUrl()}/projects/${encodeURIComponent(this.project)}/locations/${encodeURIComponent(this.location)}/publishers/google/models/${encodeURIComponent(this.getModel(model))}:${action}`;
6251
6263
  }
6252
6264
  async getHeaders() {
6265
+ if (this.apiKey?.trim()) {
6266
+ return {
6267
+ "Content-Type": "application/json",
6268
+ "x-goog-api-key": this.apiKey,
6269
+ "x-goog-user-project": this.project
6270
+ };
6271
+ }
6253
6272
  const token = await getCachedADCToken();
6254
6273
  if (!token) {
6255
6274
  throw new ProviderError(
6256
- "Vertex AI ADC token is unavailable. Re-authenticate with gcloud and retry.",
6275
+ "Vertex AI token is unavailable. Re-authenticate with gcloud or configure VERTEX_API_KEY.",
6257
6276
  { provider: this.id }
6258
6277
  );
6259
6278
  }
@@ -20329,11 +20348,11 @@ async function runMergeRelease(ctx) {
20329
20348
  }
20330
20349
  const tagName = ctx.newVersion ? `v${ctx.newVersion}` : void 0;
20331
20350
  const mergeMsg = tagName ? `Merge PR #${ctx.prNumber} to ${ctx.profile.defaultBranch} and create release ${tagName}?` : `Merge PR #${ctx.prNumber} to ${ctx.profile.defaultBranch}?`;
20332
- const confirm22 = await p26.confirm({
20351
+ const confirm23 = await p26.confirm({
20333
20352
  message: mergeMsg,
20334
20353
  initialValue: true
20335
20354
  });
20336
- if (p26.isCancel(confirm22) || !confirm22) {
20355
+ if (p26.isCancel(confirm23) || !confirm23) {
20337
20356
  return {
20338
20357
  step: "merge-release",
20339
20358
  status: "skipped",
@@ -22508,9 +22527,10 @@ var init_lifecycle = __esm({
22508
22527
  init_errors2();
22509
22528
  init_logger();
22510
22529
  init_version();
22511
- MCPServerManager = class {
22530
+ MCPServerManager = class _MCPServerManager {
22512
22531
  connections = /* @__PURE__ */ new Map();
22513
22532
  logger = getLogger();
22533
+ static STOP_TIMEOUT_MS = 5e3;
22514
22534
  /**
22515
22535
  * Create transport for a server config
22516
22536
  */
@@ -22597,7 +22617,15 @@ var init_lifecycle = __esm({
22597
22617
  }
22598
22618
  this.logger.info(`Stopping MCP server: ${name}`);
22599
22619
  try {
22600
- await connection.transport.disconnect();
22620
+ await Promise.race([
22621
+ connection.transport.disconnect(),
22622
+ new Promise(
22623
+ (_, reject) => setTimeout(
22624
+ () => reject(new Error("MCP disconnect timeout")),
22625
+ _MCPServerManager.STOP_TIMEOUT_MS
22626
+ )
22627
+ )
22628
+ ]);
22601
22629
  } catch (error) {
22602
22630
  this.logger.error(
22603
22631
  `Error disconnecting server '${name}': ${error instanceof Error ? error.message : String(error)}`
@@ -29186,13 +29214,14 @@ var PROVIDER_DEFINITIONS = {
29186
29214
  functionCalling: true,
29187
29215
  vision: true
29188
29216
  },
29189
- // Updated: March 2026 — from docs.github.com/en/copilot/reference/ai-models/supported-models
29217
+ // Updated: April 2026 — from docs.github.com/en/copilot/reference/ai-models/supported-models
29218
+ // Premium request multipliers in descriptions are for paid Copilot plans.
29190
29219
  models: [
29191
29220
  // Anthropic models
29192
29221
  {
29193
29222
  id: "claude-sonnet-4.6",
29194
29223
  name: "Claude Sonnet 4.6",
29195
- description: "Anthropic's latest balanced model via Copilot",
29224
+ description: "Balanced Claude model via Copilot \u2014 Premium x1",
29196
29225
  contextWindow: 2e5,
29197
29226
  maxOutputTokens: 64e3,
29198
29227
  recommended: true
@@ -29200,28 +29229,28 @@ var PROVIDER_DEFINITIONS = {
29200
29229
  {
29201
29230
  id: "claude-opus-4.6",
29202
29231
  name: "Claude Opus 4.6",
29203
- description: "Anthropic's most intelligent model via Copilot",
29232
+ description: "Most capable Claude model via Copilot \u2014 Premium x3",
29204
29233
  contextWindow: 2e5,
29205
29234
  maxOutputTokens: 128e3
29206
29235
  },
29207
29236
  {
29208
29237
  id: "claude-sonnet-4.5",
29209
29238
  name: "Claude Sonnet 4.5",
29210
- description: "Previous balanced Claude model via Copilot",
29239
+ description: "Previous balanced Claude model via Copilot \u2014 Premium x1",
29211
29240
  contextWindow: 2e5,
29212
29241
  maxOutputTokens: 64e3
29213
29242
  },
29214
29243
  {
29215
29244
  id: "claude-opus-4.5",
29216
29245
  name: "Claude Opus 4.5",
29217
- description: "Previous flagship Claude model via Copilot",
29246
+ description: "Previous flagship Claude model via Copilot \u2014 Premium x3",
29218
29247
  contextWindow: 2e5,
29219
29248
  maxOutputTokens: 64e3
29220
29249
  },
29221
29250
  {
29222
29251
  id: "claude-haiku-4.5",
29223
29252
  name: "Claude Haiku 4.5",
29224
- description: "Fast and affordable Claude model via Copilot",
29253
+ description: "Fast low-cost Claude model via Copilot \u2014 Premium x0.33",
29225
29254
  contextWindow: 2e5,
29226
29255
  maxOutputTokens: 64e3
29227
29256
  },
@@ -29229,7 +29258,7 @@ var PROVIDER_DEFINITIONS = {
29229
29258
  {
29230
29259
  id: "gpt-5.4-codex",
29231
29260
  name: "GPT-5.4 Codex",
29232
- description: "OpenAI's latest coding model via Copilot (Mar 2026)",
29261
+ description: "Latest coding model via Copilot \u2014 Premium x1",
29233
29262
  contextWindow: 4e5,
29234
29263
  maxOutputTokens: 128e3,
29235
29264
  recommended: true
@@ -29237,28 +29266,28 @@ var PROVIDER_DEFINITIONS = {
29237
29266
  {
29238
29267
  id: "gpt-5.3-codex",
29239
29268
  name: "GPT-5.3 Codex",
29240
- description: "OpenAI's previous coding model via Copilot",
29269
+ description: "Previous coding model via Copilot \u2014 Premium x1",
29241
29270
  contextWindow: 4e5,
29242
29271
  maxOutputTokens: 128e3
29243
29272
  },
29244
29273
  {
29245
29274
  id: "gpt-5.2-codex",
29246
29275
  name: "GPT-5.2 Codex",
29247
- description: "OpenAI's previous coding model via Copilot",
29276
+ description: "Previous coding model via Copilot \u2014 Premium x1",
29248
29277
  contextWindow: 4e5,
29249
29278
  maxOutputTokens: 128e3
29250
29279
  },
29251
29280
  {
29252
29281
  id: "gpt-5.1-codex-max",
29253
29282
  name: "GPT-5.1 Codex Max",
29254
- description: "Frontier agentic coding model via Copilot",
29283
+ description: "Frontier agentic coding model via Copilot \u2014 Premium x1",
29255
29284
  contextWindow: 4e5,
29256
29285
  maxOutputTokens: 128e3
29257
29286
  },
29258
29287
  {
29259
29288
  id: "gpt-4.1",
29260
29289
  name: "GPT-4.1",
29261
- description: "OpenAI long-context model via Copilot (1M)",
29290
+ description: "OpenAI long-context model via Copilot (1M) \u2014 Premium x0",
29262
29291
  contextWindow: 1048576,
29263
29292
  maxOutputTokens: 32768
29264
29293
  },
@@ -29266,21 +29295,21 @@ var PROVIDER_DEFINITIONS = {
29266
29295
  {
29267
29296
  id: "gemini-3.1-pro-preview",
29268
29297
  name: "Gemini 3.1 Pro",
29269
- description: "Google's latest model via Copilot (1M)",
29298
+ description: "Google's latest model via Copilot (1M) \u2014 Premium x1",
29270
29299
  contextWindow: 1e6,
29271
29300
  maxOutputTokens: 64e3
29272
29301
  },
29273
29302
  {
29274
29303
  id: "gemini-3-flash-preview",
29275
29304
  name: "Gemini 3 Flash",
29276
- description: "Google's fast model via Copilot (1M)",
29305
+ description: "Google's fast model via Copilot (1M) \u2014 Premium x0.33",
29277
29306
  contextWindow: 1e6,
29278
29307
  maxOutputTokens: 64e3
29279
29308
  },
29280
29309
  {
29281
29310
  id: "gemini-2.5-pro",
29282
29311
  name: "Gemini 2.5 Pro",
29283
- description: "Google stable model via Copilot (1M)",
29312
+ description: "Google stable model via Copilot (1M) \u2014 Premium x1",
29284
29313
  contextWindow: 1048576,
29285
29314
  maxOutputTokens: 65536
29286
29315
  }
@@ -29419,8 +29448,8 @@ var PROVIDER_DEFINITIONS = {
29419
29448
  name: "Google Vertex AI Gemini",
29420
29449
  emoji: "\u2601\uFE0F",
29421
29450
  description: "Gemini on Vertex AI with GCP project, IAM and ADC",
29422
- envVar: "VERTEX_PROJECT",
29423
- apiKeyUrl: "https://console.cloud.google.com/vertex-ai",
29451
+ envVar: "VERTEX_API_KEY",
29452
+ apiKeyUrl: "https://cloud.google.com/vertex-ai/generative-ai/docs/start/api-keys",
29424
29453
  docsUrl: "https://cloud.google.com/vertex-ai/generative-ai/docs/start/quickstart",
29425
29454
  baseUrl: "https://aiplatform.googleapis.com/v1",
29426
29455
  supportsCustomModels: true,
@@ -30098,7 +30127,7 @@ function getConfiguredProviders() {
30098
30127
  return hasLocalProviderConfig(p45.id);
30099
30128
  }
30100
30129
  if (p45.id === "vertex") {
30101
- return !!(process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"]);
30130
+ return !!(process.env["VERTEX_API_KEY"] ?? process.env["GOOGLE_API_KEY"] ?? process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"]);
30102
30131
  }
30103
30132
  return !!process.env[p45.envVar];
30104
30133
  });
@@ -30451,11 +30480,11 @@ async function runRemoveServer(name, options) {
30451
30480
  process.exit(1);
30452
30481
  }
30453
30482
  if (!options.yes) {
30454
- const confirm22 = await p26.confirm({
30483
+ const confirm23 = await p26.confirm({
30455
30484
  message: `Remove server '${name}'?`,
30456
30485
  initialValue: false
30457
30486
  });
30458
- if (p26.isCancel(confirm22) || !confirm22) {
30487
+ if (p26.isCancel(confirm23) || !confirm23) {
30459
30488
  p26.outro("Cancelled");
30460
30489
  return;
30461
30490
  }
@@ -30727,10 +30756,10 @@ async function runRemove(name, options) {
30727
30756
  return;
30728
30757
  }
30729
30758
  if (!options.yes) {
30730
- const confirm22 = await p26.confirm({
30759
+ const confirm23 = await p26.confirm({
30731
30760
  message: `Remove skill "${name}" from ${targetDir}?`
30732
30761
  });
30733
- if (p26.isCancel(confirm22) || !confirm22) {
30762
+ if (p26.isCancel(confirm23) || !confirm23) {
30734
30763
  p26.log.info("Cancelled.");
30735
30764
  p26.outro("");
30736
30765
  return;
@@ -33282,6 +33311,301 @@ function showToolsHelp() {
33282
33311
 
33283
33312
  // src/cli/repl/commands/clear.ts
33284
33313
  init_session();
33314
+ async function getGitContext(projectPath) {
33315
+ const controller = new AbortController();
33316
+ const timer = setTimeout(() => controller.abort(), 3e3);
33317
+ try {
33318
+ const git = simpleGit({ baseDir: projectPath, abort: controller.signal });
33319
+ const status = await git.status();
33320
+ clearTimeout(timer);
33321
+ return {
33322
+ branch: status.current ?? "HEAD",
33323
+ isDirty: !status.isClean(),
33324
+ staged: status.staged.length,
33325
+ modified: status.modified.length,
33326
+ untracked: status.not_added.length,
33327
+ ahead: status.ahead,
33328
+ behind: status.behind
33329
+ };
33330
+ } catch {
33331
+ clearTimeout(timer);
33332
+ return null;
33333
+ }
33334
+ }
33335
+ function formatGitLine(ctx) {
33336
+ const branchColor = ctx.isDirty ? chalk.yellow : chalk.green;
33337
+ const parts = [chalk.dim("\u{1F33F} ") + branchColor(ctx.branch)];
33338
+ const changes = [];
33339
+ if (ctx.staged > 0) changes.push(chalk.green(`+${ctx.staged}`));
33340
+ if (ctx.modified > 0) changes.push(chalk.yellow(`~${ctx.modified}`));
33341
+ if (ctx.untracked > 0) changes.push(chalk.dim(`?${ctx.untracked}`));
33342
+ if (ctx.ahead > 0) changes.push(chalk.cyan(`\u2191${ctx.ahead}`));
33343
+ if (ctx.behind > 0) changes.push(chalk.red(`\u2193${ctx.behind}`));
33344
+ if (changes.length > 0) parts.push(changes.join(" "));
33345
+ return parts.join(" \u2022 ");
33346
+ }
33347
+ function formatGitShort(ctx) {
33348
+ const branch = ctx.isDirty ? chalk.yellow(ctx.branch) : chalk.green(ctx.branch);
33349
+ const dirty = ctx.isDirty ? chalk.yellow(" \u25CF") : "";
33350
+ return chalk.dim("\u{1F33F} ") + branch + dirty;
33351
+ }
33352
+
33353
+ // src/cli/repl/startup-panel.ts
33354
+ init_version();
33355
+ init_trust_store();
33356
+ init_env();
33357
+
33358
+ // src/cli/repl/quality-loop.ts
33359
+ init_paths();
33360
+ var qualityLoopEnabled = true;
33361
+ var hintShown = false;
33362
+ function isQualityLoop() {
33363
+ return qualityLoopEnabled;
33364
+ }
33365
+ function setQualityLoop(enabled) {
33366
+ qualityLoopEnabled = enabled;
33367
+ }
33368
+ function wasHintShown() {
33369
+ return hintShown;
33370
+ }
33371
+ function markHintShown() {
33372
+ hintShown = true;
33373
+ }
33374
+ function looksLikeFeatureRequest(input) {
33375
+ const trimmed = input.trim();
33376
+ if (trimmed.length < 20) return false;
33377
+ if (trimmed.endsWith("?") && trimmed.length < 80) return false;
33378
+ const featureKeywords = [
33379
+ /\bimplement/i,
33380
+ /\bcreate\b/i,
33381
+ /\bbuild\b/i,
33382
+ /\badd\b.*\b(feature|function|component|endpoint|service|module|class)/i,
33383
+ /\brefactor/i,
33384
+ /\bmigrate/i,
33385
+ /\bsetup\b/i,
33386
+ /\bintegrate/i,
33387
+ /\bwrite\b.*\b(code|function|test|module)/i,
33388
+ /\bdevelop/i,
33389
+ /\bdesign\b/i,
33390
+ /\bfix\b.*\b(bug|issue|error|problem)/i,
33391
+ /\bupdate\b.*\b(function|component|service|module)/i,
33392
+ /\bgenerate\b/i,
33393
+ /\bconvert\b/i
33394
+ ];
33395
+ return featureKeywords.some((re) => re.test(trimmed));
33396
+ }
33397
+ function formatQualityLoopHint() {
33398
+ return chalk.dim(" tip: ") + chalk.magenta("/quality") + chalk.dim(" enables auto-test & iterate until quality converges");
33399
+ }
33400
+ function formatQualityResult(result) {
33401
+ const lines = [];
33402
+ const scores = result.scoreHistory;
33403
+ const progressStr = scores.map((s) => String(s)).join(" \u2192 ");
33404
+ const convergedLabel = result.converged ? chalk.green("converged") : chalk.yellow("max iterations");
33405
+ lines.push("");
33406
+ lines.push(
33407
+ chalk.magenta("\u2500\u2500 Quality: ") + chalk.white(progressStr) + chalk.dim(` (${convergedLabel})`) + chalk.magenta(" \u2500\u2500")
33408
+ );
33409
+ const parts = [];
33410
+ if (result.testsPassed !== void 0 && result.testsTotal !== void 0) {
33411
+ const testsColor = result.testsPassed === result.testsTotal ? chalk.green : chalk.yellow;
33412
+ parts.push(testsColor(`Tests: ${result.testsPassed}/${result.testsTotal}`));
33413
+ }
33414
+ if (result.coverage !== void 0) {
33415
+ const covColor = result.coverage >= 80 ? chalk.green : chalk.yellow;
33416
+ parts.push(covColor(`Coverage: ${result.coverage}%`));
33417
+ }
33418
+ if (result.securityScore !== void 0) {
33419
+ const secColor = result.securityScore === 100 ? chalk.green : chalk.red;
33420
+ parts.push(secColor(`Security: ${result.securityScore}`));
33421
+ }
33422
+ parts.push(chalk.dim(`Iterations: ${result.iterations}`));
33423
+ if (result.durationMs !== void 0) {
33424
+ const secs = (result.durationMs / 1e3).toFixed(1);
33425
+ parts.push(chalk.dim(`Time: ${secs}s`));
33426
+ }
33427
+ lines.push(" " + parts.join(" "));
33428
+ lines.push("");
33429
+ return lines.join("\n");
33430
+ }
33431
+ async function loadQualityLoopPreference() {
33432
+ try {
33433
+ const content = await fs35__default.readFile(CONFIG_PATHS.config, "utf-8");
33434
+ const config = JSON.parse(content);
33435
+ const value = config.qualityLoop ?? config.cocoMode;
33436
+ if (typeof value === "boolean") {
33437
+ qualityLoopEnabled = value;
33438
+ return value;
33439
+ }
33440
+ } catch {
33441
+ }
33442
+ return true;
33443
+ }
33444
+ async function saveQualityLoopPreference(enabled) {
33445
+ try {
33446
+ let config = {};
33447
+ try {
33448
+ const content = await fs35__default.readFile(CONFIG_PATHS.config, "utf-8");
33449
+ config = JSON.parse(content);
33450
+ } catch {
33451
+ }
33452
+ config.qualityLoop = enabled;
33453
+ await fs35__default.writeFile(CONFIG_PATHS.config, JSON.stringify(config, null, 2) + "\n");
33454
+ } catch {
33455
+ }
33456
+ }
33457
+ function parseQualityLoopReport(content) {
33458
+ const marker = "QUALITY_LOOP_REPORT";
33459
+ const idx = content.indexOf(marker);
33460
+ if (idx === -1) return null;
33461
+ const block = content.slice(idx);
33462
+ const getField = (name) => {
33463
+ const match = block.match(new RegExp(`${name}:\\s*(.+)`));
33464
+ return match?.[1]?.trim();
33465
+ };
33466
+ const scoreHistoryRaw = getField("score_history");
33467
+ if (!scoreHistoryRaw) return null;
33468
+ const scores = scoreHistoryRaw.replace(/[[\]]/g, "").split(",").map((s) => parseFloat(s.trim())).filter((n) => !isNaN(n));
33469
+ if (scores.length === 0) return null;
33470
+ const testsPassed = parseInt(getField("tests_passed") ?? "", 10);
33471
+ const testsTotal = parseInt(getField("tests_total") ?? "", 10);
33472
+ const coverage = parseInt(getField("coverage") ?? "", 10);
33473
+ const security = parseInt(getField("security") ?? "", 10);
33474
+ const iterations = parseInt(getField("iterations") ?? "", 10) || scores.length;
33475
+ const converged = getField("converged") === "true";
33476
+ return {
33477
+ converged,
33478
+ scoreHistory: scores,
33479
+ finalScore: scores[scores.length - 1] ?? 0,
33480
+ iterations,
33481
+ testsPassed: isNaN(testsPassed) ? void 0 : testsPassed,
33482
+ testsTotal: isNaN(testsTotal) ? void 0 : testsTotal,
33483
+ coverage: isNaN(coverage) ? void 0 : coverage,
33484
+ securityScore: isNaN(security) ? void 0 : security
33485
+ };
33486
+ }
33487
+ function getQualityLoopSystemPrompt() {
33488
+ return `
33489
+ ## Quality Loop Mode (ACTIVE)
33490
+
33491
+ You are operating in quality loop mode. After implementing code changes, you MUST follow this iteration cycle:
33492
+
33493
+ 1. **Implement** the requested changes (code + tests)
33494
+ 2. **Run tests** using the run_tests or bash_exec tool
33495
+ 3. **Self-review**: Analyze your code against these 12 quality dimensions:
33496
+ - Correctness, Completeness, Robustness, Readability
33497
+ - Maintainability, Complexity, Duplication, Test Coverage
33498
+ - Test Quality, Security, Documentation, Style
33499
+ 4. **Score** your implementation 0-100 for each dimension
33500
+ 5. **If issues found**: Fix them and go back to step 2
33501
+ 6. **If quality is good** (overall \u2265 85 and improving < 2 points): Stop and report
33502
+
33503
+ After completing the cycle, output a quality summary in this exact format:
33504
+
33505
+ \`\`\`
33506
+ QUALITY_LOOP_REPORT
33507
+ score_history: [first_score, ..., final_score]
33508
+ tests_passed: X
33509
+ tests_total: Y
33510
+ coverage: Z
33511
+ security: 100
33512
+ iterations: N
33513
+ converged: true|false
33514
+ \`\`\`
33515
+
33516
+ Key rules:
33517
+ - Always write tests alongside code
33518
+ - Run tests after every change
33519
+ - Minimum 2 iterations before declaring convergence
33520
+ - Maximum 10 iterations
33521
+ - Fix critical issues before moving on
33522
+ - Report honestly - don't inflate scores`;
33523
+ }
33524
+
33525
+ // src/cli/repl/startup-panel.ts
33526
+ async function renderStartupPanel(session, gitCtx, mcpServers = []) {
33527
+ const trustStore = createTrustStore();
33528
+ await trustStore.init();
33529
+ const trustLevel = trustStore.getLevel(session.projectPath);
33530
+ const boxWidth = 41;
33531
+ const innerWidth = boxWidth - 2;
33532
+ const versionText = `v${VERSION}`;
33533
+ const subtitleText = "open source \u2022 corbat.tech";
33534
+ const boxLine = (content) => {
33535
+ const pad = Math.max(0, innerWidth - stringWidth2(content));
33536
+ return chalk.magenta("\u2502") + content + " ".repeat(pad) + chalk.magenta("\u2502");
33537
+ };
33538
+ const titleLeftRaw = " COCO";
33539
+ const titleRightRaw = versionText + " ";
33540
+ const titleLeftStyled = " " + chalk.bold.white("COCO");
33541
+ const titleGap = Math.max(1, innerWidth - stringWidth2(titleLeftRaw) - stringWidth2(titleRightRaw));
33542
+ const titleContent = titleLeftStyled + " ".repeat(titleGap) + chalk.dim(titleRightRaw);
33543
+ const taglineText = "code that converges to quality";
33544
+ const taglineContent = " " + chalk.magenta(taglineText) + " ";
33545
+ const subtitleContent = " " + chalk.dim(subtitleText) + " ";
33546
+ console.log();
33547
+ console.log(chalk.magenta(" \u256D" + "\u2500".repeat(boxWidth - 2) + "\u256E"));
33548
+ console.log(" " + boxLine(titleContent));
33549
+ console.log(" " + boxLine(taglineContent));
33550
+ console.log(" " + boxLine(subtitleContent));
33551
+ console.log(chalk.magenta(" \u2570" + "\u2500".repeat(boxWidth - 2) + "\u256F"));
33552
+ const maxPathLen = 50;
33553
+ let displayPath = session.projectPath;
33554
+ if (displayPath.length > maxPathLen) {
33555
+ displayPath = "..." + displayPath.slice(-maxPathLen + 3);
33556
+ }
33557
+ const lastSep = displayPath.lastIndexOf("/");
33558
+ const parentPath = lastSep > 0 ? displayPath.slice(0, lastSep + 1) : "";
33559
+ const projectName = lastSep > 0 ? displayPath.slice(lastSep + 1) : displayPath;
33560
+ const providerName = session.config.provider.type;
33561
+ const configuredModel = session.config.provider.model?.trim();
33562
+ const modelName = configuredModel && !["default", "none", "null", "undefined"].includes(configuredModel.toLowerCase()) ? configuredModel : getDefaultModel(session.config.provider.type);
33563
+ const trustText = trustLevel === "full" ? "full" : trustLevel === "write" ? "write" : trustLevel === "read" ? "read" : "";
33564
+ console.log();
33565
+ console.log(chalk.dim(` \u{1F4C1} ${parentPath}`) + chalk.magenta.bold(projectName));
33566
+ console.log(
33567
+ chalk.dim(` \u{1F916} ${providerName}/`) + chalk.magenta(modelName) + (trustText ? chalk.dim(` \u2022 \u{1F510} ${trustText}`) : "")
33568
+ );
33569
+ if (gitCtx) {
33570
+ console.log(` ${formatGitLine(gitCtx)}`);
33571
+ }
33572
+ const cocoStatus = isQualityLoop() ? chalk.magenta(" \u{1F504} quality mode: ") + chalk.green.bold("on") + chalk.dim(" \u2014 iterates until quality \u2265 85. /quality to disable") : chalk.dim(" \u{1F4A1} /quality on \u2014 enable auto-test & quality iteration");
33573
+ console.log(cocoStatus);
33574
+ const skillTotal = session.skillRegistry?.size ?? 0;
33575
+ const hasSomething = skillTotal > 0 || mcpServers.length > 0;
33576
+ if (hasSomething) {
33577
+ if (skillTotal > 0) {
33578
+ const allMeta = session.skillRegistry.getAllMetadata();
33579
+ const builtinCount = allMeta.filter((s) => s.scope === "builtin").length;
33580
+ const projectCount = skillTotal - builtinCount;
33581
+ const parts = [];
33582
+ if (builtinCount > 0) parts.push(`${builtinCount} builtin`);
33583
+ if (projectCount > 0) parts.push(`${projectCount} project`);
33584
+ const detail = parts.length > 0 ? ` (${parts.join(" \xB7 ")})` : "";
33585
+ console.log(chalk.green(" \u2713") + chalk.dim(` Skills: ${skillTotal} loaded${detail}`));
33586
+ } else {
33587
+ console.log(chalk.dim(" \xB7 Skills: none loaded"));
33588
+ }
33589
+ if (mcpServers.length > 0) {
33590
+ const names = mcpServers.join(", ");
33591
+ console.log(
33592
+ chalk.green(" \u2713") + chalk.dim(
33593
+ ` MCP: ${names} (${mcpServers.length} server${mcpServers.length === 1 ? "" : "s"} active)`
33594
+ )
33595
+ );
33596
+ }
33597
+ }
33598
+ console.log();
33599
+ console.log(
33600
+ chalk.dim(" Type your request or ") + chalk.magenta("/help") + chalk.dim(" for commands")
33601
+ );
33602
+ const pasteHint = process.platform === "darwin" ? chalk.dim(" \u{1F4CB} \u2318V paste text \u2022 \u2303V paste image") : chalk.dim(" \u{1F4CB} Ctrl+V paste image from clipboard");
33603
+ console.log(pasteHint);
33604
+ console.log();
33605
+ }
33606
+
33607
+ // src/cli/repl/commands/clear.ts
33608
+ init_env();
33285
33609
  var clearCommand = {
33286
33610
  name: "clear",
33287
33611
  aliases: ["c"],
@@ -33289,7 +33613,22 @@ var clearCommand = {
33289
33613
  usage: "/clear",
33290
33614
  async execute(_args, session) {
33291
33615
  clearSession(session);
33292
- console.log(chalk.dim("Conversation cleared.\n"));
33616
+ process.stdout.write("\x1B[2J\x1B[H");
33617
+ const projectPath = session.projectPath || process.cwd();
33618
+ const gitCtx = await getGitContext(projectPath);
33619
+ const panelSession = {
33620
+ ...session,
33621
+ projectPath,
33622
+ config: session.config ?? {
33623
+ provider: {
33624
+ type: "anthropic",
33625
+ model: getDefaultModel("anthropic"),
33626
+ maxTokens: 8192
33627
+ }
33628
+ }
33629
+ };
33630
+ await renderStartupPanel(panelSession, gitCtx);
33631
+ console.log(chalk.dim("Context cleared.\n"));
33293
33632
  return false;
33294
33633
  }
33295
33634
  };
@@ -33763,6 +34102,12 @@ async function setupProviderWithAuth(provider) {
33763
34102
  showProviderInfo(provider);
33764
34103
  const apiKey = await requestApiKey(provider);
33765
34104
  if (!apiKey) return null;
34105
+ let vertexSettings;
34106
+ if (provider.id === "vertex") {
34107
+ const settings = await promptVertexSettings();
34108
+ if (!settings) return null;
34109
+ vertexSettings = settings;
34110
+ }
33766
34111
  let baseUrl;
33767
34112
  if (provider.askForCustomUrl) {
33768
34113
  const wantsCustomUrl = await p26.confirm({
@@ -33786,7 +34131,7 @@ async function setupProviderWithAuth(provider) {
33786
34131
  }
33787
34132
  const model = await selectModel(provider);
33788
34133
  if (!model) return null;
33789
- const valid = await testConnection(provider, apiKey, model, baseUrl);
34134
+ const valid = await testConnection(provider, apiKey, model, baseUrl, vertexSettings);
33790
34135
  if (!valid) {
33791
34136
  const retry = await p26.confirm({
33792
34137
  message: "Would you like to try again?",
@@ -33801,7 +34146,9 @@ async function setupProviderWithAuth(provider) {
33801
34146
  type: provider.id,
33802
34147
  model,
33803
34148
  apiKey,
33804
- baseUrl
34149
+ baseUrl,
34150
+ project: vertexSettings?.project,
34151
+ location: vertexSettings?.location
33805
34152
  };
33806
34153
  }
33807
34154
  async function setupGcloudADC(provider) {
@@ -33817,11 +34164,6 @@ async function setupGcloudADC(provider) {
33817
34164
  p26.log.error("gcloud CLI is not installed");
33818
34165
  console.log(chalk.dim(" Install it from: https://cloud.google.com/sdk/docs/install"));
33819
34166
  console.log();
33820
- if (provider.id === "vertex") {
33821
- console.log(chalk.dim(" Vertex AI requires gcloud ADC plus a Google Cloud project."));
33822
- console.log();
33823
- return null;
33824
- }
33825
34167
  const useFallback2 = await p26.confirm({
33826
34168
  message: "Use API key instead?",
33827
34169
  initialValue: true
@@ -33832,25 +34174,37 @@ async function setupGcloudADC(provider) {
33832
34174
  if (!apiKey2) return null;
33833
34175
  const model2 = await selectModel(provider);
33834
34176
  if (!model2) return null;
33835
- const valid2 = await testConnection(provider, apiKey2, model2);
34177
+ let vertexSettings2;
34178
+ if (provider.id === "vertex") {
34179
+ const settings = await promptVertexSettings();
34180
+ if (!settings) return null;
34181
+ vertexSettings2 = settings;
34182
+ }
34183
+ const valid2 = await testConnection(provider, apiKey2, model2, void 0, vertexSettings2);
33836
34184
  if (!valid2) return null;
33837
- return { type: provider.id, model: model2, apiKey: apiKey2 };
34185
+ return {
34186
+ type: provider.id,
34187
+ model: model2,
34188
+ apiKey: apiKey2,
34189
+ project: vertexSettings2?.project,
34190
+ location: vertexSettings2?.location
34191
+ };
33838
34192
  }
33839
- const adc = await inspectADC();
34193
+ let adc = await inspectADC();
33840
34194
  if (adc.status === "ok" && adc.token) {
33841
34195
  console.log(chalk.green(" \u2713 gcloud ADC is already configured!"));
33842
34196
  console.log();
33843
34197
  p26.log.success("Authentication verified");
33844
- const vertexSettings = provider.id === "vertex" ? await promptVertexSettings() : void 0;
33845
- if (provider.id === "vertex" && !vertexSettings) return null;
34198
+ const vertexSettings2 = provider.id === "vertex" ? await promptVertexSettings() : void 0;
34199
+ if (provider.id === "vertex" && !vertexSettings2) return null;
33846
34200
  const model2 = await selectModel(provider);
33847
34201
  if (!model2) return null;
33848
34202
  return {
33849
34203
  type: provider.id,
33850
34204
  model: model2,
33851
34205
  apiKey: "__gcloud_adc__",
33852
- project: vertexSettings?.project,
33853
- location: vertexSettings?.location
34206
+ project: vertexSettings2?.project,
34207
+ location: vertexSettings2?.location
33854
34208
  };
33855
34209
  }
33856
34210
  console.log(chalk.yellow(" No reusable gcloud ADC session was found for Coco."));
@@ -33859,6 +34213,36 @@ async function setupGcloudADC(provider) {
33859
34213
  console.log(chalk.dim(` ${adc.message}`));
33860
34214
  console.log();
33861
34215
  }
34216
+ const runLoginNow = await p26.confirm({
34217
+ message: "Authenticate with gcloud now from Coco?",
34218
+ initialValue: true
34219
+ });
34220
+ if (p26.isCancel(runLoginNow)) return null;
34221
+ if (runLoginNow) {
34222
+ p26.log.step("Running `gcloud auth application-default login`...");
34223
+ const loginOk = await runGcloudADCLogin();
34224
+ if (loginOk) {
34225
+ adc = await inspectADC();
34226
+ if (adc.status === "ok" && adc.token) {
34227
+ console.log(chalk.green(" \u2713 gcloud ADC is now configured."));
34228
+ console.log();
34229
+ p26.log.success("Authentication verified");
34230
+ const vertexSettings2 = provider.id === "vertex" ? await promptVertexSettings() : void 0;
34231
+ if (provider.id === "vertex" && !vertexSettings2) return null;
34232
+ const model2 = await selectModel(provider);
34233
+ if (!model2) return null;
34234
+ return {
34235
+ type: provider.id,
34236
+ model: model2,
34237
+ apiKey: "__gcloud_adc__",
34238
+ project: vertexSettings2?.project,
34239
+ location: vertexSettings2?.location
34240
+ };
34241
+ }
34242
+ }
34243
+ p26.log.error("Could not complete gcloud ADC login from Coco.");
34244
+ console.log();
34245
+ }
33862
34246
  console.log(chalk.dim(" Check the current machine-wide ADC state with:"));
33863
34247
  console.log(chalk.cyan(" $ gcloud auth application-default print-access-token"));
33864
34248
  console.log();
@@ -33871,13 +34255,6 @@ async function setupGcloudADC(provider) {
33871
34255
  }
33872
34256
  console.log(chalk.dim(" Coco will reuse the login on the next attempt if ADC is valid."));
33873
34257
  console.log();
33874
- if (provider.id === "vertex") {
33875
- console.log(
33876
- chalk.dim(" Vertex AI does not use API keys in Coco. Configure ADC, then retry.")
33877
- );
33878
- console.log();
33879
- return null;
33880
- }
33881
34258
  const useFallback = await p26.confirm({
33882
34259
  message: "Use API key for now?",
33883
34260
  initialValue: true
@@ -33888,9 +34265,21 @@ async function setupGcloudADC(provider) {
33888
34265
  if (!apiKey) return null;
33889
34266
  const model = await selectModel(provider);
33890
34267
  if (!model) return null;
33891
- const valid = await testConnection(provider, apiKey, model);
34268
+ let vertexSettings;
34269
+ if (provider.id === "vertex") {
34270
+ const settings = await promptVertexSettings();
34271
+ if (!settings) return null;
34272
+ vertexSettings = settings;
34273
+ }
34274
+ const valid = await testConnection(provider, apiKey, model, void 0, vertexSettings);
33892
34275
  if (!valid) return null;
33893
- return { type: provider.id, model, apiKey };
34276
+ return {
34277
+ type: provider.id,
34278
+ model,
34279
+ apiKey,
34280
+ project: vertexSettings?.project,
34281
+ location: vertexSettings?.location
34282
+ };
33894
34283
  }
33895
34284
  async function promptVertexSettings() {
33896
34285
  const projectDefault = process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"] ?? "";
@@ -34424,8 +34813,8 @@ async function testConnection(provider, apiKey, model, baseUrl, vertexSettings)
34424
34813
  process.env[`${provider.id.toUpperCase()}_BASE_URL`] = baseUrl;
34425
34814
  }
34426
34815
  if (provider.id === "vertex") {
34427
- if (vertexSettings?.project) ;
34428
- if (vertexSettings?.location) ;
34816
+ if (vertexSettings?.project) process.env["VERTEX_PROJECT"] = vertexSettings.project;
34817
+ if (vertexSettings?.location) process.env["VERTEX_LOCATION"] = vertexSettings.location;
34429
34818
  }
34430
34819
  const testProvider = await createProvider(provider.id, {
34431
34820
  model,
@@ -34724,8 +35113,9 @@ async function ensureConfiguredV2(config) {
34724
35113
  });
34725
35114
  for (const prov of configuredProviders) {
34726
35115
  try {
35116
+ const rememberedModel = await getLastUsedModel(prov.id);
34727
35117
  const recommended = getRecommendedModel(prov.id);
34728
- const model = recommended?.id || prov.models[0]?.id || "";
35118
+ const model = rememberedModel || recommended?.id || prov.models[0]?.id || "";
34729
35119
  let providerId = prov.id;
34730
35120
  if (prov.id === "openai" && hasOpenAIOAuthTokens && !process.env[prov.envVar]) {
34731
35121
  const tokenResult = await getOrRefreshOAuthToken("openai");
@@ -34953,7 +35343,7 @@ async function switchProvider(initialProvider, session) {
34953
35343
  `));
34954
35344
  return false;
34955
35345
  }
34956
- const supportsApiKey = newProvider.id !== "vertex" && newProvider.requiresApiKey !== false;
35346
+ const supportsApiKey = newProvider.requiresApiKey !== false;
34957
35347
  const apiKey = supportsApiKey ? process.env[newProvider.envVar] : void 0;
34958
35348
  const hasOAuth = supportsOAuth(newProvider.id) || newProvider.supportsOAuth;
34959
35349
  const hasGcloudADC = newProvider.supportsGcloudADC;
@@ -35087,7 +35477,10 @@ Using existing OAuth session...`));
35087
35477
  if (!adcResult) return false;
35088
35478
  selectedAuthMethod = "gcloud";
35089
35479
  if (newProvider.id === "vertex") {
35090
- const settings = await promptVertexSettings2();
35480
+ const settings = await promptVertexSettings2({
35481
+ project: session.config.provider.project,
35482
+ location: session.config.provider.location
35483
+ });
35091
35484
  if (!settings) return false;
35092
35485
  vertexSettings = settings;
35093
35486
  }
@@ -35108,6 +35501,14 @@ Using existing API key...`));
35108
35501
  selectedAuthMethod = "apikey";
35109
35502
  newApiKeyForSaving = key;
35110
35503
  }
35504
+ if (newProvider.id === "vertex") {
35505
+ const settings = await promptVertexSettings2({
35506
+ project: session.config.provider.project,
35507
+ location: session.config.provider.location
35508
+ });
35509
+ if (!settings) return false;
35510
+ vertexSettings = settings;
35511
+ }
35111
35512
  } else if (authChoice === "remove") {
35112
35513
  const removeOptions = [];
35113
35514
  if (oauthConnected) {
@@ -35164,7 +35565,10 @@ ${newProvider.emoji} ${newProvider.name} is not configured.`));
35164
35565
  if (!adcResult) return false;
35165
35566
  selectedAuthMethod = "gcloud";
35166
35567
  if (newProvider.id === "vertex") {
35167
- const settings = await promptVertexSettings2();
35568
+ const settings = await promptVertexSettings2({
35569
+ project: session.config.provider.project,
35570
+ location: session.config.provider.location
35571
+ });
35168
35572
  if (!settings) return false;
35169
35573
  vertexSettings = settings;
35170
35574
  }
@@ -35215,7 +35619,9 @@ ${newProvider.emoji} ${newProvider.name} is not configured.`));
35215
35619
  await saveConfiguration({
35216
35620
  type: userFacingProviderId,
35217
35621
  model: newModel,
35218
- apiKey: newApiKeyForSaving
35622
+ apiKey: newApiKeyForSaving,
35623
+ project: vertexSettings?.project,
35624
+ location: vertexSettings?.location
35219
35625
  });
35220
35626
  } else {
35221
35627
  await saveProviderPreference(userFacingProviderId, newModel, {
@@ -35255,8 +35661,26 @@ async function setupGcloudADCForProvider(_provider) {
35255
35661
  return true;
35256
35662
  }
35257
35663
  console.log(chalk.yellow("\n No reusable gcloud ADC session was found for Coco."));
35258
- if (adc.message) {
35259
- console.log(chalk.dim(` ${adc.message}`));
35664
+ console.log();
35665
+ if (adc.message) console.log(chalk.dim(` ${adc.message}`));
35666
+ console.log();
35667
+ const runLoginNow = await p26.confirm({
35668
+ message: "Authenticate with gcloud now from Coco?",
35669
+ initialValue: true
35670
+ });
35671
+ if (p26.isCancel(runLoginNow)) return false;
35672
+ if (runLoginNow) {
35673
+ p26.log.step("Running `gcloud auth application-default login`...");
35674
+ const loginOk = await runGcloudADCLogin();
35675
+ if (loginOk) {
35676
+ const refreshed = await inspectADC();
35677
+ if (refreshed.status === "ok" && refreshed.token) {
35678
+ console.log(chalk.green(" \u2713 gcloud ADC is now configured.\n"));
35679
+ return true;
35680
+ }
35681
+ }
35682
+ p26.log.error("Could not complete gcloud ADC login from Coco.");
35683
+ console.log();
35260
35684
  }
35261
35685
  console.log(chalk.dim("\n Check the current ADC state with:"));
35262
35686
  console.log(chalk.cyan(" $ gcloud auth application-default print-access-token"));
@@ -35269,9 +35693,9 @@ async function setupGcloudADCForProvider(_provider) {
35269
35693
  console.log();
35270
35694
  return false;
35271
35695
  }
35272
- async function promptVertexSettings2() {
35273
- const projectDefault = process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"] ?? "";
35274
- const locationDefault = process.env["VERTEX_LOCATION"] ?? process.env["GOOGLE_CLOUD_LOCATION"] ?? "global";
35696
+ async function promptVertexSettings2(defaults) {
35697
+ const projectDefault = defaults?.project ?? process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"] ?? "";
35698
+ const locationDefault = defaults?.location ?? process.env["VERTEX_LOCATION"] ?? process.env["GOOGLE_CLOUD_LOCATION"] ?? "global";
35275
35699
  const project = await p26.text({
35276
35700
  message: "Google Cloud project ID:",
35277
35701
  placeholder: projectDefault || "my-gcp-project",
@@ -35980,11 +36404,11 @@ async function revokeTrust(session, trustStore) {
35980
36404
  p26.log.info("This project is not currently trusted");
35981
36405
  return;
35982
36406
  }
35983
- const confirm22 = await p26.confirm({
36407
+ const confirm23 = await p26.confirm({
35984
36408
  message: "Revoke all access to this project?",
35985
36409
  initialValue: false
35986
36410
  });
35987
- if (p26.isCancel(confirm22) || !confirm22) {
36411
+ if (p26.isCancel(confirm23) || !confirm23) {
35988
36412
  p26.outro("Cancelled");
35989
36413
  return;
35990
36414
  }
@@ -36101,11 +36525,11 @@ var initCommand = {
36101
36525
  p26.log.message(` Description: ${description}`);
36102
36526
  }
36103
36527
  p26.log.message("");
36104
- const confirm22 = await p26.confirm({
36528
+ const confirm23 = await p26.confirm({
36105
36529
  message: "Create project?",
36106
36530
  initialValue: true
36107
36531
  });
36108
- if (p26.isCancel(confirm22) || !confirm22) {
36532
+ if (p26.isCancel(confirm23) || !confirm23) {
36109
36533
  p26.outro("Cancelled");
36110
36534
  return false;
36111
36535
  }
@@ -37331,11 +37755,11 @@ async function runInteractiveMode(session) {
37331
37755
  );
37332
37756
  }
37333
37757
  console.log();
37334
- const confirm22 = await p26.confirm({
37758
+ const confirm23 = await p26.confirm({
37335
37759
  message: "Proceed with restoration?",
37336
37760
  initialValue: true
37337
37761
  });
37338
- if (p26.isCancel(confirm22) || !confirm22) {
37762
+ if (p26.isCancel(confirm23) || !confirm23) {
37339
37763
  p26.outro("Cancelled");
37340
37764
  return false;
37341
37765
  }
@@ -37378,11 +37802,11 @@ async function runDirectMode(session, checkpointId) {
37378
37802
  console.log(`${chalk.dim("Conversation:")} ${checkpoint.conversation?.messageCount} messages`);
37379
37803
  }
37380
37804
  console.log();
37381
- const confirm22 = await p26.confirm({
37805
+ const confirm23 = await p26.confirm({
37382
37806
  message: "Restore this checkpoint?",
37383
37807
  initialValue: true
37384
37808
  });
37385
- if (p26.isCancel(confirm22) || !confirm22) {
37809
+ if (p26.isCancel(confirm23) || !confirm23) {
37386
37810
  p26.outro("Cancelled");
37387
37811
  return false;
37388
37812
  }
@@ -37833,11 +38257,11 @@ async function runInteractiveMode2(session) {
37833
38257
  return false;
37834
38258
  }
37835
38259
  displaySessionDetails(selectedSession);
37836
- const confirm22 = await p26.confirm({
38260
+ const confirm23 = await p26.confirm({
37837
38261
  message: "Resume this session?",
37838
38262
  initialValue: true
37839
38263
  });
37840
- if (p26.isCancel(confirm22) || !confirm22) {
38264
+ if (p26.isCancel(confirm23) || !confirm23) {
37841
38265
  p26.outro("Cancelled");
37842
38266
  return false;
37843
38267
  }
@@ -37855,11 +38279,11 @@ async function runDirectMode2(session, sessionId) {
37855
38279
  return false;
37856
38280
  }
37857
38281
  displaySessionDetails(targetSession);
37858
- const confirm22 = await p26.confirm({
38282
+ const confirm23 = await p26.confirm({
37859
38283
  message: "Resume this session?",
37860
38284
  initialValue: true
37861
38285
  });
37862
- if (p26.isCancel(confirm22) || !confirm22) {
38286
+ if (p26.isCancel(confirm23) || !confirm23) {
37863
38287
  p26.outro("Cancelled");
37864
38288
  return false;
37865
38289
  }
@@ -38872,7 +39296,9 @@ async function loadPermissionPreferences() {
38872
39296
  recommendedAllowlistApplied: config.recommendedAllowlistApplied,
38873
39297
  recommendedAllowlistDismissed: config.recommendedAllowlistDismissed,
38874
39298
  recommendedAllowlistPrompted: config.recommendedAllowlistPrompted,
38875
- recommendedAllowlistPromptedProjects: config.recommendedAllowlistPromptedProjects
39299
+ recommendedAllowlistPromptedProjects: config.recommendedAllowlistPromptedProjects,
39300
+ recommendedAllowlistAppliedProjects: config.recommendedAllowlistAppliedProjects,
39301
+ recommendedAllowlistDismissedProjects: config.recommendedAllowlistDismissedProjects
38876
39302
  };
38877
39303
  } catch {
38878
39304
  return {};
@@ -38892,7 +39318,27 @@ async function savePermissionPreference(key, value) {
38892
39318
  } catch {
38893
39319
  }
38894
39320
  }
38895
- async function markPermissionSuggestionShownForProject(projectPath) {
39321
+ function isRecommendedAllowlistAppliedForProject(prefs, projectPath) {
39322
+ const projectKey = getProjectPreferenceKey(projectPath);
39323
+ if (prefs.recommendedAllowlistAppliedProjects?.[projectKey] === true) {
39324
+ return true;
39325
+ }
39326
+ if (prefs.recommendedAllowlistApplied === true && !prefs.recommendedAllowlistAppliedProjects) {
39327
+ return true;
39328
+ }
39329
+ return false;
39330
+ }
39331
+ function isRecommendedAllowlistDismissedForProject(prefs, projectPath) {
39332
+ const projectKey = getProjectPreferenceKey(projectPath);
39333
+ if (prefs.recommendedAllowlistDismissedProjects?.[projectKey] === true) {
39334
+ return true;
39335
+ }
39336
+ if (prefs.recommendedAllowlistDismissed === true && !prefs.recommendedAllowlistDismissedProjects) {
39337
+ return true;
39338
+ }
39339
+ return false;
39340
+ }
39341
+ async function saveProjectPermissionPreference(key, projectPath, value) {
38896
39342
  try {
38897
39343
  let config = {};
38898
39344
  try {
@@ -38900,12 +39346,12 @@ async function markPermissionSuggestionShownForProject(projectPath) {
38900
39346
  config = JSON.parse(content);
38901
39347
  } catch {
38902
39348
  }
38903
- const promptedProjects = {
38904
- ...config.recommendedAllowlistPromptedProjects,
38905
- [getProjectPreferenceKey(projectPath)]: true
39349
+ const projectKey = getProjectPreferenceKey(projectPath);
39350
+ const currentMap = config[key] ?? {};
39351
+ config[key] = {
39352
+ ...currentMap,
39353
+ [projectKey]: value
38906
39354
  };
38907
- config.recommendedAllowlistPromptedProjects = promptedProjects;
38908
- config.recommendedAllowlistPrompted = true;
38909
39355
  await fs35__default.mkdir(path39__default.dirname(CONFIG_PATHS.config), { recursive: true });
38910
39356
  await fs35__default.writeFile(CONFIG_PATHS.config, JSON.stringify(config, null, 2), "utf-8");
38911
39357
  } catch {
@@ -38913,26 +39359,26 @@ async function markPermissionSuggestionShownForProject(projectPath) {
38913
39359
  }
38914
39360
  async function shouldShowPermissionSuggestion(projectPath = process.cwd()) {
38915
39361
  const prefs = await loadPermissionPreferences();
38916
- if (prefs.recommendedAllowlistDismissed) {
38917
- return false;
38918
- }
38919
- if (prefs.recommendedAllowlistApplied) {
39362
+ if (isRecommendedAllowlistDismissedForProject(prefs, projectPath)) {
38920
39363
  return false;
38921
39364
  }
38922
- const projectKey = getProjectPreferenceKey(projectPath);
38923
- if (prefs.recommendedAllowlistPromptedProjects?.[projectKey]) {
39365
+ if (isRecommendedAllowlistAppliedForProject(prefs, projectPath)) {
38924
39366
  return false;
38925
39367
  }
38926
39368
  return true;
38927
39369
  }
38928
- async function applyRecommendedPermissions() {
39370
+ async function applyRecommendedPermissions(projectPath = process.cwd()) {
38929
39371
  for (const tool of [...RECOMMENDED_GLOBAL, ...RECOMMENDED_PROJECT]) {
38930
39372
  await saveTrustedTool(tool, null, true);
38931
39373
  }
38932
- await savePermissionPreference("recommendedAllowlistApplied", true);
39374
+ await saveProjectPermissionPreference("recommendedAllowlistAppliedProjects", projectPath, true);
39375
+ await saveProjectPermissionPreference(
39376
+ "recommendedAllowlistDismissedProjects",
39377
+ projectPath,
39378
+ false
39379
+ );
38933
39380
  }
38934
39381
  async function showPermissionSuggestion(projectPath = process.cwd()) {
38935
- await markPermissionSuggestionShownForProject(projectPath);
38936
39382
  console.log();
38937
39383
  console.log(chalk.magenta.bold(" \u{1F4CB} Recommended Permissions"));
38938
39384
  console.log();
@@ -38959,8 +39405,11 @@ async function showPermissionSuggestion(projectPath = process.cwd()) {
38959
39405
  return;
38960
39406
  }
38961
39407
  if (action === "dismiss") {
38962
- await savePermissionPreference("recommendedAllowlistPrompted", true);
38963
- await savePermissionPreference("recommendedAllowlistDismissed", true);
39408
+ await saveProjectPermissionPreference(
39409
+ "recommendedAllowlistDismissedProjects",
39410
+ projectPath,
39411
+ true
39412
+ );
38964
39413
  console.log(chalk.dim(" Won't show again. Use /permissions to apply later."));
38965
39414
  return;
38966
39415
  }
@@ -38974,8 +39423,7 @@ async function showPermissionSuggestion(projectPath = process.cwd()) {
38974
39423
  return;
38975
39424
  }
38976
39425
  }
38977
- await applyRecommendedPermissions();
38978
- await savePermissionPreference("recommendedAllowlistPrompted", true);
39426
+ await applyRecommendedPermissions(projectPath);
38979
39427
  console.log(chalk.green(" \u2713 Recommended permissions applied"));
38980
39428
  console.log(chalk.dim(" Use /permissions to review or modify anytime."));
38981
39429
  }
@@ -39066,7 +39514,7 @@ async function showStatus(session) {
39066
39514
  console.log(chalk.magenta.bold(" \u{1F510} Tool Permissions"));
39067
39515
  console.log();
39068
39516
  const allowCount = RECOMMENDED_GLOBAL.length + RECOMMENDED_PROJECT.length;
39069
- if (prefs.recommendedAllowlistApplied) {
39517
+ if (isRecommendedAllowlistAppliedForProject(prefs, session.projectPath)) {
39070
39518
  console.log(
39071
39519
  chalk.green(" \u2713 Recommended allowlist applied") + chalk.dim(` (${allowCount} allow, ${RECOMMENDED_DENY.length} deny)`)
39072
39520
  );
@@ -39108,7 +39556,7 @@ async function showStatus(session) {
39108
39556
  console.log();
39109
39557
  }
39110
39558
  async function applyRecommended(session) {
39111
- await applyRecommendedPermissions();
39559
+ await applyRecommendedPermissions(session.projectPath);
39112
39560
  for (const tool of RECOMMENDED_GLOBAL) {
39113
39561
  session.trustedTools.add(tool);
39114
39562
  }
@@ -39158,177 +39606,18 @@ async function resetPermissions(session) {
39158
39606
  } catch {
39159
39607
  }
39160
39608
  await savePermissionPreference("recommendedAllowlistApplied", false);
39161
- console.log(chalk.green(" \u2713 All tool permissions reset."));
39162
- }
39163
-
39164
- // src/cli/repl/quality-loop.ts
39165
- init_paths();
39166
- var qualityLoopEnabled = true;
39167
- var hintShown = false;
39168
- function isQualityLoop() {
39169
- return qualityLoopEnabled;
39170
- }
39171
- function setQualityLoop(enabled) {
39172
- qualityLoopEnabled = enabled;
39173
- }
39174
- function wasHintShown() {
39175
- return hintShown;
39176
- }
39177
- function markHintShown() {
39178
- hintShown = true;
39179
- }
39180
- function looksLikeFeatureRequest(input) {
39181
- const trimmed = input.trim();
39182
- if (trimmed.length < 20) return false;
39183
- if (trimmed.endsWith("?") && trimmed.length < 80) return false;
39184
- const featureKeywords = [
39185
- /\bimplement/i,
39186
- /\bcreate\b/i,
39187
- /\bbuild\b/i,
39188
- /\badd\b.*\b(feature|function|component|endpoint|service|module|class)/i,
39189
- /\brefactor/i,
39190
- /\bmigrate/i,
39191
- /\bsetup\b/i,
39192
- /\bintegrate/i,
39193
- /\bwrite\b.*\b(code|function|test|module)/i,
39194
- /\bdevelop/i,
39195
- /\bdesign\b/i,
39196
- /\bfix\b.*\b(bug|issue|error|problem)/i,
39197
- /\bupdate\b.*\b(function|component|service|module)/i,
39198
- /\bgenerate\b/i,
39199
- /\bconvert\b/i
39200
- ];
39201
- return featureKeywords.some((re) => re.test(trimmed));
39202
- }
39203
- function formatQualityLoopHint() {
39204
- return chalk.dim(" tip: ") + chalk.magenta("/quality") + chalk.dim(" enables auto-test & iterate until quality converges");
39205
- }
39206
- function formatQualityResult(result) {
39207
- const lines = [];
39208
- const scores = result.scoreHistory;
39209
- const progressStr = scores.map((s) => String(s)).join(" \u2192 ");
39210
- const convergedLabel = result.converged ? chalk.green("converged") : chalk.yellow("max iterations");
39211
- lines.push("");
39212
- lines.push(
39213
- chalk.magenta("\u2500\u2500 Quality: ") + chalk.white(progressStr) + chalk.dim(` (${convergedLabel})`) + chalk.magenta(" \u2500\u2500")
39609
+ await saveProjectPermissionPreference(
39610
+ "recommendedAllowlistAppliedProjects",
39611
+ session.projectPath,
39612
+ false
39214
39613
  );
39215
- const parts = [];
39216
- if (result.testsPassed !== void 0 && result.testsTotal !== void 0) {
39217
- const testsColor = result.testsPassed === result.testsTotal ? chalk.green : chalk.yellow;
39218
- parts.push(testsColor(`Tests: ${result.testsPassed}/${result.testsTotal}`));
39219
- }
39220
- if (result.coverage !== void 0) {
39221
- const covColor = result.coverage >= 80 ? chalk.green : chalk.yellow;
39222
- parts.push(covColor(`Coverage: ${result.coverage}%`));
39223
- }
39224
- if (result.securityScore !== void 0) {
39225
- const secColor = result.securityScore === 100 ? chalk.green : chalk.red;
39226
- parts.push(secColor(`Security: ${result.securityScore}`));
39227
- }
39228
- parts.push(chalk.dim(`Iterations: ${result.iterations}`));
39229
- if (result.durationMs !== void 0) {
39230
- const secs = (result.durationMs / 1e3).toFixed(1);
39231
- parts.push(chalk.dim(`Time: ${secs}s`));
39232
- }
39233
- lines.push(" " + parts.join(" "));
39234
- lines.push("");
39235
- return lines.join("\n");
39236
- }
39237
- async function loadQualityLoopPreference() {
39238
- try {
39239
- const content = await fs35__default.readFile(CONFIG_PATHS.config, "utf-8");
39240
- const config = JSON.parse(content);
39241
- const value = config.qualityLoop ?? config.cocoMode;
39242
- if (typeof value === "boolean") {
39243
- qualityLoopEnabled = value;
39244
- return value;
39245
- }
39246
- } catch {
39247
- }
39248
- return true;
39249
- }
39250
- async function saveQualityLoopPreference(enabled) {
39251
- try {
39252
- let config = {};
39253
- try {
39254
- const content = await fs35__default.readFile(CONFIG_PATHS.config, "utf-8");
39255
- config = JSON.parse(content);
39256
- } catch {
39257
- }
39258
- config.qualityLoop = enabled;
39259
- await fs35__default.writeFile(CONFIG_PATHS.config, JSON.stringify(config, null, 2) + "\n");
39260
- } catch {
39261
- }
39262
- }
39263
- function parseQualityLoopReport(content) {
39264
- const marker = "QUALITY_LOOP_REPORT";
39265
- const idx = content.indexOf(marker);
39266
- if (idx === -1) return null;
39267
- const block = content.slice(idx);
39268
- const getField = (name) => {
39269
- const match = block.match(new RegExp(`${name}:\\s*(.+)`));
39270
- return match?.[1]?.trim();
39271
- };
39272
- const scoreHistoryRaw = getField("score_history");
39273
- if (!scoreHistoryRaw) return null;
39274
- const scores = scoreHistoryRaw.replace(/[[\]]/g, "").split(",").map((s) => parseFloat(s.trim())).filter((n) => !isNaN(n));
39275
- if (scores.length === 0) return null;
39276
- const testsPassed = parseInt(getField("tests_passed") ?? "", 10);
39277
- const testsTotal = parseInt(getField("tests_total") ?? "", 10);
39278
- const coverage = parseInt(getField("coverage") ?? "", 10);
39279
- const security = parseInt(getField("security") ?? "", 10);
39280
- const iterations = parseInt(getField("iterations") ?? "", 10) || scores.length;
39281
- const converged = getField("converged") === "true";
39282
- return {
39283
- converged,
39284
- scoreHistory: scores,
39285
- finalScore: scores[scores.length - 1] ?? 0,
39286
- iterations,
39287
- testsPassed: isNaN(testsPassed) ? void 0 : testsPassed,
39288
- testsTotal: isNaN(testsTotal) ? void 0 : testsTotal,
39289
- coverage: isNaN(coverage) ? void 0 : coverage,
39290
- securityScore: isNaN(security) ? void 0 : security
39291
- };
39292
- }
39293
- function getQualityLoopSystemPrompt() {
39294
- return `
39295
- ## Quality Loop Mode (ACTIVE)
39296
-
39297
- You are operating in quality loop mode. After implementing code changes, you MUST follow this iteration cycle:
39298
-
39299
- 1. **Implement** the requested changes (code + tests)
39300
- 2. **Run tests** using the run_tests or bash_exec tool
39301
- 3. **Self-review**: Analyze your code against these 12 quality dimensions:
39302
- - Correctness, Completeness, Robustness, Readability
39303
- - Maintainability, Complexity, Duplication, Test Coverage
39304
- - Test Quality, Security, Documentation, Style
39305
- 4. **Score** your implementation 0-100 for each dimension
39306
- 5. **If issues found**: Fix them and go back to step 2
39307
- 6. **If quality is good** (overall \u2265 85 and improving < 2 points): Stop and report
39308
-
39309
- After completing the cycle, output a quality summary in this exact format:
39310
-
39311
- \`\`\`
39312
- QUALITY_LOOP_REPORT
39313
- score_history: [first_score, ..., final_score]
39314
- tests_passed: X
39315
- tests_total: Y
39316
- coverage: Z
39317
- security: 100
39318
- iterations: N
39319
- converged: true|false
39320
- \`\`\`
39321
-
39322
- Key rules:
39323
- - Always write tests alongside code
39324
- - Run tests after every change
39325
- - Minimum 2 iterations before declaring convergence
39326
- - Maximum 10 iterations
39327
- - Fix critical issues before moving on
39328
- - Report honestly - don't inflate scores`;
39614
+ await saveProjectPermissionPreference(
39615
+ "recommendedAllowlistDismissedProjects",
39616
+ session.projectPath,
39617
+ false
39618
+ );
39619
+ console.log(chalk.green(" \u2713 All tool permissions reset."));
39329
39620
  }
39330
-
39331
- // src/cli/repl/commands/quality.ts
39332
39621
  var qualityCommand = {
39333
39622
  name: "quality",
39334
39623
  aliases: ["coco"],
@@ -40099,11 +40388,11 @@ Response format (JSON only, no prose):
40099
40388
  }
40100
40389
  }
40101
40390
  if (!options?.skipConfirmation) {
40102
- const confirm22 = await p26.confirm({
40391
+ const confirm23 = await p26.confirm({
40103
40392
  message: "Start building with this plan?",
40104
40393
  initialValue: true
40105
40394
  });
40106
- if (p26.isCancel(confirm22) || !confirm22) {
40395
+ if (p26.isCancel(confirm23) || !confirm23) {
40107
40396
  cancel5("Build cancelled.");
40108
40397
  }
40109
40398
  }
@@ -41661,8 +41950,8 @@ Examples:
41661
41950
  recursive: z.boolean().optional().default(false).describe("Delete directories recursively"),
41662
41951
  confirm: z.boolean().optional().describe("Must be true to confirm deletion")
41663
41952
  }),
41664
- async execute({ path: filePath, recursive, confirm: confirm22 }) {
41665
- if (confirm22 !== true) {
41953
+ async execute({ path: filePath, recursive, confirm: confirm23 }) {
41954
+ if (confirm23 !== true) {
41666
41955
  throw new ToolError(
41667
41956
  "Deletion requires explicit confirmation. Set confirm: true to proceed.",
41668
41957
  { tool: "delete_file" }
@@ -48645,11 +48934,11 @@ var buildAppCommand = {
48645
48934
  return false;
48646
48935
  }
48647
48936
  if (!isAutonomous && !parsed.skipConfirmation) {
48648
- const confirm22 = await p26.confirm({
48937
+ const confirm23 = await p26.confirm({
48649
48938
  message: `Build "${spec.projectName}" with ${spec.sprints.length} sprints?`,
48650
48939
  initialValue: true
48651
48940
  });
48652
- if (p26.isCancel(confirm22) || !confirm22) {
48941
+ if (p26.isCancel(confirm23) || !confirm23) {
48653
48942
  p26.cancel("Build cancelled.");
48654
48943
  return false;
48655
48944
  }
@@ -53610,44 +53899,6 @@ function createIntentRecognizer(config = {}) {
53610
53899
  // src/cli/repl/index.ts
53611
53900
  init_env();
53612
53901
  init_allowed_paths();
53613
- async function getGitContext(projectPath) {
53614
- const controller = new AbortController();
53615
- const timer = setTimeout(() => controller.abort(), 3e3);
53616
- try {
53617
- const git = simpleGit({ baseDir: projectPath, abort: controller.signal });
53618
- const status = await git.status();
53619
- clearTimeout(timer);
53620
- return {
53621
- branch: status.current ?? "HEAD",
53622
- isDirty: !status.isClean(),
53623
- staged: status.staged.length,
53624
- modified: status.modified.length,
53625
- untracked: status.not_added.length,
53626
- ahead: status.ahead,
53627
- behind: status.behind
53628
- };
53629
- } catch {
53630
- clearTimeout(timer);
53631
- return null;
53632
- }
53633
- }
53634
- function formatGitLine(ctx) {
53635
- const branchColor = ctx.isDirty ? chalk.yellow : chalk.green;
53636
- const parts = [chalk.dim("\u{1F33F} ") + branchColor(ctx.branch)];
53637
- const changes = [];
53638
- if (ctx.staged > 0) changes.push(chalk.green(`+${ctx.staged}`));
53639
- if (ctx.modified > 0) changes.push(chalk.yellow(`~${ctx.modified}`));
53640
- if (ctx.untracked > 0) changes.push(chalk.dim(`?${ctx.untracked}`));
53641
- if (ctx.ahead > 0) changes.push(chalk.cyan(`\u2191${ctx.ahead}`));
53642
- if (ctx.behind > 0) changes.push(chalk.red(`\u2193${ctx.behind}`));
53643
- if (changes.length > 0) parts.push(changes.join(" "));
53644
- return parts.join(" \u2022 ");
53645
- }
53646
- function formatGitShort(ctx) {
53647
- const branch = ctx.isDirty ? chalk.yellow(ctx.branch) : chalk.green(ctx.branch);
53648
- const dirty = ctx.isDirty ? chalk.yellow(" \u25CF") : "";
53649
- return chalk.dim("\u{1F33F} ") + branch + dirty;
53650
- }
53651
53902
 
53652
53903
  // src/cli/repl/status-bar.ts
53653
53904
  init_env();
@@ -54752,85 +55003,7 @@ ${imagePrompts}`.trim() : imagePrompts;
54752
55003
  process.off("SIGTERM", sigtermHandler);
54753
55004
  }
54754
55005
  async function printWelcome(session, gitCtx, mcpManager) {
54755
- const trustStore = createTrustStore();
54756
- await trustStore.init();
54757
- const trustLevel = trustStore.getLevel(session.projectPath);
54758
- const boxWidth = 41;
54759
- const innerWidth = boxWidth - 2;
54760
- const versionText = `v${VERSION}`;
54761
- const subtitleText = "open source \u2022 corbat.tech";
54762
- const boxLine = (content) => {
54763
- const pad = Math.max(0, innerWidth - stringWidth2(content));
54764
- return chalk.magenta("\u2502") + content + " ".repeat(pad) + chalk.magenta("\u2502");
54765
- };
54766
- const titleLeftRaw = " COCO";
54767
- const titleRightRaw = versionText + " ";
54768
- const titleLeftStyled = " " + chalk.bold.white("COCO");
54769
- const titleGap = Math.max(1, innerWidth - stringWidth2(titleLeftRaw) - stringWidth2(titleRightRaw));
54770
- const titleContent = titleLeftStyled + " ".repeat(titleGap) + chalk.dim(titleRightRaw);
54771
- const taglineText = "code that converges to quality";
54772
- const taglineContent = " " + chalk.magenta(taglineText) + " ";
54773
- const subtitleContent = " " + chalk.dim(subtitleText) + " ";
54774
- console.log();
54775
- console.log(chalk.magenta(" \u256D" + "\u2500".repeat(boxWidth - 2) + "\u256E"));
54776
- console.log(" " + boxLine(titleContent));
54777
- console.log(" " + boxLine(taglineContent));
54778
- console.log(" " + boxLine(subtitleContent));
54779
- console.log(chalk.magenta(" \u2570" + "\u2500".repeat(boxWidth - 2) + "\u256F"));
54780
- const maxPathLen = 50;
54781
- let displayPath = session.projectPath;
54782
- if (displayPath.length > maxPathLen) {
54783
- displayPath = "..." + displayPath.slice(-maxPathLen + 3);
54784
- }
54785
- const lastSep = displayPath.lastIndexOf("/");
54786
- const parentPath = lastSep > 0 ? displayPath.slice(0, lastSep + 1) : "";
54787
- const projectName = lastSep > 0 ? displayPath.slice(lastSep + 1) : displayPath;
54788
- const providerName = session.config.provider.type;
54789
- const configuredModel = session.config.provider.model?.trim();
54790
- const modelName = configuredModel && !["default", "none", "null", "undefined"].includes(configuredModel.toLowerCase()) ? configuredModel : getDefaultModel(session.config.provider.type);
54791
- const trustText = trustLevel === "full" ? "full" : trustLevel === "write" ? "write" : trustLevel === "read" ? "read" : "";
54792
- console.log();
54793
- console.log(chalk.dim(` \u{1F4C1} ${parentPath}`) + chalk.magenta.bold(projectName));
54794
- console.log(
54795
- chalk.dim(` \u{1F916} ${providerName}/`) + chalk.magenta(modelName) + (trustText ? chalk.dim(` \u2022 \u{1F510} ${trustText}`) : "")
54796
- );
54797
- if (gitCtx) {
54798
- console.log(` ${formatGitLine(gitCtx)}`);
54799
- }
54800
- const cocoStatus = isQualityLoop() ? chalk.magenta(" \u{1F504} quality mode: ") + chalk.green.bold("on") + chalk.dim(" \u2014 iterates until quality \u2265 85. /quality to disable") : chalk.dim(" \u{1F4A1} /quality on \u2014 enable auto-test & quality iteration");
54801
- console.log(cocoStatus);
54802
- const skillTotal = session.skillRegistry?.size ?? 0;
54803
- const mcpServers = mcpManager?.getConnectedServers() ?? [];
54804
- const hasSomething = skillTotal > 0 || mcpServers.length > 0;
54805
- if (hasSomething) {
54806
- if (skillTotal > 0) {
54807
- const allMeta = session.skillRegistry.getAllMetadata();
54808
- const builtinCount = allMeta.filter((s) => s.scope === "builtin").length;
54809
- const projectCount = skillTotal - builtinCount;
54810
- const parts = [];
54811
- if (builtinCount > 0) parts.push(`${builtinCount} builtin`);
54812
- if (projectCount > 0) parts.push(`${projectCount} project`);
54813
- const detail = parts.length > 0 ? ` (${parts.join(" \xB7 ")})` : "";
54814
- console.log(chalk.green(" \u2713") + chalk.dim(` Skills: ${skillTotal} loaded${detail}`));
54815
- } else {
54816
- console.log(chalk.dim(" \xB7 Skills: none loaded"));
54817
- }
54818
- if (mcpServers.length > 0) {
54819
- const names = mcpServers.join(", ");
54820
- console.log(
54821
- chalk.green(" \u2713") + chalk.dim(
54822
- ` MCP: ${names} (${mcpServers.length} server${mcpServers.length === 1 ? "" : "s"} active)`
54823
- )
54824
- );
54825
- }
54826
- }
54827
- console.log();
54828
- console.log(
54829
- chalk.dim(" Type your request or ") + chalk.magenta("/help") + chalk.dim(" for commands")
54830
- );
54831
- const pasteHint = process.platform === "darwin" ? chalk.dim(" \u{1F4CB} \u2318V paste text \u2022 \u2303V paste image") : chalk.dim(" \u{1F4CB} Ctrl+V paste image from clipboard");
54832
- console.log(pasteHint);
54833
- console.log();
55006
+ await renderStartupPanel(session, gitCtx, mcpManager?.getConnectedServers() ?? []);
54834
55007
  }
54835
55008
  async function checkProjectTrust(projectPath) {
54836
55009
  const trustStore = createTrustStore();