@corbat-tech/coco 2.2.5 → 2.3.0

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
@@ -25659,6 +25659,120 @@ async function loadAgentConfig(projectPath) {
25659
25659
  }
25660
25660
  }
25661
25661
 
25662
+ // src/swarm/complexity-classifier.ts
25663
+ var AGENT_ROSTERS = {
25664
+ trivial: ["tdd-developer"],
25665
+ simple: ["tdd-developer", "qa"],
25666
+ moderate: ["tdd-developer", "qa", "architect"],
25667
+ complex: ["tdd-developer", "qa", "architect", "security-auditor", "external-reviewer"]
25668
+ };
25669
+ var LEVEL_THRESHOLDS = [
25670
+ [3, "trivial"],
25671
+ [5, "simple"],
25672
+ [7, "moderate"],
25673
+ [10, "complex"]
25674
+ ];
25675
+ var COMPLEX_KEYWORDS = ["auth", "security", "migration", "refactor"];
25676
+ var SIMPLE_KEYWORDS = ["fix", "typo", "style"];
25677
+ function scoreToLevel(score) {
25678
+ for (const [threshold, level] of LEVEL_THRESHOLDS) {
25679
+ if (score <= threshold) return level;
25680
+ }
25681
+ return "complex";
25682
+ }
25683
+ function classifyFeatureHeuristic(feature) {
25684
+ const description = feature.description ?? "";
25685
+ const criteria = feature.acceptanceCriteria ?? [];
25686
+ const deps = feature.dependencies ?? [];
25687
+ const wordCount = description.split(/\s+/).filter(Boolean).length;
25688
+ const lowerDesc = description.toLowerCase();
25689
+ let score = 1;
25690
+ const reasons = [
25691
+ `words=${wordCount}`,
25692
+ `criteria=${criteria.length}`,
25693
+ `deps=${deps.length}`
25694
+ ];
25695
+ if (wordCount > 50) score += 3;
25696
+ else if (wordCount > 20) score += 2;
25697
+ else if (wordCount > 5) score += 1;
25698
+ if (criteria.length >= 4) score += 3;
25699
+ else if (criteria.length >= 2) score += 2;
25700
+ else if (criteria.length >= 1) score += 1;
25701
+ if (deps.length >= 3) score += 8;
25702
+ else if (deps.length >= 1) score += 4;
25703
+ const hasComplexKeyword = COMPLEX_KEYWORDS.some((kw) => lowerDesc.includes(kw));
25704
+ if (hasComplexKeyword) {
25705
+ score += 8;
25706
+ reasons.push("complex-keywords");
25707
+ }
25708
+ const hasSimpleKeyword = SIMPLE_KEYWORDS.some((kw) => lowerDesc.includes(kw));
25709
+ if (hasSimpleKeyword) {
25710
+ score -= 2;
25711
+ reasons.push("simple-keywords");
25712
+ }
25713
+ score = Math.max(1, Math.min(10, score));
25714
+ const level = scoreToLevel(score);
25715
+ return {
25716
+ score,
25717
+ level,
25718
+ agents: AGENT_ROSTERS[level],
25719
+ reasoning: `Heuristic score ${score}: ${reasons.join(", ")}`
25720
+ };
25721
+ }
25722
+ async function classifyFeatureComplexity(feature, provider) {
25723
+ const criteriaCount = (feature.acceptanceCriteria ?? []).length;
25724
+ const depsCount = (feature.dependencies ?? []).length;
25725
+ const userMessage = `Rate the complexity of this software feature on a scale of 1-10.
25726
+
25727
+ Feature: ${feature.name}
25728
+ Description: ${feature.description ?? ""}
25729
+ Acceptance Criteria count: ${criteriaCount}
25730
+ Dependencies count: ${depsCount}
25731
+
25732
+ Complexity guidelines:
25733
+ - 1-3 (trivial): Fix, typo, style change \u2014 short description, 0-1 criteria, 0 deps
25734
+ - 4-5 (simple): Small enhancement \u2014 medium description, 2-3 criteria
25735
+ - 6-7 (moderate): Feature with logic \u2014 longer description, 4+ criteria or 1+ deps
25736
+ - 8-10 (complex): Security, auth, migration, heavy refactor \u2014 many deps or critical path
25737
+
25738
+ Return only JSON: { "score": <number 1-10>, "reasoning": "<brief explanation>" }`;
25739
+ try {
25740
+ const response = await provider.chat([{ role: "user", content: userMessage }], {
25741
+ maxTokens: 256,
25742
+ temperature: 0.2
25743
+ });
25744
+ const json2 = extractJsonScore(response.content);
25745
+ if (json2 !== null && typeof json2.score === "number") {
25746
+ const score = Math.max(1, Math.min(10, Math.round(json2.score)));
25747
+ const level = scoreToLevel(score);
25748
+ return {
25749
+ score,
25750
+ level,
25751
+ agents: AGENT_ROSTERS[level],
25752
+ reasoning: json2.reasoning ?? `LLM score: ${score}`
25753
+ };
25754
+ }
25755
+ } catch {
25756
+ }
25757
+ return classifyFeatureHeuristic(feature);
25758
+ }
25759
+ function extractJsonScore(text13) {
25760
+ try {
25761
+ const stripped = text13.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "").trim();
25762
+ return JSON.parse(stripped);
25763
+ } catch {
25764
+ const match = text13.match(/\{[\s\S]*?\}/);
25765
+ if (match) {
25766
+ try {
25767
+ return JSON.parse(match[0]);
25768
+ } catch {
25769
+ return null;
25770
+ }
25771
+ }
25772
+ return null;
25773
+ }
25774
+ }
25775
+
25662
25776
  // src/swarm/task-board.ts
