@corbat-tech/coco 2.27.1 → 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
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."));
@@ -33873,16 +34227,16 @@ async function setupGcloudADC(provider) {
33873
34227
  console.log(chalk.green(" \u2713 gcloud ADC is now configured."));
33874
34228
  console.log();
33875
34229
  p26.log.success("Authentication verified");
33876
- const vertexSettings = provider.id === "vertex" ? await promptVertexSettings() : void 0;
33877
- if (provider.id === "vertex" && !vertexSettings) return null;
34230
+ const vertexSettings2 = provider.id === "vertex" ? await promptVertexSettings() : void 0;
34231
+ if (provider.id === "vertex" && !vertexSettings2) return null;
33878
34232
  const model2 = await selectModel(provider);
33879
34233
  if (!model2) return null;
33880
34234
  return {
33881
34235
  type: provider.id,
33882
34236
  model: model2,
33883
34237
  apiKey: "__gcloud_adc__",
33884
- project: vertexSettings?.project,
33885
- location: vertexSettings?.location
34238
+ project: vertexSettings2?.project,
34239
+ location: vertexSettings2?.location
33886
34240
  };
33887
34241
  }
33888
34242
  }
@@ -33901,13 +34255,6 @@ async function setupGcloudADC(provider) {
33901
34255
  }
33902
34256
  console.log(chalk.dim(" Coco will reuse the login on the next attempt if ADC is valid."));
33903
34257
  console.log();
33904
- if (provider.id === "vertex") {
33905
- console.log(
33906
- chalk.dim(" Vertex AI does not use API keys in Coco. Configure ADC, then retry.")
33907
- );
33908
- console.log();
33909
- return null;
33910
- }
33911
34258
  const useFallback = await p26.confirm({
33912
34259
  message: "Use API key for now?",
33913
34260
  initialValue: true
@@ -33918,9 +34265,21 @@ async function setupGcloudADC(provider) {
33918
34265
  if (!apiKey) return null;
33919
34266
  const model = await selectModel(provider);
33920
34267
  if (!model) return null;
33921
- 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);
33922
34275
  if (!valid) return null;
33923
- 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
+ };
33924
34283
  }
33925
34284
  async function promptVertexSettings() {
33926
34285
  const projectDefault = process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"] ?? "";
@@ -34454,8 +34813,8 @@ async function testConnection(provider, apiKey, model, baseUrl, vertexSettings)
34454
34813
  process.env[`${provider.id.toUpperCase()}_BASE_URL`] = baseUrl;
34455
34814
  }
34456
34815
  if (provider.id === "vertex") {
34457
- if (vertexSettings?.project) ;
34458
- if (vertexSettings?.location) ;
34816
+ if (vertexSettings?.project) process.env["VERTEX_PROJECT"] = vertexSettings.project;
34817
+ if (vertexSettings?.location) process.env["VERTEX_LOCATION"] = vertexSettings.location;
34459
34818
  }
