@corbat-tech/coco 2.2.4 → 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/README.md +5 -3
- package/dist/cli/index.js +302 -95
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +22 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: "/
|
|
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
|
-
|
|
28952
|
-
|
|
28953
|
-
|
|
28954
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
32839
|
-
console.log(
|
|
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
|
|
32988
|
-
console.log(chalk25.dim(" /permissions view
|
|
32989
|
-
console.log(chalk25.dim(" /permissions reset
|
|
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/
|
|
33233
|
+
// src/cli/repl/quality-loop.ts
|
|
33028
33234
|
init_paths();
|
|
33029
|
-
var
|
|
33235
|
+
var qualityLoopEnabled = true;
|
|
33030
33236
|
var hintShown = false;
|
|
33031
|
-
function
|
|
33032
|
-
return
|
|
33237
|
+
function isQualityLoop() {
|
|
33238
|
+
return qualityLoopEnabled;
|
|
33033
33239
|
}
|
|
33034
|
-
function
|
|
33035
|
-
|
|
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
|
|
33067
|
-
return chalk25.dim(" tip: ") + chalk25.magenta("/
|
|
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
|
|
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
|
-
|
|
33105
|
-
|
|
33106
|
-
|
|
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
|
|
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.
|
|
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
|
|
33126
|
-
const marker = "
|
|
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
|
|
33362
|
+
function getQualityLoopSystemPrompt() {
|
|
33156
33363
|
return `
|
|
33157
|
-
##
|
|
33364
|
+
## Quality Loop Mode (ACTIVE)
|
|
33158
33365
|
|
|
33159
|
-
You are operating in
|
|
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
|
-
|
|
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/
|
|
33194
|
-
var
|
|
33195
|
-
name: "
|
|
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: "/
|
|
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 =
|
|
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("
|
|
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 /
|
|
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 = !
|
|
33440
|
+
newState = !isQualityLoop();
|
|
33234
33441
|
}
|
|
33235
|
-
|
|
33236
|
-
|
|
33442
|
+
setQualityLoop(newState);
|
|
33443
|
+
saveQualityLoopPreference(newState).catch(() => {
|
|
33237
33444
|
});
|
|
33238
33445
|
console.log();
|
|
33239
33446
|
if (newState) {
|
|
33240
|
-
console.log(chalk25.magenta("
|
|
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("
|
|
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 /
|
|
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("/
|
|
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
|
-
|
|
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 (
|
|
41100
|
+
if (isQualityLoop()) {
|
|
40894
41101
|
return {
|
|
40895
|
-
str: "\u{1F965} " + chalk25.magenta("[
|
|
40896
|
-
// 🥥=2 + space=1 + [
|
|
40897
|
-
visualLen:
|
|
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 (
|
|
43198
|
-
parts.push(chalk25.green("\u{1F504}
|
|
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
|
-
|
|
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" && !
|
|
43740
|
+
if (typeof agentMessage === "string" && !isQualityLoop() && !wasHintShown() && looksLikeFeatureRequest(agentMessage)) {
|
|
43534
43741
|
markHintShown();
|
|
43535
|
-
console.log(
|
|
43742
|
+
console.log(formatQualityLoopHint());
|
|
43536
43743
|
}
|
|
43537
43744
|
console.log();
|
|
43538
43745
|
let cocoForkPrompt;
|
|
43539
|
-
if (
|
|
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" +
|
|
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 (
|
|
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 (
|
|
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 (
|
|
43736
|
-
const qualityResult =
|
|
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 =
|
|
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() ?? [];
|