25663
25777
  async function createBoard(projectPath, spec) {
25664
25778
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -26199,6 +26313,12 @@ var AGENT_DEFINITIONS = {
26199
26313
  };
26200
26314
 
26201
26315
  // src/swarm/lifecycle.ts
26316
+ var COMPLEXITY_RANK = {
26317
+ trivial: 0,
26318
+ simple: 1,
26319
+ moderate: 2,
26320
+ complex: 3
26321
+ };
26202
26322
  async function runSwarmLifecycle(options) {
26203
26323
  const ctx = {
26204
26324
  options,
@@ -26415,17 +26535,33 @@ async function processFeature(ctx, feature) {
26415
26535
  );
26416
26536
  continue;
26417
26537
  }
26538
+ const classified = ctx.options.skipComplexityCheck ? {
26539
+ score: 10,
26540
+ level: "complex",
26541
+ agents: AGENT_ROSTERS.complex} : await classifyFeatureComplexity(feature, provider);
26542
+ const threshold = ctx.options.complexityThreshold;
26543
+ const roster = threshold !== void 0 && COMPLEXITY_RANK[classified.level] >= COMPLEXITY_RANK[threshold] ? {
26544
+ score: 10,
26545
+ level: "complex",
26546
+ agents: AGENT_ROSTERS.complex,
26547
+ reasoning: `complexityThreshold=${threshold} (feature classified as ${classified.level})`
26548
+ } : classified;
26549
+ progress(
26550
+ ctx.options,
26551
+ "feature_loop",
26552
+ `Feature: ${feature.name} \u2014 complexity=${roster.level} (score=${roster.score}) roster=[${roster.agents.join(",")}]`
26553
+ );
26418
26554
  const [archReview, secReview, qaReview] = await Promise.all([
26419
- runArchReview(feature, provider, agentConfig.architect),
26420
- runSecurityAudit(feature, provider, agentConfig["security-auditor"]),
26421
- runQAReview(feature, provider, agentConfig.qa)
26555
+ roster.agents.includes("architect") ? runArchReview(feature, provider, agentConfig.architect) : Promise.resolve(void 0),
26556
+ roster.agents.includes("security-auditor") ? runSecurityAudit(feature, provider, agentConfig["security-auditor"]) : Promise.resolve(void 0),
26557
+ roster.agents.includes("qa") ? runQAReview(feature, provider, agentConfig.qa) : Promise.resolve(void 0)
26422
26558
  ]);
26423
- const extReview = await runExternalReviewer(
26559
+ const extReview = roster.agents.includes("external-reviewer") ? await runExternalReviewer(
26424
26560
  feature,
26425
26561
  { arch: archReview, security: secReview, qa: qaReview },
26426
26562
  provider,
26427
26563
  agentConfig["external-reviewer"]
26428
- );
26564
+ ) : synthesizeLocalReviews({ arch: archReview, security: secReview, qa: qaReview });
26429
26565
  lastReviewScore = extReview.score;
26430
26566
  await emitGate(
26431
26567
  projectPath,
@@ -26720,13 +26856,17 @@ Return JSON with: { "score": number, "issues": ["..."], "summary": "..." }`;
26720
26856
  }
26721
26857
  async function runExternalReviewer(feature, reviews, provider, _config) {
26722
26858
  const prompt = AGENT_DEFINITIONS["external-reviewer"].systemPrompt;
26859
+ const reviewLines = [
26860
+ reviews.arch ? `Architecture review: ${JSON.stringify(reviews.arch)}` : null,
26861
+ reviews.security ? `Security audit: ${JSON.stringify(reviews.security)}` : null,
26862
+ reviews.qa ? `QA review: ${JSON.stringify(reviews.qa)}` : null
26863
+ ].filter(Boolean).join("\n");
26723
26864
  const userMessage = `Synthesize these reviews for feature: ${feature.name}
26724
26865
 
26725
- Architecture review: ${JSON.stringify(reviews.arch)}
26726
- Security audit: ${JSON.stringify(reviews.security)}
26727
- QA review: ${JSON.stringify(reviews.qa)}
26866
+ ${reviewLines}
26728
26867
 
26729
26868
  Return JSON with: { "verdict": "APPROVE|REQUEST_CHANGES|REJECT", "score": number, "blockers": ["..."], "summary": "..." }`;
26869
+ const fallback = synthesizeLocalReviews(reviews);
26730
26870
  try {
26731
26871
  const response = await provider.chat([{ role: "user", content: userMessage }], {
26732
26872
  system: prompt,
@@ -26734,27 +26874,24 @@ Return JSON with: { "verdict": "APPROVE|REQUEST_CHANGES|REJECT", "score": number
26734
26874
  temperature: 0.4
26735
26875
  });
26736
26876
  const json2 = extractJson(response.content);
26737
- const avgScore = Math.round(
26738
- (reviews.arch.score + reviews.security.score + reviews.qa.score) / 3
26739
- );
26740
- return json2 ?? {
26741
- verdict: avgScore >= 85 ? "APPROVE" : "REQUEST_CHANGES",
26742
- score: avgScore,
26743
- blockers: [],
26744
- summary: `Synthesized review score: ${avgScore}`
26745
- };
26877
+ return json2 ?? fallback;
26746
26878
  } catch {
26747
- const avgScore = Math.round(
26748
- (reviews.arch.score + reviews.security.score + reviews.qa.score) / 3
26749
- );
26750
- return {
26751
- verdict: avgScore >= 85 ? "APPROVE" : "REQUEST_CHANGES",
26752
- score: avgScore,
26753
- blockers: [],
26754
- summary: `External review score: ${avgScore}`
26755
- };
26879
+ return fallback;
26756
26880
  }
26757
26881
  }
26882
+ function synthesizeLocalReviews(reviews) {
26883
+ const scores = [reviews.arch?.score, reviews.security?.score, reviews.qa?.score].filter(
26884
+ (s) => s !== void 0
26885
+ );
26886
+ const avgScore = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 85;
26887
+ const reviewCount = scores.length;
26888
+ return {
26889
+ verdict: avgScore >= 85 ? "APPROVE" : "REQUEST_CHANGES",
26890
+ score: avgScore,
26891
+ blockers: [],
26892
+ summary: `Local synthesis (${reviewCount} review${reviewCount !== 1 ? "s" : ""}): score ${avgScore}`
26893
+ };
26894
+ }
26758
26895
  async function runIntegratorAgent(spec, featureResults, provider, _config) {
26759
26896
  const prompt = AGENT_DEFINITIONS.integrator.systemPrompt;
26760
26897
  const results = Array.from(featureResults.values());
@@ -26810,14 +26947,20 @@ async function emitGate(projectPath, gate, passed, reason) {
26810
26947
  function topologicalSort(features) {
26811
26948
  const sorted = [];
26812
26949
  const visited = /* @__PURE__ */ new Set();
26950
+ const inProgress = /* @__PURE__ */ new Set();
26813
26951
  const featureMap = new Map(features.map((f) => [f.id, f]));
26814
26952
  function visit(featureId) {
26815
26953
  if (visited.has(featureId)) return;
26954
+ if (inProgress.has(featureId)) {
26955
+ throw new Error(`Circular dependency detected involving feature "${featureId}"`);
26956
+ }
26816
26957
  const feature = featureMap.get(featureId);
26817
26958
  if (!feature) return;
26959
+ inProgress.add(featureId);
26818
26960
  for (const depId of feature.dependencies) {
26819
26961
  visit(depId);
26820
26962
  }
26963
+ inProgress.delete(featureId);
26821
26964
  visited.add(featureId);
26822
26965
  sorted.push(feature);
26823
26966
  }
@@ -26984,7 +27127,7 @@ var helpCommand = {
26984
27127
  title: "Quality Mode",
26985
27128
  commands: [
26986
27129
  {
26987
- cmd: "/coco [on|off]",
27130
+ cmd: "/quality [on|off]",
26988
27131
  desc: "Auto-test, self-review, iterate until quality \u2265 85/100",
26989
27132
  highlight: true
26990
27133
  }
@@ -28725,6 +28868,7 @@ async function switchProvider(initialProvider, session) {
28725
28868
  const userFacingProviderId = initialProvider.id;
28726
28869
  let internalProviderId = initialProvider.id;
28727
28870
  let selectedAuthMethod = "apikey";
28871
+ let newApiKeyForSaving = null;
28728
28872
  if (newProvider.id === "lmstudio" || newProvider.id === "ollama") {
28729
28873
  const result = newProvider.id === "ollama" ? await setupOllamaProvider() : await setupLMStudioProvider();
28730
28874
  if (!result) {
@@ -28870,6 +29014,7 @@ Using existing API key...`));
28870
29014
  }