34460
34819
  const testProvider = await createProvider(provider.id, {
34461
34820
  model,
@@ -34754,8 +35113,9 @@ async function ensureConfiguredV2(config) {
34754
35113
  });
34755
35114
  for (const prov of configuredProviders) {
34756
35115
  try {
35116
+ const rememberedModel = await getLastUsedModel(prov.id);
34757
35117
  const recommended = getRecommendedModel(prov.id);
34758
- const model = recommended?.id || prov.models[0]?.id || "";
35118
+ const model = rememberedModel || recommended?.id || prov.models[0]?.id || "";
34759
35119
  let providerId = prov.id;
34760
35120
  if (prov.id === "openai" && hasOpenAIOAuthTokens && !process.env[prov.envVar]) {
34761
35121
  const tokenResult = await getOrRefreshOAuthToken("openai");
@@ -34983,7 +35343,7 @@ async function switchProvider(initialProvider, session) {
34983
35343
  `));
34984
35344
  return false;
34985
35345
  }
34986
- const supportsApiKey = newProvider.id !== "vertex" && newProvider.requiresApiKey !== false;
35346
+ const supportsApiKey = newProvider.requiresApiKey !== false;
34987
35347
  const apiKey = supportsApiKey ? process.env[newProvider.envVar] : void 0;
34988
35348
  const hasOAuth = supportsOAuth(newProvider.id) || newProvider.supportsOAuth;
34989
35349
  const hasGcloudADC = newProvider.supportsGcloudADC;
@@ -35117,7 +35477,10 @@ Using existing OAuth session...`));
35117
35477
  if (!adcResult) return false;
35118
35478
  selectedAuthMethod = "gcloud";
35119
35479
  if (newProvider.id === "vertex") {
35120
- const settings = await promptVertexSettings2();
35480
+ const settings = await promptVertexSettings2({
35481
+ project: session.config.provider.project,
35482
+ location: session.config.provider.location
35483
+ });
35121
35484
  if (!settings) return false;
35122
35485
  vertexSettings = settings;
35123
35486
  }
@@ -35138,6 +35501,14 @@ Using existing API key...`));
35138
35501
  selectedAuthMethod = "apikey";
35139
35502
  newApiKeyForSaving = key;
35140
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
+ }
35141
35512
  } else if (authChoice === "remove") {
35142
35513
  const removeOptions = [];
35143
35514
  if (oauthConnected) {
@@ -35194,7 +35565,10 @@ ${newProvider.emoji} ${newProvider.name} is not configured.`));
35194
35565
  if (!adcResult) return false;
35195
35566
  selectedAuthMethod = "gcloud";
35196
35567
  if (newProvider.id === "vertex") {
35197
- const settings = await promptVertexSettings2();
35568
+ const settings = await promptVertexSettings2({
35569
+ project: session.config.provider.project,
35570
+ location: session.config.provider.location
35571
+ });
35198
35572
  if (!settings) return false;
35199
35573
  vertexSettings = settings;
35200
35574
  }
@@ -35245,7 +35619,9 @@ ${newProvider.emoji} ${newProvider.name} is not configured.`));
35245
35619
  await saveConfiguration({
35246
35620
  type: userFacingProviderId,
35247
35621
  model: newModel,
35248
- apiKey: newApiKeyForSaving
35622
+ apiKey: newApiKeyForSaving,
35623
+ project: vertexSettings?.project,
35624
+ location: vertexSettings?.location
35249
35625
  });
35250
35626
  } else {
35251
35627
  await saveProviderPreference(userFacingProviderId, newModel, {
@@ -35285,8 +35661,26 @@ async function setupGcloudADCForProvider(_provider) {
35285
35661
  return true;
35286
35662
  }
35287
35663
  console.log(chalk.yellow("\n No reusable gcloud ADC session was found for Coco."));
35288
- if (adc.message) {
35289
- 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();
35290
35684
  }
35291
35685
  console.log(chalk.dim("\n Check the current ADC state with:"));
35292
35686
  console.log(chalk.cyan(" $ gcloud auth application-default print-access-token"));
@@ -35299,9 +35693,9 @@ async function setupGcloudADCForProvider(_provider) {
35299
35693
  console.log();
35300
35694
  return false;
35301
35695
  }
35302
- async function promptVertexSettings2() {
35303
- const projectDefault = process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"] ?? "";
35304
- 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";
35305
35699
  const project = await p26.text({
35306
35700
  message: "Google Cloud project ID:",
35307
35701
  placeholder: projectDefault || "my-gcp-project",
@@ -36010,11 +36404,11 @@ async function revokeTrust(session, trustStore) {
36010
36404
  p26.log.info("This project is not currently trusted");
36011
36405
  return;
36012
36406
  }
36013
- const confirm22 = await p26.confirm({
36407
+ const confirm23 = await p26.confirm({
36014
36408
  message: "Revoke all access to this project?",
36015
36409
  initialValue: false
36016
36410
  });
36017
- if (p26.isCancel(confirm22) || !confirm22) {
36411
+ if (p26.isCancel(confirm23) || !confirm23) {
36018
36412
  p26.outro("Cancelled");
36019
36413
  return;
36020
36414
  }
@@ -36131,11 +36525,11 @@ var initCommand = {
36131
36525
  p26.log.message(` Description: ${description}`);
36132
36526
  }
36133
36527
  p26.log.message("");
36134
- const confirm22 = await p26.confirm({
36528
+ const confirm23 = await p26.confirm({
36135
36529
  message: "Create project?",
36136
36530
  initialValue: true
36137
36531
  });
36138
- if (p26.isCancel(confirm22) || !confirm22) {
36532
+ if (p26.isCancel(confirm23) || !confirm23) {
36139
36533
  p26.outro("Cancelled");
36140
36534
  return false;
36141
36535
  }
@@ -37361,11 +37755,11 @@ async function runInteractiveMode(session) {
37361
37755
  );
37362
37756
  }
37363
37757
  console.log();
37364
- const confirm22 = await p26.confirm({
37758
+ const confirm23 = await p26.confirm({
37365
37759
  message: "Proceed with restoration?",
37366
37760
  initialValue: true
37367
37761
  });
37368
- if (p26.isCancel(confirm22) || !confirm22) {
37762
+ if (p26.isCancel(confirm23) || !confirm23) {
37369
37763
  p26.outro("Cancelled");
37370
37764
  return false;
37371
37765
  }
@@ -37408,11 +37802,11 @@ async function runDirectMode(session, checkpointId) {
37408
37802
  console.log(`${chalk.dim("Conversation:")} ${checkpoint.conversation?.messageCount} messages`);
37409
37803
  }
37410
37804
  console.log();
37411
- const confirm22 = await p26.confirm({
37805
+ const confirm23 = await p26.confirm({
37412
37806
  message: "Restore this checkpoint?",
37413
37807
  initialValue: true
37414
37808
  });
37415
- if (p26.isCancel(confirm22) || !confirm22) {
37809
+ if (p26.isCancel(confirm23) || !confirm23) {
37416
37810
  p26.outro("Cancelled");
37417
37811
  return false;
37418
37812
  }
@@ -37863,11 +38257,11 @@ async function runInteractiveMode2(session) {
37863
38257
  return false;
37864
38258
  }
37865
38259
  displaySessionDetails(selectedSession);
37866
- const confirm22 = await p26.confirm({
38260
+ const confirm23 = await p26.confirm({
37867
38261
  message: "Resume this session?",
37868
38262
  initialValue: true
37869
38263
  });
37870
- if (p26.isCancel(confirm22) || !confirm22) {
38264
+ if (p26.isCancel(confirm23) || !confirm23) {
37871
38265
  p26.outro("Cancelled");
37872
38266
  return false;
37873
38267
  }
@@ -37885,11 +38279,11 @@ async function runDirectMode2(session, sessionId) {
37885
38279
  return false;
37886
38280
  }
37887
38281
  displaySessionDetails(targetSession);
37888
- const confirm22 = await p26.confirm({
38282
+ const confirm23 = await p26.confirm({
37889
38283
  message: "Resume this session?",
37890
38284
  initialValue: true
37891
38285
  });
37892
- if (p26.isCancel(confirm22) || !confirm22) {
38286
+ if (p26.isCancel(confirm23) || !confirm23) {
37893
38287
  p26.outro("Cancelled");
37894
38288
  return false;
37895
38289
  }
@@ -38902,7 +39296,9 @@ async function loadPermissionPreferences() {
38902
39296
  recommendedAllowlistApplied: config.recommendedAllowlistApplied,
38903
39297
  recommendedAllowlistDismissed: config.recommendedAllowlistDismissed,
38904
39298
  recommendedAllowlistPrompted: config.recommendedAllowlistPrompted,
38905
- recommendedAllowlistPromptedProjects: config.recommendedAllowlistPromptedProjects
39299
+ recommendedAllowlistPromptedProjects: config.recommendedAllowlistPromptedProjects,
39300
+ recommendedAllowlistAppliedProjects: config.recommendedAllowlistAppliedProjects,
39301
+ recommendedAllowlistDismissedProjects: config.recommendedAllowlistDismissedProjects
38906
39302
  };
38907
39303
  } catch {
38908
39304
  return {};
@@ -38922,7 +39318,27 @@ async function savePermissionPreference(key, value) {
38922
39318
  } catch {
38923
39319
  }
38924
39320
  }
38925
- 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) {
38926
39342
  try {
38927
39343
  let config = {};
38928
39344
  try {
@@ -38930,12 +39346,12 @@ async function markPermissionSuggestionShownForProject(projectPath) {
38930
39346
  config = JSON.parse(content);
38931
39347
  } catch {
38932
39348
  }
38933
- const promptedProjects = {
38934
- ...config.recommendedAllowlistPromptedProjects,
38935
- [getProjectPreferenceKey(projectPath)]: true
39349
+ const projectKey = getProjectPreferenceKey(projectPath);
39350
+ const currentMap = config[key] ?? {};
39351
+ config[key] = {
39352
+ ...currentMap,
39353
+ [projectKey]: value
38936
39354
  };
38937
- config.recommendedAllowlistPromptedProjects = promptedProjects;
38938
- config.recommendedAllowlistPrompted = true;
38939
39355
  await fs35__default.mkdir(path39__default.dirname(CONFIG_PATHS.config), { recursive: true });
38940
39356
  await fs35__default.writeFile(CONFIG_PATHS.config, JSON.stringify(config, null, 2), "utf-8");
38941
39357
  } catch {
@@ -38943,26 +39359,26 @@ async function markPermissionSuggestionShownForProject(projectPath) {
38943
39359
  }
38944
39360
  async function shouldShowPermissionSuggestion(projectPath = process.cwd()) {
38945
39361
  const prefs = await loadPermissionPreferences();
38946
- if (prefs.recommendedAllowlistDismissed) {
38947
- return false;
38948
- }
38949
- if (prefs.recommendedAllowlistApplied) {
39362
+ if (isRecommendedAllowlistDismissedForProject(prefs, projectPath)) {
38950
39363
  return false;
38951
39364
  }
38952
- const projectKey = getProjectPreferenceKey(projectPath);
38953
- if (prefs.recommendedAllowlistPromptedProjects?.[projectKey]) {
39365
+ if (isRecommendedAllowlistAppliedForProject(prefs, projectPath)) {
38954
39366
  return false;
38955
39367
  }
38956
39368
  return true;
38957
39369
  }
38958
- async function applyRecommendedPermissions() {
39370
+ async function applyRecommendedPermissions(projectPath = process.cwd()) {
38959
39371
  for (const tool of [...RECOMMENDED_GLOBAL, ...RECOMMENDED_PROJECT]) {
38960
39372
  await saveTrustedTool(tool, null, true);
38961
39373
  }
38962
- await savePermissionPreference("recommendedAllowlistApplied", true);
39374
+ await saveProjectPermissionPreference("recommendedAllowlistAppliedProjects", projectPath, true);
39375
+ await saveProjectPermissionPreference(
39376
+ "recommendedAllowlistDismissedProjects",
39377
+ projectPath,
39378
+ false
39379
+ );
38963
39380
  }
38964
39381
  async function showPermissionSuggestion(projectPath = process.cwd()) {
38965
- await markPermissionSuggestionShownForProject(projectPath);
38966
39382
  console.log();
38967
39383
  console.log(chalk.magenta.bold(" \u{1F4CB} Recommended Permissions"));
38968
39384
  console.log();
@@ -38989,8 +39405,11 @@ async function showPermissionSuggestion(projectPath = process.cwd()) {
38989
39405
  return;
38990
39406
  }
38991
39407
  if (action === "dismiss") {
38992
- await savePermissionPreference("recommendedAllowlistPrompted", true);
38993
- await savePermissionPreference("recommendedAllowlistDismissed", true);
39408
+ await saveProjectPermissionPreference(
39409
+ "recommendedAllowlistDismissedProjects",
39410
+ projectPath,
39411
+ true
39412
+ );
38994
39413
  console.log(chalk.dim(" Won't show again. Use /permissions to apply later."));
38995
39414
  return;
38996
39415
  }
@@ -39004,8 +39423,7 @@ async function showPermissionSuggestion(projectPath = process.cwd()) {
39004
39423
  return;
39005
39424
  }
39006
39425
  }
39007
- await applyRecommendedPermissions();
39008
- await savePermissionPreference("recommendedAllowlistPrompted", true);
39426
+ await applyRecommendedPermissions(projectPath);
39009
39427
  console.log(chalk.green(" \u2713 Recommended permissions applied"));
39010
39428
  console.log(chalk.dim(" Use /permissions to review or modify anytime."));
39011
39429
  }
@@ -39096,7 +39514,7 @@ async function showStatus(session) {
39096
39514
  console.log(chalk.magenta.bold(" \u{1F510} Tool Permissions"));
39097
39515
  console.log();
39098
39516
  const allowCount = RECOMMENDED_GLOBAL.length + RECOMMENDED_PROJECT.length;
39099
- if (prefs.recommendedAllowlistApplied) {
39517
+ if (isRecommendedAllowlistAppliedForProject(prefs, session.projectPath)) {
39100
39518
  console.log(
39101
39519
  chalk.green(" \u2713 Recommended allowlist applied") + chalk.dim(` (${allowCount} allow, ${RECOMMENDED_DENY.length} deny)`)
39102
39520
  );
@@ -39138,7 +39556,7 @@ async function showStatus(session) {
39138
39556
  console.log();
39139
39557
  }
39140
39558
  async function applyRecommended(session) {
39141
- await applyRecommendedPermissions();
39559
+ await applyRecommendedPermissions(session.projectPath);
39142
39560
  for (const tool of RECOMMENDED_GLOBAL) {
39143
39561
  session.trustedTools.add(tool);
39144
39562
  }
@@ -39188,177 +39606,18 @@ async function resetPermissions(session) {
39188
39606
  } catch {
39189
39607
  }
39190
39608
  await savePermissionPreference("recommendedAllowlistApplied", false);
39191
- console.log(chalk.green(" \u2713 All tool permissions reset."));
39192
- }
39193
-
39194
- // src/cli/repl/quality-loop.ts
39195
- init_paths();
39196
- var qualityLoopEnabled = true;
39197
- var hintShown = false;
39198
- function isQualityLoop() {
39199
- return qualityLoopEnabled;
39200
- }
39201
- function setQualityLoop(enabled) {
39202
- qualityLoopEnabled = enabled;
39203
- }
39204
- function wasHintShown() {
39205
- return hintShown;
39206
- }
39207
- function markHintShown() {
39208
- hintShown = true;
39209
- }
39210
- function looksLikeFeatureRequest(input) {
39211
- const trimmed = input.trim();
39212
- if (trimmed.length < 20) return false;
39213
- if (trimmed.endsWith("?") && trimmed.length < 80) return false;
39214
- const featureKeywords = [
39215
- /\bimplement/i,
39216
- /\bcreate\b/i,
39217
- /\bbuild\b/i,
39218
- /\badd\b.*\b(feature|function|component|endpoint|service|module|class)/i,
39219
- /\brefactor/i,
39220
- /\bmigrate/i,
39221
- /\bsetup\b/i,
39222
- /\bintegrate/i,
39223
- /\bwrite\b.*\b(code|function|test|module)/i,
39224
- /\bdevelop/i,
39225
- /\bdesign\b/i,
39226
- /\bfix\b.*\b(bug|issue|error|problem)/i,
39227
- /\bupdate\b.*\b(function|component|service|module)/i,
39228
- /\bgenerate\b/i,
39229
- /\bconvert\b/i
39230
- ];
39231
- return featureKeywords.some((re) => re.test(trimmed));
39232
- }
39233
- function formatQualityLoopHint() {
39234
- return chalk.dim(" tip: ") + chalk.magenta("/quality") + chalk.dim(" enables auto-test & iterate until quality converges");
39235
- }
39236
- function formatQualityResult(result) {
39237
- const lines = [];
39238
- const scores = result.scoreHistory;
39239
- const progressStr = scores.map((s) => String(s)).join(" \u2192 ");
39240
- const convergedLabel = result.converged ? chalk.green("converged") : chalk.yellow("max iterations");
39241
- lines.push("");
39242
- lines.push(
39243
- 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
39244
39613
  );
39245
- const parts = [];
39246
- if (result.testsPassed !== void 0 && result.testsTotal !== void 0) {
39247
- const testsColor = result.testsPassed === result.testsTotal ? chalk.green : chalk.yellow;
39248
- parts.push(testsColor(`Tests: ${result.testsPassed}/${result.testsTotal}`));
39249
- }
39250
- if (result.coverage !== void 0) {
39251
- const covColor = result.coverage >= 80 ? chalk.green : chalk.yellow;
39252
- parts.push(covColor(`Coverage: ${result.coverage}%`));
39253
- }
39254
- if (result.securityScore !== void 0) {
39255
- const secColor = result.securityScore === 100 ? chalk.green : chalk.red;
39256
- parts.push(secColor(`Security: ${result.securityScore}`));
39257
- }
39258
- parts.push(chalk.dim(`Iterations: ${result.iterations}`));
39259
- if (result.durationMs !== void 0) {
39260
- const secs = (result.durationMs / 1e3).toFixed(1);
39261
- parts.push(chalk.dim(`Time: ${secs}s`));
39262
- }
39263
- lines.push(" " + parts.join(" "));
39264
- lines.push("");
39265
- return lines.join("\n");
39266
- }
39267
- async function loadQualityLoopPreference() {
39268
- try {
39269
- const content = await fs35__default.readFile(CONFIG_PATHS.config, "utf-8");
39270
- const config = JSON.parse(content);
39271
- const value = config.qualityLoop ?? config.cocoMode;
39272
- if (typeof value === "boolean") {
39273
- qualityLoopEnabled = value;
39274
- return value;
39275
- }
39276
- } catch {
39277
- }
39278
- return true;
39279
- }
39280
- async function saveQualityLoopPreference(enabled) {
39281
- try {
39282
- let config = {};
39283
- try {
39284
- const content = await fs35__default.readFile(CONFIG_PATHS.config, "utf-8");
39285
- config = JSON.parse(content);
39286
- } catch {
39287
- }
39288
- config.qualityLoop = enabled;
39289
- await fs35__default.writeFile(CONFIG_PATHS.config, JSON.stringify(config, null, 2) + "\n");
39290
- } catch {
39291
- }
39292
- }
39293
- function parseQualityLoopReport(content) {
39294
- const marker = "QUALITY_LOOP_REPORT";
39295
- const idx = content.indexOf(marker);
39296
- if (idx === -1) return null;
39297
- const block = content.slice(idx);
39298
- const getField = (name) => {
39299
- const match = block.match(new RegExp(`${name}:\\s*(.+)`));
39300
- return match?.[1]?.trim();
39301
- };
39302
- const scoreHistoryRaw = getField("score_history");
39303
- if (!scoreHistoryRaw) return null;
39304
- const scores = scoreHistoryRaw.replace(/[[\]]/g, "").split(",").map((s) => parseFloat(s.trim())).filter((n) => !isNaN(n));
39305
- if (scores.length === 0) return null;
39306
- const testsPassed = parseInt(getField("tests_passed") ?? "", 10);
39307
- const testsTotal = parseInt(getField("tests_total") ?? "", 10);
39308
- const coverage = parseInt(getField("coverage") ?? "", 10);
39309
- const security = parseInt(getField("security") ?? "", 10);
39310
- const iterations = parseInt(getField("iterations") ?? "", 10) || scores.length;
39311
- const converged = getField("converged") === "true";
39312
- return {
39313
- converged,
39314
- scoreHistory: scores,
39315
- finalScore: scores[scores.length - 1] ?? 0,
39316
- iterations,
39317
- testsPassed: isNaN(testsPassed) ? void 0 : testsPassed,
39318
- testsTotal: isNaN(testsTotal) ? void 0 : testsTotal,
39319
- coverage: isNaN(coverage) ? void 0 : coverage,
39320
- securityScore: isNaN(security) ? void 0 : security
39321
- };
39322
- }
39323
- function getQualityLoopSystemPrompt() {
39324
- return `
39325
- ## Quality Loop Mode (ACTIVE)
39326
-
39327
- You are operating in quality loop mode. After implementing code changes, you MUST follow this iteration cycle:
39328
-
39329
- 1. **Implement** the requested changes (code + tests)
39330
- 2. **Run tests** using the run_tests or bash_exec tool
39331
- 3. **Self-review**: Analyze your code against these 12 quality dimensions:
39332
- - Correctness, Completeness, Robustness, Readability
39333
- - Maintainability, Complexity, Duplication, Test Coverage
39334
- - Test Quality, Security, Documentation, Style
39335
- 4. **Score** your implementation 0-100 for each dimension
39336
- 5. **If issues found**: Fix them and go back to step 2
39337
- 6. **If quality is good** (overall \u2265 85 and improving < 2 points): Stop and report
39338
-
39339
- After completing the cycle, output a quality summary in this exact format:
39340
-
39341
- \`\`\`
39342
- QUALITY_LOOP_REPORT
39343
- score_history: [first_score, ..., final_score]
39344
- tests_passed: X
39345
- tests_total: Y
39346
- coverage: Z
39347
- security: 100
39348
- iterations: N
39349
- converged: true|false
39350
- \`\`\`
39351
-
39352
- Key rules:
39353
- - Always write tests alongside code
39354
- - Run tests after every change
39355
- - Minimum 2 iterations before declaring convergence
39356
- - Maximum 10 iterations
39357
- - Fix critical issues before moving on
39358
- - 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."));
39359
39620
  }
39360
-
39361
- // src/cli/repl/commands/quality.ts
39362
39621
  var qualityCommand = {
39363
39622
  name: "quality",
39364
39623
  aliases: ["coco"],
@@ -40129,11 +40388,11 @@ Response format (JSON only, no prose):
40129
40388
  }
40130
40389
  }
40131
40390
  if (!options?.skipConfirmation) {
40132
- const confirm22 = await p26.confirm({
40391
+ const confirm23 = await p26.confirm({
40133
40392
  message: "Start building with this plan?",
40134
40393
  initialValue: true
40135
40394
  });
40136
- if (p26.isCancel(confirm22) || !confirm22) {
40395
+ if (p26.isCancel(confirm23) || !confirm23) {
40137
40396
  cancel5("Build cancelled.");
40138
40397
  }
40139
40398
  }
@@ -41691,8 +41950,8 @@ Examples:
41691
41950
  recursive: z.boolean().optional().default(false).describe("Delete directories recursively"),
41692
41951
  confirm: z.boolean().optional().describe("Must be true to confirm deletion")
41693
41952
  }),
41694
- async execute({ path: filePath, recursive, confirm: confirm22 }) {
41695
- if (confirm22 !== true) {
41953
+ async execute({ path: filePath, recursive, confirm: confirm23 }) {
41954
+ if (confirm23 !== true) {
41696
41955
  throw new ToolError(
41697
41956
  "Deletion requires explicit confirmation. Set confirm: true to proceed.",
41698
41957
  { tool: "delete_file" }
@@ -48675,11 +48934,11 @@ var buildAppCommand = {
48675
48934
  return false;
48676
48935
  }
48677
48936
  if (!isAutonomous && !parsed.skipConfirmation) {
48678
- const confirm22 = await p26.confirm({
48937
+ const confirm23 = await p26.confirm({
48679
48938
  message: `Build "${spec.projectName}" with ${spec.sprints.length} sprints?`,
48680
48939
  initialValue: true
48681
48940
  });
48682
- if (p26.isCancel(confirm22) || !confirm22) {
48941
+ if (p26.isCancel(confirm23) || !confirm23) {
48683
48942
  p26.cancel("Build cancelled.");
48684
48943
  return false;
48685
48944
  }
@@ -53640,44 +53899,6 @@ function createIntentRecognizer(config = {}) {
53640
53899
  // src/cli/repl/index.ts
53641
53900
  init_env();
53642
53901
  init_allowed_paths();
53643
- async function getGitContext(projectPath) {
53644
- const controller = new AbortController();
53645
- const timer = setTimeout(() => controller.abort(), 3e3);
53646
- try {
53647
- const git = simpleGit({ baseDir: projectPath, abort: controller.signal });
53648
- const status = await git.status();
53649
- clearTimeout(timer);
53650
- return {
53651
- branch: status.current ?? "HEAD",
53652
- isDirty: !status.isClean(),
53653
- staged: status.staged.length,
53654
- modified: status.modified.length,
53655
- untracked: status.not_added.length,
53656
- ahead: status.ahead,
53657
- behind: status.behind
53658
- };
53659
- } catch {
53660
- clearTimeout(timer);
53661
- return null;
53662
- }
53663
- }
53664
- function formatGitLine(ctx) {
53665
- const branchColor = ctx.isDirty ? chalk.yellow : chalk.green;
53666
- const parts = [chalk.dim("\u{1F33F} ") + branchColor(ctx.branch)];
53667
- const changes = [];
53668
- if (ctx.staged > 0) changes.push(chalk.green(`+${ctx.staged}`));
53669
- if (ctx.modified > 0) changes.push(chalk.yellow(`~${ctx.modified}`));
53670
- if (ctx.untracked > 0) changes.push(chalk.dim(`?${ctx.untracked}`));
53671
- if (ctx.ahead > 0) changes.push(chalk.cyan(`\u2191${ctx.ahead}`));
53672
- if (ctx.behind > 0) changes.push(chalk.red(`\u2193${ctx.behind}`));
53673
- if (changes.length > 0) parts.push(changes.join(" "));
53674
- return parts.join(" \u2022 ");
53675
- }
53676
- function formatGitShort(ctx) {
53677
- const branch = ctx.isDirty ? chalk.yellow(ctx.branch) : chalk.green(ctx.branch);
53678
- const dirty = ctx.isDirty ? chalk.yellow(" \u25CF") : "";
53679
- return chalk.dim("\u{1F33F} ") + branch + dirty;
53680
- }
53681
53902
 
53682
53903
  // src/cli/repl/status-bar.ts
53683
53904
  init_env();
@@ -54782,85 +55003,7 @@ ${imagePrompts}`.trim() : imagePrompts;
54782
55003
  process.off("SIGTERM", sigtermHandler);
54783
55004
  }
