@corbat-tech/coco 1.0.2 → 1.1.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 +178 -373
- package/dist/cli/index.js +2403 -368
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.js +10052 -8297
- package/dist/index.js.map +1 -1
- package/package.json +36 -23
package/dist/cli/index.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import * as os2 from 'os';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import * as path20 from 'path';
|
|
5
|
-
import path20__default, { join, dirname } from 'path';
|
|
5
|
+
import path20__default, { join, dirname, basename } from 'path';
|
|
6
6
|
import * as fs19 from 'fs';
|
|
7
|
-
import fs19__default, { readFileSync, constants } from 'fs';
|
|
7
|
+
import fs19__default, { readFileSync, existsSync, constants } from 'fs';
|
|
8
8
|
import * as fs22 from 'fs/promises';
|
|
9
9
|
import fs22__default, { writeFile, access, readFile, mkdir, readdir, rm } from 'fs/promises';
|
|
10
10
|
import { Command } from 'commander';
|
|
@@ -279,8 +279,8 @@ __export(trust_store_exports, {
|
|
|
279
279
|
saveTrustStore: () => saveTrustStore,
|
|
280
280
|
updateLastAccessed: () => updateLastAccessed
|
|
281
281
|
});
|
|
282
|
-
async function ensureDir(
|
|
283
|
-
await mkdir(dirname(
|
|
282
|
+
async function ensureDir(path37) {
|
|
283
|
+
await mkdir(dirname(path37), { recursive: true });
|
|
284
284
|
}
|
|
285
285
|
async function loadTrustStore(storePath = TRUST_STORE_PATH) {
|
|
286
286
|
try {
|
|
@@ -358,8 +358,8 @@ function canPerformOperation(store, projectPath, operation) {
|
|
|
358
358
|
};
|
|
359
359
|
return permissions[level]?.includes(operation) ?? false;
|
|
360
360
|
}
|
|
361
|
-
function normalizePath(
|
|
362
|
-
return join(
|
|
361
|
+
function normalizePath(path37) {
|
|
362
|
+
return join(path37);
|
|
363
363
|
}
|
|
364
364
|
function createTrustStore(storePath = TRUST_STORE_PATH) {
|
|
365
365
|
let store = null;
|
|
@@ -609,8 +609,8 @@ Generated by Corbat-Coco v0.1.0
|
|
|
609
609
|
|
|
610
610
|
// src/cli/commands/init.ts
|
|
611
611
|
function registerInitCommand(program2) {
|
|
612
|
-
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 (
|
|
613
|
-
await runInit(
|
|
612
|
+
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 (path37, options) => {
|
|
613
|
+
await runInit(path37, options);
|
|
614
614
|
});
|
|
615
615
|
}
|
|
616
616
|
async function runInit(projectPath, options) {
|
|
@@ -689,18 +689,18 @@ async function gatherProjectInfo() {
|
|
|
689
689
|
language
|
|
690
690
|
};
|
|
691
691
|
}
|
|
692
|
-
function getDefaultProjectInfo(
|
|
693
|
-
const name =
|
|
692
|
+
function getDefaultProjectInfo(path37) {
|
|
693
|
+
const name = path37 === "." ? "my-project" : path37.split("/").pop() || "my-project";
|
|
694
694
|
return {
|
|
695
695
|
name,
|
|
696
696
|
description: "",
|
|
697
697
|
language: "typescript"
|
|
698
698
|
};
|
|
699
699
|
}
|
|
700
|
-
async function checkExistingProject(
|
|
700
|
+
async function checkExistingProject(path37) {
|
|
701
701
|
try {
|
|
702
|
-
const
|
|
703
|
-
await
|
|
702
|
+
const fs39 = await import('fs/promises');
|
|
703
|
+
await fs39.access(`${path37}/.coco`);
|
|
704
704
|
return true;
|
|
705
705
|
} catch {
|
|
706
706
|
return false;
|
|
@@ -721,6 +721,12 @@ var QualityConfigSchema = z.object({
|
|
|
721
721
|
minIterations: z.number().min(1).max(10).default(2),
|
|
722
722
|
convergenceThreshold: z.number().min(0).max(10).default(2),
|
|
723
723
|
securityThreshold: z.number().min(0).max(100).default(100)
|
|
724
|
+
}).refine((data) => data.minIterations <= data.maxIterations, {
|
|
725
|
+
message: "minIterations must be <= maxIterations",
|
|
726
|
+
path: ["minIterations"]
|
|
727
|
+
}).refine((data) => data.convergenceThreshold < data.minScore, {
|
|
728
|
+
message: "convergenceThreshold must be < minScore",
|
|
729
|
+
path: ["convergenceThreshold"]
|
|
724
730
|
});
|
|
725
731
|
var PersistenceConfigSchema = z.object({
|
|
726
732
|
checkpointInterval: z.number().min(6e4).default(3e5),
|
|
@@ -976,6 +982,35 @@ var TimeoutError = class extends CocoError {
|
|
|
976
982
|
this.operation = options.operation;
|
|
977
983
|
}
|
|
978
984
|
};
|
|
985
|
+
var ERROR_SUGGESTIONS = {
|
|
986
|
+
PROVIDER_ERROR: "Check your API key and provider configuration. Run 'coco setup' to reconfigure.",
|
|
987
|
+
CONFIG_ERROR: "Check your .coco/config.json or run 'coco setup' to reconfigure.",
|
|
988
|
+
FILESYSTEM_ERROR: "Check that the path exists and you have read/write permissions.",
|
|
989
|
+
VALIDATION_ERROR: "Check the input data format. See 'coco --help' for usage.",
|
|
990
|
+
PHASE_ERROR: "Phase execution failed. Try 'coco resume' to continue from the last checkpoint.",
|
|
991
|
+
TASK_ERROR: "Task execution failed. The task can be retried from the last checkpoint with 'coco resume'.",
|
|
992
|
+
QUALITY_ERROR: "Quality score below threshold. Review the issues listed above and iterate on the code.",
|
|
993
|
+
RECOVERY_ERROR: "Checkpoint may be corrupted. Try 'coco init --force' to start fresh.",
|
|
994
|
+
TOOL_ERROR: "A tool execution failed. Check the error details above and retry.",
|
|
995
|
+
TIMEOUT_ERROR: "Operation timed out. Try increasing the timeout in config or simplifying the request.",
|
|
996
|
+
UNEXPECTED_ERROR: "An unexpected error occurred. Please report at github.com/corbat/corbat-coco/issues."
|
|
997
|
+
};
|
|
998
|
+
function formatError(error) {
|
|
999
|
+
if (error instanceof CocoError) {
|
|
1000
|
+
let message = `[${error.code}] ${error.message}`;
|
|
1001
|
+
const suggestion = error.suggestion ?? ERROR_SUGGESTIONS[error.code];
|
|
1002
|
+
if (suggestion) {
|
|
1003
|
+
message += `
|
|
1004
|
+
Suggestion: ${suggestion}`;
|
|
1005
|
+
}
|
|
1006
|
+
return message;
|
|
1007
|
+
}
|
|
1008
|
+
if (error instanceof Error) {
|
|
1009
|
+
return `${error.message}
|
|
1010
|
+
Suggestion: ${ERROR_SUGGESTIONS["UNEXPECTED_ERROR"]}`;
|
|
1011
|
+
}
|
|
1012
|
+
return String(error);
|
|
1013
|
+
}
|
|
979
1014
|
|
|
980
1015
|
// src/config/loader.ts
|
|
981
1016
|
init_paths();
|
|
@@ -7579,35 +7614,35 @@ async function createCliPhaseContext(projectPath, _onUserInput) {
|
|
|
7579
7614
|
},
|
|
7580
7615
|
tools: {
|
|
7581
7616
|
file: {
|
|
7582
|
-
async read(
|
|
7583
|
-
const
|
|
7584
|
-
return
|
|
7617
|
+
async read(path37) {
|
|
7618
|
+
const fs39 = await import('fs/promises');
|
|
7619
|
+
return fs39.readFile(path37, "utf-8");
|
|
7585
7620
|
},
|
|
7586
|
-
async write(
|
|
7587
|
-
const
|
|
7621
|
+
async write(path37, content) {
|
|
7622
|
+
const fs39 = await import('fs/promises');
|
|
7588
7623
|
const nodePath = await import('path');
|
|
7589
|
-
await
|
|
7590
|
-
await
|
|
7624
|
+
await fs39.mkdir(nodePath.dirname(path37), { recursive: true });
|
|
7625
|
+
await fs39.writeFile(path37, content, "utf-8");
|
|
7591
7626
|
},
|
|
7592
|
-
async exists(
|
|
7593
|
-
const
|
|
7627
|
+
async exists(path37) {
|
|
7628
|
+
const fs39 = await import('fs/promises');
|
|
7594
7629
|
try {
|
|
7595
|
-
await
|
|
7630
|
+
await fs39.access(path37);
|
|
7596
7631
|
return true;
|
|
7597
7632
|
} catch {
|
|
7598
7633
|
return false;
|
|
7599
7634
|
}
|
|
7600
7635
|
},
|
|
7601
7636
|
async glob(pattern) {
|
|
7602
|
-
const { glob:
|
|
7603
|
-
return
|
|
7637
|
+
const { glob: glob15 } = await import('glob');
|
|
7638
|
+
return glob15(pattern, { cwd: projectPath });
|
|
7604
7639
|
}
|
|
7605
7640
|
},
|
|
7606
7641
|
bash: {
|
|
7607
7642
|
async exec(command, options = {}) {
|
|
7608
|
-
const { execa:
|
|
7643
|
+
const { execa: execa10 } = await import('execa');
|
|
7609
7644
|
try {
|
|
7610
|
-
const result = await
|
|
7645
|
+
const result = await execa10(command, {
|
|
7611
7646
|
shell: true,
|
|
7612
7647
|
cwd: options.cwd || projectPath,
|
|
7613
7648
|
timeout: options.timeout,
|
|
@@ -7831,16 +7866,16 @@ async function loadTasks(_options) {
|
|
|
7831
7866
|
];
|
|
7832
7867
|
}
|
|
7833
7868
|
async function checkProjectState() {
|
|
7834
|
-
const
|
|
7869
|
+
const fs39 = await import('fs/promises');
|
|
7835
7870
|
let hasProject = false;
|
|
7836
7871
|
let hasPlan = false;
|
|
7837
7872
|
try {
|
|
7838
|
-
await
|
|
7873
|
+
await fs39.access(".coco");
|
|
7839
7874
|
hasProject = true;
|
|
7840
7875
|
} catch {
|
|
7841
7876
|
}
|
|
7842
7877
|
try {
|
|
7843
|
-
await
|
|
7878
|
+
await fs39.access(".coco/planning/backlog.json");
|
|
7844
7879
|
hasPlan = true;
|
|
7845
7880
|
} catch {
|
|
7846
7881
|
}
|
|
@@ -7944,24 +7979,24 @@ function getPhaseStatusForPhase(phase) {
|
|
|
7944
7979
|
return "in_progress";
|
|
7945
7980
|
}
|
|
7946
7981
|
async function loadProjectState(cwd, config) {
|
|
7947
|
-
const
|
|
7948
|
-
const
|
|
7949
|
-
const statePath =
|
|
7950
|
-
const backlogPath =
|
|
7951
|
-
const checkpointDir =
|
|
7982
|
+
const fs39 = await import('fs/promises');
|
|
7983
|
+
const path37 = await import('path');
|
|
7984
|
+
const statePath = path37.join(cwd, ".coco", "state.json");
|
|
7985
|
+
const backlogPath = path37.join(cwd, ".coco", "planning", "backlog.json");
|
|
7986
|
+
const checkpointDir = path37.join(cwd, ".coco", "checkpoints");
|
|
7952
7987
|
let currentPhase = "idle";
|
|
7953
7988
|
let metrics;
|
|
7954
7989
|
let sprint;
|
|
7955
7990
|
let checkpoints = [];
|
|
7956
7991
|
try {
|
|
7957
|
-
const stateContent = await
|
|
7992
|
+
const stateContent = await fs39.readFile(statePath, "utf-8");
|
|
7958
7993
|
const stateData = JSON.parse(stateContent);
|
|
7959
7994
|
currentPhase = stateData.currentPhase || "idle";
|
|
7960
7995
|
metrics = stateData.metrics;
|
|
7961
7996
|
} catch {
|
|
7962
7997
|
}
|
|
7963
7998
|
try {
|
|
7964
|
-
const backlogContent = await
|
|
7999
|
+
const backlogContent = await fs39.readFile(backlogPath, "utf-8");
|
|
7965
8000
|
const backlogData = JSON.parse(backlogContent);
|
|
7966
8001
|
if (backlogData.currentSprint) {
|
|
7967
8002
|
const tasks = backlogData.tasks || [];
|
|
@@ -7983,7 +8018,7 @@ async function loadProjectState(cwd, config) {
|
|
|
7983
8018
|
} catch {
|
|
7984
8019
|
}
|
|
7985
8020
|
try {
|
|
7986
|
-
const files = await
|
|
8021
|
+
const files = await fs39.readdir(checkpointDir);
|
|
7987
8022
|
checkpoints = files.filter((f) => f.endsWith(".json")).sort().reverse();
|
|
7988
8023
|
} catch {
|
|
7989
8024
|
}
|
|
@@ -8119,8 +8154,8 @@ async function restoreFromCheckpoint(_checkpoint) {
|
|
|
8119
8154
|
}
|
|
8120
8155
|
async function checkProjectExists() {
|
|
8121
8156
|
try {
|
|
8122
|
-
const
|
|
8123
|
-
await
|
|
8157
|
+
const fs39 = await import('fs/promises');
|
|
8158
|
+
await fs39.access(".coco");
|
|
8124
8159
|
return true;
|
|
8125
8160
|
} catch {
|
|
8126
8161
|
return false;
|
|
@@ -8659,12 +8694,12 @@ async function loadConfig2() {
|
|
|
8659
8694
|
};
|
|
8660
8695
|
}
|
|
8661
8696
|
async function saveConfig(config) {
|
|
8662
|
-
const
|
|
8663
|
-
await
|
|
8664
|
-
await
|
|
8697
|
+
const fs39 = await import('fs/promises');
|
|
8698
|
+
await fs39.mkdir(".coco", { recursive: true });
|
|
8699
|
+
await fs39.writeFile(".coco/config.json", JSON.stringify(config, null, 2));
|
|
8665
8700
|
}
|
|
8666
|
-
function getNestedValue(obj,
|
|
8667
|
-
const keys =
|
|
8701
|
+
function getNestedValue(obj, path37) {
|
|
8702
|
+
const keys = path37.split(".");
|
|
8668
8703
|
let current = obj;
|
|
8669
8704
|
for (const key of keys) {
|
|
8670
8705
|
if (current === null || current === void 0 || typeof current !== "object") {
|
|
@@ -8674,8 +8709,8 @@ function getNestedValue(obj, path35) {
|
|
|
8674
8709
|
}
|
|
8675
8710
|
return current;
|
|
8676
8711
|
}
|
|
8677
|
-
function setNestedValue(obj,
|
|
8678
|
-
const keys =
|
|
8712
|
+
function setNestedValue(obj, path37, value) {
|
|
8713
|
+
const keys = path37.split(".");
|
|
8679
8714
|
let current = obj;
|
|
8680
8715
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
8681
8716
|
const key = keys[i];
|
|
@@ -8896,8 +8931,8 @@ var MCPRegistryImpl = class {
|
|
|
8896
8931
|
/**
|
|
8897
8932
|
* Ensure directory exists
|
|
8898
8933
|
*/
|
|
8899
|
-
async ensureDir(
|
|
8900
|
-
await mkdir(dirname(
|
|
8934
|
+
async ensureDir(path37) {
|
|
8935
|
+
await mkdir(dirname(path37), { recursive: true });
|
|
8901
8936
|
}
|
|
8902
8937
|
};
|
|
8903
8938
|
function createMCPRegistry(registryPath) {
|
|
@@ -9416,9 +9451,9 @@ function createEmptyMemoryContext() {
|
|
|
9416
9451
|
errors: []
|
|
9417
9452
|
};
|
|
9418
9453
|
}
|
|
9419
|
-
function createMissingMemoryFile(
|
|
9454
|
+
function createMissingMemoryFile(path37, level) {
|
|
9420
9455
|
return {
|
|
9421
|
-
path:
|
|
9456
|
+
path: path37,
|
|
9422
9457
|
level,
|
|
9423
9458
|
content: "",
|
|
9424
9459
|
sections: [],
|
|
@@ -9784,6 +9819,7 @@ var helpCommand = {
|
|
|
9784
9819
|
commands: [
|
|
9785
9820
|
{ cmd: "/help, /?", desc: "Show this help message" },
|
|
9786
9821
|
{ cmd: "/help tools", desc: "Show available agent tools" },
|
|
9822
|
+
{ cmd: "/tutorial, /tut", desc: "Quick guide to using Coco" },
|
|
9787
9823
|
{ cmd: "/clear, /c", desc: "Clear conversation history" },
|
|
9788
9824
|
{ cmd: "/exit, /quit, /q", desc: "Exit the REPL" }
|
|
9789
9825
|
]
|
|
@@ -10329,11 +10365,11 @@ async function setupGcloudADC(provider) {
|
|
|
10329
10365
|
console.log(chalk12.dim(" Opening browser for Google sign-in..."));
|
|
10330
10366
|
console.log(chalk12.dim(" (Complete the sign-in in your browser, then return here)"));
|
|
10331
10367
|
console.log();
|
|
10332
|
-
const { exec:
|
|
10333
|
-
const { promisify:
|
|
10334
|
-
const
|
|
10368
|
+
const { exec: exec3 } = await import('child_process');
|
|
10369
|
+
const { promisify: promisify4 } = await import('util');
|
|
10370
|
+
const execAsync3 = promisify4(exec3);
|
|
10335
10371
|
try {
|
|
10336
|
-
await
|
|
10372
|
+
await execAsync3("gcloud auth application-default login", {
|
|
10337
10373
|
timeout: 12e4
|
|
10338
10374
|
// 2 minute timeout
|
|
10339
10375
|
});
|
|
@@ -11417,11 +11453,11 @@ async function setupGcloudADCForProvider(_provider) {
|
|
|
11417
11453
|
if (p9.isCancel(runNow) || !runNow) {
|
|
11418
11454
|
return false;
|
|
11419
11455
|
}
|
|
11420
|
-
const { exec:
|
|
11421
|
-
const { promisify:
|
|
11422
|
-
const
|
|
11456
|
+
const { exec: exec3 } = await import('child_process');
|
|
11457
|
+
const { promisify: promisify4 } = await import('util');
|
|
11458
|
+
const execAsync3 = promisify4(exec3);
|
|
11423
11459
|
try {
|
|
11424
|
-
await
|
|
11460
|
+
await execAsync3("gcloud auth application-default login", { timeout: 12e4 });
|
|
11425
11461
|
const token = await getADCAccessToken();
|
|
11426
11462
|
if (token) {
|
|
11427
11463
|
console.log(chalk12.green("\n \u2713 Authentication successful!\n"));
|
|
@@ -12106,8 +12142,8 @@ async function listTrustedProjects2(trustStore) {
|
|
|
12106
12142
|
p9.log.message("");
|
|
12107
12143
|
for (const project of projects) {
|
|
12108
12144
|
const level = project.approvalLevel.toUpperCase().padEnd(5);
|
|
12109
|
-
const
|
|
12110
|
-
p9.log.message(` [${level}] ${
|
|
12145
|
+
const path37 = project.path.length > 50 ? "..." + project.path.slice(-47) : project.path;
|
|
12146
|
+
p9.log.message(` [${level}] ${path37}`);
|
|
12111
12147
|
p9.log.message(` Last accessed: ${new Date(project.lastAccessed).toLocaleString()}`);
|
|
12112
12148
|
}
|
|
12113
12149
|
p9.log.message("");
|
|
@@ -13277,7 +13313,7 @@ async function getCheckpoint(session, checkpointId) {
|
|
|
13277
13313
|
return store.checkpoints.find((cp) => cp.id === checkpointId) ?? null;
|
|
13278
13314
|
}
|
|
13279
13315
|
async function restoreFiles(checkpoint, excludeFiles) {
|
|
13280
|
-
const
|
|
13316
|
+
const fs39 = await import('fs/promises');
|
|
13281
13317
|
const restored = [];
|
|
13282
13318
|
const failed = [];
|
|
13283
13319
|
for (const fileCheckpoint of checkpoint.files) {
|
|
@@ -13285,7 +13321,7 @@ async function restoreFiles(checkpoint, excludeFiles) {
|
|
|
13285
13321
|
continue;
|
|
13286
13322
|
}
|
|
13287
13323
|
try {
|
|
13288
|
-
await
|
|
13324
|
+
await fs39.writeFile(fileCheckpoint.filePath, fileCheckpoint.originalContent, "utf-8");
|
|
13289
13325
|
restored.push(fileCheckpoint.filePath);
|
|
13290
13326
|
} catch (error) {
|
|
13291
13327
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -13367,8 +13403,8 @@ function displayRewindResult(result) {
|
|
|
13367
13403
|
const fileName = filePath.split("/").pop() ?? filePath;
|
|
13368
13404
|
console.log(`${chalk12.green(String.fromCodePoint(10003))} Restored: ${fileName}`);
|
|
13369
13405
|
}
|
|
13370
|
-
for (const { path:
|
|
13371
|
-
const fileName =
|
|
13406
|
+
for (const { path: path37, error } of result.filesFailed) {
|
|
13407
|
+
const fileName = path37.split("/").pop() ?? path37;
|
|
13372
13408
|
console.log(`${chalk12.red(String.fromCodePoint(10007))} Failed: ${fileName} (${error})`);
|
|
13373
13409
|
}
|
|
13374
13410
|
if (result.conversationRestored) {
|
|
@@ -14773,8 +14809,8 @@ function formatToolSummary(toolName, input) {
|
|
|
14773
14809
|
return String(input.path || ".");
|
|
14774
14810
|
case "search_files": {
|
|
14775
14811
|
const pattern = String(input.pattern || "");
|
|
14776
|
-
const
|
|
14777
|
-
return `"${pattern}"${
|
|
14812
|
+
const path37 = input.path ? ` in ${input.path}` : "";
|
|
14813
|
+
return `"${pattern}"${path37}`;
|
|
14778
14814
|
}
|
|
14779
14815
|
case "bash_exec": {
|
|
14780
14816
|
const cmd = String(input.command || "");
|
|
@@ -15985,6 +16021,56 @@ var imageCommand = {
|
|
|
15985
16021
|
return false;
|
|
15986
16022
|
}
|
|
15987
16023
|
};
|
|
16024
|
+
var tutorialCommand = {
|
|
16025
|
+
name: "tutorial",
|
|
16026
|
+
aliases: ["tut", "learn"],
|
|
16027
|
+
description: "Quick guide to using Coco",
|
|
16028
|
+
usage: "/tutorial",
|
|
16029
|
+
async execute() {
|
|
16030
|
+
console.log(chalk12.cyan.bold("\n\u2550\u2550\u2550 Coco Quick Tutorial \u2550\u2550\u2550\n"));
|
|
16031
|
+
const steps = [
|
|
16032
|
+
{
|
|
16033
|
+
step: "1",
|
|
16034
|
+
title: "Ask Coco anything",
|
|
16035
|
+
desc: 'Just type what you need: "Create a REST API with authentication"'
|
|
16036
|
+
},
|
|
16037
|
+
{
|
|
16038
|
+
step: "2",
|
|
16039
|
+
title: "Coco works autonomously",
|
|
16040
|
+
desc: "Reads your project, writes code, runs tests, and iterates until quality passes"
|
|
16041
|
+
},
|
|
16042
|
+
{
|
|
16043
|
+
step: "3",
|
|
16044
|
+
title: "Enable quality mode",
|
|
16045
|
+
desc: "Type /coco to enable auto-iteration: test \u2192 analyze \u2192 fix \u2192 repeat until score \u2265 85"
|
|
16046
|
+
},
|
|
16047
|
+
{
|
|
16048
|
+
step: "4",
|
|
16049
|
+
title: "Review changes",
|
|
16050
|
+
desc: "Use /diff to see what changed, /status for project state"
|
|
16051
|
+
},
|
|
16052
|
+
{
|
|
16053
|
+
step: "5",
|
|
16054
|
+
title: "Save your work",
|
|
16055
|
+
desc: "Use /commit to commit changes with a descriptive message"
|
|
16056
|
+
}
|
|
16057
|
+
];
|
|
16058
|
+
for (const { step, title, desc } of steps) {
|
|
16059
|
+
console.log(` ${chalk12.magenta.bold(step)}. ${chalk12.bold(title)}`);
|
|
16060
|
+
console.log(` ${chalk12.dim(desc)}`);
|
|
16061
|
+
console.log();
|
|
16062
|
+
}
|
|
16063
|
+
console.log(chalk12.bold("Useful commands:"));
|
|
16064
|
+
console.log(
|
|
16065
|
+
` ${chalk12.yellow("/coco")} ${chalk12.dim("Toggle quality mode (auto-iteration)")}`
|
|
16066
|
+
);
|
|
16067
|
+
console.log(` ${chalk12.yellow("/init")} ${chalk12.dim("Initialize a new project")}`);
|
|
16068
|
+
console.log(` ${chalk12.yellow("/help")} ${chalk12.dim("See all available commands")}`);
|
|
16069
|
+
console.log(` ${chalk12.yellow("/help tools")} ${chalk12.dim("See available agent tools")}`);
|
|
16070
|
+
console.log();
|
|
16071
|
+
return false;
|
|
16072
|
+
}
|
|
16073
|
+
};
|
|
15988
16074
|
|
|
15989
16075
|
// src/cli/repl/commands/index.ts
|
|
15990
16076
|
var commands = [
|
|
@@ -16014,7 +16100,8 @@ var commands = [
|
|
|
16014
16100
|
allowPathCommand,
|
|
16015
16101
|
permissionsCommand,
|
|
16016
16102
|
cocoCommand,
|
|
16017
|
-
imageCommand
|
|
16103
|
+
imageCommand,
|
|
16104
|
+
tutorialCommand
|
|
16018
16105
|
];
|
|
16019
16106
|
function isSlashCommand(input) {
|
|
16020
16107
|
return input.startsWith("/");
|
|
@@ -16105,17 +16192,19 @@ function createInputHandler(_session) {
|
|
|
16105
16192
|
let pasteBuffer = "";
|
|
16106
16193
|
let isReadingClipboard = false;
|
|
16107
16194
|
const getPrompt = () => {
|
|
16195
|
+
const imageIndicator = hasPendingImage() ? chalk12.cyan(" \u{1F4CE} 1 image") : "";
|
|
16196
|
+
const imageIndicatorLen = hasPendingImage() ? 10 : 0;
|
|
16108
16197
|
if (isCocoMode()) {
|
|
16109
16198
|
return {
|
|
16110
|
-
str: "\u{1F965} " + chalk12.magenta("[coco]") + " \u203A ",
|
|
16111
|
-
// 🥥=2 + space=1 + [coco]=6 + space=1 + ›=1 + space=1 = 12
|
|
16112
|
-
visualLen: 12
|
|
16199
|
+
str: "\u{1F965} " + chalk12.magenta("[coco]") + " \u203A " + imageIndicator,
|
|
16200
|
+
// 🥥=2 + space=1 + [coco]=6 + space=1 + ›=1 + space=1 = 12 + image indicator
|
|
16201
|
+
visualLen: 12 + imageIndicatorLen
|
|
16113
16202
|
};
|
|
16114
16203
|
}
|
|
16115
16204
|
return {
|
|
16116
|
-
str: chalk12.green("\u{1F965} \u203A "),
|
|
16117
|
-
// 🥥=2 + space=1 + ›=1 + space=1 = 5
|
|
16118
|
-
visualLen: 5
|
|
16205
|
+
str: chalk12.green("\u{1F965} \u203A ") + imageIndicator,
|
|
16206
|
+
// 🥥=2 + space=1 + ›=1 + space=1 = 5 + image indicator
|
|
16207
|
+
visualLen: 5 + imageIndicatorLen
|
|
16119
16208
|
};
|
|
16120
16209
|
};
|
|
16121
16210
|
const MAX_ROWS = 8;
|
|
@@ -16128,9 +16217,17 @@ function createInputHandler(_session) {
|
|
|
16128
16217
|
return Math.max(1, Math.min(3, cols));
|
|
16129
16218
|
}
|
|
16130
16219
|
function render() {
|
|
16131
|
-
process.stdout.
|
|
16220
|
+
const termCols = process.stdout.columns || 80;
|
|
16132
16221
|
const prompt = getPrompt();
|
|
16133
|
-
|
|
16222
|
+
const totalVisualLen = prompt.visualLen + currentLine.length;
|
|
16223
|
+
const wrappedLines = Math.max(0, Math.ceil(totalVisualLen / termCols) - 1);
|
|
16224
|
+
if (wrappedLines > 0) {
|
|
16225
|
+
process.stdout.write(ansiEscapes3.cursorUp(wrappedLines));
|
|
16226
|
+
}
|
|
16227
|
+
process.stdout.write("\r" + ansiEscapes3.eraseDown);
|
|
16228
|
+
const separator = chalk12.dim("\u2500".repeat(termCols));
|
|
16229
|
+
let output = separator + "\n";
|
|
16230
|
+
output += prompt.str + currentLine;
|
|
16134
16231
|
completions = findCompletions(currentLine);
|
|
16135
16232
|
selectedCompletion = Math.min(selectedCompletion, Math.max(0, completions.length - 1));
|
|
16136
16233
|
if (cursorPos === currentLine.length && completions.length > 0 && completions[selectedCompletion]) {
|
|
@@ -16183,15 +16280,25 @@ function createInputHandler(_session) {
|
|
|
16183
16280
|
for (let i = 0; i < BOTTOM_MARGIN; i++) {
|
|
16184
16281
|
output += "\n";
|
|
16185
16282
|
}
|
|
16186
|
-
output += ansiEscapes3.cursorUp(lastMenuLines + BOTTOM_MARGIN);
|
|
16283
|
+
output += ansiEscapes3.cursorUp(lastMenuLines + BOTTOM_MARGIN + 1);
|
|
16187
16284
|
} else {
|
|
16188
16285
|
lastMenuLines = 0;
|
|
16189
16286
|
for (let i = 0; i < BOTTOM_MARGIN; i++) {
|
|
16190
16287
|
output += "\n";
|
|
16191
16288
|
}
|
|
16192
|
-
output += ansiEscapes3.cursorUp(BOTTOM_MARGIN);
|
|
16289
|
+
output += ansiEscapes3.cursorUp(BOTTOM_MARGIN + 1);
|
|
16290
|
+
}
|
|
16291
|
+
output += ansiEscapes3.cursorDown(1);
|
|
16292
|
+
const cursorAbsolutePos = prompt.visualLen + cursorPos;
|
|
16293
|
+
const finalLine = Math.floor(cursorAbsolutePos / termCols);
|
|
16294
|
+
const finalCol = cursorAbsolutePos % termCols;
|
|
16295
|
+
output += "\r";
|
|
16296
|
+
if (finalLine > 0) {
|
|
16297
|
+
output += ansiEscapes3.cursorDown(finalLine);
|
|
16298
|
+
}
|
|
16299
|
+
if (finalCol > 0) {
|
|
16300
|
+
output += ansiEscapes3.cursorForward(finalCol);
|
|
16193
16301
|
}
|
|
16194
|
-
output += `\r${ansiEscapes3.cursorForward(prompt.visualLen + cursorPos)}`;
|
|
16195
16302
|
process.stdout.write(output);
|
|
16196
16303
|
}
|
|
16197
16304
|
function clearMenu() {
|
|
@@ -16339,15 +16446,14 @@ function createInputHandler(_session) {
|
|
|
16339
16446
|
process.stdout.write(
|
|
16340
16447
|
chalk12.green(" \u2713 Image captured") + chalk12.dim(` (${sizeKB} KB)`) + chalk12.dim(` \u2014 "${truncatedPrompt}"`)
|
|
16341
16448
|
);
|
|
16342
|
-
|
|
16343
|
-
|
|
16344
|
-
const result = currentLine.trim();
|
|
16345
|
-
if (result) {
|
|
16346
|
-
sessionHistory.push(result);
|
|
16347
|
-
}
|
|
16348
|
-
resolve2(result || "");
|
|
16349
|
-
}).catch(() => {
|
|
16449
|
+
setTimeout(() => render(), 1200);
|
|
16450
|
+
}).catch((err) => {
|
|
16350
16451
|
isReadingClipboard = false;
|
|
16452
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
16453
|
+
if (msg.includes("color space")) {
|
|
16454
|
+
render();
|
|
16455
|
+
return;
|
|
16456
|
+
}
|
|
16351
16457
|
render();
|
|
16352
16458
|
});
|
|
16353
16459
|
return;
|
|
@@ -16357,7 +16463,9 @@ function createInputHandler(_session) {
|
|
|
16357
16463
|
currentLine = completions[selectedCompletion].cmd;
|
|
16358
16464
|
}
|
|
16359
16465
|
cleanup();
|
|
16466
|
+
const termWidth = process.stdout.columns || 80;
|
|
16360
16467
|
console.log();
|
|
16468
|
+
console.log(chalk12.dim("\u2500".repeat(termWidth)));
|
|
16361
16469
|
const result = currentLine.trim();
|
|
16362
16470
|
if (result) {
|
|
16363
16471
|
sessionHistory.push(result);
|
|
@@ -18190,8 +18298,8 @@ function isPathAllowed(filePath, operation) {
|
|
|
18190
18298
|
if (absolute.startsWith(normalizedHome) && !absolute.startsWith(normalizedCwd)) {
|
|
18191
18299
|
if (isWithinAllowedPath(absolute, operation)) ; else if (operation === "read") {
|
|
18192
18300
|
const allowedHomeReads = [".gitconfig", ".zshrc", ".bashrc"];
|
|
18193
|
-
const
|
|
18194
|
-
if (!allowedHomeReads.includes(
|
|
18301
|
+
const basename3 = path20__default.basename(absolute);
|
|
18302
|
+
if (!allowedHomeReads.includes(basename3)) {
|
|
18195
18303
|
const targetDir = path20__default.dirname(absolute);
|
|
18196
18304
|
return {
|
|
18197
18305
|
allowed: false,
|
|
@@ -18208,12 +18316,12 @@ function isPathAllowed(filePath, operation) {
|
|
|
18208
18316
|
}
|
|
18209
18317
|
}
|
|
18210
18318
|
if (operation === "write" || operation === "delete") {
|
|
18211
|
-
const
|
|
18319
|
+
const basename3 = path20__default.basename(absolute);
|
|
18212
18320
|
for (const pattern of SENSITIVE_PATTERNS) {
|
|
18213
|
-
if (pattern.test(
|
|
18321
|
+
if (pattern.test(basename3)) {
|
|
18214
18322
|
return {
|
|
18215
18323
|
allowed: false,
|
|
18216
|
-
reason: `Operation on sensitive file '${
|
|
18324
|
+
reason: `Operation on sensitive file '${basename3}' requires explicit confirmation`
|
|
18217
18325
|
};
|
|
18218
18326
|
}
|
|
18219
18327
|
}
|
|
@@ -19471,59 +19579,322 @@ var simpleAutoCommitTool = defineTool({
|
|
|
19471
19579
|
}
|
|
19472
19580
|
});
|
|
19473
19581
|
var gitSimpleTools = [checkProtectedBranchTool, simpleAutoCommitTool];
|
|
19582
|
+
|
|
19583
|
+
// src/agents/provider-bridge.ts
|
|
19584
|
+
var agentProvider = null;
|
|
19585
|
+
var agentToolRegistry = null;
|
|
19586
|
+
function setAgentProvider(provider) {
|
|
19587
|
+
agentProvider = provider;
|
|
19588
|
+
}
|
|
19589
|
+
function getAgentProvider() {
|
|
19590
|
+
return agentProvider;
|
|
19591
|
+
}
|
|
19592
|
+
function setAgentToolRegistry(registry) {
|
|
19593
|
+
agentToolRegistry = registry;
|
|
19594
|
+
}
|
|
19595
|
+
function getAgentToolRegistry() {
|
|
19596
|
+
return agentToolRegistry;
|
|
19597
|
+
}
|
|
19598
|
+
|
|
19599
|
+
// src/agents/executor.ts
|
|
19600
|
+
var AgentExecutor = class {
|
|
19601
|
+
constructor(provider, toolRegistry) {
|
|
19602
|
+
this.provider = provider;
|
|
19603
|
+
this.toolRegistry = toolRegistry;
|
|
19604
|
+
}
|
|
19605
|
+
/**
|
|
19606
|
+
* Execute an agent on a task with multi-turn tool use
|
|
19607
|
+
*/
|
|
19608
|
+
async execute(agent, task) {
|
|
19609
|
+
const startTime = Date.now();
|
|
19610
|
+
const toolsUsed = /* @__PURE__ */ new Set();
|
|
19611
|
+
const messages = [
|
|
19612
|
+
{
|
|
19613
|
+
role: "user",
|
|
19614
|
+
content: this.buildTaskPrompt(task)
|
|
19615
|
+
}
|
|
19616
|
+
];
|
|
19617
|
+
const agentToolDefs = this.getToolDefinitionsForAgent(agent.allowedTools);
|
|
19618
|
+
let turn = 0;
|
|
19619
|
+
let totalTokens = 0;
|
|
19620
|
+
while (turn < agent.maxTurns) {
|
|
19621
|
+
turn++;
|
|
19622
|
+
try {
|
|
19623
|
+
const response = await this.provider.chatWithTools(messages, {
|
|
19624
|
+
tools: agentToolDefs,
|
|
19625
|
+
system: agent.systemPrompt
|
|
19626
|
+
});
|
|
19627
|
+
const usage = response.usage;
|
|
19628
|
+
totalTokens += (usage?.inputTokens || 0) + (usage?.outputTokens || 0);
|
|
19629
|
+
if (response.stopReason !== "tool_use" || response.toolCalls.length === 0) {
|
|
19630
|
+
return {
|
|
19631
|
+
output: response.content,
|
|
19632
|
+
success: true,
|
|
19633
|
+
turns: turn,
|
|
19634
|
+
toolsUsed: Array.from(toolsUsed),
|
|
19635
|
+
tokensUsed: totalTokens,
|
|
19636
|
+
duration: Date.now() - startTime
|
|
19637
|
+
};
|
|
19638
|
+
}
|
|
19639
|
+
const assistantContent = [];
|
|
19640
|
+
if (response.content) {
|
|
19641
|
+
assistantContent.push({
|
|
19642
|
+
type: "text",
|
|
19643
|
+
text: response.content
|
|
19644
|
+
});
|
|
19645
|
+
}
|
|
19646
|
+
for (const toolCall of response.toolCalls) {
|
|
19647
|
+
assistantContent.push({
|
|
19648
|
+
type: "tool_use",
|
|
19649
|
+
id: toolCall.id,
|
|
19650
|
+
name: toolCall.name,
|
|
19651
|
+
input: toolCall.input
|
|
19652
|
+
});
|
|
19653
|
+
}
|
|
19654
|
+
messages.push({
|
|
19655
|
+
role: "assistant",
|
|
19656
|
+
content: assistantContent
|
|
19657
|
+
});
|
|
19658
|
+
const toolResults = [];
|
|
19659
|
+
for (const toolCall of response.toolCalls) {
|
|
19660
|
+
toolsUsed.add(toolCall.name);
|
|
19661
|
+
try {
|
|
19662
|
+
const result = await this.toolRegistry.execute(toolCall.name, toolCall.input);
|
|
19663
|
+
toolResults.push({
|
|
19664
|
+
type: "tool_result",
|
|
19665
|
+
tool_use_id: toolCall.id,
|
|
19666
|
+
content: result.success ? JSON.stringify(result.data) : `Error: ${result.error}`,
|
|
19667
|
+
is_error: !result.success
|
|
19668
|
+
});
|
|
19669
|
+
} catch (error) {
|
|
19670
|
+
toolResults.push({
|
|
19671
|
+
type: "tool_result",
|
|
19672
|
+
tool_use_id: toolCall.id,
|
|
19673
|
+
content: `Tool execution error: ${error instanceof Error ? error.message : String(error)}`,
|
|
19674
|
+
is_error: true
|
|
19675
|
+
});
|
|
19676
|
+
}
|
|
19677
|
+
}
|
|
19678
|
+
messages.push({
|
|
19679
|
+
role: "user",
|
|
19680
|
+
content: toolResults
|
|
19681
|
+
});
|
|
19682
|
+
} catch (error) {
|
|
19683
|
+
return {
|
|
19684
|
+
output: `Agent error on turn ${turn}: ${error instanceof Error ? error.message : String(error)}`,
|
|
19685
|
+
success: false,
|
|
19686
|
+
turns: turn,
|
|
19687
|
+
toolsUsed: Array.from(toolsUsed),
|
|
19688
|
+
tokensUsed: totalTokens,
|
|
19689
|
+
duration: Date.now() - startTime
|
|
19690
|
+
};
|
|
19691
|
+
}
|
|
19692
|
+
}
|
|
19693
|
+
return {
|
|
19694
|
+
output: "Agent reached maximum turns without completing task",
|
|
19695
|
+
success: false,
|
|
19696
|
+
turns: turn,
|
|
19697
|
+
toolsUsed: Array.from(toolsUsed),
|
|
19698
|
+
tokensUsed: totalTokens,
|
|
19699
|
+
duration: Date.now() - startTime
|
|
19700
|
+
};
|
|
19701
|
+
}
|
|
19702
|
+
/**
|
|
19703
|
+
* Build task prompt with context
|
|
19704
|
+
*/
|
|
19705
|
+
buildTaskPrompt(task) {
|
|
19706
|
+
let prompt = `Task: ${task.description}
|
|
19707
|
+
`;
|
|
19708
|
+
if (task.context && Object.keys(task.context).length > 0) {
|
|
19709
|
+
prompt += `
|
|
19710
|
+
Context:
|
|
19711
|
+
${JSON.stringify(task.context, null, 2)}
|
|
19712
|
+
`;
|
|
19713
|
+
}
|
|
19714
|
+
prompt += `
|
|
19715
|
+
Complete this task autonomously using the available tools. When done, provide a summary of what you accomplished.`;
|
|
19716
|
+
return prompt;
|
|
19717
|
+
}
|
|
19718
|
+
/**
|
|
19719
|
+
* Get tool definitions filtered for this agent's allowed tools
|
|
19720
|
+
*/
|
|
19721
|
+
getToolDefinitionsForAgent(allowedToolNames) {
|
|
19722
|
+
const allDefs = this.toolRegistry.getToolDefinitionsForLLM();
|
|
19723
|
+
if (allowedToolNames.length === 0) return allDefs;
|
|
19724
|
+
return allDefs.filter((def) => allowedToolNames.includes(def.name));
|
|
19725
|
+
}
|
|
19726
|
+
};
|
|
19727
|
+
var AGENT_ROLES = {
|
|
19728
|
+
researcher: {
|
|
19729
|
+
role: "researcher",
|
|
19730
|
+
systemPrompt: `You are a code researcher agent. Your role is to:
|
|
19731
|
+
- Explore and understand existing codebases
|
|
19732
|
+
- Find relevant code patterns and examples
|
|
19733
|
+
- Identify dependencies and relationships
|
|
19734
|
+
- Document your findings clearly
|
|
19735
|
+
|
|
19736
|
+
Use tools to search, read files, and analyze code structure.`,
|
|
19737
|
+
allowedTools: ["read_file", "grep", "find_in_file", "glob", "codebase_map"]
|
|
19738
|
+
},
|
|
19739
|
+
coder: {
|
|
19740
|
+
role: "coder",
|
|
19741
|
+
systemPrompt: `You are a code generation agent. Your role is to:
|
|
19742
|
+
- Write high-quality, production-ready code
|
|
19743
|
+
- Follow best practices and coding standards
|
|
19744
|
+
- Ensure code is syntactically valid
|
|
19745
|
+
- Write clean, maintainable code
|
|
19746
|
+
|
|
19747
|
+
Use tools to read existing code, write new files, and validate syntax.`,
|
|
19748
|
+
allowedTools: ["read_file", "write_file", "edit_file", "bash_exec", "validateCode"]
|
|
19749
|
+
},
|
|
19750
|
+
tester: {
|
|
19751
|
+
role: "tester",
|
|
19752
|
+
systemPrompt: `You are a test generation agent. Your role is to:
|
|
19753
|
+
- Write comprehensive test suites
|
|
19754
|
+
- Achieve high code coverage
|
|
19755
|
+
- Test edge cases and error conditions
|
|
19756
|
+
- Ensure tests are reliable and maintainable
|
|
19757
|
+
|
|
19758
|
+
Use tools to read code, write tests, and run them.`,
|
|
19759
|
+
allowedTools: ["read_file", "write_file", "run_tests", "get_coverage", "run_test_file"]
|
|
19760
|
+
},
|
|
19761
|
+
reviewer: {
|
|
19762
|
+
role: "reviewer",
|
|
19763
|
+
systemPrompt: `You are a code review agent. Your role is to:
|
|
19764
|
+
- Identify code quality issues
|
|
19765
|
+
- Check for security vulnerabilities
|
|
19766
|
+
- Ensure best practices are followed
|
|
19767
|
+
- Provide actionable feedback
|
|
19768
|
+
|
|
19769
|
+
Use tools to read and analyze code quality.`,
|
|
19770
|
+
allowedTools: ["read_file", "calculate_quality", "analyze_complexity", "grep"]
|
|
19771
|
+
},
|
|
19772
|
+
optimizer: {
|
|
19773
|
+
role: "optimizer",
|
|
19774
|
+
systemPrompt: `You are a code optimization agent. Your role is to:
|
|
19775
|
+
- Reduce code complexity
|
|
19776
|
+
- Eliminate duplication
|
|
19777
|
+
- Improve performance
|
|
19778
|
+
- Refactor for maintainability
|
|
19779
|
+
|
|
19780
|
+
Use tools to analyze and improve code.`,
|
|
19781
|
+
allowedTools: ["read_file", "write_file", "edit_file", "analyze_complexity", "grep"]
|
|
19782
|
+
},
|
|
19783
|
+
planner: {
|
|
19784
|
+
role: "planner",
|
|
19785
|
+
systemPrompt: `You are a task planning agent. Your role is to:
|
|
19786
|
+
- Break down complex tasks into subtasks
|
|
19787
|
+
- Identify dependencies between tasks
|
|
19788
|
+
- Estimate complexity and effort
|
|
19789
|
+
- Create actionable plans
|
|
19790
|
+
|
|
19791
|
+
Use tools to analyze requirements and explore the codebase.`,
|
|
19792
|
+
allowedTools: ["read_file", "grep", "glob", "codebase_map"]
|
|
19793
|
+
}
|
|
19794
|
+
};
|
|
19795
|
+
|
|
19796
|
+
// src/tools/simple-agent.ts
|
|
19474
19797
|
var SpawnSimpleAgentSchema = z.object({
|
|
19475
19798
|
task: z.string().describe("Task description for the sub-agent"),
|
|
19476
|
-
context: z.string().optional().describe("Additional context or instructions for the agent")
|
|
19799
|
+
context: z.string().optional().describe("Additional context or instructions for the agent"),
|
|
19800
|
+
role: z.enum(["researcher", "coder", "tester", "reviewer", "optimizer", "planner"]).default("coder").describe("Agent role to use"),
|
|
19801
|
+
maxTurns: z.number().default(10).describe("Maximum tool-use turns for the agent")
|
|
19477
19802
|
});
|
|
19478
19803
|
var spawnSimpleAgentTool = defineTool({
|
|
19479
19804
|
name: "spawnSimpleAgent",
|
|
19480
|
-
description: `Spawn a sub-agent to handle a specific task.
|
|
19805
|
+
description: `Spawn a sub-agent to handle a specific task with real LLM tool-use execution.
|
|
19481
19806
|
|
|
19482
19807
|
Use this when you need to:
|
|
19483
19808
|
- Delegate a focused task to another agent
|
|
19484
19809
|
- Get a second opinion or alternative approach
|
|
19485
19810
|
- Handle multiple independent subtasks
|
|
19486
19811
|
|
|
19487
|
-
The sub-agent will work on the task
|
|
19812
|
+
The sub-agent will work on the task autonomously using available tools.
|
|
19488
19813
|
|
|
19489
19814
|
Example: "Write unit tests for the authentication module"`,
|
|
19490
19815
|
category: "build",
|
|
19491
19816
|
parameters: SpawnSimpleAgentSchema,
|
|
19492
19817
|
async execute(input) {
|
|
19493
19818
|
const typedInput = input;
|
|
19819
|
+
const provider = getAgentProvider();
|
|
19820
|
+
const toolRegistry = getAgentToolRegistry();
|
|
19821
|
+
if (!provider || !toolRegistry) {
|
|
19822
|
+
const agentId2 = `agent-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
19823
|
+
return {
|
|
19824
|
+
stdout: JSON.stringify({
|
|
19825
|
+
agentId: agentId2,
|
|
19826
|
+
status: "unavailable",
|
|
19827
|
+
task: typedInput.task,
|
|
19828
|
+
message: "Agent provider not initialized. Call setAgentProvider() during orchestrator startup.",
|
|
19829
|
+
success: false
|
|
19830
|
+
}),
|
|
19831
|
+
stderr: "",
|
|
19832
|
+
exitCode: 1,
|
|
19833
|
+
duration: 0
|
|
19834
|
+
};
|
|
19835
|
+
}
|
|
19494
19836
|
const agentId = `agent-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
19837
|
+
const executor = new AgentExecutor(provider, toolRegistry);
|
|
19838
|
+
const roleDef = AGENT_ROLES[typedInput.role];
|
|
19839
|
+
if (!roleDef) {
|
|
19840
|
+
return {
|
|
19841
|
+
stdout: JSON.stringify({
|
|
19842
|
+
agentId,
|
|
19843
|
+
status: "error",
|
|
19844
|
+
message: `Unknown agent role: ${typedInput.role}`,
|
|
19845
|
+
success: false
|
|
19846
|
+
}),
|
|
19847
|
+
stderr: "",
|
|
19848
|
+
exitCode: 1,
|
|
19849
|
+
duration: 0
|
|
19850
|
+
};
|
|
19851
|
+
}
|
|
19852
|
+
const agentDef = { ...roleDef, maxTurns: typedInput.maxTurns };
|
|
19853
|
+
const result = await executor.execute(agentDef, {
|
|
19854
|
+
id: agentId,
|
|
19855
|
+
description: typedInput.task,
|
|
19856
|
+
context: typedInput.context ? { userContext: typedInput.context } : void 0
|
|
19857
|
+
});
|
|
19495
19858
|
return {
|
|
19496
19859
|
stdout: JSON.stringify({
|
|
19497
19860
|
agentId,
|
|
19498
|
-
status: "
|
|
19861
|
+
status: result.success ? "completed" : "failed",
|
|
19499
19862
|
task: typedInput.task,
|
|
19500
|
-
|
|
19501
|
-
|
|
19502
|
-
|
|
19863
|
+
output: result.output,
|
|
19864
|
+
success: result.success,
|
|
19865
|
+
turns: result.turns,
|
|
19866
|
+
toolsUsed: result.toolsUsed,
|
|
19867
|
+
tokensUsed: result.tokensUsed,
|
|
19868
|
+
duration: result.duration
|
|
19503
19869
|
}),
|
|
19504
19870
|
stderr: "",
|
|
19505
|
-
exitCode: 0,
|
|
19506
|
-
duration:
|
|
19871
|
+
exitCode: result.success ? 0 : 1,
|
|
19872
|
+
duration: result.duration
|
|
19507
19873
|
};
|
|
19508
19874
|
}
|
|
19509
19875
|
});
|
|
19510
19876
|
var checkAgentCapabilityTool = defineTool({
|
|
19511
19877
|
name: "checkAgentCapability",
|
|
19512
|
-
description: "Check if multi-agent capability is available",
|
|
19878
|
+
description: "Check if multi-agent capability is available and configured",
|
|
19513
19879
|
category: "build",
|
|
19514
19880
|
parameters: z.object({}),
|
|
19515
19881
|
async execute() {
|
|
19882
|
+
const provider = getAgentProvider();
|
|
19883
|
+
const toolRegistry = getAgentToolRegistry();
|
|
19884
|
+
const isReady = provider !== null && toolRegistry !== null;
|
|
19516
19885
|
return {
|
|
19517
19886
|
stdout: JSON.stringify({
|
|
19518
19887
|
multiAgentSupported: true,
|
|
19519
|
-
|
|
19520
|
-
|
|
19888
|
+
providerConfigured: provider !== null,
|
|
19889
|
+
toolRegistryConfigured: toolRegistry !== null,
|
|
19890
|
+
ready: isReady,
|
|
19891
|
+
availableRoles: Object.keys(AGENT_ROLES),
|
|
19521
19892
|
features: {
|
|
19522
|
-
taskDelegation: "
|
|
19523
|
-
parallelSpawn: "requires
|
|
19524
|
-
|
|
19893
|
+
taskDelegation: isReady ? "ready" : "requires provider initialization",
|
|
19894
|
+
parallelSpawn: isReady ? "ready" : "requires provider initialization",
|
|
19895
|
+
multiTurnToolUse: isReady ? "ready" : "requires provider initialization"
|
|
19525
19896
|
},
|
|
19526
|
-
status: "Multi-agent
|
|
19897
|
+
status: isReady ? "Multi-agent system is ready with real LLM tool-use execution." : "Provider not initialized. Call setAgentProvider() during startup."
|
|
19527
19898
|
}),
|
|
19528
19899
|
stderr: "",
|
|
19529
19900
|
exitCode: 0,
|
|
@@ -19857,10 +20228,10 @@ var CoverageAnalyzer = class {
|
|
|
19857
20228
|
join(this.projectPath, ".coverage", "coverage-summary.json"),
|
|
19858
20229
|
join(this.projectPath, "coverage", "lcov-report", "coverage-summary.json")
|
|
19859
20230
|
];
|
|
19860
|
-
for (const
|
|
20231
|
+
for (const path37 of possiblePaths) {
|
|
19861
20232
|
try {
|
|
19862
|
-
await access(
|
|
19863
|
-
const content = await readFile(
|
|
20233
|
+
await access(path37, constants.R_OK);
|
|
20234
|
+
const content = await readFile(path37, "utf-8");
|
|
19864
20235
|
const report = JSON.parse(content);
|
|
19865
20236
|
return parseCoverageSummary(report);
|
|
19866
20237
|
} catch {
|
|
@@ -20514,147 +20885,1650 @@ var DuplicationAnalyzer = class {
|
|
|
20514
20885
|
});
|
|
20515
20886
|
}
|
|
20516
20887
|
};
|
|
20517
|
-
|
|
20518
|
-
|
|
20519
|
-
|
|
20520
|
-
|
|
20521
|
-
completeness: 0.1,
|
|
20522
|
-
robustness: 0.1,
|
|
20523
|
-
readability: 0.1,
|
|
20524
|
-
maintainability: 0.1,
|
|
20525
|
-
complexity: 0.08,
|
|
20526
|
-
duplication: 0.07,
|
|
20527
|
-
testCoverage: 0.1,
|
|
20528
|
-
testQuality: 0.05,
|
|
20529
|
-
security: 0.08,
|
|
20530
|
-
documentation: 0.04,
|
|
20531
|
-
style: 0.03
|
|
20532
|
-
};
|
|
20533
|
-
var DEFAULT_QUALITY_THRESHOLDS = {
|
|
20534
|
-
minimum: {
|
|
20535
|
-
overall: 85,
|
|
20536
|
-
testCoverage: 80,
|
|
20537
|
-
security: 100
|
|
20538
|
-
// No vulnerabilities allowed
|
|
20539
|
-
},
|
|
20540
|
-
target: {
|
|
20541
|
-
overall: 95,
|
|
20542
|
-
testCoverage: 90
|
|
20543
|
-
}};
|
|
20544
|
-
var QualityEvaluator = class {
|
|
20545
|
-
constructor(projectPath, useSnyk = false) {
|
|
20888
|
+
var execAsync2 = promisify(exec);
|
|
20889
|
+
var BuildVerifier = class {
|
|
20890
|
+
projectPath;
|
|
20891
|
+
constructor(projectPath) {
|
|
20546
20892
|
this.projectPath = projectPath;
|
|
20547
|
-
this.coverageAnalyzer = new CoverageAnalyzer(projectPath);
|
|
20548
|
-
this.securityScanner = new CompositeSecurityScanner(projectPath, useSnyk);
|
|
20549
|
-
this.complexityAnalyzer = new ComplexityAnalyzer(projectPath);
|
|
20550
|
-
this.duplicationAnalyzer = new DuplicationAnalyzer(projectPath);
|
|
20551
20893
|
}
|
|
20552
|
-
coverageAnalyzer;
|
|
20553
|
-
securityScanner;
|
|
20554
|
-
complexityAnalyzer;
|
|
20555
|
-
duplicationAnalyzer;
|
|
20556
20894
|
/**
|
|
20557
|
-
*
|
|
20558
|
-
* Returns QualityScores with 0% hardcoded values (5/12 dimensions are real)
|
|
20895
|
+
* Verify that the project builds successfully
|
|
20559
20896
|
*/
|
|
20560
|
-
async
|
|
20561
|
-
const startTime =
|
|
20562
|
-
|
|
20563
|
-
|
|
20564
|
-
|
|
20565
|
-
|
|
20566
|
-
|
|
20567
|
-
|
|
20568
|
-
|
|
20569
|
-
|
|
20570
|
-
|
|
20571
|
-
|
|
20572
|
-
|
|
20573
|
-
|
|
20574
|
-
|
|
20575
|
-
this.
|
|
20576
|
-
|
|
20577
|
-
|
|
20578
|
-
|
|
20579
|
-
|
|
20580
|
-
|
|
20581
|
-
|
|
20582
|
-
|
|
20583
|
-
|
|
20584
|
-
|
|
20585
|
-
|
|
20586
|
-
|
|
20587
|
-
|
|
20588
|
-
|
|
20589
|
-
|
|
20590
|
-
|
|
20591
|
-
|
|
20592
|
-
|
|
20593
|
-
|
|
20594
|
-
|
|
20595
|
-
|
|
20596
|
-
|
|
20597
|
-
|
|
20598
|
-
|
|
20599
|
-
|
|
20600
|
-
|
|
20601
|
-
|
|
20602
|
-
|
|
20603
|
-
|
|
20604
|
-
|
|
20605
|
-
|
|
20606
|
-
|
|
20607
|
-
|
|
20608
|
-
|
|
20609
|
-
|
|
20610
|
-
|
|
20611
|
-
|
|
20612
|
-
|
|
20613
|
-
|
|
20614
|
-
|
|
20615
|
-
|
|
20616
|
-
|
|
20617
|
-
|
|
20618
|
-
|
|
20619
|
-
|
|
20620
|
-
|
|
20621
|
-
|
|
20622
|
-
|
|
20623
|
-
|
|
20624
|
-
|
|
20625
|
-
|
|
20626
|
-
|
|
20627
|
-
|
|
20897
|
+
async verifyBuild() {
|
|
20898
|
+
const startTime = Date.now();
|
|
20899
|
+
try {
|
|
20900
|
+
const buildCommand2 = await this.detectBuildCommand();
|
|
20901
|
+
if (!buildCommand2) {
|
|
20902
|
+
return {
|
|
20903
|
+
success: true,
|
|
20904
|
+
errors: [],
|
|
20905
|
+
warnings: [],
|
|
20906
|
+
duration: Date.now() - startTime,
|
|
20907
|
+
stdout: "No build command detected",
|
|
20908
|
+
stderr: ""
|
|
20909
|
+
};
|
|
20910
|
+
}
|
|
20911
|
+
const { stdout, stderr } = await execAsync2(buildCommand2, {
|
|
20912
|
+
cwd: this.projectPath,
|
|
20913
|
+
timeout: 12e4,
|
|
20914
|
+
// 2 minutes
|
|
20915
|
+
maxBuffer: 10 * 1024 * 1024
|
|
20916
|
+
// 10MB
|
|
20917
|
+
});
|
|
20918
|
+
const errors = this.parseErrors(stdout + stderr);
|
|
20919
|
+
const warnings = this.parseWarnings(stdout + stderr);
|
|
20920
|
+
return {
|
|
20921
|
+
success: errors.length === 0,
|
|
20922
|
+
errors,
|
|
20923
|
+
warnings,
|
|
20924
|
+
duration: Date.now() - startTime,
|
|
20925
|
+
stdout,
|
|
20926
|
+
stderr
|
|
20927
|
+
};
|
|
20928
|
+
} catch (error) {
|
|
20929
|
+
const execError = error;
|
|
20930
|
+
const errors = this.parseErrors(
|
|
20931
|
+
(execError.stdout ?? "") + (execError.stderr ?? "") || (execError.message ?? "")
|
|
20932
|
+
);
|
|
20933
|
+
const warnings = this.parseWarnings(
|
|
20934
|
+
(execError.stdout ?? "") + (execError.stderr ?? "") || ""
|
|
20935
|
+
);
|
|
20936
|
+
return {
|
|
20937
|
+
success: false,
|
|
20938
|
+
errors,
|
|
20939
|
+
warnings,
|
|
20940
|
+
duration: Date.now() - startTime,
|
|
20941
|
+
stdout: execError.stdout || "",
|
|
20942
|
+
stderr: execError.stderr || execError.message || ""
|
|
20943
|
+
};
|
|
20944
|
+
}
|
|
20945
|
+
}
|
|
20946
|
+
/**
|
|
20947
|
+
* Run TypeScript type checking only (faster than full build)
|
|
20948
|
+
*/
|
|
20949
|
+
async verifyTypes() {
|
|
20950
|
+
const startTime = Date.now();
|
|
20951
|
+
try {
|
|
20952
|
+
const hasTsConfig = await this.fileExists(path20.join(this.projectPath, "tsconfig.json"));
|
|
20953
|
+
if (!hasTsConfig) {
|
|
20954
|
+
return {
|
|
20955
|
+
success: true,
|
|
20956
|
+
errors: [],
|
|
20957
|
+
warnings: [],
|
|
20958
|
+
duration: Date.now() - startTime,
|
|
20959
|
+
stdout: "No tsconfig.json found",
|
|
20960
|
+
stderr: ""
|
|
20961
|
+
};
|
|
20962
|
+
}
|
|
20963
|
+
const { stdout, stderr } = await execAsync2("npx tsc --noEmit", {
|
|
20964
|
+
cwd: this.projectPath,
|
|
20965
|
+
timeout: 6e4,
|
|
20966
|
+
// 1 minute
|
|
20967
|
+
maxBuffer: 10 * 1024 * 1024
|
|
20968
|
+
});
|
|
20969
|
+
const errors = this.parseTypeScriptErrors(stdout + stderr);
|
|
20970
|
+
const warnings = this.parseTypeScriptWarnings(stdout + stderr);
|
|
20971
|
+
return {
|
|
20972
|
+
success: errors.length === 0,
|
|
20973
|
+
errors,
|
|
20974
|
+
warnings,
|
|
20975
|
+
duration: Date.now() - startTime,
|
|
20976
|
+
stdout,
|
|
20977
|
+
stderr
|
|
20978
|
+
};
|
|
20979
|
+
} catch (error) {
|
|
20980
|
+
const execError = error;
|
|
20981
|
+
const errors = this.parseTypeScriptErrors(
|
|
20982
|
+
(execError.stdout ?? "") + (execError.stderr ?? "") || (execError.message ?? "")
|
|
20983
|
+
);
|
|
20984
|
+
const warnings = this.parseTypeScriptWarnings(
|
|
20985
|
+
(execError.stdout ?? "") + (execError.stderr ?? "") || ""
|
|
20986
|
+
);
|
|
20987
|
+
return {
|
|
20988
|
+
success: false,
|
|
20989
|
+
errors,
|
|
20990
|
+
warnings,
|
|
20991
|
+
duration: Date.now() - startTime,
|
|
20992
|
+
stdout: execError.stdout || "",
|
|
20993
|
+
stderr: execError.stderr || execError.message || ""
|
|
20994
|
+
};
|
|
20995
|
+
}
|
|
20628
20996
|
}
|
|
20629
20997
|
/**
|
|
20630
|
-
*
|
|
20631
|
-
* Low complexity = high readability
|
|
20998
|
+
* Detect build command from package.json
|
|
20632
20999
|
*/
|
|
20633
|
-
|
|
20634
|
-
|
|
20635
|
-
|
|
21000
|
+
async detectBuildCommand() {
|
|
21001
|
+
try {
|
|
21002
|
+
const packageJsonPath = path20.join(this.projectPath, "package.json");
|
|
21003
|
+
const content = await fs22.readFile(packageJsonPath, "utf-8");
|
|
21004
|
+
const packageJson = JSON.parse(content);
|
|
21005
|
+
if (packageJson.scripts?.build) {
|
|
21006
|
+
return "npm run build";
|
|
21007
|
+
}
|
|
21008
|
+
if (packageJson.devDependencies?.typescript || packageJson.dependencies?.typescript) {
|
|
21009
|
+
return "npx tsc --noEmit";
|
|
21010
|
+
}
|
|
21011
|
+
return null;
|
|
21012
|
+
} catch {
|
|
21013
|
+
return null;
|
|
21014
|
+
}
|
|
20636
21015
|
}
|
|
20637
21016
|
/**
|
|
20638
|
-
*
|
|
21017
|
+
* Parse errors from build output
|
|
20639
21018
|
*/
|
|
20640
|
-
|
|
20641
|
-
const
|
|
20642
|
-
|
|
20643
|
-
|
|
20644
|
-
|
|
20645
|
-
|
|
20646
|
-
|
|
20647
|
-
|
|
20648
|
-
|
|
21019
|
+
parseErrors(output) {
|
|
21020
|
+
const errors = [];
|
|
21021
|
+
const tsErrorRegex = /(.+?)\((\d+),(\d+)\): error (TS\d+): (.+)/g;
|
|
21022
|
+
let match;
|
|
21023
|
+
while ((match = tsErrorRegex.exec(output)) !== null) {
|
|
21024
|
+
errors.push({
|
|
21025
|
+
file: match[1] || "",
|
|
21026
|
+
line: parseInt(match[2] || "0", 10),
|
|
21027
|
+
column: parseInt(match[3] || "0", 10),
|
|
21028
|
+
code: match[4],
|
|
21029
|
+
message: match[5] || ""
|
|
20649
21030
|
});
|
|
20650
21031
|
}
|
|
20651
|
-
|
|
20652
|
-
|
|
20653
|
-
|
|
20654
|
-
|
|
20655
|
-
|
|
20656
|
-
|
|
20657
|
-
|
|
21032
|
+
const eslintErrorRegex = /(.+?):(\d+):(\d+): (.+)/g;
|
|
21033
|
+
while ((match = eslintErrorRegex.exec(output)) !== null) {
|
|
21034
|
+
if (!output.includes("error")) continue;
|
|
21035
|
+
errors.push({
|
|
21036
|
+
file: match[1] || "",
|
|
21037
|
+
line: parseInt(match[2] || "0", 10),
|
|
21038
|
+
column: parseInt(match[3] || "0", 10),
|
|
21039
|
+
message: match[4] || ""
|
|
21040
|
+
});
|
|
21041
|
+
}
|
|
21042
|
+
return errors;
|
|
21043
|
+
}
|
|
21044
|
+
/**
|
|
21045
|
+
* Parse warnings from build output
|
|
21046
|
+
*/
|
|
21047
|
+
parseWarnings(output) {
|
|
21048
|
+
const warnings = [];
|
|
21049
|
+
const tsWarningRegex = /(.+?)\((\d+),(\d+)\): warning (TS\d+): (.+)/g;
|
|
21050
|
+
let match;
|
|
21051
|
+
while ((match = tsWarningRegex.exec(output)) !== null) {
|
|
21052
|
+
warnings.push({
|
|
21053
|
+
file: match[1] || "",
|
|
21054
|
+
line: parseInt(match[2] || "0", 10),
|
|
21055
|
+
column: parseInt(match[3] || "0", 10),
|
|
21056
|
+
code: match[4],
|
|
21057
|
+
message: match[5] || ""
|
|
21058
|
+
});
|
|
21059
|
+
}
|
|
21060
|
+
return warnings;
|
|
21061
|
+
}
|
|
21062
|
+
/**
|
|
21063
|
+
* Parse TypeScript-specific errors
|
|
21064
|
+
*/
|
|
21065
|
+
parseTypeScriptErrors(output) {
|
|
21066
|
+
return this.parseErrors(output);
|
|
21067
|
+
}
|
|
21068
|
+
/**
|
|
21069
|
+
* Parse TypeScript-specific warnings
|
|
21070
|
+
*/
|
|
21071
|
+
parseTypeScriptWarnings(output) {
|
|
21072
|
+
return this.parseWarnings(output);
|
|
21073
|
+
}
|
|
21074
|
+
/**
|
|
21075
|
+
* Check if file exists
|
|
21076
|
+
*/
|
|
21077
|
+
async fileExists(filePath) {
|
|
21078
|
+
try {
|
|
21079
|
+
await fs22.access(filePath);
|
|
21080
|
+
return true;
|
|
21081
|
+
} catch {
|
|
21082
|
+
return false;
|
|
21083
|
+
}
|
|
21084
|
+
}
|
|
21085
|
+
};
|
|
21086
|
+
|
|
21087
|
+
// src/quality/analyzers/correctness.ts
|
|
21088
|
+
function parseVitestOutput(stdout) {
|
|
21089
|
+
const testsMatch = stdout.match(
|
|
21090
|
+
/Tests\s+(?:(\d+)\s+passed)?(?:\s*\|\s*(\d+)\s+failed)?(?:\s*\|\s*(\d+)\s+skipped)?/
|
|
21091
|
+
);
|
|
21092
|
+
if (testsMatch) {
|
|
21093
|
+
return {
|
|
21094
|
+
passed: parseInt(testsMatch[1] || "0", 10),
|
|
21095
|
+
failed: parseInt(testsMatch[2] || "0", 10),
|
|
21096
|
+
skipped: parseInt(testsMatch[3] || "0", 10)
|
|
21097
|
+
};
|
|
21098
|
+
}
|
|
21099
|
+
try {
|
|
21100
|
+
const json2 = JSON.parse(stdout);
|
|
21101
|
+
return {
|
|
21102
|
+
passed: json2.numPassedTests ?? 0,
|
|
21103
|
+
failed: json2.numFailedTests ?? 0,
|
|
21104
|
+
skipped: json2.numPendingTests ?? 0
|
|
21105
|
+
};
|
|
21106
|
+
} catch {
|
|
21107
|
+
}
|
|
21108
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21109
|
+
}
|
|
21110
|
+
function parseJestOutput(stdout) {
|
|
21111
|
+
try {
|
|
21112
|
+
const json2 = JSON.parse(stdout);
|
|
21113
|
+
return {
|
|
21114
|
+
passed: json2.numPassedTests ?? 0,
|
|
21115
|
+
failed: json2.numFailedTests ?? 0,
|
|
21116
|
+
skipped: json2.numPendingTests ?? 0
|
|
21117
|
+
};
|
|
21118
|
+
} catch {
|
|
21119
|
+
const match = stdout.match(
|
|
21120
|
+
/Tests:\s+(\d+)\s+passed(?:,\s*(\d+)\s+failed)?(?:,\s*(\d+)\s+skipped)?/
|
|
21121
|
+
);
|
|
21122
|
+
if (match) {
|
|
21123
|
+
return {
|
|
21124
|
+
passed: parseInt(match[1] || "0", 10),
|
|
21125
|
+
failed: parseInt(match[2] || "0", 10),
|
|
21126
|
+
skipped: parseInt(match[3] || "0", 10)
|
|
21127
|
+
};
|
|
21128
|
+
}
|
|
21129
|
+
}
|
|
21130
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21131
|
+
}
|
|
21132
|
+
function buildTestCommand(framework) {
|
|
21133
|
+
switch (framework) {
|
|
21134
|
+
case "vitest":
|
|
21135
|
+
return { command: "npx", args: ["vitest", "run", "--reporter=verbose"] };
|
|
21136
|
+
case "jest":
|
|
21137
|
+
return { command: "npx", args: ["jest", "--json"] };
|
|
21138
|
+
case "mocha":
|
|
21139
|
+
return { command: "npx", args: ["mocha", "--reporter=json"] };
|
|
21140
|
+
default:
|
|
21141
|
+
return null;
|
|
21142
|
+
}
|
|
21143
|
+
}
|
|
21144
|
+
var CorrectnessAnalyzer = class {
|
|
21145
|
+
constructor(projectPath) {
|
|
21146
|
+
this.projectPath = projectPath;
|
|
21147
|
+
this.buildVerifier = new BuildVerifier(projectPath);
|
|
21148
|
+
}
|
|
21149
|
+
buildVerifier;
|
|
21150
|
+
/**
|
|
21151
|
+
* Analyze correctness by running tests and verifying build
|
|
21152
|
+
*/
|
|
21153
|
+
async analyze() {
|
|
21154
|
+
const [testResult, buildResult] = await Promise.all([
|
|
21155
|
+
this.runTests(),
|
|
21156
|
+
this.buildVerifier.verifyTypes().catch(() => ({ success: false, errors: [] }))
|
|
21157
|
+
]);
|
|
21158
|
+
const total = testResult.passed + testResult.failed;
|
|
21159
|
+
const testPassRate = total > 0 ? testResult.passed / total * 100 : 0;
|
|
21160
|
+
const buildSuccess = buildResult.success;
|
|
21161
|
+
let score;
|
|
21162
|
+
if (total === 0 && !buildSuccess) {
|
|
21163
|
+
score = 0;
|
|
21164
|
+
} else if (total === 0) {
|
|
21165
|
+
score = 30;
|
|
21166
|
+
} else {
|
|
21167
|
+
score = testPassRate / 100 * 70 + (buildSuccess ? 30 : 0);
|
|
21168
|
+
}
|
|
21169
|
+
score = Math.round(Math.max(0, Math.min(100, score)));
|
|
21170
|
+
const details = this.buildDetails(testResult, buildSuccess, testPassRate, total);
|
|
21171
|
+
return {
|
|
21172
|
+
score,
|
|
21173
|
+
testPassRate,
|
|
21174
|
+
buildSuccess,
|
|
21175
|
+
testsPassed: testResult.passed,
|
|
21176
|
+
testsFailed: testResult.failed,
|
|
21177
|
+
testsSkipped: testResult.skipped,
|
|
21178
|
+
testsTotal: total,
|
|
21179
|
+
buildErrors: buildResult.errors?.length ?? 0,
|
|
21180
|
+
details
|
|
21181
|
+
};
|
|
21182
|
+
}
|
|
21183
|
+
/**
|
|
21184
|
+
* Run tests and parse results
|
|
21185
|
+
*/
|
|
21186
|
+
async runTests() {
|
|
21187
|
+
const framework = await detectTestFramework2(this.projectPath);
|
|
21188
|
+
if (!framework) {
|
|
21189
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21190
|
+
}
|
|
21191
|
+
const cmd = buildTestCommand(framework);
|
|
21192
|
+
if (!cmd) {
|
|
21193
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21194
|
+
}
|
|
21195
|
+
try {
|
|
21196
|
+
const result = await execa(cmd.command, cmd.args, {
|
|
21197
|
+
cwd: this.projectPath,
|
|
21198
|
+
reject: false,
|
|
21199
|
+
timeout: 3e5
|
|
21200
|
+
// 5 minutes
|
|
21201
|
+
});
|
|
21202
|
+
const output = result.stdout + "\n" + result.stderr;
|
|
21203
|
+
switch (framework) {
|
|
21204
|
+
case "vitest":
|
|
21205
|
+
return parseVitestOutput(output);
|
|
21206
|
+
case "jest":
|
|
21207
|
+
return parseJestOutput(result.stdout);
|
|
21208
|
+
case "mocha": {
|
|
21209
|
+
try {
|
|
21210
|
+
const json2 = JSON.parse(result.stdout);
|
|
21211
|
+
return {
|
|
21212
|
+
passed: json2.stats?.passes ?? 0,
|
|
21213
|
+
failed: json2.stats?.failures ?? 0,
|
|
21214
|
+
skipped: json2.stats?.pending ?? 0
|
|
21215
|
+
};
|
|
21216
|
+
} catch {
|
|
21217
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21218
|
+
}
|
|
21219
|
+
}
|
|
21220
|
+
default:
|
|
21221
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21222
|
+
}
|
|
21223
|
+
} catch {
|
|
21224
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21225
|
+
}
|
|
21226
|
+
}
|
|
21227
|
+
buildDetails(testResult, buildSuccess, testPassRate, total) {
|
|
21228
|
+
const parts = [];
|
|
21229
|
+
if (total > 0) {
|
|
21230
|
+
parts.push(`Tests: ${testResult.passed}/${total} passed (${testPassRate.toFixed(1)}%)`);
|
|
21231
|
+
if (testResult.failed > 0) {
|
|
21232
|
+
parts.push(`${testResult.failed} failed`);
|
|
21233
|
+
}
|
|
21234
|
+
} else {
|
|
21235
|
+
parts.push("No tests found");
|
|
21236
|
+
}
|
|
21237
|
+
parts.push(`Build: ${buildSuccess ? "success" : "failed"}`);
|
|
21238
|
+
return parts.join(", ");
|
|
21239
|
+
}
|
|
21240
|
+
};
|
|
21241
|
+
function countExports(ast) {
|
|
21242
|
+
let exportCount = 0;
|
|
21243
|
+
for (const node of ast.body) {
|
|
21244
|
+
switch (node.type) {
|
|
21245
|
+
case "ExportNamedDeclaration":
|
|
21246
|
+
if (node.declaration) {
|
|
21247
|
+
exportCount++;
|
|
21248
|
+
if (node.declaration.type === "VariableDeclaration" && node.declaration.declarations.length > 1) {
|
|
21249
|
+
exportCount += node.declaration.declarations.length - 1;
|
|
21250
|
+
}
|
|
21251
|
+
} else if (node.specifiers.length > 0) {
|
|
21252
|
+
exportCount += node.specifiers.length;
|
|
21253
|
+
}
|
|
21254
|
+
break;
|
|
21255
|
+
case "ExportDefaultDeclaration":
|
|
21256
|
+
exportCount++;
|
|
21257
|
+
break;
|
|
21258
|
+
case "ExportAllDeclaration":
|
|
21259
|
+
exportCount++;
|
|
21260
|
+
break;
|
|
21261
|
+
}
|
|
21262
|
+
}
|
|
21263
|
+
return exportCount;
|
|
21264
|
+
}
|
|
21265
|
+
var CompletenessAnalyzer = class {
|
|
21266
|
+
constructor(projectPath) {
|
|
21267
|
+
this.projectPath = projectPath;
|
|
21268
|
+
}
|
|
21269
|
+
/**
|
|
21270
|
+
* Analyze project completeness
|
|
21271
|
+
*/
|
|
21272
|
+
async analyze(files) {
|
|
21273
|
+
const sourceFiles = files ?? await this.findSourceFiles();
|
|
21274
|
+
const testFiles = await this.findTestFiles();
|
|
21275
|
+
const { totalExports, exportDensity } = await this.analyzeExports(sourceFiles);
|
|
21276
|
+
const testFileRatio = this.calculateTestFileRatio(sourceFiles, testFiles);
|
|
21277
|
+
const hasEntryPoint = await this.checkEntryPoint();
|
|
21278
|
+
const exportScore = Math.min(40, exportDensity * 0.4);
|
|
21279
|
+
const testScore = Math.min(40, testFileRatio * 0.4);
|
|
21280
|
+
const entryScore = hasEntryPoint ? 20 : 0;
|
|
21281
|
+
const score = Math.round(Math.max(0, Math.min(100, exportScore + testScore + entryScore)));
|
|
21282
|
+
const details = this.buildDetails(
|
|
21283
|
+
sourceFiles.length,
|
|
21284
|
+
testFiles.length,
|
|
21285
|
+
totalExports,
|
|
21286
|
+
exportDensity,
|
|
21287
|
+
testFileRatio,
|
|
21288
|
+
hasEntryPoint
|
|
21289
|
+
);
|
|
21290
|
+
return {
|
|
21291
|
+
score,
|
|
21292
|
+
exportDensity,
|
|
21293
|
+
testFileRatio,
|
|
21294
|
+
hasEntryPoint,
|
|
21295
|
+
sourceFileCount: sourceFiles.length,
|
|
21296
|
+
testFileCount: testFiles.length,
|
|
21297
|
+
totalExports,
|
|
21298
|
+
details
|
|
21299
|
+
};
|
|
21300
|
+
}
|
|
21301
|
+
/**
|
|
21302
|
+
* Analyze export density across files
|
|
21303
|
+
*/
|
|
21304
|
+
async analyzeExports(files) {
|
|
21305
|
+
let totalExports = 0;
|
|
21306
|
+
let filesWithExports = 0;
|
|
21307
|
+
for (const file of files) {
|
|
21308
|
+
try {
|
|
21309
|
+
const content = await readFile(file, "utf-8");
|
|
21310
|
+
const ast = parse(content, {
|
|
21311
|
+
loc: true,
|
|
21312
|
+
range: true,
|
|
21313
|
+
jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
|
|
21314
|
+
});
|
|
21315
|
+
const exports$1 = countExports(ast);
|
|
21316
|
+
totalExports += exports$1;
|
|
21317
|
+
if (exports$1 > 0) filesWithExports++;
|
|
21318
|
+
} catch {
|
|
21319
|
+
}
|
|
21320
|
+
}
|
|
21321
|
+
const exportDensity = files.length > 0 ? filesWithExports / files.length * 100 : 0;
|
|
21322
|
+
return { totalExports, exportDensity };
|
|
21323
|
+
}
|
|
21324
|
+
/**
|
|
21325
|
+
* Calculate ratio of source files that have corresponding test files
|
|
21326
|
+
*/
|
|
21327
|
+
calculateTestFileRatio(sourceFiles, testFiles) {
|
|
21328
|
+
if (sourceFiles.length === 0) return 0;
|
|
21329
|
+
const testBaseNames = new Set(
|
|
21330
|
+
testFiles.map((f) => {
|
|
21331
|
+
const name = basename(f);
|
|
21332
|
+
return name.replace(/\.(test|spec)\.(ts|tsx|js|jsx)$/, "");
|
|
21333
|
+
})
|
|
21334
|
+
);
|
|
21335
|
+
let coveredCount = 0;
|
|
21336
|
+
for (const sourceFile of sourceFiles) {
|
|
21337
|
+
const name = basename(sourceFile).replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
21338
|
+
if (testBaseNames.has(name)) {
|
|
21339
|
+
coveredCount++;
|
|
21340
|
+
}
|
|
21341
|
+
}
|
|
21342
|
+
return coveredCount / sourceFiles.length * 100;
|
|
21343
|
+
}
|
|
21344
|
+
/**
|
|
21345
|
+
* Check if project has a clear entry point
|
|
21346
|
+
*/
|
|
21347
|
+
async checkEntryPoint() {
|
|
21348
|
+
const entryPoints = [
|
|
21349
|
+
"src/index.ts",
|
|
21350
|
+
"src/index.js",
|
|
21351
|
+
"src/main.ts",
|
|
21352
|
+
"src/main.js",
|
|
21353
|
+
"index.ts",
|
|
21354
|
+
"index.js"
|
|
21355
|
+
];
|
|
21356
|
+
for (const entry of entryPoints) {
|
|
21357
|
+
try {
|
|
21358
|
+
await access(join(this.projectPath, entry), constants.R_OK);
|
|
21359
|
+
return true;
|
|
21360
|
+
} catch {
|
|
21361
|
+
}
|
|
21362
|
+
}
|
|
21363
|
+
try {
|
|
21364
|
+
const pkgContent = await readFile(join(this.projectPath, "package.json"), "utf-8");
|
|
21365
|
+
const pkg = JSON.parse(pkgContent);
|
|
21366
|
+
if (pkg.main || pkg.exports) return true;
|
|
21367
|
+
} catch {
|
|
21368
|
+
}
|
|
21369
|
+
return false;
|
|
21370
|
+
}
|
|
21371
|
+
buildDetails(sourceCount, testCount, totalExports, exportDensity, testFileRatio, hasEntryPoint) {
|
|
21372
|
+
return [
|
|
21373
|
+
`${sourceCount} source files, ${testCount} test files`,
|
|
21374
|
+
`${totalExports} exports (${exportDensity.toFixed(1)}% files have exports)`,
|
|
21375
|
+
`${testFileRatio.toFixed(1)}% source files have tests`,
|
|
21376
|
+
`Entry point: ${hasEntryPoint ? "found" : "missing"}`
|
|
21377
|
+
].join(", ");
|
|
21378
|
+
}
|
|
21379
|
+
async findSourceFiles() {
|
|
21380
|
+
return glob("**/*.{ts,js,tsx,jsx}", {
|
|
21381
|
+
cwd: this.projectPath,
|
|
21382
|
+
absolute: true,
|
|
21383
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
|
|
21384
|
+
});
|
|
21385
|
+
}
|
|
21386
|
+
async findTestFiles() {
|
|
21387
|
+
return glob("**/*.{test,spec}.{ts,tsx,js,jsx}", {
|
|
21388
|
+
cwd: this.projectPath,
|
|
21389
|
+
absolute: true,
|
|
21390
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"]
|
|
21391
|
+
});
|
|
21392
|
+
}
|
|
21393
|
+
};
|
|
21394
|
+
function analyzeRobustnessPatterns(ast) {
|
|
21395
|
+
let functions = 0;
|
|
21396
|
+
let functionsWithTryCatch = 0;
|
|
21397
|
+
let optionalChaining = 0;
|
|
21398
|
+
let nullishCoalescing = 0;
|
|
21399
|
+
let typeGuards = 0;
|
|
21400
|
+
let typeofChecks = 0;
|
|
21401
|
+
let nullChecks = 0;
|
|
21402
|
+
let insideFunction = false;
|
|
21403
|
+
let currentFunctionHasTryCatch = false;
|
|
21404
|
+
function traverse(node) {
|
|
21405
|
+
const isFunctionNode = node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "MethodDefinition";
|
|
21406
|
+
if (isFunctionNode) {
|
|
21407
|
+
if (insideFunction && currentFunctionHasTryCatch) {
|
|
21408
|
+
functionsWithTryCatch++;
|
|
21409
|
+
}
|
|
21410
|
+
functions++;
|
|
21411
|
+
const previousInsideFunction = insideFunction;
|
|
21412
|
+
const previousHasTryCatch = currentFunctionHasTryCatch;
|
|
21413
|
+
insideFunction = true;
|
|
21414
|
+
currentFunctionHasTryCatch = false;
|
|
21415
|
+
traverseChildren(node);
|
|
21416
|
+
if (currentFunctionHasTryCatch) {
|
|
21417
|
+
functionsWithTryCatch++;
|
|
21418
|
+
}
|
|
21419
|
+
if (isFunctionNode) {
|
|
21420
|
+
functions--;
|
|
21421
|
+
functions++;
|
|
21422
|
+
}
|
|
21423
|
+
insideFunction = previousInsideFunction;
|
|
21424
|
+
currentFunctionHasTryCatch = previousHasTryCatch;
|
|
21425
|
+
return;
|
|
21426
|
+
}
|
|
21427
|
+
switch (node.type) {
|
|
21428
|
+
case "TryStatement":
|
|
21429
|
+
if (insideFunction) {
|
|
21430
|
+
currentFunctionHasTryCatch = true;
|
|
21431
|
+
}
|
|
21432
|
+
break;
|
|
21433
|
+
case "ChainExpression":
|
|
21434
|
+
optionalChaining++;
|
|
21435
|
+
break;
|
|
21436
|
+
case "MemberExpression":
|
|
21437
|
+
if (node.optional) {
|
|
21438
|
+
optionalChaining++;
|
|
21439
|
+
}
|
|
21440
|
+
break;
|
|
21441
|
+
case "CallExpression":
|
|
21442
|
+
if (node.optional) {
|
|
21443
|
+
optionalChaining++;
|
|
21444
|
+
}
|
|
21445
|
+
break;
|
|
21446
|
+
case "LogicalExpression":
|
|
21447
|
+
if (node.operator === "??") {
|
|
21448
|
+
nullishCoalescing++;
|
|
21449
|
+
}
|
|
21450
|
+
break;
|
|
21451
|
+
case "TSTypeAliasDeclaration":
|
|
21452
|
+
typeGuards++;
|
|
21453
|
+
break;
|
|
21454
|
+
case "UnaryExpression":
|
|
21455
|
+
if (node.operator === "typeof") {
|
|
21456
|
+
typeofChecks++;
|
|
21457
|
+
}
|
|
21458
|
+
break;
|
|
21459
|
+
case "BinaryExpression":
|
|
21460
|
+
if (node.operator === "===" || node.operator === "!==" || node.operator === "==" || node.operator === "!=") {
|
|
21461
|
+
if (isNullOrUndefined(node.left) || isNullOrUndefined(node.right)) {
|
|
21462
|
+
nullChecks++;
|
|
21463
|
+
}
|
|
21464
|
+
}
|
|
21465
|
+
break;
|
|
21466
|
+
}
|
|
21467
|
+
traverseChildren(node);
|
|
21468
|
+
}
|
|
21469
|
+
function traverseChildren(node) {
|
|
21470
|
+
for (const key of Object.keys(node)) {
|
|
21471
|
+
if (key === "parent") continue;
|
|
21472
|
+
const child = node[key];
|
|
21473
|
+
if (child && typeof child === "object") {
|
|
21474
|
+
if (Array.isArray(child)) {
|
|
21475
|
+
for (const item of child) {
|
|
21476
|
+
if (item && typeof item === "object" && item.type) {
|
|
21477
|
+
traverse(item);
|
|
21478
|
+
}
|
|
21479
|
+
}
|
|
21480
|
+
} else if (child.type) {
|
|
21481
|
+
traverse(child);
|
|
21482
|
+
}
|
|
21483
|
+
}
|
|
21484
|
+
}
|
|
21485
|
+
}
|
|
21486
|
+
traverse(ast);
|
|
21487
|
+
return {
|
|
21488
|
+
functions,
|
|
21489
|
+
functionsWithTryCatch,
|
|
21490
|
+
optionalChaining,
|
|
21491
|
+
nullishCoalescing,
|
|
21492
|
+
typeGuards,
|
|
21493
|
+
typeofChecks,
|
|
21494
|
+
nullChecks
|
|
21495
|
+
};
|
|
21496
|
+
}
|
|
21497
|
+
function isNullOrUndefined(node) {
|
|
21498
|
+
if (node.type === "Literal" && node.value === null) return true;
|
|
21499
|
+
if (node.type === "Identifier" && node.name === "undefined") return true;
|
|
21500
|
+
return false;
|
|
21501
|
+
}
|
|
21502
|
+
var RobustnessAnalyzer = class {
|
|
21503
|
+
constructor(projectPath) {
|
|
21504
|
+
this.projectPath = projectPath;
|
|
21505
|
+
}
|
|
21506
|
+
/**
|
|
21507
|
+
* Analyze robustness of project files
|
|
21508
|
+
*/
|
|
21509
|
+
async analyze(files) {
|
|
21510
|
+
const targetFiles = files ?? await this.findSourceFiles();
|
|
21511
|
+
let totalFunctions = 0;
|
|
21512
|
+
let totalFunctionsWithTryCatch = 0;
|
|
21513
|
+
let totalOptionalChaining = 0;
|
|
21514
|
+
let totalNullishCoalescing = 0;
|
|
21515
|
+
let totalTypeGuards = 0;
|
|
21516
|
+
let totalTypeofChecks = 0;
|
|
21517
|
+
let totalNullChecks = 0;
|
|
21518
|
+
for (const file of targetFiles) {
|
|
21519
|
+
try {
|
|
21520
|
+
const content = await readFile(file, "utf-8");
|
|
21521
|
+
const ast = parse(content, {
|
|
21522
|
+
loc: true,
|
|
21523
|
+
range: true,
|
|
21524
|
+
jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
|
|
21525
|
+
});
|
|
21526
|
+
const result = analyzeRobustnessPatterns(ast);
|
|
21527
|
+
totalFunctions += result.functions;
|
|
21528
|
+
totalFunctionsWithTryCatch += result.functionsWithTryCatch;
|
|
21529
|
+
totalOptionalChaining += result.optionalChaining;
|
|
21530
|
+
totalNullishCoalescing += result.nullishCoalescing;
|
|
21531
|
+
totalTypeGuards += result.typeGuards;
|
|
21532
|
+
totalTypeofChecks += result.typeofChecks;
|
|
21533
|
+
totalNullChecks += result.nullChecks;
|
|
21534
|
+
} catch {
|
|
21535
|
+
}
|
|
21536
|
+
}
|
|
21537
|
+
const tryCatchRatio = totalFunctions > 0 ? totalFunctionsWithTryCatch / totalFunctions * 100 : 0;
|
|
21538
|
+
const validationPerFunction = totalFunctions > 0 ? (totalTypeGuards + totalTypeofChecks + totalNullChecks) / totalFunctions : 0;
|
|
21539
|
+
const inputValidationScore = Math.min(100, validationPerFunction * 50);
|
|
21540
|
+
const defensivePerFunction = totalFunctions > 0 ? (totalOptionalChaining + totalNullishCoalescing) / totalFunctions : 0;
|
|
21541
|
+
const defensiveCodingScore = Math.min(100, defensivePerFunction * 40);
|
|
21542
|
+
const score = Math.round(
|
|
21543
|
+
Math.max(
|
|
21544
|
+
0,
|
|
21545
|
+
Math.min(
|
|
21546
|
+
100,
|
|
21547
|
+
tryCatchRatio * 0.4 + inputValidationScore * 0.3 + defensiveCodingScore * 0.3
|
|
21548
|
+
)
|
|
21549
|
+
)
|
|
21550
|
+
);
|
|
21551
|
+
const details = [
|
|
21552
|
+
`${totalFunctionsWithTryCatch}/${totalFunctions} functions with error handling`,
|
|
21553
|
+
`${totalOptionalChaining} optional chaining, ${totalNullishCoalescing} nullish coalescing`,
|
|
21554
|
+
`${totalTypeGuards + totalTypeofChecks + totalNullChecks} validation checks`
|
|
21555
|
+
].join(", ");
|
|
21556
|
+
return {
|
|
21557
|
+
score,
|
|
21558
|
+
tryCatchRatio,
|
|
21559
|
+
inputValidationScore,
|
|
21560
|
+
defensiveCodingScore,
|
|
21561
|
+
functionsAnalyzed: totalFunctions,
|
|
21562
|
+
functionsWithErrorHandling: totalFunctionsWithTryCatch,
|
|
21563
|
+
optionalChainingCount: totalOptionalChaining,
|
|
21564
|
+
nullishCoalescingCount: totalNullishCoalescing,
|
|
21565
|
+
typeGuardCount: totalTypeGuards,
|
|
21566
|
+
details
|
|
21567
|
+
};
|
|
21568
|
+
}
|
|
21569
|
+
async findSourceFiles() {
|
|
21570
|
+
return glob("**/*.{ts,js,tsx,jsx}", {
|
|
21571
|
+
cwd: this.projectPath,
|
|
21572
|
+
absolute: true,
|
|
21573
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
|
|
21574
|
+
});
|
|
21575
|
+
}
|
|
21576
|
+
};
|
|
21577
|
+
var TRIVIAL_PATTERNS = [
|
|
21578
|
+
/\.toBeDefined\(\)/,
|
|
21579
|
+
/\.toBeUndefined\(\)/,
|
|
21580
|
+
/\.toBeTruthy\(\)/,
|
|
21581
|
+
/\.toBeFalsy\(\)/,
|
|
21582
|
+
/\.toBe\(true\)/,
|
|
21583
|
+
/\.toBe\(false\)/,
|
|
21584
|
+
/\.not\.toBeNull\(\)/,
|
|
21585
|
+
/\.toBeInstanceOf\(/,
|
|
21586
|
+
/\.toBeTypeOf\(/
|
|
21587
|
+
];
|
|
21588
|
+
var EDGE_CASE_PATTERNS = [
|
|
21589
|
+
/error/i,
|
|
21590
|
+
/edge/i,
|
|
21591
|
+
/boundary/i,
|
|
21592
|
+
/invalid/i,
|
|
21593
|
+
/empty/i,
|
|
21594
|
+
/null/i,
|
|
21595
|
+
/undefined/i,
|
|
21596
|
+
/negative/i,
|
|
21597
|
+
/overflow/i,
|
|
21598
|
+
/timeout/i,
|
|
21599
|
+
/fail/i,
|
|
21600
|
+
/reject/i,
|
|
21601
|
+
/throw/i,
|
|
21602
|
+
/missing/i,
|
|
21603
|
+
/malformed/i,
|
|
21604
|
+
/corrupt/i
|
|
21605
|
+
];
|
|
21606
|
+
var MATCHER_PATTERNS = [
|
|
21607
|
+
/\.toBe\(/,
|
|
21608
|
+
/\.toEqual\(/,
|
|
21609
|
+
/\.toStrictEqual\(/,
|
|
21610
|
+
/\.toContain\(/,
|
|
21611
|
+
/\.toMatch\(/,
|
|
21612
|
+
/\.toHaveLength\(/,
|
|
21613
|
+
/\.toHaveProperty\(/,
|
|
21614
|
+
/\.toThrow/,
|
|
21615
|
+
/\.toHaveBeenCalled/,
|
|
21616
|
+
/\.toHaveBeenCalledWith\(/,
|
|
21617
|
+
/\.toHaveBeenCalledTimes\(/,
|
|
21618
|
+
/\.toBeGreaterThan\(/,
|
|
21619
|
+
/\.toBeLessThan\(/,
|
|
21620
|
+
/\.toBeCloseTo\(/,
|
|
21621
|
+
/\.resolves\./,
|
|
21622
|
+
/\.rejects\./
|
|
21623
|
+
];
|
|
21624
|
+
function analyzeTestFile(content) {
|
|
21625
|
+
const lines = content.split("\n");
|
|
21626
|
+
let totalAssertions = 0;
|
|
21627
|
+
let trivialAssertions = 0;
|
|
21628
|
+
let totalTests = 0;
|
|
21629
|
+
let edgeCaseTests = 0;
|
|
21630
|
+
const matchersUsed = /* @__PURE__ */ new Set();
|
|
21631
|
+
for (const line of lines) {
|
|
21632
|
+
const trimmed = line.trim();
|
|
21633
|
+
if (/^\s*(it|test)\s*\(/.test(trimmed) || /^\s*(it|test)\s*\./.test(trimmed)) {
|
|
21634
|
+
totalTests++;
|
|
21635
|
+
for (const pattern of EDGE_CASE_PATTERNS) {
|
|
21636
|
+
if (pattern.test(trimmed)) {
|
|
21637
|
+
edgeCaseTests++;
|
|
21638
|
+
break;
|
|
21639
|
+
}
|
|
21640
|
+
}
|
|
21641
|
+
}
|
|
21642
|
+
if (/expect\s*\(/.test(trimmed)) {
|
|
21643
|
+
totalAssertions++;
|
|
21644
|
+
for (const pattern of TRIVIAL_PATTERNS) {
|
|
21645
|
+
if (pattern.test(trimmed)) {
|
|
21646
|
+
trivialAssertions++;
|
|
21647
|
+
break;
|
|
21648
|
+
}
|
|
21649
|
+
}
|
|
21650
|
+
for (let i = 0; i < MATCHER_PATTERNS.length; i++) {
|
|
21651
|
+
if (MATCHER_PATTERNS[i].test(trimmed)) {
|
|
21652
|
+
matchersUsed.add(i);
|
|
21653
|
+
}
|
|
21654
|
+
}
|
|
21655
|
+
}
|
|
21656
|
+
}
|
|
21657
|
+
return { totalAssertions, trivialAssertions, totalTests, edgeCaseTests, matchersUsed };
|
|
21658
|
+
}
|
|
21659
|
+
var TestQualityAnalyzer = class {
|
|
21660
|
+
constructor(projectPath) {
|
|
21661
|
+
this.projectPath = projectPath;
|
|
21662
|
+
}
|
|
21663
|
+
/**
|
|
21664
|
+
* Analyze test quality across the project
|
|
21665
|
+
*/
|
|
21666
|
+
async analyze(files) {
|
|
21667
|
+
const testFiles = files ?? await this.findTestFiles();
|
|
21668
|
+
let totalAssertions = 0;
|
|
21669
|
+
let trivialAssertions = 0;
|
|
21670
|
+
let totalTests = 0;
|
|
21671
|
+
let edgeCaseTests = 0;
|
|
21672
|
+
const allMatchersUsed = /* @__PURE__ */ new Set();
|
|
21673
|
+
for (const file of testFiles) {
|
|
21674
|
+
try {
|
|
21675
|
+
const content = await readFile(file, "utf-8");
|
|
21676
|
+
const result = analyzeTestFile(content);
|
|
21677
|
+
totalAssertions += result.totalAssertions;
|
|
21678
|
+
trivialAssertions += result.trivialAssertions;
|
|
21679
|
+
totalTests += result.totalTests;
|
|
21680
|
+
edgeCaseTests += result.edgeCaseTests;
|
|
21681
|
+
for (const m of result.matchersUsed) {
|
|
21682
|
+
allMatchersUsed.add(m);
|
|
21683
|
+
}
|
|
21684
|
+
} catch {
|
|
21685
|
+
}
|
|
21686
|
+
}
|
|
21687
|
+
const assertionDensity = totalTests > 0 ? totalAssertions / totalTests : 0;
|
|
21688
|
+
const trivialAssertionRatio = totalAssertions > 0 ? trivialAssertions / totalAssertions * 100 : 0;
|
|
21689
|
+
const edgeCaseRatio = totalTests > 0 ? edgeCaseTests / totalTests * 100 : 0;
|
|
21690
|
+
const assertionDiversity = allMatchersUsed.size / MATCHER_PATTERNS.length * 100;
|
|
21691
|
+
let score = 100;
|
|
21692
|
+
score -= Math.min(40, trivialAssertionRatio * 0.4);
|
|
21693
|
+
if (assertionDensity < 2) {
|
|
21694
|
+
score -= (2 - assertionDensity) * 10;
|
|
21695
|
+
}
|
|
21696
|
+
if (edgeCaseRatio > 10) {
|
|
21697
|
+
score += Math.min(10, (edgeCaseRatio - 10) * 0.5);
|
|
21698
|
+
}
|
|
21699
|
+
if (assertionDiversity > 30) {
|
|
21700
|
+
score += Math.min(10, (assertionDiversity - 30) * 0.15);
|
|
21701
|
+
}
|
|
21702
|
+
if (totalTests === 0) {
|
|
21703
|
+
score = 0;
|
|
21704
|
+
}
|
|
21705
|
+
score = Math.round(Math.max(0, Math.min(100, score)));
|
|
21706
|
+
const details = [
|
|
21707
|
+
`${totalTests} tests, ${totalAssertions} assertions`,
|
|
21708
|
+
`${assertionDensity.toFixed(1)} assertions/test`,
|
|
21709
|
+
`${trivialAssertionRatio.toFixed(1)}% trivial`,
|
|
21710
|
+
`${edgeCaseRatio.toFixed(1)}% edge cases`,
|
|
21711
|
+
`${allMatchersUsed.size}/${MATCHER_PATTERNS.length} matcher types`
|
|
21712
|
+
].join(", ");
|
|
21713
|
+
return {
|
|
21714
|
+
score,
|
|
21715
|
+
assertionDensity,
|
|
21716
|
+
trivialAssertionRatio,
|
|
21717
|
+
edgeCaseRatio,
|
|
21718
|
+
assertionDiversity,
|
|
21719
|
+
totalTestFiles: testFiles.length,
|
|
21720
|
+
totalAssertions,
|
|
21721
|
+
trivialAssertions,
|
|
21722
|
+
edgeCaseTests,
|
|
21723
|
+
totalTests,
|
|
21724
|
+
details
|
|
21725
|
+
};
|
|
21726
|
+
}
|
|
21727
|
+
async findTestFiles() {
|
|
21728
|
+
return glob("**/*.{test,spec}.{ts,tsx,js,jsx}", {
|
|
21729
|
+
cwd: this.projectPath,
|
|
21730
|
+
absolute: true,
|
|
21731
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"]
|
|
21732
|
+
});
|
|
21733
|
+
}
|
|
21734
|
+
};
|
|
21735
|
+
function hasJSDocComment(node, sourceCode) {
|
|
21736
|
+
const startLine = node.loc.start.line;
|
|
21737
|
+
const lines = sourceCode.split("\n");
|
|
21738
|
+
for (let i = startLine - 2; i >= Math.max(0, startLine - 10); i--) {
|
|
21739
|
+
const line = lines[i]?.trim() ?? "";
|
|
21740
|
+
if (line.endsWith("*/")) {
|
|
21741
|
+
for (let j = i; j >= Math.max(0, i - 20); j--) {
|
|
21742
|
+
const commentLine = lines[j]?.trim() ?? "";
|
|
21743
|
+
if (commentLine.startsWith("/**")) return true;
|
|
21744
|
+
if (commentLine.startsWith("/*") && !commentLine.startsWith("/**")) return false;
|
|
21745
|
+
}
|
|
21746
|
+
}
|
|
21747
|
+
if (line && !line.startsWith("*") && !line.startsWith("//") && !line.startsWith("/*")) {
|
|
21748
|
+
break;
|
|
21749
|
+
}
|
|
21750
|
+
}
|
|
21751
|
+
return false;
|
|
21752
|
+
}
|
|
21753
|
+
function analyzeExportDocumentation(ast, sourceCode) {
|
|
21754
|
+
let exported = 0;
|
|
21755
|
+
let documented = 0;
|
|
21756
|
+
for (const node of ast.body) {
|
|
21757
|
+
if (node.type === "ExportNamedDeclaration" && node.declaration) {
|
|
21758
|
+
const decl = node.declaration;
|
|
21759
|
+
switch (decl.type) {
|
|
21760
|
+
case "FunctionDeclaration":
|
|
21761
|
+
exported++;
|
|
21762
|
+
if (hasJSDocComment(node, sourceCode)) documented++;
|
|
21763
|
+
break;
|
|
21764
|
+
case "ClassDeclaration":
|
|
21765
|
+
exported++;
|
|
21766
|
+
if (hasJSDocComment(node, sourceCode)) documented++;
|
|
21767
|
+
break;
|
|
21768
|
+
case "TSInterfaceDeclaration":
|
|
21769
|
+
case "TSTypeAliasDeclaration":
|
|
21770
|
+
case "TSEnumDeclaration":
|
|
21771
|
+
exported++;
|
|
21772
|
+
if (hasJSDocComment(node, sourceCode)) documented++;
|
|
21773
|
+
break;
|
|
21774
|
+
case "VariableDeclaration":
|
|
21775
|
+
for (const _declarator of decl.declarations) {
|
|
21776
|
+
exported++;
|
|
21777
|
+
if (hasJSDocComment(node, sourceCode)) documented++;
|
|
21778
|
+
}
|
|
21779
|
+
break;
|
|
21780
|
+
}
|
|
21781
|
+
} else if (node.type === "ExportDefaultDeclaration") {
|
|
21782
|
+
exported++;
|
|
21783
|
+
if (hasJSDocComment(node, sourceCode)) documented++;
|
|
21784
|
+
}
|
|
21785
|
+
}
|
|
21786
|
+
return { exported, documented };
|
|
21787
|
+
}
|
|
21788
|
+
var DocumentationAnalyzer = class {
|
|
21789
|
+
constructor(projectPath) {
|
|
21790
|
+
this.projectPath = projectPath;
|
|
21791
|
+
}
|
|
21792
|
+
/**
|
|
21793
|
+
* Analyze documentation quality
|
|
21794
|
+
*/
|
|
21795
|
+
async analyze(files) {
|
|
21796
|
+
const targetFiles = files ?? await this.findSourceFiles();
|
|
21797
|
+
let totalExported = 0;
|
|
21798
|
+
let totalDocumented = 0;
|
|
21799
|
+
for (const file of targetFiles) {
|
|
21800
|
+
try {
|
|
21801
|
+
const content = await readFile(file, "utf-8");
|
|
21802
|
+
const ast = parse(content, {
|
|
21803
|
+
loc: true,
|
|
21804
|
+
range: true,
|
|
21805
|
+
comment: true,
|
|
21806
|
+
jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
|
|
21807
|
+
});
|
|
21808
|
+
const result = analyzeExportDocumentation(ast, content);
|
|
21809
|
+
totalExported += result.exported;
|
|
21810
|
+
totalDocumented += result.documented;
|
|
21811
|
+
} catch {
|
|
21812
|
+
}
|
|
21813
|
+
}
|
|
21814
|
+
const jsdocCoverage = totalExported > 0 ? totalDocumented / totalExported * 100 : 0;
|
|
21815
|
+
const hasReadme = await this.fileExists("README.md");
|
|
21816
|
+
const hasChangelog = await this.fileExists("CHANGELOG.md") || await this.fileExists("CHANGES.md");
|
|
21817
|
+
const score = Math.round(
|
|
21818
|
+
Math.max(
|
|
21819
|
+
0,
|
|
21820
|
+
Math.min(100, jsdocCoverage * 0.7 + (hasReadme ? 20 : 0) + (hasChangelog ? 10 : 0))
|
|
21821
|
+
)
|
|
21822
|
+
);
|
|
21823
|
+
const details = [
|
|
21824
|
+
`${totalDocumented}/${totalExported} exports documented (${jsdocCoverage.toFixed(1)}%)`,
|
|
21825
|
+
`README: ${hasReadme ? "yes" : "no"}`,
|
|
21826
|
+
`CHANGELOG: ${hasChangelog ? "yes" : "no"}`
|
|
21827
|
+
].join(", ");
|
|
21828
|
+
return {
|
|
21829
|
+
score,
|
|
21830
|
+
jsdocCoverage,
|
|
21831
|
+
hasReadme,
|
|
21832
|
+
hasChangelog,
|
|
21833
|
+
exportedDeclarations: totalExported,
|
|
21834
|
+
documentedDeclarations: totalDocumented,
|
|
21835
|
+
details
|
|
21836
|
+
};
|
|
21837
|
+
}
|
|
21838
|
+
async fileExists(relativePath) {
|
|
21839
|
+
try {
|
|
21840
|
+
await access(join(this.projectPath, relativePath), constants.R_OK);
|
|
21841
|
+
return true;
|
|
21842
|
+
} catch {
|
|
21843
|
+
return false;
|
|
21844
|
+
}
|
|
21845
|
+
}
|
|
21846
|
+
async findSourceFiles() {
|
|
21847
|
+
return glob("**/*.{ts,js,tsx,jsx}", {
|
|
21848
|
+
cwd: this.projectPath,
|
|
21849
|
+
absolute: true,
|
|
21850
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
|
|
21851
|
+
});
|
|
21852
|
+
}
|
|
21853
|
+
};
|
|
21854
|
+
async function detectLinter(projectPath) {
|
|
21855
|
+
try {
|
|
21856
|
+
const pkgContent = await readFile(join(projectPath, "package.json"), "utf-8");
|
|
21857
|
+
const pkg = JSON.parse(pkgContent);
|
|
21858
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
21859
|
+
if (deps.oxlint) return "oxlint";
|
|
21860
|
+
if (deps["@biomejs/biome"] || deps.biome) return "biome";
|
|
21861
|
+
if (deps.eslint) return "eslint";
|
|
21862
|
+
return null;
|
|
21863
|
+
} catch {
|
|
21864
|
+
return null;
|
|
21865
|
+
}
|
|
21866
|
+
}
|
|
21867
|
+
async function runOxlint(projectPath) {
|
|
21868
|
+
try {
|
|
21869
|
+
const result = await execa("npx", ["oxlint", "src", "--format=json"], {
|
|
21870
|
+
cwd: projectPath,
|
|
21871
|
+
reject: false,
|
|
21872
|
+
timeout: 6e4
|
|
21873
|
+
});
|
|
21874
|
+
try {
|
|
21875
|
+
const output = JSON.parse(result.stdout);
|
|
21876
|
+
const errors = Array.isArray(output) ? output.filter((d) => {
|
|
21877
|
+
const diag = d;
|
|
21878
|
+
return diag.severity === 2 || diag.severity === "error";
|
|
21879
|
+
}).length : 0;
|
|
21880
|
+
const warnings = Array.isArray(output) ? output.filter((d) => {
|
|
21881
|
+
const diag = d;
|
|
21882
|
+
return diag.severity === 1 || diag.severity === "warning";
|
|
21883
|
+
}).length : 0;
|
|
21884
|
+
return { errors, warnings };
|
|
21885
|
+
} catch {
|
|
21886
|
+
const lines = (result.stdout + result.stderr).split("\n");
|
|
21887
|
+
let errors = 0;
|
|
21888
|
+
let warnings = 0;
|
|
21889
|
+
for (const line of lines) {
|
|
21890
|
+
if (/error\[/.test(line) || /✖.*error/.test(line)) errors++;
|
|
21891
|
+
if (/warning\[/.test(line) || /⚠.*warning/.test(line)) warnings++;
|
|
21892
|
+
}
|
|
21893
|
+
return { errors, warnings };
|
|
21894
|
+
}
|
|
21895
|
+
} catch {
|
|
21896
|
+
return { errors: 0, warnings: 0 };
|
|
21897
|
+
}
|
|
21898
|
+
}
|
|
21899
|
+
async function runEslint(projectPath) {
|
|
21900
|
+
try {
|
|
21901
|
+
const result = await execa("npx", ["eslint", "src", "--format=json"], {
|
|
21902
|
+
cwd: projectPath,
|
|
21903
|
+
reject: false,
|
|
21904
|
+
timeout: 12e4
|
|
21905
|
+
});
|
|
21906
|
+
try {
|
|
21907
|
+
const output = JSON.parse(result.stdout);
|
|
21908
|
+
const errors = output.reduce((sum, f) => sum + f.errorCount, 0);
|
|
21909
|
+
const warnings = output.reduce((sum, f) => sum + f.warningCount, 0);
|
|
21910
|
+
return { errors, warnings };
|
|
21911
|
+
} catch {
|
|
21912
|
+
return { errors: 0, warnings: 0 };
|
|
21913
|
+
}
|
|
21914
|
+
} catch {
|
|
21915
|
+
return { errors: 0, warnings: 0 };
|
|
21916
|
+
}
|
|
21917
|
+
}
|
|
21918
|
+
async function runBiome(projectPath) {
|
|
21919
|
+
try {
|
|
21920
|
+
const result = await execa("npx", ["@biomejs/biome", "lint", "src", "--reporter=json"], {
|
|
21921
|
+
cwd: projectPath,
|
|
21922
|
+
reject: false,
|
|
21923
|
+
timeout: 6e4
|
|
21924
|
+
});
|
|
21925
|
+
try {
|
|
21926
|
+
const output = JSON.parse(result.stdout);
|
|
21927
|
+
const diagnostics = output.diagnostics ?? [];
|
|
21928
|
+
const errors = diagnostics.filter(
|
|
21929
|
+
(d) => d.severity === "error"
|
|
21930
|
+
).length;
|
|
21931
|
+
const warnings = diagnostics.filter(
|
|
21932
|
+
(d) => d.severity === "warning"
|
|
21933
|
+
).length;
|
|
21934
|
+
return { errors, warnings };
|
|
21935
|
+
} catch {
|
|
21936
|
+
return { errors: 0, warnings: 0 };
|
|
21937
|
+
}
|
|
21938
|
+
} catch {
|
|
21939
|
+
return { errors: 0, warnings: 0 };
|
|
21940
|
+
}
|
|
21941
|
+
}
|
|
21942
|
+
var StyleAnalyzer = class {
|
|
21943
|
+
constructor(projectPath) {
|
|
21944
|
+
this.projectPath = projectPath;
|
|
21945
|
+
}
|
|
21946
|
+
/**
|
|
21947
|
+
* Analyze style/linting quality
|
|
21948
|
+
*/
|
|
21949
|
+
async analyze() {
|
|
21950
|
+
const linter = await detectLinter(this.projectPath);
|
|
21951
|
+
if (!linter) {
|
|
21952
|
+
return {
|
|
21953
|
+
score: 50,
|
|
21954
|
+
// Neutral score when no linter is configured
|
|
21955
|
+
errors: 0,
|
|
21956
|
+
warnings: 0,
|
|
21957
|
+
linterUsed: null,
|
|
21958
|
+
details: "No linter configured"
|
|
21959
|
+
};
|
|
21960
|
+
}
|
|
21961
|
+
let result;
|
|
21962
|
+
switch (linter) {
|
|
21963
|
+
case "oxlint":
|
|
21964
|
+
result = await runOxlint(this.projectPath);
|
|
21965
|
+
break;
|
|
21966
|
+
case "eslint":
|
|
21967
|
+
result = await runEslint(this.projectPath);
|
|
21968
|
+
break;
|
|
21969
|
+
case "biome":
|
|
21970
|
+
result = await runBiome(this.projectPath);
|
|
21971
|
+
break;
|
|
21972
|
+
}
|
|
21973
|
+
const score = Math.round(
|
|
21974
|
+
Math.max(0, Math.min(100, 100 - result.errors * 5 - result.warnings * 2))
|
|
21975
|
+
);
|
|
21976
|
+
const details = [
|
|
21977
|
+
`Linter: ${linter}`,
|
|
21978
|
+
`${result.errors} errors, ${result.warnings} warnings`
|
|
21979
|
+
].join(", ");
|
|
21980
|
+
return {
|
|
21981
|
+
score,
|
|
21982
|
+
errors: result.errors,
|
|
21983
|
+
warnings: result.warnings,
|
|
21984
|
+
linterUsed: linter,
|
|
21985
|
+
details
|
|
21986
|
+
};
|
|
21987
|
+
}
|
|
21988
|
+
};
|
|
21989
|
+
function analyzeReadabilityPatterns(ast) {
|
|
21990
|
+
let goodNames = 0;
|
|
21991
|
+
let totalNames = 0;
|
|
21992
|
+
const functionLengths = [];
|
|
21993
|
+
let maxNestingDepth = 0;
|
|
21994
|
+
const cognitiveComplexities = [];
|
|
21995
|
+
const allowedSingleChar = /* @__PURE__ */ new Set(["i", "j", "k", "_", "e", "x", "y", "t", "n"]);
|
|
21996
|
+
function isGoodName(name) {
|
|
21997
|
+
if (name.length === 1) {
|
|
21998
|
+
return allowedSingleChar.has(name);
|
|
21999
|
+
}
|
|
22000
|
+
return name.length >= 3 && name.length <= 30;
|
|
22001
|
+
}
|
|
22002
|
+
function analyzeFunction(node) {
|
|
22003
|
+
if (node.loc) {
|
|
22004
|
+
const length = node.loc.end.line - node.loc.start.line + 1;
|
|
22005
|
+
functionLengths.push(length);
|
|
22006
|
+
}
|
|
22007
|
+
let localMaxNesting = 0;
|
|
22008
|
+
let cognitiveComplexity = 0;
|
|
22009
|
+
function traverseForComplexity(n, currentNesting) {
|
|
22010
|
+
localMaxNesting = Math.max(localMaxNesting, currentNesting);
|
|
22011
|
+
let nextNesting = currentNesting;
|
|
22012
|
+
let incrementsNesting = false;
|
|
22013
|
+
switch (n.type) {
|
|
22014
|
+
case "IfStatement":
|
|
22015
|
+
cognitiveComplexity += 1;
|
|
22016
|
+
incrementsNesting = true;
|
|
22017
|
+
break;
|
|
22018
|
+
case "ForStatement":
|
|
22019
|
+
case "ForInStatement":
|
|
22020
|
+
case "ForOfStatement":
|
|
22021
|
+
case "WhileStatement":
|
|
22022
|
+
case "DoWhileStatement":
|
|
22023
|
+
cognitiveComplexity += 1 + currentNesting;
|
|
22024
|
+
incrementsNesting = true;
|
|
22025
|
+
break;
|
|
22026
|
+
case "SwitchStatement":
|
|
22027
|
+
cognitiveComplexity += 1 + currentNesting;
|
|
22028
|
+
incrementsNesting = true;
|
|
22029
|
+
break;
|
|
22030
|
+
case "CatchClause":
|
|
22031
|
+
cognitiveComplexity += 1 + currentNesting;
|
|
22032
|
+
incrementsNesting = true;
|
|
22033
|
+
break;
|
|
22034
|
+
case "ConditionalExpression":
|
|
22035
|
+
cognitiveComplexity += 1 + currentNesting;
|
|
22036
|
+
incrementsNesting = true;
|
|
22037
|
+
break;
|
|
22038
|
+
case "LogicalExpression":
|
|
22039
|
+
if (n.operator === "&&" || n.operator === "||") {
|
|
22040
|
+
cognitiveComplexity += 1;
|
|
22041
|
+
}
|
|
22042
|
+
break;
|
|
22043
|
+
case "FunctionExpression":
|
|
22044
|
+
case "ArrowFunctionExpression":
|
|
22045
|
+
incrementsNesting = true;
|
|
22046
|
+
break;
|
|
22047
|
+
}
|
|
22048
|
+
if (incrementsNesting) {
|
|
22049
|
+
nextNesting = currentNesting + 1;
|
|
22050
|
+
}
|
|
22051
|
+
traverseChildren(n, (child) => traverseForComplexity(child, nextNesting));
|
|
22052
|
+
}
|
|
22053
|
+
traverseForComplexity(node, 0);
|
|
22054
|
+
maxNestingDepth = Math.max(maxNestingDepth, localMaxNesting);
|
|
22055
|
+
cognitiveComplexities.push(cognitiveComplexity);
|
|
22056
|
+
}
|
|
22057
|
+
function collectNames(node) {
|
|
22058
|
+
switch (node.type) {
|
|
22059
|
+
case "FunctionDeclaration":
|
|
22060
|
+
if (node.id) {
|
|
22061
|
+
totalNames++;
|
|
22062
|
+
if (isGoodName(node.id.name)) {
|
|
22063
|
+
goodNames++;
|
|
22064
|
+
}
|
|
22065
|
+
}
|
|
22066
|
+
for (const param of node.params) {
|
|
22067
|
+
collectParamNames(param);
|
|
22068
|
+
}
|
|
22069
|
+
break;
|
|
22070
|
+
case "FunctionExpression":
|
|
22071
|
+
case "ArrowFunctionExpression":
|
|
22072
|
+
for (const param of node.params) {
|
|
22073
|
+
collectParamNames(param);
|
|
22074
|
+
}
|
|
22075
|
+
break;
|
|
22076
|
+
case "VariableDeclarator":
|
|
22077
|
+
if (node.id.type === "Identifier") {
|
|
22078
|
+
totalNames++;
|
|
22079
|
+
if (isGoodName(node.id.name)) {
|
|
22080
|
+
goodNames++;
|
|
22081
|
+
}
|
|
22082
|
+
}
|
|
22083
|
+
break;
|
|
22084
|
+
case "MethodDefinition":
|
|
22085
|
+
if (node.key.type === "Identifier") {
|
|
22086
|
+
totalNames++;
|
|
22087
|
+
if (isGoodName(node.key.name)) {
|
|
22088
|
+
goodNames++;
|
|
22089
|
+
}
|
|
22090
|
+
}
|
|
22091
|
+
if (node.value.type === "FunctionExpression") {
|
|
22092
|
+
for (const param of node.value.params) {
|
|
22093
|
+
collectParamNames(param);
|
|
22094
|
+
}
|
|
22095
|
+
}
|
|
22096
|
+
break;
|
|
22097
|
+
}
|
|
22098
|
+
}
|
|
22099
|
+
function collectParamNames(param) {
|
|
22100
|
+
if (param.type === "Identifier") {
|
|
22101
|
+
totalNames++;
|
|
22102
|
+
if (isGoodName(param.name)) {
|
|
22103
|
+
goodNames++;
|
|
22104
|
+
}
|
|
22105
|
+
} else if (param.type === "AssignmentPattern" && param.left.type === "Identifier") {
|
|
22106
|
+
totalNames++;
|
|
22107
|
+
if (isGoodName(param.left.name)) {
|
|
22108
|
+
goodNames++;
|
|
22109
|
+
}
|
|
22110
|
+
} else if (param.type === "RestElement" && param.argument.type === "Identifier") {
|
|
22111
|
+
totalNames++;
|
|
22112
|
+
if (isGoodName(param.argument.name)) {
|
|
22113
|
+
goodNames++;
|
|
22114
|
+
}
|
|
22115
|
+
}
|
|
22116
|
+
}
|
|
22117
|
+
function traverse(node) {
|
|
22118
|
+
const isFunctionNode = node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "MethodDefinition";
|
|
22119
|
+
collectNames(node);
|
|
22120
|
+
if (isFunctionNode) {
|
|
22121
|
+
analyzeFunction(node);
|
|
22122
|
+
}
|
|
22123
|
+
traverseChildren(node, traverse);
|
|
22124
|
+
}
|
|
22125
|
+
function traverseChildren(node, callback) {
|
|
22126
|
+
for (const key of Object.keys(node)) {
|
|
22127
|
+
if (key === "parent") continue;
|
|
22128
|
+
const child = node[key];
|
|
22129
|
+
if (child && typeof child === "object") {
|
|
22130
|
+
if (Array.isArray(child)) {
|
|
22131
|
+
for (const item of child) {
|
|
22132
|
+
if (item && typeof item === "object" && item.type) {
|
|
22133
|
+
callback(item);
|
|
22134
|
+
}
|
|
22135
|
+
}
|
|
22136
|
+
} else if (child.type) {
|
|
22137
|
+
callback(child);
|
|
22138
|
+
}
|
|
22139
|
+
}
|
|
22140
|
+
}
|
|
22141
|
+
}
|
|
22142
|
+
traverse(ast);
|
|
22143
|
+
return {
|
|
22144
|
+
goodNames,
|
|
22145
|
+
totalNames,
|
|
22146
|
+
functionLengths,
|
|
22147
|
+
maxNestingDepth,
|
|
22148
|
+
cognitiveComplexities
|
|
22149
|
+
};
|
|
22150
|
+
}
|
|
22151
|
+
var ReadabilityAnalyzer = class {
|
|
22152
|
+
constructor(projectPath) {
|
|
22153
|
+
this.projectPath = projectPath;
|
|
22154
|
+
}
|
|
22155
|
+
/**
|
|
22156
|
+
* Analyze readability of project files
|
|
22157
|
+
*/
|
|
22158
|
+
async analyze(files) {
|
|
22159
|
+
const targetFiles = files ?? await this.findSourceFiles();
|
|
22160
|
+
let totalGoodNames = 0;
|
|
22161
|
+
let totalNames = 0;
|
|
22162
|
+
const allFunctionLengths = [];
|
|
22163
|
+
let globalMaxNestingDepth = 0;
|
|
22164
|
+
const allCognitiveComplexities = [];
|
|
22165
|
+
for (const file of targetFiles) {
|
|
22166
|
+
try {
|
|
22167
|
+
const content = await readFile(file, "utf-8");
|
|
22168
|
+
const ast = parse(content, {
|
|
22169
|
+
loc: true,
|
|
22170
|
+
range: true,
|
|
22171
|
+
jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
|
|
22172
|
+
});
|
|
22173
|
+
const result = analyzeReadabilityPatterns(ast);
|
|
22174
|
+
totalGoodNames += result.goodNames;
|
|
22175
|
+
totalNames += result.totalNames;
|
|
22176
|
+
allFunctionLengths.push(...result.functionLengths);
|
|
22177
|
+
globalMaxNestingDepth = Math.max(globalMaxNestingDepth, result.maxNestingDepth);
|
|
22178
|
+
allCognitiveComplexities.push(...result.cognitiveComplexities);
|
|
22179
|
+
} catch {
|
|
22180
|
+
}
|
|
22181
|
+
}
|
|
22182
|
+
const namingScore = totalNames > 0 ? totalGoodNames / totalNames * 100 : 100;
|
|
22183
|
+
const averageFunctionLength = allFunctionLengths.length > 0 ? allFunctionLengths.reduce((a, b) => a + b, 0) / allFunctionLengths.length : 0;
|
|
22184
|
+
const functionLengthScore = allFunctionLengths.length > 0 ? Math.max(0, Math.min(100, 100 - (averageFunctionLength - 20) * 2.5)) : 100;
|
|
22185
|
+
const maxNestingDepth = globalMaxNestingDepth;
|
|
22186
|
+
const nestingDepthScore = Math.max(0, Math.min(100, 100 - (maxNestingDepth - 2) * 20));
|
|
22187
|
+
const averageCognitiveComplexity = allCognitiveComplexities.length > 0 ? allCognitiveComplexities.reduce((a, b) => a + b, 0) / allCognitiveComplexities.length : 0;
|
|
22188
|
+
const cognitiveComplexityScore = allCognitiveComplexities.length > 0 ? Math.max(0, Math.min(100, 100 - (averageCognitiveComplexity - 5) * 5)) : 100;
|
|
22189
|
+
const score = Math.round(
|
|
22190
|
+
namingScore * 0.25 + functionLengthScore * 0.25 + nestingDepthScore * 0.25 + cognitiveComplexityScore * 0.25
|
|
22191
|
+
);
|
|
22192
|
+
const details = [
|
|
22193
|
+
`${totalGoodNames}/${totalNames} good names`,
|
|
22194
|
+
`avg func ${averageFunctionLength.toFixed(1)} LOC`,
|
|
22195
|
+
`max nesting ${maxNestingDepth}`,
|
|
22196
|
+
`avg weighted complexity ${averageCognitiveComplexity.toFixed(1)}`
|
|
22197
|
+
].join(", ");
|
|
22198
|
+
return {
|
|
22199
|
+
score,
|
|
22200
|
+
namingScore,
|
|
22201
|
+
functionLengthScore,
|
|
22202
|
+
nestingDepthScore,
|
|
22203
|
+
cognitiveComplexityScore,
|
|
22204
|
+
averageFunctionLength,
|
|
22205
|
+
maxNestingDepth,
|
|
22206
|
+
averageCognitiveComplexity,
|
|
22207
|
+
details
|
|
22208
|
+
};
|
|
22209
|
+
}
|
|
22210
|
+
async findSourceFiles() {
|
|
22211
|
+
return glob("**/*.{ts,js,tsx,jsx}", {
|
|
22212
|
+
cwd: this.projectPath,
|
|
22213
|
+
absolute: true,
|
|
22214
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
|
|
22215
|
+
});
|
|
22216
|
+
}
|
|
22217
|
+
};
|
|
22218
|
+
function analyzeMaintainabilityPatterns(ast, filePath) {
|
|
22219
|
+
let functionCount = 0;
|
|
22220
|
+
let importCount = 0;
|
|
22221
|
+
let crossBoundaryImportCount = 0;
|
|
22222
|
+
function traverse(node) {
|
|
22223
|
+
switch (node.type) {
|
|
22224
|
+
case "FunctionDeclaration":
|
|
22225
|
+
case "FunctionExpression":
|
|
22226
|
+
case "ArrowFunctionExpression":
|
|
22227
|
+
case "MethodDefinition":
|
|
22228
|
+
functionCount++;
|
|
22229
|
+
break;
|
|
22230
|
+
case "ImportDeclaration":
|
|
22231
|
+
importCount++;
|
|
22232
|
+
if (isCrossBoundaryImport(node)) {
|
|
22233
|
+
crossBoundaryImportCount++;
|
|
22234
|
+
}
|
|
22235
|
+
break;
|
|
22236
|
+
}
|
|
22237
|
+
traverseChildren(node);
|
|
22238
|
+
}
|
|
22239
|
+
function traverseChildren(node) {
|
|
22240
|
+
for (const key of Object.keys(node)) {
|
|
22241
|
+
if (key === "parent") continue;
|
|
22242
|
+
const child = node[key];
|
|
22243
|
+
if (child && typeof child === "object") {
|
|
22244
|
+
if (Array.isArray(child)) {
|
|
22245
|
+
for (const item of child) {
|
|
22246
|
+
if (item && typeof item === "object" && item.type) {
|
|
22247
|
+
traverse(item);
|
|
22248
|
+
}
|
|
22249
|
+
}
|
|
22250
|
+
} else if (child.type) {
|
|
22251
|
+
traverse(child);
|
|
22252
|
+
}
|
|
22253
|
+
}
|
|
22254
|
+
}
|
|
22255
|
+
}
|
|
22256
|
+
traverse(ast);
|
|
22257
|
+
return {
|
|
22258
|
+
lineCount: 0,
|
|
22259
|
+
// Will be set separately from content
|
|
22260
|
+
functionCount,
|
|
22261
|
+
importCount,
|
|
22262
|
+
crossBoundaryImportCount
|
|
22263
|
+
};
|
|
22264
|
+
}
|
|
22265
|
+
function isCrossBoundaryImport(node, _filePath) {
|
|
22266
|
+
if (node.source.type !== "Literal" || typeof node.source.value !== "string") {
|
|
22267
|
+
return false;
|
|
22268
|
+
}
|
|
22269
|
+
const importPath = node.source.value;
|
|
22270
|
+
if (importPath.startsWith("node:")) {
|
|
22271
|
+
return false;
|
|
22272
|
+
}
|
|
22273
|
+
if (!importPath.startsWith(".")) {
|
|
22274
|
+
return false;
|
|
22275
|
+
}
|
|
22276
|
+
if (importPath.startsWith("../")) {
|
|
22277
|
+
return true;
|
|
22278
|
+
}
|
|
22279
|
+
if (importPath.startsWith("./")) {
|
|
22280
|
+
const relativePath = importPath.slice(2);
|
|
22281
|
+
return relativePath.includes("/");
|
|
22282
|
+
}
|
|
22283
|
+
return false;
|
|
22284
|
+
}
|
|
22285
|
+
function countLines(content) {
|
|
22286
|
+
return content.split("\n").length;
|
|
22287
|
+
}
|
|
22288
|
+
var MaintainabilityAnalyzer = class {
|
|
22289
|
+
constructor(projectPath) {
|
|
22290
|
+
this.projectPath = projectPath;
|
|
22291
|
+
}
|
|
22292
|
+
/**
|
|
22293
|
+
* Analyze maintainability of project files
|
|
22294
|
+
*/
|
|
22295
|
+
async analyze(files) {
|
|
22296
|
+
const targetFiles = files ?? await this.findSourceFiles();
|
|
22297
|
+
if (targetFiles.length === 0) {
|
|
22298
|
+
return {
|
|
22299
|
+
score: 100,
|
|
22300
|
+
fileLengthScore: 100,
|
|
22301
|
+
functionCountScore: 100,
|
|
22302
|
+
dependencyCountScore: 100,
|
|
22303
|
+
couplingScore: 100,
|
|
22304
|
+
averageFileLength: 0,
|
|
22305
|
+
averageFunctionsPerFile: 0,
|
|
22306
|
+
averageImportsPerFile: 0,
|
|
22307
|
+
fileCount: 0,
|
|
22308
|
+
details: "No files to analyze"
|
|
22309
|
+
};
|
|
22310
|
+
}
|
|
22311
|
+
let totalLines = 0;
|
|
22312
|
+
let totalFunctions = 0;
|
|
22313
|
+
let totalImports = 0;
|
|
22314
|
+
let totalCrossBoundaryImports = 0;
|
|
22315
|
+
let filesAnalyzed = 0;
|
|
22316
|
+
for (const file of targetFiles) {
|
|
22317
|
+
try {
|
|
22318
|
+
const content = await readFile(file, "utf-8");
|
|
22319
|
+
const lineCount = countLines(content);
|
|
22320
|
+
const ast = parse(content, {
|
|
22321
|
+
loc: true,
|
|
22322
|
+
range: true,
|
|
22323
|
+
jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
|
|
22324
|
+
});
|
|
22325
|
+
const result = analyzeMaintainabilityPatterns(ast, file);
|
|
22326
|
+
totalLines += lineCount;
|
|
22327
|
+
totalFunctions += result.functionCount;
|
|
22328
|
+
totalImports += result.importCount;
|
|
22329
|
+
totalCrossBoundaryImports += result.crossBoundaryImportCount;
|
|
22330
|
+
filesAnalyzed++;
|
|
22331
|
+
} catch {
|
|
22332
|
+
}
|
|
22333
|
+
}
|
|
22334
|
+
const averageFileLength = filesAnalyzed > 0 ? totalLines / filesAnalyzed : 0;
|
|
22335
|
+
const averageFunctionsPerFile = filesAnalyzed > 0 ? totalFunctions / filesAnalyzed : 0;
|
|
22336
|
+
const averageImportsPerFile = filesAnalyzed > 0 ? totalImports / filesAnalyzed : 0;
|
|
22337
|
+
const fileLengthScore = Math.max(0, Math.min(100, 100 - (averageFileLength - 200) * 0.33));
|
|
22338
|
+
const functionCountScore = Math.max(0, Math.min(100, 100 - (averageFunctionsPerFile - 10) * 5));
|
|
22339
|
+
const dependencyCountScore = Math.max(0, Math.min(100, 100 - (averageImportsPerFile - 5) * 5));
|
|
22340
|
+
const crossBoundaryRatio = totalImports > 0 ? totalCrossBoundaryImports / totalImports : 0;
|
|
22341
|
+
const couplingScore = Math.max(0, Math.min(100, 100 - crossBoundaryRatio * 100 * 0.5));
|
|
22342
|
+
const score = Math.round(
|
|
22343
|
+
fileLengthScore * 0.3 + functionCountScore * 0.25 + dependencyCountScore * 0.25 + couplingScore * 0.2
|
|
22344
|
+
);
|
|
22345
|
+
const details = [
|
|
22346
|
+
`${Math.round(averageFileLength)} avg lines/file`,
|
|
22347
|
+
`${averageFunctionsPerFile.toFixed(1)} avg functions/file`,
|
|
22348
|
+
`${averageImportsPerFile.toFixed(1)} avg imports/file`,
|
|
22349
|
+
`${Math.round(crossBoundaryRatio * 100)}% cross-boundary coupling`
|
|
22350
|
+
].join(", ");
|
|
22351
|
+
return {
|
|
22352
|
+
score,
|
|
22353
|
+
fileLengthScore,
|
|
22354
|
+
functionCountScore,
|
|
22355
|
+
dependencyCountScore,
|
|
22356
|
+
couplingScore,
|
|
22357
|
+
averageFileLength,
|
|
22358
|
+
averageFunctionsPerFile,
|
|
22359
|
+
averageImportsPerFile,
|
|
22360
|
+
fileCount: filesAnalyzed,
|
|
22361
|
+
details
|
|
22362
|
+
};
|
|
22363
|
+
}
|
|
22364
|
+
async findSourceFiles() {
|
|
22365
|
+
return glob("**/*.{ts,js,tsx,jsx}", {
|
|
22366
|
+
cwd: this.projectPath,
|
|
22367
|
+
absolute: true,
|
|
22368
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
|
|
22369
|
+
});
|
|
22370
|
+
}
|
|
22371
|
+
};
|
|
22372
|
+
|
|
22373
|
+
// src/quality/types.ts
|
|
22374
|
+
var DEFAULT_QUALITY_WEIGHTS = {
|
|
22375
|
+
correctness: 0.15,
|
|
22376
|
+
completeness: 0.1,
|
|
22377
|
+
robustness: 0.1,
|
|
22378
|
+
readability: 0.1,
|
|
22379
|
+
maintainability: 0.1,
|
|
22380
|
+
complexity: 0.08,
|
|
22381
|
+
duplication: 0.07,
|
|
22382
|
+
testCoverage: 0.1,
|
|
22383
|
+
testQuality: 0.05,
|
|
22384
|
+
security: 0.08,
|
|
22385
|
+
documentation: 0.04,
|
|
22386
|
+
style: 0.03
|
|
22387
|
+
};
|
|
22388
|
+
var DEFAULT_QUALITY_THRESHOLDS = {
|
|
22389
|
+
minimum: {
|
|
22390
|
+
overall: 85,
|
|
22391
|
+
testCoverage: 80,
|
|
22392
|
+
security: 100
|
|
22393
|
+
// No vulnerabilities allowed
|
|
22394
|
+
},
|
|
22395
|
+
target: {
|
|
22396
|
+
overall: 95,
|
|
22397
|
+
testCoverage: 90
|
|
22398
|
+
}};
|
|
22399
|
+
var QualityEvaluator = class {
|
|
22400
|
+
constructor(projectPath, useSnyk = false) {
|
|
22401
|
+
this.projectPath = projectPath;
|
|
22402
|
+
this.coverageAnalyzer = new CoverageAnalyzer(projectPath);
|
|
22403
|
+
this.securityScanner = new CompositeSecurityScanner(projectPath, useSnyk);
|
|
22404
|
+
this.complexityAnalyzer = new ComplexityAnalyzer(projectPath);
|
|
22405
|
+
this.duplicationAnalyzer = new DuplicationAnalyzer(projectPath);
|
|
22406
|
+
this.correctnessAnalyzer = new CorrectnessAnalyzer(projectPath);
|
|
22407
|
+
this.completenessAnalyzer = new CompletenessAnalyzer(projectPath);
|
|
22408
|
+
this.robustnessAnalyzer = new RobustnessAnalyzer(projectPath);
|
|
22409
|
+
this.testQualityAnalyzer = new TestQualityAnalyzer(projectPath);
|
|
22410
|
+
this.documentationAnalyzer = new DocumentationAnalyzer(projectPath);
|
|
22411
|
+
this.styleAnalyzer = new StyleAnalyzer(projectPath);
|
|
22412
|
+
this.readabilityAnalyzer = new ReadabilityAnalyzer(projectPath);
|
|
22413
|
+
this.maintainabilityAnalyzer = new MaintainabilityAnalyzer(projectPath);
|
|
22414
|
+
}
|
|
22415
|
+
coverageAnalyzer;
|
|
22416
|
+
securityScanner;
|
|
22417
|
+
complexityAnalyzer;
|
|
22418
|
+
duplicationAnalyzer;
|
|
22419
|
+
correctnessAnalyzer;
|
|
22420
|
+
completenessAnalyzer;
|
|
22421
|
+
robustnessAnalyzer;
|
|
22422
|
+
testQualityAnalyzer;
|
|
22423
|
+
documentationAnalyzer;
|
|
22424
|
+
styleAnalyzer;
|
|
22425
|
+
readabilityAnalyzer;
|
|
22426
|
+
maintainabilityAnalyzer;
|
|
22427
|
+
/**
|
|
22428
|
+
* Evaluate quality across all 12 dimensions
|
|
22429
|
+
* Every dimension is computed by real static analysis — zero hardcoded values
|
|
22430
|
+
*/
|
|
22431
|
+
async evaluate(files) {
|
|
22432
|
+
const startTime = performance.now();
|
|
22433
|
+
const targetFiles = files ?? await this.findSourceFiles();
|
|
22434
|
+
const fileContents = await Promise.all(
|
|
22435
|
+
targetFiles.map(async (file) => ({
|
|
22436
|
+
path: file,
|
|
22437
|
+
content: await readFile(file, "utf-8")
|
|
22438
|
+
}))
|
|
22439
|
+
);
|
|
22440
|
+
const [
|
|
22441
|
+
coverageResult,
|
|
22442
|
+
securityResult,
|
|
22443
|
+
complexityResult,
|
|
22444
|
+
duplicationResult,
|
|
22445
|
+
correctnessResult,
|
|
22446
|
+
completenessResult,
|
|
22447
|
+
robustnessResult,
|
|
22448
|
+
testQualityResult,
|
|
22449
|
+
documentationResult,
|
|
22450
|
+
styleResult,
|
|
22451
|
+
readabilityResult,
|
|
22452
|
+
maintainabilityResult
|
|
22453
|
+
] = await Promise.all([
|
|
22454
|
+
this.coverageAnalyzer.analyze().catch(() => null),
|
|
22455
|
+
this.securityScanner.scan(fileContents),
|
|
22456
|
+
this.complexityAnalyzer.analyze(targetFiles),
|
|
22457
|
+
this.duplicationAnalyzer.analyze(targetFiles),
|
|
22458
|
+
this.correctnessAnalyzer.analyze().catch(() => ({ score: 0 })),
|
|
22459
|
+
this.completenessAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
|
|
22460
|
+
this.robustnessAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
|
|
22461
|
+
this.testQualityAnalyzer.analyze().catch(() => ({ score: 0 })),
|
|
22462
|
+
this.documentationAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
|
|
22463
|
+
this.styleAnalyzer.analyze().catch(() => ({ score: 50 })),
|
|
22464
|
+
this.readabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 50 })),
|
|
22465
|
+
this.maintainabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 50 }))
|
|
22466
|
+
]);
|
|
22467
|
+
const dimensions = {
|
|
22468
|
+
testCoverage: coverageResult?.lines.percentage ?? 0,
|
|
22469
|
+
security: securityResult.score,
|
|
22470
|
+
complexity: complexityResult.score,
|
|
22471
|
+
duplication: Math.max(0, 100 - duplicationResult.percentage),
|
|
22472
|
+
style: styleResult.score,
|
|
22473
|
+
readability: readabilityResult.score,
|
|
22474
|
+
maintainability: maintainabilityResult.score,
|
|
22475
|
+
correctness: correctnessResult.score,
|
|
22476
|
+
completeness: completenessResult.score,
|
|
22477
|
+
robustness: robustnessResult.score,
|
|
22478
|
+
testQuality: testQualityResult.score,
|
|
22479
|
+
documentation: documentationResult.score
|
|
22480
|
+
};
|
|
22481
|
+
const overall = Object.entries(dimensions).reduce((sum, [key, value]) => {
|
|
22482
|
+
const weight = DEFAULT_QUALITY_WEIGHTS[key] ?? 0;
|
|
22483
|
+
return sum + value * weight;
|
|
22484
|
+
}, 0);
|
|
22485
|
+
const scores = {
|
|
22486
|
+
overall: Math.round(overall),
|
|
22487
|
+
dimensions,
|
|
22488
|
+
evaluatedAt: /* @__PURE__ */ new Date(),
|
|
22489
|
+
evaluationDurationMs: performance.now() - startTime
|
|
22490
|
+
};
|
|
22491
|
+
const issues = this.generateIssues(
|
|
22492
|
+
securityResult.vulnerabilities,
|
|
22493
|
+
complexityResult,
|
|
22494
|
+
duplicationResult,
|
|
22495
|
+
correctnessResult,
|
|
22496
|
+
styleResult,
|
|
22497
|
+
documentationResult
|
|
22498
|
+
);
|
|
22499
|
+
const suggestions = this.generateSuggestions(dimensions);
|
|
22500
|
+
const meetsMinimum = scores.overall >= DEFAULT_QUALITY_THRESHOLDS.minimum.overall && dimensions.testCoverage >= DEFAULT_QUALITY_THRESHOLDS.minimum.testCoverage && dimensions.security >= DEFAULT_QUALITY_THRESHOLDS.minimum.security;
|
|
22501
|
+
const meetsTarget = scores.overall >= DEFAULT_QUALITY_THRESHOLDS.target.overall && dimensions.testCoverage >= DEFAULT_QUALITY_THRESHOLDS.target.testCoverage;
|
|
22502
|
+
return {
|
|
22503
|
+
scores,
|
|
22504
|
+
meetsMinimum,
|
|
22505
|
+
meetsTarget,
|
|
22506
|
+
converged: false,
|
|
22507
|
+
issues,
|
|
22508
|
+
suggestions
|
|
22509
|
+
};
|
|
22510
|
+
}
|
|
22511
|
+
/**
|
|
22512
|
+
* Generate quality issues from analyzer results
|
|
22513
|
+
*/
|
|
22514
|
+
generateIssues(securityVulns, complexityResult, duplicationResult, correctnessResult, styleResult, documentationResult) {
|
|
22515
|
+
const issues = [];
|
|
22516
|
+
for (const vuln of securityVulns) {
|
|
22517
|
+
issues.push({
|
|
22518
|
+
dimension: "security",
|
|
22519
|
+
severity: vuln.severity === "critical" ? "critical" : vuln.severity === "high" ? "major" : "minor",
|
|
22520
|
+
message: `${vuln.type}: ${vuln.description}`,
|
|
22521
|
+
file: vuln.location.file,
|
|
22522
|
+
line: vuln.location.line
|
|
22523
|
+
});
|
|
22524
|
+
}
|
|
22525
|
+
for (const file of complexityResult.files) {
|
|
22526
|
+
for (const fn of file.functions) {
|
|
22527
|
+
if (fn.complexity > 10) {
|
|
22528
|
+
issues.push({
|
|
22529
|
+
dimension: "complexity",
|
|
22530
|
+
severity: "major",
|
|
22531
|
+
message: `Function '${fn.name}' has high complexity (${fn.complexity})`,
|
|
20658
22532
|
file: file.file,
|
|
20659
22533
|
line: fn.line,
|
|
20660
22534
|
suggestion: "Refactor into smaller functions or reduce branching"
|
|
@@ -20670,6 +22544,38 @@ var QualityEvaluator = class {
|
|
|
20670
22544
|
suggestion: "Extract common code into reusable functions or modules"
|
|
20671
22545
|
});
|
|
20672
22546
|
}
|
|
22547
|
+
if (correctnessResult.testsFailed != null && correctnessResult.testsFailed > 0) {
|
|
22548
|
+
issues.push({
|
|
22549
|
+
dimension: "correctness",
|
|
22550
|
+
severity: "critical",
|
|
22551
|
+
message: `${correctnessResult.testsFailed} tests failing`,
|
|
22552
|
+
suggestion: "Fix failing tests to improve correctness score"
|
|
22553
|
+
});
|
|
22554
|
+
}
|
|
22555
|
+
if (correctnessResult.buildSuccess === false) {
|
|
22556
|
+
issues.push({
|
|
22557
|
+
dimension: "correctness",
|
|
22558
|
+
severity: "critical",
|
|
22559
|
+
message: "Build/type check failed",
|
|
22560
|
+
suggestion: "Fix type errors to pass build verification"
|
|
22561
|
+
});
|
|
22562
|
+
}
|
|
22563
|
+
if (styleResult.errors != null && styleResult.errors > 0) {
|
|
22564
|
+
issues.push({
|
|
22565
|
+
dimension: "style",
|
|
22566
|
+
severity: "minor",
|
|
22567
|
+
message: `${styleResult.errors} linting errors found`,
|
|
22568
|
+
suggestion: "Run linter with --fix to auto-correct style issues"
|
|
22569
|
+
});
|
|
22570
|
+
}
|
|
22571
|
+
if (documentationResult.jsdocCoverage !== void 0 && documentationResult.jsdocCoverage < 50) {
|
|
22572
|
+
issues.push({
|
|
22573
|
+
dimension: "documentation",
|
|
22574
|
+
severity: "minor",
|
|
22575
|
+
message: `Low JSDoc coverage: ${documentationResult.jsdocCoverage?.toFixed(1) ?? 0}%`,
|
|
22576
|
+
suggestion: "Add JSDoc comments to exported functions and classes"
|
|
22577
|
+
});
|
|
22578
|
+
}
|
|
20673
22579
|
return issues;
|
|
20674
22580
|
}
|
|
20675
22581
|
/**
|
|
@@ -20693,6 +22599,14 @@ var QualityEvaluator = class {
|
|
|
20693
22599
|
estimatedImpact: 100 - dimensions.security
|
|
20694
22600
|
});
|
|
20695
22601
|
}
|
|
22602
|
+
if (dimensions.correctness < 85) {
|
|
22603
|
+
suggestions.push({
|
|
22604
|
+
dimension: "correctness",
|
|
22605
|
+
priority: "high",
|
|
22606
|
+
description: "Fix failing tests and build errors",
|
|
22607
|
+
estimatedImpact: 85 - dimensions.correctness
|
|
22608
|
+
});
|
|
22609
|
+
}
|
|
20696
22610
|
if (dimensions.complexity < 80) {
|
|
20697
22611
|
suggestions.push({
|
|
20698
22612
|
dimension: "complexity",
|
|
@@ -20701,6 +22615,22 @@ var QualityEvaluator = class {
|
|
|
20701
22615
|
estimatedImpact: Math.min(10, 80 - dimensions.complexity)
|
|
20702
22616
|
});
|
|
20703
22617
|
}
|
|
22618
|
+
if (dimensions.documentation < 60) {
|
|
22619
|
+
suggestions.push({
|
|
22620
|
+
dimension: "documentation",
|
|
22621
|
+
priority: "medium",
|
|
22622
|
+
description: "Add JSDoc comments to exported declarations",
|
|
22623
|
+
estimatedImpact: Math.min(15, 60 - dimensions.documentation)
|
|
22624
|
+
});
|
|
22625
|
+
}
|
|
22626
|
+
if (dimensions.testQuality < 70) {
|
|
22627
|
+
suggestions.push({
|
|
22628
|
+
dimension: "testQuality",
|
|
22629
|
+
priority: "medium",
|
|
22630
|
+
description: "Replace trivial assertions with meaningful behavioral tests",
|
|
22631
|
+
estimatedImpact: Math.min(10, 70 - dimensions.testQuality)
|
|
22632
|
+
});
|
|
22633
|
+
}
|
|
20704
22634
|
if (dimensions.duplication < 95) {
|
|
20705
22635
|
suggestions.push({
|
|
20706
22636
|
dimension: "duplication",
|
|
@@ -20727,7 +22657,7 @@ function createQualityEvaluator(projectPath, useSnyk) {
|
|
|
20727
22657
|
}
|
|
20728
22658
|
|
|
20729
22659
|
// src/tools/quality.ts
|
|
20730
|
-
async function
|
|
22660
|
+
async function detectLinter2(cwd) {
|
|
20731
22661
|
try {
|
|
20732
22662
|
const pkgPath = path20__default.join(cwd, "package.json");
|
|
20733
22663
|
const pkgContent = await fs22__default.readFile(pkgPath, "utf-8");
|
|
@@ -20762,7 +22692,7 @@ Examples:
|
|
|
20762
22692
|
}),
|
|
20763
22693
|
async execute({ cwd, files, fix, linter }) {
|
|
20764
22694
|
const projectDir = cwd ?? process.cwd();
|
|
20765
|
-
const detectedLinter = linter ?? await
|
|
22695
|
+
const detectedLinter = linter ?? await detectLinter2(projectDir);
|
|
20766
22696
|
if (!detectedLinter) {
|
|
20767
22697
|
return {
|
|
20768
22698
|
errors: 0,
|
|
@@ -20917,8 +22847,8 @@ Examples:
|
|
|
20917
22847
|
}
|
|
20918
22848
|
});
|
|
20919
22849
|
async function findSourceFiles(cwd) {
|
|
20920
|
-
const { glob:
|
|
20921
|
-
return
|
|
22850
|
+
const { glob: glob15 } = await import('glob');
|
|
22851
|
+
return glob15("src/**/*.{ts,js,tsx,jsx}", {
|
|
20922
22852
|
cwd,
|
|
20923
22853
|
absolute: true,
|
|
20924
22854
|
ignore: ["**/*.test.*", "**/*.spec.*", "**/node_modules/**"]
|
|
@@ -22475,9 +24405,9 @@ Examples:
|
|
|
22475
24405
|
}
|
|
22476
24406
|
});
|
|
22477
24407
|
var diffTools = [showDiffTool];
|
|
22478
|
-
async function fileExists(
|
|
24408
|
+
async function fileExists(path37) {
|
|
22479
24409
|
try {
|
|
22480
|
-
await access(
|
|
24410
|
+
await access(path37);
|
|
22481
24411
|
return true;
|
|
22482
24412
|
} catch {
|
|
22483
24413
|
return false;
|
|
@@ -22567,7 +24497,7 @@ async function detectMaturity(cwd) {
|
|
|
22567
24497
|
if (!hasLintConfig && hasPackageJson) {
|
|
22568
24498
|
try {
|
|
22569
24499
|
const pkgRaw = await import('fs/promises').then(
|
|
22570
|
-
(
|
|
24500
|
+
(fs39) => fs39.readFile(join(cwd, "package.json"), "utf-8")
|
|
22571
24501
|
);
|
|
22572
24502
|
const pkg = JSON.parse(pkgRaw);
|
|
22573
24503
|
if (pkg.scripts?.lint || pkg.scripts?.["lint:fix"]) {
|
|
@@ -22934,9 +24864,9 @@ Examples:
|
|
|
22934
24864
|
}
|
|
22935
24865
|
});
|
|
22936
24866
|
var reviewTools = [reviewCodeTool];
|
|
22937
|
-
var
|
|
22938
|
-
var
|
|
22939
|
-
var { glob:
|
|
24867
|
+
var fs28 = await import('fs/promises');
|
|
24868
|
+
var path26 = await import('path');
|
|
24869
|
+
var { glob: glob12 } = await import('glob');
|
|
22940
24870
|
var DEFAULT_MAX_FILES = 200;
|
|
22941
24871
|
var LANGUAGE_EXTENSIONS = {
|
|
22942
24872
|
typescript: [".ts", ".tsx", ".mts", ".cts"],
|
|
@@ -22961,7 +24891,7 @@ var DEFAULT_EXCLUDES = [
|
|
|
22961
24891
|
"**/*.d.ts"
|
|
22962
24892
|
];
|
|
22963
24893
|
function detectLanguage2(filePath) {
|
|
22964
|
-
const ext =
|
|
24894
|
+
const ext = path26.extname(filePath).toLowerCase();
|
|
22965
24895
|
for (const [lang, extensions] of Object.entries(LANGUAGE_EXTENSIONS)) {
|
|
22966
24896
|
if (extensions.includes(ext)) return lang;
|
|
22967
24897
|
}
|
|
@@ -23370,9 +25300,9 @@ Examples:
|
|
|
23370
25300
|
}),
|
|
23371
25301
|
async execute({ path: rootPath, include, exclude, languages, maxFiles, depth }) {
|
|
23372
25302
|
const startTime = performance.now();
|
|
23373
|
-
const absPath =
|
|
25303
|
+
const absPath = path26.resolve(rootPath);
|
|
23374
25304
|
try {
|
|
23375
|
-
const stat2 = await
|
|
25305
|
+
const stat2 = await fs28.stat(absPath);
|
|
23376
25306
|
if (!stat2.isDirectory()) {
|
|
23377
25307
|
throw new ToolError(`Path is not a directory: ${absPath}`, {
|
|
23378
25308
|
tool: "codebase_map"
|
|
@@ -23397,7 +25327,7 @@ Examples:
|
|
|
23397
25327
|
pattern = `**/*{${allExts.join(",")}}`;
|
|
23398
25328
|
}
|
|
23399
25329
|
const excludePatterns = [...DEFAULT_EXCLUDES, ...exclude ?? []];
|
|
23400
|
-
const files = await
|
|
25330
|
+
const files = await glob12(pattern, {
|
|
23401
25331
|
cwd: absPath,
|
|
23402
25332
|
ignore: excludePatterns,
|
|
23403
25333
|
nodir: true,
|
|
@@ -23409,14 +25339,14 @@ Examples:
|
|
|
23409
25339
|
let totalDefinitions = 0;
|
|
23410
25340
|
let exportedSymbols = 0;
|
|
23411
25341
|
for (const file of limitedFiles) {
|
|
23412
|
-
const fullPath =
|
|
25342
|
+
const fullPath = path26.join(absPath, file);
|
|
23413
25343
|
const language = detectLanguage2(file);
|
|
23414
25344
|
if (!language) continue;
|
|
23415
25345
|
if (languages && !languages.includes(language)) {
|
|
23416
25346
|
continue;
|
|
23417
25347
|
}
|
|
23418
25348
|
try {
|
|
23419
|
-
const content = await
|
|
25349
|
+
const content = await fs28.readFile(fullPath, "utf-8");
|
|
23420
25350
|
const lineCount = content.split("\n").length;
|
|
23421
25351
|
const parsed = parseFile(content, language);
|
|
23422
25352
|
const definitions = depth === "overview" ? parsed.definitions.filter((d) => d.exported) : parsed.definitions;
|
|
@@ -23449,23 +25379,23 @@ Examples:
|
|
|
23449
25379
|
});
|
|
23450
25380
|
var codebaseMapTools = [codebaseMapTool];
|
|
23451
25381
|
init_paths();
|
|
23452
|
-
var
|
|
23453
|
-
var
|
|
25382
|
+
var fs29 = await import('fs/promises');
|
|
25383
|
+
var path27 = await import('path');
|
|
23454
25384
|
var crypto2 = await import('crypto');
|
|
23455
|
-
var GLOBAL_MEMORIES_DIR =
|
|
25385
|
+
var GLOBAL_MEMORIES_DIR = path27.join(COCO_HOME, "memories");
|
|
23456
25386
|
var PROJECT_MEMORIES_DIR = ".coco/memories";
|
|
23457
25387
|
var DEFAULT_MAX_MEMORIES = 1e3;
|
|
23458
25388
|
async function ensureDir2(dirPath) {
|
|
23459
|
-
await
|
|
25389
|
+
await fs29.mkdir(dirPath, { recursive: true });
|
|
23460
25390
|
}
|
|
23461
25391
|
function getMemoriesDir(scope) {
|
|
23462
25392
|
return scope === "global" ? GLOBAL_MEMORIES_DIR : PROJECT_MEMORIES_DIR;
|
|
23463
25393
|
}
|
|
23464
25394
|
async function loadIndex(scope) {
|
|
23465
25395
|
const dir = getMemoriesDir(scope);
|
|
23466
|
-
const indexPath =
|
|
25396
|
+
const indexPath = path27.join(dir, "index.json");
|
|
23467
25397
|
try {
|
|
23468
|
-
const content = await
|
|
25398
|
+
const content = await fs29.readFile(indexPath, "utf-8");
|
|
23469
25399
|
return JSON.parse(content);
|
|
23470
25400
|
} catch {
|
|
23471
25401
|
return [];
|
|
@@ -23474,14 +25404,14 @@ async function loadIndex(scope) {
|
|
|
23474
25404
|
async function saveIndex(scope, index) {
|
|
23475
25405
|
const dir = getMemoriesDir(scope);
|
|
23476
25406
|
await ensureDir2(dir);
|
|
23477
|
-
const indexPath =
|
|
23478
|
-
await
|
|
25407
|
+
const indexPath = path27.join(dir, "index.json");
|
|
25408
|
+
await fs29.writeFile(indexPath, JSON.stringify(index, null, 2), "utf-8");
|
|
23479
25409
|
}
|
|
23480
25410
|
async function loadMemory(scope, id) {
|
|
23481
25411
|
const dir = getMemoriesDir(scope);
|
|
23482
|
-
const memPath =
|
|
25412
|
+
const memPath = path27.join(dir, `${id}.json`);
|
|
23483
25413
|
try {
|
|
23484
|
-
const content = await
|
|
25414
|
+
const content = await fs29.readFile(memPath, "utf-8");
|
|
23485
25415
|
return JSON.parse(content);
|
|
23486
25416
|
} catch {
|
|
23487
25417
|
return null;
|
|
@@ -23490,8 +25420,8 @@ async function loadMemory(scope, id) {
|
|
|
23490
25420
|
async function saveMemory(scope, memory) {
|
|
23491
25421
|
const dir = getMemoriesDir(scope);
|
|
23492
25422
|
await ensureDir2(dir);
|
|
23493
|
-
const memPath =
|
|
23494
|
-
await
|
|
25423
|
+
const memPath = path27.join(dir, `${memory.id}.json`);
|
|
25424
|
+
await fs29.writeFile(memPath, JSON.stringify(memory, null, 2), "utf-8");
|
|
23495
25425
|
}
|
|
23496
25426
|
var createMemoryTool = defineTool({
|
|
23497
25427
|
name: "create_memory",
|
|
@@ -23643,17 +25573,17 @@ Examples:
|
|
|
23643
25573
|
}
|
|
23644
25574
|
});
|
|
23645
25575
|
var memoryTools = [createMemoryTool, recallMemoryTool, listMemoriesTool];
|
|
23646
|
-
var
|
|
25576
|
+
var fs30 = await import('fs/promises');
|
|
23647
25577
|
var crypto3 = await import('crypto');
|
|
23648
25578
|
var CHECKPOINT_FILE = ".coco/checkpoints.json";
|
|
23649
25579
|
var DEFAULT_MAX_CHECKPOINTS = 50;
|
|
23650
25580
|
var STASH_PREFIX = "coco-cp";
|
|
23651
25581
|
async function ensureCocoDir() {
|
|
23652
|
-
await
|
|
25582
|
+
await fs30.mkdir(".coco", { recursive: true });
|
|
23653
25583
|
}
|
|
23654
25584
|
async function loadCheckpoints() {
|
|
23655
25585
|
try {
|
|
23656
|
-
const content = await
|
|
25586
|
+
const content = await fs30.readFile(CHECKPOINT_FILE, "utf-8");
|
|
23657
25587
|
return JSON.parse(content);
|
|
23658
25588
|
} catch {
|
|
23659
25589
|
return [];
|
|
@@ -23661,7 +25591,7 @@ async function loadCheckpoints() {
|
|
|
23661
25591
|
}
|
|
23662
25592
|
async function saveCheckpoints(checkpoints) {
|
|
23663
25593
|
await ensureCocoDir();
|
|
23664
|
-
await
|
|
25594
|
+
await fs30.writeFile(CHECKPOINT_FILE, JSON.stringify(checkpoints, null, 2), "utf-8");
|
|
23665
25595
|
}
|
|
23666
25596
|
async function execGit(args) {
|
|
23667
25597
|
const { execaCommand } = await import('execa');
|
|
@@ -23821,9 +25751,9 @@ Examples:
|
|
|
23821
25751
|
}
|
|
23822
25752
|
});
|
|
23823
25753
|
var checkpointTools = [createCheckpointTool, restoreCheckpointTool, listCheckpointsTool];
|
|
23824
|
-
var
|
|
23825
|
-
var
|
|
23826
|
-
var { glob:
|
|
25754
|
+
var fs31 = await import('fs/promises');
|
|
25755
|
+
var path28 = await import('path');
|
|
25756
|
+
var { glob: glob13 } = await import('glob');
|
|
23827
25757
|
var INDEX_DIR = ".coco/search-index";
|
|
23828
25758
|
var DEFAULT_CHUNK_SIZE = 20;
|
|
23829
25759
|
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -23948,20 +25878,20 @@ async function getEmbedding(text9) {
|
|
|
23948
25878
|
}
|
|
23949
25879
|
async function loadIndex2(indexDir) {
|
|
23950
25880
|
try {
|
|
23951
|
-
const indexPath =
|
|
23952
|
-
const content = await
|
|
25881
|
+
const indexPath = path28.join(indexDir, "index.json");
|
|
25882
|
+
const content = await fs31.readFile(indexPath, "utf-8");
|
|
23953
25883
|
return JSON.parse(content);
|
|
23954
25884
|
} catch {
|
|
23955
25885
|
return null;
|
|
23956
25886
|
}
|
|
23957
25887
|
}
|
|
23958
25888
|
async function saveIndex2(indexDir, index) {
|
|
23959
|
-
await
|
|
23960
|
-
const indexPath =
|
|
23961
|
-
await
|
|
25889
|
+
await fs31.mkdir(indexDir, { recursive: true });
|
|
25890
|
+
const indexPath = path28.join(indexDir, "index.json");
|
|
25891
|
+
await fs31.writeFile(indexPath, JSON.stringify(index), "utf-8");
|
|
23962
25892
|
}
|
|
23963
25893
|
function isBinary(filePath) {
|
|
23964
|
-
return BINARY_EXTENSIONS.has(
|
|
25894
|
+
return BINARY_EXTENSIONS.has(path28.extname(filePath).toLowerCase());
|
|
23965
25895
|
}
|
|
23966
25896
|
var semanticSearchTool = defineTool({
|
|
23967
25897
|
name: "semantic_search",
|
|
@@ -23986,12 +25916,12 @@ Examples:
|
|
|
23986
25916
|
const effectivePath = rootPath ?? ".";
|
|
23987
25917
|
const effectiveMaxResults = maxResults ?? 10;
|
|
23988
25918
|
const effectiveThreshold = threshold ?? 0.3;
|
|
23989
|
-
const absPath =
|
|
23990
|
-
const indexDir =
|
|
25919
|
+
const absPath = path28.resolve(effectivePath);
|
|
25920
|
+
const indexDir = path28.join(absPath, INDEX_DIR);
|
|
23991
25921
|
let index = reindex ? null : await loadIndex2(indexDir);
|
|
23992
25922
|
if (!index) {
|
|
23993
25923
|
const pattern = include ?? "**/*";
|
|
23994
|
-
const files = await
|
|
25924
|
+
const files = await glob13(pattern, {
|
|
23995
25925
|
cwd: absPath,
|
|
23996
25926
|
ignore: DEFAULT_EXCLUDES2,
|
|
23997
25927
|
nodir: true,
|
|
@@ -24000,10 +25930,10 @@ Examples:
|
|
|
24000
25930
|
const chunks = [];
|
|
24001
25931
|
for (const file of files) {
|
|
24002
25932
|
if (isBinary(file)) continue;
|
|
24003
|
-
const fullPath =
|
|
25933
|
+
const fullPath = path28.join(absPath, file);
|
|
24004
25934
|
try {
|
|
24005
|
-
const stat2 = await
|
|
24006
|
-
const content = await
|
|
25935
|
+
const stat2 = await fs31.stat(fullPath);
|
|
25936
|
+
const content = await fs31.readFile(fullPath, "utf-8");
|
|
24007
25937
|
if (content.length > 1e5) continue;
|
|
24008
25938
|
const fileChunks = chunkContent(content, DEFAULT_CHUNK_SIZE);
|
|
24009
25939
|
for (const chunk of fileChunks) {
|
|
@@ -24062,12 +25992,12 @@ Examples:
|
|
|
24062
25992
|
}
|
|
24063
25993
|
});
|
|
24064
25994
|
var semanticSearchTools = [semanticSearchTool];
|
|
24065
|
-
var
|
|
24066
|
-
var
|
|
24067
|
-
var { glob:
|
|
25995
|
+
var fs32 = await import('fs/promises');
|
|
25996
|
+
var path29 = await import('path');
|
|
25997
|
+
var { glob: glob14 } = await import('glob');
|
|
24068
25998
|
async function parseClassRelationships(rootPath, include) {
|
|
24069
25999
|
const pattern = include ?? "**/*.{ts,tsx,js,jsx}";
|
|
24070
|
-
const files = await
|
|
26000
|
+
const files = await glob14(pattern, {
|
|
24071
26001
|
cwd: rootPath,
|
|
24072
26002
|
ignore: ["**/node_modules/**", "**/dist/**", "**/*.test.*", "**/*.d.ts"],
|
|
24073
26003
|
nodir: true
|
|
@@ -24076,7 +26006,7 @@ async function parseClassRelationships(rootPath, include) {
|
|
|
24076
26006
|
const interfaces = [];
|
|
24077
26007
|
for (const file of files.slice(0, 100)) {
|
|
24078
26008
|
try {
|
|
24079
|
-
const content = await
|
|
26009
|
+
const content = await fs32.readFile(path29.join(rootPath, file), "utf-8");
|
|
24080
26010
|
const lines = content.split("\n");
|
|
24081
26011
|
for (let i = 0; i < lines.length; i++) {
|
|
24082
26012
|
const line = lines[i];
|
|
@@ -24195,14 +26125,14 @@ async function generateClassDiagram(rootPath, include) {
|
|
|
24195
26125
|
};
|
|
24196
26126
|
}
|
|
24197
26127
|
async function generateArchitectureDiagram(rootPath) {
|
|
24198
|
-
const entries = await
|
|
26128
|
+
const entries = await fs32.readdir(rootPath, { withFileTypes: true });
|
|
24199
26129
|
const dirs = entries.filter(
|
|
24200
26130
|
(e) => e.isDirectory() && !e.name.startsWith(".") && !["node_modules", "dist", "build", "coverage", "__pycache__", "target"].includes(e.name)
|
|
24201
26131
|
);
|
|
24202
26132
|
const lines = ["graph TD"];
|
|
24203
26133
|
let nodeCount = 0;
|
|
24204
26134
|
let edgeCount = 0;
|
|
24205
|
-
const rootName =
|
|
26135
|
+
const rootName = path29.basename(rootPath);
|
|
24206
26136
|
lines.push(` ROOT["${rootName}"]`);
|
|
24207
26137
|
nodeCount++;
|
|
24208
26138
|
for (const dir of dirs) {
|
|
@@ -24212,7 +26142,7 @@ async function generateArchitectureDiagram(rootPath) {
|
|
|
24212
26142
|
nodeCount++;
|
|
24213
26143
|
edgeCount++;
|
|
24214
26144
|
try {
|
|
24215
|
-
const subEntries = await
|
|
26145
|
+
const subEntries = await fs32.readdir(path29.join(rootPath, dir.name), {
|
|
24216
26146
|
withFileTypes: true
|
|
24217
26147
|
});
|
|
24218
26148
|
const subDirs = subEntries.filter(
|
|
@@ -24335,7 +26265,7 @@ Examples:
|
|
|
24335
26265
|
tool: "generate_diagram"
|
|
24336
26266
|
});
|
|
24337
26267
|
}
|
|
24338
|
-
const absPath = rootPath ?
|
|
26268
|
+
const absPath = rootPath ? path29.resolve(rootPath) : process.cwd();
|
|
24339
26269
|
switch (type) {
|
|
24340
26270
|
case "class":
|
|
24341
26271
|
return generateClassDiagram(absPath, include);
|
|
@@ -24396,8 +26326,8 @@ Examples:
|
|
|
24396
26326
|
}
|
|
24397
26327
|
});
|
|
24398
26328
|
var diagramTools = [generateDiagramTool];
|
|
24399
|
-
var
|
|
24400
|
-
var
|
|
26329
|
+
var fs33 = await import('fs/promises');
|
|
26330
|
+
var path30 = await import('path');
|
|
24401
26331
|
var DEFAULT_MAX_PAGES = 20;
|
|
24402
26332
|
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
24403
26333
|
function parsePageRange(rangeStr, totalPages) {
|
|
@@ -24432,9 +26362,9 @@ Examples:
|
|
|
24432
26362
|
}),
|
|
24433
26363
|
async execute({ path: filePath, pages, maxPages }) {
|
|
24434
26364
|
const startTime = performance.now();
|
|
24435
|
-
const absPath =
|
|
26365
|
+
const absPath = path30.resolve(filePath);
|
|
24436
26366
|
try {
|
|
24437
|
-
const stat2 = await
|
|
26367
|
+
const stat2 = await fs33.stat(absPath);
|
|
24438
26368
|
if (!stat2.isFile()) {
|
|
24439
26369
|
throw new ToolError(`Path is not a file: ${absPath}`, {
|
|
24440
26370
|
tool: "read_pdf"
|
|
@@ -24465,7 +26395,7 @@ Examples:
|
|
|
24465
26395
|
}
|
|
24466
26396
|
try {
|
|
24467
26397
|
const pdfParse = await import('pdf-parse');
|
|
24468
|
-
const dataBuffer = await
|
|
26398
|
+
const dataBuffer = await fs33.readFile(absPath);
|
|
24469
26399
|
const pdfData = await pdfParse.default(dataBuffer, {
|
|
24470
26400
|
max: maxPages
|
|
24471
26401
|
});
|
|
@@ -24511,8 +26441,8 @@ Examples:
|
|
|
24511
26441
|
}
|
|
24512
26442
|
});
|
|
24513
26443
|
var pdfTools = [readPdfTool];
|
|
24514
|
-
var
|
|
24515
|
-
var
|
|
26444
|
+
var fs34 = await import('fs/promises');
|
|
26445
|
+
var path31 = await import('path');
|
|
24516
26446
|
var SUPPORTED_FORMATS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
|
|
24517
26447
|
var MAX_IMAGE_SIZE = 20 * 1024 * 1024;
|
|
24518
26448
|
var MIME_TYPES = {
|
|
@@ -24540,8 +26470,8 @@ Examples:
|
|
|
24540
26470
|
async execute({ path: filePath, prompt, provider }) {
|
|
24541
26471
|
const startTime = performance.now();
|
|
24542
26472
|
const effectivePrompt = prompt ?? "Describe this image in detail. If it's code or a UI, identify the key elements.";
|
|
24543
|
-
const absPath =
|
|
24544
|
-
const ext =
|
|
26473
|
+
const absPath = path31.resolve(filePath);
|
|
26474
|
+
const ext = path31.extname(absPath).toLowerCase();
|
|
24545
26475
|
if (!SUPPORTED_FORMATS.has(ext)) {
|
|
24546
26476
|
throw new ToolError(
|
|
24547
26477
|
`Unsupported image format '${ext}'. Supported: ${Array.from(SUPPORTED_FORMATS).join(", ")}`,
|
|
@@ -24549,7 +26479,7 @@ Examples:
|
|
|
24549
26479
|
);
|
|
24550
26480
|
}
|
|
24551
26481
|
try {
|
|
24552
|
-
const stat2 = await
|
|
26482
|
+
const stat2 = await fs34.stat(absPath);
|
|
24553
26483
|
if (!stat2.isFile()) {
|
|
24554
26484
|
throw new ToolError(`Path is not a file: ${absPath}`, {
|
|
24555
26485
|
tool: "read_image"
|
|
@@ -24570,7 +26500,7 @@ Examples:
|
|
|
24570
26500
|
if (error instanceof ToolError) throw error;
|
|
24571
26501
|
throw error;
|
|
24572
26502
|
}
|
|
24573
|
-
const imageBuffer = await
|
|
26503
|
+
const imageBuffer = await fs34.readFile(absPath);
|
|
24574
26504
|
const base64 = imageBuffer.toString("base64");
|
|
24575
26505
|
const mimeType = MIME_TYPES[ext] ?? "image/png";
|
|
24576
26506
|
const selectedProvider = provider ?? "anthropic";
|
|
@@ -24683,7 +26613,7 @@ Examples:
|
|
|
24683
26613
|
}
|
|
24684
26614
|
});
|
|
24685
26615
|
var imageTools = [readImageTool];
|
|
24686
|
-
var
|
|
26616
|
+
var path32 = await import('path');
|
|
24687
26617
|
var DANGEROUS_PATTERNS2 = [
|
|
24688
26618
|
/\bDROP\s+(?:TABLE|DATABASE|INDEX|VIEW)\b/i,
|
|
24689
26619
|
/\bTRUNCATE\b/i,
|
|
@@ -24714,7 +26644,7 @@ Examples:
|
|
|
24714
26644
|
async execute({ database, query, params, readonly: isReadonlyParam }) {
|
|
24715
26645
|
const isReadonly = isReadonlyParam ?? true;
|
|
24716
26646
|
const startTime = performance.now();
|
|
24717
|
-
const absPath =
|
|
26647
|
+
const absPath = path32.resolve(database);
|
|
24718
26648
|
if (isReadonly && isDangerousSql(query)) {
|
|
24719
26649
|
throw new ToolError(
|
|
24720
26650
|
"Write operations (INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE) are blocked in readonly mode. Set readonly: false to allow writes.",
|
|
@@ -24787,7 +26717,7 @@ Examples:
|
|
|
24787
26717
|
}),
|
|
24788
26718
|
async execute({ database, table }) {
|
|
24789
26719
|
const startTime = performance.now();
|
|
24790
|
-
const absPath =
|
|
26720
|
+
const absPath = path32.resolve(database);
|
|
24791
26721
|
try {
|
|
24792
26722
|
const { default: Database } = await import('better-sqlite3');
|
|
24793
26723
|
const db = new Database(absPath, { readonly: true, fileMustExist: true });
|
|
@@ -24958,14 +26888,14 @@ var findMissingImportsTool = defineTool({
|
|
|
24958
26888
|
}
|
|
24959
26889
|
});
|
|
24960
26890
|
var astValidatorTools = [validateCodeTool, findMissingImportsTool];
|
|
24961
|
-
var
|
|
24962
|
-
var
|
|
26891
|
+
var fs35 = await import('fs/promises');
|
|
26892
|
+
var path33 = await import('path');
|
|
24963
26893
|
var AnalyzeFileSchema = z.object({
|
|
24964
26894
|
filePath: z.string().describe("Path to file to analyze"),
|
|
24965
26895
|
includeAst: z.boolean().default(false).describe("Include AST in result")
|
|
24966
26896
|
});
|
|
24967
26897
|
async function analyzeFile(filePath, includeAst = false) {
|
|
24968
|
-
const content = await
|
|
26898
|
+
const content = await fs35.readFile(filePath, "utf-8");
|
|
24969
26899
|
const lines = content.split("\n").length;
|
|
24970
26900
|
const functions = [];
|
|
24971
26901
|
const classes = [];
|
|
@@ -25056,8 +26986,8 @@ async function analyzeFile(filePath, includeAst = false) {
|
|
|
25056
26986
|
};
|
|
25057
26987
|
}
|
|
25058
26988
|
async function analyzeDirectory(dirPath) {
|
|
25059
|
-
const { glob:
|
|
25060
|
-
const files = await
|
|
26989
|
+
const { glob: glob15 } = await import('glob');
|
|
26990
|
+
const files = await glob15("**/*.{ts,tsx,js,jsx}", {
|
|
25061
26991
|
cwd: dirPath,
|
|
25062
26992
|
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
|
|
25063
26993
|
absolute: true
|
|
@@ -25069,10 +26999,10 @@ async function analyzeDirectory(dirPath) {
|
|
|
25069
26999
|
try {
|
|
25070
27000
|
const analysis = await analyzeFile(file, false);
|
|
25071
27001
|
totalLines += analysis.lines;
|
|
25072
|
-
const ext =
|
|
27002
|
+
const ext = path33.extname(file);
|
|
25073
27003
|
filesByType[ext] = (filesByType[ext] || 0) + 1;
|
|
25074
27004
|
fileStats.push({
|
|
25075
|
-
file:
|
|
27005
|
+
file: path33.relative(dirPath, file),
|
|
25076
27006
|
lines: analysis.lines,
|
|
25077
27007
|
complexity: analysis.complexity.cyclomatic
|
|
25078
27008
|
});
|
|
@@ -25132,8 +27062,8 @@ var codeAnalyzerTools = [analyzeFileTool, analyzeDirectoryTool];
|
|
|
25132
27062
|
var AgentTaskQueue = class {
|
|
25133
27063
|
tasks = /* @__PURE__ */ new Map();
|
|
25134
27064
|
completionOrder = [];
|
|
25135
|
-
addTask(task) {
|
|
25136
|
-
const id = `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
27065
|
+
addTask(task, idOverride) {
|
|
27066
|
+
const id = idOverride ?? `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
25137
27067
|
this.tasks.set(id, {
|
|
25138
27068
|
...task,
|
|
25139
27069
|
id,
|
|
@@ -25179,10 +27109,20 @@ var AgentTaskQueue = class {
|
|
|
25179
27109
|
};
|
|
25180
27110
|
function planExecution(tasks, strategy) {
|
|
25181
27111
|
const taskGraph = /* @__PURE__ */ new Map();
|
|
27112
|
+
const unresolvedDependencies = [];
|
|
25182
27113
|
tasks.forEach((task, idx) => {
|
|
25183
|
-
const id = `task-${idx}`;
|
|
27114
|
+
const id = task.id ?? `task-${idx}`;
|
|
25184
27115
|
taskGraph.set(id, task.dependencies || []);
|
|
25185
27116
|
});
|
|
27117
|
+
const taskIds = new Set(taskGraph.keys());
|
|
27118
|
+
for (const [id, deps] of taskGraph) {
|
|
27119
|
+
const filtered = deps.filter((dep) => {
|
|
27120
|
+
if (taskIds.has(dep)) return true;
|
|
27121
|
+
unresolvedDependencies.push({ taskId: id, dependency: dep });
|
|
27122
|
+
return false;
|
|
27123
|
+
});
|
|
27124
|
+
taskGraph.set(id, filtered);
|
|
27125
|
+
}
|
|
25186
27126
|
let plan = [];
|
|
25187
27127
|
let estimatedTime = 0;
|
|
25188
27128
|
let maxParallelism = 1;
|
|
@@ -25200,7 +27140,11 @@ function planExecution(tasks, strategy) {
|
|
|
25200
27140
|
break;
|
|
25201
27141
|
}
|
|
25202
27142
|
case "priority-based": {
|
|
25203
|
-
const
|
|
27143
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
27144
|
+
const sorted = tasks.map((t, idx) => ({
|
|
27145
|
+
id: t.id ?? `task-${idx}`,
|
|
27146
|
+
priority: t.priority ?? "medium"
|
|
27147
|
+
})).sort((a, b) => (priorityOrder[a.priority] ?? 1) - (priorityOrder[b.priority] ?? 1));
|
|
25204
27148
|
plan = sorted.map((t) => t.id);
|
|
25205
27149
|
estimatedTime = tasks.length * 80;
|
|
25206
27150
|
maxParallelism = 3;
|
|
@@ -25229,7 +27173,7 @@ function planExecution(tasks, strategy) {
|
|
|
25229
27173
|
break;
|
|
25230
27174
|
}
|
|
25231
27175
|
}
|
|
25232
|
-
return { plan, estimatedTime, maxParallelism };
|
|
27176
|
+
return { plan, estimatedTime, maxParallelism, unresolvedDependencies };
|
|
25233
27177
|
}
|
|
25234
27178
|
var createAgentPlanTool = defineTool({
|
|
25235
27179
|
name: "createAgentPlan",
|
|
@@ -25248,15 +27192,35 @@ var createAgentPlanTool = defineTool({
|
|
|
25248
27192
|
async execute(input) {
|
|
25249
27193
|
const typedInput = input;
|
|
25250
27194
|
const queue = new AgentTaskQueue();
|
|
25251
|
-
|
|
25252
|
-
|
|
25253
|
-
|
|
25254
|
-
|
|
25255
|
-
|
|
25256
|
-
|
|
25257
|
-
|
|
27195
|
+
const indexIdMap = /* @__PURE__ */ new Map();
|
|
27196
|
+
for (let idx = 0; idx < typedInput.tasks.length; idx++) {
|
|
27197
|
+
indexIdMap.set(idx, `task-${idx}`);
|
|
27198
|
+
}
|
|
27199
|
+
const normalizeDependency = (dep) => {
|
|
27200
|
+
if (indexIdMap.has(Number(dep))) {
|
|
27201
|
+
return indexIdMap.get(Number(dep));
|
|
27202
|
+
}
|
|
27203
|
+
return dep;
|
|
27204
|
+
};
|
|
27205
|
+
for (const [idx, task] of typedInput.tasks.entries()) {
|
|
27206
|
+
const id = indexIdMap.get(idx);
|
|
27207
|
+
const normalizedDependencies = (task.dependencies || []).map(normalizeDependency);
|
|
27208
|
+
queue.addTask(
|
|
27209
|
+
{
|
|
27210
|
+
description: task.description,
|
|
27211
|
+
priority: task.priority,
|
|
27212
|
+
estimatedDuration: 100,
|
|
27213
|
+
dependencies: normalizedDependencies
|
|
27214
|
+
},
|
|
27215
|
+
id
|
|
27216
|
+
);
|
|
25258
27217
|
}
|
|
25259
|
-
const
|
|
27218
|
+
const plannedTasks = typedInput.tasks.map((task, idx) => ({
|
|
27219
|
+
id: indexIdMap.get(idx),
|
|
27220
|
+
description: task.description,
|
|
27221
|
+
dependencies: (task.dependencies || []).map(normalizeDependency)
|
|
27222
|
+
}));
|
|
27223
|
+
const executionPlan = planExecution(plannedTasks, typedInput.strategy);
|
|
25260
27224
|
return {
|
|
25261
27225
|
planId: `plan-${Date.now()}`,
|
|
25262
27226
|
strategy: typedInput.strategy,
|
|
@@ -25264,6 +27228,7 @@ var createAgentPlanTool = defineTool({
|
|
|
25264
27228
|
executionOrder: executionPlan.plan,
|
|
25265
27229
|
estimatedTime: executionPlan.estimatedTime,
|
|
25266
27230
|
maxParallelism: executionPlan.maxParallelism,
|
|
27231
|
+
unresolvedDependencies: executionPlan.unresolvedDependencies,
|
|
25267
27232
|
tasks: queue.getTasks().map((t) => ({
|
|
25268
27233
|
id: t.id,
|
|
25269
27234
|
description: t.description,
|
|
@@ -25276,30 +27241,59 @@ var createAgentPlanTool = defineTool({
|
|
|
25276
27241
|
});
|
|
25277
27242
|
var delegateTaskTool = defineTool({
|
|
25278
27243
|
name: "delegateTask",
|
|
25279
|
-
description: "Delegate a task to a
|
|
27244
|
+
description: "Delegate a task to a specialized sub-agent with real LLM tool-use execution.",
|
|
25280
27245
|
category: "build",
|
|
25281
27246
|
parameters: z.object({
|
|
25282
27247
|
taskId: z.string(),
|
|
27248
|
+
task: z.string().describe("Description of the task for the agent to execute"),
|
|
25283
27249
|
agentRole: z.enum(["researcher", "coder", "reviewer", "tester", "optimizer"]).default("coder"),
|
|
25284
|
-
context: z.string().optional()
|
|
27250
|
+
context: z.string().optional(),
|
|
27251
|
+
maxTurns: z.number().default(10)
|
|
25285
27252
|
}),
|
|
25286
27253
|
async execute(input) {
|
|
25287
27254
|
const typedInput = input;
|
|
27255
|
+
const provider = getAgentProvider();
|
|
27256
|
+
const toolRegistry = getAgentToolRegistry();
|
|
27257
|
+
if (!provider || !toolRegistry) {
|
|
27258
|
+
return {
|
|
27259
|
+
agentId: `agent-${Date.now()}-${typedInput.agentRole}`,
|
|
27260
|
+
taskId: typedInput.taskId,
|
|
27261
|
+
role: typedInput.agentRole,
|
|
27262
|
+
status: "unavailable",
|
|
27263
|
+
message: "Agent provider not initialized. Call setAgentProvider() during orchestrator startup.",
|
|
27264
|
+
success: false
|
|
27265
|
+
};
|
|
27266
|
+
}
|
|
25288
27267
|
const agentId = `agent-${Date.now()}-${typedInput.agentRole}`;
|
|
27268
|
+
const executor = new AgentExecutor(provider, toolRegistry);
|
|
27269
|
+
const roleDef = AGENT_ROLES[typedInput.agentRole];
|
|
27270
|
+
if (!roleDef) {
|
|
27271
|
+
return {
|
|
27272
|
+
agentId,
|
|
27273
|
+
taskId: typedInput.taskId,
|
|
27274
|
+
role: typedInput.agentRole,
|
|
27275
|
+
status: "error",
|
|
27276
|
+
message: `Unknown agent role: ${typedInput.agentRole}`,
|
|
27277
|
+
success: false
|
|
27278
|
+
};
|
|
27279
|
+
}
|
|
27280
|
+
const agentDef = { ...roleDef, maxTurns: typedInput.maxTurns };
|
|
27281
|
+
const result = await executor.execute(agentDef, {
|
|
27282
|
+
id: typedInput.taskId,
|
|
27283
|
+
description: typedInput.task,
|
|
27284
|
+
context: typedInput.context ? { userContext: typedInput.context } : void 0
|
|
27285
|
+
});
|
|
25289
27286
|
return {
|
|
25290
27287
|
agentId,
|
|
25291
27288
|
taskId: typedInput.taskId,
|
|
25292
27289
|
role: typedInput.agentRole,
|
|
25293
|
-
status: "
|
|
25294
|
-
|
|
25295
|
-
|
|
25296
|
-
|
|
25297
|
-
|
|
25298
|
-
|
|
25299
|
-
|
|
25300
|
-
optimizer: ["performance tuning", "complexity reduction", "resource optimization"]
|
|
25301
|
-
}[typedInput.agentRole],
|
|
25302
|
-
note: "This is a simulated agent. Full implementation requires provider integration."
|
|
27290
|
+
status: result.success ? "completed" : "failed",
|
|
27291
|
+
output: result.output,
|
|
27292
|
+
success: result.success,
|
|
27293
|
+
turns: result.turns,
|
|
27294
|
+
toolsUsed: result.toolsUsed,
|
|
27295
|
+
tokensUsed: result.tokensUsed,
|
|
27296
|
+
duration: result.duration
|
|
25303
27297
|
};
|
|
25304
27298
|
}
|
|
25305
27299
|
});
|
|
@@ -25335,10 +27329,14 @@ var aggregateResultsTool = defineTool({
|
|
|
25335
27329
|
const winner = Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0];
|
|
25336
27330
|
aggregatedOutput = winner ? winner[0] : "";
|
|
25337
27331
|
break;
|
|
25338
|
-
case "best":
|
|
25339
|
-
const
|
|
25340
|
-
|
|
27332
|
+
case "best": {
|
|
27333
|
+
const successRate = typedInput.results.length > 0 ? completed.length / typedInput.results.length : 0;
|
|
27334
|
+
const bestResult = completed.length > 0 ? completed[0] : void 0;
|
|
27335
|
+
aggregatedOutput = bestResult ? `[Success rate: ${Math.round(successRate * 100)}%]
|
|
27336
|
+
|
|
27337
|
+
${bestResult.output}` : "";
|
|
25341
27338
|
break;
|
|
27339
|
+
}
|
|
25342
27340
|
case "summary":
|
|
25343
27341
|
aggregatedOutput = `Completed: ${completed.length}, Failed: ${failed.length}
|
|
25344
27342
|
|
|
@@ -25360,13 +27358,13 @@ ${completed.map((r) => `- ${r.agentId}: Success`).join("\n")}`;
|
|
|
25360
27358
|
}
|
|
25361
27359
|
});
|
|
25362
27360
|
var agentCoordinatorTools = [createAgentPlanTool, delegateTaskTool, aggregateResultsTool];
|
|
25363
|
-
var
|
|
27361
|
+
var fs36 = await import('fs/promises');
|
|
25364
27362
|
var SuggestImprovementsSchema = z.object({
|
|
25365
27363
|
filePath: z.string().describe("File to analyze for improvement suggestions"),
|
|
25366
27364
|
context: z.string().optional().describe("Additional context about the code")
|
|
25367
27365
|
});
|
|
25368
27366
|
async function analyzeAndSuggest(filePath, _context) {
|
|
25369
|
-
const content = await
|
|
27367
|
+
const content = await fs36.readFile(filePath, "utf-8");
|
|
25370
27368
|
const lines = content.split("\n");
|
|
25371
27369
|
const suggestions = [];
|
|
25372
27370
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -25412,7 +27410,8 @@ async function analyzeAndSuggest(filePath, _context) {
|
|
|
25412
27410
|
reasoning: "'any' bypasses all type checking and can hide bugs"
|
|
25413
27411
|
});
|
|
25414
27412
|
}
|
|
25415
|
-
|
|
27413
|
+
const trimmedLine = line.trim();
|
|
27414
|
+
if (trimmedLine.endsWith("catch (error) {") || trimmedLine.endsWith("catch (e) {") || trimmedLine === "catch (error) {" || trimmedLine === "catch (e) {") {
|
|
25416
27415
|
const nextLine = lines[i + 1];
|
|
25417
27416
|
if (nextLine && nextLine.trim() === "}") {
|
|
25418
27417
|
suggestions.push({
|
|
@@ -25457,7 +27456,7 @@ async function analyzeAndSuggest(filePath, _context) {
|
|
|
25457
27456
|
if (filePath.endsWith(".ts") && !filePath.includes("test") && !filePath.includes(".d.ts") && line.includes("export ")) {
|
|
25458
27457
|
const testPath = filePath.replace(".ts", ".test.ts");
|
|
25459
27458
|
try {
|
|
25460
|
-
await
|
|
27459
|
+
await fs36.access(testPath);
|
|
25461
27460
|
} catch {
|
|
25462
27461
|
suggestions.push({
|
|
25463
27462
|
type: "testing",
|
|
@@ -25514,7 +27513,7 @@ var calculateCodeScoreTool = defineTool({
|
|
|
25514
27513
|
async execute(input) {
|
|
25515
27514
|
const { filePath } = input;
|
|
25516
27515
|
const suggestions = await analyzeAndSuggest(filePath);
|
|
25517
|
-
const content = await
|
|
27516
|
+
const content = await fs36.readFile(filePath, "utf-8");
|
|
25518
27517
|
const lines = content.split("\n");
|
|
25519
27518
|
const nonEmptyLines = lines.filter((l) => l.trim()).length;
|
|
25520
27519
|
let score = 100;
|
|
@@ -25548,8 +27547,8 @@ var calculateCodeScoreTool = defineTool({
|
|
|
25548
27547
|
}
|
|
25549
27548
|
});
|
|
25550
27549
|
var smartSuggestionsTools = [suggestImprovementsTool, calculateCodeScoreTool];
|
|
25551
|
-
var
|
|
25552
|
-
var
|
|
27550
|
+
var fs37 = await import('fs/promises');
|
|
27551
|
+
var path34 = await import('path');
|
|
25553
27552
|
var ContextMemoryStore = class {
|
|
25554
27553
|
items = /* @__PURE__ */ new Map();
|
|
25555
27554
|
learnings = /* @__PURE__ */ new Map();
|
|
@@ -25561,7 +27560,7 @@ var ContextMemoryStore = class {
|
|
|
25561
27560
|
}
|
|
25562
27561
|
async load() {
|
|
25563
27562
|
try {
|
|
25564
|
-
const content = await
|
|
27563
|
+
const content = await fs37.readFile(this.storePath, "utf-8");
|
|
25565
27564
|
const data = JSON.parse(content);
|
|
25566
27565
|
this.items = new Map(Object.entries(data.items || {}));
|
|
25567
27566
|
this.learnings = new Map(Object.entries(data.learnings || {}));
|
|
@@ -25569,15 +27568,15 @@ var ContextMemoryStore = class {
|
|
|
25569
27568
|
}
|
|
25570
27569
|
}
|
|
25571
27570
|
async save() {
|
|
25572
|
-
const dir =
|
|
25573
|
-
await
|
|
27571
|
+
const dir = path34.dirname(this.storePath);
|
|
27572
|
+
await fs37.mkdir(dir, { recursive: true });
|
|
25574
27573
|
const data = {
|
|
25575
27574
|
sessionId: this.sessionId,
|
|
25576
27575
|
items: Object.fromEntries(this.items),
|
|
25577
27576
|
learnings: Object.fromEntries(this.learnings),
|
|
25578
27577
|
savedAt: Date.now()
|
|
25579
27578
|
};
|
|
25580
|
-
await
|
|
27579
|
+
await fs37.writeFile(this.storePath, JSON.stringify(data, null, 2));
|
|
25581
27580
|
}
|
|
25582
27581
|
addContext(id, item) {
|
|
25583
27582
|
this.items.set(id, item);
|
|
@@ -25742,11 +27741,11 @@ var contextEnhancerTools = [
|
|
|
25742
27741
|
recordLearningTool,
|
|
25743
27742
|
getLearnedPatternsTool
|
|
25744
27743
|
];
|
|
25745
|
-
var
|
|
25746
|
-
var
|
|
27744
|
+
var fs38 = await import('fs/promises');
|
|
27745
|
+
var path35 = await import('path');
|
|
25747
27746
|
async function discoverSkills(skillsDir) {
|
|
25748
27747
|
try {
|
|
25749
|
-
const files = await
|
|
27748
|
+
const files = await fs38.readdir(skillsDir);
|
|
25750
27749
|
return files.filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
|
|
25751
27750
|
} catch {
|
|
25752
27751
|
return [];
|
|
@@ -25754,12 +27753,12 @@ async function discoverSkills(skillsDir) {
|
|
|
25754
27753
|
}
|
|
25755
27754
|
async function loadSkillMetadata(skillPath) {
|
|
25756
27755
|
try {
|
|
25757
|
-
const content = await
|
|
27756
|
+
const content = await fs38.readFile(skillPath, "utf-8");
|
|
25758
27757
|
const nameMatch = content.match(/@name\s+(\S+)/);
|
|
25759
27758
|
const descMatch = content.match(/@description\s+(.+)/);
|
|
25760
27759
|
const versionMatch = content.match(/@version\s+(\S+)/);
|
|
25761
27760
|
return {
|
|
25762
|
-
name: nameMatch?.[1] ||
|
|
27761
|
+
name: nameMatch?.[1] || path35.basename(skillPath, path35.extname(skillPath)),
|
|
25763
27762
|
description: descMatch?.[1] || "No description",
|
|
25764
27763
|
version: versionMatch?.[1] || "1.0.0",
|
|
25765
27764
|
dependencies: []
|
|
@@ -25803,7 +27802,7 @@ var discoverSkillsTool = defineTool({
|
|
|
25803
27802
|
const { skillsDir } = input;
|
|
25804
27803
|
const skills = await discoverSkills(skillsDir);
|
|
25805
27804
|
const metadata = await Promise.all(
|
|
25806
|
-
skills.map((s) => loadSkillMetadata(
|
|
27805
|
+
skills.map((s) => loadSkillMetadata(path35.join(skillsDir, s)))
|
|
25807
27806
|
);
|
|
25808
27807
|
return {
|
|
25809
27808
|
skillsDir,
|
|
@@ -26532,6 +28531,24 @@ var pc = chalk12;
|
|
|
26532
28531
|
});
|
|
26533
28532
|
|
|
26534
28533
|
// src/cli/repl/index.ts
|
|
28534
|
+
function visualWidth(str) {
|
|
28535
|
+
const stripped = str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
28536
|
+
let width = 0;
|
|
28537
|
+
for (const char of stripped) {
|
|
28538
|
+
const cp = char.codePointAt(0) || 0;
|
|
28539
|
+
if (cp > 128512 || cp >= 9728 && cp <= 10175 || cp >= 127744 && cp <= 129750 || cp >= 129280 && cp <= 129535 || cp >= 9986 && cp <= 10160 || cp >= 65024 && cp <= 65039 || cp === 8205) {
|
|
28540
|
+
width += 2;
|
|
28541
|
+
} else if (
|
|
28542
|
+
// CJK Unified Ideographs and other wide characters
|
|
28543
|
+
cp >= 4352 && cp <= 4447 || cp >= 11904 && cp <= 12350 || cp >= 12352 && cp <= 13247 || cp >= 19968 && cp <= 40959 || cp >= 63744 && cp <= 64255 || cp >= 65072 && cp <= 65135 || cp >= 65281 && cp <= 65376 || cp >= 65504 && cp <= 65510
|
|
28544
|
+
) {
|
|
28545
|
+
width += 2;
|
|
28546
|
+
} else {
|
|
28547
|
+
width += 1;
|
|
28548
|
+
}
|
|
28549
|
+
}
|
|
28550
|
+
return width;
|
|
28551
|
+
}
|
|
26535
28552
|
async function startRepl(options = {}) {
|
|
26536
28553
|
const projectPath = options.projectPath ?? process.cwd();
|
|
26537
28554
|
const session = createSession(projectPath, options.config);
|
|
@@ -26576,6 +28593,8 @@ async function startRepl(options = {}) {
|
|
|
26576
28593
|
}
|
|
26577
28594
|
await loadCocoModePreference();
|
|
26578
28595
|
const toolRegistry = createFullToolRegistry();
|
|
28596
|
+
setAgentProvider(provider);
|
|
28597
|
+
setAgentToolRegistry(toolRegistry);
|
|
26579
28598
|
const inputHandler = createInputHandler();
|
|
26580
28599
|
const intentRecognizer = createIntentRecognizer();
|
|
26581
28600
|
await printWelcome(session);
|
|
@@ -26796,23 +28815,39 @@ async function startRepl(options = {}) {
|
|
|
26796
28815
|
inputHandler.close();
|
|
26797
28816
|
}
|
|
26798
28817
|
async function printWelcome(session) {
|
|
28818
|
+
const providerType = session.config.provider.type;
|
|
28819
|
+
const model = session.config.provider.model || "default";
|
|
28820
|
+
const isReturningUser = existsSync(path20__default.join(session.projectPath, ".coco"));
|
|
28821
|
+
if (isReturningUser) {
|
|
28822
|
+
const versionStr = chalk12.dim(`v${VERSION}`);
|
|
28823
|
+
const providerStr = chalk12.dim(`${providerType}/${model}`);
|
|
28824
|
+
console.log(
|
|
28825
|
+
`
|
|
28826
|
+
\u{1F965} Coco ${versionStr} ${chalk12.dim("\u2502")} ${providerStr} ${chalk12.dim("\u2502")} ${chalk12.yellow("/help")}
|
|
28827
|
+
`
|
|
28828
|
+
);
|
|
28829
|
+
return;
|
|
28830
|
+
}
|
|
26799
28831
|
const trustStore = createTrustStore();
|
|
26800
28832
|
await trustStore.init();
|
|
26801
28833
|
const trustLevel = trustStore.getLevel(session.projectPath);
|
|
26802
28834
|
const boxWidth = 41;
|
|
26803
28835
|
const innerWidth = boxWidth - 4;
|
|
26804
|
-
const
|
|
28836
|
+
const titleContent = "\u{1F965} CORBAT-COCO";
|
|
26805
28837
|
const versionText = `v${VERSION}`;
|
|
26806
|
-
const
|
|
28838
|
+
const titleContentVisualWidth = visualWidth(titleContent);
|
|
28839
|
+
const versionVisualWidth = visualWidth(versionText);
|
|
28840
|
+
const titlePadding = innerWidth - titleContentVisualWidth - versionVisualWidth;
|
|
26807
28841
|
const subtitleText = "open source \u2022 corbat.tech";
|
|
26808
|
-
const
|
|
28842
|
+
const subtitleVisualWidth = visualWidth(subtitleText);
|
|
28843
|
+
const subtitlePadding = innerWidth - subtitleVisualWidth;
|
|
26809
28844
|
console.log();
|
|
26810
28845
|
console.log(chalk12.magenta(" \u256D" + "\u2500".repeat(boxWidth - 2) + "\u256E"));
|
|
26811
28846
|
console.log(
|
|
26812
|
-
chalk12.magenta(" \u2502 ") + "\u{1F965} " + chalk12.bold.white(
|
|
28847
|
+
chalk12.magenta(" \u2502 ") + "\u{1F965} " + chalk12.bold.white("CORBAT-COCO") + " ".repeat(Math.max(0, titlePadding)) + chalk12.dim(versionText) + chalk12.magenta(" \u2502")
|
|
26813
28848
|
);
|
|
26814
28849
|
console.log(
|
|
26815
|
-
chalk12.magenta(" \u2502 ") + chalk12.dim(subtitleText) + " ".repeat(subtitlePadding) + chalk12.magenta(" \u2502")
|
|
28850
|
+
chalk12.magenta(" \u2502 ") + chalk12.dim(subtitleText) + " ".repeat(Math.max(0, subtitlePadding)) + chalk12.magenta(" \u2502")
|
|
26816
28851
|
);
|
|
26817
28852
|
console.log(chalk12.magenta(" \u2570" + "\u2500".repeat(boxWidth - 2) + "\u256F"));
|
|
26818
28853
|
const updateInfo = await checkForUpdates();
|
|
@@ -27067,7 +29102,7 @@ async function main() {
|
|
|
27067
29102
|
await program.parseAsync(process.argv);
|
|
27068
29103
|
}
|
|
27069
29104
|
main().catch((error) => {
|
|
27070
|
-
console.error(
|
|
29105
|
+
console.error(formatError(error));
|
|
27071
29106
|
process.exit(1);
|
|
27072
29107
|
});
|
|
27073
29108
|
//# sourceMappingURL=index.js.map
|