28871
29015
  process.env[newProvider.envVar] = key;
28872
29016
  selectedAuthMethod = "apikey";
29017
+ newApiKeyForSaving = key;
28873
29018
  }
28874
29019
  } else if (authChoice === "remove") {
28875
29020
  const removeOptions = [];
@@ -28927,6 +29072,7 @@ ${newProvider.emoji} ${newProvider.name} is not configured.`));
28927
29072
  }
28928
29073
  process.env[newProvider.envVar] = key;
28929
29074
  selectedAuthMethod = "apikey";
29075
+ newApiKeyForSaving = key;
28930
29076
  }
28931
29077
  }
28932
29078
  const recommendedModel = getRecommendedModel(newProvider.id);
@@ -28948,16 +29094,25 @@ ${newProvider.emoji} ${newProvider.name} is not configured.`));
28948
29094
  spinner19.stop(chalk25.green("Connected!"));
28949
29095
  session.config.provider.type = userFacingProviderId;
28950
29096
  session.config.provider.model = newModel;
28951
- await saveProviderPreference(
28952
- userFacingProviderId,
28953
- newModel,
28954
- selectedAuthMethod
28955
- );
29097
+ if (newApiKeyForSaving) {
29098
+ await saveConfiguration({
29099
+ type: userFacingProviderId,
29100
+ model: newModel,
29101
+ apiKey: newApiKeyForSaving
29102
+ });
29103
+ } else {
29104
+ await saveProviderPreference(
29105
+ userFacingProviderId,
29106
+ newModel,
29107
+ selectedAuthMethod
29108
+ );
29109
+ }
28956
29110
  console.log(chalk25.green(`
28957
29111
  \u2713 Switched to ${newProvider.emoji} ${newProvider.name}`));