54784
55005
  async function printWelcome(session, gitCtx, mcpManager) {
54785
- const trustStore = createTrustStore();
54786
- await trustStore.init();
54787
- const trustLevel = trustStore.getLevel(session.projectPath);
54788
- const boxWidth = 41;
54789
- const innerWidth = boxWidth - 2;
54790
- const versionText = `v${VERSION}`;
54791
- const subtitleText = "open source \u2022 corbat.tech";
54792
- const boxLine = (content) => {
54793
- const pad = Math.max(0, innerWidth - stringWidth2(content));
54794
- return chalk.magenta("\u2502") + content + " ".repeat(pad) + chalk.magenta("\u2502");
54795
- };
54796
- const titleLeftRaw = " COCO";
54797
- const titleRightRaw = versionText + " ";
54798
- const titleLeftStyled = " " + chalk.bold.white("COCO");
54799
- const titleGap = Math.max(1, innerWidth - stringWidth2(titleLeftRaw) - stringWidth2(titleRightRaw));
54800
- const titleContent = titleLeftStyled + " ".repeat(titleGap) + chalk.dim(titleRightRaw);
54801
- const taglineText = "code that converges to quality";
54802
- const taglineContent = " " + chalk.magenta(taglineText) + " ";
54803
- const subtitleContent = " " + chalk.dim(subtitleText) + " ";
54804
- console.log();
54805
- console.log(chalk.magenta(" \u256D" + "\u2500".repeat(boxWidth - 2) + "\u256E"));
54806
- console.log(" " + boxLine(titleContent));
54807
- console.log(" " + boxLine(taglineContent));
54808
- console.log(" " + boxLine(subtitleContent));
54809
- console.log(chalk.magenta(" \u2570" + "\u2500".repeat(boxWidth - 2) + "\u256F"));
54810
- const maxPathLen = 50;
54811
- let displayPath = session.projectPath;
54812
- if (displayPath.length > maxPathLen) {
54813
- displayPath = "..." + displayPath.slice(-maxPathLen + 3);
54814
- }
54815
- const lastSep = displayPath.lastIndexOf("/");
54816
- const parentPath = lastSep > 0 ? displayPath.slice(0, lastSep + 1) : "";
54817
- const projectName = lastSep > 0 ? displayPath.slice(lastSep + 1) : displayPath;
54818
- const providerName = session.config.provider.type;
54819
- const configuredModel = session.config.provider.model?.trim();
54820
- const modelName = configuredModel && !["default", "none", "null", "undefined"].includes(configuredModel.toLowerCase()) ? configuredModel : getDefaultModel(session.config.provider.type);
54821
- const trustText = trustLevel === "full" ? "full" : trustLevel === "write" ? "write" : trustLevel === "read" ? "read" : "";
54822
- console.log();
54823
- console.log(chalk.dim(` \u{1F4C1} ${parentPath}`) + chalk.magenta.bold(projectName));
54824
- console.log(
54825
- chalk.dim(` \u{1F916} ${providerName}/`) + chalk.magenta(modelName) + (trustText ? chalk.dim(` \u2022 \u{1F510} ${trustText}`) : "")
54826
- );
54827
- if (gitCtx) {
54828
- console.log(` ${formatGitLine(gitCtx)}`);
54829
- }
54830
- 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");
54831
- console.log(cocoStatus);
54832
- const skillTotal = session.skillRegistry?.size ?? 0;
54833
- const mcpServers = mcpManager?.getConnectedServers() ?? [];
54834
- const hasSomething = skillTotal > 0 || mcpServers.length > 0;
54835
- if (hasSomething) {
54836
- if (skillTotal > 0) {
54837
- const allMeta = session.skillRegistry.getAllMetadata();
54838
- const builtinCount = allMeta.filter((s) => s.scope === "builtin").length;
54839
- const projectCount = skillTotal - builtinCount;
54840
- const parts = [];
54841
- if (builtinCount > 0) parts.push(`${builtinCount} builtin`);
54842
- if (projectCount > 0) parts.push(`${projectCount} project`);
54843
- const detail = parts.length > 0 ? ` (${parts.join(" \xB7 ")})` : "";
54844
- console.log(chalk.green(" \u2713") + chalk.dim(` Skills: ${skillTotal} loaded${detail}`));
54845
- } else {
54846
- console.log(chalk.dim(" \xB7 Skills: none loaded"));
54847
- }
54848
- if (mcpServers.length > 0) {
54849
- const names = mcpServers.join(", ");
54850
- console.log(
54851
- chalk.green(" \u2713") + chalk.dim(
54852
- ` MCP: ${names} (${mcpServers.length} server${mcpServers.length === 1 ? "" : "s"} active)`
54853
- )
54854
- );
54855
- }
54856
- }
54857
- console.log();
54858
- console.log(
54859
- chalk.dim(" Type your request or ") + chalk.magenta("/help") + chalk.dim(" for commands")
54860
- );
54861
- 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");
54862
- console.log(pasteHint);
54863
- console.log();
55006
+ await renderStartupPanel(session, gitCtx, mcpManager?.getConnectedServers() ?? []);
54864
55007
  }
54865
55008
  async function checkProjectTrust(projectPath) {
54866
55009
  const trustStore = createTrustStore();