@corbat-tech/coco 1.1.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -173
- package/dist/cli/index.js +231 -141
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +94 -26
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -5973,17 +5973,17 @@ var QualityEvaluator = class {
|
|
|
5973
5973
|
maintainabilityResult
|
|
5974
5974
|
] = await Promise.all([
|
|
5975
5975
|
this.coverageAnalyzer.analyze().catch(() => null),
|
|
5976
|
-
this.securityScanner.scan(fileContents),
|
|
5977
|
-
this.complexityAnalyzer.analyze(targetFiles),
|
|
5978
|
-
this.duplicationAnalyzer.analyze(targetFiles),
|
|
5976
|
+
this.securityScanner.scan(fileContents).catch(() => ({ score: 0, vulnerabilities: [] })),
|
|
5977
|
+
this.complexityAnalyzer.analyze(targetFiles).catch(() => ({ score: 0, files: [] })),
|
|
5978
|
+
this.duplicationAnalyzer.analyze(targetFiles).catch(() => ({ score: 0, percentage: 0 })),
|
|
5979
5979
|
this.correctnessAnalyzer.analyze().catch(() => ({ score: 0 })),
|
|
5980
5980
|
this.completenessAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
|
|
5981
5981
|
this.robustnessAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
|
|
5982
5982
|
this.testQualityAnalyzer.analyze().catch(() => ({ score: 0 })),
|
|
5983
5983
|
this.documentationAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
|
|
5984
|
-
this.styleAnalyzer.analyze().catch(() => ({ score:
|
|
5985
|
-
this.readabilityAnalyzer.analyze(targetFiles).catch(() => ({ score:
|
|
5986
|
-
this.maintainabilityAnalyzer.analyze(targetFiles).catch(() => ({ score:
|
|
5984
|
+
this.styleAnalyzer.analyze().catch(() => ({ score: 0 })),
|
|
5985
|
+
this.readabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
|
|
5986
|
+
this.maintainabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 }))
|
|
5987
5987
|
]);
|
|
5988
5988
|
const dimensions = {
|
|
5989
5989
|
testCoverage: coverageResult?.lines.percentage ?? 0,
|
|
@@ -9992,6 +9992,9 @@ var AnthropicProvider = class {
|
|
|
9992
9992
|
try {
|
|
9993
9993
|
currentToolCall.input = currentToolInputJson ? JSON.parse(currentToolInputJson) : {};
|
|
9994
9994
|
} catch {
|
|
9995
|
+
console.warn(
|
|
9996
|
+
`[Anthropic] Failed to parse tool call arguments: ${currentToolInputJson?.slice(0, 100)}`
|
|
9997
|
+
);
|
|
9995
9998
|
currentToolCall.input = {};
|
|
9996
9999
|
}
|
|
9997
10000
|
yield {
|
|
@@ -10532,6 +10535,9 @@ var OpenAIProvider = class {
|
|
|
10532
10535
|
try {
|
|
10533
10536
|
input = builder.arguments ? JSON.parse(builder.arguments) : {};
|
|
10534
10537
|
} catch {
|
|
10538
|
+
console.warn(
|
|
10539
|
+
`[OpenAI] Failed to parse tool call arguments: ${builder.arguments?.slice(0, 100)}`
|
|
10540
|
+
);
|
|
10535
10541
|
}
|
|
10536
10542
|
yield {
|
|
10537
10543
|
type: "tool_use_end",
|
|
@@ -10820,7 +10826,16 @@ var OpenAIProvider = class {
|
|
|
10820
10826
|
).map((tc) => ({
|
|
10821
10827
|
id: tc.id,
|
|
10822
10828
|
name: tc.function.name,
|
|
10823
|
-
input:
|
|
10829
|
+
input: (() => {
|
|
10830
|
+
try {
|
|
10831
|
+
return JSON.parse(tc.function.arguments || "{}");
|
|
10832
|
+
} catch {
|
|
10833
|
+
console.warn(
|
|
10834
|
+
`[OpenAI] Failed to parse tool call arguments: ${tc.function.arguments?.slice(0, 100)}`
|
|
10835
|
+
);
|
|
10836
|
+
return {};
|
|
10837
|
+
}
|
|
10838
|
+
})()
|
|
10824
10839
|
}));
|
|
10825
10840
|
}
|
|
10826
10841
|
/**
|
|
@@ -11475,7 +11490,10 @@ var GeminiProvider = class {
|
|
|
11475
11490
|
for (const part of candidate.content.parts) {
|
|
11476
11491
|
if ("functionCall" in part && part.functionCall) {
|
|
11477
11492
|
const funcCall = part.functionCall;
|
|
11478
|
-
const
|
|
11493
|
+
const sortedArgs = funcCall.args ? Object.keys(funcCall.args).sort().map(
|
|
11494
|
+
(k) => `${k}:${JSON.stringify(funcCall.args[k])}`
|
|
11495
|
+
).join(",") : "";
|
|
11496
|
+
const callKey = `${funcCall.name}-${sortedArgs}`;
|
|
11479
11497
|
if (!emittedToolCalls.has(callKey)) {
|
|
11480
11498
|
emittedToolCalls.add(callKey);
|
|
11481
11499
|
const toolCall = {
|
|
@@ -11507,9 +11525,18 @@ var GeminiProvider = class {
|
|
|
11507
11525
|
}
|
|
11508
11526
|
/**
|
|
11509
11527
|
* Count tokens (approximate)
|
|
11528
|
+
*
|
|
11529
|
+
* Gemini uses a SentencePiece tokenizer. The average ratio varies:
|
|
11530
|
+
* - English text: ~4 characters per token
|
|
11531
|
+
* - Code: ~3.2 characters per token
|
|
11532
|
+
* - Mixed content: ~3.5 characters per token
|
|
11533
|
+
*
|
|
11534
|
+
* Using 3.5 as the default provides a better estimate for typical
|
|
11535
|
+
* coding agent workloads which mix code and natural language.
|
|
11510
11536
|
*/
|
|
11511
11537
|
countTokens(text) {
|
|
11512
|
-
|
|
11538
|
+
if (!text) return 0;
|
|
11539
|
+
return Math.ceil(text.length / 3.5);
|
|
11513
11540
|
}
|
|
11514
11541
|
/**
|
|
11515
11542
|
* Get context window size
|
|
@@ -11800,6 +11827,19 @@ function createOrchestrator(config) {
|
|
|
11800
11827
|
},
|
|
11801
11828
|
async transitionTo(phase) {
|
|
11802
11829
|
const previousPhase = state.currentPhase;
|
|
11830
|
+
const validTransitions = {
|
|
11831
|
+
idle: ["converge"],
|
|
11832
|
+
converge: ["orchestrate"],
|
|
11833
|
+
orchestrate: ["complete"],
|
|
11834
|
+
complete: ["output"],
|
|
11835
|
+
output: []
|
|
11836
|
+
};
|
|
11837
|
+
const allowed = validTransitions[previousPhase];
|
|
11838
|
+
if (allowed && !allowed.includes(phase)) {
|
|
11839
|
+
throw new Error(
|
|
11840
|
+
`Invalid phase transition: ${previousPhase} \u2192 ${phase}. Allowed: ${allowed.join(", ") || "none"}`
|
|
11841
|
+
);
|
|
11842
|
+
}
|
|
11803
11843
|
state.phaseHistory.push({
|
|
11804
11844
|
from: previousPhase,
|
|
11805
11845
|
to: phase,
|
|
@@ -11868,7 +11908,10 @@ async function saveState(state) {
|
|
|
11868
11908
|
const fs33 = await import('fs/promises');
|
|
11869
11909
|
const statePath = `${state.path}/.coco/state`;
|
|
11870
11910
|
await fs33.mkdir(statePath, { recursive: true });
|
|
11871
|
-
|
|
11911
|
+
const filePath = `${statePath}/project.json`;
|
|
11912
|
+
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
11913
|
+
await fs33.writeFile(tmpPath, JSON.stringify(state, null, 2), "utf-8");
|
|
11914
|
+
await fs33.rename(tmpPath, filePath);
|
|
11872
11915
|
}
|
|
11873
11916
|
function getPhaseExecutor(phase) {
|
|
11874
11917
|
switch (phase) {
|
|
@@ -12145,7 +12188,10 @@ async function saveSnapshot(state, snapshotId) {
|
|
|
12145
12188
|
}
|
|
12146
12189
|
}
|
|
12147
12190
|
function restoreFromSnapshot(target, snapshot) {
|
|
12148
|
-
|
|
12191
|
+
const deep = JSON.parse(JSON.stringify(snapshot));
|
|
12192
|
+
deep.createdAt = new Date(deep.createdAt);
|
|
12193
|
+
deep.updatedAt = new Date(deep.updatedAt);
|
|
12194
|
+
Object.assign(target, deep);
|
|
12149
12195
|
}
|
|
12150
12196
|
async function executePhase(phase, state, config) {
|
|
12151
12197
|
const executor = getPhaseExecutor(phase);
|
|
@@ -12234,9 +12280,6 @@ var QualityConfigSchema = z.object({
|
|
|
12234
12280
|
}).refine((data) => data.minIterations <= data.maxIterations, {
|
|
12235
12281
|
message: "minIterations must be <= maxIterations",
|
|
12236
12282
|
path: ["minIterations"]
|
|
12237
|
-
}).refine((data) => data.convergenceThreshold < data.minScore, {
|
|
12238
|
-
message: "convergenceThreshold must be < minScore",
|
|
12239
|
-
path: ["convergenceThreshold"]
|
|
12240
12283
|
});
|
|
12241
12284
|
var PersistenceConfigSchema = z.object({
|
|
12242
12285
|
checkpointInterval: z.number().min(6e4).default(3e5),
|
|
@@ -12429,7 +12472,17 @@ function deepMergeConfig(base, override) {
|
|
|
12429
12472
|
project: { ...base.project, ...override.project },
|
|
12430
12473
|
provider: { ...base.provider, ...override.provider },
|
|
12431
12474
|
quality: { ...base.quality, ...override.quality },
|
|
12432
|
-
persistence: { ...base.persistence, ...override.persistence }
|
|
12475
|
+
persistence: { ...base.persistence, ...override.persistence },
|
|
12476
|
+
// Merge optional sections only if present in either base or override
|
|
12477
|
+
...base.stack || override.stack ? { stack: { ...base.stack, ...override.stack } } : {},
|
|
12478
|
+
...base.integrations || override.integrations ? {
|
|
12479
|
+
integrations: {
|
|
12480
|
+
...base.integrations,
|
|
12481
|
+
...override.integrations
|
|
12482
|
+
}
|
|
12483
|
+
} : {},
|
|
12484
|
+
...base.mcp || override.mcp ? { mcp: { ...base.mcp, ...override.mcp } } : {},
|
|
12485
|
+
...base.tools || override.tools ? { tools: { ...base.tools, ...override.tools } } : {}
|
|
12433
12486
|
};
|
|
12434
12487
|
}
|
|
12435
12488
|
function getProjectConfigPath() {
|
|
@@ -14536,7 +14589,17 @@ Examples:
|
|
|
14536
14589
|
regexPattern = `\\b${pattern}\\b`;
|
|
14537
14590
|
}
|
|
14538
14591
|
const flags = caseSensitive ? "g" : "gi";
|
|
14539
|
-
|
|
14592
|
+
let regex;
|
|
14593
|
+
try {
|
|
14594
|
+
regex = new RegExp(regexPattern, flags);
|
|
14595
|
+
} catch {
|
|
14596
|
+
throw new ToolError(`Invalid regex pattern: ${pattern}`, { tool: "grep" });
|
|
14597
|
+
}
|
|
14598
|
+
if (/(\.\*|\.\+|\[.*\][*+])\s*(\.\*|\.\+|\[.*\][*+])/.test(regexPattern)) {
|
|
14599
|
+
throw new ToolError(`Regex pattern rejected: nested quantifiers may cause slow matching`, {
|
|
14600
|
+
tool: "grep"
|
|
14601
|
+
});
|
|
14602
|
+
}
|
|
14540
14603
|
const stats = await fs14__default.stat(targetPath);
|
|
14541
14604
|
let filesToSearch;
|
|
14542
14605
|
if (stats.isFile()) {
|
|
@@ -16116,9 +16179,11 @@ function parseDiff(raw) {
|
|
|
16116
16179
|
function parseFileBlock(lines, start) {
|
|
16117
16180
|
const diffLine = lines[start];
|
|
16118
16181
|
let i = start + 1;
|
|
16119
|
-
const
|
|
16120
|
-
const
|
|
16121
|
-
const
|
|
16182
|
+
const gitPrefix = "diff --git a/";
|
|
16183
|
+
const pathPart = diffLine.slice(gitPrefix.length);
|
|
16184
|
+
const lastBSlash = pathPart.lastIndexOf(" b/");
|
|
16185
|
+
const oldPath = lastBSlash >= 0 ? pathPart.slice(0, lastBSlash) : pathPart;
|
|
16186
|
+
const newPath = lastBSlash >= 0 ? pathPart.slice(lastBSlash + 3) : oldPath;
|
|
16122
16187
|
let fileType = "modified";
|
|
16123
16188
|
while (i < lines.length && !lines[i].startsWith("diff --git ")) {
|
|
16124
16189
|
const current = lines[i];
|
|
@@ -16264,7 +16329,7 @@ function renderDiff(diff, options) {
|
|
|
16264
16329
|
function renderFileBlock(file, opts) {
|
|
16265
16330
|
const { maxWidth, showLineNumbers, compact } = opts;
|
|
16266
16331
|
const lang = detectLanguage(file.path);
|
|
16267
|
-
const contentWidth = maxWidth - 4;
|
|
16332
|
+
const contentWidth = Math.max(1, maxWidth - 4);
|
|
16268
16333
|
const typeLabel = file.type === "modified" ? "modified" : file.type === "added" ? "new file" : file.type === "deleted" ? "deleted" : `renamed from ${file.oldPath}`;
|
|
16269
16334
|
const statsLabel = ` +${file.additions} -${file.deletions}`;
|
|
16270
16335
|
const title = ` ${file.path} (${typeLabel}${statsLabel}) `;
|
|
@@ -16311,12 +16376,8 @@ function renderFileBlock(file, opts) {
|
|
|
16311
16376
|
}
|
|
16312
16377
|
function formatLineNo(line, show) {
|
|
16313
16378
|
if (!show) return "";
|
|
16314
|
-
|
|
16315
|
-
|
|
16316
|
-
} else if (line.type === "delete") {
|
|
16317
|
-
return chalk3.dim(`${String(line.oldLineNo ?? "").padStart(4)} `);
|
|
16318
|
-
}
|
|
16319
|
-
return chalk3.dim(`${String(line.newLineNo ?? "").padStart(4)} `);
|
|
16379
|
+
const lineNo = line.type === "delete" ? line.oldLineNo : line.newLineNo;
|
|
16380
|
+
return chalk3.dim(`${String(lineNo ?? "").padStart(5)} `);
|
|
16320
16381
|
}
|
|
16321
16382
|
function getChangedLines(diff) {
|
|
16322
16383
|
const result = /* @__PURE__ */ new Map();
|
|
@@ -18473,6 +18534,13 @@ Examples:
|
|
|
18473
18534
|
const startTime = performance.now();
|
|
18474
18535
|
const effectivePrompt = prompt ?? "Describe this image in detail. If it's code or a UI, identify the key elements.";
|
|
18475
18536
|
const absPath = path27.resolve(filePath);
|
|
18537
|
+
const cwd = process.cwd();
|
|
18538
|
+
if (!absPath.startsWith(cwd + path27.sep) && absPath !== cwd) {
|
|
18539
|
+
throw new ToolError(
|
|
18540
|
+
`Path traversal denied: '${filePath}' resolves outside the project directory`,
|
|
18541
|
+
{ tool: "read_image" }
|
|
18542
|
+
);
|
|
18543
|
+
}
|
|
18476
18544
|
const ext = path27.extname(absPath).toLowerCase();
|
|
18477
18545
|
if (!SUPPORTED_FORMATS.has(ext)) {
|
|
18478
18546
|
throw new ToolError(
|