28958
29112
  console.log(chalk25.dim(` Model: ${newModel}`));
28959
29113
  if (selectedAuthMethod === "oauth") {
28960
- console.log(chalk25.dim(` Auth: ChatGPT subscription (OAuth)`));
29114
+ const authLabel = newProvider.id === "gemini" ? "Google account (OAuth)" : "ChatGPT subscription (OAuth)";
29115
+ console.log(chalk25.dim(` Auth: ${authLabel}`));
28961
29116
  }
28962
29117
  console.log(chalk25.dim(` Use /model to change models
28963
29118
  `));
@@ -32654,7 +32809,6 @@ var RECOMMENDED_PROJECT = [
32654
32809
  "copy_file",
32655
32810
  "move_file",
32656
32811
  "git_add",
32657
- "git_commit",
32658
32812
  "run_tests",
32659
32813
  "run_test_file",
32660
32814
  "run_script",
@@ -32673,12 +32827,28 @@ var RECOMMENDED_PROJECT = [
32673
32827
  "bash:npm:test",
32674
32828
  "bash:npm:ci",
32675
32829
  "bash:pnpm:install",
32830
+ "bash:pnpm:i",
32676
32831
  "bash:pnpm:run",
32677
32832
  "bash:pnpm:test",
32833
+ "bash:pnpm:typecheck",
32834
+ "bash:pnpm:lint",
32835
+ "bash:pnpm:build",
32836
+ "bash:pnpm:check",
32837
+ "bash:pnpm:format",
32838
+ "bash:pnpm:dev",
32839
+ "bash:pnpm:add",
32840
+ "bash:pnpm:remove",
32841
+ "bash:pnpm:update",
32842
+ "bash:pnpm:exec",
32843
+ "bash:pnpm:rebuild",
32678
32844
  "bash:yarn:install",
32679
32845
  "bash:yarn:run",
32680
32846
  "bash:yarn:test",
32681
32847
  "bash:node",
32848
+ "bash:vitest",
32849
+ "bash:tsc",
32850
+ "bash:tsx",
32851
+ "bash:oxlint",
32682
32852
  // ── Bash: JVM toolchain ──
32683
32853
  "bash:java",
32684
32854
  "bash:javac",
@@ -32706,11 +32876,13 @@ var RECOMMENDED_PROJECT = [
32706
32876
  "bash:go:test",
32707
32877
  "bash:go:vet",
32708
32878
  "bash:pip:install",
32709
- // ── Bash: git local (staging only — push is in ASK) ──
32710
- "bash:git:add",
32711
- "bash:git:commit"
32879
+ // ── Bash: git local (staging only — commit and push are in ASK) ──
32880
+ "bash:git:add"
32712
32881
  ];
32713
32882
  var ALWAYS_ASK = [
32883
+ // ── Git commit — always ask by default; use /permissions allow-commits to opt in ──
32884
+ "git_commit",
32885
+ "bash:git:commit",
32714
32886
  // ── Coco native (risky) ──
32715
32887
  "delete_file",
32716
32888
  "git_push",
@@ -32787,6 +32959,7 @@ var RECOMMENDED_DENY = [
32787
32959
  // ── Package publishing ──
32788
32960
  "bash:npm:publish",
32789
32961
  "bash:yarn:publish",
32962
+ "bash:pnpm:publish",
32790
32963
  "bash:cargo:publish",
32791
32964
  // ── Disk / low-level destructive ──
32792
32965
  "bash:dd",
@@ -32835,8 +33008,10 @@ async function showPermissionSuggestion() {
32835
33008
  console.log(chalk25.magenta.bold(" \u{1F4CB} Recommended Permissions"));
32836
33009
  console.log();
32837
33010
  console.log(chalk25.dim(" Coco has a curated set of tool permissions for developers:"));
32838
- console.log(chalk25.dim(" \u2022 Allow: file read/write, search, git (local), build, tests..."));
32839
- console.log(chalk25.dim(" \u2022 Ask each time: curl, rm, git pull, docker exec, cloud..."));
33011
+ console.log(chalk25.dim(" \u2022 Allow: file read/write, search, git staging, build, tests..."));
33012
+ console.log(
33013
+ chalk25.dim(" \u2022 Ask each time: git commit, curl, rm, git pull, docker exec, cloud...")
33014
+ );
32840
33015
  console.log(chalk25.dim(" \u2022 Deny: sudo, git push, git rebase, docker push, k8s apply..."));
32841
33016
  console.log();
32842
33017
  console.log(chalk25.dim(" Stored in ~/.coco/trusted-tools.json \u2014 edit manually or let"));
@@ -32927,7 +33102,7 @@ var permissionsCommand = {
32927
33102
  name: "permissions",
32928
33103
  aliases: ["perms"],
32929
33104
  description: "Manage tool permissions and recommended allowlist",
32930
- usage: "/permissions [apply|view|reset]",
33105
+ usage: "/permissions [apply|view|reset|allow-commits|revoke-commits]",
32931
33106
  async execute(args, session) {
32932
33107
  const subcommand = args[0]?.toLowerCase() ?? "status";
32933
33108
  switch (subcommand) {
@@ -32940,6 +33115,12 @@ var permissionsCommand = {
32940
33115
  case "reset":
32941
33116
  await resetPermissions(session);
32942
33117
  return false;
33118
+ case "allow-commits":
33119
+ await allowCommits(session);
33120
+ return false;
33121
+ case "revoke-commits":
33122
+ await revokeCommits(session);
33123
+ return false;
32943
33124
  case "status":
32944
33125
  default:
32945
33126
  await showStatus(session);
@@ -32984,9 +33165,15 @@ async function showStatus(session) {
32984
33165
  }
32985
33166
  }
32986
33167
  console.log();
32987
- console.log(chalk25.dim(" /permissions apply \u2014 Apply recommended permissions"));
32988
- console.log(chalk25.dim(" /permissions view \u2014 View recommended template"));
32989
- console.log(chalk25.dim(" /permissions reset \u2014 Reset to empty"));
33168
+ console.log(chalk25.dim(" /permissions apply \u2014 Apply recommended permissions"));
33169
+ console.log(chalk25.dim(" /permissions view \u2014 View recommended template"));
33170
+ console.log(chalk25.dim(" /permissions reset \u2014 Reset to empty"));
33171
+ console.log(
33172
+ chalk25.dim(" /permissions allow-commits \u2014 Auto-approve git commit for this project")
33173
+ );
33174
+ console.log(
33175
+ chalk25.dim(" /permissions revoke-commits \u2014 Require confirmation for git commit again")
33176
+ );
32990
33177
  console.log();
32991
33178
  }
32992
33179
  async function applyRecommended(session) {
@@ -33000,6 +33187,25 @@ async function applyRecommended(session) {
33000
33187
  console.log(chalk25.green(" \u2713 Recommended permissions applied!"));
33001
33188
  console.log(chalk25.dim(" Use /permissions to review."));
33002
33189
  }
33190
+ async function allowCommits(session) {
33191
+ const commitTools = ["git_commit", "bash:git:commit"];
33192
+ for (const tool of commitTools) {
33193
+ session.trustedTools.add(tool);
33194
+ await saveTrustedTool(tool, session.projectPath, false);
33195
+ }
33196
+ console.log(chalk25.green(" \u2713 git commit will be auto-approved for this project"));
33197
+ console.log(chalk25.dim(" Use /permissions revoke-commits to require confirmation again."));
33198
+ }
33199
+ async function revokeCommits(session) {
33200
+ const commitTools = ["git_commit", "bash:git:commit"];
33201
+ for (const tool of commitTools) {
33202
+ session.trustedTools.delete(tool);
33203
+ await removeTrustedTool(tool, session.projectPath, false);
33204
+ await removeTrustedTool(tool, session.projectPath, true);
33205
+ }
33206
+ console.log(chalk25.yellow(" \u25CB git commit will now require confirmation for this project"));
33207
+ console.log(chalk25.dim(" Use /permissions allow-commits to enable auto-approve again."));
33208
+ }
33003
33209
  async function resetPermissions(session) {
33004
33210
  const confirmed = await p25.confirm({
33005
33211
  message: "Reset all tool permissions? This removes all trusted tools.",
@@ -33024,15 +33230,15 @@ async function resetPermissions(session) {
33024
33230
  console.log(chalk25.green(" \u2713 All tool permissions reset."));
33025
33231
  }
33026
33232
 
33027
- // src/cli/repl/coco-mode.ts
33233
+ // src/cli/repl/quality-loop.ts
33028
33234
  init_paths();
33029
- var cocoModeEnabled = true;
33235
+ var qualityLoopEnabled = true;
33030
33236
  var hintShown = false;
33031
- function isCocoMode() {
33032
- return cocoModeEnabled;
33237
+ function isQualityLoop() {
33238
+ return qualityLoopEnabled;
33033
33239
  }
33034
- function setCocoMode(enabled) {
33035
- cocoModeEnabled = enabled;
33240
+ function setQualityLoop(enabled) {
33241
+ qualityLoopEnabled = enabled;
33036
33242
  }
33037
33243
  function wasHintShown() {
33038
33244
  return hintShown;
@@ -33063,8 +33269,8 @@ function looksLikeFeatureRequest(input) {
33063
33269
  ];
33064
33270
  return featureKeywords.some((re) => re.test(trimmed));
33065
33271
  }
33066
- function formatCocoHint() {
33067
- return chalk25.dim(" tip: ") + chalk25.magenta("/coco") + chalk25.dim(" enables auto-test & iterate until quality converges");
33272
+ function formatQualityLoopHint() {
33273
+ return chalk25.dim(" tip: ") + chalk25.magenta("/quality") + chalk25.dim(" enables auto-test & iterate until quality converges");
33068
33274
  }
33069
33275
  function formatQualityResult(result) {
33070
33276
  const lines = [];
@@ -33097,19 +33303,20 @@ function formatQualityResult(result) {
33097
33303
  lines.push("");
33098
33304
  return lines.join("\n");
33099
33305
  }
33100
- async function loadCocoModePreference() {
33306
+ async function loadQualityLoopPreference() {
33101
33307
  try {
33102
33308
  const content = await fs32__default.readFile(CONFIG_PATHS.config, "utf-8");
33103
33309
  const config = JSON.parse(content);
33104
- if (typeof config.cocoMode === "boolean") {
33105
- cocoModeEnabled = config.cocoMode;
33106
- return config.cocoMode;
33310
+ const value = config.qualityLoop ?? config.cocoMode;
33311
+ if (typeof value === "boolean") {
33312
+ qualityLoopEnabled = value;
33313
+ return value;
33107
33314
  }
33108
33315
  } catch {
33109
33316
  }
33110
33317
  return true;
33111
33318
  }
33112
- async function saveCocoModePreference(enabled) {
33319
+ async function saveQualityLoopPreference(enabled) {
33113
33320
  try {
33114
33321
  let config = {};
33115
33322
  try {
@@ -33117,13 +33324,13 @@ async function saveCocoModePreference(enabled) {
33117
33324
  config = JSON.parse(content);
33118
33325
  } catch {
33119
33326
  }
33120
- config.cocoMode = enabled;
33327
+ config.qualityLoop = enabled;
33121
33328
  await fs32__default.writeFile(CONFIG_PATHS.config, JSON.stringify(config, null, 2) + "\n");
33122
33329
  } catch {
33123
33330
  }
33124
33331
  }
33125
- function parseCocoQualityReport(content) {
33126
- const marker = "COCO_QUALITY_REPORT";
33332
+ function parseQualityLoopReport(content) {
33333
+ const marker = "QUALITY_LOOP_REPORT";
33127
33334
  const idx = content.indexOf(marker);
33128
33335
  if (idx === -1) return null;
33129
33336
  const block = content.slice(idx);
@@ -33152,11 +33359,11 @@ function parseCocoQualityReport(content) {
33152
33359
  securityScore: isNaN(security) ? void 0 : security
33153
33360
  };
33154
33361
  }
33155
- function getCocoModeSystemPrompt() {
33362
+ function getQualityLoopSystemPrompt() {
33156
33363
  return `
33157
- ## COCO Quality Mode (ACTIVE)
33364
+ ## Quality Loop Mode (ACTIVE)
33158
33365
 
33159
- You are operating in COCO quality mode. After implementing code changes, you MUST follow this iteration cycle:
33366
+ You are operating in quality loop mode. After implementing code changes, you MUST follow this iteration cycle:
33160
33367
 
33161
33368
  1. **Implement** the requested changes (code + tests)
33162
33369
  2. **Run tests** using the run_tests or bash_exec tool
@@ -33171,7 +33378,7 @@ You are operating in COCO quality mode. After implementing code changes, you MUS
33171
33378
  After completing the cycle, output a quality summary in this exact format:
33172
33379
 
33173
33380
  \`\`\`
33174
- COCO_QUALITY_REPORT
33381
+ QUALITY_LOOP_REPORT
33175
33382
  score_history: [first_score, ..., final_score]
33176
33383
  tests_passed: X
33177
33384
  tests_total: Y
@@ -33190,12 +33397,12 @@ Key rules:
33190
33397
  - Report honestly - don't inflate scores`;
33191
33398
  }
33192
33399
 
33193
- // src/cli/repl/commands/coco.ts
33194
- var cocoCommand = {
33195
- name: "coco",
33196
- aliases: [],
33400
+ // src/cli/repl/commands/quality.ts
33401
+ var qualityCommand = {
33402
+ name: "quality",
33403
+ aliases: ["coco"],
33197
33404
  description: "Toggle quality mode \u2014 auto-test, self-review, iterate until converged",
33198
- usage: "/coco [on|off]",
33405
+ usage: "/quality [on|off]",
33199
33406
  async execute(args, session) {
33200
33407
  const arg = args[0]?.toLowerCase();
33201
33408
  let newState;
@@ -33204,12 +33411,12 @@ var cocoCommand = {
33204
33411
  } else if (arg === "off") {
33205
33412
  newState = false;
33206
33413
  } else if (arg === "status") {
33207
- const state = isCocoMode();
33414
+ const state = isQualityLoop();
33208
33415
  const skillAvailable = session.skillRegistry?.has("coco-fix-iterate");
33209
33416
  const modeType = state && skillAvailable ? chalk25.cyan(" (skill-based)") : state ? chalk25.dim(" (prompt-based)") : "";
33210
33417
  console.log();
33211
33418
  console.log(
33212
- chalk25.magenta(" COCO quality mode: ") + (state ? chalk25.green.bold("ON") : chalk25.dim("OFF")) + modeType
33419
+ chalk25.magenta(" Quality loop: ") + (state ? chalk25.green.bold("ON") : chalk25.dim("OFF")) + modeType
33213
33420
  );
33214
33421
  console.log();
33215
33422
  if (state) {
@@ -33225,24 +33432,24 @@ var cocoCommand = {
33225
33432
  console.log(chalk25.dim(" 3. Self-review against 12 quality dimensions"));
33226
33433
  console.log(chalk25.dim(" 4. Iterate until quality converges (\u226585/100)"));
33227
33434
  } else {
33228
- console.log(chalk25.dim(" Enable with /coco on for quality-driven development"));
33435
+ console.log(chalk25.dim(" Enable with /quality on for quality-driven development"));
33229
33436
  }
33230
33437
  console.log();
33231
33438
  return false;
33232
33439
  } else {
33233
- newState = !isCocoMode();
33440
+ newState = !isQualityLoop();
33234
33441
  }
33235
- setCocoMode(newState);
33236
- saveCocoModePreference(newState).catch(() => {
33442
+ setQualityLoop(newState);
33443
+ saveQualityLoopPreference(newState).catch(() => {
33237
33444
  });
33238
33445
  console.log();
33239
33446
  if (newState) {
33240
- console.log(chalk25.magenta(" COCO quality mode: ") + chalk25.green.bold("ON"));
33447
+ console.log(chalk25.magenta(" Quality loop: ") + chalk25.green.bold("ON"));
33241
33448
  console.log(
33242
33449
  chalk25.dim(" Agent will auto-test, self-review, and iterate until quality \u2265 85/100")
33243
33450
  );
33244
33451
  } else {
33245
- console.log(chalk25.magenta(" COCO quality mode: ") + chalk25.dim("OFF"));
33452
+ console.log(chalk25.magenta(" Quality loop: ") + chalk25.dim("OFF"));
33246
33453
  console.log(chalk25.dim(" Fast mode \u2014 agent responds without quality iteration"));
33247
33454
  }
33248
33455
  console.log();
@@ -33447,7 +33654,7 @@ var tutorialCommand = {
33447
33654
  {
33448
33655
  step: "3",
33449
33656
  title: "Enable quality mode",
33450
- desc: "Type /coco to enable auto-iteration: test \u2192 analyze \u2192 fix \u2192 repeat until score \u2265 85"
33657
+ desc: "Type /quality to enable auto-iteration: test \u2192 analyze \u2192 fix \u2192 repeat until score \u2265 85"
33451
33658
  },
33452
33659
  {
33453
33660
  step: "4",
@@ -33467,7 +33674,7 @@ var tutorialCommand = {
33467
33674
  }
33468
33675
  console.log(chalk25.bold("Useful commands:"));
33469
33676
  console.log(
33470
- ` ${chalk25.yellow("/coco")} ${chalk25.dim("Toggle quality mode (auto-iteration)")}`
33677
+ ` ${chalk25.yellow("/quality")} ${chalk25.dim("Toggle quality mode (auto-iteration)")}`
33471
33678
  );
33472
33679
  console.log(` ${chalk25.yellow("/init")} ${chalk25.dim("Initialize a new project")}`);
33473
33680
  console.log(` ${chalk25.yellow("/help")} ${chalk25.dim("See all available commands")}`);
@@ -40741,7 +40948,7 @@ var commands = [
40741
40948
  copyCommand,
40742
40949
  allowPathCommand,
40743
40950
  permissionsCommand,
40744
- cocoCommand,
40951
+ qualityCommand,
40745
40952
  fullAccessCommand,
40746
40953
  updateCocoCommand,
40747
40954
  imageCommand,
@@ -40890,11 +41097,11 @@ function createInputHandler(_session) {
40890
41097
  const getPrompt = () => {
40891
41098
  const imageIndicator = hasPendingImage() ? chalk25.cyan(" \u{1F4CE} 1 image") : "";
40892
41099
  const imageIndicatorLen = hasPendingImage() ? 10 : 0;
40893
- if (isCocoMode()) {
41100
+ if (isQualityLoop()) {
40894
41101
  return {
40895
- str: "\u{1F965} " + chalk25.magenta("[coco]") + " \u203A " + imageIndicator,
40896
- // 🥥=2 + space=1 + [coco]=6 + space=1 + ›=1 + space=1 = 12 + image indicator
40897
- visualLen: 12 + imageIndicatorLen
41102
+ str: "\u{1F965} " + chalk25.magenta("[quality]") + " \u203A " + imageIndicator,
41103
+ // 🥥=2 + space=1 + [quality]=9 + space=1 + ›=1 + space=1 = 15 + image indicator
41104
+ visualLen: 15 + imageIndicatorLen
40898
41105
  };
40899
41106
  }
40900
41107
  return {
@@ -43194,8 +43401,8 @@ function formatStatusBar(projectPath, config, gitCtx) {
43194
43401
  const providerName = config.provider.type;
43195
43402
  const modelName = config.provider.model || "default";
43196
43403
  parts.push(chalk25.dim(`${providerName}/`) + chalk25.cyan(modelName));
43197
- if (isCocoMode()) {
43198
- parts.push(chalk25.green("\u{1F504} coco"));
43404
+ if (isQualityLoop()) {
43405
+ parts.push(chalk25.green("\u{1F504} quality loop"));
43199
43406
  }
43200
43407
  if (isFullAccessMode()) {
43201
43408
  parts.push(chalk25.yellow("\u26A1 full-access"));
@@ -43268,7 +43475,7 @@ async function startRepl(options = {}) {
43268
43475
  const { loadFullAccessPreference: loadFullAccessPreference2 } = await Promise.resolve().then(() => (init_full_access_mode(), full_access_mode_exports));
43269
43476
  const { loadFullPowerRiskPreference: loadFullPowerRiskPreference2 } = await Promise.resolve().then(() => (init_full_power_risk_mode(), full_power_risk_mode_exports));
43270
43477
  await Promise.all([
43271
- loadCocoModePreference(),
43478
+ loadQualityLoopPreference(),
43272
43479
  loadFullAccessPreference2(),
43273
43480
  loadFullPowerRiskPreference2()
43274
43481
  ]);
@@ -43530,13 +43737,13 @@ async function startRepl(options = {}) {
43530
43737
  };
43531
43738
  let originalSystemPrompt;
43532
43739
  try {
43533
- if (typeof agentMessage === "string" && !isCocoMode() && !wasHintShown() && looksLikeFeatureRequest(agentMessage)) {
43740
+ if (typeof agentMessage === "string" && !isQualityLoop() && !wasHintShown() && looksLikeFeatureRequest(agentMessage)) {
43534
43741
  markHintShown();
43535
- console.log(formatCocoHint());
43742
+ console.log(formatQualityLoopHint());
43536
43743
  }
43537
43744
  console.log();
43538
43745
  let cocoForkPrompt;
43539
- if (isCocoMode()) {
43746
+ if (isQualityLoop()) {
43540
43747
  const skillId = "coco-fix-iterate";
43541
43748
  const skillAvailable = session.skillRegistry?.has(skillId);
43542
43749
  if (skillAvailable && typeof agentMessage === "string") {
@@ -43550,7 +43757,7 @@ async function startRepl(options = {}) {
43550
43757
  }
43551
43758
  } else {
43552
43759
  originalSystemPrompt = session.config.agent.systemPrompt;
43553
- session.config.agent.systemPrompt = originalSystemPrompt + "\n" + getCocoModeSystemPrompt();
43760
+ session.config.agent.systemPrompt = originalSystemPrompt + "\n" + getQualityLoopSystemPrompt();
43554
43761
  }
43555
43762
  }
43556
43763
  const effectiveMessage = cocoForkPrompt ?? agentMessage;
@@ -43628,7 +43835,7 @@ async function startRepl(options = {}) {
43628
43835
  }
43629
43836
  renderToolStart(result2.name, result2.input);
43630
43837
  renderToolEnd(result2);
43631
- if (isCocoMode()) {
43838
+ if (isQualityLoop()) {
43632
43839
  setSpinner("Processing results & checking quality...");
43633
43840
  } else {
43634
43841
  setSpinner("Processing...");
@@ -43645,7 +43852,7 @@ async function startRepl(options = {}) {
43645
43852
  if (!thinkingStartTime) return;
43646
43853
  const elapsed = Math.floor((Date.now() - thinkingStartTime) / 1e3);
43647
43854
  if (elapsed < 4) return;
43648
- if (isCocoMode()) {
43855
+ if (isQualityLoop()) {
43649
43856
  if (elapsed < 8) setSpinner("Analyzing request...");
43650
43857
  else if (elapsed < 15) setSpinner("Running quality checks...");
43651
43858
  else if (elapsed < 25) setSpinner("Iterating for quality...");
@@ -43732,8 +43939,8 @@ async function startRepl(options = {}) {
43732
43939
  continue;
43733
43940
  }
43734
43941
  console.log();
43735
- if (isCocoMode() && result.content) {
43736
- const qualityResult = parseCocoQualityReport(result.content);
43942
+ if (isQualityLoop() && result.content) {
43943
+ const qualityResult = parseQualityLoopReport(result.content);
43737
43944
  if (qualityResult) {
43738
43945
  console.log(formatQualityResult(qualityResult));
43739
43946
  }
@@ -43854,7 +44061,7 @@ async function printWelcome(session, gitCtx, mcpManager) {
43854
44061
  if (gitCtx) {
43855
44062
  console.log(` ${formatGitLine(gitCtx)}`);
43856
44063
  }
43857
- const cocoStatus = isCocoMode() ? chalk25.magenta(" \u{1F504} quality mode: ") + chalk25.green.bold("on") + chalk25.dim(" \u2014 iterates until quality \u2265 85. /coco to disable") : chalk25.dim(" \u{1F4A1} /coco on \u2014 enable auto-test & quality iteration");
44064
+ const cocoStatus = isQualityLoop() ? chalk25.magenta(" \u{1F504} quality mode: ") + chalk25.green.bold("on") + chalk25.dim(" \u2014 iterates until quality \u2265 85. /quality to disable") : chalk25.dim(" \u{1F4A1} /quality on \u2014 enable auto-test & quality iteration");
43858
44065
  console.log(cocoStatus);
43859
44066
  const skillTotal = session.skillRegistry?.size ?? 0;
43860
44067
  const mcpServers = mcpManager?.getConnectedServers() ?? [];