@corbat-tech/coco 2.27.1 → 2.27.3

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
@@ -791,8 +791,8 @@ async function requestDeviceCode(provider) {
791
791
  }
792
792
  const contentType = response.headers.get("content-type") || "";
793
793
  if (!contentType.includes("application/json")) {
794
- const text14 = await response.text();
795
- if (text14.includes("<!DOCTYPE") || text14.includes("<html")) {
794
+ const text15 = await response.text();
795
+ if (text15.includes("<!DOCTYPE") || text15.includes("<html")) {
796
796
  throw new Error(
797
797
  "OAuth service returned HTML instead of JSON.\n The service may be temporarily unavailable.\n Please use an API key instead, or try again later."
798
798
  );
@@ -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":
@@ -3370,25 +3377,25 @@ var init_anthropic = __esm({
3370
3377
  *
3371
3378
  * This heuristic analyzes the text to provide a better estimate.
3372
3379
  */
3373
- countTokens(text14) {
3374
- if (!text14) return 0;
3380
+ countTokens(text15) {
3381
+ if (!text15) return 0;
3375
3382
  const codePatterns = /[{}[\]();=<>!&|+\-*/]/g;
3376
3383
  const whitespacePattern = /\s/g;
3377
3384
  const wordPattern = /\b\w+\b/g;
3378
- const codeChars = (text14.match(codePatterns) || []).length;
3379
- const whitespace = (text14.match(whitespacePattern) || []).length;
3380
- const words = (text14.match(wordPattern) || []).length;
3381
- const isCodeLike = codeChars > text14.length * 0.05;
3385
+ const codeChars = (text15.match(codePatterns) || []).length;
3386
+ const whitespace = (text15.match(whitespacePattern) || []).length;
3387
+ const words = (text15.match(wordPattern) || []).length;
3388
+ const isCodeLike = codeChars > text15.length * 0.05;
3382
3389
  let charsPerToken;
3383
3390
  if (isCodeLike) {
3384
3391
  charsPerToken = 3.5;
3385
- } else if (whitespace > text14.length * 0.3) {
3392
+ } else if (whitespace > text15.length * 0.3) {
3386
3393
  charsPerToken = 5;
3387
3394
  } else {
3388
3395
  charsPerToken = 4.5;
3389
3396
  }
3390
3397
  const wordBasedEstimate = words * 1.3;
3391
- const charBasedEstimate = text14.length / charsPerToken;
3398
+ const charBasedEstimate = text15.length / charsPerToken;
3392
3399
  return Math.ceil((wordBasedEstimate + charBasedEstimate) / 2);
3393
3400
  }
3394
3401
  /**
@@ -3437,8 +3444,8 @@ var init_anthropic = __esm({
3437
3444
  const systemMsg = messages.find((m) => m.role === "system");
3438
3445
  if (!systemMsg) return void 0;
3439
3446
  if (typeof systemMsg.content === "string") return systemMsg.content;
3440
- const text14 = systemMsg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
3441
- return text14 || void 0;
3447
+ const text15 = systemMsg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
3448
+ return text15 || void 0;
3442
3449
  }
3443
3450
  /**
3444
3451
  * Convert messages to Anthropic format
@@ -3626,15 +3633,15 @@ var init_tool_call_normalizer = __esm({
3626
3633
  if (delta.function?.name) {
3627
3634
  builder.name = delta.function.name;
3628
3635
  }
3629
- const text14 = delta.function?.arguments ?? "";
3630
- if (!text14) return { started };
3631
- builder.arguments += text14;
3636
+ const text15 = delta.function?.arguments ?? "";
3637
+ if (!text15) return { started };
3638
+ builder.arguments += text15;
3632
3639
  return {
3633
3640
  started,
3634
3641
  argumentDelta: {
3635
3642
  id: builder.id,
3636
3643
  name: builder.name,
3637
- text: text14
3644
+ text: text15
3638
3645
  }
3639
3646
  };
3640
3647
  }
@@ -4223,23 +4230,23 @@ var init_openai = __esm({
4223
4230
  * For accurate counting, use the model's native tokenizer.
4224
4231
  * This heuristic provides a reasonable estimate without dependencies.
4225
4232
  */
4226
- countTokens(text14) {
4227
- if (!text14) return 0;
4233
+ countTokens(text15) {
4234
+ if (!text15) return 0;
4228
4235
  const codePatterns = /[{}[\]();=<>!&|+\-*/]/g;
4229
4236
  const whitespacePattern = /\s/g;
4230
4237
  const wordPattern = /\b\w+\b/g;
4231
4238
  const nonAsciiPattern = /[^\x00-\x7F]/g;
4232
- const codeChars = (text14.match(codePatterns) || []).length;
4233
- const whitespace = (text14.match(whitespacePattern) || []).length;
4234
- const words = (text14.match(wordPattern) || []).length;
4235
- const nonAscii = (text14.match(nonAsciiPattern) || []).length;
4236
- const isCodeLike = codeChars > text14.length * 0.05;
4239
+ const codeChars = (text15.match(codePatterns) || []).length;
4240
+ const whitespace = (text15.match(whitespacePattern) || []).length;
4241
+ const words = (text15.match(wordPattern) || []).length;
4242
+ const nonAscii = (text15.match(nonAsciiPattern) || []).length;
4243
+ const isCodeLike = codeChars > text15.length * 0.05;
4237
4244
  const isLocal = this.isLocalModel();
4238
4245
  let charsPerToken;
4239
4246
  if (isLocal) {
4240
4247
  if (isCodeLike) {
4241
4248
  charsPerToken = 3.2;
4242
- } else if (nonAscii > text14.length * 0.1) {
4249
+ } else if (nonAscii > text15.length * 0.1) {
4243
4250
  charsPerToken = 2;
4244
4251
  } else {
4245
4252
  charsPerToken = 3.5;
@@ -4247,7 +4254,7 @@ var init_openai = __esm({
4247
4254
  } else {
4248
4255
  if (isCodeLike) {
4249
4256
  charsPerToken = 3.3;
4250
- } else if (whitespace > text14.length * 0.3) {
4257
+ } else if (whitespace > text15.length * 0.3) {
4251
4258
  charsPerToken = 4.5;
4252
4259
  } else {
4253
4260
  charsPerToken = 4;
@@ -4255,7 +4262,7 @@ var init_openai = __esm({
4255
4262
  }
4256
4263
  const tokensPerWord = isLocal ? 1.4 : 1.3;
4257
4264
  const wordBasedEstimate = words * tokensPerWord;
4258
- const charBasedEstimate = text14.length / charsPerToken;
4265
+ const charBasedEstimate = text15.length / charsPerToken;
4259
4266
  const weight = isCodeLike ? 0.7 : 0.5;
4260
4267
  return Math.ceil(charBasedEstimate * weight + wordBasedEstimate * (1 - weight));
4261
4268
  }
@@ -5036,8 +5043,8 @@ var init_codex = __esm({
5036
5043
  * Count tokens in text (approximate)
5037
5044
  * Uses GPT-4 approximation: ~4 chars per token
5038
5045
  */
5039
- countTokens(text14) {
5040
- return Math.ceil(text14.length / 4);
5046
+ countTokens(text15) {
5047
+ return Math.ceil(text15.length / 4);
5041
5048
  }
5042
5049
  /**
5043
5050
  * Check if provider is available (has valid OAuth tokens)
@@ -5705,9 +5712,9 @@ var init_copilot2 = __esm({
5705
5712
  /**
5706
5713
  * Count tokens (approximate — Copilot models vary in tokenizer)
5707
5714
  */
5708
- countTokens(text14) {
5709
- if (!text14) return 0;
5710
- return Math.ceil(text14.length / 3.5);
5715
+ countTokens(text15) {
5716
+ if (!text15) return 0;
5717
+ return Math.ceil(text15.length / 3.5);
5711
5718
  }
5712
5719
  /**
5713
5720
  * Get context window for the current model
@@ -5805,9 +5812,9 @@ var init_gemini = __esm({
5805
5812
  });
5806
5813
  let streamStopReason = "end_turn";
5807
5814
  for await (const chunk of stream) {
5808
- const text14 = chunk.text;
5809
- if (text14) {
5810
- yield { type: "text", text: text14 };
5815
+ const text15 = chunk.text;
5816
+ if (text15) {
5817
+ yield { type: "text", text: text15 };
5811
5818
  }
5812
5819
  const finishReason = chunk.candidates?.[0]?.finishReason;
5813
5820
  if (finishReason) {
@@ -5831,9 +5838,9 @@ var init_gemini = __esm({
5831
5838
  let fallbackToolCounter = 0;
5832
5839
  const emittedToolIds = /* @__PURE__ */ new Set();
5833
5840
  for await (const chunk of stream) {
5834
- const text14 = chunk.text;
5835
- if (text14) {
5836
- yield { type: "text", text: text14 };
5841
+ const text15 = chunk.text;
5842
+ if (text15) {
5843
+ yield { type: "text", text: text15 };
5837
5844
  }
5838
5845
  const functionCalls = this.extractFunctionCalls(chunk);
5839
5846
  for (const functionCall of functionCalls) {
@@ -5869,9 +5876,9 @@ var init_gemini = __esm({
5869
5876
  throw this.handleError(error);
5870
5877
  }
5871
5878
  }
5872
- countTokens(text14) {
5873
- if (!text14) return 0;
5874
- return Math.ceil(text14.length / 3.5);
5879
+ countTokens(text15) {
5880
+ if (!text15) return 0;
5881
+ return Math.ceil(text15.length / 3.5);
5875
5882
  }
5876
5883
  getContextWindow() {
5877
5884
  const model = this.config.model ?? DEFAULT_MODEL5;
@@ -5919,8 +5926,8 @@ var init_gemini = __esm({
5919
5926
  const systemMsg = messages.find((m) => m.role === "system");
5920
5927
  if (!systemMsg) return void 0;
5921
5928
  if (typeof systemMsg.content === "string") return systemMsg.content;
5922
- const text14 = systemMsg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
5923
- return text14 || void 0;
5929
+ const text15 = systemMsg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
5930
+ return text15 || void 0;
5924
5931
  }
5925
5932
  convertContents(messages) {
5926
5933
  const toolNameByUseId = this.buildToolUseNameMap(messages);
@@ -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
  }
@@ -6212,8 +6224,8 @@ var init_vertex = __esm({
6212
6224
  }
6213
6225
  yield { type: "done", stopReason };
6214
6226
  }
6215
- countTokens(text14) {
6216
- return Math.ceil(text14.length / 4);
6227
+ countTokens(text15) {
6228
+ return Math.ceil(text15.length / 4);
6217
6229
  }
6218
6230
  getContextWindow() {
6219
6231
  return CONTEXT_WINDOWS6[this.config.model ?? DEFAULT_MODEL6] ?? 1048576;
@@ -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
  }
@@ -6268,8 +6287,8 @@ var init_vertex = __esm({
6268
6287
  const systemMsg = messages.find((m) => m.role === "system");
6269
6288
  if (!systemMsg) return void 0;
6270
6289
  if (typeof systemMsg.content === "string") return systemMsg.content;
6271
- const text14 = systemMsg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
6272
- return text14 || void 0;
6290
+ const text15 = systemMsg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
6291
+ return text15 || void 0;
6273
6292
  }
6274
6293
  buildToolUseNameMap(messages) {
6275
6294
  const map = /* @__PURE__ */ new Map();
@@ -6446,10 +6465,10 @@ var init_vertex = __esm({
6446
6465
  }
6447
6466
  parseResponse(response, model) {
6448
6467
  const candidate = response.candidates?.[0];
6449
- const text14 = (candidate?.content?.parts ?? []).filter((part) => part.text).map((part) => part.text).join("");
6468
+ const text15 = (candidate?.content?.parts ?? []).filter((part) => part.text).map((part) => part.text).join("");
6450
6469
  return {
6451
6470
  id: `vertex-${Date.now()}`,
6452
- content: text14,
6471
+ content: text15,
6453
6472
  stopReason: this.mapFinishReason(candidate?.finishReason),
6454
6473
  usage: {
6455
6474
  inputTokens: response.usageMetadata?.promptTokenCount ?? 0,
@@ -6835,7 +6854,7 @@ var init_fallback = __esm({
6835
6854
  */
6836
6855
  async initialize(config) {
6837
6856
  const results = await Promise.allSettled(
6838
- this.providers.map((p45) => p45.provider.initialize(config))
6857
+ this.providers.map((p46) => p46.provider.initialize(config))
6839
6858
  );
6840
6859
  const anySuccess = results.some((r) => r.status === "fulfilled");
6841
6860
  if (!anySuccess) {
@@ -6932,9 +6951,9 @@ var init_fallback = __esm({
6932
6951
  * @param text - Text to count tokens for
6933
6952
  * @returns Estimated token count
6934
6953
  */
6935
- countTokens(text14) {
6954
+ countTokens(text15) {
6936
6955
  const provider = this.getCurrentProvider();
6937
- return provider.provider.countTokens(text14);
6956
+ return provider.provider.countTokens(text15);
6938
6957
  }
6939
6958
  /**
6940
6959
  * Get context window from current provider
@@ -6957,11 +6976,11 @@ var init_fallback = __esm({
6957
6976
  */
6958
6977
  async isAvailable() {
6959
6978
  const results = await Promise.all(
6960
- this.providers.map(async (p45) => {
6961
- if (p45.breaker.isOpen()) {
6979
+ this.providers.map(async (p46) => {
6980
+ if (p46.breaker.isOpen()) {
6962
6981
  return false;
6963
6982
  }
6964
- return p45.provider.isAvailable();
6983
+ return p46.provider.isAvailable();
6965
6984
  })
6966
6985
  );
6967
6986
  return results.some((available) => available);
@@ -6992,10 +7011,10 @@ var init_fallback = __esm({
6992
7011
  * @returns Array of provider status objects
6993
7012
  */
6994
7013
  getCircuitStatus() {
6995
- return this.providers.map((p45) => ({
6996
- providerId: p45.provider.id,
6997
- state: p45.breaker.getState(),
6998
- failureCount: p45.breaker.getFailureCount()
7014
+ return this.providers.map((p46) => ({
7015
+ providerId: p46.provider.id,
7016
+ state: p46.breaker.getState(),
7017
+ failureCount: p46.breaker.getFailureCount()
6999
7018
  }));
7000
7019
  }
7001
7020
  /**
@@ -7005,8 +7024,8 @@ var init_fallback = __esm({
7005
7024
  * previously failing providers to be tried again.
7006
7025
  */
7007
7026
  resetCircuits() {
7008
- for (const p45 of this.providers) {
7009
- p45.breaker.reset();
7027
+ for (const p46 of this.providers) {
7028
+ p46.breaker.reset();
7010
7029
  }
7011
7030
  }
7012
7031
  /**
@@ -7150,8 +7169,8 @@ var init_resilient = __esm({
7150
7169
  async *streamWithTools(messages, options) {
7151
7170
  yield* this.streamWithPolicy(() => this.provider.streamWithTools(messages, options));
7152
7171
  }
7153
- countTokens(text14) {
7154
- return this.provider.countTokens(text14);
7172
+ countTokens(text15) {
7173
+ return this.provider.countTokens(text15);
7155
7174
  }
7156
7175
  getContextWindow() {
7157
7176
  return this.provider.getContextWindow();
@@ -8464,8 +8483,8 @@ function tokenOverlap(queryTokens, targetTokens) {
8464
8483
  }
8465
8484
  return hits / queryTokens.length;
8466
8485
  }
8467
- function tokenize(text14) {
8468
- return text14.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/[\s-]+/).filter((word) => word.length > 1 && !STOP_WORDS.has(word)).map(stem);
8486
+ function tokenize(text15) {
8487
+ return text15.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/[\s-]+/).filter((word) => word.length > 1 && !STOP_WORDS.has(word)).map(stem);
8469
8488
  }
8470
8489
  function stem(word) {
8471
8490
  if (word.length < 4) return word;
@@ -9562,8 +9581,8 @@ ${tail}`
9562
9581
  estimateTokens(messages, provider) {
9563
9582
  let total = 0;
9564
9583
  for (const message of messages) {
9565
- const text14 = this.extractTextContent(message.content);
9566
- total += provider.countTokens(text14);
9584
+ const text15 = this.extractTextContent(message.content);
9585
+ total += provider.countTokens(text15);
9567
9586
  }
9568
9587
  return total;
9569
9588
  }
@@ -11597,7 +11616,7 @@ function humanizeError(message, toolName) {
11597
11616
  return msg;
11598
11617
  }
11599
11618
  function looksLikeTechnicalJargon(message) {
11600
- return JARGON_PATTERNS.some((p45) => p45.test(message));
11619
+ return JARGON_PATTERNS.some((p46) => p46.test(message));
11601
11620
  }
11602
11621
  async function humanizeWithLLM(errorMessage, toolName, provider) {
11603
11622
  const prompt = [
@@ -12358,9 +12377,9 @@ function renderFileBlock(file, opts) {
12358
12377
  );
12359
12378
  }
12360
12379
  const pairs = pairAdjacentLines(hunk.lines);
12361
- const pairedDeleteIndices = new Set(pairs.map((p45) => p45.deleteIdx));
12362
- const pairedAddIndices = new Set(pairs.map((p45) => p45.addIdx));
12363
- const pairByAdd = new Map(pairs.map((p45) => [p45.addIdx, p45.deleteIdx]));
12380
+ const pairedDeleteIndices = new Set(pairs.map((p46) => p46.deleteIdx));
12381
+ const pairedAddIndices = new Set(pairs.map((p46) => p46.addIdx));
12382
+ const pairByAdd = new Map(pairs.map((p46) => [p46.addIdx, p46.deleteIdx]));
12364
12383
  const wordHighlights = /* @__PURE__ */ new Map();
12365
12384
  for (const pair of pairs) {
12366
12385
  const delLine = hunk.lines[pair.deleteIdx];
@@ -12816,10 +12835,10 @@ var init_coverage = __esm({
12816
12835
  join(this.projectPath, ".coverage", "coverage-summary.json"),
12817
12836
  join(this.projectPath, "coverage", "lcov-report", "coverage-summary.json")
12818
12837
  ];
12819
- for (const p45 of possiblePaths) {
12838
+ for (const p46 of possiblePaths) {
12820
12839
  try {
12821
- await access(p45, constants.R_OK);
12822
- const content = await readFile(p45, "utf-8");
12840
+ await access(p46, constants.R_OK);
12841
+ const content = await readFile(p46, "utf-8");
12823
12842
  const report = JSON.parse(content);
12824
12843
  return parseCoverageSummary(report);
12825
12844
  } catch {
@@ -18653,15 +18672,15 @@ ${message}
18653
18672
  let stdoutBuffer = "";
18654
18673
  let stderrBuffer = "";
18655
18674
  subprocess.stdout?.on("data", (chunk) => {
18656
- const text14 = chunk.toString();
18657
- stdoutBuffer += text14;
18658
- process.stdout.write(text14);
18675
+ const text15 = chunk.toString();
18676
+ stdoutBuffer += text15;
18677
+ process.stdout.write(text15);
18659
18678
  heartbeat.activity();
18660
18679
  });
18661
18680
  subprocess.stderr?.on("data", (chunk) => {
18662
- const text14 = chunk.toString();
18663
- stderrBuffer += text14;
18664
- process.stderr.write(text14);
18681
+ const text15 = chunk.toString();
18682
+ stderrBuffer += text15;
18683
+ process.stderr.write(text15);
18665
18684
  heartbeat.activity();
18666
18685
  });
18667
18686
  const result = await subprocess;
@@ -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",
@@ -20622,11 +20641,11 @@ function isBlockedPath(absolute) {
20622
20641
  return void 0;
20623
20642
  }
20624
20643
  function isBlockedExecFile(filePath) {
20625
- return BLOCKED_EXEC_PATTERNS.some((p45) => p45.test(filePath));
20644
+ return BLOCKED_EXEC_PATTERNS.some((p46) => p46.test(filePath));
20626
20645
  }
20627
20646
  function hasDangerousArgs(args) {
20628
20647
  const joined = args.join(" ");
20629
- return DANGEROUS_ARG_PATTERNS.some((p45) => p45.test(joined));
20648
+ return DANGEROUS_ARG_PATTERNS.some((p46) => p46.test(joined));
20630
20649
  }
20631
20650
  function getInterpreter(ext) {
20632
20651
  return INTERPRETER_MAP[ext.toLowerCase()];
@@ -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)}`
@@ -23118,22 +23146,22 @@ var init_types7 = __esm({
23118
23146
  });
23119
23147
 
23120
23148
  // src/cli/repl/interruptions/classifier.ts
23121
- function matchPatterns(text14, patterns) {
23149
+ function matchPatterns(text15, patterns) {
23122
23150
  for (let i = 0; i < patterns.length; i++) {
23123
- if (patterns[i].test(text14)) {
23151
+ if (patterns[i].test(text15)) {
23124
23152
  return 1 - i * 0.1;
23125
23153
  }
23126
23154
  }
23127
23155
  return 0;
23128
23156
  }
23129
23157
  function classifyInterruption(message) {
23130
- const text14 = message.text;
23131
- const abortConf = matchPatterns(text14, ABORT_PATTERNS);
23132
- const modifyConf = matchPatterns(text14, MODIFY_PATTERNS);
23133
- const correctConf = matchPatterns(text14, CORRECT_PATTERNS);
23158
+ const text15 = message.text;
23159
+ const abortConf = matchPatterns(text15, ABORT_PATTERNS);
23160
+ const modifyConf = matchPatterns(text15, MODIFY_PATTERNS);
23161
+ const correctConf = matchPatterns(text15, CORRECT_PATTERNS);
23134
23162
  if (abortConf > 0 && abortConf >= modifyConf && abortConf >= correctConf) {
23135
23163
  return {
23136
- text: text14,
23164
+ text: text15,
23137
23165
  type: "abort" /* Abort */,
23138
23166
  confidence: Math.min(1, abortConf),
23139
23167
  timestamp: message.timestamp
@@ -23141,7 +23169,7 @@ function classifyInterruption(message) {
23141
23169
  }
23142
23170
  if (modifyConf > 0 && modifyConf >= correctConf) {
23143
23171
  return {
23144
- text: text14,
23172
+ text: text15,
23145
23173
  type: "modify" /* Modify */,
23146
23174
  confidence: Math.min(1, modifyConf),
23147
23175
  timestamp: message.timestamp
@@ -23149,14 +23177,14 @@ function classifyInterruption(message) {
23149
23177
  }
23150
23178
  if (correctConf > 0) {
23151
23179
  return {
23152
- text: text14,
23180
+ text: text15,
23153
23181
  type: "correct" /* Correct */,
23154
23182
  confidence: Math.min(1, correctConf),
23155
23183
  timestamp: message.timestamp
23156
23184
  };
23157
23185
  }
23158
23186
  return {
23159
- text: text14,
23187
+ text: text15,
23160
23188
  type: "info" /* Info */,
23161
23189
  confidence: 0.5,
23162
23190
  timestamp: message.timestamp
@@ -25664,10 +25692,10 @@ function inferTargetUsers(session) {
25664
25692
  /(?:for|by)\s+(developers?|users?|administrators?|customers?)/gi,
25665
25693
  /(developers?|users?|administrators?|customers?)\s+(?:can|will|should)/gi
25666
25694
  ];
25667
- const text14 = session.requirements.map((r) => r.description).join(" ");
25695
+ const text15 = session.requirements.map((r) => r.description).join(" ");
25668
25696
  for (const pattern of userPatterns) {
25669
25697
  let match;
25670
- while ((match = pattern.exec(text14)) !== null) {
25698
+ while ((match = pattern.exec(text15)) !== null) {
25671
25699
  const user = match[1]?.toLowerCase();
25672
25700
  if (user && !users.includes(user)) {
25673
25701
  users.push(user);
@@ -25680,13 +25708,13 @@ function inferTargetUsers(session) {
25680
25708
  return users;
25681
25709
  }
25682
25710
  function inferProjectType(session) {
25683
- const text14 = session.initialInput.toLowerCase();
25684
- if (text14.includes("cli") || text14.includes("command line")) return "cli";
25685
- if (text14.includes("api") || text14.includes("rest") || text14.includes("graphql")) return "api";
25686
- if (text14.includes("web app") || text14.includes("frontend")) return "web_app";
25687
- if (text14.includes("library") || text14.includes("package")) return "library";
25688
- if (text14.includes("service") || text14.includes("daemon")) return "service";
25689
- if (text14.includes("full stack") || text14.includes("fullstack")) return "full_stack";
25711
+ const text15 = session.initialInput.toLowerCase();
25712
+ if (text15.includes("cli") || text15.includes("command line")) return "cli";
25713
+ if (text15.includes("api") || text15.includes("rest") || text15.includes("graphql")) return "api";
25714
+ if (text15.includes("web app") || text15.includes("frontend")) return "web_app";
25715
+ if (text15.includes("library") || text15.includes("package")) return "library";
25716
+ if (text15.includes("service") || text15.includes("daemon")) return "service";
25717
+ if (text15.includes("full stack") || text15.includes("fullstack")) return "full_stack";
25690
25718
  return "unknown";
25691
25719
  }
25692
25720
  function assessComplexity(session) {
@@ -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,
@@ -30066,7 +30095,7 @@ function getProviderDefinition(type) {
30066
30095
  return PROVIDER_DEFINITIONS[type];
30067
30096
  }
30068
30097
  function getAllProviders() {
30069
- return Object.values(PROVIDER_DEFINITIONS).filter((p45) => !p45.internal);
30098
+ return Object.values(PROVIDER_DEFINITIONS).filter((p46) => !p46.internal);
30070
30099
  }
30071
30100
  function getRecommendedModel(type) {
30072
30101
  const provider = PROVIDER_DEFINITIONS[type];
@@ -30087,20 +30116,20 @@ function hasLocalProviderConfig(type) {
30087
30116
  return process.env["COCO_PROVIDER"] === "ollama" || !!process.env["OLLAMA_MODEL"] || !!process.env["OLLAMA_BASE_URL"];
30088
30117
  }
30089
30118
  function getConfiguredProviders() {
30090
- return getAllProviders().filter((p45) => {
30091
- if (p45.id === "copilot") {
30119
+ return getAllProviders().filter((p46) => {
30120
+ if (p46.id === "copilot") {
30092
30121
  return !!process.env["GITHUB_TOKEN"] || !!process.env["GH_TOKEN"] || hasCopilotCredentials();
30093
30122
  }
30094
- if (p45.id === "openai") {
30095
- return !!process.env[p45.envVar] || !!process.env["OPENAI_CODEX_TOKEN"] || !!process.env["OPENAI_ACCESS_TOKEN"];
30123
+ if (p46.id === "openai") {
30124
+ return !!process.env[p46.envVar] || !!process.env["OPENAI_CODEX_TOKEN"] || !!process.env["OPENAI_ACCESS_TOKEN"];
30096
30125
  }
30097
- if (p45.id === "lmstudio" || p45.id === "ollama") {
30098
- return hasLocalProviderConfig(p45.id);
30126
+ if (p46.id === "lmstudio" || p46.id === "ollama") {
30127
+ return hasLocalProviderConfig(p46.id);
30099
30128
  }
30100
- if (p45.id === "vertex") {
30101
- return !!(process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"]);
30129
+ if (p46.id === "vertex") {
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
- return !!process.env[p45.envVar];
30132
+ return !!process.env[p46.envVar];
30104
30133
  });
30105
30134
  }
30106
30135
  function isProviderConfigured(type) {
@@ -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;
@@ -31235,8 +31264,8 @@ ${suggestionsHtml}
31235
31264
  return filePath;
31236
31265
  }
31237
31266
  // ── Private helpers ───────────────────────────────────────────────────────
31238
- htmlEscape(text14) {
31239
- return text14.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
31267
+ htmlEscape(text15) {
31268
+ return text15.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
31240
31269
  }
31241
31270
  markdownIssue(issue) {
31242
31271
  const severity = issue.severity === "critical" ? "\u{1F534}" : issue.severity === "major" ? "\u{1F7E1}" : "\u{1F535}";
@@ -31758,12 +31787,12 @@ Return only JSON: { "score": <number 1-10>, "reasoning": "<brief explanation>" }
31758
31787
  }
31759
31788
  return classifyFeatureHeuristic(feature);
31760
31789
  }
31761
- function extractJsonScore(text14) {
31790
+ function extractJsonScore(text15) {
31762
31791
  try {
31763
- const stripped = text14.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "").trim();
31792
+ const stripped = text15.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "").trim();
31764
31793
  return JSON.parse(stripped);
31765
31794
  } catch {
31766
- const match = text14.match(/\{[\s\S]*?\}/);
31795
+ const match = text15.match(/\{[\s\S]*?\}/);
31767
31796
  if (match) {
31768
31797
  try {
31769
31798
  return JSON.parse(match[0]);
@@ -31987,25 +32016,25 @@ Rules:
31987
32016
  }
31988
32017
  }
31989
32018
  async function defaultPromptHandler(q) {
31990
- const p45 = await import('@clack/prompts');
32019
+ const p46 = await import('@clack/prompts');
31991
32020
  if (q.options && q.options.length > 0) {
31992
- const result = await p45.select({
32021
+ const result = await p46.select({
31993
32022
  message: q.question,
31994
32023
  options: [
31995
32024
  ...q.options.map((o) => ({ value: o, label: o })),
31996
32025
  { value: q.assumedAnswer, label: `${q.assumedAnswer} (default)` }
31997
32026
  ]
31998
32027
  });
31999
- if (p45.isCancel(result)) {
32028
+ if (p46.isCancel(result)) {
32000
32029
  return q.assumedAnswer;
32001
32030
  }
32002
32031
  return result;
32003
32032
  } else {
32004
- const result = await p45.text({
32033
+ const result = await p46.text({
32005
32034
  message: q.question,
32006
32035
  placeholder: q.assumedAnswer
32007
32036
  });
32008
- if (p45.isCancel(result) || !result) {
32037
+ if (p46.isCancel(result) || !result) {
32009
32038
  return q.assumedAnswer;
32010
32039
  }
32011
32040
  return result;
@@ -32976,12 +33005,12 @@ function computeGlobalScore(results) {
32976
33005
  const total = results.reduce((sum, r) => sum + r.reviewScore, 0);
32977
33006
  return Math.round(total / results.length);
32978
33007
  }
32979
- function extractJson(text14) {
33008
+ function extractJson(text15) {
32980
33009
  try {
32981
- const stripped = text14.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "").trim();
33010
+ const stripped = text15.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "").trim();
32982
33011
  return JSON.parse(stripped);
32983
33012
  } catch {
32984
- const match = text14.match(/\{[\s\S]*\}/);
33013
+ const match = text15.match(/\{[\s\S]*\}/);
32985
33014
  if (match) {
32986
33015
  try {
32987
33016
  return JSON.parse(match[0]);
@@ -33282,6 +33311,302 @@ 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 = false;
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 on") + chalk.dim(" enables Coco quality mode: auto-test, self-review, and iterate until robust");
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
+ qualityLoopEnabled = false;
33443
+ return false;
33444
+ }
33445
+ async function saveQualityLoopPreference(enabled) {
33446
+ try {
33447
+ let config = {};
33448
+ try {
33449
+ const content = await fs35__default.readFile(CONFIG_PATHS.config, "utf-8");
33450
+ config = JSON.parse(content);
33451
+ } catch {
33452
+ }
33453
+ config.qualityLoop = enabled;
33454
+ await fs35__default.writeFile(CONFIG_PATHS.config, JSON.stringify(config, null, 2) + "\n");
33455
+ } catch {
33456
+ }
33457
+ }
33458
+ function parseQualityLoopReport(content) {
33459
+ const marker = "QUALITY_LOOP_REPORT";
33460
+ const idx = content.indexOf(marker);
33461
+ if (idx === -1) return null;
33462
+ const block = content.slice(idx);
33463
+ const getField = (name) => {
33464
+ const match = block.match(new RegExp(`${name}:\\s*(.+)`));
33465
+ return match?.[1]?.trim();
33466
+ };
33467
+ const scoreHistoryRaw = getField("score_history");
33468
+ if (!scoreHistoryRaw) return null;
33469
+ const scores = scoreHistoryRaw.replace(/[[\]]/g, "").split(",").map((s) => parseFloat(s.trim())).filter((n) => !isNaN(n));
33470
+ if (scores.length === 0) return null;
33471
+ const testsPassed = parseInt(getField("tests_passed") ?? "", 10);
33472
+ const testsTotal = parseInt(getField("tests_total") ?? "", 10);
33473
+ const coverage = parseInt(getField("coverage") ?? "", 10);
33474
+ const security = parseInt(getField("security") ?? "", 10);
33475
+ const iterations = parseInt(getField("iterations") ?? "", 10) || scores.length;
33476
+ const converged = getField("converged") === "true";
33477
+ return {
33478
+ converged,
33479
+ scoreHistory: scores,
33480
+ finalScore: scores[scores.length - 1] ?? 0,
33481
+ iterations,
33482
+ testsPassed: isNaN(testsPassed) ? void 0 : testsPassed,
33483
+ testsTotal: isNaN(testsTotal) ? void 0 : testsTotal,
33484
+ coverage: isNaN(coverage) ? void 0 : coverage,
33485
+ securityScore: isNaN(security) ? void 0 : security
33486
+ };
33487
+ }
33488
+ function getQualityLoopSystemPrompt() {
33489
+ return `
33490
+ ## Quality Loop Mode (ACTIVE)
33491
+
33492
+ You are operating in quality loop mode. After implementing code changes, you MUST follow this iteration cycle:
33493
+
33494
+ 1. **Implement** the requested changes (code + tests)
33495
+ 2. **Run tests** using the run_tests or bash_exec tool
33496
+ 3. **Self-review**: Analyze your code against these 12 quality dimensions:
33497
+ - Correctness, Completeness, Robustness, Readability
33498
+ - Maintainability, Complexity, Duplication, Test Coverage
33499
+ - Test Quality, Security, Documentation, Style
33500
+ 4. **Score** your implementation 0-100 for each dimension
33501
+ 5. **If issues found**: Fix them and go back to step 2
33502
+ 6. **If quality is good** (overall \u2265 85 and improving < 2 points): Stop and report
33503
+
33504
+ After completing the cycle, output a quality summary in this exact format:
33505
+
33506
+ \`\`\`
33507
+ QUALITY_LOOP_REPORT
33508
+ score_history: [first_score, ..., final_score]
33509
+ tests_passed: X
33510
+ tests_total: Y
33511
+ coverage: Z
33512
+ security: 100
33513
+ iterations: N
33514
+ converged: true|false
33515
+ \`\`\`
33516
+
33517
+ Key rules:
33518
+ - Always write tests alongside code
33519
+ - Run tests after every change
33520
+ - Minimum 2 iterations before declaring convergence
33521
+ - Maximum 10 iterations
33522
+ - Fix critical issues before moving on
33523
+ - Report honestly - don't inflate scores`;
33524
+ }
33525
+
33526
+ // src/cli/repl/startup-panel.ts
33527
+ async function renderStartupPanel(session, gitCtx, mcpServers = []) {
33528
+ const trustStore = createTrustStore();
33529
+ await trustStore.init();
33530
+ const trustLevel = trustStore.getLevel(session.projectPath);
33531
+ const boxWidth = 41;
33532
+ const innerWidth = boxWidth - 2;
33533
+ const versionText = `v${VERSION}`;
33534
+ const subtitleText = "open source \u2022 corbat.tech";
33535
+ const boxLine = (content) => {
33536
+ const pad = Math.max(0, innerWidth - stringWidth2(content));
33537
+ return chalk.magenta("\u2502") + content + " ".repeat(pad) + chalk.magenta("\u2502");
33538
+ };
33539
+ const titleLeftRaw = " COCO";
33540
+ const titleRightRaw = versionText + " ";
33541
+ const titleLeftStyled = " " + chalk.bold.white("COCO");
33542
+ const titleGap = Math.max(1, innerWidth - stringWidth2(titleLeftRaw) - stringWidth2(titleRightRaw));
33543
+ const titleContent = titleLeftStyled + " ".repeat(titleGap) + chalk.dim(titleRightRaw);
33544
+ const taglineText = "code that converges to quality";
33545
+ const taglineContent = " " + chalk.magenta(taglineText) + " ";
33546
+ const subtitleContent = " " + chalk.dim(subtitleText) + " ";
33547
+ console.log();
33548
+ console.log(chalk.magenta(" \u256D" + "\u2500".repeat(boxWidth - 2) + "\u256E"));
33549
+ console.log(" " + boxLine(titleContent));
33550
+ console.log(" " + boxLine(taglineContent));
33551
+ console.log(" " + boxLine(subtitleContent));
33552
+ console.log(chalk.magenta(" \u2570" + "\u2500".repeat(boxWidth - 2) + "\u256F"));
33553
+ const maxPathLen = 50;
33554
+ let displayPath = session.projectPath;
33555
+ if (displayPath.length > maxPathLen) {
33556
+ displayPath = "..." + displayPath.slice(-maxPathLen + 3);
33557
+ }
33558
+ const lastSep = displayPath.lastIndexOf("/");
33559
+ const parentPath = lastSep > 0 ? displayPath.slice(0, lastSep + 1) : "";
33560
+ const projectName = lastSep > 0 ? displayPath.slice(lastSep + 1) : displayPath;
33561
+ const providerName = session.config.provider.type;
33562
+ const configuredModel = session.config.provider.model?.trim();
33563
+ const modelName = configuredModel && !["default", "none", "null", "undefined"].includes(configuredModel.toLowerCase()) ? configuredModel : getDefaultModel(session.config.provider.type);
33564
+ const trustText = trustLevel === "full" ? "full" : trustLevel === "write" ? "write" : trustLevel === "read" ? "read" : "";
33565
+ console.log();
33566
+ console.log(chalk.dim(` \u{1F4C1} ${parentPath}`) + chalk.magenta.bold(projectName));
33567
+ console.log(
33568
+ chalk.dim(` \u{1F916} ${providerName}/`) + chalk.magenta(modelName) + (trustText ? chalk.dim(` \u2022 \u{1F510} ${trustText}`) : "")
33569
+ );
33570
+ if (gitCtx) {
33571
+ console.log(` ${formatGitLine(gitCtx)}`);
33572
+ }
33573
+ 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 mode is Coco's edge for robust code. Enable with /quality on");
33574
+ console.log(cocoStatus);
33575
+ const skillTotal = session.skillRegistry?.size ?? 0;
33576
+ const hasSomething = skillTotal > 0 || mcpServers.length > 0;
33577
+ if (hasSomething) {
33578
+ if (skillTotal > 0) {
33579
+ const allMeta = session.skillRegistry.getAllMetadata();
33580
+ const builtinCount = allMeta.filter((s) => s.scope === "builtin").length;
33581
+ const projectCount = skillTotal - builtinCount;
33582
+ const parts = [];
33583
+ if (builtinCount > 0) parts.push(`${builtinCount} builtin`);
33584
+ if (projectCount > 0) parts.push(`${projectCount} project`);
33585
+ const detail = parts.length > 0 ? ` (${parts.join(" \xB7 ")})` : "";
33586
+ console.log(chalk.green(" \u2713") + chalk.dim(` Skills: ${skillTotal} loaded${detail}`));
33587
+ } else {
33588
+ console.log(chalk.dim(" \xB7 Skills: none loaded"));
33589
+ }
33590
+ if (mcpServers.length > 0) {
33591
+ const names = mcpServers.join(", ");
33592
+ console.log(
33593
+ chalk.green(" \u2713") + chalk.dim(
33594
+ ` MCP: ${names} (${mcpServers.length} server${mcpServers.length === 1 ? "" : "s"} active)`
33595
+ )
33596
+ );
33597
+ }
33598
+ }
33599
+ console.log();
33600
+ console.log(
33601
+ chalk.dim(" Type your request or ") + chalk.magenta("/help") + chalk.dim(" for commands")
33602
+ );
33603
+ 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");
33604
+ console.log(pasteHint);
33605
+ console.log();
33606
+ }
33607
+
33608
+ // src/cli/repl/commands/clear.ts
33609
+ init_env();
33285
33610
  var clearCommand = {
33286
33611
  name: "clear",
33287
33612
  aliases: ["c"],
@@ -33289,7 +33614,22 @@ var clearCommand = {
33289
33614
  usage: "/clear",
33290
33615
  async execute(_args, session) {
33291
33616
  clearSession(session);
33292
- console.log(chalk.dim("Conversation cleared.\n"));
33617
+ process.stdout.write("\x1B[2J\x1B[H");
33618
+ const projectPath = session.projectPath || process.cwd();
33619
+ const gitCtx = await getGitContext(projectPath);
33620
+ const panelSession = {
33621
+ ...session,
33622
+ projectPath,
33623
+ config: session.config ?? {
33624
+ provider: {
33625
+ type: "anthropic",
33626
+ model: getDefaultModel("anthropic"),
33627
+ maxTokens: 8192
33628
+ }
33629
+ }
33630
+ };
33631
+ await renderStartupPanel(panelSession, gitCtx);
33632
+ console.log(chalk.dim("Context cleared.\n"));
33293
33633
  return false;
33294
33634
  }
33295
33635
  };
@@ -33656,7 +33996,7 @@ async function runOnboardingV2() {
33656
33996
  console.log(chalk.magenta(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
33657
33997
  console.log();
33658
33998
  p26.log.info(
33659
- `Found ${configuredProviders.length} configured provider(s): ${configuredProviders.map((p45) => p45.emoji + " " + p45.name).join(", ")}`
33999
+ `Found ${configuredProviders.length} configured provider(s): ${configuredProviders.map((p46) => p46.emoji + " " + p46.name).join(", ")}`
33660
34000
  );
33661
34001
  const useExisting = await p26.confirm({
33662
34002
  message: "Use an existing provider?",
@@ -33763,6 +34103,12 @@ async function setupProviderWithAuth(provider) {
33763
34103
  showProviderInfo(provider);
33764
34104
  const apiKey = await requestApiKey(provider);
33765
34105
  if (!apiKey) return null;
34106
+ let vertexSettings;
34107
+ if (provider.id === "vertex") {
34108
+ const settings = await promptVertexSettings();
34109
+ if (!settings) return null;
34110
+ vertexSettings = settings;
34111
+ }
33766
34112
  let baseUrl;
33767
34113
  if (provider.askForCustomUrl) {
33768
34114
  const wantsCustomUrl = await p26.confirm({
@@ -33786,7 +34132,7 @@ async function setupProviderWithAuth(provider) {
33786
34132
  }
33787
34133
  const model = await selectModel(provider);
33788
34134
  if (!model) return null;
33789
- const valid = await testConnection(provider, apiKey, model, baseUrl);
34135
+ const valid = await testConnection(provider, apiKey, model, baseUrl, vertexSettings);
33790
34136
  if (!valid) {
33791
34137
  const retry = await p26.confirm({
33792
34138
  message: "Would you like to try again?",
@@ -33801,7 +34147,9 @@ async function setupProviderWithAuth(provider) {
33801
34147
  type: provider.id,
33802
34148
  model,
33803
34149
  apiKey,
33804
- baseUrl
34150
+ baseUrl,
34151
+ project: vertexSettings?.project,
34152
+ location: vertexSettings?.location
33805
34153
  };
33806
34154
  }
33807
34155
  async function setupGcloudADC(provider) {
@@ -33817,11 +34165,6 @@ async function setupGcloudADC(provider) {
33817
34165
  p26.log.error("gcloud CLI is not installed");
33818
34166
  console.log(chalk.dim(" Install it from: https://cloud.google.com/sdk/docs/install"));
33819
34167
  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
34168
  const useFallback2 = await p26.confirm({
33826
34169
  message: "Use API key instead?",
33827
34170
  initialValue: true
@@ -33832,25 +34175,37 @@ async function setupGcloudADC(provider) {
33832
34175
  if (!apiKey2) return null;
33833
34176
  const model2 = await selectModel(provider);
33834
34177
  if (!model2) return null;
33835
- const valid2 = await testConnection(provider, apiKey2, model2);
34178
+ let vertexSettings2;
34179
+ if (provider.id === "vertex") {
34180
+ const settings = await promptVertexSettings();
34181
+ if (!settings) return null;
34182
+ vertexSettings2 = settings;
34183
+ }
34184
+ const valid2 = await testConnection(provider, apiKey2, model2, void 0, vertexSettings2);
33836
34185
  if (!valid2) return null;
33837
- return { type: provider.id, model: model2, apiKey: apiKey2 };
34186
+ return {
34187
+ type: provider.id,
34188
+ model: model2,
34189
+ apiKey: apiKey2,
34190
+ project: vertexSettings2?.project,
34191
+ location: vertexSettings2?.location
34192
+ };
33838
34193
  }
33839
34194
  let adc = await inspectADC();
33840
34195
  if (adc.status === "ok" && adc.token) {
33841
34196
  console.log(chalk.green(" \u2713 gcloud ADC is already configured!"));
33842
34197
  console.log();
33843
34198
  p26.log.success("Authentication verified");
33844
- const vertexSettings = provider.id === "vertex" ? await promptVertexSettings() : void 0;
33845
- if (provider.id === "vertex" && !vertexSettings) return null;
34199
+ const vertexSettings2 = provider.id === "vertex" ? await promptVertexSettings() : void 0;
34200
+ if (provider.id === "vertex" && !vertexSettings2) return null;
33846
34201
  const model2 = await selectModel(provider);
33847
34202
  if (!model2) return null;
33848
34203
  return {
33849
34204
  type: provider.id,
33850
34205
  model: model2,
33851
34206
  apiKey: "__gcloud_adc__",
33852
- project: vertexSettings?.project,
33853
- location: vertexSettings?.location
34207
+ project: vertexSettings2?.project,
34208
+ location: vertexSettings2?.location
33854
34209
  };
33855
34210
  }
33856
34211
  console.log(chalk.yellow(" No reusable gcloud ADC session was found for Coco."));
@@ -33873,16 +34228,16 @@ async function setupGcloudADC(provider) {
33873
34228
  console.log(chalk.green(" \u2713 gcloud ADC is now configured."));
33874
34229
  console.log();
33875
34230
  p26.log.success("Authentication verified");
33876
- const vertexSettings = provider.id === "vertex" ? await promptVertexSettings() : void 0;
33877
- if (provider.id === "vertex" && !vertexSettings) return null;
34231
+ const vertexSettings2 = provider.id === "vertex" ? await promptVertexSettings() : void 0;
34232
+ if (provider.id === "vertex" && !vertexSettings2) return null;
33878
34233
  const model2 = await selectModel(provider);
33879
34234
  if (!model2) return null;
33880
34235
  return {
33881
34236
  type: provider.id,
33882
34237
  model: model2,
33883
34238
  apiKey: "__gcloud_adc__",
33884
- project: vertexSettings?.project,
33885
- location: vertexSettings?.location
34239
+ project: vertexSettings2?.project,
34240
+ location: vertexSettings2?.location
33886
34241
  };
33887
34242
  }
33888
34243
  }
@@ -33901,13 +34256,6 @@ async function setupGcloudADC(provider) {
33901
34256
  }
33902
34257
  console.log(chalk.dim(" Coco will reuse the login on the next attempt if ADC is valid."));
33903
34258
  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
34259
  const useFallback = await p26.confirm({
33912
34260
  message: "Use API key for now?",
33913
34261
  initialValue: true
@@ -33918,9 +34266,21 @@ async function setupGcloudADC(provider) {
33918
34266
  if (!apiKey) return null;
33919
34267
  const model = await selectModel(provider);
33920
34268
  if (!model) return null;
33921
- const valid = await testConnection(provider, apiKey, model);
34269
+ let vertexSettings;
34270
+ if (provider.id === "vertex") {
34271
+ const settings = await promptVertexSettings();
34272
+ if (!settings) return null;
34273
+ vertexSettings = settings;
34274
+ }
34275
+ const valid = await testConnection(provider, apiKey, model, void 0, vertexSettings);
33922
34276
  if (!valid) return null;
33923
- return { type: provider.id, model, apiKey };
34277
+ return {
34278
+ type: provider.id,
34279
+ model,
34280
+ apiKey,
34281
+ project: vertexSettings?.project,
34282
+ location: vertexSettings?.location
34283
+ };
33924
34284
  }
33925
34285
  async function promptVertexSettings() {
33926
34286
  const projectDefault = process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"] ?? "";
@@ -34321,9 +34681,9 @@ async function setupOllamaProvider(port) {
34321
34681
  return setupLocalProvider("ollama", port);
34322
34682
  }
34323
34683
  async function selectExistingProvider(providers) {
34324
- const options = providers.map((p45) => ({
34325
- value: p45.id,
34326
- label: `${p45.emoji} ${p45.name}`,
34684
+ const options = providers.map((p46) => ({
34685
+ value: p46.id,
34686
+ label: `${p46.emoji} ${p46.name}`,
34327
34687
  hint: "Configured"
34328
34688
  }));
34329
34689
  options.push({ value: "__new__", label: "\u2795 Setup new provider", hint: "" });
@@ -34454,8 +34814,8 @@ async function testConnection(provider, apiKey, model, baseUrl, vertexSettings)
34454
34814
  process.env[`${provider.id.toUpperCase()}_BASE_URL`] = baseUrl;
34455
34815
  }
34456
34816
  if (provider.id === "vertex") {
34457
- if (vertexSettings?.project) ;
34458
- if (vertexSettings?.location) ;
34817
+ if (vertexSettings?.project) process.env["VERTEX_PROJECT"] = vertexSettings.project;
34818
+ if (vertexSettings?.location) process.env["VERTEX_LOCATION"] = vertexSettings.location;
34459
34819
  }
34460
34820
  const testProvider = await createProvider(provider.id, {
34461
34821
  model,
@@ -34715,7 +35075,7 @@ async function ensureConfiguredV2(config) {
34715
35075
  } catch {
34716
35076
  }
34717
35077
  }
34718
- const preferredProviderDef = providers.find((p45) => p45.id === config.provider.type);
35078
+ const preferredProviderDef = providers.find((p46) => p46.id === config.provider.type);
34719
35079
  const preferredIsLocal = preferredProviderDef?.requiresApiKey === false && preferredProviderDef?.id !== "copilot";
34720
35080
  const preferredHasApiKey = preferredProviderDef ? !!process.env[preferredProviderDef.envVar] : false;
34721
35081
  const preferredHasOpenAIOAuth = preferredProviderDef?.id === "openai" && hasOpenAIOAuthTokens;
@@ -34745,17 +35105,18 @@ async function ensureConfiguredV2(config) {
34745
35105
  preferredUnavailableWasLocal = preferredIsLocal;
34746
35106
  }
34747
35107
  if (!preferredWasConfiguredButUnavailable || !preferredUnavailableWasLocal) {
34748
- const configuredProviders = providers.filter((p45) => {
34749
- if (p45.id === "copilot") return isProviderConfigured();
34750
- if (p45.id === "openai") {
34751
- return hasOpenAIOAuthTokens || !!process.env[p45.envVar];
35108
+ const configuredProviders = providers.filter((p46) => {
35109
+ if (p46.id === "copilot") return isProviderConfigured();
35110
+ if (p46.id === "openai") {
35111
+ return hasOpenAIOAuthTokens || !!process.env[p46.envVar];
34752
35112
  }
34753
- return p45.requiresApiKey === false || !!process.env[p45.envVar];
35113
+ return p46.requiresApiKey === false || !!process.env[p46.envVar];
34754
35114
  });
34755
35115
  for (const prov of configuredProviders) {
34756
35116
  try {
35117
+ const rememberedModel = await getLastUsedModel(prov.id);
34757
35118
  const recommended = getRecommendedModel(prov.id);
34758
- const model = recommended?.id || prov.models[0]?.id || "";
35119
+ const model = rememberedModel || recommended?.id || prov.models[0]?.id || "";
34759
35120
  let providerId = prov.id;
34760
35121
  if (prov.id === "openai" && hasOpenAIOAuthTokens && !process.env[prov.envVar]) {
34761
35122
  const tokenResult = await getOrRefreshOAuthToken("openai");
@@ -34822,7 +35183,7 @@ init_auth();
34822
35183
  init_env();
34823
35184
  async function selectProviderInteractively(providers, currentProviderId) {
34824
35185
  return new Promise((resolve4) => {
34825
- let selectedIndex = providers.findIndex((p45) => p45.id === currentProviderId);
35186
+ let selectedIndex = providers.findIndex((p46) => p46.id === currentProviderId);
34826
35187
  if (selectedIndex === -1) selectedIndex = 0;
34827
35188
  let lastTotalLines = 0;
34828
35189
  const clearPrevious = () => {
@@ -34920,12 +35281,12 @@ var providerCommand = {
34920
35281
  `));
34921
35282
  const allProviders2 = getAllProviders();
34922
35283
  const configuredProviders = getConfiguredProviders();
34923
- const providerOptions = allProviders2.map((p45) => ({
34924
- id: p45.id,
34925
- name: p45.name,
34926
- emoji: p45.emoji,
34927
- description: p45.description,
34928
- isConfigured: configuredProviders.some((cp) => cp.id === p45.id)
35284
+ const providerOptions = allProviders2.map((p46) => ({
35285
+ id: p46.id,
35286
+ name: p46.name,
35287
+ emoji: p46.emoji,
35288
+ description: p46.description,
35289
+ isConfigured: configuredProviders.some((cp) => cp.id === p46.id)
34929
35290
  }));
34930
35291
  const selectedProviderId = await selectProviderInteractively(
34931
35292
  providerOptions,
@@ -34940,15 +35301,15 @@ var providerCommand = {
34940
35301
  `));
34941
35302
  return false;
34942
35303
  }
34943
- const newProvider2 = allProviders2.find((p45) => p45.id === selectedProviderId);
35304
+ const newProvider2 = allProviders2.find((p46) => p46.id === selectedProviderId);
34944
35305
  return await switchProvider(newProvider2, session);
34945
35306
  }
34946
35307
  const newProviderId = args[0]?.toLowerCase();
34947
35308
  const allProviders = getAllProviders();
34948
- const newProvider = allProviders.find((p45) => p45.id === newProviderId);
35309
+ const newProvider = allProviders.find((p46) => p46.id === newProviderId);
34949
35310
  if (!newProvider) {
34950
35311
  console.log(chalk.red(`Unknown provider: ${newProviderId}`));
34951
- console.log(chalk.dim(`Available: ${allProviders.map((p45) => p45.id).join(", ")}
35312
+ console.log(chalk.dim(`Available: ${allProviders.map((p46) => p46.id).join(", ")}
34952
35313
  `));
34953
35314
  return false;
34954
35315
  }
@@ -34983,7 +35344,7 @@ async function switchProvider(initialProvider, session) {
34983
35344
  `));
34984
35345
  return false;
34985
35346
  }
34986
- const supportsApiKey = newProvider.id !== "vertex" && newProvider.requiresApiKey !== false;
35347
+ const supportsApiKey = newProvider.requiresApiKey !== false;
34987
35348
  const apiKey = supportsApiKey ? process.env[newProvider.envVar] : void 0;
34988
35349
  const hasOAuth = supportsOAuth(newProvider.id) || newProvider.supportsOAuth;
34989
35350
  const hasGcloudADC = newProvider.supportsGcloudADC;
@@ -35117,7 +35478,10 @@ Using existing OAuth session...`));
35117
35478
  if (!adcResult) return false;
35118
35479
  selectedAuthMethod = "gcloud";
35119
35480
  if (newProvider.id === "vertex") {
35120
- const settings = await promptVertexSettings2();
35481
+ const settings = await promptVertexSettings2({
35482
+ project: session.config.provider.project,
35483
+ location: session.config.provider.location
35484
+ });
35121
35485
  if (!settings) return false;
35122
35486
  vertexSettings = settings;
35123
35487
  }
@@ -35138,6 +35502,14 @@ Using existing API key...`));
35138
35502
  selectedAuthMethod = "apikey";
35139
35503
  newApiKeyForSaving = key;
35140
35504
  }
35505
+ if (newProvider.id === "vertex") {
35506
+ const settings = await promptVertexSettings2({
35507
+ project: session.config.provider.project,
35508
+ location: session.config.provider.location
35509
+ });
35510
+ if (!settings) return false;
35511
+ vertexSettings = settings;
35512
+ }
35141
35513
  } else if (authChoice === "remove") {
35142
35514
  const removeOptions = [];
35143
35515
  if (oauthConnected) {
@@ -35194,7 +35566,10 @@ ${newProvider.emoji} ${newProvider.name} is not configured.`));
35194
35566
  if (!adcResult) return false;
35195
35567
  selectedAuthMethod = "gcloud";
35196
35568
  if (newProvider.id === "vertex") {
35197
- const settings = await promptVertexSettings2();
35569
+ const settings = await promptVertexSettings2({
35570
+ project: session.config.provider.project,
35571
+ location: session.config.provider.location
35572
+ });
35198
35573
  if (!settings) return false;
35199
35574
  vertexSettings = settings;
35200
35575
  }
@@ -35245,7 +35620,9 @@ ${newProvider.emoji} ${newProvider.name} is not configured.`));
35245
35620
  await saveConfiguration({
35246
35621
  type: userFacingProviderId,
35247
35622
  model: newModel,
35248
- apiKey: newApiKeyForSaving
35623
+ apiKey: newApiKeyForSaving,
35624
+ project: vertexSettings?.project,
35625
+ location: vertexSettings?.location
35249
35626
  });
35250
35627
  } else {
35251
35628
  await saveProviderPreference(userFacingProviderId, newModel, {
@@ -35285,8 +35662,26 @@ async function setupGcloudADCForProvider(_provider) {
35285
35662
  return true;
35286
35663
  }
35287
35664
  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}`));
35665
+ console.log();
35666
+ if (adc.message) console.log(chalk.dim(` ${adc.message}`));
35667
+ console.log();
35668
+ const runLoginNow = await p26.confirm({
35669
+ message: "Authenticate with gcloud now from Coco?",
35670
+ initialValue: true
35671
+ });
35672
+ if (p26.isCancel(runLoginNow)) return false;
35673
+ if (runLoginNow) {
35674
+ p26.log.step("Running `gcloud auth application-default login`...");
35675
+ const loginOk = await runGcloudADCLogin();
35676
+ if (loginOk) {
35677
+ const refreshed = await inspectADC();
35678
+ if (refreshed.status === "ok" && refreshed.token) {
35679
+ console.log(chalk.green(" \u2713 gcloud ADC is now configured.\n"));
35680
+ return true;
35681
+ }
35682
+ }
35683
+ p26.log.error("Could not complete gcloud ADC login from Coco.");
35684
+ console.log();
35290
35685
  }
35291
35686
  console.log(chalk.dim("\n Check the current ADC state with:"));
35292
35687
  console.log(chalk.cyan(" $ gcloud auth application-default print-access-token"));
@@ -35299,9 +35694,9 @@ async function setupGcloudADCForProvider(_provider) {
35299
35694
  console.log();
35300
35695
  return false;
35301
35696
  }
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";
35697
+ async function promptVertexSettings2(defaults) {
35698
+ const projectDefault = defaults?.project ?? process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"] ?? "";
35699
+ const locationDefault = defaults?.location ?? process.env["VERTEX_LOCATION"] ?? process.env["GOOGLE_CLOUD_LOCATION"] ?? "global";
35305
35700
  const project = await p26.text({
35306
35701
  message: "Google Cloud project ID:",
35307
35702
  placeholder: projectDefault || "my-gcp-project",
@@ -35951,7 +36346,7 @@ async function showTrustStatus(session, trustStore) {
35951
36346
  }
35952
36347
  const level = trustStore.getLevel(projectPath);
35953
36348
  const list = trustStore.list();
35954
- const project = list.find((p45) => p45.path === projectPath);
36349
+ const project = list.find((p46) => p46.path === projectPath);
35955
36350
  p26.log.message("");
35956
36351
  p26.log.message(`\u{1F510} Project Trust Status`);
35957
36352
  p26.log.message(` Path: ${projectPath}`);
@@ -36010,11 +36405,11 @@ async function revokeTrust(session, trustStore) {
36010
36405
  p26.log.info("This project is not currently trusted");
36011
36406
  return;
36012
36407
  }
36013
- const confirm22 = await p26.confirm({
36408
+ const confirm23 = await p26.confirm({
36014
36409
  message: "Revoke all access to this project?",
36015
36410
  initialValue: false
36016
36411
  });
36017
- if (p26.isCancel(confirm22) || !confirm22) {
36412
+ if (p26.isCancel(confirm23) || !confirm23) {
36018
36413
  p26.outro("Cancelled");
36019
36414
  return;
36020
36415
  }
@@ -36131,11 +36526,11 @@ var initCommand = {
36131
36526
  p26.log.message(` Description: ${description}`);
36132
36527
  }
36133
36528
  p26.log.message("");
36134
- const confirm22 = await p26.confirm({
36529
+ const confirm23 = await p26.confirm({
36135
36530
  message: "Create project?",
36136
36531
  initialValue: true
36137
36532
  });
36138
- if (p26.isCancel(confirm22) || !confirm22) {
36533
+ if (p26.isCancel(confirm23) || !confirm23) {
36139
36534
  p26.outro("Cancelled");
36140
36535
  return false;
36141
36536
  }
@@ -37361,11 +37756,11 @@ async function runInteractiveMode(session) {
37361
37756
  );
37362
37757
  }
37363
37758
  console.log();
37364
- const confirm22 = await p26.confirm({
37759
+ const confirm23 = await p26.confirm({
37365
37760
  message: "Proceed with restoration?",
37366
37761
  initialValue: true
37367
37762
  });
37368
- if (p26.isCancel(confirm22) || !confirm22) {
37763
+ if (p26.isCancel(confirm23) || !confirm23) {
37369
37764
  p26.outro("Cancelled");
37370
37765
  return false;
37371
37766
  }
@@ -37408,11 +37803,11 @@ async function runDirectMode(session, checkpointId) {
37408
37803
  console.log(`${chalk.dim("Conversation:")} ${checkpoint.conversation?.messageCount} messages`);
37409
37804
  }
37410
37805
  console.log();
37411
- const confirm22 = await p26.confirm({
37806
+ const confirm23 = await p26.confirm({
37412
37807
  message: "Restore this checkpoint?",
37413
37808
  initialValue: true
37414
37809
  });
37415
- if (p26.isCancel(confirm22) || !confirm22) {
37810
+ if (p26.isCancel(confirm23) || !confirm23) {
37416
37811
  p26.outro("Cancelled");
37417
37812
  return false;
37418
37813
  }
@@ -37863,11 +38258,11 @@ async function runInteractiveMode2(session) {
37863
38258
  return false;
37864
38259
  }
37865
38260
  displaySessionDetails(selectedSession);
37866
- const confirm22 = await p26.confirm({
38261
+ const confirm23 = await p26.confirm({
37867
38262
  message: "Resume this session?",
37868
38263
  initialValue: true
37869
38264
  });
37870
- if (p26.isCancel(confirm22) || !confirm22) {
38265
+ if (p26.isCancel(confirm23) || !confirm23) {
37871
38266
  p26.outro("Cancelled");
37872
38267
  return false;
37873
38268
  }
@@ -37885,11 +38280,11 @@ async function runDirectMode2(session, sessionId) {
37885
38280
  return false;
37886
38281
  }
37887
38282
  displaySessionDetails(targetSession);
37888
- const confirm22 = await p26.confirm({
38283
+ const confirm23 = await p26.confirm({
37889
38284
  message: "Resume this session?",
37890
38285
  initialValue: true
37891
38286
  });
37892
- if (p26.isCancel(confirm22) || !confirm22) {
38287
+ if (p26.isCancel(confirm23) || !confirm23) {
37893
38288
  p26.outro("Cancelled");
37894
38289
  return false;
37895
38290
  }
@@ -37918,8 +38313,8 @@ var STARTUP_TIMEOUT_MS = 5500;
37918
38313
  var CACHE_DIR = path39__default.join(os4__default.homedir(), ".coco");
37919
38314
  var CACHE_FILE = path39__default.join(CACHE_DIR, "version-check-cache.json");
37920
38315
  function compareVersions(a, b) {
37921
- const partsA = a.replace(/^v/, "").split(".").map((p45) => Number(p45.replace(/-.*$/, "")));
37922
- const partsB = b.replace(/^v/, "").split(".").map((p45) => Number(p45.replace(/-.*$/, "")));
38316
+ const partsA = a.replace(/^v/, "").split(".").map((p46) => Number(p46.replace(/-.*$/, "")));
38317
+ const partsB = b.replace(/^v/, "").split(".").map((p46) => Number(p46.replace(/-.*$/, "")));
37923
38318
  for (let i = 0; i < 3; i++) {
37924
38319
  const numA = partsA[i] ?? 0;
37925
38320
  const numB = partsB[i] ?? 0;
@@ -38043,13 +38438,13 @@ async function checkForUpdatesInteractive() {
38043
38438
  ]);
38044
38439
  clearTimeout(startupTimerId);
38045
38440
  if (!updateInfo) return;
38046
- const p45 = await import('@clack/prompts');
38441
+ const p46 = await import('@clack/prompts');
38047
38442
  printUpdateBanner(updateInfo);
38048
- const answer = await p45.confirm({
38443
+ const answer = await p46.confirm({
38049
38444
  message: "Exit now to update?",
38050
38445
  initialValue: true
38051
38446
  });
38052
- if (!p45.isCancel(answer) && answer) {
38447
+ if (!p46.isCancel(answer) && answer) {
38053
38448
  console.log();
38054
38449
  console.log(chalk.dim(` Running: ${updateInfo.updateCommand}`));
38055
38450
  console.log();
@@ -38178,7 +38573,7 @@ function getClipboardCommand() {
38178
38573
  }
38179
38574
  return null;
38180
38575
  }
38181
- async function copyToClipboard(text14) {
38576
+ async function copyToClipboard(text15) {
38182
38577
  const clipboardCmd = getClipboardCommand();
38183
38578
  if (!clipboardCmd) {
38184
38579
  return false;
@@ -38199,7 +38594,7 @@ async function copyToClipboard(text14) {
38199
38594
  });
38200
38595
  xselProc.on("error", () => resolve4(false));
38201
38596
  xselProc.on("close", (code) => resolve4(code === 0));
38202
- xselProc.stdin.write(text14);
38597
+ xselProc.stdin.write(text15);
38203
38598
  xselProc.stdin.end();
38204
38599
  } catch {
38205
38600
  resolve4(false);
@@ -38215,7 +38610,7 @@ async function copyToClipboard(text14) {
38215
38610
  });
38216
38611
  proc.stdin.on("error", () => {
38217
38612
  });
38218
- proc.stdin.write(text14);
38613
+ proc.stdin.write(text15);
38219
38614
  proc.stdin.end();
38220
38615
  } catch {
38221
38616
  resolve4(false);
@@ -38902,7 +39297,9 @@ async function loadPermissionPreferences() {
38902
39297
  recommendedAllowlistApplied: config.recommendedAllowlistApplied,
38903
39298
  recommendedAllowlistDismissed: config.recommendedAllowlistDismissed,
38904
39299
  recommendedAllowlistPrompted: config.recommendedAllowlistPrompted,
38905
- recommendedAllowlistPromptedProjects: config.recommendedAllowlistPromptedProjects
39300
+ recommendedAllowlistPromptedProjects: config.recommendedAllowlistPromptedProjects,
39301
+ recommendedAllowlistAppliedProjects: config.recommendedAllowlistAppliedProjects,
39302
+ recommendedAllowlistDismissedProjects: config.recommendedAllowlistDismissedProjects
38906
39303
  };
38907
39304
  } catch {
38908
39305
  return {};
@@ -38922,7 +39319,27 @@ async function savePermissionPreference(key, value) {
38922
39319
  } catch {
38923
39320
  }
38924
39321
  }
38925
- async function markPermissionSuggestionShownForProject(projectPath) {
39322
+ function isRecommendedAllowlistAppliedForProject(prefs, projectPath) {
39323
+ const projectKey = getProjectPreferenceKey(projectPath);
39324
+ if (prefs.recommendedAllowlistAppliedProjects?.[projectKey] === true) {
39325
+ return true;
39326
+ }
39327
+ if (prefs.recommendedAllowlistApplied === true && !prefs.recommendedAllowlistAppliedProjects) {
39328
+ return true;
39329
+ }
39330
+ return false;
39331
+ }
39332
+ function isRecommendedAllowlistDismissedForProject(prefs, projectPath) {
39333
+ const projectKey = getProjectPreferenceKey(projectPath);
39334
+ if (prefs.recommendedAllowlistDismissedProjects?.[projectKey] === true) {
39335
+ return true;
39336
+ }
39337
+ if (prefs.recommendedAllowlistDismissed === true && !prefs.recommendedAllowlistDismissedProjects) {
39338
+ return true;
39339
+ }
39340
+ return false;
39341
+ }
39342
+ async function saveProjectPermissionPreference(key, projectPath, value) {
38926
39343
  try {
38927
39344
  let config = {};
38928
39345
  try {
@@ -38930,12 +39347,12 @@ async function markPermissionSuggestionShownForProject(projectPath) {
38930
39347
  config = JSON.parse(content);
38931
39348
  } catch {
38932
39349
  }
38933
- const promptedProjects = {
38934
- ...config.recommendedAllowlistPromptedProjects,
38935
- [getProjectPreferenceKey(projectPath)]: true
39350
+ const projectKey = getProjectPreferenceKey(projectPath);
39351
+ const currentMap = config[key] ?? {};
39352
+ config[key] = {
39353
+ ...currentMap,
39354
+ [projectKey]: value
38936
39355
  };
38937
- config.recommendedAllowlistPromptedProjects = promptedProjects;
38938
- config.recommendedAllowlistPrompted = true;
38939
39356
  await fs35__default.mkdir(path39__default.dirname(CONFIG_PATHS.config), { recursive: true });
38940
39357
  await fs35__default.writeFile(CONFIG_PATHS.config, JSON.stringify(config, null, 2), "utf-8");
38941
39358
  } catch {
@@ -38943,30 +39360,30 @@ async function markPermissionSuggestionShownForProject(projectPath) {
38943
39360
  }
38944
39361
  async function shouldShowPermissionSuggestion(projectPath = process.cwd()) {
38945
39362
  const prefs = await loadPermissionPreferences();
38946
- if (prefs.recommendedAllowlistDismissed) {
39363
+ if (isRecommendedAllowlistDismissedForProject(prefs, projectPath)) {
38947
39364
  return false;
38948
39365
  }
38949
- if (prefs.recommendedAllowlistApplied) {
38950
- return false;
38951
- }
38952
- const projectKey = getProjectPreferenceKey(projectPath);
38953
- if (prefs.recommendedAllowlistPromptedProjects?.[projectKey]) {
39366
+ if (isRecommendedAllowlistAppliedForProject(prefs, projectPath)) {
38954
39367
  return false;
38955
39368
  }
38956
39369
  return true;
38957
39370
  }
38958
- async function applyRecommendedPermissions() {
39371
+ async function applyRecommendedPermissions(projectPath = process.cwd()) {
38959
39372
  for (const tool of [...RECOMMENDED_GLOBAL, ...RECOMMENDED_PROJECT]) {
38960
- await saveTrustedTool(tool, null, true);
39373
+ await saveTrustedTool(tool, projectPath, false);
38961
39374
  }
38962
- await savePermissionPreference("recommendedAllowlistApplied", true);
39375
+ await saveProjectPermissionPreference("recommendedAllowlistAppliedProjects", projectPath, true);
39376
+ await saveProjectPermissionPreference(
39377
+ "recommendedAllowlistDismissedProjects",
39378
+ projectPath,
39379
+ false
39380
+ );
38963
39381
  }
38964
39382
  async function showPermissionSuggestion(projectPath = process.cwd()) {
38965
- await markPermissionSuggestionShownForProject(projectPath);
38966
39383
  console.log();
38967
39384
  console.log(chalk.magenta.bold(" \u{1F4CB} Recommended Permissions"));
38968
39385
  console.log();
38969
- console.log(chalk.dim(" Coco has a curated set of tool permissions for developers:"));
39386
+ console.log(chalk.dim(" Coco has a curated set of tool permissions for this project:"));
38970
39387
  console.log(chalk.dim(" \u2022 Allow: file read/write, search, git staging, build, tests..."));
38971
39388
  console.log(
38972
39389
  chalk.dim(" \u2022 Ask each time: git commit, curl, rm, git pull, docker exec, cloud...")
@@ -38975,12 +39392,17 @@ async function showPermissionSuggestion(projectPath = process.cwd()) {
38975
39392
  console.log();
38976
39393
  console.log(chalk.dim(" Stored in ~/.coco/trusted-tools.json \u2014 edit manually or let"));
38977
39394
  console.log(chalk.dim(" Coco manage it when you approve actions from the prompt."));
39395
+ console.log(chalk.dim(" Note: applying here affects only the current project."));
38978
39396
  console.log();
38979
39397
  const action = await p26.select({
38980
39398
  message: "Apply recommended permissions?",
38981
39399
  options: [
38982
39400
  { value: "view", label: "View details", hint: "See the full list before deciding" },
38983
- { value: "apply", label: "Apply", hint: "Apply recommended permissions now" },
39401
+ {
39402
+ value: "apply",
39403
+ label: "Apply",
39404
+ hint: "Apply recommended permissions for this project"
39405
+ },
38984
39406
  { value: "later", label: "Later", hint: "Remind me next time" },
38985
39407
  { value: "dismiss", label: "No thanks", hint: "Don't show again" }
38986
39408
  ]
@@ -38989,8 +39411,11 @@ async function showPermissionSuggestion(projectPath = process.cwd()) {
38989
39411
  return;
38990
39412
  }
38991
39413
  if (action === "dismiss") {
38992
- await savePermissionPreference("recommendedAllowlistPrompted", true);
38993
- await savePermissionPreference("recommendedAllowlistDismissed", true);
39414
+ await saveProjectPermissionPreference(
39415
+ "recommendedAllowlistDismissedProjects",
39416
+ projectPath,
39417
+ true
39418
+ );
38994
39419
  console.log(chalk.dim(" Won't show again. Use /permissions to apply later."));
38995
39420
  return;
38996
39421
  }
@@ -39004,8 +39429,7 @@ async function showPermissionSuggestion(projectPath = process.cwd()) {
39004
39429
  return;
39005
39430
  }
39006
39431
  }
39007
- await applyRecommendedPermissions();
39008
- await savePermissionPreference("recommendedAllowlistPrompted", true);
39432
+ await applyRecommendedPermissions(projectPath);
39009
39433
  console.log(chalk.green(" \u2713 Recommended permissions applied"));
39010
39434
  console.log(chalk.dim(" Use /permissions to review or modify anytime."));
39011
39435
  }
@@ -39096,7 +39520,7 @@ async function showStatus(session) {
39096
39520
  console.log(chalk.magenta.bold(" \u{1F510} Tool Permissions"));
39097
39521
  console.log();
39098
39522
  const allowCount = RECOMMENDED_GLOBAL.length + RECOMMENDED_PROJECT.length;
39099
- if (prefs.recommendedAllowlistApplied) {
39523
+ if (isRecommendedAllowlistAppliedForProject(prefs, session.projectPath)) {
39100
39524
  console.log(
39101
39525
  chalk.green(" \u2713 Recommended allowlist applied") + chalk.dim(` (${allowCount} allow, ${RECOMMENDED_DENY.length} deny)`)
39102
39526
  );
@@ -39138,7 +39562,7 @@ async function showStatus(session) {
39138
39562
  console.log();
39139
39563
  }
39140
39564
  async function applyRecommended(session) {
39141
- await applyRecommendedPermissions();
39565
+ await applyRecommendedPermissions(session.projectPath);
39142
39566
  for (const tool of RECOMMENDED_GLOBAL) {
39143
39567
  session.trustedTools.add(tool);
39144
39568
  }
@@ -39188,177 +39612,18 @@ async function resetPermissions(session) {
39188
39612
  } catch {
39189
39613
  }
39190
39614
  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")
39615
+ await saveProjectPermissionPreference(
39616
+ "recommendedAllowlistAppliedProjects",
39617
+ session.projectPath,
39618
+ false
39244
39619
  );
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`;
39620
+ await saveProjectPermissionPreference(
39621
+ "recommendedAllowlistDismissedProjects",
39622
+ session.projectPath,
39623
+ false
39624
+ );
39625
+ console.log(chalk.green(" \u2713 All tool permissions reset."));
39359
39626
  }
39360
-
39361
- // src/cli/repl/commands/quality.ts
39362
39627
  var qualityCommand = {
39363
39628
  name: "quality",
39364
39629
  aliases: ["coco"],
@@ -39949,9 +40214,9 @@ var UserCancelledError = class extends Error {
39949
40214
  var SPEC_AGENT_SYSTEM = `You are a senior technical product manager specialising in rapid MVP delivery.
39950
40215
  Your job is to help a developer plan a software project efficiently and honestly.
39951
40216
  Always respond with valid JSON only \u2014 no markdown fences, no prose outside JSON.`;
39952
- function extractJson2(text14) {
39953
- const match = text14.match(/```(?:json)?\s*([\s\S]*?)```/) ?? text14.match(/(\{[\s\S]*\})/);
39954
- return match ? (match[1] ?? text14).trim() : text14.trim();
40217
+ function extractJson2(text15) {
40218
+ const match = text15.match(/```(?:json)?\s*([\s\S]*?)```/) ?? text15.match(/(\{[\s\S]*\})/);
40219
+ return match ? (match[1] ?? text15).trim() : text15.trim();
39955
40220
  }
39956
40221
  function validateBacklogSpec(raw) {
39957
40222
  if (!raw.sprints || raw.sprints.length === 0) {
@@ -40129,11 +40394,11 @@ Response format (JSON only, no prose):
40129
40394
  }
40130
40395
  }
40131
40396
  if (!options?.skipConfirmation) {
40132
- const confirm22 = await p26.confirm({
40397
+ const confirm23 = await p26.confirm({
40133
40398
  message: "Start building with this plan?",
40134
40399
  initialValue: true
40135
40400
  });
40136
- if (p26.isCancel(confirm22) || !confirm22) {
40401
+ if (p26.isCancel(confirm23) || !confirm23) {
40137
40402
  cancel5("Build cancelled.");
40138
40403
  }
40139
40404
  }
@@ -41691,8 +41956,8 @@ Examples:
41691
41956
  recursive: z.boolean().optional().default(false).describe("Delete directories recursively"),
41692
41957
  confirm: z.boolean().optional().describe("Must be true to confirm deletion")
41693
41958
  }),
41694
- async execute({ path: filePath, recursive, confirm: confirm22 }) {
41695
- if (confirm22 !== true) {
41959
+ async execute({ path: filePath, recursive, confirm: confirm23 }) {
41960
+ if (confirm23 !== true) {
41696
41961
  throw new ToolError(
41697
41962
  "Deletion requires explicit confirmation. Set confirm: true to proceed.",
41698
41963
  { tool: "delete_file" }
@@ -43592,15 +43857,15 @@ ${message}
43592
43857
  let stdoutBuffer = "";
43593
43858
  let stderrBuffer = "";
43594
43859
  subprocess.stdout?.on("data", (chunk) => {
43595
- const text14 = chunk.toString();
43596
- stdoutBuffer += text14;
43597
- process.stdout.write(text14);
43860
+ const text15 = chunk.toString();
43861
+ stdoutBuffer += text15;
43862
+ process.stdout.write(text15);
43598
43863
  heartbeat.activity();
43599
43864
  });
43600
43865
  subprocess.stderr?.on("data", (chunk) => {
43601
- const text14 = chunk.toString();
43602
- stderrBuffer += text14;
43603
- process.stderr.write(text14);
43866
+ const text15 = chunk.toString();
43867
+ stderrBuffer += text15;
43868
+ process.stderr.write(text15);
43604
43869
  heartbeat.activity();
43605
43870
  });
43606
43871
  const result = await subprocess;
@@ -43717,15 +43982,15 @@ ${message}
43717
43982
  let stdoutBuffer = "";
43718
43983
  let stderrBuffer = "";
43719
43984
  subprocess.stdout?.on("data", (chunk) => {
43720
- const text14 = chunk.toString();
43721
- stdoutBuffer += text14;
43722
- process.stdout.write(text14);
43985
+ const text15 = chunk.toString();
43986
+ stdoutBuffer += text15;
43987
+ process.stdout.write(text15);
43723
43988
  heartbeat.activity();
43724
43989
  });
43725
43990
  subprocess.stderr?.on("data", (chunk) => {
43726
- const text14 = chunk.toString();
43727
- stderrBuffer += text14;
43728
- process.stderr.write(text14);
43991
+ const text15 = chunk.toString();
43992
+ stderrBuffer += text15;
43993
+ process.stderr.write(text15);
43729
43994
  heartbeat.activity();
43730
43995
  });
43731
43996
  const result = await subprocess;
@@ -43819,15 +44084,15 @@ ${message}
43819
44084
  let stdoutBuffer = "";
43820
44085
  let stderrBuffer = "";
43821
44086
  subprocess.stdout?.on("data", (chunk) => {
43822
- const text14 = chunk.toString();
43823
- stdoutBuffer += text14;
43824
- process.stdout.write(text14);
44087
+ const text15 = chunk.toString();
44088
+ stdoutBuffer += text15;
44089
+ process.stdout.write(text15);
43825
44090
  heartbeat.activity();
43826
44091
  });
43827
44092
  subprocess.stderr?.on("data", (chunk) => {
43828
- const text14 = chunk.toString();
43829
- stderrBuffer += text14;
43830
- process.stderr.write(text14);
44093
+ const text15 = chunk.toString();
44094
+ stderrBuffer += text15;
44095
+ process.stderr.write(text15);
43831
44096
  heartbeat.activity();
43832
44097
  });
43833
44098
  const result = await subprocess;
@@ -43922,15 +44187,15 @@ ${message}
43922
44187
  let stdoutBuffer = "";
43923
44188
  let stderrBuffer = "";
43924
44189
  subprocess.stdout?.on("data", (chunk) => {
43925
- const text14 = chunk.toString();
43926
- stdoutBuffer += text14;
43927
- process.stdout.write(text14);
44190
+ const text15 = chunk.toString();
44191
+ stdoutBuffer += text15;
44192
+ process.stdout.write(text15);
43928
44193
  heartbeat.activity();
43929
44194
  });
43930
44195
  subprocess.stderr?.on("data", (chunk) => {
43931
- const text14 = chunk.toString();
43932
- stderrBuffer += text14;
43933
- process.stderr.write(text14);
44196
+ const text15 = chunk.toString();
44197
+ stderrBuffer += text15;
44198
+ process.stderr.write(text15);
43934
44199
  heartbeat.activity();
43935
44200
  });
43936
44201
  const result = await subprocess;
@@ -44026,15 +44291,15 @@ ${message}
44026
44291
  let stdoutBuffer = "";
44027
44292
  let stderrBuffer = "";
44028
44293
  subprocess.stdout?.on("data", (chunk) => {
44029
- const text14 = chunk.toString();
44030
- stdoutBuffer += text14;
44031
- process.stdout.write(text14);
44294
+ const text15 = chunk.toString();
44295
+ stdoutBuffer += text15;
44296
+ process.stdout.write(text15);
44032
44297
  heartbeat.activity();
44033
44298
  });
44034
44299
  subprocess.stderr?.on("data", (chunk) => {
44035
- const text14 = chunk.toString();
44036
- stderrBuffer += text14;
44037
- process.stderr.write(text14);
44300
+ const text15 = chunk.toString();
44301
+ stderrBuffer += text15;
44302
+ process.stderr.write(text15);
44038
44303
  heartbeat.activity();
44039
44304
  });
44040
44305
  const result = await subprocess;
@@ -44113,15 +44378,15 @@ ${message}
44113
44378
  let stdoutBuffer = "";
44114
44379
  let stderrBuffer = "";
44115
44380
  subprocess.stdout?.on("data", (chunk) => {
44116
- const text14 = chunk.toString();
44117
- stdoutBuffer += text14;
44118
- process.stdout.write(text14);
44381
+ const text15 = chunk.toString();
44382
+ stdoutBuffer += text15;
44383
+ process.stdout.write(text15);
44119
44384
  heartbeat.activity();
44120
44385
  });
44121
44386
  subprocess.stderr?.on("data", (chunk) => {
44122
- const text14 = chunk.toString();
44123
- stderrBuffer += text14;
44124
- process.stderr.write(text14);
44387
+ const text15 = chunk.toString();
44388
+ stderrBuffer += text15;
44389
+ process.stderr.write(text15);
44125
44390
  heartbeat.activity();
44126
44391
  });
44127
44392
  const result = await subprocess;
@@ -44612,16 +44877,16 @@ function htmlToMarkdown(html) {
44612
44877
  const prefix = "#".repeat(i);
44613
44878
  const regex = new RegExp(`<h${i}[^>]*>([\\s\\S]*?)<\\/h${i}>`, "gi");
44614
44879
  md = md.replace(regex, (_, content) => {
44615
- const text14 = content.replace(/<[^>]*>/g, "").trim();
44616
- return text14 ? `
44880
+ const text15 = content.replace(/<[^>]*>/g, "").trim();
44881
+ return text15 ? `
44617
44882
 
44618
- ${prefix} ${text14}
44883
+ ${prefix} ${text15}
44619
44884
 
44620
44885
  ` : "";
44621
44886
  });
44622
44887
  }
44623
- md = md.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, href, text14) => {
44624
- const cleanText = text14.replace(/<[^>]*>/g, "").trim();
44888
+ md = md.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, href, text15) => {
44889
+ const cleanText = text15.replace(/<[^>]*>/g, "").trim();
44625
44890
  if (!cleanText) return "";
44626
44891
  if (href.startsWith("#") || href.startsWith("javascript:")) return cleanText;
44627
44892
  return `[${cleanText}](${href})`;
@@ -44648,8 +44913,8 @@ ${decoded.trim()}
44648
44913
  });
44649
44914
  md = md.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_, items) => {
44650
44915
  return "\n" + items.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_2, item) => {
44651
- const text14 = item.replace(/<[^>]*>/g, "").trim();
44652
- return text14 ? `- ${text14}
44916
+ const text15 = item.replace(/<[^>]*>/g, "").trim();
44917
+ return text15 ? `- ${text15}
44653
44918
  ` : "";
44654
44919
  }) + "\n";
44655
44920
  });
@@ -44657,29 +44922,29 @@ ${decoded.trim()}
44657
44922
  let counter = 0;
44658
44923
  return "\n" + items.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_2, item) => {
44659
44924
  counter++;
44660
- const text14 = item.replace(/<[^>]*>/g, "").trim();
44661
- return text14 ? `${counter}. ${text14}
44925
+ const text15 = item.replace(/<[^>]*>/g, "").trim();
44926
+ return text15 ? `${counter}. ${text15}
44662
44927
  ` : "";
44663
44928
  }) + "\n";
44664
44929
  });
44665
44930
  md = md.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_, content) => {
44666
- const text14 = content.replace(/<[^>]*>/g, "").trim();
44667
- return text14 ? "\n" + text14.split("\n").map((line) => `> ${line.trim()}`).join("\n") + "\n" : "";
44931
+ const text15 = content.replace(/<[^>]*>/g, "").trim();
44932
+ return text15 ? "\n" + text15.split("\n").map((line) => `> ${line.trim()}`).join("\n") + "\n" : "";
44668
44933
  });
44669
44934
  md = md.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, content) => {
44670
- const text14 = content.replace(/<[^>]*>/g, "").trim();
44671
- return text14 ? `
44935
+ const text15 = content.replace(/<[^>]*>/g, "").trim();
44936
+ return text15 ? `
44672
44937
 
44673
- ${text14}
44938
+ ${text15}
44674
44939
 
44675
44940
  ` : "";
44676
44941
  });
44677
44942
  md = md.replace(/<br\s*\/?>/gi, "\n");
44678
44943
  md = md.replace(
44679
44944
  /<(?:strong|b)[^>]*>([\s\S]*?)<\/(?:strong|b)>/gi,
44680
- (_, text14) => `**${text14.trim()}**`
44945
+ (_, text15) => `**${text15.trim()}**`
44681
44946
  );
44682
- md = md.replace(/<(?:em|i)[^>]*>([\s\S]*?)<\/(?:em|i)>/gi, (_, text14) => `*${text14.trim()}*`);
44947
+ md = md.replace(/<(?:em|i)[^>]*>([\s\S]*?)<\/(?:em|i)>/gi, (_, text15) => `*${text15.trim()}*`);
44683
44948
  md = md.replace(/<hr\s*\/?>/gi, "\n---\n");
44684
44949
  md = md.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, tableContent) => {
44685
44950
  const rows = [];
@@ -45809,10 +46074,10 @@ function chunkContent(content, chunkSize) {
45809
46074
  const chunks = [];
45810
46075
  for (let i = 0; i < lines.length; i += chunkSize) {
45811
46076
  const chunkLines = lines.slice(i, Math.min(i + chunkSize, lines.length));
45812
- const text14 = chunkLines.join("\n").trim();
45813
- if (text14.length > 10) {
46077
+ const text15 = chunkLines.join("\n").trim();
46078
+ if (text15.length > 10) {
45814
46079
  chunks.push({
45815
- text: text14,
46080
+ text: text15,
45816
46081
  startLine: i + 1,
45817
46082
  endLine: Math.min(i + chunkSize, lines.length)
45818
46083
  });
@@ -45820,8 +46085,8 @@ function chunkContent(content, chunkSize) {
45820
46085
  }
45821
46086
  return chunks;
45822
46087
  }
45823
- function simpleEmbedding(text14) {
45824
- const words = text14.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 1);
46088
+ function simpleEmbedding(text15) {
46089
+ const words = text15.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 1);
45825
46090
  const freq = /* @__PURE__ */ new Map();
45826
46091
  for (const word of words) {
45827
46092
  freq.set(word, (freq.get(word) ?? 0) + 1);
@@ -45847,7 +46112,7 @@ function simpleEmbedding(text14) {
45847
46112
  }
45848
46113
  var embedFn = null;
45849
46114
  var usingFallbackEmbedding = false;
45850
- async function getEmbedding(text14) {
46115
+ async function getEmbedding(text15) {
45851
46116
  if (!embedFn) {
45852
46117
  try {
45853
46118
  const transformers = await import('@xenova/transformers');
@@ -45864,7 +46129,7 @@ async function getEmbedding(text14) {
45864
46129
  usingFallbackEmbedding = true;
45865
46130
  }
45866
46131
  }
45867
- return embedFn(text14);
46132
+ return embedFn(text15);
45868
46133
  }
45869
46134
  async function loadIndex2(indexDir) {
45870
46135
  try {
@@ -46417,23 +46682,23 @@ Examples:
46417
46682
  const pdfData = await pdfParse.default(dataBuffer, {
46418
46683
  max: maxPages
46419
46684
  });
46420
- let text14 = pdfData.text;
46685
+ let text15 = pdfData.text;
46421
46686
  let truncated = false;
46422
46687
  const totalPages = pdfData.numpages;
46423
46688
  if (pages) {
46424
46689
  const range = parsePageRange(pages, totalPages);
46425
- const pageTexts = text14.split(/\f/);
46690
+ const pageTexts = text15.split(/\f/);
46426
46691
  if (pageTexts.length > 1) {
46427
46692
  const selectedPages = pageTexts.slice(range.start - 1, range.end);
46428
- text14 = selectedPages.join("\n\n--- Page Break ---\n\n");
46693
+ text15 = selectedPages.join("\n\n--- Page Break ---\n\n");
46429
46694
  }
46430
46695
  }
46431
- if (text14.length > 5e5) {
46432
- text14 = text14.slice(0, 5e5);
46696
+ if (text15.length > 5e5) {
46697
+ text15 = text15.slice(0, 5e5);
46433
46698
  truncated = true;
46434
46699
  }
46435
46700
  return {
46436
- text: text14,
46701
+ text: text15,
46437
46702
  pages: totalPages,
46438
46703
  metadata: {
46439
46704
  title: pdfData.info?.Title,
@@ -47447,7 +47712,8 @@ var SuggestImprovementsSchema = z.object({
47447
47712
  context: z.string().optional().describe("Additional context about the code")
47448
47713
  });
47449
47714
  async function analyzeAndSuggest(filePath, _context) {
47450
- const content = await fs46.readFile(filePath, "utf-8");
47715
+ const rawContent = await fs46.readFile(filePath, "utf-8");
47716
+ const content = typeof rawContent === "string" ? rawContent : String(rawContent ?? "");
47451
47717
  const lines = content.split("\n");
47452
47718
  const suggestions = [];
47453
47719
  for (let i = 0; i < lines.length; i++) {
@@ -47812,11 +48078,11 @@ var getLearnedPatternsTool = defineTool({
47812
48078
  const patterns = store.getFrequentPatterns(typedInput.limit);
47813
48079
  return {
47814
48080
  totalPatterns: patterns.length,
47815
- patterns: patterns.map((p45) => ({
47816
- pattern: p45.pattern,
47817
- preference: p45.userPreference,
47818
- frequency: p45.frequency,
47819
- lastUsed: new Date(p45.lastUsed).toISOString()
48081
+ patterns: patterns.map((p46) => ({
48082
+ pattern: p46.pattern,
48083
+ preference: p46.userPreference,
48084
+ frequency: p46.frequency,
48085
+ lastUsed: new Date(p46.lastUsed).toISOString()
47820
48086
  }))
47821
48087
  };
47822
48088
  }
@@ -48675,11 +48941,11 @@ var buildAppCommand = {
48675
48941
  return false;
48676
48942
  }
48677
48943
  if (!isAutonomous && !parsed.skipConfirmation) {
48678
- const confirm22 = await p26.confirm({
48944
+ const confirm23 = await p26.confirm({
48679
48945
  message: `Build "${spec.projectName}" with ${spec.sprints.length} sprints?`,
48680
48946
  initialValue: true
48681
48947
  });
48682
- if (p26.isCancel(confirm22) || !confirm22) {
48948
+ if (p26.isCancel(confirm23) || !confirm23) {
48683
48949
  p26.cancel("Build cancelled.");
48684
48950
  return false;
48685
48951
  }
@@ -49860,24 +50126,24 @@ function formatHtmlLine(line) {
49860
50126
  }
49861
50127
  return null;
49862
50128
  }
49863
- function formatInlineMarkdown(text14) {
49864
- text14 = text14.replace(/\*\*\*(.+?)\*\*\*/g, (_, content) => chalk.bold.italic(content));
49865
- text14 = text14.replace(/\*\*(.+?)\*\*/g, (_, content) => chalk.bold(content));
49866
- text14 = text14.replace(/\*([^*]+)\*/g, (_, content) => chalk.italic(content));
49867
- text14 = text14.replace(/_([^_]+)_/g, (_, content) => chalk.italic(content));
49868
- text14 = text14.replace(/`([^`]+)`/g, (_, content) => chalk.cyan(content));
49869
- text14 = text14.replace(/~~(.+?)~~/g, (_, content) => chalk.strikethrough(content));
49870
- text14 = text14.replace(/\[([^\]]+)\]\([^)]+\)/g, (_, linkText) => chalk.blue.underline(linkText));
49871
- return text14;
49872
- }
49873
- function wrapText(text14, maxWidth) {
49874
- if (maxWidth <= 0) return [text14];
49875
- const plainText = stripAnsi(text14);
50129
+ function formatInlineMarkdown(text15) {
50130
+ text15 = text15.replace(/\*\*\*(.+?)\*\*\*/g, (_, content) => chalk.bold.italic(content));
50131
+ text15 = text15.replace(/\*\*(.+?)\*\*/g, (_, content) => chalk.bold(content));
50132
+ text15 = text15.replace(/\*([^*]+)\*/g, (_, content) => chalk.italic(content));
50133
+ text15 = text15.replace(/_([^_]+)_/g, (_, content) => chalk.italic(content));
50134
+ text15 = text15.replace(/`([^`]+)`/g, (_, content) => chalk.cyan(content));
50135
+ text15 = text15.replace(/~~(.+?)~~/g, (_, content) => chalk.strikethrough(content));
50136
+ text15 = text15.replace(/\[([^\]]+)\]\([^)]+\)/g, (_, linkText) => chalk.blue.underline(linkText));
50137
+ return text15;
50138
+ }
50139
+ function wrapText(text15, maxWidth) {
50140
+ if (maxWidth <= 0) return [text15];
50141
+ const plainText = stripAnsi(text15);
49876
50142
  if (plainText.length <= maxWidth) {
49877
- return [text14];
50143
+ return [text15];
49878
50144
  }
49879
50145
  const lines = [];
49880
- let remaining = text14;
50146
+ let remaining = text15;
49881
50147
  while (true) {
49882
50148
  const plain = stripAnsi(remaining);
49883
50149
  if (plain.length <= maxWidth) break;
@@ -49915,7 +50181,7 @@ function wrapText(text14, maxWidth) {
49915
50181
  if (remaining) {
49916
50182
  lines.push(remaining);
49917
50183
  }
49918
- return lines.length > 0 ? lines : [text14];
50184
+ return lines.length > 0 ? lines : [text15];
49919
50185
  }
49920
50186
  function stripAnsi(str) {
49921
50187
  return str.replace(/\x1b\[[0-9;]*m/g, "");
@@ -50067,9 +50333,9 @@ function printEditDiff(oldStr, newStr) {
50067
50333
  if (lines.length === 0) return;
50068
50334
  const truncate4 = (s) => s.length > termWidth - 2 ? s.slice(0, termWidth - 3) + "\u2026" : s;
50069
50335
  for (const l of lines) {
50070
- const text14 = `+ ${truncate4(l)}`;
50071
- const pad = Math.max(0, termWidth - stripAnsi(text14).length + 2);
50072
- console.log(" " + diffBgAdd(text14 + " ".repeat(pad)));
50336
+ const text15 = `+ ${truncate4(l)}`;
50337
+ const pad = Math.max(0, termWidth - stripAnsi(text15).length + 2);
50338
+ console.log(" " + diffBgAdd(text15 + " ".repeat(pad)));
50073
50339
  }
50074
50340
  return;
50075
50341
  }
@@ -50097,9 +50363,9 @@ function printEditDiff(oldStr, newStr) {
50097
50363
  }
50098
50364
  if (diffLineList.length === 0) return;
50099
50365
  const pairs = pairAdjacentDiffLines(diffLineList);
50100
- const pairedDeletes = new Set(pairs.map((p45) => p45.deleteIdx));
50101
- const pairedAdds = new Set(pairs.map((p45) => p45.addIdx));
50102
- const pairByAdd = new Map(pairs.map((p45) => [p45.addIdx, p45.deleteIdx]));
50366
+ const pairedDeletes = new Set(pairs.map((p46) => p46.deleteIdx));
50367
+ const pairedAdds = new Set(pairs.map((p46) => p46.addIdx));
50368
+ const pairByAdd = new Map(pairs.map((p46) => [p46.addIdx, p46.deleteIdx]));
50103
50369
  const wordHighlights = /* @__PURE__ */ new Map();
50104
50370
  for (const pair of pairs) {
50105
50371
  const del = diffLineList[pair.deleteIdx];
@@ -50525,10 +50791,10 @@ function findNextWordBoundary(line, pos) {
50525
50791
  while (i < line.length && line[i] === " ") i++;
50526
50792
  return i;
50527
50793
  }
50528
- function countVisualRows(text14, startCol, termCols) {
50794
+ function countVisualRows(text15, startCol, termCols) {
50529
50795
  let rows = 1;
50530
50796
  let col = startCol;
50531
- for (const char of text14) {
50797
+ for (const char of text15) {
50532
50798
  if (char === "\n") {
50533
50799
  if (col > 0) rows++;
50534
50800
  col = 0;
@@ -50542,11 +50808,11 @@ function countVisualRows(text14, startCol, termCols) {
50542
50808
  }
50543
50809
  return rows;
50544
50810
  }
50545
- function getCursorVisualPos(text14, cursorPos, promptLen, termCols) {
50811
+ function getCursorVisualPos(text15, cursorPos, promptLen, termCols) {
50546
50812
  let row = 0;
50547
50813
  let col = promptLen;
50548
50814
  for (let i = 0; i < cursorPos; i++) {
50549
- if (text14[i] === "\n") {
50815
+ if (text15[i] === "\n") {
50550
50816
  if (col > 0) row++;
50551
50817
  col = 0;
50552
50818
  } else {
@@ -50559,14 +50825,14 @@ function getCursorVisualPos(text14, cursorPos, promptLen, termCols) {
50559
50825
  }
50560
50826
  return { row, col };
50561
50827
  }
50562
- function computeWordWrap(text14, startCol, termCols) {
50828
+ function computeWordWrap(text15, startCol, termCols) {
50563
50829
  const passthrough = {
50564
- display: text14,
50565
- toDisplayPos: (p45) => p45,
50566
- toOrigPos: (p45) => p45
50830
+ display: text15,
50831
+ toDisplayPos: (p46) => p46,
50832
+ toOrigPos: (p46) => p46
50567
50833
  };
50568
- if (!text14 || termCols <= 1) return passthrough;
50569
- const origToDisp = new Int32Array(text14.length + 1);
50834
+ if (!text15 || termCols <= 1) return passthrough;
50835
+ const origToDisp = new Int32Array(text15.length + 1);
50570
50836
  const dispToOrig = [];
50571
50837
  let display = "";
50572
50838
  let col = startCol;
@@ -50582,15 +50848,15 @@ function computeWordWrap(text14, startCol, termCols) {
50582
50848
  col = 0;
50583
50849
  }
50584
50850
  let i = 0;
50585
- while (i < text14.length) {
50586
- const ch = text14[i];
50851
+ while (i < text15.length) {
50852
+ const ch = text15[i];
50587
50853
  if (ch === "\n") {
50588
50854
  emitChar("\n", i++);
50589
50855
  continue;
50590
50856
  }
50591
50857
  if (ch !== " ") {
50592
50858
  let wordEnd = i;
50593
- while (wordEnd < text14.length && text14[wordEnd] !== " " && text14[wordEnd] !== "\n") {
50859
+ while (wordEnd < text15.length && text15[wordEnd] !== " " && text15[wordEnd] !== "\n") {
50594
50860
  wordEnd++;
50595
50861
  }
50596
50862
  const wordLen = wordEnd - i;
@@ -50598,7 +50864,7 @@ function computeWordWrap(text14, startCol, termCols) {
50598
50864
  injectNewline();
50599
50865
  }
50600
50866
  for (let k = i; k < wordEnd; k++) {
50601
- emitChar(text14[k], k);
50867
+ emitChar(text15[k], k);
50602
50868
  if (col >= termCols && k + 1 < wordEnd) {
50603
50869
  injectNewline();
50604
50870
  }
@@ -50610,7 +50876,7 @@ function computeWordWrap(text14, startCol, termCols) {
50610
50876
  col = 0;
50611
50877
  } else {
50612
50878
  let nextWordEnd = i;
50613
- while (nextWordEnd < text14.length && text14[nextWordEnd] !== " " && text14[nextWordEnd] !== "\n") {
50879
+ while (nextWordEnd < text15.length && text15[nextWordEnd] !== " " && text15[nextWordEnd] !== "\n") {
50614
50880
  nextWordEnd++;
50615
50881
  }
50616
50882
  const nextWordLen = nextWordEnd - i;
@@ -50620,10 +50886,10 @@ function computeWordWrap(text14, startCol, termCols) {
50620
50886
  }
50621
50887
  }
50622
50888
  }
50623
- origToDisp[text14.length] = display.length;
50889
+ origToDisp[text15.length] = display.length;
50624
50890
  return {
50625
50891
  display,
50626
- toDisplayPos: (origPos) => origToDisp[Math.min(origPos, text14.length)] ?? display.length,
50892
+ toDisplayPos: (origPos) => origToDisp[Math.min(origPos, text15.length)] ?? display.length,
50627
50893
  toOrigPos: (displayPos) => {
50628
50894
  const dp = Math.max(0, Math.min(displayPos, dispToOrig.length - 1));
50629
50895
  for (let d = dp; d >= 0; d--) {
@@ -50756,11 +51022,11 @@ function createInputHandler(_session) {
50756
51022
  const item = visibleItems[itemIndex];
50757
51023
  const actualIndex = startIndex + itemIndex;
50758
51024
  const isSelected = actualIndex === selectedCompletion;
50759
- const text14 = ` ${item.cmd}`.padEnd(ITEM_WIDTH);
51025
+ const text15 = ` ${item.cmd}`.padEnd(ITEM_WIDTH);
50760
51026
  if (isSelected) {
50761
- output += chalk.bgBlue.white(text14);
51027
+ output += chalk.bgBlue.white(text15);
50762
51028
  } else {
50763
- output += chalk.cyan(text14);
51029
+ output += chalk.cyan(text15);
50764
51030
  }
50765
51031
  }
50766
51032
  }
@@ -50812,8 +51078,8 @@ function createInputHandler(_session) {
50812
51078
  process.stdout.write("\r" + ansiEscapes.eraseDown);
50813
51079
  lastMenuLines = 0;
50814
51080
  }
50815
- function insertTextAtCursor(text14) {
50816
- const cleaned = text14.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
51081
+ function insertTextAtCursor(text15) {
51082
+ const cleaned = text15.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
50817
51083
  const printable = cleaned.replace(/[^\n\x20-\x7E\u00A0-\uFFFF]/g, "");
50818
51084
  if (printable.length === 0) return;
50819
51085
  currentLine = currentLine.slice(0, cursorPos) + printable + currentLine.slice(cursorPos);
@@ -51716,10 +51982,10 @@ function formatWriteFilePreview(toolCall, maxLines = 10) {
51716
51982
  const footer = truncated ? chalk.dim(` \u2514\u2500 ... ${lines.length - maxLines} more lines`) : "";
51717
51983
  return formatted + (footer ? "\n" + footer : "");
51718
51984
  }
51719
- function wrapCommandText(text14, maxWidth = 70, indent = " ") {
51720
- if (text14.length <= maxWidth) return text14;
51985
+ function wrapCommandText(text15, maxWidth = 70, indent = " ") {
51986
+ if (text15.length <= maxWidth) return text15;
51721
51987
  const lines = [];
51722
- let remaining = text14;
51988
+ let remaining = text15;
51723
51989
  while (remaining.length > maxWidth) {
51724
51990
  let breakAt = maxWidth;
51725
51991
  const spaceIdx = remaining.lastIndexOf(" ", maxWidth);
@@ -51822,16 +52088,16 @@ function formatToolCallForConfirmation(toolCall, metadata) {
51822
52088
  const reason = input.reason ? String(input.reason) : void 0;
51823
52089
  const actionLabel = action === "allow" ? chalk.green.bold("ALLOW") : chalk.red.bold(action.toUpperCase());
51824
52090
  const scopeLabel = scope === "global" ? chalk.blue("Global (all projects)") : chalk.magenta("Project (current only)");
51825
- const patternList = patterns.map((p45) => chalk.cyan(p45)).join(", ");
52091
+ const patternList = patterns.map((p46) => chalk.cyan(p46)).join(", ");
51826
52092
  const lines = [`${actionLabel}: ${patternList}`];
51827
52093
  lines.push(`${chalk.dim(" Scope:")} ${scopeLabel}`);
51828
52094
  if (reason) {
51829
52095
  lines.push(`${chalk.dim(" Reason:")} ${reason}`);
51830
52096
  }
51831
- for (const p45 of patterns) {
51832
- lines.push(`${chalk.dim(" Risk:")} ${getRiskDescription(p45)}`);
52097
+ for (const p46 of patterns) {
52098
+ lines.push(`${chalk.dim(" Risk:")} ${getRiskDescription(p46)}`);
51833
52099
  lines.push(
51834
- `${chalk.dim(" Effect:")} ${getEffectDescription(action, p45, scope)}`
52100
+ `${chalk.dim(" Effect:")} ${getEffectDescription(action, p46, scope)}`
51835
52101
  );
51836
52102
  }
51837
52103
  description = lines.join("\n ");
@@ -52072,6 +52338,46 @@ async function confirmToolExecution(toolCall) {
52072
52338
  process.stdin.on("data", onData);
52073
52339
  });
52074
52340
  }
52341
+ async function confirmToolExecutionFallback(toolCall) {
52342
+ const { description } = formatToolCallForConfirmation(toolCall);
52343
+ const isBashExec = toolCall.name === "bash_exec";
52344
+ console.log();
52345
+ console.log(chalk.yellow(" \u26A0 Interactive tool selector unavailable. Using safe fallback."));
52346
+ const options = [
52347
+ { value: "yes", label: "yes", hint: "Allow once" },
52348
+ { value: "no", label: "no", hint: "Skip this action" },
52349
+ { value: "trust_project", label: "trust (project)", hint: "Always allow in this project" },
52350
+ { value: "trust_global", label: "trust (global)", hint: "Always allow everywhere" }
52351
+ ];
52352
+ if (isBashExec) {
52353
+ options.splice(2, 0, { value: "edit", label: "edit command", hint: "Modify before running" });
52354
+ }
52355
+ const choice = await p26.select({
52356
+ message: `Confirm tool action:
52357
+ ${description}`,
52358
+ options
52359
+ });
52360
+ if (p26.isCancel(choice)) return "abort";
52361
+ if (choice === "edit") {
52362
+ const currentCommand = String(toolCall.input.command ?? "");
52363
+ const edited = await p26.text({
52364
+ message: "Edit command:",
52365
+ placeholder: currentCommand,
52366
+ initialValue: currentCommand,
52367
+ validate: (value) => !value?.trim() ? "Command is required" : void 0
52368
+ });
52369
+ if (p26.isCancel(edited)) return "abort";
52370
+ return { type: "edit", newCommand: edited.trim() };
52371
+ }
52372
+ return choice;
52373
+ }
52374
+ async function confirmToolExecutionWithFallback(toolCall) {
52375
+ try {
52376
+ return await confirmToolExecution(toolCall);
52377
+ } catch {
52378
+ return await confirmToolExecutionFallback(toolCall);
52379
+ }
52380
+ }
52075
52381
 
52076
52382
  // src/cli/repl/parallel-executor.ts
52077
52383
  init_error_resilience();
@@ -52801,14 +53107,15 @@ ${tail}`;
52801
53107
  options.onBeforeConfirmation?.();
52802
53108
  let confirmResult;
52803
53109
  try {
52804
- confirmResult = await confirmToolExecution(toolCall);
53110
+ confirmResult = await confirmToolExecutionWithFallback(toolCall);
52805
53111
  } catch (confirmError) {
52806
53112
  options.onAfterConfirmation?.();
52807
- declinedTools.set(
52808
- toolCall.id,
53113
+ declinedTools.set(toolCall.id, "Confirmation failed");
53114
+ options.onToolSkipped?.(
53115
+ toolCall,
52809
53116
  `Confirmation failed: ${confirmError instanceof Error ? confirmError.message : String(confirmError)}`
52810
53117
  );
52811
- options.onToolSkipped?.(toolCall, "Confirmation error");
53118
+ turnAborted = true;
52812
53119
  continue;
52813
53120
  }
52814
53121
  options.onAfterConfirmation?.();
@@ -52878,29 +53185,29 @@ ${tail}`;
52878
53185
  const patterns = executed.input.patterns;
52879
53186
  const scope = executed.input.scope || "project";
52880
53187
  if (Array.isArray(patterns)) {
52881
- for (const p45 of patterns) {
53188
+ for (const p46 of patterns) {
52882
53189
  if (action === "allow") {
52883
- session.trustedTools.add(p45);
53190
+ session.trustedTools.add(p46);
52884
53191
  if (scope === "global") {
52885
- saveTrustedTool(p45, null, true).catch(() => {
53192
+ saveTrustedTool(p46, null, true).catch(() => {
52886
53193
  });
52887
53194
  } else {
52888
- saveTrustedTool(p45, session.projectPath, false).catch(() => {
53195
+ saveTrustedTool(p46, session.projectPath, false).catch(() => {
52889
53196
  });
52890
53197
  }
52891
- removeDeniedTool(p45, session.projectPath).catch(() => {
53198
+ removeDeniedTool(p46, session.projectPath).catch(() => {
52892
53199
  });
52893
53200
  } else if (action === "deny") {
52894
- session.trustedTools.delete(p45);
53201
+ session.trustedTools.delete(p46);
52895
53202
  if (scope === "global") {
52896
- removeTrustedTool(p45, session.projectPath, true).catch(() => {
53203
+ removeTrustedTool(p46, session.projectPath, true).catch(() => {
52897
53204
  });
52898
53205
  } else {
52899
- saveDeniedTool(p45, session.projectPath).catch(() => {
53206
+ saveDeniedTool(p46, session.projectPath).catch(() => {
52900
53207
  });
52901
53208
  }
52902
53209
  } else {
52903
- session.trustedTools.delete(p45);
53210
+ session.trustedTools.delete(p46);
52904
53211
  }
52905
53212
  }
52906
53213
  }
@@ -53640,44 +53947,6 @@ function createIntentRecognizer(config = {}) {
53640
53947
  // src/cli/repl/index.ts
53641
53948
  init_env();
53642
53949
  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
53950
 
53682
53951
  // src/cli/repl/status-bar.ts
53683
53952
  init_env();
@@ -53986,14 +54255,14 @@ async function startRepl(options = {}) {
53986
54255
  imageCount++;
53987
54256
  }
53988
54257
  }
53989
- const text14 = textParts.join("\n\n").trim();
53990
- if (text14.length > 0) {
54258
+ const text15 = textParts.join("\n\n").trim();
54259
+ if (text15.length > 0) {
53991
54260
  if (imageCount > 0) {
53992
- return `${text14}
54261
+ return `${text15}
53993
54262
 
53994
54263
  [System: The original request included ${imageCount} image(s). Use the image context already provided in this conversation.]`;
53995
54264
  }
53996
- return text14;
54265
+ return text15;
53997
54266
  }
53998
54267
  if (imageCount > 0) {
53999
54268
  return `[System: Retry the previous image-based user request (${imageCount} image(s)). Use the existing image context in the conversation and do not repeat the same failed action.]`;
@@ -54012,8 +54281,8 @@ async function startRepl(options = {}) {
54012
54281
  };
54013
54282
  const getAutoSwitchCandidates = (current) => {
54014
54283
  const ordered = [];
54015
- const push = (p45) => {
54016
- if (p45 !== current && !ordered.includes(p45)) ordered.push(p45);
54284
+ const push = (p46) => {
54285
+ if (p46 !== current && !ordered.includes(p46)) ordered.push(p46);
54017
54286
  };
54018
54287
  if (current === "openai") {
54019
54288
  push("codex");
@@ -54051,7 +54320,7 @@ async function startRepl(options = {}) {
54051
54320
  "lmstudio",
54052
54321
  "ollama"
54053
54322
  ];
54054
- for (const p45 of genericOrder) push(p45);
54323
+ for (const p46 of genericOrder) push(p46);
54055
54324
  return ordered;
54056
54325
  };
54057
54326
  const attemptAutoProviderSwitch = async (reason, originalMessage) => {
@@ -54782,85 +55051,7 @@ ${imagePrompts}`.trim() : imagePrompts;
54782
55051
  process.off("SIGTERM", sigtermHandler);
54783
55052
  }
54784
55053
  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();
55054
+ await renderStartupPanel(session, gitCtx, mcpManager?.getConnectedServers() ?? []);
54864
55055
  }
54865
55056
  async function checkProjectTrust(projectPath) {
54866
55057
  const trustStore = createTrustStore();