@corbat-tech/coco 2.28.2 → 2.28.4

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
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import * as fs5 from 'fs';
3
- import fs5__default, { accessSync, readFileSync, constants } from 'fs';
3
+ import fs5__default, { accessSync, readFileSync, constants as constants$1 } from 'fs';
4
4
  import * as path39 from 'path';
5
5
  import path39__default, { join, dirname, resolve, basename } from 'path';
6
6
  import { URL as URL$1, fileURLToPath } from 'url';
@@ -8,7 +8,7 @@ import { z } from 'zod';
8
8
  import * as os4 from 'os';
9
9
  import os4__default, { homedir } from 'os';
10
10
  import * as fs35 from 'fs/promises';
11
- import fs35__default, { mkdir, writeFile, readFile, access, readdir, rm } from 'fs/promises';
11
+ import fs35__default, { mkdir, writeFile, readFile, access, constants, readdir, rm } from 'fs/promises';
12
12
  import JSON5 from 'json5';
13
13
  import * as crypto from 'crypto';
14
14
  import { randomUUID, randomBytes, createHash } from 'crypto';
@@ -707,8 +707,8 @@ async function configExists(configPath, scope = "any") {
707
707
  }
708
708
  return false;
709
709
  }
710
- function getConfigValue(config, path60) {
711
- const keys = path60.split(".");
710
+ function getConfigValue(config, path62) {
711
+ const keys = path62.split(".");
712
712
  let current = config;
713
713
  for (const key of keys) {
714
714
  if (current === null || current === void 0 || typeof current !== "object") {
@@ -6980,7 +6980,7 @@ var init_fallback = __esm({
6980
6980
  */
6981
6981
  async initialize(config) {
6982
6982
  const results = await Promise.allSettled(
6983
- this.providers.map((p46) => p46.provider.initialize(config))
6983
+ this.providers.map((p47) => p47.provider.initialize(config))
6984
6984
  );
6985
6985
  const anySuccess = results.some((r) => r.status === "fulfilled");
6986
6986
  if (!anySuccess) {
@@ -7102,11 +7102,11 @@ var init_fallback = __esm({
7102
7102
  */
7103
7103
  async isAvailable() {
7104
7104
  const results = await Promise.all(
7105
- this.providers.map(async (p46) => {
7106
- if (p46.breaker.isOpen()) {
7105
+ this.providers.map(async (p47) => {
7106
+ if (p47.breaker.isOpen()) {
7107
7107
  return false;
7108
7108
  }
7109
- return p46.provider.isAvailable();
7109
+ return p47.provider.isAvailable();
7110
7110
  })
7111
7111
  );
7112
7112
  return results.some((available) => available);
@@ -7137,10 +7137,10 @@ var init_fallback = __esm({
7137
7137
  * @returns Array of provider status objects
7138
7138
  */
7139
7139
  getCircuitStatus() {
7140
- return this.providers.map((p46) => ({
7141
- providerId: p46.provider.id,
7142
- state: p46.breaker.getState(),
7143
- failureCount: p46.breaker.getFailureCount()
7140
+ return this.providers.map((p47) => ({
7141
+ providerId: p47.provider.id,
7142
+ state: p47.breaker.getState(),
7143
+ failureCount: p47.breaker.getFailureCount()
7144
7144
  }));
7145
7145
  }
7146
7146
  /**
@@ -7150,8 +7150,8 @@ var init_fallback = __esm({
7150
7150
  * previously failing providers to be tried again.
7151
7151
  */
7152
7152
  resetCircuits() {
7153
- for (const p46 of this.providers) {
7154
- p46.breaker.reset();
7153
+ for (const p47 of this.providers) {
7154
+ p47.breaker.reset();
7155
7155
  }
7156
7156
  }
7157
7157
  /**
@@ -7726,9 +7726,9 @@ async function migrateGlobalConfig(oldDir, newConfigPath) {
7726
7726
  );
7727
7727
  }
7728
7728
  }
7729
- async function fileExists(path60) {
7729
+ async function fileExists(path62) {
7730
7730
  try {
7731
- await access(path60);
7731
+ await access(path62);
7732
7732
  return true;
7733
7733
  } catch {
7734
7734
  return false;
@@ -8111,8 +8111,8 @@ var init_registry = __esm({
8111
8111
  /**
8112
8112
  * Ensure directory exists
8113
8113
  */
8114
- async ensureDir(path60) {
8115
- await mkdir(dirname(path60), { recursive: true });
8114
+ async ensureDir(path62) {
8115
+ await mkdir(dirname(path62), { recursive: true });
8116
8116
  }
8117
8117
  };
8118
8118
  }
@@ -9749,9 +9749,9 @@ function createEmptyMemoryContext() {
9749
9749
  errors: []
9750
9750
  };
9751
9751
  }
9752
- function createMissingMemoryFile(path60, level) {
9752
+ function createMissingMemoryFile(path62, level) {
9753
9753
  return {
9754
- path: path60,
9754
+ path: path62,
9755
9755
  level,
9756
9756
  content: "",
9757
9757
  sections: [],
@@ -10217,6 +10217,9 @@ After outputting the plan, the user will decide to approve, edit, or reject it.
10217
10217
  if (subcommand === "status") {
10218
10218
  if (session.planMode) {
10219
10219
  p26.log.info(chalk.cyan("Plan mode is ACTIVE (read-only tools only)"));
10220
+ if (session.config.agent.planModeStrict) {
10221
+ p26.log.info("Strict plan mode is ON. Only the strict read-only allowlist is available.");
10222
+ }
10220
10223
  if (session.pendingPlan) {
10221
10224
  p26.log.info("A plan is pending approval. Use /plan approve or /plan reject.");
10222
10225
  }
@@ -10271,6 +10274,9 @@ ${plan}`
10271
10274
  const instruction = args.join(" ");
10272
10275
  p26.log.success(chalk.cyan("Plan mode ACTIVATED"));
10273
10276
  p26.log.info("Agent will explore the codebase and create a plan.");
10277
+ if (session.config.agent.planModeStrict) {
10278
+ p26.log.info("Strict plan mode is ON. Execution tools remain blocked until approval.");
10279
+ }
10274
10280
  p26.log.info(chalk.dim(`Task: ${instruction}`));
10275
10281
  p26.log.info("After the plan is generated, use /plan approve or /plan reject.");
10276
10282
  session.messages.push({
@@ -10357,10 +10363,27 @@ async function createDefaultReplConfig() {
10357
10363
  systemPrompt: COCO_SYSTEM_PROMPT,
10358
10364
  maxToolIterations: 25,
10359
10365
  confirmDestructive: true,
10360
- enableAutoSwitchProvider: false
10366
+ enableAutoSwitchProvider: false,
10367
+ recoveryV2: readBooleanFlag("COCO_AGENT_RECOVERY_V2", true),
10368
+ planModeStrict: readBooleanFlag("COCO_AGENT_PLAN_MODE_STRICT", true),
10369
+ doctorV2: readBooleanFlag("COCO_AGENT_DOCTOR_V2", true),
10370
+ outputOffload: readBooleanFlag("COCO_AGENT_OUTPUT_OFFLOAD", false)
10361
10371
  }
10362
10372
  };
10363
10373
  }
10374
+ function readBooleanFlag(envName, defaultValue) {
10375
+ const value = process.env[envName]?.trim().toLowerCase();
10376
+ if (value === void 0) {
10377
+ return defaultValue;
10378
+ }
10379
+ if (value === "1" || value === "true" || value === "yes" || value === "on") {
10380
+ return true;
10381
+ }
10382
+ if (value === "0" || value === "false" || value === "no" || value === "off") {
10383
+ return false;
10384
+ }
10385
+ return defaultValue;
10386
+ }
10364
10387
  async function createSession(projectPath, config) {
10365
10388
  const defaultConfig = await createDefaultReplConfig();
10366
10389
  return {
@@ -11234,8 +11257,8 @@ __export(trust_store_exports, {
11234
11257
  saveTrustStore: () => saveTrustStore,
11235
11258
  updateLastAccessed: () => updateLastAccessed
11236
11259
  });
11237
- async function ensureDir(path60) {
11238
- await mkdir(dirname(path60), { recursive: true });
11260
+ async function ensureDir(path62) {
11261
+ await mkdir(dirname(path62), { recursive: true });
11239
11262
  }
11240
11263
  async function loadTrustStore(storePath = TRUST_STORE_PATH) {
11241
11264
  try {
@@ -11320,8 +11343,8 @@ function canPerformOperation(store, projectPath, operation) {
11320
11343
  };
11321
11344
  return permissions[level]?.includes(operation) ?? false;
11322
11345
  }
11323
- function normalizePath(path60) {
11324
- return join(path60);
11346
+ function normalizePath(path62) {
11347
+ return join(path62);
11325
11348
  }
11326
11349
  function createTrustStore(storePath = TRUST_STORE_PATH) {
11327
11350
  let store = null;
@@ -11677,12 +11700,12 @@ function humanizeError(message, toolName) {
11677
11700
  return msg;
11678
11701
  }
11679
11702
  if (/ENOENT/i.test(msg)) {
11680
- const path60 = extractQuotedPath(msg);
11681
- return path60 ? `File or directory not found: ${path60}` : "File or directory not found";
11703
+ const path62 = extractQuotedPath(msg);
11704
+ return path62 ? `File or directory not found: ${path62}` : "File or directory not found";
11682
11705
  }
11683
11706
  if (/EACCES/i.test(msg)) {
11684
- const path60 = extractQuotedPath(msg);
11685
- return path60 ? `Permission denied: ${path60}` : "Permission denied \u2014 check file permissions";
11707
+ const path62 = extractQuotedPath(msg);
11708
+ return path62 ? `Permission denied: ${path62}` : "Permission denied \u2014 check file permissions";
11686
11709
  }
11687
11710
  if (/EISDIR/i.test(msg)) {
11688
11711
  return "Expected a file but found a directory at the specified path";
@@ -11774,7 +11797,7 @@ function humanizeError(message, toolName) {
11774
11797
  return msg;
11775
11798
  }
11776
11799
  function looksLikeTechnicalJargon(message) {
11777
- return JARGON_PATTERNS.some((p46) => p46.test(message));
11800
+ return JARGON_PATTERNS.some((p47) => p47.test(message));
11778
11801
  }
11779
11802
  async function humanizeWithLLM(errorMessage, toolName, provider) {
11780
11803
  const prompt = [
@@ -12535,9 +12558,9 @@ function renderFileBlock(file, opts) {
12535
12558
  );
12536
12559
  }
12537
12560
  const pairs = pairAdjacentLines(hunk.lines);
12538
- const pairedDeleteIndices = new Set(pairs.map((p46) => p46.deleteIdx));
12539
- const pairedAddIndices = new Set(pairs.map((p46) => p46.addIdx));
12540
- const pairByAdd = new Map(pairs.map((p46) => [p46.addIdx, p46.deleteIdx]));
12561
+ const pairedDeleteIndices = new Set(pairs.map((p47) => p47.deleteIdx));
12562
+ const pairedAddIndices = new Set(pairs.map((p47) => p47.addIdx));
12563
+ const pairByAdd = new Map(pairs.map((p47) => [p47.addIdx, p47.deleteIdx]));
12541
12564
  const wordHighlights = /* @__PURE__ */ new Map();
12542
12565
  for (const pair of pairs) {
12543
12566
  const delLine = hunk.lines[pair.deleteIdx];
@@ -12616,9 +12639,9 @@ var init_diff_renderer = __esm({
12616
12639
  getTerminalWidth = () => process.stdout.columns || 80;
12617
12640
  }
12618
12641
  });
12619
- async function fileExists4(path60) {
12642
+ async function fileExists4(path62) {
12620
12643
  try {
12621
- await access(path60);
12644
+ await access(path62);
12622
12645
  return true;
12623
12646
  } catch {
12624
12647
  return false;
@@ -12816,13 +12839,13 @@ var init_subprocess_registry = __esm({
12816
12839
  });
12817
12840
  async function detectTestFramework(projectPath) {
12818
12841
  try {
12819
- await access(join(projectPath, "pom.xml"), constants.R_OK);
12842
+ await access(join(projectPath, "pom.xml"), constants$1.R_OK);
12820
12843
  return "maven";
12821
12844
  } catch {
12822
12845
  }
12823
12846
  for (const f of ["build.gradle", "build.gradle.kts"]) {
12824
12847
  try {
12825
- await access(join(projectPath, f), constants.R_OK);
12848
+ await access(join(projectPath, f), constants$1.R_OK);
12826
12849
  return "gradle";
12827
12850
  } catch {
12828
12851
  }
@@ -12980,7 +13003,7 @@ var init_coverage = __esm({
12980
13003
  ] : [join(this.projectPath, "build", "reports", "jacoco", "test", "jacocoTestReport.csv")];
12981
13004
  for (const csvPath of jacocoPaths) {
12982
13005
  try {
12983
- await access(csvPath, constants.R_OK);
13006
+ await access(csvPath, constants$1.R_OK);
12984
13007
  const csv = await readFile(csvPath, "utf-8");
12985
13008
  return parseJacocoCsv(csv);
12986
13009
  } catch {
@@ -12993,10 +13016,10 @@ var init_coverage = __esm({
12993
13016
  join(this.projectPath, ".coverage", "coverage-summary.json"),
12994
13017
  join(this.projectPath, "coverage", "lcov-report", "coverage-summary.json")
12995
13018
  ];
12996
- for (const p46 of possiblePaths) {
13019
+ for (const p47 of possiblePaths) {
12997
13020
  try {
12998
- await access(p46, constants.R_OK);
12999
- const content = await readFile(p46, "utf-8");
13021
+ await access(p47, constants$1.R_OK);
13022
+ const content = await readFile(p47, "utf-8");
13000
13023
  const report = JSON.parse(content);
13001
13024
  return parseCoverageSummary(report);
13002
13025
  } catch {
@@ -14198,7 +14221,7 @@ var init_completeness = __esm({
14198
14221
  ];
14199
14222
  for (const entry of entryPoints) {
14200
14223
  try {
14201
- await access(join(this.projectPath, entry), constants.R_OK);
14224
+ await access(join(this.projectPath, entry), constants$1.R_OK);
14202
14225
  return true;
14203
14226
  } catch {
14204
14227
  }
@@ -14691,7 +14714,7 @@ var init_documentation = __esm({
14691
14714
  }
14692
14715
  async fileExists(relativePath) {
14693
14716
  try {
14694
- await access(join(this.projectPath, relativePath), constants.R_OK);
14717
+ await access(join(this.projectPath, relativePath), constants$1.R_OK);
14695
14718
  return true;
14696
14719
  } catch {
14697
14720
  return false;
@@ -16998,16 +17021,16 @@ var init_evaluator = __esm({
16998
17021
  * Find source files in project, adapting to the detected language stack.
16999
17022
  */
17000
17023
  async findSourceFiles() {
17001
- const { access: access17 } = await import('fs/promises');
17024
+ const { access: access18 } = await import('fs/promises');
17002
17025
  const { join: join26 } = await import('path');
17003
17026
  let isJava = false;
17004
17027
  try {
17005
- await access17(join26(this.projectPath, "pom.xml"));
17028
+ await access18(join26(this.projectPath, "pom.xml"));
17006
17029
  isJava = true;
17007
17030
  } catch {
17008
17031
  for (const f of ["build.gradle", "build.gradle.kts"]) {
17009
17032
  try {
17010
- await access17(join26(this.projectPath, f));
17033
+ await access18(join26(this.projectPath, f));
17011
17034
  isJava = true;
17012
17035
  break;
17013
17036
  } catch {
@@ -17934,13 +17957,13 @@ function buildReviewMarkdown(result) {
17934
17957
  const { summary, required, suggestions, maturity } = result;
17935
17958
  const lines = [];
17936
17959
  lines.push("## Code Review\n");
17937
- const statusIcon = summary.status === "approved" ? "Approved" : "Needs Work";
17960
+ const statusIcon2 = summary.status === "approved" ? "Approved" : "Needs Work";
17938
17961
  lines.push(`**Branch:** \`${summary.branch}\` \u2192 \`${summary.baseBranch}\``);
17939
17962
  lines.push(
17940
17963
  `**Files:** ${summary.filesChanged} changed | +${summary.additions} -${summary.deletions}`
17941
17964
  );
17942
17965
  lines.push(`**Maturity:** ${maturity}`);
17943
- lines.push(`**Status:** ${statusIcon}`);
17966
+ lines.push(`**Status:** ${statusIcon2}`);
17944
17967
  lines.push("");
17945
17968
  if (required.length > 0) {
17946
17969
  lines.push(`### Required (${required.length})
@@ -18366,9 +18389,9 @@ Examples:
18366
18389
  if (file) {
18367
18390
  options.file = file;
18368
18391
  }
18369
- const log38 = await git.log(options);
18392
+ const log39 = await git.log(options);
18370
18393
  return {
18371
- commits: log38.all.map((commit) => ({
18394
+ commits: log39.all.map((commit) => ({
18372
18395
  hash: commit.hash,
18373
18396
  message: commit.message,
18374
18397
  author: commit.author_name,
@@ -20031,18 +20054,18 @@ async function runVersion(ctx) {
20031
20054
  });
20032
20055
  const tag = lastTag.stdout.trim();
20033
20056
  if (tag) {
20034
- const log38 = await bashExecTool.execute({
20057
+ const log39 = await bashExecTool.execute({
20035
20058
  command: `git log ${tag}..HEAD --oneline`,
20036
20059
  cwd: ctx.cwd
20037
20060
  });
20038
- commitMessages = log38.stdout.trim().split("\n").filter(Boolean);
20061
+ commitMessages = log39.stdout.trim().split("\n").filter(Boolean);
20039
20062
  } else {
20040
- const log38 = await gitLogTool.execute({ cwd: ctx.cwd, maxCount: 20 });
20041
- commitMessages = log38.commits.map((c) => c.message);
20063
+ const log39 = await gitLogTool.execute({ cwd: ctx.cwd, maxCount: 20 });
20064
+ commitMessages = log39.commits.map((c) => c.message);
20042
20065
  }
20043
20066
  } catch {
20044
- const log38 = await gitLogTool.execute({ cwd: ctx.cwd, maxCount: 10 });
20045
- commitMessages = log38.commits.map((c) => c.message);
20067
+ const log39 = await gitLogTool.execute({ cwd: ctx.cwd, maxCount: 10 });
20068
+ commitMessages = log39.commits.map((c) => c.message);
20046
20069
  }
20047
20070
  detectedBump = detectBumpFromCommits(commitMessages);
20048
20071
  }
@@ -20080,8 +20103,8 @@ async function runVersion(ctx) {
20080
20103
  p26.log.success(`Updated ${versionFile.path}: ${currentVersion} \u2192 ${finalVersion}`);
20081
20104
  if (ctx.profile.changelog && !ctx.options.noChangelog) {
20082
20105
  try {
20083
- const log38 = await gitLogTool.execute({ cwd: ctx.cwd, maxCount: 30 });
20084
- const entries = generateChangelogEntries(log38.commits.map((c) => c.message));
20106
+ const log39 = await gitLogTool.execute({ cwd: ctx.cwd, maxCount: 30 });
20107
+ const entries = generateChangelogEntries(log39.commits.map((c) => c.message));
20085
20108
  if (entries.length > 0) {
20086
20109
  await insertChangelogEntry(ctx.cwd, ctx.profile.changelog, finalVersion, entries);
20087
20110
  p26.log.success(`Updated ${ctx.profile.changelog.path} with ${entries.length} entries`);
@@ -20799,11 +20822,11 @@ function isBlockedPath(absolute) {
20799
20822
  return void 0;
20800
20823
  }
20801
20824
  function isBlockedExecFile(filePath) {
20802
- return BLOCKED_EXEC_PATTERNS.some((p46) => p46.test(filePath));
20825
+ return BLOCKED_EXEC_PATTERNS.some((p47) => p47.test(filePath));
20803
20826
  }
20804
20827
  function hasDangerousArgs(args) {
20805
20828
  const joined = args.join(" ");
20806
- return DANGEROUS_ARG_PATTERNS.some((p46) => p46.test(joined));
20829
+ return DANGEROUS_ARG_PATTERNS.some((p47) => p47.test(joined));
20807
20830
  }
20808
20831
  function getInterpreter(ext) {
20809
20832
  return INTERPRETER_MAP[ext.toLowerCase()];
@@ -23311,413 +23334,6 @@ var init_allow_path_prompt = __esm({
23311
23334
  }
23312
23335
  });
23313
23336
 
23314
- // src/cli/repl/interruptions/types.ts
23315
- var init_types7 = __esm({
23316
- "src/cli/repl/interruptions/types.ts"() {
23317
- }
23318
- });
23319
-
23320
- // src/cli/repl/interruptions/classifier.ts
23321
- function matchPatterns(text15, patterns) {
23322
- for (let i = 0; i < patterns.length; i++) {
23323
- if (patterns[i].test(text15)) {
23324
- return 1 - i * 0.1;
23325
- }
23326
- }
23327
- return 0;
23328
- }
23329
- function classifyInterruption(message) {
23330
- const text15 = message.text;
23331
- const abortConf = matchPatterns(text15, ABORT_PATTERNS);
23332
- const modifyConf = matchPatterns(text15, MODIFY_PATTERNS);
23333
- const correctConf = matchPatterns(text15, CORRECT_PATTERNS);
23334
- if (abortConf > 0 && abortConf >= modifyConf && abortConf >= correctConf) {
23335
- return {
23336
- text: text15,
23337
- type: "abort" /* Abort */,
23338
- confidence: Math.min(1, abortConf),
23339
- timestamp: message.timestamp
23340
- };
23341
- }
23342
- if (modifyConf > 0 && modifyConf >= correctConf) {
23343
- return {
23344
- text: text15,
23345
- type: "modify" /* Modify */,
23346
- confidence: Math.min(1, modifyConf),
23347
- timestamp: message.timestamp
23348
- };
23349
- }
23350
- if (correctConf > 0) {
23351
- return {
23352
- text: text15,
23353
- type: "correct" /* Correct */,
23354
- confidence: Math.min(1, correctConf),
23355
- timestamp: message.timestamp
23356
- };
23357
- }
23358
- return {
23359
- text: text15,
23360
- type: "info" /* Info */,
23361
- confidence: 0.5,
23362
- timestamp: message.timestamp
23363
- };
23364
- }
23365
- var ABORT_PATTERNS, MODIFY_PATTERNS, CORRECT_PATTERNS;
23366
- var init_classifier = __esm({
23367
- "src/cli/repl/interruptions/classifier.ts"() {
23368
- init_types7();
23369
- ABORT_PATTERNS = [
23370
- /^(para|stop|cancel|abort|quit|exit|detente|basta)$/i,
23371
- /^(para\s+ya|stop\s+it|cancel\s+that)$/i,
23372
- /\b(para|stop|cancel|abort)\b/i
23373
- ];
23374
- MODIFY_PATTERNS = [
23375
- /^(a[ñn]ade|add|incluye|include|pon|put|agrega)\b/i,
23376
- /^(cambia|change|modifica|modify|usa|use|haz|make)\b/i,
23377
- /^no[,.]?\s+/i,
23378
- // "no, mejor de la griega" — negation signals redirection
23379
- /^(prefiero|prefer|quiero|i\s+want)\b/i,
23380
- // "prefiero en gijon" — preference signals redirection
23381
- /\b(a[ñn]ade|add|incluye|include)\s+/i,
23382
- /\b(cambia|change|modifica|modify)\s+/i,
23383
- /\b(en\s+vez\s+de|instead\s+of|rather\s+than)\b/i,
23384
- /\b(prefiero|prefer|mejor|better|más|more|less|menos|bigger|smaller|larger)\b/i,
23385
- /\b(también|also|además|additionally)\b/i
23386
- ];
23387
- CORRECT_PATTERNS = [
23388
- /^(error|bug|fallo|wrong|mal|incorrect)\b/i,
23389
- /^(arregla|fix|corrige|correct|repara|repair)\b/i,
23390
- /\b(error\s+en|bug\s+in|fallo\s+en)\b/i,
23391
- /\b(está\s+mal|is\s+wrong|no\s+funciona|doesn'?t\s+work)\b/i,
23392
- /\b(arregla|fix|corrige|correct)\s+/i
23393
- ];
23394
- }
23395
- });
23396
- function mapClassificationToAction(type) {
23397
- switch (type) {
23398
- case "abort" /* Abort */:
23399
- return "abort" /* Abort */;
23400
- // Explicit modification/correction keywords → pre-select Modify
23401
- case "modify" /* Modify */:
23402
- case "correct" /* Correct */:
23403
- return "modify" /* Modify */;
23404
- // Info or unclassified → pre-select Queue (likely a new topic/task)
23405
- // The user can always switch to Modify via the menu if needed
23406
- case "info" /* Info */:
23407
- default:
23408
- return "queue" /* Queue */;
23409
- }
23410
- }
23411
- var init_action_selector = __esm({
23412
- "src/cli/repl/input/action-selector.ts"() {
23413
- init_types7();
23414
- }
23415
- });
23416
-
23417
- // src/cli/repl/interruptions/llm-classifier.ts
23418
- var llm_classifier_exports = {};
23419
- __export(llm_classifier_exports, {
23420
- createLLMClassifier: () => createLLMClassifier
23421
- });
23422
- function buildClassificationPrompt(userMessage, currentTask) {
23423
- if (currentTask) {
23424
- return `Current task: "${currentTask}"
23425
- User's new message: "${userMessage}"`;
23426
- }
23427
- return `User's new message: "${userMessage}"`;
23428
- }
23429
- function parseResponse(response) {
23430
- const normalized = response.trim().toUpperCase();
23431
- if (normalized.includes("STEER")) return "steer" /* Steer */;
23432
- if (normalized.includes("MODIFY")) return "modify" /* Modify */;
23433
- if (normalized.includes("QUEUE")) return "queue" /* Queue */;
23434
- if (normalized.includes("ABORT")) return "abort" /* Abort */;
23435
- return null;
23436
- }
23437
- function classifyWithKeywords(message) {
23438
- const classified = classifyInterruption(message);
23439
- return mapClassificationToAction(classified.type);
23440
- }
23441
- function createLLMClassifier(provider, config) {
23442
- const cfg = { ...DEFAULT_CONFIG6, ...config };
23443
- return {
23444
- /**
23445
- * Classify a user message captured during agent execution.
23446
- *
23447
- * Makes a fast parallel LLM call. If the LLM doesn't respond within
23448
- * the timeout, falls back to keyword-based classification.
23449
- *
23450
- * @param message - The captured message
23451
- * @param currentTask - The original task the agent is working on (for context)
23452
- * @returns Classification result with action and source
23453
- */
23454
- async classify(message, currentTask) {
23455
- const llmPromise = classifyWithLLM(provider, message, currentTask, cfg);
23456
- let timeoutId;
23457
- const timeoutPromise = new Promise((resolve4) => {
23458
- timeoutId = setTimeout(() => resolve4(null), cfg.timeoutMs);
23459
- });
23460
- try {
23461
- const llmResult = await Promise.race([llmPromise, timeoutPromise]);
23462
- if (llmResult !== null) {
23463
- return { action: llmResult, source: "llm" };
23464
- }
23465
- return {
23466
- action: classifyWithKeywords(message),
23467
- source: "keywords"
23468
- };
23469
- } finally {
23470
- clearTimeout(timeoutId);
23471
- }
23472
- }
23473
- };
23474
- }
23475
- async function classifyWithLLM(provider, message, currentTask, cfg) {
23476
- try {
23477
- const userPrompt = buildClassificationPrompt(message.text, currentTask);
23478
- const options = {
23479
- maxTokens: cfg.maxTokens,
23480
- temperature: cfg.temperature,
23481
- system: CLASSIFICATION_SYSTEM_PROMPT2,
23482
- timeout: cfg.timeoutMs
23483
- };
23484
- const response = await provider.chat([{ role: "user", content: userPrompt }], options);
23485
- return parseResponse(response.content);
23486
- } catch {
23487
- return null;
23488
- }
23489
- }
23490
- var DEFAULT_CONFIG6, CLASSIFICATION_SYSTEM_PROMPT2;
23491
- var init_llm_classifier = __esm({
23492
- "src/cli/repl/interruptions/llm-classifier.ts"() {
23493
- init_types7();
23494
- init_classifier();
23495
- init_action_selector();
23496
- DEFAULT_CONFIG6 = {
23497
- timeoutMs: 8e3,
23498
- maxTokens: 10,
23499
- temperature: 0
23500
- };
23501
- CLASSIFICATION_SYSTEM_PROMPT2 = `You are a message classifier for a coding assistant. The user sent a message while the assistant was working on a task.
23502
-
23503
- Classify the message into exactly ONE category. Reply with ONLY the category word, nothing else.
23504
-
23505
- Categories:
23506
- - STEER: The message provides a hint, preference, or minor adjustment to the CURRENT task without requiring a restart (e.g. "also add a test", "use camelCase", "make sure to handle errors", "btw the file is in src/", "prefer async/await")
23507
- - MODIFY: The message fundamentally changes the CURRENT task requiring a fresh start (e.g. "actually use Python instead of JS", "no, do it completely differently", "start over with a new approach")
23508
- - QUEUE: The message is a NEW, DIFFERENT task unrelated to what's being done (e.g. "what's 2+2", "tell me the weather", "create another file for X")
23509
- - ABORT: The message asks to stop/cancel the current work (e.g. "stop", "cancel", "para", "never mind")
23510
-
23511
- Reply with exactly one word: STEER, MODIFY, QUEUE, or ABORT`;
23512
- }
23513
- });
23514
-
23515
- // src/cli/repl/context/stack-detector.ts
23516
- var stack_detector_exports = {};
23517
- __export(stack_detector_exports, {
23518
- detectProjectStack: () => detectProjectStack
23519
- });
23520
- async function detectStack2(cwd) {
23521
- if (await fileExists3(path39__default.join(cwd, "package.json"))) return "node";
23522
- if (await fileExists3(path39__default.join(cwd, "Cargo.toml"))) return "rust";
23523
- if (await fileExists3(path39__default.join(cwd, "pyproject.toml"))) return "python";
23524
- if (await fileExists3(path39__default.join(cwd, "go.mod"))) return "go";
23525
- if (await fileExists3(path39__default.join(cwd, "pom.xml"))) return "java";
23526
- if (await fileExists3(path39__default.join(cwd, "build.gradle"))) return "java";
23527
- if (await fileExists3(path39__default.join(cwd, "build.gradle.kts"))) return "java";
23528
- return "unknown";
23529
- }
23530
- async function detectPackageManager3(cwd, stack) {
23531
- if (stack === "rust") return "cargo";
23532
- if (stack === "python") return "pip";
23533
- if (stack === "go") return "go";
23534
- if (stack === "java") {
23535
- if (await fileExists3(path39__default.join(cwd, "build.gradle")) || await fileExists3(path39__default.join(cwd, "build.gradle.kts"))) {
23536
- return "gradle";
23537
- }
23538
- if (await fileExists3(path39__default.join(cwd, "pom.xml"))) {
23539
- return "maven";
23540
- }
23541
- }
23542
- if (stack === "node") {
23543
- if (await fileExists3(path39__default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
23544
- if (await fileExists3(path39__default.join(cwd, "yarn.lock"))) return "yarn";
23545
- if (await fileExists3(path39__default.join(cwd, "bun.lockb"))) return "bun";
23546
- return "npm";
23547
- }
23548
- return null;
23549
- }
23550
- async function parsePackageJson(cwd) {
23551
- const packageJsonPath = path39__default.join(cwd, "package.json");
23552
- try {
23553
- const content = await fs35__default.readFile(packageJsonPath, "utf-8");
23554
- const pkg = JSON.parse(content);
23555
- const allDeps = {
23556
- ...pkg.dependencies,
23557
- ...pkg.devDependencies
23558
- };
23559
- const frameworks = [];
23560
- if (allDeps.react) frameworks.push("React");
23561
- if (allDeps.vue) frameworks.push("Vue");
23562
- if (allDeps["@angular/core"]) frameworks.push("Angular");
23563
- if (allDeps.next) frameworks.push("Next.js");
23564
- if (allDeps.nuxt) frameworks.push("Nuxt");
23565
- if (allDeps.express) frameworks.push("Express");
23566
- if (allDeps.fastify) frameworks.push("Fastify");
23567
- if (allDeps.nestjs || allDeps["@nestjs/core"]) frameworks.push("NestJS");
23568
- const buildTools2 = [];
23569
- if (allDeps.webpack) buildTools2.push("webpack");
23570
- if (allDeps.vite) buildTools2.push("vite");
23571
- if (allDeps.rollup) buildTools2.push("rollup");
23572
- if (allDeps.tsup) buildTools2.push("tsup");
23573
- if (allDeps.esbuild) buildTools2.push("esbuild");
23574
- if (pkg.scripts?.build) buildTools2.push("build");
23575
- const testingFrameworks = [];
23576
- if (allDeps.vitest) testingFrameworks.push("vitest");
23577
- if (allDeps.jest) testingFrameworks.push("jest");
23578
- if (allDeps.mocha) testingFrameworks.push("mocha");
23579
- if (allDeps.chai) testingFrameworks.push("chai");
23580
- if (allDeps["@playwright/test"]) testingFrameworks.push("playwright");
23581
- if (allDeps.cypress) testingFrameworks.push("cypress");
23582
- const languages = ["JavaScript"];
23583
- if (allDeps.typescript || await fileExists3(path39__default.join(cwd, "tsconfig.json"))) {
23584
- languages.push("TypeScript");
23585
- }
23586
- return {
23587
- dependencies: allDeps,
23588
- frameworks,
23589
- buildTools: buildTools2,
23590
- testingFrameworks,
23591
- languages
23592
- };
23593
- } catch {
23594
- return {
23595
- dependencies: {},
23596
- frameworks: [],
23597
- buildTools: [],
23598
- testingFrameworks: [],
23599
- languages: []
23600
- };
23601
- }
23602
- }
23603
- async function parsePomXml(cwd) {
23604
- const pomPath = path39__default.join(cwd, "pom.xml");
23605
- try {
23606
- const content = await fs35__default.readFile(pomPath, "utf-8");
23607
- const dependencies = {};
23608
- const frameworks = [];
23609
- const buildTools2 = ["maven"];
23610
- const testingFrameworks = [];
23611
- const depRegex = /<groupId>([^<]+)<\/groupId>\s*<artifactId>([^<]+)<\/artifactId>/g;
23612
- let match;
23613
- while ((match = depRegex.exec(content)) !== null) {
23614
- const groupId = match[1];
23615
- const artifactId = match[2];
23616
- if (!groupId || !artifactId) continue;
23617
- const fullName = `${groupId}:${artifactId}`;
23618
- dependencies[fullName] = "unknown";
23619
- if (artifactId.includes("spring-boot")) {
23620
- if (!frameworks.includes("Spring Boot")) frameworks.push("Spring Boot");
23621
- }
23622
- if (artifactId.includes("spring-webmvc") || artifactId.includes("spring-web")) {
23623
- if (!frameworks.includes("Spring MVC")) frameworks.push("Spring MVC");
23624
- }
23625
- if (artifactId.includes("hibernate")) {
23626
- if (!frameworks.includes("Hibernate")) frameworks.push("Hibernate");
23627
- }
23628
- if (artifactId === "junit-jupiter" || artifactId === "junit") {
23629
- if (!testingFrameworks.includes("JUnit")) testingFrameworks.push("JUnit");
23630
- }
23631
- if (artifactId === "mockito-core") {
23632
- if (!testingFrameworks.includes("Mockito")) testingFrameworks.push("Mockito");
23633
- }
23634
- }
23635
- return { dependencies, frameworks, buildTools: buildTools2, testingFrameworks };
23636
- } catch {
23637
- return { dependencies: {}, frameworks: [], buildTools: ["maven"], testingFrameworks: [] };
23638
- }
23639
- }
23640
- async function parsePyprojectToml(cwd) {
23641
- const pyprojectPath = path39__default.join(cwd, "pyproject.toml");
23642
- try {
23643
- const content = await fs35__default.readFile(pyprojectPath, "utf-8");
23644
- const dependencies = {};
23645
- const frameworks = [];
23646
- const buildTools2 = ["pip"];
23647
- const testingFrameworks = [];
23648
- const lines = content.split("\n");
23649
- for (const line of lines) {
23650
- const trimmed = line.trim();
23651
- if (trimmed.match(/^["']?[\w-]+["']?\s*=\s*["'][\^~>=<]+[\d.]+["']/)) {
23652
- const depMatch = trimmed.match(/^["']?([\w-]+)["']?\s*=\s*["']([\^~>=<]+[\d.]+)["']/);
23653
- if (depMatch && depMatch[1] && depMatch[2]) {
23654
- dependencies[depMatch[1]] = depMatch[2];
23655
- }
23656
- }
23657
- if (trimmed.includes("fastapi")) frameworks.push("FastAPI");
23658
- if (trimmed.includes("django")) frameworks.push("Django");
23659
- if (trimmed.includes("flask")) frameworks.push("Flask");
23660
- if (trimmed.includes("pytest")) testingFrameworks.push("pytest");
23661
- if (trimmed.includes("unittest")) testingFrameworks.push("unittest");
23662
- }
23663
- return { dependencies, frameworks, buildTools: buildTools2, testingFrameworks };
23664
- } catch {
23665
- return { dependencies: {}, frameworks: [], buildTools: ["pip"], testingFrameworks: [] };
23666
- }
23667
- }
23668
- async function detectProjectStack(cwd) {
23669
- const stack = await detectStack2(cwd);
23670
- const packageManager = await detectPackageManager3(cwd, stack);
23671
- let dependencies = {};
23672
- let frameworks = [];
23673
- let buildTools2 = [];
23674
- let testingFrameworks = [];
23675
- let languages = [];
23676
- if (stack === "node") {
23677
- const parsed = await parsePackageJson(cwd);
23678
- dependencies = parsed.dependencies;
23679
- frameworks = parsed.frameworks;
23680
- buildTools2 = parsed.buildTools;
23681
- testingFrameworks = parsed.testingFrameworks;
23682
- languages = parsed.languages;
23683
- } else if (stack === "java") {
23684
- const isGradle = await fileExists3(path39__default.join(cwd, "build.gradle")) || await fileExists3(path39__default.join(cwd, "build.gradle.kts"));
23685
- const parsed = isGradle ? { dependencies: {}, frameworks: [], buildTools: ["gradle"], testingFrameworks: ["JUnit"] } : await parsePomXml(cwd);
23686
- dependencies = parsed.dependencies;
23687
- frameworks = parsed.frameworks;
23688
- buildTools2 = parsed.buildTools;
23689
- testingFrameworks = parsed.testingFrameworks;
23690
- languages = ["Java"];
23691
- } else if (stack === "python") {
23692
- const parsed = await parsePyprojectToml(cwd);
23693
- dependencies = parsed.dependencies;
23694
- frameworks = parsed.frameworks;
23695
- buildTools2 = parsed.buildTools;
23696
- testingFrameworks = parsed.testingFrameworks;
23697
- languages = ["Python"];
23698
- } else if (stack === "go") {
23699
- languages = ["Go"];
23700
- buildTools2 = ["go"];
23701
- } else if (stack === "rust") {
23702
- languages = ["Rust"];
23703
- buildTools2 = ["cargo"];
23704
- }
23705
- return {
23706
- stack,
23707
- packageManager,
23708
- dependencies,
23709
- frameworks,
23710
- buildTools: buildTools2,
23711
- testingFrameworks,
23712
- languages
23713
- };
23714
- }
23715
- var init_stack_detector = __esm({
23716
- "src/cli/repl/context/stack-detector.ts"() {
23717
- init_files();
23718
- }
23719
- });
23720
-
23721
23337
  // src/cli/repl/hooks/types.ts
23722
23338
  function isHookEvent(value) {
23723
23339
  return typeof value === "string" && HOOK_EVENTS.includes(value);
@@ -23729,7 +23345,7 @@ function isHookAction(value) {
23729
23345
  return value === "allow" || value === "deny" || value === "modify";
23730
23346
  }
23731
23347
  var HOOK_EVENTS;
23732
- var init_types8 = __esm({
23348
+ var init_types7 = __esm({
23733
23349
  "src/cli/repl/hooks/types.ts"() {
23734
23350
  HOOK_EVENTS = [
23735
23351
  "PreToolUse",
@@ -23748,7 +23364,7 @@ function createHookRegistry() {
23748
23364
  var HookRegistry;
23749
23365
  var init_registry5 = __esm({
23750
23366
  "src/cli/repl/hooks/registry.ts"() {
23751
- init_types8();
23367
+ init_types7();
23752
23368
  HookRegistry = class {
23753
23369
  /** Hooks indexed by event type for O(1) lookup */
23754
23370
  hooksByEvent;
@@ -24386,12 +24002,419 @@ __export(hooks_exports, {
24386
24002
  });
24387
24003
  var init_hooks = __esm({
24388
24004
  "src/cli/repl/hooks/index.ts"() {
24389
- init_types8();
24005
+ init_types7();
24390
24006
  init_registry5();
24391
24007
  init_executor();
24392
24008
  }
24393
24009
  });
24394
24010
 
24011
+ // src/cli/repl/interruptions/types.ts
24012
+ var init_types8 = __esm({
24013
+ "src/cli/repl/interruptions/types.ts"() {
24014
+ }
24015
+ });
24016
+
24017
+ // src/cli/repl/interruptions/classifier.ts
24018
+ function matchPatterns(text15, patterns) {
24019
+ for (let i = 0; i < patterns.length; i++) {
24020
+ if (patterns[i].test(text15)) {
24021
+ return 1 - i * 0.1;
24022
+ }
24023
+ }
24024
+ return 0;
24025
+ }
24026
+ function classifyInterruption(message) {
24027
+ const text15 = message.text;
24028
+ const abortConf = matchPatterns(text15, ABORT_PATTERNS);
24029
+ const modifyConf = matchPatterns(text15, MODIFY_PATTERNS);
24030
+ const correctConf = matchPatterns(text15, CORRECT_PATTERNS);
24031
+ if (abortConf > 0 && abortConf >= modifyConf && abortConf >= correctConf) {
24032
+ return {
24033
+ text: text15,
24034
+ type: "abort" /* Abort */,
24035
+ confidence: Math.min(1, abortConf),
24036
+ timestamp: message.timestamp
24037
+ };
24038
+ }
24039
+ if (modifyConf > 0 && modifyConf >= correctConf) {
24040
+ return {
24041
+ text: text15,
24042
+ type: "modify" /* Modify */,
24043
+ confidence: Math.min(1, modifyConf),
24044
+ timestamp: message.timestamp
24045
+ };
24046
+ }
24047
+ if (correctConf > 0) {
24048
+ return {
24049
+ text: text15,
24050
+ type: "correct" /* Correct */,
24051
+ confidence: Math.min(1, correctConf),
24052
+ timestamp: message.timestamp
24053
+ };
24054
+ }
24055
+ return {
24056
+ text: text15,
24057
+ type: "info" /* Info */,
24058
+ confidence: 0.5,
24059
+ timestamp: message.timestamp
24060
+ };
24061
+ }
24062
+ var ABORT_PATTERNS, MODIFY_PATTERNS, CORRECT_PATTERNS;
24063
+ var init_classifier = __esm({
24064
+ "src/cli/repl/interruptions/classifier.ts"() {
24065
+ init_types8();
24066
+ ABORT_PATTERNS = [
24067
+ /^(para|stop|cancel|abort|quit|exit|detente|basta)$/i,
24068
+ /^(para\s+ya|stop\s+it|cancel\s+that)$/i,
24069
+ /\b(para|stop|cancel|abort)\b/i
24070
+ ];
24071
+ MODIFY_PATTERNS = [
24072
+ /^(a[ñn]ade|add|incluye|include|pon|put|agrega)\b/i,
24073
+ /^(cambia|change|modifica|modify|usa|use|haz|make)\b/i,
24074
+ /^no[,.]?\s+/i,
24075
+ // "no, mejor de la griega" — negation signals redirection
24076
+ /^(prefiero|prefer|quiero|i\s+want)\b/i,
24077
+ // "prefiero en gijon" — preference signals redirection
24078
+ /\b(a[ñn]ade|add|incluye|include)\s+/i,
24079
+ /\b(cambia|change|modifica|modify)\s+/i,
24080
+ /\b(en\s+vez\s+de|instead\s+of|rather\s+than)\b/i,
24081
+ /\b(prefiero|prefer|mejor|better|más|more|less|menos|bigger|smaller|larger)\b/i,
24082
+ /\b(también|also|además|additionally)\b/i
24083
+ ];
24084
+ CORRECT_PATTERNS = [
24085
+ /^(error|bug|fallo|wrong|mal|incorrect)\b/i,
24086
+ /^(arregla|fix|corrige|correct|repara|repair)\b/i,
24087
+ /\b(error\s+en|bug\s+in|fallo\s+en)\b/i,
24088
+ /\b(está\s+mal|is\s+wrong|no\s+funciona|doesn'?t\s+work)\b/i,
24089
+ /\b(arregla|fix|corrige|correct)\s+/i
24090
+ ];
24091
+ }
24092
+ });
24093
+ function mapClassificationToAction(type) {
24094
+ switch (type) {
24095
+ case "abort" /* Abort */:
24096
+ return "abort" /* Abort */;
24097
+ // Explicit modification/correction keywords → pre-select Modify
24098
+ case "modify" /* Modify */:
24099
+ case "correct" /* Correct */:
24100
+ return "modify" /* Modify */;
24101
+ // Info or unclassified → pre-select Queue (likely a new topic/task)
24102
+ // The user can always switch to Modify via the menu if needed
24103
+ case "info" /* Info */:
24104
+ default:
24105
+ return "queue" /* Queue */;
24106
+ }
24107
+ }
24108
+ var init_action_selector = __esm({
24109
+ "src/cli/repl/input/action-selector.ts"() {
24110
+ init_types8();
24111
+ }
24112
+ });
24113
+
24114
+ // src/cli/repl/interruptions/llm-classifier.ts
24115
+ var llm_classifier_exports = {};
24116
+ __export(llm_classifier_exports, {
24117
+ createLLMClassifier: () => createLLMClassifier
24118
+ });
24119
+ function buildClassificationPrompt(userMessage, currentTask) {
24120
+ if (currentTask) {
24121
+ return `Current task: "${currentTask}"
24122
+ User's new message: "${userMessage}"`;
24123
+ }
24124
+ return `User's new message: "${userMessage}"`;
24125
+ }
24126
+ function parseResponse(response) {
24127
+ const normalized = response.trim().toUpperCase();
24128
+ if (normalized.includes("STEER")) return "steer" /* Steer */;
24129
+ if (normalized.includes("MODIFY")) return "modify" /* Modify */;
24130
+ if (normalized.includes("QUEUE")) return "queue" /* Queue */;
24131
+ if (normalized.includes("ABORT")) return "abort" /* Abort */;
24132
+ return null;
24133
+ }
24134
+ function classifyWithKeywords(message) {
24135
+ const classified = classifyInterruption(message);
24136
+ return mapClassificationToAction(classified.type);
24137
+ }
24138
+ function createLLMClassifier(provider, config) {
24139
+ const cfg = { ...DEFAULT_CONFIG6, ...config };
24140
+ return {
24141
+ /**
24142
+ * Classify a user message captured during agent execution.
24143
+ *
24144
+ * Makes a fast parallel LLM call. If the LLM doesn't respond within
24145
+ * the timeout, falls back to keyword-based classification.
24146
+ *
24147
+ * @param message - The captured message
24148
+ * @param currentTask - The original task the agent is working on (for context)
24149
+ * @returns Classification result with action and source
24150
+ */
24151
+ async classify(message, currentTask) {
24152
+ const llmPromise = classifyWithLLM(provider, message, currentTask, cfg);
24153
+ let timeoutId;
24154
+ const timeoutPromise = new Promise((resolve4) => {
24155
+ timeoutId = setTimeout(() => resolve4(null), cfg.timeoutMs);
24156
+ });
24157
+ try {
24158
+ const llmResult = await Promise.race([llmPromise, timeoutPromise]);
24159
+ if (llmResult !== null) {
24160
+ return { action: llmResult, source: "llm" };
24161
+ }
24162
+ return {
24163
+ action: classifyWithKeywords(message),
24164
+ source: "keywords"
24165
+ };
24166
+ } finally {
24167
+ clearTimeout(timeoutId);
24168
+ }
24169
+ }
24170
+ };
24171
+ }
24172
+ async function classifyWithLLM(provider, message, currentTask, cfg) {
24173
+ try {
24174
+ const userPrompt = buildClassificationPrompt(message.text, currentTask);
24175
+ const options = {
24176
+ maxTokens: cfg.maxTokens,
24177
+ temperature: cfg.temperature,
24178
+ system: CLASSIFICATION_SYSTEM_PROMPT2,
24179
+ timeout: cfg.timeoutMs
24180
+ };
24181
+ const response = await provider.chat([{ role: "user", content: userPrompt }], options);
24182
+ return parseResponse(response.content);
24183
+ } catch {
24184
+ return null;
24185
+ }
24186
+ }
24187
+ var DEFAULT_CONFIG6, CLASSIFICATION_SYSTEM_PROMPT2;
24188
+ var init_llm_classifier = __esm({
24189
+ "src/cli/repl/interruptions/llm-classifier.ts"() {
24190
+ init_types8();
24191
+ init_classifier();
24192
+ init_action_selector();
24193
+ DEFAULT_CONFIG6 = {
24194
+ timeoutMs: 8e3,
24195
+ maxTokens: 10,
24196
+ temperature: 0
24197
+ };
24198
+ CLASSIFICATION_SYSTEM_PROMPT2 = `You are a message classifier for a coding assistant. The user sent a message while the assistant was working on a task.
24199
+
24200
+ Classify the message into exactly ONE category. Reply with ONLY the category word, nothing else.
24201
+
24202
+ Categories:
24203
+ - STEER: The message provides a hint, preference, or minor adjustment to the CURRENT task without requiring a restart (e.g. "also add a test", "use camelCase", "make sure to handle errors", "btw the file is in src/", "prefer async/await")
24204
+ - MODIFY: The message fundamentally changes the CURRENT task requiring a fresh start (e.g. "actually use Python instead of JS", "no, do it completely differently", "start over with a new approach")
24205
+ - QUEUE: The message is a NEW, DIFFERENT task unrelated to what's being done (e.g. "what's 2+2", "tell me the weather", "create another file for X")
24206
+ - ABORT: The message asks to stop/cancel the current work (e.g. "stop", "cancel", "para", "never mind")
24207
+
24208
+ Reply with exactly one word: STEER, MODIFY, QUEUE, or ABORT`;
24209
+ }
24210
+ });
24211
+
24212
+ // src/cli/repl/context/stack-detector.ts
24213
+ var stack_detector_exports = {};
24214
+ __export(stack_detector_exports, {
24215
+ detectProjectStack: () => detectProjectStack
24216
+ });
24217
+ async function detectStack2(cwd) {
24218
+ if (await fileExists3(path39__default.join(cwd, "package.json"))) return "node";
24219
+ if (await fileExists3(path39__default.join(cwd, "Cargo.toml"))) return "rust";
24220
+ if (await fileExists3(path39__default.join(cwd, "pyproject.toml"))) return "python";
24221
+ if (await fileExists3(path39__default.join(cwd, "go.mod"))) return "go";
24222
+ if (await fileExists3(path39__default.join(cwd, "pom.xml"))) return "java";
24223
+ if (await fileExists3(path39__default.join(cwd, "build.gradle"))) return "java";
24224
+ if (await fileExists3(path39__default.join(cwd, "build.gradle.kts"))) return "java";
24225
+ return "unknown";
24226
+ }
24227
+ async function detectPackageManager3(cwd, stack) {
24228
+ if (stack === "rust") return "cargo";
24229
+ if (stack === "python") return "pip";
24230
+ if (stack === "go") return "go";
24231
+ if (stack === "java") {
24232
+ if (await fileExists3(path39__default.join(cwd, "build.gradle")) || await fileExists3(path39__default.join(cwd, "build.gradle.kts"))) {
24233
+ return "gradle";
24234
+ }
24235
+ if (await fileExists3(path39__default.join(cwd, "pom.xml"))) {
24236
+ return "maven";
24237
+ }
24238
+ }
24239
+ if (stack === "node") {
24240
+ if (await fileExists3(path39__default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
24241
+ if (await fileExists3(path39__default.join(cwd, "yarn.lock"))) return "yarn";
24242
+ if (await fileExists3(path39__default.join(cwd, "bun.lockb"))) return "bun";
24243
+ return "npm";
24244
+ }
24245
+ return null;
24246
+ }
24247
+ async function parsePackageJson(cwd) {
24248
+ const packageJsonPath = path39__default.join(cwd, "package.json");
24249
+ try {
24250
+ const content = await fs35__default.readFile(packageJsonPath, "utf-8");
24251
+ const pkg = JSON.parse(content);
24252
+ const allDeps = {
24253
+ ...pkg.dependencies,
24254
+ ...pkg.devDependencies
24255
+ };
24256
+ const frameworks = [];
24257
+ if (allDeps.react) frameworks.push("React");
24258
+ if (allDeps.vue) frameworks.push("Vue");
24259
+ if (allDeps["@angular/core"]) frameworks.push("Angular");
24260
+ if (allDeps.next) frameworks.push("Next.js");
24261
+ if (allDeps.nuxt) frameworks.push("Nuxt");
24262
+ if (allDeps.express) frameworks.push("Express");
24263
+ if (allDeps.fastify) frameworks.push("Fastify");
24264
+ if (allDeps.nestjs || allDeps["@nestjs/core"]) frameworks.push("NestJS");
24265
+ const buildTools2 = [];
24266
+ if (allDeps.webpack) buildTools2.push("webpack");
24267
+ if (allDeps.vite) buildTools2.push("vite");
24268
+ if (allDeps.rollup) buildTools2.push("rollup");
24269
+ if (allDeps.tsup) buildTools2.push("tsup");
24270
+ if (allDeps.esbuild) buildTools2.push("esbuild");
24271
+ if (pkg.scripts?.build) buildTools2.push("build");
24272
+ const testingFrameworks = [];
24273
+ if (allDeps.vitest) testingFrameworks.push("vitest");
24274
+ if (allDeps.jest) testingFrameworks.push("jest");
24275
+ if (allDeps.mocha) testingFrameworks.push("mocha");
24276
+ if (allDeps.chai) testingFrameworks.push("chai");
24277
+ if (allDeps["@playwright/test"]) testingFrameworks.push("playwright");
24278
+ if (allDeps.cypress) testingFrameworks.push("cypress");
24279
+ const languages = ["JavaScript"];
24280
+ if (allDeps.typescript || await fileExists3(path39__default.join(cwd, "tsconfig.json"))) {
24281
+ languages.push("TypeScript");
24282
+ }
24283
+ return {
24284
+ dependencies: allDeps,
24285
+ frameworks,
24286
+ buildTools: buildTools2,
24287
+ testingFrameworks,
24288
+ languages
24289
+ };
24290
+ } catch {
24291
+ return {
24292
+ dependencies: {},
24293
+ frameworks: [],
24294
+ buildTools: [],
24295
+ testingFrameworks: [],
24296
+ languages: []
24297
+ };
24298
+ }
24299
+ }
24300
+ async function parsePomXml(cwd) {
24301
+ const pomPath = path39__default.join(cwd, "pom.xml");
24302
+ try {
24303
+ const content = await fs35__default.readFile(pomPath, "utf-8");
24304
+ const dependencies = {};
24305
+ const frameworks = [];
24306
+ const buildTools2 = ["maven"];
24307
+ const testingFrameworks = [];
24308
+ const depRegex = /<groupId>([^<]+)<\/groupId>\s*<artifactId>([^<]+)<\/artifactId>/g;
24309
+ let match;
24310
+ while ((match = depRegex.exec(content)) !== null) {
24311
+ const groupId = match[1];
24312
+ const artifactId = match[2];
24313
+ if (!groupId || !artifactId) continue;
24314
+ const fullName = `${groupId}:${artifactId}`;
24315
+ dependencies[fullName] = "unknown";
24316
+ if (artifactId.includes("spring-boot")) {
24317
+ if (!frameworks.includes("Spring Boot")) frameworks.push("Spring Boot");
24318
+ }
24319
+ if (artifactId.includes("spring-webmvc") || artifactId.includes("spring-web")) {
24320
+ if (!frameworks.includes("Spring MVC")) frameworks.push("Spring MVC");
24321
+ }
24322
+ if (artifactId.includes("hibernate")) {
24323
+ if (!frameworks.includes("Hibernate")) frameworks.push("Hibernate");
24324
+ }
24325
+ if (artifactId === "junit-jupiter" || artifactId === "junit") {
24326
+ if (!testingFrameworks.includes("JUnit")) testingFrameworks.push("JUnit");
24327
+ }
24328
+ if (artifactId === "mockito-core") {
24329
+ if (!testingFrameworks.includes("Mockito")) testingFrameworks.push("Mockito");
24330
+ }
24331
+ }
24332
+ return { dependencies, frameworks, buildTools: buildTools2, testingFrameworks };
24333
+ } catch {
24334
+ return { dependencies: {}, frameworks: [], buildTools: ["maven"], testingFrameworks: [] };
24335
+ }
24336
+ }
24337
+ async function parsePyprojectToml(cwd) {
24338
+ const pyprojectPath = path39__default.join(cwd, "pyproject.toml");
24339
+ try {
24340
+ const content = await fs35__default.readFile(pyprojectPath, "utf-8");
24341
+ const dependencies = {};
24342
+ const frameworks = [];
24343
+ const buildTools2 = ["pip"];
24344
+ const testingFrameworks = [];
24345
+ const lines = content.split("\n");
24346
+ for (const line of lines) {
24347
+ const trimmed = line.trim();
24348
+ if (trimmed.match(/^["']?[\w-]+["']?\s*=\s*["'][\^~>=<]+[\d.]+["']/)) {
24349
+ const depMatch = trimmed.match(/^["']?([\w-]+)["']?\s*=\s*["']([\^~>=<]+[\d.]+)["']/);
24350
+ if (depMatch && depMatch[1] && depMatch[2]) {
24351
+ dependencies[depMatch[1]] = depMatch[2];
24352
+ }
24353
+ }
24354
+ if (trimmed.includes("fastapi")) frameworks.push("FastAPI");
24355
+ if (trimmed.includes("django")) frameworks.push("Django");
24356
+ if (trimmed.includes("flask")) frameworks.push("Flask");
24357
+ if (trimmed.includes("pytest")) testingFrameworks.push("pytest");
24358
+ if (trimmed.includes("unittest")) testingFrameworks.push("unittest");
24359
+ }
24360
+ return { dependencies, frameworks, buildTools: buildTools2, testingFrameworks };
24361
+ } catch {
24362
+ return { dependencies: {}, frameworks: [], buildTools: ["pip"], testingFrameworks: [] };
24363
+ }
24364
+ }
24365
+ async function detectProjectStack(cwd) {
24366
+ const stack = await detectStack2(cwd);
24367
+ const packageManager = await detectPackageManager3(cwd, stack);
24368
+ let dependencies = {};
24369
+ let frameworks = [];
24370
+ let buildTools2 = [];
24371
+ let testingFrameworks = [];
24372
+ let languages = [];
24373
+ if (stack === "node") {
24374
+ const parsed = await parsePackageJson(cwd);
24375
+ dependencies = parsed.dependencies;
24376
+ frameworks = parsed.frameworks;
24377
+ buildTools2 = parsed.buildTools;
24378
+ testingFrameworks = parsed.testingFrameworks;
24379
+ languages = parsed.languages;
24380
+ } else if (stack === "java") {
24381
+ const isGradle = await fileExists3(path39__default.join(cwd, "build.gradle")) || await fileExists3(path39__default.join(cwd, "build.gradle.kts"));
24382
+ const parsed = isGradle ? { dependencies: {}, frameworks: [], buildTools: ["gradle"], testingFrameworks: ["JUnit"] } : await parsePomXml(cwd);
24383
+ dependencies = parsed.dependencies;
24384
+ frameworks = parsed.frameworks;
24385
+ buildTools2 = parsed.buildTools;
24386
+ testingFrameworks = parsed.testingFrameworks;
24387
+ languages = ["Java"];
24388
+ } else if (stack === "python") {
24389
+ const parsed = await parsePyprojectToml(cwd);
24390
+ dependencies = parsed.dependencies;
24391
+ frameworks = parsed.frameworks;
24392
+ buildTools2 = parsed.buildTools;
24393
+ testingFrameworks = parsed.testingFrameworks;
24394
+ languages = ["Python"];
24395
+ } else if (stack === "go") {
24396
+ languages = ["Go"];
24397
+ buildTools2 = ["go"];
24398
+ } else if (stack === "rust") {
24399
+ languages = ["Rust"];
24400
+ buildTools2 = ["cargo"];
24401
+ }
24402
+ return {
24403
+ stack,
24404
+ packageManager,
24405
+ dependencies,
24406
+ frameworks,
24407
+ buildTools: buildTools2,
24408
+ testingFrameworks,
24409
+ languages
24410
+ };
24411
+ }
24412
+ var init_stack_detector = __esm({
24413
+ "src/cli/repl/context/stack-detector.ts"() {
24414
+ init_files();
24415
+ }
24416
+ });
24417
+
24395
24418
  // src/cli/repl/input/message-queue.ts
24396
24419
  function createMessageQueue(maxSize = 50) {
24397
24420
  let messages = [];
@@ -24769,7 +24792,7 @@ function createFeedbackSystem(getSpinner, config) {
24769
24792
  var DEFAULT_FEEDBACK_CONFIG;
24770
24793
  var init_feedback_system = __esm({
24771
24794
  "src/cli/repl/feedback/feedback-system.ts"() {
24772
- init_types7();
24795
+ init_types8();
24773
24796
  DEFAULT_FEEDBACK_CONFIG = {
24774
24797
  method: "spinner",
24775
24798
  displayDurationMs: 2e3,
@@ -25045,8 +25068,8 @@ Generated by Corbat-Coco v0.1.0
25045
25068
 
25046
25069
  // src/cli/commands/init.ts
25047
25070
  function registerInitCommand(program2) {
25048
- program2.command("init").description("Initialize a new Corbat-Coco project").argument("[path]", "Project directory path", ".").option("-t, --template <template>", "Project template to use").option("-y, --yes", "Skip prompts and use defaults").option("--skip-discovery", "Skip the discovery phase (use existing spec)").action(async (path60, options) => {
25049
- await runInit(path60, options);
25071
+ program2.command("init").description("Initialize a new Corbat-Coco project").argument("[path]", "Project directory path", ".").option("-t, --template <template>", "Project template to use").option("-y, --yes", "Skip prompts and use defaults").option("--skip-discovery", "Skip the discovery phase (use existing spec)").action(async (path62, options) => {
25072
+ await runInit(path62, options);
25050
25073
  });
25051
25074
  }
25052
25075
  async function runInit(projectPath, options) {
@@ -25125,18 +25148,18 @@ async function gatherProjectInfo() {
25125
25148
  language
25126
25149
  };
25127
25150
  }
25128
- function getDefaultProjectInfo(path60) {
25129
- const name = path60 === "." ? "my-project" : path60.split("/").pop() || "my-project";
25151
+ function getDefaultProjectInfo(path62) {
25152
+ const name = path62 === "." ? "my-project" : path62.split("/").pop() || "my-project";
25130
25153
  return {
25131
25154
  name,
25132
25155
  description: "",
25133
25156
  language: "typescript"
25134
25157
  };
25135
25158
  }
25136
- async function checkExistingProject(path60) {
25159
+ async function checkExistingProject(path62) {
25137
25160
  try {
25138
25161
  const fs56 = await import('fs/promises');
25139
- await fs56.access(`${path60}/.coco`);
25162
+ await fs56.access(`${path62}/.coco`);
25140
25163
  return true;
25141
25164
  } catch {
25142
25165
  return false;
@@ -28637,20 +28660,20 @@ async function createCliPhaseContext(projectPath, _onUserInput) {
28637
28660
  },
28638
28661
  tools: {
28639
28662
  file: {
28640
- async read(path60) {
28663
+ async read(path62) {
28641
28664
  const fs56 = await import('fs/promises');
28642
- return fs56.readFile(path60, "utf-8");
28665
+ return fs56.readFile(path62, "utf-8");
28643
28666
  },
28644
- async write(path60, content) {
28667
+ async write(path62, content) {
28645
28668
  const fs56 = await import('fs/promises');
28646
28669
  const nodePath = await import('path');
28647
- await fs56.mkdir(nodePath.dirname(path60), { recursive: true });
28648
- await fs56.writeFile(path60, content, "utf-8");
28670
+ await fs56.mkdir(nodePath.dirname(path62), { recursive: true });
28671
+ await fs56.writeFile(path62, content, "utf-8");
28649
28672
  },
28650
- async exists(path60) {
28673
+ async exists(path62) {
28651
28674
  const fs56 = await import('fs/promises');
28652
28675
  try {
28653
- await fs56.access(path60);
28676
+ await fs56.access(path62);
28654
28677
  return true;
28655
28678
  } catch {
28656
28679
  return false;
@@ -29006,10 +29029,10 @@ function getPhaseStatusForPhase(phase) {
29006
29029
  }
29007
29030
  async function loadProjectState(cwd, config) {
29008
29031
  const fs56 = await import('fs/promises');
29009
- const path60 = await import('path');
29010
- const statePath = path60.join(cwd, ".coco", "state.json");
29011
- const backlogPath = path60.join(cwd, ".coco", "planning", "backlog.json");
29012
- const checkpointDir = path60.join(cwd, ".coco", "checkpoints");
29032
+ const path62 = await import('path');
29033
+ const statePath = path62.join(cwd, ".coco", "state.json");
29034
+ const backlogPath = path62.join(cwd, ".coco", "planning", "backlog.json");
29035
+ const checkpointDir = path62.join(cwd, ".coco", "checkpoints");
29013
29036
  let currentPhase = "idle";
29014
29037
  let metrics;
29015
29038
  let sprint;
@@ -30352,7 +30375,7 @@ function getProviderDefinition(type) {
30352
30375
  return PROVIDER_DEFINITIONS[type];
30353
30376
  }
30354
30377
  function getAllProviders() {
30355
- return Object.values(PROVIDER_DEFINITIONS).filter((p46) => !p46.internal);
30378
+ return Object.values(PROVIDER_DEFINITIONS).filter((p47) => !p47.internal);
30356
30379
  }
30357
30380
  function getRecommendedModel(type) {
30358
30381
  const provider = PROVIDER_DEFINITIONS[type];
@@ -30373,20 +30396,20 @@ function hasLocalProviderConfig(type) {
30373
30396
  return process.env["COCO_PROVIDER"] === "ollama" || !!process.env["OLLAMA_MODEL"] || !!process.env["OLLAMA_BASE_URL"];
30374
30397
  }
30375
30398
  function getConfiguredProviders() {
30376
- return getAllProviders().filter((p46) => {
30377
- if (p46.id === "copilot") {
30399
+ return getAllProviders().filter((p47) => {
30400
+ if (p47.id === "copilot") {
30378
30401
  return !!process.env["GITHUB_TOKEN"] || !!process.env["GH_TOKEN"] || hasCopilotCredentials();
30379
30402
  }
30380
- if (p46.id === "openai") {
30381
- return !!process.env[p46.envVar] || !!process.env["OPENAI_CODEX_TOKEN"] || !!process.env["OPENAI_ACCESS_TOKEN"];
30403
+ if (p47.id === "openai") {
30404
+ return !!process.env[p47.envVar] || !!process.env["OPENAI_CODEX_TOKEN"] || !!process.env["OPENAI_ACCESS_TOKEN"];
30382
30405
  }
30383
- if (p46.id === "lmstudio" || p46.id === "ollama") {
30384
- return hasLocalProviderConfig(p46.id);
30406
+ if (p47.id === "lmstudio" || p47.id === "ollama") {
30407
+ return hasLocalProviderConfig(p47.id);
30385
30408
  }
30386
- if (p46.id === "vertex") {
30409
+ if (p47.id === "vertex") {
30387
30410
  return !!(process.env["VERTEX_API_KEY"] ?? process.env["GOOGLE_API_KEY"] ?? process.env["VERTEX_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"]);
30388
30411
  }
30389
- return !!process.env[p46.envVar];
30412
+ return !!process.env[p47.envVar];
30390
30413
  });
30391
30414
  }
30392
30415
  function isProviderConfigured(type) {
@@ -30554,8 +30577,8 @@ async function saveConfig2(config) {
30554
30577
  await fs56.mkdir(dir, { recursive: true });
30555
30578
  await fs56.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));
30556
30579
  }
30557
- function getNestedValue(obj, path60) {
30558
- const keys = path60.split(".");
30580
+ function getNestedValue(obj, path62) {
30581
+ const keys = path62.split(".");
30559
30582
  let current = obj;
30560
30583
  for (const key of keys) {
30561
30584
  if (current === null || current === void 0 || typeof current !== "object") {
@@ -30565,8 +30588,8 @@ function getNestedValue(obj, path60) {
30565
30588
  }
30566
30589
  return current;
30567
30590
  }
30568
- function setNestedValue(obj, path60, value) {
30569
- const keys = path60.split(".");
30591
+ function setNestedValue(obj, path62, value) {
30592
+ const keys = path62.split(".");
30570
30593
  let current = obj;
30571
30594
  for (let i = 0; i < keys.length - 1; i++) {
30572
30595
  const key = keys[i];
@@ -31598,9 +31621,9 @@ function registerCheckCommand(program2) {
31598
31621
  // src/swarm/spec-parser.ts
31599
31622
  async function parseSwarmSpec(filePath) {
31600
31623
  const fs56 = await import('fs/promises');
31601
- const path60 = await import('path');
31624
+ const path62 = await import('path');
31602
31625
  const rawContent = await fs56.readFile(filePath, "utf-8");
31603
- const ext = path60.extname(filePath).toLowerCase();
31626
+ const ext = path62.extname(filePath).toLowerCase();
31604
31627
  if (ext === ".yaml" || ext === ".yml") {
31605
31628
  return parseYamlSpec(rawContent);
31606
31629
  }
@@ -31930,8 +31953,8 @@ var DEFAULT_AGENT_CONFIG = {
31930
31953
  };
31931
31954
  async function loadAgentConfig(projectPath) {
31932
31955
  const fs56 = await import('fs/promises');
31933
- const path60 = await import('path');
31934
- const configPath = path60.join(projectPath, ".coco", "swarm", "agents.json");
31956
+ const path62 = await import('path');
31957
+ const configPath = path62.join(projectPath, ".coco", "swarm", "agents.json");
31935
31958
  try {
31936
31959
  const raw = await fs56.readFile(configPath, "utf-8");
31937
31960
  const parsed = JSON.parse(raw);
@@ -32122,16 +32145,16 @@ async function createBoard(projectPath, spec) {
32122
32145
  }
32123
32146
  async function loadBoard(projectPath) {
32124
32147
  const fs56 = await import('fs/promises');
32125
- const path60 = await import('path');
32126
- const boardPath = path60.join(projectPath, ".coco", "swarm", "task-board.json");
32148
+ const path62 = await import('path');
32149
+ const boardPath = path62.join(projectPath, ".coco", "swarm", "task-board.json");
32127
32150
  const raw = await fs56.readFile(boardPath, "utf-8");
32128
32151
  return JSON.parse(raw);
32129
32152
  }
32130
32153
  async function saveBoard(projectPath, board) {
32131
32154
  const fs56 = await import('fs/promises');
32132
- const path60 = await import('path');
32133
- const boardDir = path60.join(projectPath, ".coco", "swarm");
32134
- const boardPath = path60.join(boardDir, "task-board.json");
32155
+ const path62 = await import('path');
32156
+ const boardDir = path62.join(projectPath, ".coco", "swarm");
32157
+ const boardPath = path62.join(boardDir, "task-board.json");
32135
32158
  await fs56.mkdir(boardDir, { recursive: true });
32136
32159
  await fs56.writeFile(boardPath, JSON.stringify(board, null, 2), "utf-8");
32137
32160
  }
@@ -32273,25 +32296,25 @@ Rules:
32273
32296
  }
32274
32297
  }
32275
32298
  async function defaultPromptHandler(q) {
32276
- const p46 = await import('@clack/prompts');
32299
+ const p47 = await import('@clack/prompts');
32277
32300
  if (q.options && q.options.length > 0) {
32278
- const result = await p46.select({
32301
+ const result = await p47.select({
32279
32302
  message: q.question,
32280
32303
  options: [
32281
32304
  ...q.options.map((o) => ({ value: o, label: o })),
32282
32305
  { value: q.assumedAnswer, label: `${q.assumedAnswer} (default)` }
32283
32306
  ]
32284
32307
  });
32285
- if (p46.isCancel(result)) {
32308
+ if (p47.isCancel(result)) {
32286
32309
  return q.assumedAnswer;
32287
32310
  }
32288
32311
  return result;
32289
32312
  } else {
32290
- const result = await p46.text({
32313
+ const result = await p47.text({
32291
32314
  message: q.question,
32292
32315
  placeholder: q.assumedAnswer
32293
32316
  });
32294
- if (p46.isCancel(result) || !result) {
32317
+ if (p47.isCancel(result) || !result) {
32295
32318
  return q.assumedAnswer;
32296
32319
  }
32297
32320
  return result;
@@ -32299,9 +32322,9 @@ async function defaultPromptHandler(q) {
32299
32322
  }
32300
32323
  async function writeAssumptionsFile(projectPath, projectName, questions, assumptions) {
32301
32324
  const fs56 = await import('fs/promises');
32302
- const path60 = await import('path');
32303
- const swarmDir = path60.join(projectPath, ".coco", "swarm");
32304
- const assumptionsPath = path60.join(swarmDir, "assumptions.md");
32325
+ const path62 = await import('path');
32326
+ const swarmDir = path62.join(projectPath, ".coco", "swarm");
32327
+ const assumptionsPath = path62.join(swarmDir, "assumptions.md");
32305
32328
  await fs56.mkdir(swarmDir, { recursive: true });
32306
32329
  const now = (/* @__PURE__ */ new Date()).toISOString();
32307
32330
  const content = [
@@ -32325,9 +32348,9 @@ async function writeAssumptionsFile(projectPath, projectName, questions, assumpt
32325
32348
  // src/swarm/events.ts
32326
32349
  async function appendSwarmEvent(projectPath, event) {
32327
32350
  const fs56 = await import('fs/promises');
32328
- const path60 = await import('path');
32329
- const eventsDir = path60.join(projectPath, ".coco", "swarm");
32330
- const eventsFile = path60.join(eventsDir, "events.jsonl");
32351
+ const path62 = await import('path');
32352
+ const eventsDir = path62.join(projectPath, ".coco", "swarm");
32353
+ const eventsFile = path62.join(eventsDir, "events.jsonl");
32331
32354
  await fs56.mkdir(eventsDir, { recursive: true });
32332
32355
  await fs56.appendFile(eventsFile, JSON.stringify(event) + "\n", "utf-8");
32333
32356
  }
@@ -32338,9 +32361,9 @@ function createEventId() {
32338
32361
  // src/swarm/knowledge.ts
32339
32362
  async function appendKnowledge(projectPath, entry) {
32340
32363
  const fs56 = await import('fs/promises');
32341
- const path60 = await import('path');
32342
- const knowledgeDir = path60.join(projectPath, ".coco", "swarm");
32343
- const knowledgeFile = path60.join(knowledgeDir, "knowledge.jsonl");
32364
+ const path62 = await import('path');
32365
+ const knowledgeDir = path62.join(projectPath, ".coco", "swarm");
32366
+ const knowledgeFile = path62.join(knowledgeDir, "knowledge.jsonl");
32344
32367
  await fs56.mkdir(knowledgeDir, { recursive: true });
32345
32368
  await fs56.appendFile(knowledgeFile, JSON.stringify(entry) + "\n", "utf-8");
32346
32369
  }
@@ -32647,10 +32670,10 @@ async function runSwarmLifecycle(options) {
32647
32670
  async function stageInit(ctx) {
32648
32671
  const { projectPath, spec } = ctx.options;
32649
32672
  const fs56 = await import('fs/promises');
32650
- const path60 = await import('path');
32651
- await fs56.mkdir(path60.join(projectPath, ".coco", "swarm"), { recursive: true });
32673
+ const path62 = await import('path');
32674
+ await fs56.mkdir(path62.join(projectPath, ".coco", "swarm"), { recursive: true });
32652
32675
  await fs56.mkdir(ctx.options.outputPath, { recursive: true });
32653
- const specSummaryPath = path60.join(projectPath, ".coco", "swarm", "spec-summary.json");
32676
+ const specSummaryPath = path62.join(projectPath, ".coco", "swarm", "spec-summary.json");
32654
32677
  const specSummary = {
32655
32678
  projectName: spec.projectName,
32656
32679
  description: spec.description,
@@ -32722,8 +32745,8 @@ async function stagePlan(ctx) {
32722
32745
  ]);
32723
32746
  await createBoard(projectPath, spec);
32724
32747
  const fs56 = await import('fs/promises');
32725
- const path60 = await import('path');
32726
- const planPath = path60.join(projectPath, ".coco", "swarm", "plan.json");
32748
+ const path62 = await import('path');
32749
+ const planPath = path62.join(projectPath, ".coco", "swarm", "plan.json");
32727
32750
  await fs56.writeFile(
32728
32751
  planPath,
32729
32752
  JSON.stringify({ pm: pmResult, architect: archResult, bestPractices: bpResult }, null, 2),
@@ -32940,7 +32963,7 @@ async function stageIntegrate(ctx) {
32940
32963
  async function stageOutput(ctx) {
32941
32964
  const { projectPath, outputPath } = ctx.options;
32942
32965
  const fs56 = await import('fs/promises');
32943
- const path60 = await import('path');
32966
+ const path62 = await import('path');
32944
32967
  const board = await loadBoard(projectPath);
32945
32968
  const featureResults = Array.from(ctx.featureResults.values());
32946
32969
  const summary = {
@@ -32955,7 +32978,7 @@ async function stageOutput(ctx) {
32955
32978
  globalScore: computeGlobalScore(featureResults)
32956
32979
  };
32957
32980
  await fs56.mkdir(outputPath, { recursive: true });
32958
- const summaryPath = path60.join(outputPath, "swarm-summary.json");
32981
+ const summaryPath = path62.join(outputPath, "swarm-summary.json");
32959
32982
  await fs56.writeFile(summaryPath, JSON.stringify(summary, null, 2), "utf-8");
32960
32983
  const passed = summary.globalScore >= ctx.options.minScore;
32961
32984
  await emitGate(projectPath, "global-score", passed, `Global score: ${summary.globalScore}`);
@@ -33302,8 +33325,8 @@ var SwarmOrchestrator = class {
33302
33325
  noQuestions = false,
33303
33326
  onProgress
33304
33327
  } = options;
33305
- const path60 = await import('path');
33306
- const projectPath = path60.dirname(path60.resolve(specFile));
33328
+ const path62 = await import('path');
33329
+ const projectPath = path62.dirname(path62.resolve(specFile));
33307
33330
  onProgress?.("init", `Parsing spec file: ${specFile}`);
33308
33331
  const spec = await parseSwarmSpec(specFile);
33309
33332
  onProgress?.("init", `Initializing provider: ${providerType}`);
@@ -33314,7 +33337,7 @@ var SwarmOrchestrator = class {
33314
33337
  await runSwarmLifecycle({
33315
33338
  spec,
33316
33339
  projectPath,
33317
- outputPath: path60.resolve(outputPath),
33340
+ outputPath: path62.resolve(outputPath),
33318
33341
  provider,
33319
33342
  agentConfig,
33320
33343
  minScore,
@@ -33446,6 +33469,7 @@ var helpCommand = {
33446
33469
  commands: [
33447
33470
  { cmd: "/model, /m", desc: "View or change the current model" },
33448
33471
  { cmd: "/provider", desc: "View or change the LLM provider" },
33472
+ { cmd: "/doctor, /dr", desc: "Run local diagnostics for config, auth, hooks, and tools" },
33449
33473
  { cmd: "/compact", desc: "Toggle compact mode (less verbose)" },
33450
33474
  { cmd: "/cost, /tokens", desc: "Show token usage and cost" },
33451
33475
  { cmd: "/trust", desc: "Manage project trust permissions" },
@@ -34256,7 +34280,7 @@ async function runOnboardingV2() {
34256
34280
  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"));
34257
34281
  console.log();
34258
34282
  p26.log.info(
34259
- `Found ${configuredProviders.length} configured provider(s): ${configuredProviders.map((p46) => p46.emoji + " " + p46.name).join(", ")}`
34283
+ `Found ${configuredProviders.length} configured provider(s): ${configuredProviders.map((p47) => p47.emoji + " " + p47.name).join(", ")}`
34260
34284
  );
34261
34285
  const useExisting = await p26.confirm({
34262
34286
  message: "Use an existing provider?",
@@ -35008,9 +35032,9 @@ async function setupOllamaProvider(port) {
35008
35032
  return setupLocalProvider("ollama", port);
35009
35033
  }
35010
35034
  async function selectExistingProvider(providers) {
35011
- const options = providers.map((p46) => ({
35012
- value: p46.id,
35013
- label: `${p46.emoji} ${p46.name}`,
35035
+ const options = providers.map((p47) => ({
35036
+ value: p47.id,
35037
+ label: `${p47.emoji} ${p47.name}`,
35014
35038
  hint: "Configured"
35015
35039
  }));
35016
35040
  options.push({ value: "__new__", label: "\u2795 Setup new provider", hint: "" });
@@ -35402,7 +35426,7 @@ async function ensureConfiguredV2(config) {
35402
35426
  } catch {
35403
35427
  }
35404
35428
  }
35405
- const preferredProviderDef = providers.find((p46) => p46.id === config.provider.type);
35429
+ const preferredProviderDef = providers.find((p47) => p47.id === config.provider.type);
35406
35430
  const preferredIsLocal = preferredProviderDef?.requiresApiKey === false && preferredProviderDef?.id !== "copilot";
35407
35431
  const preferredHasApiKey = preferredProviderDef ? !!process.env[preferredProviderDef.envVar] : false;
35408
35432
  const preferredHasOpenAIOAuth = preferredProviderDef?.id === "openai" && hasOpenAIOAuthTokens;
@@ -35432,12 +35456,12 @@ async function ensureConfiguredV2(config) {
35432
35456
  preferredUnavailableWasLocal = preferredIsLocal;
35433
35457
  }
35434
35458
  if (!preferredWasConfiguredButUnavailable || !preferredUnavailableWasLocal) {
35435
- const configuredProviders = providers.filter((p46) => {
35436
- if (p46.id === "copilot") return isProviderConfigured();
35437
- if (p46.id === "openai") {
35438
- return hasOpenAIOAuthTokens || !!process.env[p46.envVar];
35459
+ const configuredProviders = providers.filter((p47) => {
35460
+ if (p47.id === "copilot") return isProviderConfigured();
35461
+ if (p47.id === "openai") {
35462
+ return hasOpenAIOAuthTokens || !!process.env[p47.envVar];
35439
35463
  }
35440
- return p46.requiresApiKey === false || !!process.env[p46.envVar];
35464
+ return p47.requiresApiKey === false || !!process.env[p47.envVar];
35441
35465
  });
35442
35466
  for (const prov of configuredProviders) {
35443
35467
  try {
@@ -35510,7 +35534,7 @@ init_auth();
35510
35534
  init_env();
35511
35535
  async function selectProviderInteractively(providers, currentProviderId) {
35512
35536
  return new Promise((resolve4) => {
35513
- let selectedIndex = providers.findIndex((p46) => p46.id === currentProviderId);
35537
+ let selectedIndex = providers.findIndex((p47) => p47.id === currentProviderId);
35514
35538
  if (selectedIndex === -1) selectedIndex = 0;
35515
35539
  let lastTotalLines = 0;
35516
35540
  const clearPrevious = () => {
@@ -35608,12 +35632,12 @@ var providerCommand = {
35608
35632
  `));
35609
35633
  const allProviders2 = getAllProviders();
35610
35634
  const configuredProviders = getConfiguredProviders();
35611
- const providerOptions = allProviders2.map((p46) => ({
35612
- id: p46.id,
35613
- name: p46.name,
35614
- emoji: p46.emoji,
35615
- description: p46.description,
35616
- isConfigured: configuredProviders.some((cp) => cp.id === p46.id)
35635
+ const providerOptions = allProviders2.map((p47) => ({
35636
+ id: p47.id,
35637
+ name: p47.name,
35638
+ emoji: p47.emoji,
35639
+ description: p47.description,
35640
+ isConfigured: configuredProviders.some((cp) => cp.id === p47.id)
35617
35641
  }));
35618
35642
  const selectedProviderId = await selectProviderInteractively(
35619
35643
  providerOptions,
@@ -35628,15 +35652,15 @@ var providerCommand = {
35628
35652
  `));
35629
35653
  return false;
35630
35654
  }
35631
- const newProvider2 = allProviders2.find((p46) => p46.id === selectedProviderId);
35655
+ const newProvider2 = allProviders2.find((p47) => p47.id === selectedProviderId);
35632
35656
  return await switchProvider(newProvider2, session);
35633
35657
  }
35634
35658
  const newProviderId = args[0]?.toLowerCase();
35635
35659
  const allProviders = getAllProviders();
35636
- const newProvider = allProviders.find((p46) => p46.id === newProviderId);
35660
+ const newProvider = allProviders.find((p47) => p47.id === newProviderId);
35637
35661
  if (!newProvider) {
35638
35662
  console.log(chalk.red(`Unknown provider: ${newProviderId}`));
35639
- console.log(chalk.dim(`Available: ${allProviders.map((p46) => p46.id).join(", ")}
35663
+ console.log(chalk.dim(`Available: ${allProviders.map((p47) => p47.id).join(", ")}
35640
35664
  `));
35641
35665
  return false;
35642
35666
  }
@@ -36368,9 +36392,9 @@ function formatChangeSummary(files, options = {}) {
36368
36392
  const filesToShow = opts.maxFiles && opts.maxFiles > 0 ? files.slice(0, opts.maxFiles) : files;
36369
36393
  const remaining = files.length - filesToShow.length;
36370
36394
  for (const file of filesToShow) {
36371
- const statusIcon = file.status === "added" ? chalk.green("A") : file.status === "deleted" ? chalk.red("D") : chalk.yellow("M");
36395
+ const statusIcon2 = file.status === "added" ? chalk.green("A") : file.status === "deleted" ? chalk.red("D") : chalk.yellow("M");
36372
36396
  const stats = `${chalk.green("+" + file.additions)} ${chalk.red("-" + file.deletions)}`;
36373
- output += ` ${statusIcon} ${chalk.white(file.path)} ${chalk.gray(stats)}
36397
+ output += ` ${statusIcon2} ${chalk.white(file.path)} ${chalk.gray(stats)}
36374
36398
  `;
36375
36399
  }
36376
36400
  if (remaining > 0) {
@@ -36795,7 +36819,7 @@ async function showTrustStatus(session, trustStore) {
36795
36819
  }
36796
36820
  const level = trustStore.getLevel(projectPath);
36797
36821
  const list = trustStore.list();
36798
- const project = list.find((p46) => p46.path === projectPath);
36822
+ const project = list.find((p47) => p47.path === projectPath);
36799
36823
  p26.log.message("");
36800
36824
  p26.log.message(`\u{1F510} Project Trust Status`);
36801
36825
  p26.log.message(` Path: ${projectPath}`);
@@ -36888,8 +36912,8 @@ async function listTrustedProjects2(trustStore) {
36888
36912
  p26.log.message("");
36889
36913
  for (const project of projects) {
36890
36914
  const level = project.approvalLevel.toUpperCase().padEnd(5);
36891
- const path60 = project.path.length > 50 ? "..." + project.path.slice(-47) : project.path;
36892
- p26.log.message(` [${level}] ${path60}`);
36915
+ const path62 = project.path.length > 50 ? "..." + project.path.slice(-47) : project.path;
36916
+ p26.log.message(` [${level}] ${path62}`);
36893
36917
  p26.log.message(` Last accessed: ${new Date(project.lastAccessed).toLocaleString()}`);
36894
36918
  }
36895
36919
  p26.log.message("");
@@ -38120,8 +38144,8 @@ function displayRewindResult(result) {
38120
38144
  const fileName = filePath.split("/").pop() ?? filePath;
38121
38145
  console.log(`${chalk.green(String.fromCodePoint(10003))} Restored: ${fileName}`);
38122
38146
  }
38123
- for (const { path: path60, error } of result.filesFailed) {
38124
- const fileName = path60.split("/").pop() ?? path60;
38147
+ for (const { path: path62, error } of result.filesFailed) {
38148
+ const fileName = path62.split("/").pop() ?? path62;
38125
38149
  console.log(`${chalk.red(String.fromCodePoint(10007))} Failed: ${fileName} (${error})`);
38126
38150
  }
38127
38151
  if (result.conversationRestored) {
@@ -38580,10 +38604,10 @@ function getStatusIcon(status) {
38580
38604
  function displaySessionEntry(index, session, isCurrentProject) {
38581
38605
  const timeAgo = formatRelativeTime2(new Date(session.lastSavedAt));
38582
38606
  const tokenStr = formatTokenCount(session.totalTokens);
38583
- const statusIcon = getStatusIcon(session.status);
38607
+ const statusIcon2 = getStatusIcon(session.status);
38584
38608
  console.log();
38585
38609
  console.log(
38586
- `${chalk.yellow(`${index}.`)} ${statusIcon} ${chalk.dim(`[${timeAgo}]`)} ${session.messageCount} messages, ${tokenStr}`
38610
+ `${chalk.yellow(`${index}.`)} ${statusIcon2} ${chalk.dim(`[${timeAgo}]`)} ${session.messageCount} messages, ${tokenStr}`
38587
38611
  );
38588
38612
  if (session.title) {
38589
38613
  console.log(` ${chalk.cyan(`"${session.title}"`)}`);
@@ -38762,8 +38786,8 @@ var STARTUP_TIMEOUT_MS = 5500;
38762
38786
  var CACHE_DIR = path39__default.join(os4__default.homedir(), ".coco");
38763
38787
  var CACHE_FILE = path39__default.join(CACHE_DIR, "version-check-cache.json");
38764
38788
  function compareVersions(a, b) {
38765
- const partsA = a.replace(/^v/, "").split(".").map((p46) => Number(p46.replace(/-.*$/, "")));
38766
- const partsB = b.replace(/^v/, "").split(".").map((p46) => Number(p46.replace(/-.*$/, "")));
38789
+ const partsA = a.replace(/^v/, "").split(".").map((p47) => Number(p47.replace(/-.*$/, "")));
38790
+ const partsB = b.replace(/^v/, "").split(".").map((p47) => Number(p47.replace(/-.*$/, "")));
38767
38791
  for (let i = 0; i < 3; i++) {
38768
38792
  const numA = partsA[i] ?? 0;
38769
38793
  const numB = partsB[i] ?? 0;
@@ -38887,13 +38911,13 @@ async function checkForUpdatesInteractive() {
38887
38911
  ]);
38888
38912
  clearTimeout(startupTimerId);
38889
38913
  if (!updateInfo) return;
38890
- const p46 = await import('@clack/prompts');
38914
+ const p47 = await import('@clack/prompts');
38891
38915
  printUpdateBanner(updateInfo);
38892
- const answer = await p46.confirm({
38916
+ const answer = await p47.confirm({
38893
38917
  message: "Exit now to update?",
38894
38918
  initialValue: true
38895
38919
  });
38896
- if (!p46.isCancel(answer) && answer) {
38920
+ if (!p47.isCancel(answer) && answer) {
38897
38921
  console.log();
38898
38922
  console.log(chalk.dim(` Running: ${updateInfo.updateCommand}`));
38899
38923
  console.log();
@@ -48530,11 +48554,11 @@ var getLearnedPatternsTool = defineTool({
48530
48554
  const patterns = store.getFrequentPatterns(typedInput.limit);
48531
48555
  return {
48532
48556
  totalPatterns: patterns.length,
48533
- patterns: patterns.map((p46) => ({
48534
- pattern: p46.pattern,
48535
- preference: p46.userPreference,
48536
- frequency: p46.frequency,
48537
- lastUsed: new Date(p46.lastUsed).toISOString()
48557
+ patterns: patterns.map((p47) => ({
48558
+ pattern: p47.pattern,
48559
+ preference: p47.userPreference,
48560
+ frequency: p47.frequency,
48561
+ lastUsed: new Date(p47.lastUsed).toISOString()
48538
48562
  }))
48539
48563
  };
48540
48564
  }
@@ -50047,6 +50071,188 @@ var bestOfNCommand = {
50047
50071
  }
50048
50072
  };
50049
50073
 
50074
+ // src/cli/repl/commands/doctor.ts
50075
+ init_paths();
50076
+ init_loader();
50077
+ init_hooks();
50078
+ init_auth();
50079
+ function statusIcon(status) {
50080
+ if (status === "pass") return "PASS";
50081
+ if (status === "warn") return "WARN";
50082
+ return "FAIL";
50083
+ }
50084
+ async function checkProjectAccess(projectPath) {
50085
+ try {
50086
+ await access(projectPath, constants.R_OK | constants.W_OK);
50087
+ return {
50088
+ name: "Project access",
50089
+ status: "pass",
50090
+ detail: `Readable and writable: ${projectPath}`
50091
+ };
50092
+ } catch (error) {
50093
+ return {
50094
+ name: "Project access",
50095
+ status: "fail",
50096
+ detail: error instanceof Error ? error.message : String(error)
50097
+ };
50098
+ }
50099
+ }
50100
+ async function checkConfig(projectPath) {
50101
+ try {
50102
+ const paths = await findAllConfigPaths(projectPath);
50103
+ await loadConfig(path39__default.join(projectPath, ".coco", "config.json"));
50104
+ const found = [paths.project, paths.global].filter(Boolean).length;
50105
+ return {
50106
+ name: "Configuration",
50107
+ status: "pass",
50108
+ detail: found > 0 ? `${found} config file(s) parsed successfully` : "Using built-in defaults"
50109
+ };
50110
+ } catch (error) {
50111
+ return {
50112
+ name: "Configuration",
50113
+ status: "fail",
50114
+ detail: error instanceof Error ? error.message : String(error)
50115
+ };
50116
+ }
50117
+ }
50118
+ async function checkAuth(session) {
50119
+ const provider = session.config.provider.type;
50120
+ if (provider === "anthropic") {
50121
+ return process.env["ANTHROPIC_API_KEY"] ? {
50122
+ name: "Provider auth",
50123
+ status: "pass",
50124
+ detail: "ANTHROPIC_API_KEY detected"
50125
+ } : {
50126
+ name: "Provider auth",
50127
+ status: "warn",
50128
+ detail: "Missing ANTHROPIC_API_KEY for current provider"
50129
+ };
50130
+ }
50131
+ if (provider === "openai" || provider === "codex") {
50132
+ const hasApiKey = !!process.env["OPENAI_API_KEY"];
50133
+ const hasOauth = await isOAuthConfigured("openai").catch(() => false);
50134
+ return hasApiKey || hasOauth ? {
50135
+ name: "Provider auth",
50136
+ status: "pass",
50137
+ detail: hasApiKey ? "OPENAI_API_KEY detected" : "OpenAI OAuth is configured"
50138
+ } : {
50139
+ name: "Provider auth",
50140
+ status: "warn",
50141
+ detail: "Missing OPENAI_API_KEY and no OpenAI OAuth configuration found"
50142
+ };
50143
+ }
50144
+ if (provider === "gemini") {
50145
+ const hasApiKey = !!process.env["GEMINI_API_KEY"] || !!process.env["GOOGLE_API_KEY"];
50146
+ const hasOauth = await isOAuthConfigured("gemini").catch(() => false);
50147
+ const hasAdc = await isADCConfigured().catch(() => false);
50148
+ return hasApiKey || hasOauth || hasAdc ? {
50149
+ name: "Provider auth",
50150
+ status: "pass",
50151
+ detail: hasApiKey ? "Gemini API key detected" : hasAdc ? "Google ADC is configured" : "Gemini OAuth is configured"
50152
+ } : {
50153
+ name: "Provider auth",
50154
+ status: "warn",
50155
+ detail: "Missing Gemini auth (API key, OAuth, or ADC)"
50156
+ };
50157
+ }
50158
+ if (provider === "copilot") {
50159
+ const hasOauth = await isOAuthConfigured("copilot").catch(() => false);
50160
+ return hasOauth ? {
50161
+ name: "Provider auth",
50162
+ status: "pass",
50163
+ detail: "Copilot auth is configured"
50164
+ } : {
50165
+ name: "Provider auth",
50166
+ status: "warn",
50167
+ detail: "Copilot auth not configured"
50168
+ };
50169
+ }
50170
+ return {
50171
+ name: "Provider auth",
50172
+ status: "warn",
50173
+ detail: `No doctor auth rule for provider '${provider}'`
50174
+ };
50175
+ }
50176
+ async function checkHooks(projectPath) {
50177
+ const hooksPath = path39__default.join(projectPath, ".coco", "hooks.json");
50178
+ try {
50179
+ await access(hooksPath, constants.R_OK);
50180
+ } catch {
50181
+ return {
50182
+ name: "Hooks",
50183
+ status: "pass",
50184
+ detail: "No project hooks configured"
50185
+ };
50186
+ }
50187
+ try {
50188
+ const registry = createHookRegistry();
50189
+ await registry.loadFromFile(hooksPath);
50190
+ return {
50191
+ name: "Hooks",
50192
+ status: "pass",
50193
+ detail: `${registry.size} hook(s) loaded from .coco/hooks.json`
50194
+ };
50195
+ } catch (error) {
50196
+ return {
50197
+ name: "Hooks",
50198
+ status: "fail",
50199
+ detail: error instanceof Error ? error.message : String(error)
50200
+ };
50201
+ }
50202
+ }
50203
+ async function checkTooling() {
50204
+ try {
50205
+ const registry = createFullToolRegistry();
50206
+ const defs = registry.getToolDefinitionsForLLM();
50207
+ return {
50208
+ name: "Tool registry",
50209
+ status: defs.length > 0 ? "pass" : "fail",
50210
+ detail: `${defs.length} tool(s) available to the agent`
50211
+ };
50212
+ } catch (error) {
50213
+ return {
50214
+ name: "Tool registry",
50215
+ status: "fail",
50216
+ detail: error instanceof Error ? error.message : String(error)
50217
+ };
50218
+ }
50219
+ }
50220
+ async function runDoctorChecks(session) {
50221
+ return Promise.all([
50222
+ checkProjectAccess(session.projectPath),
50223
+ checkConfig(session.projectPath),
50224
+ checkAuth(session),
50225
+ checkHooks(session.projectPath),
50226
+ checkTooling()
50227
+ ]);
50228
+ }
50229
+ var doctorCommand = {
50230
+ name: "doctor",
50231
+ aliases: ["dr"],
50232
+ description: "Run read-only diagnostics for config, auth, hooks, and tools",
50233
+ usage: "/doctor",
50234
+ async execute(_args, session) {
50235
+ if (!session.config.agent.doctorV2) {
50236
+ p26.log.warn("Doctor v2 is disabled. Set agent.doctorV2=true to enable it.");
50237
+ return false;
50238
+ }
50239
+ p26.intro(chalk.cyan("Coco Doctor"));
50240
+ const checks = await runDoctorChecks(session);
50241
+ const failures = checks.filter((check) => check.status === "fail").length;
50242
+ const warnings = checks.filter((check) => check.status === "warn").length;
50243
+ for (const check of checks) {
50244
+ p26.log.message(`${statusIcon(check.status)} ${check.name}: ${check.detail}`);
50245
+ }
50246
+ p26.log.message("");
50247
+ p26.log.info(
50248
+ `Summary: ${checks.length - failures - warnings} pass, ${warnings} warn, ${failures} fail`
50249
+ );
50250
+ p26.log.message(`Config home: ${CONFIG_PATHS.home}`);
50251
+ p26.outro(failures > 0 ? "Doctor finished with failures" : "Doctor finished");
50252
+ return false;
50253
+ }
50254
+ };
50255
+
50050
50256
  // src/cli/repl/output/renderer.ts
50051
50257
  init_syntax();
50052
50258
  var lineBuffer = "";
@@ -50815,9 +51021,9 @@ function printEditDiff(oldStr, newStr) {
50815
51021
  }
50816
51022
  if (diffLineList.length === 0) return;
50817
51023
  const pairs = pairAdjacentDiffLines(diffLineList);
50818
- const pairedDeletes = new Set(pairs.map((p46) => p46.deleteIdx));
50819
- const pairedAdds = new Set(pairs.map((p46) => p46.addIdx));
50820
- const pairByAdd = new Map(pairs.map((p46) => [p46.addIdx, p46.deleteIdx]));
51024
+ const pairedDeletes = new Set(pairs.map((p47) => p47.deleteIdx));
51025
+ const pairedAdds = new Set(pairs.map((p47) => p47.addIdx));
51026
+ const pairByAdd = new Map(pairs.map((p47) => [p47.addIdx, p47.deleteIdx]));
50821
51027
  const wordHighlights = /* @__PURE__ */ new Map();
50822
51028
  for (const pair of pairs) {
50823
51029
  const del = diffLineList[pair.deleteIdx];
@@ -50897,8 +51103,8 @@ function formatToolSummary(toolName, input) {
50897
51103
  case "grep":
50898
51104
  case "search_files": {
50899
51105
  const pattern = String(input.pattern || "");
50900
- const path60 = input.path ? ` in ${input.path}` : "";
50901
- return `"${pattern}"${path60}`;
51106
+ const path62 = input.path ? ` in ${input.path}` : "";
51107
+ return `"${pattern}"${path62}`;
50902
51108
  }
50903
51109
  case "bash_exec": {
50904
51110
  const cmd = String(input.command || "");
@@ -50919,8 +51125,8 @@ function formatToolSummary(toolName, input) {
50919
51125
  function formatUrl(url) {
50920
51126
  try {
50921
51127
  const u = new URL(url);
50922
- const path60 = u.pathname.replace(/\/$/, "");
50923
- const display = path60 ? `${u.hostname} \u203A ${path60.slice(1)}` : u.hostname;
51128
+ const path62 = u.pathname.replace(/\/$/, "");
51129
+ const display = path62 ? `${u.hostname} \u203A ${path62.slice(1)}` : u.hostname;
50924
51130
  const max = Math.max(getTerminalWidth2() - 20, 50);
50925
51131
  return display.length > max ? display.slice(0, max - 1) + "\u2026" : display;
50926
51132
  } catch {
@@ -51077,7 +51283,8 @@ var commands = [
51077
51283
  fullPowerRiskCommand,
51078
51284
  buildAppCommand,
51079
51285
  contextCommand,
51080
- bestOfNCommand
51286
+ bestOfNCommand,
51287
+ doctorCommand
51081
51288
  ];
51082
51289
  function isSlashCommand(input) {
51083
51290
  return input.startsWith("/");
@@ -51280,8 +51487,8 @@ function getCursorVisualPos(text15, cursorPos, promptLen, termCols) {
51280
51487
  function computeWordWrap(text15, startCol, termCols) {
51281
51488
  const passthrough = {
51282
51489
  display: text15,
51283
- toDisplayPos: (p46) => p46,
51284
- toOrigPos: (p46) => p46
51490
+ toDisplayPos: (p47) => p47,
51491
+ toOrigPos: (p47) => p47
51285
51492
  };
51286
51493
  if (!text15 || termCols <= 1) return passthrough;
51287
51494
  const origToDisp = new Int32Array(text15.length + 1);
@@ -51908,7 +52115,7 @@ function createInputHandler(_session) {
51908
52115
  }
51909
52116
 
51910
52117
  // src/cli/repl/index.ts
51911
- init_types7();
52118
+ init_types8();
51912
52119
  var COCO_SPINNER = {
51913
52120
  interval: 120,
51914
52121
  frames: ["\u{1F965} ", " \u{1F965} ", " \u{1F965} ", " \u{1F965} ", " \u{1F965}", " \u{1F965} ", " \u{1F965} ", " \u{1F965} "]
@@ -52540,16 +52747,16 @@ function formatToolCallForConfirmation(toolCall, metadata) {
52540
52747
  const reason = input.reason ? String(input.reason) : void 0;
52541
52748
  const actionLabel = action === "allow" ? chalk.green.bold("ALLOW") : chalk.red.bold(action.toUpperCase());
52542
52749
  const scopeLabel = scope === "global" ? chalk.blue("Global (all projects)") : chalk.magenta("Project (current only)");
52543
- const patternList = patterns.map((p46) => chalk.cyan(p46)).join(", ");
52750
+ const patternList = patterns.map((p47) => chalk.cyan(p47)).join(", ");
52544
52751
  const lines = [`${actionLabel}: ${patternList}`];
52545
52752
  lines.push(`${chalk.dim(" Scope:")} ${scopeLabel}`);
52546
52753
  if (reason) {
52547
52754
  lines.push(`${chalk.dim(" Reason:")} ${reason}`);
52548
52755
  }
52549
- for (const p46 of patterns) {
52550
- lines.push(`${chalk.dim(" Risk:")} ${getRiskDescription(p46)}`);
52756
+ for (const p47 of patterns) {
52757
+ lines.push(`${chalk.dim(" Risk:")} ${getRiskDescription(p47)}`);
52551
52758
  lines.push(
52552
- `${chalk.dim(" Effect:")} ${getEffectDescription(action, p46, scope)}`
52759
+ `${chalk.dim(" Effect:")} ${getEffectDescription(action, p47, scope)}`
52553
52760
  );
52554
52761
  }
52555
52762
  description = lines.join("\n ");
@@ -53219,7 +53426,9 @@ function computeTurnQualityMetrics(input) {
53219
53426
  successfulToolCalls,
53220
53427
  failedToolCalls,
53221
53428
  hadError: input.hadError,
53222
- repeatedOutputsSuppressed: input.repeatedOutputsSuppressed
53429
+ repeatedOutputsSuppressed: input.repeatedOutputsSuppressed,
53430
+ observedLargeOutputs: input.observedLargeOutputs ?? 0,
53431
+ observedLargeOutputChars: input.observedLargeOutputChars ?? 0
53223
53432
  };
53224
53433
  }
53225
53434
  var RepeatedOutputSuppressor = class {
@@ -53254,13 +53463,20 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
53254
53463
  let finalContent = "";
53255
53464
  let hadTurnError = false;
53256
53465
  let repeatedOutputsSuppressed = 0;
53466
+ let observedLargeOutputs = 0;
53467
+ let observedLargeOutputChars = 0;
53257
53468
  const repeatedOutputSuppressor = new RepeatedOutputSuppressor();
53469
+ const recoveryV2Enabled = session.config.agent.recoveryV2 === true;
53470
+ const strictPlanModeEnabled = session.planMode && session.config.agent.planModeStrict === true;
53471
+ const outputOffloadObservationEnabled = session.config.agent.outputOffload === true;
53258
53472
  const buildQualityMetrics = () => computeTurnQualityMetrics({
53259
53473
  iterationsUsed: iteration,
53260
53474
  maxIterations,
53261
53475
  executedTools,
53262
53476
  hadError: hadTurnError,
53263
- repeatedOutputsSuppressed
53477
+ repeatedOutputsSuppressed,
53478
+ observedLargeOutputs,
53479
+ observedLargeOutputChars
53264
53480
  });
53265
53481
  const abortReturn = () => {
53266
53482
  session.messages.length = messageSnapshot;
@@ -53275,7 +53491,7 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
53275
53491
  };
53276
53492
  };
53277
53493
  const allTools = toolRegistry.getToolDefinitionsForLLM();
53278
- const tools = session.planMode ? filterReadOnlyTools(allTools) : allTools;
53494
+ const tools = session.planMode ? strictPlanModeEnabled ? filterStrictPlanModeTools(allTools) : filterReadOnlyTools(allTools) : allTools;
53279
53495
  const availableMcpToolNames = allTools.map((t) => t.name).filter((name) => name.startsWith("mcp_"));
53280
53496
  function extractPlainText(content) {
53281
53497
  if (typeof content === "string") return content;
@@ -53318,6 +53534,38 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
53318
53534
  const INLINE_RESULT_MAX_CHARS = 8e3;
53319
53535
  const INLINE_RESULT_HEAD_CHARS = 6500;
53320
53536
  const INLINE_RESULT_TAIL_CHARS = 1e3;
53537
+ const OUTPUT_OFFLOAD_OBSERVATION_THRESHOLD = 12e3;
53538
+ const OUTPUT_OFFLOAD_PERSIST_THRESHOLD = 2e4;
53539
+ function shouldRetryStreamError(classification, responseSoFar, toolCallsSoFar, attempt, maxAttempts) {
53540
+ if (!recoveryV2Enabled) return false;
53541
+ if (attempt >= maxAttempts) return false;
53542
+ if (toolCallsSoFar.length > 0) return false;
53543
+ if (responseSoFar.trim().length > 0) return false;
53544
+ return classification.kind === "provider_retryable" || classification.kind === "unexpected";
53545
+ }
53546
+ async function pauseBeforeRetry(attempt) {
53547
+ const delayMs = Math.min(1200, 250 * attempt);
53548
+ await new Promise((resolve4) => setTimeout(resolve4, delayMs));
53549
+ }
53550
+ async function offloadLargeOutput(toolCall, content) {
53551
+ const artifactDir = path39__default.join(session.projectPath, ".coco", "session-artifacts", session.id);
53552
+ const safeToolName = toolCall.name.replace(/[^a-zA-Z0-9_-]/g, "_");
53553
+ const artifactPath = path39__default.join(artifactDir, `${safeToolName}-${toolCall.id}.txt`);
53554
+ const relativeArtifactPath = path39__default.relative(session.projectPath, artifactPath);
53555
+ await mkdir(artifactDir, { recursive: true });
53556
+ await writeFile(artifactPath, content, "utf-8");
53557
+ return {
53558
+ artifactPath,
53559
+ toolResultContent: [
53560
+ `Large tool output stored in local artifact: ${relativeArtifactPath}`,
53561
+ `Original size: ${content.length.toLocaleString()} characters.`,
53562
+ "Use read_file on the artifact path if you need the full output.",
53563
+ "",
53564
+ "Preview:",
53565
+ truncateInlineResult(content, toolCall.name)
53566
+ ].join("\n")
53567
+ };
53568
+ }
53321
53569
  function truncateInlineResult(content, toolName) {
53322
53570
  if (content.length <= INLINE_RESULT_MAX_CHARS) return content;
53323
53571
  const head = content.slice(0, INLINE_RESULT_HEAD_CHARS);
@@ -53353,28 +53601,80 @@ ${tail}`;
53353
53601
  }
53354
53602
  function shouldRecoverNoToolTurn(stopReason, content) {
53355
53603
  const trimmed = content.trim();
53604
+ if (trimmed.length === 0) {
53605
+ return {
53606
+ recover: true,
53607
+ reason: "The previous response produced no usable output. Continue the task immediately or explain the blocker in one concise sentence.",
53608
+ category: "empty"
53609
+ };
53610
+ }
53356
53611
  if (stopReason === "tool_use") {
53357
53612
  return {
53358
53613
  recover: true,
53359
- reason: "The previous response indicated tool use, but no tool calls were received. Re-emit the tool call(s) now."
53614
+ reason: "The previous response indicated tool use, but no tool calls were received. Re-emit the tool call(s) now.",
53615
+ category: "tool_use"
53360
53616
  };
53361
53617
  }
53362
53618
  if (stopReason === "max_tokens" && trimmed.length === 0) {
53363
53619
  return {
53364
53620
  recover: true,
53365
- reason: "The previous response was cut off before producing any usable output. Continue immediately."
53621
+ reason: "The previous response was cut off before producing any usable output. Continue immediately.",
53622
+ category: "empty"
53366
53623
  };
53367
53624
  }
53368
- const planningOnly = trimmed.length > 0 && trimmed.length < 320 && /^(voy a|ahora voy|i('| )?ll|i will|let me|starting|preparing|activating|continuo|contin[uú]o|de acuerdo[, ]+voy)\b/i.test(
53625
+ const planningOnly = trimmed.length > 0 && trimmed.length < 320 && /^(voy a|ahora voy|voy a revisar|voy a comprobar|voy a mirar|déjame|dejame|a continuación|i('| )?ll|i will|let me|starting|preparing|activating|checking|reviewing|looking into|working on|continuo|contin[uú]o|de acuerdo[, ]+voy)\b/i.test(
53369
53626
  trimmed
53370
53627
  );
53371
53628
  if (planningOnly) {
53372
53629
  return {
53373
53630
  recover: true,
53374
- reason: "Do not only describe the next step. Execute it now with concrete tool calls."
53631
+ reason: "Do not only describe the next step. Execute it now with concrete tool calls.",
53632
+ category: "planning_only"
53375
53633
  };
53376
53634
  }
53377
- return { recover: false, reason: "" };
53635
+ return { recover: false, reason: "", category: "none" };
53636
+ }
53637
+ async function requestNoToolFallbackExplanation(priorContent, reason) {
53638
+ let explanation = "";
53639
+ let explanationThinkingEnded = false;
53640
+ options.onThinkingStart?.();
53641
+ try {
53642
+ const finalMessages = [
53643
+ ...getConversationContext(session, toolRegistry),
53644
+ {
53645
+ role: "assistant",
53646
+ content: priorContent || "[No output returned in previous step.]"
53647
+ },
53648
+ {
53649
+ role: "user",
53650
+ content: `[System: ${reason} Do not call tools. Either explain the exact blocker and ask the user for the smallest missing input, or, if the task is already complete, summarize it briefly. Do not return an empty response.]`
53651
+ }
53652
+ ];
53653
+ for await (const chunk of provider.streamWithTools(finalMessages, {
53654
+ tools: [],
53655
+ maxTokens: session.config.provider.maxTokens,
53656
+ signal: options.signal
53657
+ })) {
53658
+ if (options.signal?.aborted) break;
53659
+ if (chunk.type === "text" && chunk.text) {
53660
+ if (!explanationThinkingEnded) {
53661
+ options.onThinkingEnd?.();
53662
+ explanationThinkingEnded = true;
53663
+ }
53664
+ explanation += chunk.text;
53665
+ options.onStream?.(chunk);
53666
+ }
53667
+ if (chunk.type === "done") break;
53668
+ }
53669
+ } catch {
53670
+ } finally {
53671
+ if (!explanationThinkingEnded) options.onThinkingEnd?.();
53672
+ }
53673
+ const trimmed = explanation.trim();
53674
+ if (trimmed.length > 0) {
53675
+ return explanation;
53676
+ }
53677
+ return "I could not continue because the model stopped returning actionable output. Please retry, switch provider/model, or tell me the exact next step you want me to take.";
53378
53678
  }
53379
53679
  function shouldAutoExtendIterationBudget(latestExecuted, latestToolResults, isInErrorLoop) {
53380
53680
  if (isInErrorLoop) return false;
@@ -53393,80 +53693,110 @@ ${tail}`;
53393
53693
  const messages = getConversationContext(session, toolRegistry);
53394
53694
  options.onThinkingStart?.();
53395
53695
  let responseContent = "";
53396
- const collectedToolCalls = [];
53696
+ let collectedToolCalls = [];
53397
53697
  let thinkingEnded = false;
53398
53698
  let lastStopReason;
53399
- const toolCallBuilders = /* @__PURE__ */ new Map();
53699
+ const maxStreamAttempts = recoveryV2Enabled ? 2 : 1;
53400
53700
  try {
53401
- for await (const chunk of provider.streamWithTools(messages, {
53402
- tools,
53403
- maxTokens: session.config.provider.maxTokens,
53404
- signal: options.signal
53405
- })) {
53406
- if (options.signal?.aborted) {
53407
- break;
53408
- }
53701
+ let streamAttempt = 0;
53702
+ while (streamAttempt < maxStreamAttempts) {
53703
+ responseContent = "";
53704
+ collectedToolCalls = [];
53705
+ lastStopReason = void 0;
53706
+ const toolCallBuilders = /* @__PURE__ */ new Map();
53409
53707
  try {
53410
- if (chunk.type === "text" && chunk.text) {
53411
- if (!thinkingEnded) {
53412
- options.onThinkingEnd?.();
53413
- thinkingEnded = true;
53414
- }
53415
- responseContent += chunk.text;
53416
- finalContent += chunk.text;
53417
- iterationTextChunks.push(chunk);
53418
- }
53419
- if (chunk.type === "tool_use_start" && chunk.toolCall) {
53420
- flushLineBuffer();
53421
- if (!thinkingEnded) {
53422
- options.onThinkingEnd?.();
53423
- thinkingEnded = true;
53424
- }
53425
- const id = chunk.toolCall.id ?? `tool_${toolCallBuilders.size}`;
53426
- const toolName = chunk.toolCall.name ?? "";
53427
- toolCallBuilders.set(id, {
53428
- id,
53429
- name: toolName,
53430
- input: {},
53431
- geminiThoughtSignature: chunk.toolCall.geminiThoughtSignature
53432
- });
53433
- if (toolName) {
53434
- options.onToolPreparing?.(toolName);
53708
+ for await (const chunk of provider.streamWithTools(messages, {
53709
+ tools,
53710
+ maxTokens: session.config.provider.maxTokens,
53711
+ signal: options.signal
53712
+ })) {
53713
+ if (options.signal?.aborted) {
53714
+ break;
53435
53715
  }
53436
- }
53437
- if (chunk.type === "tool_use_end" && chunk.toolCall) {
53438
- const id = chunk.toolCall.id ?? "";
53439
- const builder = toolCallBuilders.get(id);
53440
- if (builder) {
53441
- const finalToolCall = {
53442
- id: builder.id,
53443
- name: chunk.toolCall.name ?? builder.name,
53444
- input: chunk.toolCall.input ?? builder.input,
53445
- geminiThoughtSignature: chunk.toolCall.geminiThoughtSignature ?? builder.geminiThoughtSignature
53446
- };
53447
- collectedToolCalls.push(finalToolCall);
53448
- } else if (chunk.toolCall.id && chunk.toolCall.name) {
53449
- collectedToolCalls.push({
53450
- id: chunk.toolCall.id,
53451
- name: chunk.toolCall.name,
53452
- input: chunk.toolCall.input ?? {},
53453
- geminiThoughtSignature: chunk.toolCall.geminiThoughtSignature
53454
- });
53716
+ try {
53717
+ if (chunk.type === "text" && chunk.text) {
53718
+ if (!thinkingEnded) {
53719
+ options.onThinkingEnd?.();
53720
+ thinkingEnded = true;
53721
+ }
53722
+ responseContent += chunk.text;
53723
+ finalContent += chunk.text;
53724
+ iterationTextChunks.push(chunk);
53725
+ }
53726
+ if (chunk.type === "tool_use_start" && chunk.toolCall) {
53727
+ flushLineBuffer();
53728
+ if (!thinkingEnded) {
53729
+ options.onThinkingEnd?.();
53730
+ thinkingEnded = true;
53731
+ }
53732
+ const id = chunk.toolCall.id ?? `tool_${toolCallBuilders.size}`;
53733
+ const toolName = chunk.toolCall.name ?? "";
53734
+ toolCallBuilders.set(id, {
53735
+ id,
53736
+ name: toolName,
53737
+ input: {},
53738
+ geminiThoughtSignature: chunk.toolCall.geminiThoughtSignature
53739
+ });
53740
+ if (toolName) {
53741
+ options.onToolPreparing?.(toolName);
53742
+ }
53743
+ }
53744
+ if (chunk.type === "tool_use_end" && chunk.toolCall) {
53745
+ const id = chunk.toolCall.id ?? "";
53746
+ const builder = toolCallBuilders.get(id);
53747
+ if (builder) {
53748
+ const finalToolCall = {
53749
+ id: builder.id,
53750
+ name: chunk.toolCall.name ?? builder.name,
53751
+ input: chunk.toolCall.input ?? builder.input,
53752
+ geminiThoughtSignature: chunk.toolCall.geminiThoughtSignature ?? builder.geminiThoughtSignature
53753
+ };
53754
+ collectedToolCalls.push(finalToolCall);
53755
+ } else if (chunk.toolCall.id && chunk.toolCall.name) {
53756
+ collectedToolCalls.push({
53757
+ id: chunk.toolCall.id,
53758
+ name: chunk.toolCall.name,
53759
+ input: chunk.toolCall.input ?? {},
53760
+ geminiThoughtSignature: chunk.toolCall.geminiThoughtSignature
53761
+ });
53762
+ }
53763
+ }
53764
+ if (chunk.type === "done") {
53765
+ if (chunk.stopReason) {
53766
+ lastStopReason = chunk.stopReason;
53767
+ }
53768
+ if (!thinkingEnded) {
53769
+ options.onThinkingEnd?.();
53770
+ thinkingEnded = true;
53771
+ }
53772
+ break;
53773
+ }
53774
+ } catch (chunkError) {
53775
+ const errorMsg = chunkError instanceof Error ? chunkError.message : String(chunkError);
53776
+ console.error(`[agent-loop] Error processing chunk: ${errorMsg}`);
53455
53777
  }
53456
53778
  }
53457
- if (chunk.type === "done") {
53458
- if (chunk.stopReason) {
53459
- lastStopReason = chunk.stopReason;
53460
- }
53461
- if (!thinkingEnded) {
53462
- options.onThinkingEnd?.();
53463
- thinkingEnded = true;
53464
- }
53465
- break;
53779
+ break;
53780
+ } catch (streamError) {
53781
+ const classification = classifyAgentLoopError(streamError, options.signal);
53782
+ if (classification.kind === "abort") {
53783
+ throw classification.original;
53784
+ }
53785
+ if (classification.kind === "provider_non_retryable") {
53786
+ throw classification.original;
53787
+ }
53788
+ streamAttempt++;
53789
+ if (shouldRetryStreamError(
53790
+ classification,
53791
+ responseContent,
53792
+ collectedToolCalls,
53793
+ streamAttempt,
53794
+ maxStreamAttempts
53795
+ )) {
53796
+ await pauseBeforeRetry(streamAttempt);
53797
+ continue;
53466
53798
  }
53467
- } catch (chunkError) {
53468
- const errorMsg = chunkError instanceof Error ? chunkError.message : String(chunkError);
53469
- console.error(`[agent-loop] Error processing chunk: ${errorMsg}`);
53799
+ throw classification.original;
53470
53800
  }
53471
53801
  }
53472
53802
  } catch (streamError) {
@@ -53544,6 +53874,15 @@ ${tail}`;
53544
53874
  });
53545
53875
  continue;
53546
53876
  }
53877
+ if (noToolRecovery.recover) {
53878
+ noToolRecoveryAttempts = 0;
53879
+ finalContent += await requestNoToolFallbackExplanation(
53880
+ responseContent,
53881
+ noToolRecovery.reason
53882
+ );
53883
+ addMessage(session, { role: "assistant", content: finalContent });
53884
+ break;
53885
+ }
53547
53886
  noToolRecoveryAttempts = 0;
53548
53887
  addMessage(session, { role: "assistant", content: responseContent });
53549
53888
  break;
@@ -53590,6 +53929,14 @@ ${tail}`;
53590
53929
  options.onToolSkipped?.(toolCall, "Use mcp_list_servers instead of coco mcp CLI");
53591
53930
  continue;
53592
53931
  }
53932
+ if (strictPlanModeEnabled && !STRICT_PLAN_MODE_ALLOWED_TOOLS.has(toolCall.name)) {
53933
+ declinedTools.set(
53934
+ toolCall.id,
53935
+ `Blocked by strict plan mode: tool '${toolCall.name}' is not read-only`
53936
+ );
53937
+ options.onToolSkipped?.(toolCall, "Blocked by strict plan mode");
53938
+ continue;
53939
+ }
53593
53940
  const trustPattern = getTrustPattern(toolCall.name, toolCall.input);
53594
53941
  const needsConfirmation = !options.skipConfirmation && !session.trustedTools.has(trustPattern) && requiresConfirmation(toolCall.name, toolCall.input);
53595
53942
  if (needsConfirmation) {
@@ -53674,29 +54021,29 @@ ${tail}`;
53674
54021
  const patterns = executed.input.patterns;
53675
54022
  const scope = executed.input.scope || "project";
53676
54023
  if (Array.isArray(patterns)) {
53677
- for (const p46 of patterns) {
54024
+ for (const p47 of patterns) {
53678
54025
  if (action === "allow") {
53679
- session.trustedTools.add(p46);
54026
+ session.trustedTools.add(p47);
53680
54027
  if (scope === "global") {
53681
- saveTrustedTool(p46, null, true).catch(() => {
54028
+ saveTrustedTool(p47, null, true).catch(() => {
53682
54029
  });
53683
54030
  } else {
53684
- saveTrustedTool(p46, session.projectPath, false).catch(() => {
54031
+ saveTrustedTool(p47, session.projectPath, false).catch(() => {
53685
54032
  });
53686
54033
  }
53687
- removeDeniedTool(p46, session.projectPath).catch(() => {
54034
+ removeDeniedTool(p47, session.projectPath).catch(() => {
53688
54035
  });
53689
54036
  } else if (action === "deny") {
53690
- session.trustedTools.delete(p46);
54037
+ session.trustedTools.delete(p47);
53691
54038
  if (scope === "global") {
53692
- removeTrustedTool(p46, session.projectPath, true).catch(() => {
54039
+ removeTrustedTool(p47, session.projectPath, true).catch(() => {
53693
54040
  });
53694
54041
  } else {
53695
- saveDeniedTool(p46, session.projectPath).catch(() => {
54042
+ saveDeniedTool(p47, session.projectPath).catch(() => {
53696
54043
  });
53697
54044
  }
53698
54045
  } else {
53699
- session.trustedTools.delete(p46);
54046
+ session.trustedTools.delete(p47);
53700
54047
  }
53701
54048
  }
53702
54049
  }
@@ -53742,10 +54089,21 @@ ${tail}`;
53742
54089
  }
53743
54090
  const executedCall = executedTools.find((e) => e.id === toolCall.id);
53744
54091
  if (executedCall) {
53745
- const truncatedOutput = truncateInlineResult(executedCall.result.output, toolCall.name);
54092
+ if (outputOffloadObservationEnabled && executedCall.result.output.length > OUTPUT_OFFLOAD_OBSERVATION_THRESHOLD) {
54093
+ observedLargeOutputs++;
54094
+ observedLargeOutputChars += executedCall.result.output.length;
54095
+ }
54096
+ let toolResultContent = truncateInlineResult(executedCall.result.output, toolCall.name);
54097
+ if (outputOffloadObservationEnabled && executedCall.result.success && executedCall.result.output.length > OUTPUT_OFFLOAD_PERSIST_THRESHOLD) {
54098
+ try {
54099
+ const offloaded = await offloadLargeOutput(toolCall, executedCall.result.output);
54100
+ toolResultContent = offloaded.toolResultContent;
54101
+ } catch {
54102
+ }
54103
+ }
53746
54104
  const transformedOutput = repeatedOutputSuppressor.transform(
53747
54105
  toolCall.name,
53748
- truncatedOutput
54106
+ toolResultContent
53749
54107
  );
53750
54108
  if (transformedOutput.suppressed) {
53751
54109
  repeatedOutputsSuppressed++;
@@ -53993,9 +54351,30 @@ var PLAN_MODE_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
53993
54351
  "spawnSimpleAgent",
53994
54352
  "checkAgentCapability"
53995
54353
  ]);
54354
+ var STRICT_PLAN_MODE_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
54355
+ "glob",
54356
+ "read_file",
54357
+ "list_dir",
54358
+ "tree",
54359
+ "grep",
54360
+ "find_in_file",
54361
+ "semantic_search",
54362
+ "codebase_map",
54363
+ "git_status",
54364
+ "git_log",
54365
+ "git_diff",
54366
+ "git_show",
54367
+ "git_branch",
54368
+ "recall_memory",
54369
+ "list_memories",
54370
+ "list_checkpoints"
54371
+ ]);
53996
54372
  function filterReadOnlyTools(tools) {
53997
54373
  return tools.filter((tool) => PLAN_MODE_ALLOWED_TOOLS.has(tool.name));
53998
54374
  }
54375
+ function filterStrictPlanModeTools(tools) {
54376
+ return tools.filter((tool) => STRICT_PLAN_MODE_ALLOWED_TOOLS.has(tool.name));
54377
+ }
53999
54378
 
54000
54379
  // src/cli/repl/index.ts
54001
54380
  init_error_resilience();
@@ -54775,8 +55154,8 @@ async function startRepl(options = {}) {
54775
55154
  };
54776
55155
  const getAutoSwitchCandidates = (current) => {
54777
55156
  const ordered = [];
54778
- const push = (p46) => {
54779
- if (p46 !== current && !ordered.includes(p46)) ordered.push(p46);
55157
+ const push = (p47) => {
55158
+ if (p47 !== current && !ordered.includes(p47)) ordered.push(p47);
54780
55159
  };
54781
55160
  if (current === "openai") {
54782
55161
  push("codex");
@@ -54814,7 +55193,7 @@ async function startRepl(options = {}) {
54814
55193
  "lmstudio",
54815
55194
  "ollama"
54816
55195
  ];
54817
- for (const p46 of genericOrder) push(p46);
55196
+ for (const p47 of genericOrder) push(p47);
54818
55197
  return ordered;
54819
55198
  };
54820
55199
  const attemptAutoProviderSwitch = async (reason, originalMessage) => {