@corbat-tech/coco 1.0.2 → 1.2.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 +167 -376
- package/dist/cli/index.js +2524 -384
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.js +10136 -8313
- package/dist/index.js.map +1 -1
- package/package.json +38 -25
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,9 @@ 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"]
|
|
724
727
|
});
|
|
725
728
|
var PersistenceConfigSchema = z.object({
|
|
726
729
|
checkpointInterval: z.number().min(6e4).default(3e5),
|
|
@@ -976,6 +979,35 @@ var TimeoutError = class extends CocoError {
|
|
|
976
979
|
this.operation = options.operation;
|
|
977
980
|
}
|
|
978
981
|
};
|
|
982
|
+
var ERROR_SUGGESTIONS = {
|
|
983
|
+
PROVIDER_ERROR: "Check your API key and provider configuration. Run 'coco setup' to reconfigure.",
|
|
984
|
+
CONFIG_ERROR: "Check your .coco/config.json or run 'coco setup' to reconfigure.",
|
|
985
|
+
FILESYSTEM_ERROR: "Check that the path exists and you have read/write permissions.",
|
|
986
|
+
VALIDATION_ERROR: "Check the input data format. See 'coco --help' for usage.",
|
|
987
|
+
PHASE_ERROR: "Phase execution failed. Try 'coco resume' to continue from the last checkpoint.",
|
|
988
|
+
TASK_ERROR: "Task execution failed. The task can be retried from the last checkpoint with 'coco resume'.",
|
|
989
|
+
QUALITY_ERROR: "Quality score below threshold. Review the issues listed above and iterate on the code.",
|
|
990
|
+
RECOVERY_ERROR: "Checkpoint may be corrupted. Try 'coco init --force' to start fresh.",
|
|
991
|
+
TOOL_ERROR: "A tool execution failed. Check the error details above and retry.",
|
|
992
|
+
TIMEOUT_ERROR: "Operation timed out. Try increasing the timeout in config or simplifying the request.",
|
|
993
|
+
UNEXPECTED_ERROR: "An unexpected error occurred. Please report at github.com/corbat/corbat-coco/issues."
|
|
994
|
+
};
|
|
995
|
+
function formatError(error) {
|
|
996
|
+
if (error instanceof CocoError) {
|
|
997
|
+
let message = `[${error.code}] ${error.message}`;
|
|
998
|
+
const suggestion = error.suggestion ?? ERROR_SUGGESTIONS[error.code];
|
|
999
|
+
if (suggestion) {
|
|
1000
|
+
message += `
|
|
1001
|
+
Suggestion: ${suggestion}`;
|
|
1002
|
+
}
|
|
1003
|
+
return message;
|
|
1004
|
+
}
|
|
1005
|
+
if (error instanceof Error) {
|
|
1006
|
+
return `${error.message}
|
|
1007
|
+
Suggestion: ${ERROR_SUGGESTIONS["UNEXPECTED_ERROR"]}`;
|
|
1008
|
+
}
|
|
1009
|
+
return String(error);
|
|
1010
|
+
}
|
|
979
1011
|
|
|
980
1012
|
// src/config/loader.ts
|
|
981
1013
|
init_paths();
|
|
@@ -1037,7 +1069,17 @@ function deepMergeConfig(base, override) {
|
|
|
1037
1069
|
project: { ...base.project, ...override.project },
|
|
1038
1070
|
provider: { ...base.provider, ...override.provider },
|
|
1039
1071
|
quality: { ...base.quality, ...override.quality },
|
|
1040
|
-
persistence: { ...base.persistence, ...override.persistence }
|
|
1072
|
+
persistence: { ...base.persistence, ...override.persistence },
|
|
1073
|
+
// Merge optional sections only if present in either base or override
|
|
1074
|
+
...base.stack || override.stack ? { stack: { ...base.stack, ...override.stack } } : {},
|
|
1075
|
+
...base.integrations || override.integrations ? {
|
|
1076
|
+
integrations: {
|
|
1077
|
+
...base.integrations,
|
|
1078
|
+
...override.integrations
|
|
1079
|
+
}
|
|
1080
|
+
} : {},
|
|
1081
|
+
...base.mcp || override.mcp ? { mcp: { ...base.mcp, ...override.mcp } } : {},
|
|
1082
|
+
...base.tools || override.tools ? { tools: { ...base.tools, ...override.tools } } : {}
|
|
1041
1083
|
};
|
|
1042
1084
|
}
|
|
1043
1085
|
function getProjectConfigPath() {
|
|
@@ -4743,6 +4785,9 @@ var AnthropicProvider = class {
|
|
|
4743
4785
|
try {
|
|
4744
4786
|
currentToolCall.input = currentToolInputJson ? JSON.parse(currentToolInputJson) : {};
|
|
4745
4787
|
} catch {
|
|
4788
|
+
console.warn(
|
|
4789
|
+
`[Anthropic] Failed to parse tool call arguments: ${currentToolInputJson?.slice(0, 100)}`
|
|
4790
|
+
);
|
|
4746
4791
|
currentToolCall.input = {};
|
|
4747
4792
|
}
|
|
4748
4793
|
yield {
|
|
@@ -5275,6 +5320,9 @@ var OpenAIProvider = class {
|
|
|
5275
5320
|
try {
|
|
5276
5321
|
input = builder.arguments ? JSON.parse(builder.arguments) : {};
|
|
5277
5322
|
} catch {
|
|
5323
|
+
console.warn(
|
|
5324
|
+
`[OpenAI] Failed to parse tool call arguments: ${builder.arguments?.slice(0, 100)}`
|
|
5325
|
+
);
|
|
5278
5326
|
}
|
|
5279
5327
|
yield {
|
|
5280
5328
|
type: "tool_use_end",
|
|
@@ -5563,7 +5611,16 @@ var OpenAIProvider = class {
|
|
|
5563
5611
|
).map((tc) => ({
|
|
5564
5612
|
id: tc.id,
|
|
5565
5613
|
name: tc.function.name,
|
|
5566
|
-
input:
|
|
5614
|
+
input: (() => {
|
|
5615
|
+
try {
|
|
5616
|
+
return JSON.parse(tc.function.arguments || "{}");
|
|
5617
|
+
} catch {
|
|
5618
|
+
console.warn(
|
|
5619
|
+
`[OpenAI] Failed to parse tool call arguments: ${tc.function.arguments?.slice(0, 100)}`
|
|
5620
|
+
);
|
|
5621
|
+
return {};
|
|
5622
|
+
}
|
|
5623
|
+
})()
|
|
5567
5624
|
}));
|
|
5568
5625
|
}
|
|
5569
5626
|
/**
|
|
@@ -7211,7 +7268,10 @@ var GeminiProvider = class {
|
|
|
7211
7268
|
for (const part of candidate.content.parts) {
|
|
7212
7269
|
if ("functionCall" in part && part.functionCall) {
|
|
7213
7270
|
const funcCall = part.functionCall;
|
|
7214
|
-
const
|
|
7271
|
+
const sortedArgs = funcCall.args ? Object.keys(funcCall.args).sort().map(
|
|
7272
|
+
(k) => `${k}:${JSON.stringify(funcCall.args[k])}`
|
|
7273
|
+
).join(",") : "";
|
|
7274
|
+
const callKey = `${funcCall.name}-${sortedArgs}`;
|
|
7215
7275
|
if (!emittedToolCalls.has(callKey)) {
|
|
7216
7276
|
emittedToolCalls.add(callKey);
|
|
7217
7277
|
const toolCall = {
|
|
@@ -7243,9 +7303,18 @@ var GeminiProvider = class {
|
|
|
7243
7303
|
}
|
|
7244
7304
|
/**
|
|
7245
7305
|
* Count tokens (approximate)
|
|
7306
|
+
*
|
|
7307
|
+
* Gemini uses a SentencePiece tokenizer. The average ratio varies:
|
|
7308
|
+
* - English text: ~4 characters per token
|
|
7309
|
+
* - Code: ~3.2 characters per token
|
|
7310
|
+
* - Mixed content: ~3.5 characters per token
|
|
7311
|
+
*
|
|
7312
|
+
* Using 3.5 as the default provides a better estimate for typical
|
|
7313
|
+
* coding agent workloads which mix code and natural language.
|
|
7246
7314
|
*/
|
|
7247
7315
|
countTokens(text9) {
|
|
7248
|
-
|
|
7316
|
+
if (!text9) return 0;
|
|
7317
|
+
return Math.ceil(text9.length / 3.5);
|
|
7249
7318
|
}
|
|
7250
7319
|
/**
|
|
7251
7320
|
* Get context window size
|
|
@@ -7579,35 +7648,35 @@ async function createCliPhaseContext(projectPath, _onUserInput) {
|
|
|
7579
7648
|
},
|
|
7580
7649
|
tools: {
|
|
7581
7650
|
file: {
|
|
7582
|
-
async read(
|
|
7583
|
-
const
|
|
7584
|
-
return
|
|
7651
|
+
async read(path37) {
|
|
7652
|
+
const fs39 = await import('fs/promises');
|
|
7653
|
+
return fs39.readFile(path37, "utf-8");
|
|
7585
7654
|
},
|
|
7586
|
-
async write(
|
|
7587
|
-
const
|
|
7655
|
+
async write(path37, content) {
|
|
7656
|
+
const fs39 = await import('fs/promises');
|
|
7588
7657
|
const nodePath = await import('path');
|
|
7589
|
-
await
|
|
7590
|
-
await
|
|
7658
|
+
await fs39.mkdir(nodePath.dirname(path37), { recursive: true });
|
|
7659
|
+
await fs39.writeFile(path37, content, "utf-8");
|
|
7591
7660
|
},
|
|
7592
|
-
async exists(
|
|
7593
|
-
const
|
|
7661
|
+
async exists(path37) {
|
|
7662
|
+
const fs39 = await import('fs/promises');
|
|
7594
7663
|
try {
|
|
7595
|
-
await
|
|
7664
|
+
await fs39.access(path37);
|
|
7596
7665
|
return true;
|
|
7597
7666
|
} catch {
|
|
7598
7667
|
return false;
|
|
7599
7668
|
}
|
|
7600
7669
|
},
|
|
7601
7670
|
async glob(pattern) {
|
|
7602
|
-
const { glob:
|
|
7603
|
-
return
|
|
7671
|
+
const { glob: glob15 } = await import('glob');
|
|
7672
|
+
return glob15(pattern, { cwd: projectPath });
|
|
7604
7673
|
}
|
|
7605
7674
|
},
|
|
7606
7675
|
bash: {
|
|
7607
7676
|
async exec(command, options = {}) {
|
|
7608
|
-
const { execa:
|
|
7677
|
+
const { execa: execa10 } = await import('execa');
|
|
7609
7678
|
try {
|
|
7610
|
-
const result = await
|
|
7679
|
+
const result = await execa10(command, {
|
|
7611
7680
|
shell: true,
|
|
7612
7681
|
cwd: options.cwd || projectPath,
|
|
7613
7682
|
timeout: options.timeout,
|
|
@@ -7831,16 +7900,16 @@ async function loadTasks(_options) {
|
|
|
7831
7900
|
];
|
|
7832
7901
|
}
|
|
7833
7902
|
async function checkProjectState() {
|
|
7834
|
-
const
|
|
7903
|
+
const fs39 = await import('fs/promises');
|
|
7835
7904
|
let hasProject = false;
|
|
7836
7905
|
let hasPlan = false;
|
|
7837
7906
|
try {
|
|
7838
|
-
await
|
|
7907
|
+
await fs39.access(".coco");
|
|
7839
7908
|
hasProject = true;
|
|
7840
7909
|
} catch {
|
|
7841
7910
|
}
|
|
7842
7911
|
try {
|
|
7843
|
-
await
|
|
7912
|
+
await fs39.access(".coco/planning/backlog.json");
|
|
7844
7913
|
hasPlan = true;
|
|
7845
7914
|
} catch {
|
|
7846
7915
|
}
|
|
@@ -7944,24 +8013,24 @@ function getPhaseStatusForPhase(phase) {
|
|
|
7944
8013
|
return "in_progress";
|
|
7945
8014
|
}
|
|
7946
8015
|
async function loadProjectState(cwd, config) {
|
|
7947
|
-
const
|
|
7948
|
-
const
|
|
7949
|
-
const statePath =
|
|
7950
|
-
const backlogPath =
|
|
7951
|
-
const checkpointDir =
|
|
8016
|
+
const fs39 = await import('fs/promises');
|
|
8017
|
+
const path37 = await import('path');
|
|
8018
|
+
const statePath = path37.join(cwd, ".coco", "state.json");
|
|
8019
|
+
const backlogPath = path37.join(cwd, ".coco", "planning", "backlog.json");
|
|
8020
|
+
const checkpointDir = path37.join(cwd, ".coco", "checkpoints");
|
|
7952
8021
|
let currentPhase = "idle";
|
|
7953
8022
|
let metrics;
|
|
7954
8023
|
let sprint;
|
|
7955
8024
|
let checkpoints = [];
|
|
7956
8025
|
try {
|
|
7957
|
-
const stateContent = await
|
|
8026
|
+
const stateContent = await fs39.readFile(statePath, "utf-8");
|
|
7958
8027
|
const stateData = JSON.parse(stateContent);
|
|
7959
8028
|
currentPhase = stateData.currentPhase || "idle";
|
|
7960
8029
|
metrics = stateData.metrics;
|
|
7961
8030
|
} catch {
|
|
7962
8031
|
}
|
|
7963
8032
|
try {
|
|
7964
|
-
const backlogContent = await
|
|
8033
|
+
const backlogContent = await fs39.readFile(backlogPath, "utf-8");
|
|
7965
8034
|
const backlogData = JSON.parse(backlogContent);
|
|
7966
8035
|
if (backlogData.currentSprint) {
|
|
7967
8036
|
const tasks = backlogData.tasks || [];
|
|
@@ -7983,7 +8052,7 @@ async function loadProjectState(cwd, config) {
|
|
|
7983
8052
|
} catch {
|
|
7984
8053
|
}
|
|
7985
8054
|
try {
|
|
7986
|
-
const files = await
|
|
8055
|
+
const files = await fs39.readdir(checkpointDir);
|
|
7987
8056
|
checkpoints = files.filter((f) => f.endsWith(".json")).sort().reverse();
|
|
7988
8057
|
} catch {
|
|
7989
8058
|
}
|
|
@@ -8119,8 +8188,8 @@ async function restoreFromCheckpoint(_checkpoint) {
|
|
|
8119
8188
|
}
|
|
8120
8189
|
async function checkProjectExists() {
|
|
8121
8190
|
try {
|
|
8122
|
-
const
|
|
8123
|
-
await
|
|
8191
|
+
const fs39 = await import('fs/promises');
|
|
8192
|
+
await fs39.access(".coco");
|
|
8124
8193
|
return true;
|
|
8125
8194
|
} catch {
|
|
8126
8195
|
return false;
|
|
@@ -8659,12 +8728,12 @@ async function loadConfig2() {
|
|
|
8659
8728
|
};
|
|
8660
8729
|
}
|
|
8661
8730
|
async function saveConfig(config) {
|
|
8662
|
-
const
|
|
8663
|
-
await
|
|
8664
|
-
await
|
|
8731
|
+
const fs39 = await import('fs/promises');
|
|
8732
|
+
await fs39.mkdir(".coco", { recursive: true });
|
|
8733
|
+
await fs39.writeFile(".coco/config.json", JSON.stringify(config, null, 2));
|
|
8665
8734
|
}
|
|
8666
|
-
function getNestedValue(obj,
|
|
8667
|
-
const keys =
|
|
8735
|
+
function getNestedValue(obj, path37) {
|
|
8736
|
+
const keys = path37.split(".");
|
|
8668
8737
|
let current = obj;
|
|
8669
8738
|
for (const key of keys) {
|
|
8670
8739
|
if (current === null || current === void 0 || typeof current !== "object") {
|
|
@@ -8674,8 +8743,8 @@ function getNestedValue(obj, path35) {
|
|
|
8674
8743
|
}
|
|
8675
8744
|
return current;
|
|
8676
8745
|
}
|
|
8677
|
-
function setNestedValue(obj,
|
|
8678
|
-
const keys =
|
|
8746
|
+
function setNestedValue(obj, path37, value) {
|
|
8747
|
+
const keys = path37.split(".");
|
|
8679
8748
|
let current = obj;
|
|
8680
8749
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
8681
8750
|
const key = keys[i];
|
|
@@ -8896,8 +8965,8 @@ var MCPRegistryImpl = class {
|
|
|
8896
8965
|
/**
|
|
8897
8966
|
* Ensure directory exists
|
|
8898
8967
|
*/
|
|
8899
|
-
async ensureDir(
|
|
8900
|
-
await mkdir(dirname(
|
|
8968
|
+
async ensureDir(path37) {
|
|
8969
|
+
await mkdir(dirname(path37), { recursive: true });
|
|
8901
8970
|
}
|
|
8902
8971
|
};
|
|
8903
8972
|
function createMCPRegistry(registryPath) {
|
|
@@ -9416,9 +9485,9 @@ function createEmptyMemoryContext() {
|
|
|
9416
9485
|
errors: []
|
|
9417
9486
|
};
|
|
9418
9487
|
}
|
|
9419
|
-
function createMissingMemoryFile(
|
|
9488
|
+
function createMissingMemoryFile(path37, level) {
|
|
9420
9489
|
return {
|
|
9421
|
-
path:
|
|
9490
|
+
path: path37,
|
|
9422
9491
|
level,
|
|
9423
9492
|
content: "",
|
|
9424
9493
|
sections: [],
|
|
@@ -9623,7 +9692,9 @@ async function saveTrustSettings(settings) {
|
|
|
9623
9692
|
await fs22__default.mkdir(TRUST_SETTINGS_DIR, { recursive: true });
|
|
9624
9693
|
settings.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9625
9694
|
await fs22__default.writeFile(TRUST_SETTINGS_FILE, JSON.stringify(settings, null, 2), "utf-8");
|
|
9626
|
-
} catch {
|
|
9695
|
+
} catch (error) {
|
|
9696
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
9697
|
+
console.warn(`[Trust] Failed to save trust settings: ${msg}`);
|
|
9627
9698
|
}
|
|
9628
9699
|
}
|
|
9629
9700
|
async function loadTrustedTools(projectPath) {
|
|
@@ -9784,6 +9855,7 @@ var helpCommand = {
|
|
|
9784
9855
|
commands: [
|
|
9785
9856
|
{ cmd: "/help, /?", desc: "Show this help message" },
|
|
9786
9857
|
{ cmd: "/help tools", desc: "Show available agent tools" },
|
|
9858
|
+
{ cmd: "/tutorial, /tut", desc: "Quick guide to using Coco" },
|
|
9787
9859
|
{ cmd: "/clear, /c", desc: "Clear conversation history" },
|
|
9788
9860
|
{ cmd: "/exit, /quit, /q", desc: "Exit the REPL" }
|
|
9789
9861
|
]
|
|
@@ -10329,11 +10401,11 @@ async function setupGcloudADC(provider) {
|
|
|
10329
10401
|
console.log(chalk12.dim(" Opening browser for Google sign-in..."));
|
|
10330
10402
|
console.log(chalk12.dim(" (Complete the sign-in in your browser, then return here)"));
|
|
10331
10403
|
console.log();
|
|
10332
|
-
const { exec:
|
|
10333
|
-
const { promisify:
|
|
10334
|
-
const
|
|
10404
|
+
const { exec: exec3 } = await import('child_process');
|
|
10405
|
+
const { promisify: promisify4 } = await import('util');
|
|
10406
|
+
const execAsync3 = promisify4(exec3);
|
|
10335
10407
|
try {
|
|
10336
|
-
await
|
|
10408
|
+
await execAsync3("gcloud auth application-default login", {
|
|
10337
10409
|
timeout: 12e4
|
|
10338
10410
|
// 2 minute timeout
|
|
10339
10411
|
});
|
|
@@ -11417,11 +11489,11 @@ async function setupGcloudADCForProvider(_provider) {
|
|
|
11417
11489
|
if (p9.isCancel(runNow) || !runNow) {
|
|
11418
11490
|
return false;
|
|
11419
11491
|
}
|
|
11420
|
-
const { exec:
|
|
11421
|
-
const { promisify:
|
|
11422
|
-
const
|
|
11492
|
+
const { exec: exec3 } = await import('child_process');
|
|
11493
|
+
const { promisify: promisify4 } = await import('util');
|
|
11494
|
+
const execAsync3 = promisify4(exec3);
|
|
11423
11495
|
try {
|
|
11424
|
-
await
|
|
11496
|
+
await execAsync3("gcloud auth application-default login", { timeout: 12e4 });
|
|
11425
11497
|
const token = await getADCAccessToken();
|
|
11426
11498
|
if (token) {
|
|
11427
11499
|
console.log(chalk12.green("\n \u2713 Authentication successful!\n"));
|
|
@@ -12106,8 +12178,8 @@ async function listTrustedProjects2(trustStore) {
|
|
|
12106
12178
|
p9.log.message("");
|
|
12107
12179
|
for (const project of projects) {
|
|
12108
12180
|
const level = project.approvalLevel.toUpperCase().padEnd(5);
|
|
12109
|
-
const
|
|
12110
|
-
p9.log.message(` [${level}] ${
|
|
12181
|
+
const path37 = project.path.length > 50 ? "..." + project.path.slice(-47) : project.path;
|
|
12182
|
+
p9.log.message(` [${level}] ${path37}`);
|
|
12111
12183
|
p9.log.message(` Last accessed: ${new Date(project.lastAccessed).toLocaleString()}`);
|
|
12112
12184
|
}
|
|
12113
12185
|
p9.log.message("");
|
|
@@ -13277,7 +13349,7 @@ async function getCheckpoint(session, checkpointId) {
|
|
|
13277
13349
|
return store.checkpoints.find((cp) => cp.id === checkpointId) ?? null;
|
|
13278
13350
|
}
|
|
13279
13351
|
async function restoreFiles(checkpoint, excludeFiles) {
|
|
13280
|
-
const
|
|
13352
|
+
const fs39 = await import('fs/promises');
|
|
13281
13353
|
const restored = [];
|
|
13282
13354
|
const failed = [];
|
|
13283
13355
|
for (const fileCheckpoint of checkpoint.files) {
|
|
@@ -13285,7 +13357,7 @@ async function restoreFiles(checkpoint, excludeFiles) {
|
|
|
13285
13357
|
continue;
|
|
13286
13358
|
}
|
|
13287
13359
|
try {
|
|
13288
|
-
await
|
|
13360
|
+
await fs39.writeFile(fileCheckpoint.filePath, fileCheckpoint.originalContent, "utf-8");
|
|
13289
13361
|
restored.push(fileCheckpoint.filePath);
|
|
13290
13362
|
} catch (error) {
|
|
13291
13363
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -13367,8 +13439,8 @@ function displayRewindResult(result) {
|
|
|
13367
13439
|
const fileName = filePath.split("/").pop() ?? filePath;
|
|
13368
13440
|
console.log(`${chalk12.green(String.fromCodePoint(10003))} Restored: ${fileName}`);
|
|
13369
13441
|
}
|
|
13370
|
-
for (const { path:
|
|
13371
|
-
const fileName =
|
|
13442
|
+
for (const { path: path37, error } of result.filesFailed) {
|
|
13443
|
+
const fileName = path37.split("/").pop() ?? path37;
|
|
13372
13444
|
console.log(`${chalk12.red(String.fromCodePoint(10007))} Failed: ${fileName} (${error})`);
|
|
13373
13445
|
}
|
|
13374
13446
|
if (result.conversationRestored) {
|
|
@@ -14333,7 +14405,11 @@ function flushLineBuffer() {
|
|
|
14333
14405
|
}
|
|
14334
14406
|
if (inCodeBlock && codeBlockLines.length > 0) {
|
|
14335
14407
|
stopStreamingIndicator();
|
|
14336
|
-
|
|
14408
|
+
try {
|
|
14409
|
+
renderCodeBlock(codeBlockLang, codeBlockLines);
|
|
14410
|
+
} finally {
|
|
14411
|
+
stopStreamingIndicator();
|
|
14412
|
+
}
|
|
14337
14413
|
inCodeBlock = false;
|
|
14338
14414
|
codeBlockLang = "";
|
|
14339
14415
|
codeBlockLines = [];
|
|
@@ -14689,6 +14765,7 @@ function formatInlineMarkdown(text9) {
|
|
|
14689
14765
|
return text9;
|
|
14690
14766
|
}
|
|
14691
14767
|
function wrapText(text9, maxWidth) {
|
|
14768
|
+
if (maxWidth <= 0) return [text9];
|
|
14692
14769
|
const plainText = stripAnsi(text9);
|
|
14693
14770
|
if (plainText.length <= maxWidth) {
|
|
14694
14771
|
return [text9];
|
|
@@ -14696,14 +14773,37 @@ function wrapText(text9, maxWidth) {
|
|
|
14696
14773
|
const lines = [];
|
|
14697
14774
|
let remaining = text9;
|
|
14698
14775
|
while (stripAnsi(remaining).length > maxWidth) {
|
|
14699
|
-
let breakPoint = maxWidth;
|
|
14700
14776
|
const plain = stripAnsi(remaining);
|
|
14777
|
+
let breakPoint = maxWidth;
|
|
14701
14778
|
const lastSpace = plain.lastIndexOf(" ", maxWidth);
|
|
14702
14779
|
if (lastSpace > maxWidth * 0.5) {
|
|
14703
14780
|
breakPoint = lastSpace;
|
|
14704
14781
|
}
|
|
14705
|
-
|
|
14706
|
-
|
|
14782
|
+
const ansiRegex = /\x1b\[[0-9;]*m/g;
|
|
14783
|
+
let match;
|
|
14784
|
+
const ansiPositions = [];
|
|
14785
|
+
ansiRegex.lastIndex = 0;
|
|
14786
|
+
while ((match = ansiRegex.exec(remaining)) !== null) {
|
|
14787
|
+
ansiPositions.push({ start: match.index, end: match.index + match[0].length });
|
|
14788
|
+
}
|
|
14789
|
+
let rawPos = 0;
|
|
14790
|
+
let visualPos = 0;
|
|
14791
|
+
let ansiIdx = 0;
|
|
14792
|
+
while (visualPos < breakPoint && rawPos < remaining.length) {
|
|
14793
|
+
while (ansiIdx < ansiPositions.length && ansiPositions[ansiIdx].start === rawPos) {
|
|
14794
|
+
rawPos = ansiPositions[ansiIdx].end;
|
|
14795
|
+
ansiIdx++;
|
|
14796
|
+
}
|
|
14797
|
+
if (rawPos >= remaining.length) break;
|
|
14798
|
+
rawPos++;
|
|
14799
|
+
visualPos++;
|
|
14800
|
+
}
|
|
14801
|
+
while (ansiIdx < ansiPositions.length && ansiPositions[ansiIdx].start === rawPos) {
|
|
14802
|
+
rawPos = ansiPositions[ansiIdx].end;
|
|
14803
|
+
ansiIdx++;
|
|
14804
|
+
}
|
|
14805
|
+
lines.push(remaining.slice(0, rawPos));
|
|
14806
|
+
remaining = remaining.slice(rawPos).trimStart();
|
|
14707
14807
|
}
|
|
14708
14808
|
if (remaining) {
|
|
14709
14809
|
lines.push(remaining);
|
|
@@ -14773,8 +14873,8 @@ function formatToolSummary(toolName, input) {
|
|
|
14773
14873
|
return String(input.path || ".");
|
|
14774
14874
|
case "search_files": {
|
|
14775
14875
|
const pattern = String(input.pattern || "");
|
|
14776
|
-
const
|
|
14777
|
-
return `"${pattern}"${
|
|
14876
|
+
const path37 = input.path ? ` in ${input.path}` : "";
|
|
14877
|
+
return `"${pattern}"${path37}`;
|
|
14778
14878
|
}
|
|
14779
14879
|
case "bash_exec": {
|
|
14780
14880
|
const cmd = String(input.command || "");
|
|
@@ -15985,6 +16085,56 @@ var imageCommand = {
|
|
|
15985
16085
|
return false;
|
|
15986
16086
|
}
|
|
15987
16087
|
};
|
|
16088
|
+
var tutorialCommand = {
|
|
16089
|
+
name: "tutorial",
|
|
16090
|
+
aliases: ["tut", "learn"],
|
|
16091
|
+
description: "Quick guide to using Coco",
|
|
16092
|
+
usage: "/tutorial",
|
|
16093
|
+
async execute() {
|
|
16094
|
+
console.log(chalk12.cyan.bold("\n\u2550\u2550\u2550 Coco Quick Tutorial \u2550\u2550\u2550\n"));
|
|
16095
|
+
const steps = [
|
|
16096
|
+
{
|
|
16097
|
+
step: "1",
|
|
16098
|
+
title: "Ask Coco anything",
|
|
16099
|
+
desc: 'Just type what you need: "Create a REST API with authentication"'
|
|
16100
|
+
},
|
|
16101
|
+
{
|
|
16102
|
+
step: "2",
|
|
16103
|
+
title: "Coco works autonomously",
|
|
16104
|
+
desc: "Reads your project, writes code, runs tests, and iterates until quality passes"
|
|
16105
|
+
},
|
|
16106
|
+
{
|
|
16107
|
+
step: "3",
|
|
16108
|
+
title: "Enable quality mode",
|
|
16109
|
+
desc: "Type /coco to enable auto-iteration: test \u2192 analyze \u2192 fix \u2192 repeat until score \u2265 85"
|
|
16110
|
+
},
|
|
16111
|
+
{
|
|
16112
|
+
step: "4",
|
|
16113
|
+
title: "Review changes",
|
|
16114
|
+
desc: "Use /diff to see what changed, /status for project state"
|
|
16115
|
+
},
|
|
16116
|
+
{
|
|
16117
|
+
step: "5",
|
|
16118
|
+
title: "Save your work",
|
|
16119
|
+
desc: "Use /commit to commit changes with a descriptive message"
|
|
16120
|
+
}
|
|
16121
|
+
];
|
|
16122
|
+
for (const { step, title, desc } of steps) {
|
|
16123
|
+
console.log(` ${chalk12.magenta.bold(step)}. ${chalk12.bold(title)}`);
|
|
16124
|
+
console.log(` ${chalk12.dim(desc)}`);
|
|
16125
|
+
console.log();
|
|
16126
|
+
}
|
|
16127
|
+
console.log(chalk12.bold("Useful commands:"));
|
|
16128
|
+
console.log(
|
|
16129
|
+
` ${chalk12.yellow("/coco")} ${chalk12.dim("Toggle quality mode (auto-iteration)")}`
|
|
16130
|
+
);
|
|
16131
|
+
console.log(` ${chalk12.yellow("/init")} ${chalk12.dim("Initialize a new project")}`);
|
|
16132
|
+
console.log(` ${chalk12.yellow("/help")} ${chalk12.dim("See all available commands")}`);
|
|
16133
|
+
console.log(` ${chalk12.yellow("/help tools")} ${chalk12.dim("See available agent tools")}`);
|
|
16134
|
+
console.log();
|
|
16135
|
+
return false;
|
|
16136
|
+
}
|
|
16137
|
+
};
|
|
15988
16138
|
|
|
15989
16139
|
// src/cli/repl/commands/index.ts
|
|
15990
16140
|
var commands = [
|
|
@@ -16014,7 +16164,8 @@ var commands = [
|
|
|
16014
16164
|
allowPathCommand,
|
|
16015
16165
|
permissionsCommand,
|
|
16016
16166
|
cocoCommand,
|
|
16017
|
-
imageCommand
|
|
16167
|
+
imageCommand,
|
|
16168
|
+
tutorialCommand
|
|
16018
16169
|
];
|
|
16019
16170
|
function isSlashCommand(input) {
|
|
16020
16171
|
return input.startsWith("/");
|
|
@@ -16101,21 +16252,25 @@ function createInputHandler(_session) {
|
|
|
16101
16252
|
let historyIndex = -1;
|
|
16102
16253
|
let tempLine = "";
|
|
16103
16254
|
let lastMenuLines = 0;
|
|
16255
|
+
let lastCursorRow = 0;
|
|
16256
|
+
let isFirstRender = true;
|
|
16104
16257
|
let isPasting = false;
|
|
16105
16258
|
let pasteBuffer = "";
|
|
16106
16259
|
let isReadingClipboard = false;
|
|
16107
16260
|
const getPrompt = () => {
|
|
16261
|
+
const imageIndicator = hasPendingImage() ? chalk12.cyan(" \u{1F4CE} 1 image") : "";
|
|
16262
|
+
const imageIndicatorLen = hasPendingImage() ? 10 : 0;
|
|
16108
16263
|
if (isCocoMode()) {
|
|
16109
16264
|
return {
|
|
16110
|
-
str: "\u{1F965} " + chalk12.magenta("[coco]") + " \u203A ",
|
|
16111
|
-
// 🥥=2 + space=1 + [coco]=6 + space=1 + ›=1 + space=1 = 12
|
|
16112
|
-
visualLen: 12
|
|
16265
|
+
str: "\u{1F965} " + chalk12.magenta("[coco]") + " \u203A " + imageIndicator,
|
|
16266
|
+
// 🥥=2 + space=1 + [coco]=6 + space=1 + ›=1 + space=1 = 12 + image indicator
|
|
16267
|
+
visualLen: 12 + imageIndicatorLen
|
|
16113
16268
|
};
|
|
16114
16269
|
}
|
|
16115
16270
|
return {
|
|
16116
|
-
str: chalk12.green("\u{1F965} \u203A "),
|
|
16117
|
-
// 🥥=2 + space=1 + ›=1 + space=1 = 5
|
|
16118
|
-
visualLen: 5
|
|
16271
|
+
str: chalk12.green("\u{1F965} \u203A ") + imageIndicator,
|
|
16272
|
+
// 🥥=2 + space=1 + ›=1 + space=1 = 5 + image indicator
|
|
16273
|
+
visualLen: 5 + imageIndicatorLen
|
|
16119
16274
|
};
|
|
16120
16275
|
};
|
|
16121
16276
|
const MAX_ROWS = 8;
|
|
@@ -16128,9 +16283,19 @@ function createInputHandler(_session) {
|
|
|
16128
16283
|
return Math.max(1, Math.min(3, cols));
|
|
16129
16284
|
}
|
|
16130
16285
|
function render() {
|
|
16131
|
-
process.stdout.
|
|
16286
|
+
const termCols = process.stdout.columns || 80;
|
|
16132
16287
|
const prompt = getPrompt();
|
|
16133
|
-
|
|
16288
|
+
if (!isFirstRender) {
|
|
16289
|
+
const linesToGoUp = lastCursorRow + 1;
|
|
16290
|
+
if (linesToGoUp > 0) {
|
|
16291
|
+
process.stdout.write(ansiEscapes3.cursorUp(linesToGoUp));
|
|
16292
|
+
}
|
|
16293
|
+
}
|
|
16294
|
+
isFirstRender = false;
|
|
16295
|
+
process.stdout.write("\r" + ansiEscapes3.eraseDown);
|
|
16296
|
+
const separator = chalk12.dim("\u2500".repeat(termCols));
|
|
16297
|
+
let output = separator + "\n";
|
|
16298
|
+
output += prompt.str + currentLine;
|
|
16134
16299
|
completions = findCompletions(currentLine);
|
|
16135
16300
|
selectedCompletion = Math.min(selectedCompletion, Math.max(0, completions.length - 1));
|
|
16136
16301
|
if (cursorPos === currentLine.length && completions.length > 0 && completions[selectedCompletion]) {
|
|
@@ -16183,15 +16348,26 @@ function createInputHandler(_session) {
|
|
|
16183
16348
|
for (let i = 0; i < BOTTOM_MARGIN; i++) {
|
|
16184
16349
|
output += "\n";
|
|
16185
16350
|
}
|
|
16186
|
-
output += ansiEscapes3.cursorUp(lastMenuLines + BOTTOM_MARGIN);
|
|
16351
|
+
output += ansiEscapes3.cursorUp(lastMenuLines + BOTTOM_MARGIN + 1);
|
|
16187
16352
|
} else {
|
|
16188
16353
|
lastMenuLines = 0;
|
|
16189
16354
|
for (let i = 0; i < BOTTOM_MARGIN; i++) {
|
|
16190
16355
|
output += "\n";
|
|
16191
16356
|
}
|
|
16192
|
-
output += ansiEscapes3.cursorUp(BOTTOM_MARGIN);
|
|
16357
|
+
output += ansiEscapes3.cursorUp(BOTTOM_MARGIN + 1);
|
|
16358
|
+
}
|
|
16359
|
+
output += ansiEscapes3.cursorDown(1);
|
|
16360
|
+
const cursorAbsolutePos = prompt.visualLen + cursorPos;
|
|
16361
|
+
const finalLine = Math.floor(cursorAbsolutePos / termCols);
|
|
16362
|
+
const finalCol = cursorAbsolutePos % termCols;
|
|
16363
|
+
output += "\r";
|
|
16364
|
+
if (finalLine > 0) {
|
|
16365
|
+
output += ansiEscapes3.cursorDown(finalLine);
|
|
16193
16366
|
}
|
|
16194
|
-
|
|
16367
|
+
if (finalCol > 0) {
|
|
16368
|
+
output += ansiEscapes3.cursorForward(finalCol);
|
|
16369
|
+
}
|
|
16370
|
+
lastCursorRow = finalLine;
|
|
16195
16371
|
process.stdout.write(output);
|
|
16196
16372
|
}
|
|
16197
16373
|
function clearMenu() {
|
|
@@ -16218,15 +16394,20 @@ function createInputHandler(_session) {
|
|
|
16218
16394
|
historyIndex = -1;
|
|
16219
16395
|
tempLine = "";
|
|
16220
16396
|
lastMenuLines = 0;
|
|
16397
|
+
lastCursorRow = 0;
|
|
16398
|
+
isFirstRender = true;
|
|
16221
16399
|
render();
|
|
16222
16400
|
if (process.stdin.isTTY) {
|
|
16223
16401
|
process.stdin.setRawMode(true);
|
|
16224
16402
|
}
|
|
16225
16403
|
process.stdin.resume();
|
|
16226
16404
|
process.stdout.write("\x1B[?2004h");
|
|
16405
|
+
const onResize = () => render();
|
|
16406
|
+
process.stdout.on("resize", onResize);
|
|
16227
16407
|
const cleanup = () => {
|
|
16228
16408
|
process.stdout.write("\x1B[?2004l");
|
|
16229
16409
|
process.stdin.removeListener("data", onData);
|
|
16410
|
+
process.stdout.removeListener("resize", onResize);
|
|
16230
16411
|
if (process.stdin.isTTY) {
|
|
16231
16412
|
process.stdin.setRawMode(false);
|
|
16232
16413
|
}
|
|
@@ -16339,15 +16520,14 @@ function createInputHandler(_session) {
|
|
|
16339
16520
|
process.stdout.write(
|
|
16340
16521
|
chalk12.green(" \u2713 Image captured") + chalk12.dim(` (${sizeKB} KB)`) + chalk12.dim(` \u2014 "${truncatedPrompt}"`)
|
|
16341
16522
|
);
|
|
16342
|
-
|
|
16343
|
-
|
|
16344
|
-
const result = currentLine.trim();
|
|
16345
|
-
if (result) {
|
|
16346
|
-
sessionHistory.push(result);
|
|
16347
|
-
}
|
|
16348
|
-
resolve2(result || "");
|
|
16349
|
-
}).catch(() => {
|
|
16523
|
+
setTimeout(() => render(), 1200);
|
|
16524
|
+
}).catch((err) => {
|
|
16350
16525
|
isReadingClipboard = false;
|
|
16526
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
16527
|
+
if (msg.includes("color space")) {
|
|
16528
|
+
render();
|
|
16529
|
+
return;
|
|
16530
|
+
}
|
|
16351
16531
|
render();
|
|
16352
16532
|
});
|
|
16353
16533
|
return;
|
|
@@ -16357,7 +16537,9 @@ function createInputHandler(_session) {
|
|
|
16357
16537
|
currentLine = completions[selectedCompletion].cmd;
|
|
16358
16538
|
}
|
|
16359
16539
|
cleanup();
|
|
16540
|
+
const termWidth = process.stdout.columns || 80;
|
|
16360
16541
|
console.log();
|
|
16542
|
+
console.log(chalk12.dim("\u2500".repeat(termWidth)));
|
|
16361
16543
|
const result = currentLine.trim();
|
|
16362
16544
|
if (result) {
|
|
16363
16545
|
sessionHistory.push(result);
|
|
@@ -17396,7 +17578,8 @@ async function promptEditCommand(originalCommand) {
|
|
|
17396
17578
|
console.log();
|
|
17397
17579
|
console.log(chalk12.dim(" Edit command (or press Enter to cancel):"));
|
|
17398
17580
|
console.log(chalk12.cyan(` Current: ${originalCommand}`));
|
|
17399
|
-
|
|
17581
|
+
const wasRaw = process.stdin.isTTY ? process.stdin.isRaw : false;
|
|
17582
|
+
if (process.stdin.isTTY && wasRaw) {
|
|
17400
17583
|
process.stdin.setRawMode(false);
|
|
17401
17584
|
}
|
|
17402
17585
|
const rl = readline2.createInterface({
|
|
@@ -17409,6 +17592,9 @@ async function promptEditCommand(originalCommand) {
|
|
|
17409
17592
|
return trimmed || null;
|
|
17410
17593
|
} finally {
|
|
17411
17594
|
rl.close();
|
|
17595
|
+
if (process.stdin.isTTY && wasRaw) {
|
|
17596
|
+
process.stdin.setRawMode(true);
|
|
17597
|
+
}
|
|
17412
17598
|
}
|
|
17413
17599
|
}
|
|
17414
17600
|
async function confirmToolExecution(toolCall) {
|
|
@@ -18190,8 +18376,8 @@ function isPathAllowed(filePath, operation) {
|
|
|
18190
18376
|
if (absolute.startsWith(normalizedHome) && !absolute.startsWith(normalizedCwd)) {
|
|
18191
18377
|
if (isWithinAllowedPath(absolute, operation)) ; else if (operation === "read") {
|
|
18192
18378
|
const allowedHomeReads = [".gitconfig", ".zshrc", ".bashrc"];
|
|
18193
|
-
const
|
|
18194
|
-
if (!allowedHomeReads.includes(
|
|
18379
|
+
const basename3 = path20__default.basename(absolute);
|
|
18380
|
+
if (!allowedHomeReads.includes(basename3)) {
|
|
18195
18381
|
const targetDir = path20__default.dirname(absolute);
|
|
18196
18382
|
return {
|
|
18197
18383
|
allowed: false,
|
|
@@ -18208,12 +18394,12 @@ function isPathAllowed(filePath, operation) {
|
|
|
18208
18394
|
}
|
|
18209
18395
|
}
|
|
18210
18396
|
if (operation === "write" || operation === "delete") {
|
|
18211
|
-
const
|
|
18397
|
+
const basename3 = path20__default.basename(absolute);
|
|
18212
18398
|
for (const pattern of SENSITIVE_PATTERNS) {
|
|
18213
|
-
if (pattern.test(
|
|
18399
|
+
if (pattern.test(basename3)) {
|
|
18214
18400
|
return {
|
|
18215
18401
|
allowed: false,
|
|
18216
|
-
reason: `Operation on sensitive file '${
|
|
18402
|
+
reason: `Operation on sensitive file '${basename3}' requires explicit confirmation`
|
|
18217
18403
|
};
|
|
18218
18404
|
}
|
|
18219
18405
|
}
|
|
@@ -19471,59 +19657,322 @@ var simpleAutoCommitTool = defineTool({
|
|
|
19471
19657
|
}
|
|
19472
19658
|
});
|
|
19473
19659
|
var gitSimpleTools = [checkProtectedBranchTool, simpleAutoCommitTool];
|
|
19660
|
+
|
|
19661
|
+
// src/agents/provider-bridge.ts
|
|
19662
|
+
var agentProvider = null;
|
|
19663
|
+
var agentToolRegistry = null;
|
|
19664
|
+
function setAgentProvider(provider) {
|
|
19665
|
+
agentProvider = provider;
|
|
19666
|
+
}
|
|
19667
|
+
function getAgentProvider() {
|
|
19668
|
+
return agentProvider;
|
|
19669
|
+
}
|
|
19670
|
+
function setAgentToolRegistry(registry) {
|
|
19671
|
+
agentToolRegistry = registry;
|
|
19672
|
+
}
|
|
19673
|
+
function getAgentToolRegistry() {
|
|
19674
|
+
return agentToolRegistry;
|
|
19675
|
+
}
|
|
19676
|
+
|
|
19677
|
+
// src/agents/executor.ts
|
|
19678
|
+
var AgentExecutor = class {
|
|
19679
|
+
constructor(provider, toolRegistry) {
|
|
19680
|
+
this.provider = provider;
|
|
19681
|
+
this.toolRegistry = toolRegistry;
|
|
19682
|
+
}
|
|
19683
|
+
/**
|
|
19684
|
+
* Execute an agent on a task with multi-turn tool use
|
|
19685
|
+
*/
|
|
19686
|
+
async execute(agent, task) {
|
|
19687
|
+
const startTime = Date.now();
|
|
19688
|
+
const toolsUsed = /* @__PURE__ */ new Set();
|
|
19689
|
+
const messages = [
|
|
19690
|
+
{
|
|
19691
|
+
role: "user",
|
|
19692
|
+
content: this.buildTaskPrompt(task)
|
|
19693
|
+
}
|
|
19694
|
+
];
|
|
19695
|
+
const agentToolDefs = this.getToolDefinitionsForAgent(agent.allowedTools);
|
|
19696
|
+
let turn = 0;
|
|
19697
|
+
let totalTokens = 0;
|
|
19698
|
+
while (turn < agent.maxTurns) {
|
|
19699
|
+
turn++;
|
|
19700
|
+
try {
|
|
19701
|
+
const response = await this.provider.chatWithTools(messages, {
|
|
19702
|
+
tools: agentToolDefs,
|
|
19703
|
+
system: agent.systemPrompt
|
|
19704
|
+
});
|
|
19705
|
+
const usage = response.usage;
|
|
19706
|
+
totalTokens += (usage?.inputTokens || 0) + (usage?.outputTokens || 0);
|
|
19707
|
+
if (response.stopReason !== "tool_use" || response.toolCalls.length === 0) {
|
|
19708
|
+
return {
|
|
19709
|
+
output: response.content,
|
|
19710
|
+
success: true,
|
|
19711
|
+
turns: turn,
|
|
19712
|
+
toolsUsed: Array.from(toolsUsed),
|
|
19713
|
+
tokensUsed: totalTokens,
|
|
19714
|
+
duration: Date.now() - startTime
|
|
19715
|
+
};
|
|
19716
|
+
}
|
|
19717
|
+
const assistantContent = [];
|
|
19718
|
+
if (response.content) {
|
|
19719
|
+
assistantContent.push({
|
|
19720
|
+
type: "text",
|
|
19721
|
+
text: response.content
|
|
19722
|
+
});
|
|
19723
|
+
}
|
|
19724
|
+
for (const toolCall of response.toolCalls) {
|
|
19725
|
+
assistantContent.push({
|
|
19726
|
+
type: "tool_use",
|
|
19727
|
+
id: toolCall.id,
|
|
19728
|
+
name: toolCall.name,
|
|
19729
|
+
input: toolCall.input
|
|
19730
|
+
});
|
|
19731
|
+
}
|
|
19732
|
+
messages.push({
|
|
19733
|
+
role: "assistant",
|
|
19734
|
+
content: assistantContent
|
|
19735
|
+
});
|
|
19736
|
+
const toolResults = [];
|
|
19737
|
+
for (const toolCall of response.toolCalls) {
|
|
19738
|
+
toolsUsed.add(toolCall.name);
|
|
19739
|
+
try {
|
|
19740
|
+
const result = await this.toolRegistry.execute(toolCall.name, toolCall.input);
|
|
19741
|
+
toolResults.push({
|
|
19742
|
+
type: "tool_result",
|
|
19743
|
+
tool_use_id: toolCall.id,
|
|
19744
|
+
content: result.success ? JSON.stringify(result.data) : `Error: ${result.error}`,
|
|
19745
|
+
is_error: !result.success
|
|
19746
|
+
});
|
|
19747
|
+
} catch (error) {
|
|
19748
|
+
toolResults.push({
|
|
19749
|
+
type: "tool_result",
|
|
19750
|
+
tool_use_id: toolCall.id,
|
|
19751
|
+
content: `Tool execution error: ${error instanceof Error ? error.message : String(error)}`,
|
|
19752
|
+
is_error: true
|
|
19753
|
+
});
|
|
19754
|
+
}
|
|
19755
|
+
}
|
|
19756
|
+
messages.push({
|
|
19757
|
+
role: "user",
|
|
19758
|
+
content: toolResults
|
|
19759
|
+
});
|
|
19760
|
+
} catch (error) {
|
|
19761
|
+
return {
|
|
19762
|
+
output: `Agent error on turn ${turn}: ${error instanceof Error ? error.message : String(error)}`,
|
|
19763
|
+
success: false,
|
|
19764
|
+
turns: turn,
|
|
19765
|
+
toolsUsed: Array.from(toolsUsed),
|
|
19766
|
+
tokensUsed: totalTokens,
|
|
19767
|
+
duration: Date.now() - startTime
|
|
19768
|
+
};
|
|
19769
|
+
}
|
|
19770
|
+
}
|
|
19771
|
+
return {
|
|
19772
|
+
output: "Agent reached maximum turns without completing task",
|
|
19773
|
+
success: false,
|
|
19774
|
+
turns: turn,
|
|
19775
|
+
toolsUsed: Array.from(toolsUsed),
|
|
19776
|
+
tokensUsed: totalTokens,
|
|
19777
|
+
duration: Date.now() - startTime
|
|
19778
|
+
};
|
|
19779
|
+
}
|
|
19780
|
+
/**
|
|
19781
|
+
* Build task prompt with context
|
|
19782
|
+
*/
|
|
19783
|
+
buildTaskPrompt(task) {
|
|
19784
|
+
let prompt = `Task: ${task.description}
|
|
19785
|
+
`;
|
|
19786
|
+
if (task.context && Object.keys(task.context).length > 0) {
|
|
19787
|
+
prompt += `
|
|
19788
|
+
Context:
|
|
19789
|
+
${JSON.stringify(task.context, null, 2)}
|
|
19790
|
+
`;
|
|
19791
|
+
}
|
|
19792
|
+
prompt += `
|
|
19793
|
+
Complete this task autonomously using the available tools. When done, provide a summary of what you accomplished.`;
|
|
19794
|
+
return prompt;
|
|
19795
|
+
}
|
|
19796
|
+
/**
|
|
19797
|
+
* Get tool definitions filtered for this agent's allowed tools
|
|
19798
|
+
*/
|
|
19799
|
+
getToolDefinitionsForAgent(allowedToolNames) {
|
|
19800
|
+
const allDefs = this.toolRegistry.getToolDefinitionsForLLM();
|
|
19801
|
+
if (allowedToolNames.length === 0) return allDefs;
|
|
19802
|
+
return allDefs.filter((def) => allowedToolNames.includes(def.name));
|
|
19803
|
+
}
|
|
19804
|
+
};
|
|
19805
|
+
var AGENT_ROLES = {
|
|
19806
|
+
researcher: {
|
|
19807
|
+
role: "researcher",
|
|
19808
|
+
systemPrompt: `You are a code researcher agent. Your role is to:
|
|
19809
|
+
- Explore and understand existing codebases
|
|
19810
|
+
- Find relevant code patterns and examples
|
|
19811
|
+
- Identify dependencies and relationships
|
|
19812
|
+
- Document your findings clearly
|
|
19813
|
+
|
|
19814
|
+
Use tools to search, read files, and analyze code structure.`,
|
|
19815
|
+
allowedTools: ["read_file", "grep", "find_in_file", "glob", "codebase_map"]
|
|
19816
|
+
},
|
|
19817
|
+
coder: {
|
|
19818
|
+
role: "coder",
|
|
19819
|
+
systemPrompt: `You are a code generation agent. Your role is to:
|
|
19820
|
+
- Write high-quality, production-ready code
|
|
19821
|
+
- Follow best practices and coding standards
|
|
19822
|
+
- Ensure code is syntactically valid
|
|
19823
|
+
- Write clean, maintainable code
|
|
19824
|
+
|
|
19825
|
+
Use tools to read existing code, write new files, and validate syntax.`,
|
|
19826
|
+
allowedTools: ["read_file", "write_file", "edit_file", "bash_exec", "validateCode"]
|
|
19827
|
+
},
|
|
19828
|
+
tester: {
|
|
19829
|
+
role: "tester",
|
|
19830
|
+
systemPrompt: `You are a test generation agent. Your role is to:
|
|
19831
|
+
- Write comprehensive test suites
|
|
19832
|
+
- Achieve high code coverage
|
|
19833
|
+
- Test edge cases and error conditions
|
|
19834
|
+
- Ensure tests are reliable and maintainable
|
|
19835
|
+
|
|
19836
|
+
Use tools to read code, write tests, and run them.`,
|
|
19837
|
+
allowedTools: ["read_file", "write_file", "run_tests", "get_coverage", "run_test_file"]
|
|
19838
|
+
},
|
|
19839
|
+
reviewer: {
|
|
19840
|
+
role: "reviewer",
|
|
19841
|
+
systemPrompt: `You are a code review agent. Your role is to:
|
|
19842
|
+
- Identify code quality issues
|
|
19843
|
+
- Check for security vulnerabilities
|
|
19844
|
+
- Ensure best practices are followed
|
|
19845
|
+
- Provide actionable feedback
|
|
19846
|
+
|
|
19847
|
+
Use tools to read and analyze code quality.`,
|
|
19848
|
+
allowedTools: ["read_file", "calculate_quality", "analyze_complexity", "grep"]
|
|
19849
|
+
},
|
|
19850
|
+
optimizer: {
|
|
19851
|
+
role: "optimizer",
|
|
19852
|
+
systemPrompt: `You are a code optimization agent. Your role is to:
|
|
19853
|
+
- Reduce code complexity
|
|
19854
|
+
- Eliminate duplication
|
|
19855
|
+
- Improve performance
|
|
19856
|
+
- Refactor for maintainability
|
|
19857
|
+
|
|
19858
|
+
Use tools to analyze and improve code.`,
|
|
19859
|
+
allowedTools: ["read_file", "write_file", "edit_file", "analyze_complexity", "grep"]
|
|
19860
|
+
},
|
|
19861
|
+
planner: {
|
|
19862
|
+
role: "planner",
|
|
19863
|
+
systemPrompt: `You are a task planning agent. Your role is to:
|
|
19864
|
+
- Break down complex tasks into subtasks
|
|
19865
|
+
- Identify dependencies between tasks
|
|
19866
|
+
- Estimate complexity and effort
|
|
19867
|
+
- Create actionable plans
|
|
19868
|
+
|
|
19869
|
+
Use tools to analyze requirements and explore the codebase.`,
|
|
19870
|
+
allowedTools: ["read_file", "grep", "glob", "codebase_map"]
|
|
19871
|
+
}
|
|
19872
|
+
};
|
|
19873
|
+
|
|
19874
|
+
// src/tools/simple-agent.ts
|
|
19474
19875
|
var SpawnSimpleAgentSchema = z.object({
|
|
19475
19876
|
task: z.string().describe("Task description for the sub-agent"),
|
|
19476
|
-
context: z.string().optional().describe("Additional context or instructions for the agent")
|
|
19877
|
+
context: z.string().optional().describe("Additional context or instructions for the agent"),
|
|
19878
|
+
role: z.enum(["researcher", "coder", "tester", "reviewer", "optimizer", "planner"]).default("coder").describe("Agent role to use"),
|
|
19879
|
+
maxTurns: z.number().default(10).describe("Maximum tool-use turns for the agent")
|
|
19477
19880
|
});
|
|
19478
19881
|
var spawnSimpleAgentTool = defineTool({
|
|
19479
19882
|
name: "spawnSimpleAgent",
|
|
19480
|
-
description: `Spawn a sub-agent to handle a specific task.
|
|
19883
|
+
description: `Spawn a sub-agent to handle a specific task with real LLM tool-use execution.
|
|
19481
19884
|
|
|
19482
19885
|
Use this when you need to:
|
|
19483
19886
|
- Delegate a focused task to another agent
|
|
19484
19887
|
- Get a second opinion or alternative approach
|
|
19485
19888
|
- Handle multiple independent subtasks
|
|
19486
19889
|
|
|
19487
|
-
The sub-agent will work on the task
|
|
19890
|
+
The sub-agent will work on the task autonomously using available tools.
|
|
19488
19891
|
|
|
19489
19892
|
Example: "Write unit tests for the authentication module"`,
|
|
19490
19893
|
category: "build",
|
|
19491
19894
|
parameters: SpawnSimpleAgentSchema,
|
|
19492
19895
|
async execute(input) {
|
|
19493
19896
|
const typedInput = input;
|
|
19897
|
+
const provider = getAgentProvider();
|
|
19898
|
+
const toolRegistry = getAgentToolRegistry();
|
|
19899
|
+
if (!provider || !toolRegistry) {
|
|
19900
|
+
const agentId2 = `agent-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
19901
|
+
return {
|
|
19902
|
+
stdout: JSON.stringify({
|
|
19903
|
+
agentId: agentId2,
|
|
19904
|
+
status: "unavailable",
|
|
19905
|
+
task: typedInput.task,
|
|
19906
|
+
message: "Agent provider not initialized. Call setAgentProvider() during orchestrator startup.",
|
|
19907
|
+
success: false
|
|
19908
|
+
}),
|
|
19909
|
+
stderr: "",
|
|
19910
|
+
exitCode: 1,
|
|
19911
|
+
duration: 0
|
|
19912
|
+
};
|
|
19913
|
+
}
|
|
19494
19914
|
const agentId = `agent-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
19915
|
+
const executor = new AgentExecutor(provider, toolRegistry);
|
|
19916
|
+
const roleDef = AGENT_ROLES[typedInput.role];
|
|
19917
|
+
if (!roleDef) {
|
|
19918
|
+
return {
|
|
19919
|
+
stdout: JSON.stringify({
|
|
19920
|
+
agentId,
|
|
19921
|
+
status: "error",
|
|
19922
|
+
message: `Unknown agent role: ${typedInput.role}`,
|
|
19923
|
+
success: false
|
|
19924
|
+
}),
|
|
19925
|
+
stderr: "",
|
|
19926
|
+
exitCode: 1,
|
|
19927
|
+
duration: 0
|
|
19928
|
+
};
|
|
19929
|
+
}
|
|
19930
|
+
const agentDef = { ...roleDef, maxTurns: typedInput.maxTurns };
|
|
19931
|
+
const result = await executor.execute(agentDef, {
|
|
19932
|
+
id: agentId,
|
|
19933
|
+
description: typedInput.task,
|
|
19934
|
+
context: typedInput.context ? { userContext: typedInput.context } : void 0
|
|
19935
|
+
});
|
|
19495
19936
|
return {
|
|
19496
19937
|
stdout: JSON.stringify({
|
|
19497
19938
|
agentId,
|
|
19498
|
-
status: "
|
|
19939
|
+
status: result.success ? "completed" : "failed",
|
|
19499
19940
|
task: typedInput.task,
|
|
19500
|
-
|
|
19501
|
-
|
|
19502
|
-
|
|
19941
|
+
output: result.output,
|
|
19942
|
+
success: result.success,
|
|
19943
|
+
turns: result.turns,
|
|
19944
|
+
toolsUsed: result.toolsUsed,
|
|
19945
|
+
tokensUsed: result.tokensUsed,
|
|
19946
|
+
duration: result.duration
|
|
19503
19947
|
}),
|
|
19504
19948
|
stderr: "",
|
|
19505
|
-
exitCode: 0,
|
|
19506
|
-
duration:
|
|
19949
|
+
exitCode: result.success ? 0 : 1,
|
|
19950
|
+
duration: result.duration
|
|
19507
19951
|
};
|
|
19508
19952
|
}
|
|
19509
19953
|
});
|
|
19510
19954
|
var checkAgentCapabilityTool = defineTool({
|
|
19511
19955
|
name: "checkAgentCapability",
|
|
19512
|
-
description: "Check if multi-agent capability is available",
|
|
19956
|
+
description: "Check if multi-agent capability is available and configured",
|
|
19513
19957
|
category: "build",
|
|
19514
19958
|
parameters: z.object({}),
|
|
19515
19959
|
async execute() {
|
|
19960
|
+
const provider = getAgentProvider();
|
|
19961
|
+
const toolRegistry = getAgentToolRegistry();
|
|
19962
|
+
const isReady = provider !== null && toolRegistry !== null;
|
|
19516
19963
|
return {
|
|
19517
19964
|
stdout: JSON.stringify({
|
|
19518
19965
|
multiAgentSupported: true,
|
|
19519
|
-
|
|
19520
|
-
|
|
19966
|
+
providerConfigured: provider !== null,
|
|
19967
|
+
toolRegistryConfigured: toolRegistry !== null,
|
|
19968
|
+
ready: isReady,
|
|
19969
|
+
availableRoles: Object.keys(AGENT_ROLES),
|
|
19521
19970
|
features: {
|
|
19522
|
-
taskDelegation: "
|
|
19523
|
-
parallelSpawn: "requires
|
|
19524
|
-
|
|
19971
|
+
taskDelegation: isReady ? "ready" : "requires provider initialization",
|
|
19972
|
+
parallelSpawn: isReady ? "ready" : "requires provider initialization",
|
|
19973
|
+
multiTurnToolUse: isReady ? "ready" : "requires provider initialization"
|
|
19525
19974
|
},
|
|
19526
|
-
status: "Multi-agent
|
|
19975
|
+
status: isReady ? "Multi-agent system is ready with real LLM tool-use execution." : "Provider not initialized. Call setAgentProvider() during startup."
|
|
19527
19976
|
}),
|
|
19528
19977
|
stderr: "",
|
|
19529
19978
|
exitCode: 0,
|
|
@@ -19857,10 +20306,10 @@ var CoverageAnalyzer = class {
|
|
|
19857
20306
|
join(this.projectPath, ".coverage", "coverage-summary.json"),
|
|
19858
20307
|
join(this.projectPath, "coverage", "lcov-report", "coverage-summary.json")
|
|
19859
20308
|
];
|
|
19860
|
-
for (const
|
|
20309
|
+
for (const path37 of possiblePaths) {
|
|
19861
20310
|
try {
|
|
19862
|
-
await access(
|
|
19863
|
-
const content = await readFile(
|
|
20311
|
+
await access(path37, constants.R_OK);
|
|
20312
|
+
const content = await readFile(path37, "utf-8");
|
|
19864
20313
|
const report = JSON.parse(content);
|
|
19865
20314
|
return parseCoverageSummary(report);
|
|
19866
20315
|
} catch {
|
|
@@ -20514,132 +20963,1635 @@ var DuplicationAnalyzer = class {
|
|
|
20514
20963
|
});
|
|
20515
20964
|
}
|
|
20516
20965
|
};
|
|
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) {
|
|
20966
|
+
var execAsync2 = promisify(exec);
|
|
20967
|
+
var BuildVerifier = class {
|
|
20968
|
+
projectPath;
|
|
20969
|
+
constructor(projectPath) {
|
|
20546
20970
|
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
20971
|
}
|
|
20552
|
-
coverageAnalyzer;
|
|
20553
|
-
securityScanner;
|
|
20554
|
-
complexityAnalyzer;
|
|
20555
|
-
duplicationAnalyzer;
|
|
20556
20972
|
/**
|
|
20557
|
-
*
|
|
20558
|
-
* Returns QualityScores with 0% hardcoded values (5/12 dimensions are real)
|
|
20973
|
+
* Verify that the project builds successfully
|
|
20559
20974
|
*/
|
|
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
|
-
evaluatedAt: /* @__PURE__ */ new Date(),
|
|
20609
|
-
evaluationDurationMs: performance.now() - startTime
|
|
20610
|
-
};
|
|
20611
|
-
const issues = this.generateIssues(
|
|
20612
|
-
securityResult.vulnerabilities,
|
|
20613
|
-
complexityResult,
|
|
20614
|
-
duplicationResult
|
|
20615
|
-
);
|
|
20616
|
-
const suggestions = this.generateSuggestions(dimensions);
|
|
20617
|
-
const meetsMinimum = scores.overall >= DEFAULT_QUALITY_THRESHOLDS.minimum.overall && dimensions.testCoverage >= DEFAULT_QUALITY_THRESHOLDS.minimum.testCoverage && dimensions.security >= DEFAULT_QUALITY_THRESHOLDS.minimum.security;
|
|
20618
|
-
const meetsTarget = scores.overall >= DEFAULT_QUALITY_THRESHOLDS.target.overall && dimensions.testCoverage >= DEFAULT_QUALITY_THRESHOLDS.target.testCoverage;
|
|
20619
|
-
return {
|
|
20620
|
-
scores,
|
|
20621
|
-
meetsMinimum,
|
|
20622
|
-
meetsTarget,
|
|
20623
|
-
converged: false,
|
|
20624
|
-
// Determined by iteration loop
|
|
20625
|
-
issues,
|
|
20626
|
-
suggestions
|
|
20627
|
-
};
|
|
20975
|
+
async verifyBuild() {
|
|
20976
|
+
const startTime = Date.now();
|
|
20977
|
+
try {
|
|
20978
|
+
const buildCommand2 = await this.detectBuildCommand();
|
|
20979
|
+
if (!buildCommand2) {
|
|
20980
|
+
return {
|
|
20981
|
+
success: true,
|
|
20982
|
+
errors: [],
|
|
20983
|
+
warnings: [],
|
|
20984
|
+
duration: Date.now() - startTime,
|
|
20985
|
+
stdout: "No build command detected",
|
|
20986
|
+
stderr: ""
|
|
20987
|
+
};
|
|
20988
|
+
}
|
|
20989
|
+
const { stdout, stderr } = await execAsync2(buildCommand2, {
|
|
20990
|
+
cwd: this.projectPath,
|
|
20991
|
+
timeout: 12e4,
|
|
20992
|
+
// 2 minutes
|
|
20993
|
+
maxBuffer: 10 * 1024 * 1024
|
|
20994
|
+
// 10MB
|
|
20995
|
+
});
|
|
20996
|
+
const errors = this.parseErrors(stdout + stderr);
|
|
20997
|
+
const warnings = this.parseWarnings(stdout + stderr);
|
|
20998
|
+
return {
|
|
20999
|
+
success: errors.length === 0,
|
|
21000
|
+
errors,
|
|
21001
|
+
warnings,
|
|
21002
|
+
duration: Date.now() - startTime,
|
|
21003
|
+
stdout,
|
|
21004
|
+
stderr
|
|
21005
|
+
};
|
|
21006
|
+
} catch (error) {
|
|
21007
|
+
const execError = error;
|
|
21008
|
+
const errors = this.parseErrors(
|
|
21009
|
+
(execError.stdout ?? "") + (execError.stderr ?? "") || (execError.message ?? "")
|
|
21010
|
+
);
|
|
21011
|
+
const warnings = this.parseWarnings(
|
|
21012
|
+
(execError.stdout ?? "") + (execError.stderr ?? "") || ""
|
|
21013
|
+
);
|
|
21014
|
+
return {
|
|
21015
|
+
success: false,
|
|
21016
|
+
errors,
|
|
21017
|
+
warnings,
|
|
21018
|
+
duration: Date.now() - startTime,
|
|
21019
|
+
stdout: execError.stdout || "",
|
|
21020
|
+
stderr: execError.stderr || execError.message || ""
|
|
21021
|
+
};
|
|
21022
|
+
}
|
|
20628
21023
|
}
|
|
20629
21024
|
/**
|
|
20630
|
-
*
|
|
20631
|
-
* Low complexity = high readability
|
|
21025
|
+
* Run TypeScript type checking only (faster than full build)
|
|
20632
21026
|
*/
|
|
20633
|
-
|
|
20634
|
-
|
|
20635
|
-
|
|
21027
|
+
async verifyTypes() {
|
|
21028
|
+
const startTime = Date.now();
|
|
21029
|
+
try {
|
|
21030
|
+
const hasTsConfig = await this.fileExists(path20.join(this.projectPath, "tsconfig.json"));
|
|
21031
|
+
if (!hasTsConfig) {
|
|
21032
|
+
return {
|
|
21033
|
+
success: true,
|
|
21034
|
+
errors: [],
|
|
21035
|
+
warnings: [],
|
|
21036
|
+
duration: Date.now() - startTime,
|
|
21037
|
+
stdout: "No tsconfig.json found",
|
|
21038
|
+
stderr: ""
|
|
21039
|
+
};
|
|
21040
|
+
}
|
|
21041
|
+
const { stdout, stderr } = await execAsync2("npx tsc --noEmit", {
|
|
21042
|
+
cwd: this.projectPath,
|
|
21043
|
+
timeout: 6e4,
|
|
21044
|
+
// 1 minute
|
|
21045
|
+
maxBuffer: 10 * 1024 * 1024
|
|
21046
|
+
});
|
|
21047
|
+
const errors = this.parseTypeScriptErrors(stdout + stderr);
|
|
21048
|
+
const warnings = this.parseTypeScriptWarnings(stdout + stderr);
|
|
21049
|
+
return {
|
|
21050
|
+
success: errors.length === 0,
|
|
21051
|
+
errors,
|
|
21052
|
+
warnings,
|
|
21053
|
+
duration: Date.now() - startTime,
|
|
21054
|
+
stdout,
|
|
21055
|
+
stderr
|
|
21056
|
+
};
|
|
21057
|
+
} catch (error) {
|
|
21058
|
+
const execError = error;
|
|
21059
|
+
const errors = this.parseTypeScriptErrors(
|
|
21060
|
+
(execError.stdout ?? "") + (execError.stderr ?? "") || (execError.message ?? "")
|
|
21061
|
+
);
|
|
21062
|
+
const warnings = this.parseTypeScriptWarnings(
|
|
21063
|
+
(execError.stdout ?? "") + (execError.stderr ?? "") || ""
|
|
21064
|
+
);
|
|
21065
|
+
return {
|
|
21066
|
+
success: false,
|
|
21067
|
+
errors,
|
|
21068
|
+
warnings,
|
|
21069
|
+
duration: Date.now() - startTime,
|
|
21070
|
+
stdout: execError.stdout || "",
|
|
21071
|
+
stderr: execError.stderr || execError.message || ""
|
|
21072
|
+
};
|
|
21073
|
+
}
|
|
20636
21074
|
}
|
|
20637
21075
|
/**
|
|
20638
|
-
*
|
|
21076
|
+
* Detect build command from package.json
|
|
20639
21077
|
*/
|
|
20640
|
-
|
|
20641
|
-
|
|
20642
|
-
|
|
21078
|
+
async detectBuildCommand() {
|
|
21079
|
+
try {
|
|
21080
|
+
const packageJsonPath = path20.join(this.projectPath, "package.json");
|
|
21081
|
+
const content = await fs22.readFile(packageJsonPath, "utf-8");
|
|
21082
|
+
const packageJson = JSON.parse(content);
|
|
21083
|
+
if (packageJson.scripts?.build) {
|
|
21084
|
+
return "npm run build";
|
|
21085
|
+
}
|
|
21086
|
+
if (packageJson.devDependencies?.typescript || packageJson.dependencies?.typescript) {
|
|
21087
|
+
return "npx tsc --noEmit";
|
|
21088
|
+
}
|
|
21089
|
+
return null;
|
|
21090
|
+
} catch {
|
|
21091
|
+
return null;
|
|
21092
|
+
}
|
|
21093
|
+
}
|
|
21094
|
+
/**
|
|
21095
|
+
* Parse errors from build output
|
|
21096
|
+
*/
|
|
21097
|
+
parseErrors(output) {
|
|
21098
|
+
const errors = [];
|
|
21099
|
+
const tsErrorRegex = /(.+?)\((\d+),(\d+)\): error (TS\d+): (.+)/g;
|
|
21100
|
+
let match;
|
|
21101
|
+
while ((match = tsErrorRegex.exec(output)) !== null) {
|
|
21102
|
+
errors.push({
|
|
21103
|
+
file: match[1] || "",
|
|
21104
|
+
line: parseInt(match[2] || "0", 10),
|
|
21105
|
+
column: parseInt(match[3] || "0", 10),
|
|
21106
|
+
code: match[4],
|
|
21107
|
+
message: match[5] || ""
|
|
21108
|
+
});
|
|
21109
|
+
}
|
|
21110
|
+
const eslintErrorRegex = /(.+?):(\d+):(\d+): (.+)/g;
|
|
21111
|
+
while ((match = eslintErrorRegex.exec(output)) !== null) {
|
|
21112
|
+
if (!output.includes("error")) continue;
|
|
21113
|
+
errors.push({
|
|
21114
|
+
file: match[1] || "",
|
|
21115
|
+
line: parseInt(match[2] || "0", 10),
|
|
21116
|
+
column: parseInt(match[3] || "0", 10),
|
|
21117
|
+
message: match[4] || ""
|
|
21118
|
+
});
|
|
21119
|
+
}
|
|
21120
|
+
return errors;
|
|
21121
|
+
}
|
|
21122
|
+
/**
|
|
21123
|
+
* Parse warnings from build output
|
|
21124
|
+
*/
|
|
21125
|
+
parseWarnings(output) {
|
|
21126
|
+
const warnings = [];
|
|
21127
|
+
const tsWarningRegex = /(.+?)\((\d+),(\d+)\): warning (TS\d+): (.+)/g;
|
|
21128
|
+
let match;
|
|
21129
|
+
while ((match = tsWarningRegex.exec(output)) !== null) {
|
|
21130
|
+
warnings.push({
|
|
21131
|
+
file: match[1] || "",
|
|
21132
|
+
line: parseInt(match[2] || "0", 10),
|
|
21133
|
+
column: parseInt(match[3] || "0", 10),
|
|
21134
|
+
code: match[4],
|
|
21135
|
+
message: match[5] || ""
|
|
21136
|
+
});
|
|
21137
|
+
}
|
|
21138
|
+
return warnings;
|
|
21139
|
+
}
|
|
21140
|
+
/**
|
|
21141
|
+
* Parse TypeScript-specific errors
|
|
21142
|
+
*/
|
|
21143
|
+
parseTypeScriptErrors(output) {
|
|
21144
|
+
return this.parseErrors(output);
|
|
21145
|
+
}
|
|
21146
|
+
/**
|
|
21147
|
+
* Parse TypeScript-specific warnings
|
|
21148
|
+
*/
|
|
21149
|
+
parseTypeScriptWarnings(output) {
|
|
21150
|
+
return this.parseWarnings(output);
|
|
21151
|
+
}
|
|
21152
|
+
/**
|
|
21153
|
+
* Check if file exists
|
|
21154
|
+
*/
|
|
21155
|
+
async fileExists(filePath) {
|
|
21156
|
+
try {
|
|
21157
|
+
await fs22.access(filePath);
|
|
21158
|
+
return true;
|
|
21159
|
+
} catch {
|
|
21160
|
+
return false;
|
|
21161
|
+
}
|
|
21162
|
+
}
|
|
21163
|
+
};
|
|
21164
|
+
|
|
21165
|
+
// src/quality/analyzers/correctness.ts
|
|
21166
|
+
function parseVitestOutput(stdout) {
|
|
21167
|
+
const testsMatch = stdout.match(
|
|
21168
|
+
/Tests\s+(?:(\d+)\s+passed)?(?:\s*\|\s*(\d+)\s+failed)?(?:\s*\|\s*(\d+)\s+skipped)?/
|
|
21169
|
+
);
|
|
21170
|
+
if (testsMatch) {
|
|
21171
|
+
return {
|
|
21172
|
+
passed: parseInt(testsMatch[1] || "0", 10),
|
|
21173
|
+
failed: parseInt(testsMatch[2] || "0", 10),
|
|
21174
|
+
skipped: parseInt(testsMatch[3] || "0", 10)
|
|
21175
|
+
};
|
|
21176
|
+
}
|
|
21177
|
+
try {
|
|
21178
|
+
const json2 = JSON.parse(stdout);
|
|
21179
|
+
return {
|
|
21180
|
+
passed: json2.numPassedTests ?? 0,
|
|
21181
|
+
failed: json2.numFailedTests ?? 0,
|
|
21182
|
+
skipped: json2.numPendingTests ?? 0
|
|
21183
|
+
};
|
|
21184
|
+
} catch {
|
|
21185
|
+
}
|
|
21186
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21187
|
+
}
|
|
21188
|
+
function parseJestOutput(stdout) {
|
|
21189
|
+
try {
|
|
21190
|
+
const json2 = JSON.parse(stdout);
|
|
21191
|
+
return {
|
|
21192
|
+
passed: json2.numPassedTests ?? 0,
|
|
21193
|
+
failed: json2.numFailedTests ?? 0,
|
|
21194
|
+
skipped: json2.numPendingTests ?? 0
|
|
21195
|
+
};
|
|
21196
|
+
} catch {
|
|
21197
|
+
const match = stdout.match(
|
|
21198
|
+
/Tests:\s+(\d+)\s+passed(?:,\s*(\d+)\s+failed)?(?:,\s*(\d+)\s+skipped)?/
|
|
21199
|
+
);
|
|
21200
|
+
if (match) {
|
|
21201
|
+
return {
|
|
21202
|
+
passed: parseInt(match[1] || "0", 10),
|
|
21203
|
+
failed: parseInt(match[2] || "0", 10),
|
|
21204
|
+
skipped: parseInt(match[3] || "0", 10)
|
|
21205
|
+
};
|
|
21206
|
+
}
|
|
21207
|
+
}
|
|
21208
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21209
|
+
}
|
|
21210
|
+
function buildTestCommand(framework) {
|
|
21211
|
+
switch (framework) {
|
|
21212
|
+
case "vitest":
|
|
21213
|
+
return { command: "npx", args: ["vitest", "run", "--reporter=verbose"] };
|
|
21214
|
+
case "jest":
|
|
21215
|
+
return { command: "npx", args: ["jest", "--json"] };
|
|
21216
|
+
case "mocha":
|
|
21217
|
+
return { command: "npx", args: ["mocha", "--reporter=json"] };
|
|
21218
|
+
default:
|
|
21219
|
+
return null;
|
|
21220
|
+
}
|
|
21221
|
+
}
|
|
21222
|
+
var CorrectnessAnalyzer = class {
|
|
21223
|
+
constructor(projectPath) {
|
|
21224
|
+
this.projectPath = projectPath;
|
|
21225
|
+
this.buildVerifier = new BuildVerifier(projectPath);
|
|
21226
|
+
}
|
|
21227
|
+
buildVerifier;
|
|
21228
|
+
/**
|
|
21229
|
+
* Analyze correctness by running tests and verifying build
|
|
21230
|
+
*/
|
|
21231
|
+
async analyze() {
|
|
21232
|
+
const [testResult, buildResult] = await Promise.all([
|
|
21233
|
+
this.runTests(),
|
|
21234
|
+
this.buildVerifier.verifyTypes().catch(() => ({ success: false, errors: [] }))
|
|
21235
|
+
]);
|
|
21236
|
+
const total = testResult.passed + testResult.failed;
|
|
21237
|
+
const testPassRate = total > 0 ? testResult.passed / total * 100 : 0;
|
|
21238
|
+
const buildSuccess = buildResult.success;
|
|
21239
|
+
let score;
|
|
21240
|
+
if (total === 0 && !buildSuccess) {
|
|
21241
|
+
score = 0;
|
|
21242
|
+
} else if (total === 0) {
|
|
21243
|
+
score = 30;
|
|
21244
|
+
} else {
|
|
21245
|
+
score = testPassRate / 100 * 70 + (buildSuccess ? 30 : 0);
|
|
21246
|
+
}
|
|
21247
|
+
score = Math.round(Math.max(0, Math.min(100, score)));
|
|
21248
|
+
const details = this.buildDetails(testResult, buildSuccess, testPassRate, total);
|
|
21249
|
+
return {
|
|
21250
|
+
score,
|
|
21251
|
+
testPassRate,
|
|
21252
|
+
buildSuccess,
|
|
21253
|
+
testsPassed: testResult.passed,
|
|
21254
|
+
testsFailed: testResult.failed,
|
|
21255
|
+
testsSkipped: testResult.skipped,
|
|
21256
|
+
testsTotal: total,
|
|
21257
|
+
buildErrors: buildResult.errors?.length ?? 0,
|
|
21258
|
+
details
|
|
21259
|
+
};
|
|
21260
|
+
}
|
|
21261
|
+
/**
|
|
21262
|
+
* Run tests and parse results
|
|
21263
|
+
*/
|
|
21264
|
+
async runTests() {
|
|
21265
|
+
const framework = await detectTestFramework2(this.projectPath);
|
|
21266
|
+
if (!framework) {
|
|
21267
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21268
|
+
}
|
|
21269
|
+
const cmd = buildTestCommand(framework);
|
|
21270
|
+
if (!cmd) {
|
|
21271
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21272
|
+
}
|
|
21273
|
+
try {
|
|
21274
|
+
const result = await execa(cmd.command, cmd.args, {
|
|
21275
|
+
cwd: this.projectPath,
|
|
21276
|
+
reject: false,
|
|
21277
|
+
timeout: 3e5
|
|
21278
|
+
// 5 minutes
|
|
21279
|
+
});
|
|
21280
|
+
const output = result.stdout + "\n" + result.stderr;
|
|
21281
|
+
switch (framework) {
|
|
21282
|
+
case "vitest":
|
|
21283
|
+
return parseVitestOutput(output);
|
|
21284
|
+
case "jest":
|
|
21285
|
+
return parseJestOutput(result.stdout);
|
|
21286
|
+
case "mocha": {
|
|
21287
|
+
try {
|
|
21288
|
+
const json2 = JSON.parse(result.stdout);
|
|
21289
|
+
return {
|
|
21290
|
+
passed: json2.stats?.passes ?? 0,
|
|
21291
|
+
failed: json2.stats?.failures ?? 0,
|
|
21292
|
+
skipped: json2.stats?.pending ?? 0
|
|
21293
|
+
};
|
|
21294
|
+
} catch {
|
|
21295
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21296
|
+
}
|
|
21297
|
+
}
|
|
21298
|
+
default:
|
|
21299
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21300
|
+
}
|
|
21301
|
+
} catch {
|
|
21302
|
+
return { passed: 0, failed: 0, skipped: 0 };
|
|
21303
|
+
}
|
|
21304
|
+
}
|
|
21305
|
+
buildDetails(testResult, buildSuccess, testPassRate, total) {
|
|
21306
|
+
const parts = [];
|
|
21307
|
+
if (total > 0) {
|
|
21308
|
+
parts.push(`Tests: ${testResult.passed}/${total} passed (${testPassRate.toFixed(1)}%)`);
|
|
21309
|
+
if (testResult.failed > 0) {
|
|
21310
|
+
parts.push(`${testResult.failed} failed`);
|
|
21311
|
+
}
|
|
21312
|
+
} else {
|
|
21313
|
+
parts.push("No tests found");
|
|
21314
|
+
}
|
|
21315
|
+
parts.push(`Build: ${buildSuccess ? "success" : "failed"}`);
|
|
21316
|
+
return parts.join(", ");
|
|
21317
|
+
}
|
|
21318
|
+
};
|
|
21319
|
+
function countExports(ast) {
|
|
21320
|
+
let exportCount = 0;
|
|
21321
|
+
for (const node of ast.body) {
|
|
21322
|
+
switch (node.type) {
|
|
21323
|
+
case "ExportNamedDeclaration":
|
|
21324
|
+
if (node.declaration) {
|
|
21325
|
+
exportCount++;
|
|
21326
|
+
if (node.declaration.type === "VariableDeclaration" && node.declaration.declarations.length > 1) {
|
|
21327
|
+
exportCount += node.declaration.declarations.length - 1;
|
|
21328
|
+
}
|
|
21329
|
+
} else if (node.specifiers.length > 0) {
|
|
21330
|
+
exportCount += node.specifiers.length;
|
|
21331
|
+
}
|
|
21332
|
+
break;
|
|
21333
|
+
case "ExportDefaultDeclaration":
|
|
21334
|
+
exportCount++;
|
|
21335
|
+
break;
|
|
21336
|
+
case "ExportAllDeclaration":
|
|
21337
|
+
exportCount++;
|
|
21338
|
+
break;
|
|
21339
|
+
}
|
|
21340
|
+
}
|
|
21341
|
+
return exportCount;
|
|
21342
|
+
}
|
|
21343
|
+
var CompletenessAnalyzer = class {
|
|
21344
|
+
constructor(projectPath) {
|
|
21345
|
+
this.projectPath = projectPath;
|
|
21346
|
+
}
|
|
21347
|
+
/**
|
|
21348
|
+
* Analyze project completeness
|
|
21349
|
+
*/
|
|
21350
|
+
async analyze(files) {
|
|
21351
|
+
const sourceFiles = files ?? await this.findSourceFiles();
|
|
21352
|
+
const testFiles = await this.findTestFiles();
|
|
21353
|
+
const { totalExports, exportDensity } = await this.analyzeExports(sourceFiles);
|
|
21354
|
+
const testFileRatio = this.calculateTestFileRatio(sourceFiles, testFiles);
|
|
21355
|
+
const hasEntryPoint = await this.checkEntryPoint();
|
|
21356
|
+
const exportScore = Math.min(40, exportDensity * 0.4);
|
|
21357
|
+
const testScore = Math.min(40, testFileRatio * 0.4);
|
|
21358
|
+
const entryScore = hasEntryPoint ? 20 : 0;
|
|
21359
|
+
const score = Math.round(Math.max(0, Math.min(100, exportScore + testScore + entryScore)));
|
|
21360
|
+
const details = this.buildDetails(
|
|
21361
|
+
sourceFiles.length,
|
|
21362
|
+
testFiles.length,
|
|
21363
|
+
totalExports,
|
|
21364
|
+
exportDensity,
|
|
21365
|
+
testFileRatio,
|
|
21366
|
+
hasEntryPoint
|
|
21367
|
+
);
|
|
21368
|
+
return {
|
|
21369
|
+
score,
|
|
21370
|
+
exportDensity,
|
|
21371
|
+
testFileRatio,
|
|
21372
|
+
hasEntryPoint,
|
|
21373
|
+
sourceFileCount: sourceFiles.length,
|
|
21374
|
+
testFileCount: testFiles.length,
|
|
21375
|
+
totalExports,
|
|
21376
|
+
details
|
|
21377
|
+
};
|
|
21378
|
+
}
|
|
21379
|
+
/**
|
|
21380
|
+
* Analyze export density across files
|
|
21381
|
+
*/
|
|
21382
|
+
async analyzeExports(files) {
|
|
21383
|
+
let totalExports = 0;
|
|
21384
|
+
let filesWithExports = 0;
|
|
21385
|
+
for (const file of files) {
|
|
21386
|
+
try {
|
|
21387
|
+
const content = await readFile(file, "utf-8");
|
|
21388
|
+
const ast = parse(content, {
|
|
21389
|
+
loc: true,
|
|
21390
|
+
range: true,
|
|
21391
|
+
jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
|
|
21392
|
+
});
|
|
21393
|
+
const exports$1 = countExports(ast);
|
|
21394
|
+
totalExports += exports$1;
|
|
21395
|
+
if (exports$1 > 0) filesWithExports++;
|
|
21396
|
+
} catch {
|
|
21397
|
+
}
|
|
21398
|
+
}
|
|
21399
|
+
const exportDensity = files.length > 0 ? filesWithExports / files.length * 100 : 0;
|
|
21400
|
+
return { totalExports, exportDensity };
|
|
21401
|
+
}
|
|
21402
|
+
/**
|
|
21403
|
+
* Calculate ratio of source files that have corresponding test files
|
|
21404
|
+
*/
|
|
21405
|
+
calculateTestFileRatio(sourceFiles, testFiles) {
|
|
21406
|
+
if (sourceFiles.length === 0) return 0;
|
|
21407
|
+
const testBaseNames = new Set(
|
|
21408
|
+
testFiles.map((f) => {
|
|
21409
|
+
const name = basename(f);
|
|
21410
|
+
return name.replace(/\.(test|spec)\.(ts|tsx|js|jsx)$/, "");
|
|
21411
|
+
})
|
|
21412
|
+
);
|
|
21413
|
+
let coveredCount = 0;
|
|
21414
|
+
for (const sourceFile of sourceFiles) {
|
|
21415
|
+
const name = basename(sourceFile).replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
21416
|
+
if (testBaseNames.has(name)) {
|
|
21417
|
+
coveredCount++;
|
|
21418
|
+
}
|
|
21419
|
+
}
|
|
21420
|
+
return coveredCount / sourceFiles.length * 100;
|
|
21421
|
+
}
|
|
21422
|
+
/**
|
|
21423
|
+
* Check if project has a clear entry point
|
|
21424
|
+
*/
|
|
21425
|
+
async checkEntryPoint() {
|
|
21426
|
+
const entryPoints = [
|
|
21427
|
+
"src/index.ts",
|
|
21428
|
+
"src/index.js",
|
|
21429
|
+
"src/main.ts",
|
|
21430
|
+
"src/main.js",
|
|
21431
|
+
"index.ts",
|
|
21432
|
+
"index.js"
|
|
21433
|
+
];
|
|
21434
|
+
for (const entry of entryPoints) {
|
|
21435
|
+
try {
|
|
21436
|
+
await access(join(this.projectPath, entry), constants.R_OK);
|
|
21437
|
+
return true;
|
|
21438
|
+
} catch {
|
|
21439
|
+
}
|
|
21440
|
+
}
|
|
21441
|
+
try {
|
|
21442
|
+
const pkgContent = await readFile(join(this.projectPath, "package.json"), "utf-8");
|
|
21443
|
+
const pkg = JSON.parse(pkgContent);
|
|
21444
|
+
if (pkg.main || pkg.exports) return true;
|
|
21445
|
+
} catch {
|
|
21446
|
+
}
|
|
21447
|
+
return false;
|
|
21448
|
+
}
|
|
21449
|
+
buildDetails(sourceCount, testCount, totalExports, exportDensity, testFileRatio, hasEntryPoint) {
|
|
21450
|
+
return [
|
|
21451
|
+
`${sourceCount} source files, ${testCount} test files`,
|
|
21452
|
+
`${totalExports} exports (${exportDensity.toFixed(1)}% files have exports)`,
|
|
21453
|
+
`${testFileRatio.toFixed(1)}% source files have tests`,
|
|
21454
|
+
`Entry point: ${hasEntryPoint ? "found" : "missing"}`
|
|
21455
|
+
].join(", ");
|
|
21456
|
+
}
|
|
21457
|
+
async findSourceFiles() {
|
|
21458
|
+
return glob("**/*.{ts,js,tsx,jsx}", {
|
|
21459
|
+
cwd: this.projectPath,
|
|
21460
|
+
absolute: true,
|
|
21461
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
|
|
21462
|
+
});
|
|
21463
|
+
}
|
|
21464
|
+
async findTestFiles() {
|
|
21465
|
+
return glob("**/*.{test,spec}.{ts,tsx,js,jsx}", {
|
|
21466
|
+
cwd: this.projectPath,
|
|
21467
|
+
absolute: true,
|
|
21468
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"]
|
|
21469
|
+
});
|
|
21470
|
+
}
|
|
21471
|
+
};
|
|
21472
|
+
function analyzeRobustnessPatterns(ast) {
|
|
21473
|
+
let functions = 0;
|
|
21474
|
+
let functionsWithTryCatch = 0;
|
|
21475
|
+
let optionalChaining = 0;
|
|
21476
|
+
let nullishCoalescing = 0;
|
|
21477
|
+
let typeGuards = 0;
|
|
21478
|
+
let typeofChecks = 0;
|
|
21479
|
+
let nullChecks = 0;
|
|
21480
|
+
let insideFunction = false;
|
|
21481
|
+
let currentFunctionHasTryCatch = false;
|
|
21482
|
+
function traverse(node) {
|
|
21483
|
+
const isFunctionNode = node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "MethodDefinition";
|
|
21484
|
+
if (isFunctionNode) {
|
|
21485
|
+
if (insideFunction && currentFunctionHasTryCatch) {
|
|
21486
|
+
functionsWithTryCatch++;
|
|
21487
|
+
}
|
|
21488
|
+
functions++;
|
|
21489
|
+
const previousInsideFunction = insideFunction;
|
|
21490
|
+
const previousHasTryCatch = currentFunctionHasTryCatch;
|
|
21491
|
+
insideFunction = true;
|
|
21492
|
+
currentFunctionHasTryCatch = false;
|
|
21493
|
+
traverseChildren(node);
|
|
21494
|
+
if (currentFunctionHasTryCatch) {
|
|
21495
|
+
functionsWithTryCatch++;
|
|
21496
|
+
}
|
|
21497
|
+
if (isFunctionNode) {
|
|
21498
|
+
functions--;
|
|
21499
|
+
functions++;
|
|
21500
|
+
}
|
|
21501
|
+
insideFunction = previousInsideFunction;
|
|
21502
|
+
currentFunctionHasTryCatch = previousHasTryCatch;
|
|
21503
|
+
return;
|
|
21504
|
+
}
|
|
21505
|
+
switch (node.type) {
|
|
21506
|
+
case "TryStatement":
|
|
21507
|
+
if (insideFunction) {
|
|
21508
|
+
currentFunctionHasTryCatch = true;
|
|
21509
|
+
}
|
|
21510
|
+
break;
|
|
21511
|
+
case "ChainExpression":
|
|
21512
|
+
optionalChaining++;
|
|
21513
|
+
break;
|
|
21514
|
+
case "MemberExpression":
|
|
21515
|
+
if (node.optional) {
|
|
21516
|
+
optionalChaining++;
|
|
21517
|
+
}
|
|
21518
|
+
break;
|
|
21519
|
+
case "CallExpression":
|
|
21520
|
+
if (node.optional) {
|
|
21521
|
+
optionalChaining++;
|
|
21522
|
+
}
|
|
21523
|
+
break;
|
|
21524
|
+
case "LogicalExpression":
|
|
21525
|
+
if (node.operator === "??") {
|
|
21526
|
+
nullishCoalescing++;
|
|
21527
|
+
}
|
|
21528
|
+
break;
|
|
21529
|
+
case "TSTypeAliasDeclaration":
|
|
21530
|
+
typeGuards++;
|
|
21531
|
+
break;
|
|
21532
|
+
case "UnaryExpression":
|
|
21533
|
+
if (node.operator === "typeof") {
|
|
21534
|
+
typeofChecks++;
|
|
21535
|
+
}
|
|
21536
|
+
break;
|
|
21537
|
+
case "BinaryExpression":
|
|
21538
|
+
if (node.operator === "===" || node.operator === "!==" || node.operator === "==" || node.operator === "!=") {
|
|
21539
|
+
if (isNullOrUndefined(node.left) || isNullOrUndefined(node.right)) {
|
|
21540
|
+
nullChecks++;
|
|
21541
|
+
}
|
|
21542
|
+
}
|
|
21543
|
+
break;
|
|
21544
|
+
}
|
|
21545
|
+
traverseChildren(node);
|
|
21546
|
+
}
|
|
21547
|
+
function traverseChildren(node) {
|
|
21548
|
+
for (const key of Object.keys(node)) {
|
|
21549
|
+
if (key === "parent") continue;
|
|
21550
|
+
const child = node[key];
|
|
21551
|
+
if (child && typeof child === "object") {
|
|
21552
|
+
if (Array.isArray(child)) {
|
|
21553
|
+
for (const item of child) {
|
|
21554
|
+
if (item && typeof item === "object" && item.type) {
|
|
21555
|
+
traverse(item);
|
|
21556
|
+
}
|
|
21557
|
+
}
|
|
21558
|
+
} else if (child.type) {
|
|
21559
|
+
traverse(child);
|
|
21560
|
+
}
|
|
21561
|
+
}
|
|
21562
|
+
}
|
|
21563
|
+
}
|
|
21564
|
+
traverse(ast);
|
|
21565
|
+
return {
|
|
21566
|
+
functions,
|
|
21567
|
+
functionsWithTryCatch,
|
|
21568
|
+
optionalChaining,
|
|
21569
|
+
nullishCoalescing,
|
|
21570
|
+
typeGuards,
|
|
21571
|
+
typeofChecks,
|
|
21572
|
+
nullChecks
|
|
21573
|
+
};
|
|
21574
|
+
}
|
|
21575
|
+
function isNullOrUndefined(node) {
|
|
21576
|
+
if (node.type === "Literal" && node.value === null) return true;
|
|
21577
|
+
if (node.type === "Identifier" && node.name === "undefined") return true;
|
|
21578
|
+
return false;
|
|
21579
|
+
}
|
|
21580
|
+
var RobustnessAnalyzer = class {
|
|
21581
|
+
constructor(projectPath) {
|
|
21582
|
+
this.projectPath = projectPath;
|
|
21583
|
+
}
|
|
21584
|
+
/**
|
|
21585
|
+
* Analyze robustness of project files
|
|
21586
|
+
*/
|
|
21587
|
+
async analyze(files) {
|
|
21588
|
+
const targetFiles = files ?? await this.findSourceFiles();
|
|
21589
|
+
let totalFunctions = 0;
|
|
21590
|
+
let totalFunctionsWithTryCatch = 0;
|
|
21591
|
+
let totalOptionalChaining = 0;
|
|
21592
|
+
let totalNullishCoalescing = 0;
|
|
21593
|
+
let totalTypeGuards = 0;
|
|
21594
|
+
let totalTypeofChecks = 0;
|
|
21595
|
+
let totalNullChecks = 0;
|
|
21596
|
+
for (const file of targetFiles) {
|
|
21597
|
+
try {
|
|
21598
|
+
const content = await readFile(file, "utf-8");
|
|
21599
|
+
const ast = parse(content, {
|
|
21600
|
+
loc: true,
|
|
21601
|
+
range: true,
|
|
21602
|
+
jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
|
|
21603
|
+
});
|
|
21604
|
+
const result = analyzeRobustnessPatterns(ast);
|
|
21605
|
+
totalFunctions += result.functions;
|
|
21606
|
+
totalFunctionsWithTryCatch += result.functionsWithTryCatch;
|
|
21607
|
+
totalOptionalChaining += result.optionalChaining;
|
|
21608
|
+
totalNullishCoalescing += result.nullishCoalescing;
|
|
21609
|
+
totalTypeGuards += result.typeGuards;
|
|
21610
|
+
totalTypeofChecks += result.typeofChecks;
|
|
21611
|
+
totalNullChecks += result.nullChecks;
|
|
21612
|
+
} catch {
|
|
21613
|
+
}
|
|
21614
|
+
}
|
|
21615
|
+
const tryCatchRatio = totalFunctions > 0 ? totalFunctionsWithTryCatch / totalFunctions * 100 : 0;
|
|
21616
|
+
const validationPerFunction = totalFunctions > 0 ? (totalTypeGuards + totalTypeofChecks + totalNullChecks) / totalFunctions : 0;
|
|
21617
|
+
const inputValidationScore = Math.min(100, validationPerFunction * 50);
|
|
21618
|
+
const defensivePerFunction = totalFunctions > 0 ? (totalOptionalChaining + totalNullishCoalescing) / totalFunctions : 0;
|
|
21619
|
+
const defensiveCodingScore = Math.min(100, defensivePerFunction * 40);
|
|
21620
|
+
const score = Math.round(
|
|
21621
|
+
Math.max(
|
|
21622
|
+
0,
|
|
21623
|
+
Math.min(
|
|
21624
|
+
100,
|
|
21625
|
+
tryCatchRatio * 0.4 + inputValidationScore * 0.3 + defensiveCodingScore * 0.3
|
|
21626
|
+
)
|
|
21627
|
+
)
|
|
21628
|
+
);
|
|
21629
|
+
const details = [
|
|
21630
|
+
`${totalFunctionsWithTryCatch}/${totalFunctions} functions with error handling`,
|
|
21631
|
+
`${totalOptionalChaining} optional chaining, ${totalNullishCoalescing} nullish coalescing`,
|
|
21632
|
+
`${totalTypeGuards + totalTypeofChecks + totalNullChecks} validation checks`
|
|
21633
|
+
].join(", ");
|
|
21634
|
+
return {
|
|
21635
|
+
score,
|
|
21636
|
+
tryCatchRatio,
|
|
21637
|
+
inputValidationScore,
|
|
21638
|
+
defensiveCodingScore,
|
|
21639
|
+
functionsAnalyzed: totalFunctions,
|
|
21640
|
+
functionsWithErrorHandling: totalFunctionsWithTryCatch,
|
|
21641
|
+
optionalChainingCount: totalOptionalChaining,
|
|
21642
|
+
nullishCoalescingCount: totalNullishCoalescing,
|
|
21643
|
+
typeGuardCount: totalTypeGuards,
|
|
21644
|
+
details
|
|
21645
|
+
};
|
|
21646
|
+
}
|
|
21647
|
+
async findSourceFiles() {
|
|
21648
|
+
return glob("**/*.{ts,js,tsx,jsx}", {
|
|
21649
|
+
cwd: this.projectPath,
|
|
21650
|
+
absolute: true,
|
|
21651
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
|
|
21652
|
+
});
|
|
21653
|
+
}
|
|
21654
|
+
};
|
|
21655
|
+
var TRIVIAL_PATTERNS = [
|
|
21656
|
+
/\.toBeDefined\(\)/,
|
|
21657
|
+
/\.toBeUndefined\(\)/,
|
|
21658
|
+
/\.toBeTruthy\(\)/,
|
|
21659
|
+
/\.toBeFalsy\(\)/,
|
|
21660
|
+
/\.toBe\(true\)/,
|
|
21661
|
+
/\.toBe\(false\)/,
|
|
21662
|
+
/\.not\.toBeNull\(\)/,
|
|
21663
|
+
/\.toBeInstanceOf\(/,
|
|
21664
|
+
/\.toBeTypeOf\(/
|
|
21665
|
+
];
|
|
21666
|
+
var EDGE_CASE_PATTERNS = [
|
|
21667
|
+
/error/i,
|
|
21668
|
+
/edge/i,
|
|
21669
|
+
/boundary/i,
|
|
21670
|
+
/invalid/i,
|
|
21671
|
+
/empty/i,
|
|
21672
|
+
/null/i,
|
|
21673
|
+
/undefined/i,
|
|
21674
|
+
/negative/i,
|
|
21675
|
+
/overflow/i,
|
|
21676
|
+
/timeout/i,
|
|
21677
|
+
/fail/i,
|
|
21678
|
+
/reject/i,
|
|
21679
|
+
/throw/i,
|
|
21680
|
+
/missing/i,
|
|
21681
|
+
/malformed/i,
|
|
21682
|
+
/corrupt/i
|
|
21683
|
+
];
|
|
21684
|
+
var MATCHER_PATTERNS = [
|
|
21685
|
+
/\.toBe\(/,
|
|
21686
|
+
/\.toEqual\(/,
|
|
21687
|
+
/\.toStrictEqual\(/,
|
|
21688
|
+
/\.toContain\(/,
|
|
21689
|
+
/\.toMatch\(/,
|
|
21690
|
+
/\.toHaveLength\(/,
|
|
21691
|
+
/\.toHaveProperty\(/,
|
|
21692
|
+
/\.toThrow/,
|
|
21693
|
+
/\.toHaveBeenCalled/,
|
|
21694
|
+
/\.toHaveBeenCalledWith\(/,
|
|
21695
|
+
/\.toHaveBeenCalledTimes\(/,
|
|
21696
|
+
/\.toBeGreaterThan\(/,
|
|
21697
|
+
/\.toBeLessThan\(/,
|
|
21698
|
+
/\.toBeCloseTo\(/,
|
|
21699
|
+
/\.resolves\./,
|
|
21700
|
+
/\.rejects\./
|
|
21701
|
+
];
|
|
21702
|
+
function analyzeTestFile(content) {
|
|
21703
|
+
const lines = content.split("\n");
|
|
21704
|
+
let totalAssertions = 0;
|
|
21705
|
+
let trivialAssertions = 0;
|
|
21706
|
+
let totalTests = 0;
|
|
21707
|
+
let edgeCaseTests = 0;
|
|
21708
|
+
const matchersUsed = /* @__PURE__ */ new Set();
|
|
21709
|
+
for (const line of lines) {
|
|
21710
|
+
const trimmed = line.trim();
|
|
21711
|
+
if (/^\s*(it|test)\s*\(/.test(trimmed) || /^\s*(it|test)\s*\./.test(trimmed)) {
|
|
21712
|
+
totalTests++;
|
|
21713
|
+
for (const pattern of EDGE_CASE_PATTERNS) {
|
|
21714
|
+
if (pattern.test(trimmed)) {
|
|
21715
|
+
edgeCaseTests++;
|
|
21716
|
+
break;
|
|
21717
|
+
}
|
|
21718
|
+
}
|
|
21719
|
+
}
|
|
21720
|
+
if (/expect\s*\(/.test(trimmed)) {
|
|
21721
|
+
totalAssertions++;
|
|
21722
|
+
for (const pattern of TRIVIAL_PATTERNS) {
|
|
21723
|
+
if (pattern.test(trimmed)) {
|
|
21724
|
+
trivialAssertions++;
|
|
21725
|
+
break;
|
|
21726
|
+
}
|
|
21727
|
+
}
|
|
21728
|
+
for (let i = 0; i < MATCHER_PATTERNS.length; i++) {
|
|
21729
|
+
if (MATCHER_PATTERNS[i].test(trimmed)) {
|
|
21730
|
+
matchersUsed.add(i);
|
|
21731
|
+
}
|
|
21732
|
+
}
|
|
21733
|
+
}
|
|
21734
|
+
}
|
|
21735
|
+
return { totalAssertions, trivialAssertions, totalTests, edgeCaseTests, matchersUsed };
|
|
21736
|
+
}
|
|
21737
|
+
var TestQualityAnalyzer = class {
|
|
21738
|
+
constructor(projectPath) {
|
|
21739
|
+
this.projectPath = projectPath;
|
|
21740
|
+
}
|
|
21741
|
+
/**
|
|
21742
|
+
* Analyze test quality across the project
|
|
21743
|
+
*/
|
|
21744
|
+
async analyze(files) {
|
|
21745
|
+
const testFiles = files ?? await this.findTestFiles();
|
|
21746
|
+
let totalAssertions = 0;
|
|
21747
|
+
let trivialAssertions = 0;
|
|
21748
|
+
let totalTests = 0;
|
|
21749
|
+
let edgeCaseTests = 0;
|
|
21750
|
+
const allMatchersUsed = /* @__PURE__ */ new Set();
|
|
21751
|
+
for (const file of testFiles) {
|
|
21752
|
+
try {
|
|
21753
|
+
const content = await readFile(file, "utf-8");
|
|
21754
|
+
const result = analyzeTestFile(content);
|
|
21755
|
+
totalAssertions += result.totalAssertions;
|
|
21756
|
+
trivialAssertions += result.trivialAssertions;
|
|
21757
|
+
totalTests += result.totalTests;
|
|
21758
|
+
edgeCaseTests += result.edgeCaseTests;
|
|
21759
|
+
for (const m of result.matchersUsed) {
|
|
21760
|
+
allMatchersUsed.add(m);
|
|
21761
|
+
}
|
|
21762
|
+
} catch {
|
|
21763
|
+
}
|
|
21764
|
+
}
|
|
21765
|
+
const assertionDensity = totalTests > 0 ? totalAssertions / totalTests : 0;
|
|
21766
|
+
const trivialAssertionRatio = totalAssertions > 0 ? trivialAssertions / totalAssertions * 100 : 0;
|
|
21767
|
+
const edgeCaseRatio = totalTests > 0 ? edgeCaseTests / totalTests * 100 : 0;
|
|
21768
|
+
const assertionDiversity = allMatchersUsed.size / MATCHER_PATTERNS.length * 100;
|
|
21769
|
+
let score = 100;
|
|
21770
|
+
score -= Math.min(40, trivialAssertionRatio * 0.4);
|
|
21771
|
+
if (assertionDensity < 2) {
|
|
21772
|
+
score -= (2 - assertionDensity) * 10;
|
|
21773
|
+
}
|
|
21774
|
+
if (edgeCaseRatio > 10) {
|
|
21775
|
+
score += Math.min(10, (edgeCaseRatio - 10) * 0.5);
|
|
21776
|
+
}
|
|
21777
|
+
if (assertionDiversity > 30) {
|
|
21778
|
+
score += Math.min(10, (assertionDiversity - 30) * 0.15);
|
|
21779
|
+
}
|
|
21780
|
+
if (totalTests === 0) {
|
|
21781
|
+
score = 0;
|
|
21782
|
+
}
|
|
21783
|
+
score = Math.round(Math.max(0, Math.min(100, score)));
|
|
21784
|
+
const details = [
|
|
21785
|
+
`${totalTests} tests, ${totalAssertions} assertions`,
|
|
21786
|
+
`${assertionDensity.toFixed(1)} assertions/test`,
|
|
21787
|
+
`${trivialAssertionRatio.toFixed(1)}% trivial`,
|
|
21788
|
+
`${edgeCaseRatio.toFixed(1)}% edge cases`,
|
|
21789
|
+
`${allMatchersUsed.size}/${MATCHER_PATTERNS.length} matcher types`
|
|
21790
|
+
].join(", ");
|
|
21791
|
+
return {
|
|
21792
|
+
score,
|
|
21793
|
+
assertionDensity,
|
|
21794
|
+
trivialAssertionRatio,
|
|
21795
|
+
edgeCaseRatio,
|
|
21796
|
+
assertionDiversity,
|
|
21797
|
+
totalTestFiles: testFiles.length,
|
|
21798
|
+
totalAssertions,
|
|
21799
|
+
trivialAssertions,
|
|
21800
|
+
edgeCaseTests,
|
|
21801
|
+
totalTests,
|
|
21802
|
+
details
|
|
21803
|
+
};
|
|
21804
|
+
}
|
|
21805
|
+
async findTestFiles() {
|
|
21806
|
+
return glob("**/*.{test,spec}.{ts,tsx,js,jsx}", {
|
|
21807
|
+
cwd: this.projectPath,
|
|
21808
|
+
absolute: true,
|
|
21809
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"]
|
|
21810
|
+
});
|
|
21811
|
+
}
|
|
21812
|
+
};
|
|
21813
|
+
function hasJSDocComment(node, sourceCode) {
|
|
21814
|
+
const startLine = node.loc.start.line;
|
|
21815
|
+
const lines = sourceCode.split("\n");
|
|
21816
|
+
for (let i = startLine - 2; i >= Math.max(0, startLine - 10); i--) {
|
|
21817
|
+
const line = lines[i]?.trim() ?? "";
|
|
21818
|
+
if (line.endsWith("*/")) {
|
|
21819
|
+
for (let j = i; j >= Math.max(0, i - 20); j--) {
|
|
21820
|
+
const commentLine = lines[j]?.trim() ?? "";
|
|
21821
|
+
if (commentLine.startsWith("/**")) return true;
|
|
21822
|
+
if (commentLine.startsWith("/*") && !commentLine.startsWith("/**")) return false;
|
|
21823
|
+
}
|
|
21824
|
+
}
|
|
21825
|
+
if (line && !line.startsWith("*") && !line.startsWith("//") && !line.startsWith("/*")) {
|
|
21826
|
+
break;
|
|
21827
|
+
}
|
|
21828
|
+
}
|
|
21829
|
+
return false;
|
|
21830
|
+
}
|
|
21831
|
+
function analyzeExportDocumentation(ast, sourceCode) {
|
|
21832
|
+
let exported = 0;
|
|
21833
|
+
let documented = 0;
|
|
21834
|
+
for (const node of ast.body) {
|
|
21835
|
+
if (node.type === "ExportNamedDeclaration" && node.declaration) {
|
|
21836
|
+
const decl = node.declaration;
|
|
21837
|
+
switch (decl.type) {
|
|
21838
|
+
case "FunctionDeclaration":
|
|
21839
|
+
exported++;
|
|
21840
|
+
if (hasJSDocComment(node, sourceCode)) documented++;
|
|
21841
|
+
break;
|
|
21842
|
+
case "ClassDeclaration":
|
|
21843
|
+
exported++;
|
|
21844
|
+
if (hasJSDocComment(node, sourceCode)) documented++;
|
|
21845
|
+
break;
|
|
21846
|
+
case "TSInterfaceDeclaration":
|
|
21847
|
+
case "TSTypeAliasDeclaration":
|
|
21848
|
+
case "TSEnumDeclaration":
|
|
21849
|
+
exported++;
|
|
21850
|
+
if (hasJSDocComment(node, sourceCode)) documented++;
|
|
21851
|
+
break;
|
|
21852
|
+
case "VariableDeclaration":
|
|
21853
|
+
for (const _declarator of decl.declarations) {
|
|
21854
|
+
exported++;
|
|
21855
|
+
if (hasJSDocComment(node, sourceCode)) documented++;
|
|
21856
|
+
}
|
|
21857
|
+
break;
|
|
21858
|
+
}
|
|
21859
|
+
} else if (node.type === "ExportDefaultDeclaration") {
|
|
21860
|
+
exported++;
|
|
21861
|
+
if (hasJSDocComment(node, sourceCode)) documented++;
|
|
21862
|
+
}
|
|
21863
|
+
}
|
|
21864
|
+
return { exported, documented };
|
|
21865
|
+
}
|
|
21866
|
+
var DocumentationAnalyzer = class {
|
|
21867
|
+
constructor(projectPath) {
|
|
21868
|
+
this.projectPath = projectPath;
|
|
21869
|
+
}
|
|
21870
|
+
/**
|
|
21871
|
+
* Analyze documentation quality
|
|
21872
|
+
*/
|
|
21873
|
+
async analyze(files) {
|
|
21874
|
+
const targetFiles = files ?? await this.findSourceFiles();
|
|
21875
|
+
let totalExported = 0;
|
|
21876
|
+
let totalDocumented = 0;
|
|
21877
|
+
for (const file of targetFiles) {
|
|
21878
|
+
try {
|
|
21879
|
+
const content = await readFile(file, "utf-8");
|
|
21880
|
+
const ast = parse(content, {
|
|
21881
|
+
loc: true,
|
|
21882
|
+
range: true,
|
|
21883
|
+
comment: true,
|
|
21884
|
+
jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
|
|
21885
|
+
});
|
|
21886
|
+
const result = analyzeExportDocumentation(ast, content);
|
|
21887
|
+
totalExported += result.exported;
|
|
21888
|
+
totalDocumented += result.documented;
|
|
21889
|
+
} catch {
|
|
21890
|
+
}
|
|
21891
|
+
}
|
|
21892
|
+
const jsdocCoverage = totalExported > 0 ? totalDocumented / totalExported * 100 : 0;
|
|
21893
|
+
const hasReadme = await this.fileExists("README.md");
|
|
21894
|
+
const hasChangelog = await this.fileExists("CHANGELOG.md") || await this.fileExists("CHANGES.md");
|
|
21895
|
+
const score = Math.round(
|
|
21896
|
+
Math.max(
|
|
21897
|
+
0,
|
|
21898
|
+
Math.min(100, jsdocCoverage * 0.7 + (hasReadme ? 20 : 0) + (hasChangelog ? 10 : 0))
|
|
21899
|
+
)
|
|
21900
|
+
);
|
|
21901
|
+
const details = [
|
|
21902
|
+
`${totalDocumented}/${totalExported} exports documented (${jsdocCoverage.toFixed(1)}%)`,
|
|
21903
|
+
`README: ${hasReadme ? "yes" : "no"}`,
|
|
21904
|
+
`CHANGELOG: ${hasChangelog ? "yes" : "no"}`
|
|
21905
|
+
].join(", ");
|
|
21906
|
+
return {
|
|
21907
|
+
score,
|
|
21908
|
+
jsdocCoverage,
|
|
21909
|
+
hasReadme,
|
|
21910
|
+
hasChangelog,
|
|
21911
|
+
exportedDeclarations: totalExported,
|
|
21912
|
+
documentedDeclarations: totalDocumented,
|
|
21913
|
+
details
|
|
21914
|
+
};
|
|
21915
|
+
}
|
|
21916
|
+
async fileExists(relativePath) {
|
|
21917
|
+
try {
|
|
21918
|
+
await access(join(this.projectPath, relativePath), constants.R_OK);
|
|
21919
|
+
return true;
|
|
21920
|
+
} catch {
|
|
21921
|
+
return false;
|
|
21922
|
+
}
|
|
21923
|
+
}
|
|
21924
|
+
async findSourceFiles() {
|
|
21925
|
+
return glob("**/*.{ts,js,tsx,jsx}", {
|
|
21926
|
+
cwd: this.projectPath,
|
|
21927
|
+
absolute: true,
|
|
21928
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
|
|
21929
|
+
});
|
|
21930
|
+
}
|
|
21931
|
+
};
|
|
21932
|
+
async function detectLinter(projectPath) {
|
|
21933
|
+
try {
|
|
21934
|
+
const pkgContent = await readFile(join(projectPath, "package.json"), "utf-8");
|
|
21935
|
+
const pkg = JSON.parse(pkgContent);
|
|
21936
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
21937
|
+
if (deps.oxlint) return "oxlint";
|
|
21938
|
+
if (deps["@biomejs/biome"] || deps.biome) return "biome";
|
|
21939
|
+
if (deps.eslint) return "eslint";
|
|
21940
|
+
return null;
|
|
21941
|
+
} catch {
|
|
21942
|
+
return null;
|
|
21943
|
+
}
|
|
21944
|
+
}
|
|
21945
|
+
async function runOxlint(projectPath) {
|
|
21946
|
+
try {
|
|
21947
|
+
const result = await execa("npx", ["oxlint", "src", "--format=json"], {
|
|
21948
|
+
cwd: projectPath,
|
|
21949
|
+
reject: false,
|
|
21950
|
+
timeout: 6e4
|
|
21951
|
+
});
|
|
21952
|
+
try {
|
|
21953
|
+
const output = JSON.parse(result.stdout);
|
|
21954
|
+
const errors = Array.isArray(output) ? output.filter((d) => {
|
|
21955
|
+
const diag = d;
|
|
21956
|
+
return diag.severity === 2 || diag.severity === "error";
|
|
21957
|
+
}).length : 0;
|
|
21958
|
+
const warnings = Array.isArray(output) ? output.filter((d) => {
|
|
21959
|
+
const diag = d;
|
|
21960
|
+
return diag.severity === 1 || diag.severity === "warning";
|
|
21961
|
+
}).length : 0;
|
|
21962
|
+
return { errors, warnings };
|
|
21963
|
+
} catch {
|
|
21964
|
+
const lines = (result.stdout + result.stderr).split("\n");
|
|
21965
|
+
let errors = 0;
|
|
21966
|
+
let warnings = 0;
|
|
21967
|
+
for (const line of lines) {
|
|
21968
|
+
if (/error\[/.test(line) || /✖.*error/.test(line)) errors++;
|
|
21969
|
+
if (/warning\[/.test(line) || /⚠.*warning/.test(line)) warnings++;
|
|
21970
|
+
}
|
|
21971
|
+
return { errors, warnings };
|
|
21972
|
+
}
|
|
21973
|
+
} catch {
|
|
21974
|
+
return { errors: 0, warnings: 0 };
|
|
21975
|
+
}
|
|
21976
|
+
}
|
|
21977
|
+
async function runEslint(projectPath) {
|
|
21978
|
+
try {
|
|
21979
|
+
const result = await execa("npx", ["eslint", "src", "--format=json"], {
|
|
21980
|
+
cwd: projectPath,
|
|
21981
|
+
reject: false,
|
|
21982
|
+
timeout: 12e4
|
|
21983
|
+
});
|
|
21984
|
+
try {
|
|
21985
|
+
const output = JSON.parse(result.stdout);
|
|
21986
|
+
const errors = output.reduce((sum, f) => sum + f.errorCount, 0);
|
|
21987
|
+
const warnings = output.reduce((sum, f) => sum + f.warningCount, 0);
|
|
21988
|
+
return { errors, warnings };
|
|
21989
|
+
} catch {
|
|
21990
|
+
return { errors: 0, warnings: 0 };
|
|
21991
|
+
}
|
|
21992
|
+
} catch {
|
|
21993
|
+
return { errors: 0, warnings: 0 };
|
|
21994
|
+
}
|
|
21995
|
+
}
|
|
21996
|
+
async function runBiome(projectPath) {
|
|
21997
|
+
try {
|
|
21998
|
+
const result = await execa("npx", ["@biomejs/biome", "lint", "src", "--reporter=json"], {
|
|
21999
|
+
cwd: projectPath,
|
|
22000
|
+
reject: false,
|
|
22001
|
+
timeout: 6e4
|
|
22002
|
+
});
|
|
22003
|
+
try {
|
|
22004
|
+
const output = JSON.parse(result.stdout);
|
|
22005
|
+
const diagnostics = output.diagnostics ?? [];
|
|
22006
|
+
const errors = diagnostics.filter(
|
|
22007
|
+
(d) => d.severity === "error"
|
|
22008
|
+
).length;
|
|
22009
|
+
const warnings = diagnostics.filter(
|
|
22010
|
+
(d) => d.severity === "warning"
|
|
22011
|
+
).length;
|
|
22012
|
+
return { errors, warnings };
|
|
22013
|
+
} catch {
|
|
22014
|
+
return { errors: 0, warnings: 0 };
|
|
22015
|
+
}
|
|
22016
|
+
} catch {
|
|
22017
|
+
return { errors: 0, warnings: 0 };
|
|
22018
|
+
}
|
|
22019
|
+
}
|
|
22020
|
+
var StyleAnalyzer = class {
|
|
22021
|
+
constructor(projectPath) {
|
|
22022
|
+
this.projectPath = projectPath;
|
|
22023
|
+
}
|
|
22024
|
+
/**
|
|
22025
|
+
* Analyze style/linting quality
|
|
22026
|
+
*/
|
|
22027
|
+
async analyze() {
|
|
22028
|
+
const linter = await detectLinter(this.projectPath);
|
|
22029
|
+
if (!linter) {
|
|
22030
|
+
return {
|
|
22031
|
+
score: 50,
|
|
22032
|
+
// Neutral score when no linter is configured
|
|
22033
|
+
errors: 0,
|
|
22034
|
+
warnings: 0,
|
|
22035
|
+
linterUsed: null,
|
|
22036
|
+
details: "No linter configured"
|
|
22037
|
+
};
|
|
22038
|
+
}
|
|
22039
|
+
let result;
|
|
22040
|
+
switch (linter) {
|
|
22041
|
+
case "oxlint":
|
|
22042
|
+
result = await runOxlint(this.projectPath);
|
|
22043
|
+
break;
|
|
22044
|
+
case "eslint":
|
|
22045
|
+
result = await runEslint(this.projectPath);
|
|
22046
|
+
break;
|
|
22047
|
+
case "biome":
|
|
22048
|
+
result = await runBiome(this.projectPath);
|
|
22049
|
+
break;
|
|
22050
|
+
}
|
|
22051
|
+
const score = Math.round(
|
|
22052
|
+
Math.max(0, Math.min(100, 100 - result.errors * 5 - result.warnings * 2))
|
|
22053
|
+
);
|
|
22054
|
+
const details = [
|
|
22055
|
+
`Linter: ${linter}`,
|
|
22056
|
+
`${result.errors} errors, ${result.warnings} warnings`
|
|
22057
|
+
].join(", ");
|
|
22058
|
+
return {
|
|
22059
|
+
score,
|
|
22060
|
+
errors: result.errors,
|
|
22061
|
+
warnings: result.warnings,
|
|
22062
|
+
linterUsed: linter,
|
|
22063
|
+
details
|
|
22064
|
+
};
|
|
22065
|
+
}
|
|
22066
|
+
};
|
|
22067
|
+
function analyzeReadabilityPatterns(ast) {
|
|
22068
|
+
let goodNames = 0;
|
|
22069
|
+
let totalNames = 0;
|
|
22070
|
+
const functionLengths = [];
|
|
22071
|
+
let maxNestingDepth = 0;
|
|
22072
|
+
const cognitiveComplexities = [];
|
|
22073
|
+
const allowedSingleChar = /* @__PURE__ */ new Set(["i", "j", "k", "_", "e", "x", "y", "t", "n"]);
|
|
22074
|
+
function isGoodName(name) {
|
|
22075
|
+
if (name.length === 1) {
|
|
22076
|
+
return allowedSingleChar.has(name);
|
|
22077
|
+
}
|
|
22078
|
+
return name.length >= 3 && name.length <= 30;
|
|
22079
|
+
}
|
|
22080
|
+
function analyzeFunction(node) {
|
|
22081
|
+
if (node.loc) {
|
|
22082
|
+
const length = node.loc.end.line - node.loc.start.line + 1;
|
|
22083
|
+
functionLengths.push(length);
|
|
22084
|
+
}
|
|
22085
|
+
let localMaxNesting = 0;
|
|
22086
|
+
let cognitiveComplexity = 0;
|
|
22087
|
+
function traverseForComplexity(n, currentNesting) {
|
|
22088
|
+
localMaxNesting = Math.max(localMaxNesting, currentNesting);
|
|
22089
|
+
let nextNesting = currentNesting;
|
|
22090
|
+
let incrementsNesting = false;
|
|
22091
|
+
switch (n.type) {
|
|
22092
|
+
case "IfStatement":
|
|
22093
|
+
cognitiveComplexity += 1;
|
|
22094
|
+
incrementsNesting = true;
|
|
22095
|
+
break;
|
|
22096
|
+
case "ForStatement":
|
|
22097
|
+
case "ForInStatement":
|
|
22098
|
+
case "ForOfStatement":
|
|
22099
|
+
case "WhileStatement":
|
|
22100
|
+
case "DoWhileStatement":
|
|
22101
|
+
cognitiveComplexity += 1 + currentNesting;
|
|
22102
|
+
incrementsNesting = true;
|
|
22103
|
+
break;
|
|
22104
|
+
case "SwitchStatement":
|
|
22105
|
+
cognitiveComplexity += 1 + currentNesting;
|
|
22106
|
+
incrementsNesting = true;
|
|
22107
|
+
break;
|
|
22108
|
+
case "CatchClause":
|
|
22109
|
+
cognitiveComplexity += 1 + currentNesting;
|
|
22110
|
+
incrementsNesting = true;
|
|
22111
|
+
break;
|
|
22112
|
+
case "ConditionalExpression":
|
|
22113
|
+
cognitiveComplexity += 1 + currentNesting;
|
|
22114
|
+
incrementsNesting = true;
|
|
22115
|
+
break;
|
|
22116
|
+
case "LogicalExpression":
|
|
22117
|
+
if (n.operator === "&&" || n.operator === "||") {
|
|
22118
|
+
cognitiveComplexity += 1;
|
|
22119
|
+
}
|
|
22120
|
+
break;
|
|
22121
|
+
case "FunctionExpression":
|
|
22122
|
+
case "ArrowFunctionExpression":
|
|
22123
|
+
incrementsNesting = true;
|
|
22124
|
+
break;
|
|
22125
|
+
}
|
|
22126
|
+
if (incrementsNesting) {
|
|
22127
|
+
nextNesting = currentNesting + 1;
|
|
22128
|
+
}
|
|
22129
|
+
traverseChildren(n, (child) => traverseForComplexity(child, nextNesting));
|
|
22130
|
+
}
|
|
22131
|
+
traverseForComplexity(node, 0);
|
|
22132
|
+
maxNestingDepth = Math.max(maxNestingDepth, localMaxNesting);
|
|
22133
|
+
cognitiveComplexities.push(cognitiveComplexity);
|
|
22134
|
+
}
|
|
22135
|
+
function collectNames(node) {
|
|
22136
|
+
switch (node.type) {
|
|
22137
|
+
case "FunctionDeclaration":
|
|
22138
|
+
if (node.id) {
|
|
22139
|
+
totalNames++;
|
|
22140
|
+
if (isGoodName(node.id.name)) {
|
|
22141
|
+
goodNames++;
|
|
22142
|
+
}
|
|
22143
|
+
}
|
|
22144
|
+
for (const param of node.params) {
|
|
22145
|
+
collectParamNames(param);
|
|
22146
|
+
}
|
|
22147
|
+
break;
|
|
22148
|
+
case "FunctionExpression":
|
|
22149
|
+
case "ArrowFunctionExpression":
|
|
22150
|
+
for (const param of node.params) {
|
|
22151
|
+
collectParamNames(param);
|
|
22152
|
+
}
|
|
22153
|
+
break;
|
|
22154
|
+
case "VariableDeclarator":
|
|
22155
|
+
if (node.id.type === "Identifier") {
|
|
22156
|
+
totalNames++;
|
|
22157
|
+
if (isGoodName(node.id.name)) {
|
|
22158
|
+
goodNames++;
|
|
22159
|
+
}
|
|
22160
|
+
}
|
|
22161
|
+
break;
|
|
22162
|
+
case "MethodDefinition":
|
|
22163
|
+
if (node.key.type === "Identifier") {
|
|
22164
|
+
totalNames++;
|
|
22165
|
+
if (isGoodName(node.key.name)) {
|
|
22166
|
+
goodNames++;
|
|
22167
|
+
}
|
|
22168
|
+
}
|
|
22169
|
+
if (node.value.type === "FunctionExpression") {
|
|
22170
|
+
for (const param of node.value.params) {
|
|
22171
|
+
collectParamNames(param);
|
|
22172
|
+
}
|
|
22173
|
+
}
|
|
22174
|
+
break;
|
|
22175
|
+
}
|
|
22176
|
+
}
|
|
22177
|
+
function collectParamNames(param) {
|
|
22178
|
+
if (param.type === "Identifier") {
|
|
22179
|
+
totalNames++;
|
|
22180
|
+
if (isGoodName(param.name)) {
|
|
22181
|
+
goodNames++;
|
|
22182
|
+
}
|
|
22183
|
+
} else if (param.type === "AssignmentPattern" && param.left.type === "Identifier") {
|
|
22184
|
+
totalNames++;
|
|
22185
|
+
if (isGoodName(param.left.name)) {
|
|
22186
|
+
goodNames++;
|
|
22187
|
+
}
|
|
22188
|
+
} else if (param.type === "RestElement" && param.argument.type === "Identifier") {
|
|
22189
|
+
totalNames++;
|
|
22190
|
+
if (isGoodName(param.argument.name)) {
|
|
22191
|
+
goodNames++;
|
|
22192
|
+
}
|
|
22193
|
+
}
|
|
22194
|
+
}
|
|
22195
|
+
function traverse(node) {
|
|
22196
|
+
const isFunctionNode = node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "MethodDefinition";
|
|
22197
|
+
collectNames(node);
|
|
22198
|
+
if (isFunctionNode) {
|
|
22199
|
+
analyzeFunction(node);
|
|
22200
|
+
}
|
|
22201
|
+
traverseChildren(node, traverse);
|
|
22202
|
+
}
|
|
22203
|
+
function traverseChildren(node, callback) {
|
|
22204
|
+
for (const key of Object.keys(node)) {
|
|
22205
|
+
if (key === "parent") continue;
|
|
22206
|
+
const child = node[key];
|
|
22207
|
+
if (child && typeof child === "object") {
|
|
22208
|
+
if (Array.isArray(child)) {
|
|
22209
|
+
for (const item of child) {
|
|
22210
|
+
if (item && typeof item === "object" && item.type) {
|
|
22211
|
+
callback(item);
|
|
22212
|
+
}
|
|
22213
|
+
}
|
|
22214
|
+
} else if (child.type) {
|
|
22215
|
+
callback(child);
|
|
22216
|
+
}
|
|
22217
|
+
}
|
|
22218
|
+
}
|
|
22219
|
+
}
|
|
22220
|
+
traverse(ast);
|
|
22221
|
+
return {
|
|
22222
|
+
goodNames,
|
|
22223
|
+
totalNames,
|
|
22224
|
+
functionLengths,
|
|
22225
|
+
maxNestingDepth,
|
|
22226
|
+
cognitiveComplexities
|
|
22227
|
+
};
|
|
22228
|
+
}
|
|
22229
|
+
var ReadabilityAnalyzer = class {
|
|
22230
|
+
constructor(projectPath) {
|
|
22231
|
+
this.projectPath = projectPath;
|
|
22232
|
+
}
|
|
22233
|
+
/**
|
|
22234
|
+
* Analyze readability of project files
|
|
22235
|
+
*/
|
|
22236
|
+
async analyze(files) {
|
|
22237
|
+
const targetFiles = files ?? await this.findSourceFiles();
|
|
22238
|
+
let totalGoodNames = 0;
|
|
22239
|
+
let totalNames = 0;
|
|
22240
|
+
const allFunctionLengths = [];
|
|
22241
|
+
let globalMaxNestingDepth = 0;
|
|
22242
|
+
const allCognitiveComplexities = [];
|
|
22243
|
+
for (const file of targetFiles) {
|
|
22244
|
+
try {
|
|
22245
|
+
const content = await readFile(file, "utf-8");
|
|
22246
|
+
const ast = parse(content, {
|
|
22247
|
+
loc: true,
|
|
22248
|
+
range: true,
|
|
22249
|
+
jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
|
|
22250
|
+
});
|
|
22251
|
+
const result = analyzeReadabilityPatterns(ast);
|
|
22252
|
+
totalGoodNames += result.goodNames;
|
|
22253
|
+
totalNames += result.totalNames;
|
|
22254
|
+
allFunctionLengths.push(...result.functionLengths);
|
|
22255
|
+
globalMaxNestingDepth = Math.max(globalMaxNestingDepth, result.maxNestingDepth);
|
|
22256
|
+
allCognitiveComplexities.push(...result.cognitiveComplexities);
|
|
22257
|
+
} catch {
|
|
22258
|
+
}
|
|
22259
|
+
}
|
|
22260
|
+
const namingScore = totalNames > 0 ? totalGoodNames / totalNames * 100 : 100;
|
|
22261
|
+
const averageFunctionLength = allFunctionLengths.length > 0 ? allFunctionLengths.reduce((a, b) => a + b, 0) / allFunctionLengths.length : 0;
|
|
22262
|
+
const functionLengthScore = allFunctionLengths.length > 0 ? Math.max(0, Math.min(100, 100 - (averageFunctionLength - 20) * 2.5)) : 100;
|
|
22263
|
+
const maxNestingDepth = globalMaxNestingDepth;
|
|
22264
|
+
const nestingDepthScore = Math.max(0, Math.min(100, 100 - (maxNestingDepth - 2) * 20));
|
|
22265
|
+
const averageCognitiveComplexity = allCognitiveComplexities.length > 0 ? allCognitiveComplexities.reduce((a, b) => a + b, 0) / allCognitiveComplexities.length : 0;
|
|
22266
|
+
const cognitiveComplexityScore = allCognitiveComplexities.length > 0 ? Math.max(0, Math.min(100, 100 - (averageCognitiveComplexity - 5) * 5)) : 100;
|
|
22267
|
+
const score = Math.round(
|
|
22268
|
+
namingScore * 0.25 + functionLengthScore * 0.25 + nestingDepthScore * 0.25 + cognitiveComplexityScore * 0.25
|
|
22269
|
+
);
|
|
22270
|
+
const details = [
|
|
22271
|
+
`${totalGoodNames}/${totalNames} good names`,
|
|
22272
|
+
`avg func ${averageFunctionLength.toFixed(1)} LOC`,
|
|
22273
|
+
`max nesting ${maxNestingDepth}`,
|
|
22274
|
+
`avg weighted complexity ${averageCognitiveComplexity.toFixed(1)}`
|
|
22275
|
+
].join(", ");
|
|
22276
|
+
return {
|
|
22277
|
+
score,
|
|
22278
|
+
namingScore,
|
|
22279
|
+
functionLengthScore,
|
|
22280
|
+
nestingDepthScore,
|
|
22281
|
+
cognitiveComplexityScore,
|
|
22282
|
+
averageFunctionLength,
|
|
22283
|
+
maxNestingDepth,
|
|
22284
|
+
averageCognitiveComplexity,
|
|
22285
|
+
details
|
|
22286
|
+
};
|
|
22287
|
+
}
|
|
22288
|
+
async findSourceFiles() {
|
|
22289
|
+
return glob("**/*.{ts,js,tsx,jsx}", {
|
|
22290
|
+
cwd: this.projectPath,
|
|
22291
|
+
absolute: true,
|
|
22292
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
|
|
22293
|
+
});
|
|
22294
|
+
}
|
|
22295
|
+
};
|
|
22296
|
+
function analyzeMaintainabilityPatterns(ast, filePath) {
|
|
22297
|
+
let functionCount = 0;
|
|
22298
|
+
let importCount = 0;
|
|
22299
|
+
let crossBoundaryImportCount = 0;
|
|
22300
|
+
function traverse(node) {
|
|
22301
|
+
switch (node.type) {
|
|
22302
|
+
case "FunctionDeclaration":
|
|
22303
|
+
case "FunctionExpression":
|
|
22304
|
+
case "ArrowFunctionExpression":
|
|
22305
|
+
case "MethodDefinition":
|
|
22306
|
+
functionCount++;
|
|
22307
|
+
break;
|
|
22308
|
+
case "ImportDeclaration":
|
|
22309
|
+
importCount++;
|
|
22310
|
+
if (isCrossBoundaryImport(node)) {
|
|
22311
|
+
crossBoundaryImportCount++;
|
|
22312
|
+
}
|
|
22313
|
+
break;
|
|
22314
|
+
}
|
|
22315
|
+
traverseChildren(node);
|
|
22316
|
+
}
|
|
22317
|
+
function traverseChildren(node) {
|
|
22318
|
+
for (const key of Object.keys(node)) {
|
|
22319
|
+
if (key === "parent") continue;
|
|
22320
|
+
const child = node[key];
|
|
22321
|
+
if (child && typeof child === "object") {
|
|
22322
|
+
if (Array.isArray(child)) {
|
|
22323
|
+
for (const item of child) {
|
|
22324
|
+
if (item && typeof item === "object" && item.type) {
|
|
22325
|
+
traverse(item);
|
|
22326
|
+
}
|
|
22327
|
+
}
|
|
22328
|
+
} else if (child.type) {
|
|
22329
|
+
traverse(child);
|
|
22330
|
+
}
|
|
22331
|
+
}
|
|
22332
|
+
}
|
|
22333
|
+
}
|
|
22334
|
+
traverse(ast);
|
|
22335
|
+
return {
|
|
22336
|
+
lineCount: 0,
|
|
22337
|
+
// Will be set separately from content
|
|
22338
|
+
functionCount,
|
|
22339
|
+
importCount,
|
|
22340
|
+
crossBoundaryImportCount
|
|
22341
|
+
};
|
|
22342
|
+
}
|
|
22343
|
+
function isCrossBoundaryImport(node, _filePath) {
|
|
22344
|
+
if (node.source.type !== "Literal" || typeof node.source.value !== "string") {
|
|
22345
|
+
return false;
|
|
22346
|
+
}
|
|
22347
|
+
const importPath = node.source.value;
|
|
22348
|
+
if (importPath.startsWith("node:")) {
|
|
22349
|
+
return false;
|
|
22350
|
+
}
|
|
22351
|
+
if (!importPath.startsWith(".")) {
|
|
22352
|
+
return false;
|
|
22353
|
+
}
|
|
22354
|
+
if (importPath.startsWith("../")) {
|
|
22355
|
+
return true;
|
|
22356
|
+
}
|
|
22357
|
+
if (importPath.startsWith("./")) {
|
|
22358
|
+
const relativePath = importPath.slice(2);
|
|
22359
|
+
return relativePath.includes("/");
|
|
22360
|
+
}
|
|
22361
|
+
return false;
|
|
22362
|
+
}
|
|
22363
|
+
function countLines(content) {
|
|
22364
|
+
return content.split("\n").length;
|
|
22365
|
+
}
|
|
22366
|
+
var MaintainabilityAnalyzer = class {
|
|
22367
|
+
constructor(projectPath) {
|
|
22368
|
+
this.projectPath = projectPath;
|
|
22369
|
+
}
|
|
22370
|
+
/**
|
|
22371
|
+
* Analyze maintainability of project files
|
|
22372
|
+
*/
|
|
22373
|
+
async analyze(files) {
|
|
22374
|
+
const targetFiles = files ?? await this.findSourceFiles();
|
|
22375
|
+
if (targetFiles.length === 0) {
|
|
22376
|
+
return {
|
|
22377
|
+
score: 100,
|
|
22378
|
+
fileLengthScore: 100,
|
|
22379
|
+
functionCountScore: 100,
|
|
22380
|
+
dependencyCountScore: 100,
|
|
22381
|
+
couplingScore: 100,
|
|
22382
|
+
averageFileLength: 0,
|
|
22383
|
+
averageFunctionsPerFile: 0,
|
|
22384
|
+
averageImportsPerFile: 0,
|
|
22385
|
+
fileCount: 0,
|
|
22386
|
+
details: "No files to analyze"
|
|
22387
|
+
};
|
|
22388
|
+
}
|
|
22389
|
+
let totalLines = 0;
|
|
22390
|
+
let totalFunctions = 0;
|
|
22391
|
+
let totalImports = 0;
|
|
22392
|
+
let totalCrossBoundaryImports = 0;
|
|
22393
|
+
let filesAnalyzed = 0;
|
|
22394
|
+
for (const file of targetFiles) {
|
|
22395
|
+
try {
|
|
22396
|
+
const content = await readFile(file, "utf-8");
|
|
22397
|
+
const lineCount = countLines(content);
|
|
22398
|
+
const ast = parse(content, {
|
|
22399
|
+
loc: true,
|
|
22400
|
+
range: true,
|
|
22401
|
+
jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
|
|
22402
|
+
});
|
|
22403
|
+
const result = analyzeMaintainabilityPatterns(ast, file);
|
|
22404
|
+
totalLines += lineCount;
|
|
22405
|
+
totalFunctions += result.functionCount;
|
|
22406
|
+
totalImports += result.importCount;
|
|
22407
|
+
totalCrossBoundaryImports += result.crossBoundaryImportCount;
|
|
22408
|
+
filesAnalyzed++;
|
|
22409
|
+
} catch {
|
|
22410
|
+
}
|
|
22411
|
+
}
|
|
22412
|
+
const averageFileLength = filesAnalyzed > 0 ? totalLines / filesAnalyzed : 0;
|
|
22413
|
+
const averageFunctionsPerFile = filesAnalyzed > 0 ? totalFunctions / filesAnalyzed : 0;
|
|
22414
|
+
const averageImportsPerFile = filesAnalyzed > 0 ? totalImports / filesAnalyzed : 0;
|
|
22415
|
+
const fileLengthScore = Math.max(0, Math.min(100, 100 - (averageFileLength - 200) * 0.33));
|
|
22416
|
+
const functionCountScore = Math.max(0, Math.min(100, 100 - (averageFunctionsPerFile - 10) * 5));
|
|
22417
|
+
const dependencyCountScore = Math.max(0, Math.min(100, 100 - (averageImportsPerFile - 5) * 5));
|
|
22418
|
+
const crossBoundaryRatio = totalImports > 0 ? totalCrossBoundaryImports / totalImports : 0;
|
|
22419
|
+
const couplingScore = Math.max(0, Math.min(100, 100 - crossBoundaryRatio * 100 * 0.5));
|
|
22420
|
+
const score = Math.round(
|
|
22421
|
+
fileLengthScore * 0.3 + functionCountScore * 0.25 + dependencyCountScore * 0.25 + couplingScore * 0.2
|
|
22422
|
+
);
|
|
22423
|
+
const details = [
|
|
22424
|
+
`${Math.round(averageFileLength)} avg lines/file`,
|
|
22425
|
+
`${averageFunctionsPerFile.toFixed(1)} avg functions/file`,
|
|
22426
|
+
`${averageImportsPerFile.toFixed(1)} avg imports/file`,
|
|
22427
|
+
`${Math.round(crossBoundaryRatio * 100)}% cross-boundary coupling`
|
|
22428
|
+
].join(", ");
|
|
22429
|
+
return {
|
|
22430
|
+
score,
|
|
22431
|
+
fileLengthScore,
|
|
22432
|
+
functionCountScore,
|
|
22433
|
+
dependencyCountScore,
|
|
22434
|
+
couplingScore,
|
|
22435
|
+
averageFileLength,
|
|
22436
|
+
averageFunctionsPerFile,
|
|
22437
|
+
averageImportsPerFile,
|
|
22438
|
+
fileCount: filesAnalyzed,
|
|
22439
|
+
details
|
|
22440
|
+
};
|
|
22441
|
+
}
|
|
22442
|
+
async findSourceFiles() {
|
|
22443
|
+
return glob("**/*.{ts,js,tsx,jsx}", {
|
|
22444
|
+
cwd: this.projectPath,
|
|
22445
|
+
absolute: true,
|
|
22446
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
|
|
22447
|
+
});
|
|
22448
|
+
}
|
|
22449
|
+
};
|
|
22450
|
+
|
|
22451
|
+
// src/quality/types.ts
|
|
22452
|
+
var DEFAULT_QUALITY_WEIGHTS = {
|
|
22453
|
+
correctness: 0.15,
|
|
22454
|
+
completeness: 0.1,
|
|
22455
|
+
robustness: 0.1,
|
|
22456
|
+
readability: 0.1,
|
|
22457
|
+
maintainability: 0.1,
|
|
22458
|
+
complexity: 0.08,
|
|
22459
|
+
duplication: 0.07,
|
|
22460
|
+
testCoverage: 0.1,
|
|
22461
|
+
testQuality: 0.05,
|
|
22462
|
+
security: 0.08,
|
|
22463
|
+
documentation: 0.04,
|
|
22464
|
+
style: 0.03
|
|
22465
|
+
};
|
|
22466
|
+
var DEFAULT_QUALITY_THRESHOLDS = {
|
|
22467
|
+
minimum: {
|
|
22468
|
+
overall: 85,
|
|
22469
|
+
testCoverage: 80,
|
|
22470
|
+
security: 100
|
|
22471
|
+
// No vulnerabilities allowed
|
|
22472
|
+
},
|
|
22473
|
+
target: {
|
|
22474
|
+
overall: 95,
|
|
22475
|
+
testCoverage: 90
|
|
22476
|
+
}};
|
|
22477
|
+
var QualityEvaluator = class {
|
|
22478
|
+
constructor(projectPath, useSnyk = false) {
|
|
22479
|
+
this.projectPath = projectPath;
|
|
22480
|
+
this.coverageAnalyzer = new CoverageAnalyzer(projectPath);
|
|
22481
|
+
this.securityScanner = new CompositeSecurityScanner(projectPath, useSnyk);
|
|
22482
|
+
this.complexityAnalyzer = new ComplexityAnalyzer(projectPath);
|
|
22483
|
+
this.duplicationAnalyzer = new DuplicationAnalyzer(projectPath);
|
|
22484
|
+
this.correctnessAnalyzer = new CorrectnessAnalyzer(projectPath);
|
|
22485
|
+
this.completenessAnalyzer = new CompletenessAnalyzer(projectPath);
|
|
22486
|
+
this.robustnessAnalyzer = new RobustnessAnalyzer(projectPath);
|
|
22487
|
+
this.testQualityAnalyzer = new TestQualityAnalyzer(projectPath);
|
|
22488
|
+
this.documentationAnalyzer = new DocumentationAnalyzer(projectPath);
|
|
22489
|
+
this.styleAnalyzer = new StyleAnalyzer(projectPath);
|
|
22490
|
+
this.readabilityAnalyzer = new ReadabilityAnalyzer(projectPath);
|
|
22491
|
+
this.maintainabilityAnalyzer = new MaintainabilityAnalyzer(projectPath);
|
|
22492
|
+
}
|
|
22493
|
+
coverageAnalyzer;
|
|
22494
|
+
securityScanner;
|
|
22495
|
+
complexityAnalyzer;
|
|
22496
|
+
duplicationAnalyzer;
|
|
22497
|
+
correctnessAnalyzer;
|
|
22498
|
+
completenessAnalyzer;
|
|
22499
|
+
robustnessAnalyzer;
|
|
22500
|
+
testQualityAnalyzer;
|
|
22501
|
+
documentationAnalyzer;
|
|
22502
|
+
styleAnalyzer;
|
|
22503
|
+
readabilityAnalyzer;
|
|
22504
|
+
maintainabilityAnalyzer;
|
|
22505
|
+
/**
|
|
22506
|
+
* Evaluate quality across all 12 dimensions
|
|
22507
|
+
* Every dimension is computed by real static analysis — zero hardcoded values
|
|
22508
|
+
*/
|
|
22509
|
+
async evaluate(files) {
|
|
22510
|
+
const startTime = performance.now();
|
|
22511
|
+
const targetFiles = files ?? await this.findSourceFiles();
|
|
22512
|
+
const fileContents = await Promise.all(
|
|
22513
|
+
targetFiles.map(async (file) => ({
|
|
22514
|
+
path: file,
|
|
22515
|
+
content: await readFile(file, "utf-8")
|
|
22516
|
+
}))
|
|
22517
|
+
);
|
|
22518
|
+
const [
|
|
22519
|
+
coverageResult,
|
|
22520
|
+
securityResult,
|
|
22521
|
+
complexityResult,
|
|
22522
|
+
duplicationResult,
|
|
22523
|
+
correctnessResult,
|
|
22524
|
+
completenessResult,
|
|
22525
|
+
robustnessResult,
|
|
22526
|
+
testQualityResult,
|
|
22527
|
+
documentationResult,
|
|
22528
|
+
styleResult,
|
|
22529
|
+
readabilityResult,
|
|
22530
|
+
maintainabilityResult
|
|
22531
|
+
] = await Promise.all([
|
|
22532
|
+
this.coverageAnalyzer.analyze().catch(() => null),
|
|
22533
|
+
this.securityScanner.scan(fileContents).catch(() => ({ score: 0, vulnerabilities: [] })),
|
|
22534
|
+
this.complexityAnalyzer.analyze(targetFiles).catch(() => ({ score: 0, files: [] })),
|
|
22535
|
+
this.duplicationAnalyzer.analyze(targetFiles).catch(() => ({ score: 0, percentage: 0 })),
|
|
22536
|
+
this.correctnessAnalyzer.analyze().catch(() => ({ score: 0 })),
|
|
22537
|
+
this.completenessAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
|
|
22538
|
+
this.robustnessAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
|
|
22539
|
+
this.testQualityAnalyzer.analyze().catch(() => ({ score: 0 })),
|
|
22540
|
+
this.documentationAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
|
|
22541
|
+
this.styleAnalyzer.analyze().catch(() => ({ score: 0 })),
|
|
22542
|
+
this.readabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 })),
|
|
22543
|
+
this.maintainabilityAnalyzer.analyze(targetFiles).catch(() => ({ score: 0 }))
|
|
22544
|
+
]);
|
|
22545
|
+
const dimensions = {
|
|
22546
|
+
testCoverage: coverageResult?.lines.percentage ?? 0,
|
|
22547
|
+
security: securityResult.score,
|
|
22548
|
+
complexity: complexityResult.score,
|
|
22549
|
+
duplication: Math.max(0, 100 - duplicationResult.percentage),
|
|
22550
|
+
style: styleResult.score,
|
|
22551
|
+
readability: readabilityResult.score,
|
|
22552
|
+
maintainability: maintainabilityResult.score,
|
|
22553
|
+
correctness: correctnessResult.score,
|
|
22554
|
+
completeness: completenessResult.score,
|
|
22555
|
+
robustness: robustnessResult.score,
|
|
22556
|
+
testQuality: testQualityResult.score,
|
|
22557
|
+
documentation: documentationResult.score
|
|
22558
|
+
};
|
|
22559
|
+
const overall = Object.entries(dimensions).reduce((sum, [key, value]) => {
|
|
22560
|
+
const weight = DEFAULT_QUALITY_WEIGHTS[key] ?? 0;
|
|
22561
|
+
return sum + value * weight;
|
|
22562
|
+
}, 0);
|
|
22563
|
+
const scores = {
|
|
22564
|
+
overall: Math.round(overall),
|
|
22565
|
+
dimensions,
|
|
22566
|
+
evaluatedAt: /* @__PURE__ */ new Date(),
|
|
22567
|
+
evaluationDurationMs: performance.now() - startTime
|
|
22568
|
+
};
|
|
22569
|
+
const issues = this.generateIssues(
|
|
22570
|
+
securityResult.vulnerabilities,
|
|
22571
|
+
complexityResult,
|
|
22572
|
+
duplicationResult,
|
|
22573
|
+
correctnessResult,
|
|
22574
|
+
styleResult,
|
|
22575
|
+
documentationResult
|
|
22576
|
+
);
|
|
22577
|
+
const suggestions = this.generateSuggestions(dimensions);
|
|
22578
|
+
const meetsMinimum = scores.overall >= DEFAULT_QUALITY_THRESHOLDS.minimum.overall && dimensions.testCoverage >= DEFAULT_QUALITY_THRESHOLDS.minimum.testCoverage && dimensions.security >= DEFAULT_QUALITY_THRESHOLDS.minimum.security;
|
|
22579
|
+
const meetsTarget = scores.overall >= DEFAULT_QUALITY_THRESHOLDS.target.overall && dimensions.testCoverage >= DEFAULT_QUALITY_THRESHOLDS.target.testCoverage;
|
|
22580
|
+
return {
|
|
22581
|
+
scores,
|
|
22582
|
+
meetsMinimum,
|
|
22583
|
+
meetsTarget,
|
|
22584
|
+
converged: false,
|
|
22585
|
+
issues,
|
|
22586
|
+
suggestions
|
|
22587
|
+
};
|
|
22588
|
+
}
|
|
22589
|
+
/**
|
|
22590
|
+
* Generate quality issues from analyzer results
|
|
22591
|
+
*/
|
|
22592
|
+
generateIssues(securityVulns, complexityResult, duplicationResult, correctnessResult, styleResult, documentationResult) {
|
|
22593
|
+
const issues = [];
|
|
22594
|
+
for (const vuln of securityVulns) {
|
|
20643
22595
|
issues.push({
|
|
20644
22596
|
dimension: "security",
|
|
20645
22597
|
severity: vuln.severity === "critical" ? "critical" : vuln.severity === "high" ? "major" : "minor",
|
|
@@ -20670,6 +22622,38 @@ var QualityEvaluator = class {
|
|
|
20670
22622
|
suggestion: "Extract common code into reusable functions or modules"
|
|
20671
22623
|
});
|
|
20672
22624
|
}
|
|
22625
|
+
if (correctnessResult.testsFailed != null && correctnessResult.testsFailed > 0) {
|
|
22626
|
+
issues.push({
|
|
22627
|
+
dimension: "correctness",
|
|
22628
|
+
severity: "critical",
|
|
22629
|
+
message: `${correctnessResult.testsFailed} tests failing`,
|
|
22630
|
+
suggestion: "Fix failing tests to improve correctness score"
|
|
22631
|
+
});
|
|
22632
|
+
}
|
|
22633
|
+
if (correctnessResult.buildSuccess === false) {
|
|
22634
|
+
issues.push({
|
|
22635
|
+
dimension: "correctness",
|
|
22636
|
+
severity: "critical",
|
|
22637
|
+
message: "Build/type check failed",
|
|
22638
|
+
suggestion: "Fix type errors to pass build verification"
|
|
22639
|
+
});
|
|
22640
|
+
}
|
|
22641
|
+
if (styleResult.errors != null && styleResult.errors > 0) {
|
|
22642
|
+
issues.push({
|
|
22643
|
+
dimension: "style",
|
|
22644
|
+
severity: "minor",
|
|
22645
|
+
message: `${styleResult.errors} linting errors found`,
|
|
22646
|
+
suggestion: "Run linter with --fix to auto-correct style issues"
|
|
22647
|
+
});
|
|
22648
|
+
}
|
|
22649
|
+
if (documentationResult.jsdocCoverage !== void 0 && documentationResult.jsdocCoverage < 50) {
|
|
22650
|
+
issues.push({
|
|
22651
|
+
dimension: "documentation",
|
|
22652
|
+
severity: "minor",
|
|
22653
|
+
message: `Low JSDoc coverage: ${documentationResult.jsdocCoverage?.toFixed(1) ?? 0}%`,
|
|
22654
|
+
suggestion: "Add JSDoc comments to exported functions and classes"
|
|
22655
|
+
});
|
|
22656
|
+
}
|
|
20673
22657
|
return issues;
|
|
20674
22658
|
}
|
|
20675
22659
|
/**
|
|
@@ -20693,6 +22677,14 @@ var QualityEvaluator = class {
|
|
|
20693
22677
|
estimatedImpact: 100 - dimensions.security
|
|
20694
22678
|
});
|
|
20695
22679
|
}
|
|
22680
|
+
if (dimensions.correctness < 85) {
|
|
22681
|
+
suggestions.push({
|
|
22682
|
+
dimension: "correctness",
|
|
22683
|
+
priority: "high",
|
|
22684
|
+
description: "Fix failing tests and build errors",
|
|
22685
|
+
estimatedImpact: 85 - dimensions.correctness
|
|
22686
|
+
});
|
|
22687
|
+
}
|
|
20696
22688
|
if (dimensions.complexity < 80) {
|
|
20697
22689
|
suggestions.push({
|
|
20698
22690
|
dimension: "complexity",
|
|
@@ -20701,6 +22693,22 @@ var QualityEvaluator = class {
|
|
|
20701
22693
|
estimatedImpact: Math.min(10, 80 - dimensions.complexity)
|
|
20702
22694
|
});
|
|
20703
22695
|
}
|
|
22696
|
+
if (dimensions.documentation < 60) {
|
|
22697
|
+
suggestions.push({
|
|
22698
|
+
dimension: "documentation",
|
|
22699
|
+
priority: "medium",
|
|
22700
|
+
description: "Add JSDoc comments to exported declarations",
|
|
22701
|
+
estimatedImpact: Math.min(15, 60 - dimensions.documentation)
|
|
22702
|
+
});
|
|
22703
|
+
}
|
|
22704
|
+
if (dimensions.testQuality < 70) {
|
|
22705
|
+
suggestions.push({
|
|
22706
|
+
dimension: "testQuality",
|
|
22707
|
+
priority: "medium",
|
|
22708
|
+
description: "Replace trivial assertions with meaningful behavioral tests",
|
|
22709
|
+
estimatedImpact: Math.min(10, 70 - dimensions.testQuality)
|
|
22710
|
+
});
|
|
22711
|
+
}
|
|
20704
22712
|
if (dimensions.duplication < 95) {
|
|
20705
22713
|
suggestions.push({
|
|
20706
22714
|
dimension: "duplication",
|
|
@@ -20727,7 +22735,7 @@ function createQualityEvaluator(projectPath, useSnyk) {
|
|
|
20727
22735
|
}
|
|
20728
22736
|
|
|
20729
22737
|
// src/tools/quality.ts
|
|
20730
|
-
async function
|
|
22738
|
+
async function detectLinter2(cwd) {
|
|
20731
22739
|
try {
|
|
20732
22740
|
const pkgPath = path20__default.join(cwd, "package.json");
|
|
20733
22741
|
const pkgContent = await fs22__default.readFile(pkgPath, "utf-8");
|
|
@@ -20762,7 +22770,7 @@ Examples:
|
|
|
20762
22770
|
}),
|
|
20763
22771
|
async execute({ cwd, files, fix, linter }) {
|
|
20764
22772
|
const projectDir = cwd ?? process.cwd();
|
|
20765
|
-
const detectedLinter = linter ?? await
|
|
22773
|
+
const detectedLinter = linter ?? await detectLinter2(projectDir);
|
|
20766
22774
|
if (!detectedLinter) {
|
|
20767
22775
|
return {
|
|
20768
22776
|
errors: 0,
|
|
@@ -20917,8 +22925,8 @@ Examples:
|
|
|
20917
22925
|
}
|
|
20918
22926
|
});
|
|
20919
22927
|
async function findSourceFiles(cwd) {
|
|
20920
|
-
const { glob:
|
|
20921
|
-
return
|
|
22928
|
+
const { glob: glob15 } = await import('glob');
|
|
22929
|
+
return glob15("src/**/*.{ts,js,tsx,jsx}", {
|
|
20922
22930
|
cwd,
|
|
20923
22931
|
absolute: true,
|
|
20924
22932
|
ignore: ["**/*.test.*", "**/*.spec.*", "**/node_modules/**"]
|
|
@@ -21046,7 +23054,17 @@ Examples:
|
|
|
21046
23054
|
regexPattern = `\\b${pattern}\\b`;
|
|
21047
23055
|
}
|
|
21048
23056
|
const flags = caseSensitive ? "g" : "gi";
|
|
21049
|
-
|
|
23057
|
+
let regex;
|
|
23058
|
+
try {
|
|
23059
|
+
regex = new RegExp(regexPattern, flags);
|
|
23060
|
+
} catch {
|
|
23061
|
+
throw new ToolError(`Invalid regex pattern: ${pattern}`, { tool: "grep" });
|
|
23062
|
+
}
|
|
23063
|
+
if (/(\.\*|\.\+|\[.*\][*+])\s*(\.\*|\.\+|\[.*\][*+])/.test(regexPattern)) {
|
|
23064
|
+
throw new ToolError(`Regex pattern rejected: nested quantifiers may cause slow matching`, {
|
|
23065
|
+
tool: "grep"
|
|
23066
|
+
});
|
|
23067
|
+
}
|
|
21050
23068
|
const stats = await fs22__default.stat(targetPath);
|
|
21051
23069
|
let filesToSearch;
|
|
21052
23070
|
if (stats.isFile()) {
|
|
@@ -22184,9 +24202,11 @@ function parseDiff(raw) {
|
|
|
22184
24202
|
function parseFileBlock(lines, start) {
|
|
22185
24203
|
const diffLine = lines[start];
|
|
22186
24204
|
let i = start + 1;
|
|
22187
|
-
const
|
|
22188
|
-
const
|
|
22189
|
-
const
|
|
24205
|
+
const gitPrefix = "diff --git a/";
|
|
24206
|
+
const pathPart = diffLine.slice(gitPrefix.length);
|
|
24207
|
+
const lastBSlash = pathPart.lastIndexOf(" b/");
|
|
24208
|
+
const oldPath = lastBSlash >= 0 ? pathPart.slice(0, lastBSlash) : pathPart;
|
|
24209
|
+
const newPath = lastBSlash >= 0 ? pathPart.slice(lastBSlash + 3) : oldPath;
|
|
22190
24210
|
let fileType = "modified";
|
|
22191
24211
|
while (i < lines.length && !lines[i].startsWith("diff --git ")) {
|
|
22192
24212
|
const current = lines[i];
|
|
@@ -22332,7 +24352,7 @@ function renderDiff(diff, options) {
|
|
|
22332
24352
|
function renderFileBlock(file, opts) {
|
|
22333
24353
|
const { maxWidth, showLineNumbers, compact } = opts;
|
|
22334
24354
|
const lang = detectLanguage(file.path);
|
|
22335
|
-
const contentWidth = maxWidth - 4;
|
|
24355
|
+
const contentWidth = Math.max(1, maxWidth - 4);
|
|
22336
24356
|
const typeLabel = file.type === "modified" ? "modified" : file.type === "added" ? "new file" : file.type === "deleted" ? "deleted" : `renamed from ${file.oldPath}`;
|
|
22337
24357
|
const statsLabel = ` +${file.additions} -${file.deletions}`;
|
|
22338
24358
|
const title = ` ${file.path} (${typeLabel}${statsLabel}) `;
|
|
@@ -22379,12 +24399,8 @@ function renderFileBlock(file, opts) {
|
|
|
22379
24399
|
}
|
|
22380
24400
|
function formatLineNo(line, show) {
|
|
22381
24401
|
if (!show) return "";
|
|
22382
|
-
|
|
22383
|
-
|
|
22384
|
-
} else if (line.type === "delete") {
|
|
22385
|
-
return chalk12.dim(`${String(line.oldLineNo ?? "").padStart(4)} `);
|
|
22386
|
-
}
|
|
22387
|
-
return chalk12.dim(`${String(line.newLineNo ?? "").padStart(4)} `);
|
|
24402
|
+
const lineNo = line.type === "delete" ? line.oldLineNo : line.newLineNo;
|
|
24403
|
+
return chalk12.dim(`${String(lineNo ?? "").padStart(5)} `);
|
|
22388
24404
|
}
|
|
22389
24405
|
function getChangedLines(diff) {
|
|
22390
24406
|
const result = /* @__PURE__ */ new Map();
|
|
@@ -22475,9 +24491,9 @@ Examples:
|
|
|
22475
24491
|
}
|
|
22476
24492
|
});
|
|
22477
24493
|
var diffTools = [showDiffTool];
|
|
22478
|
-
async function fileExists(
|
|
24494
|
+
async function fileExists(path37) {
|
|
22479
24495
|
try {
|
|
22480
|
-
await access(
|
|
24496
|
+
await access(path37);
|
|
22481
24497
|
return true;
|
|
22482
24498
|
} catch {
|
|
22483
24499
|
return false;
|
|
@@ -22567,7 +24583,7 @@ async function detectMaturity(cwd) {
|
|
|
22567
24583
|
if (!hasLintConfig && hasPackageJson) {
|
|
22568
24584
|
try {
|
|
22569
24585
|
const pkgRaw = await import('fs/promises').then(
|
|
22570
|
-
(
|
|
24586
|
+
(fs39) => fs39.readFile(join(cwd, "package.json"), "utf-8")
|
|
22571
24587
|
);
|
|
22572
24588
|
const pkg = JSON.parse(pkgRaw);
|
|
22573
24589
|
if (pkg.scripts?.lint || pkg.scripts?.["lint:fix"]) {
|
|
@@ -22934,9 +24950,9 @@ Examples:
|
|
|
22934
24950
|
}
|
|
22935
24951
|
});
|
|
22936
24952
|
var reviewTools = [reviewCodeTool];
|
|
22937
|
-
var
|
|
22938
|
-
var
|
|
22939
|
-
var { glob:
|
|
24953
|
+
var fs28 = await import('fs/promises');
|
|
24954
|
+
var path26 = await import('path');
|
|
24955
|
+
var { glob: glob12 } = await import('glob');
|
|
22940
24956
|
var DEFAULT_MAX_FILES = 200;
|
|
22941
24957
|
var LANGUAGE_EXTENSIONS = {
|
|
22942
24958
|
typescript: [".ts", ".tsx", ".mts", ".cts"],
|
|
@@ -22961,7 +24977,7 @@ var DEFAULT_EXCLUDES = [
|
|
|
22961
24977
|
"**/*.d.ts"
|
|
22962
24978
|
];
|
|
22963
24979
|
function detectLanguage2(filePath) {
|
|
22964
|
-
const ext =
|
|
24980
|
+
const ext = path26.extname(filePath).toLowerCase();
|
|
22965
24981
|
for (const [lang, extensions] of Object.entries(LANGUAGE_EXTENSIONS)) {
|
|
22966
24982
|
if (extensions.includes(ext)) return lang;
|
|
22967
24983
|
}
|
|
@@ -23370,9 +25386,9 @@ Examples:
|
|
|
23370
25386
|
}),
|
|
23371
25387
|
async execute({ path: rootPath, include, exclude, languages, maxFiles, depth }) {
|
|
23372
25388
|
const startTime = performance.now();
|
|
23373
|
-
const absPath =
|
|
25389
|
+
const absPath = path26.resolve(rootPath);
|
|
23374
25390
|
try {
|
|
23375
|
-
const stat2 = await
|
|
25391
|
+
const stat2 = await fs28.stat(absPath);
|
|
23376
25392
|
if (!stat2.isDirectory()) {
|
|
23377
25393
|
throw new ToolError(`Path is not a directory: ${absPath}`, {
|
|
23378
25394
|
tool: "codebase_map"
|
|
@@ -23397,7 +25413,7 @@ Examples:
|
|
|
23397
25413
|
pattern = `**/*{${allExts.join(",")}}`;
|
|
23398
25414
|
}
|
|
23399
25415
|
const excludePatterns = [...DEFAULT_EXCLUDES, ...exclude ?? []];
|
|
23400
|
-
const files = await
|
|
25416
|
+
const files = await glob12(pattern, {
|
|
23401
25417
|
cwd: absPath,
|
|
23402
25418
|
ignore: excludePatterns,
|
|
23403
25419
|
nodir: true,
|
|
@@ -23409,14 +25425,14 @@ Examples:
|
|
|
23409
25425
|
let totalDefinitions = 0;
|
|
23410
25426
|
let exportedSymbols = 0;
|
|
23411
25427
|
for (const file of limitedFiles) {
|
|
23412
|
-
const fullPath =
|
|
25428
|
+
const fullPath = path26.join(absPath, file);
|
|
23413
25429
|
const language = detectLanguage2(file);
|
|
23414
25430
|
if (!language) continue;
|
|
23415
25431
|
if (languages && !languages.includes(language)) {
|
|
23416
25432
|
continue;
|
|
23417
25433
|
}
|
|
23418
25434
|
try {
|
|
23419
|
-
const content = await
|
|
25435
|
+
const content = await fs28.readFile(fullPath, "utf-8");
|
|
23420
25436
|
const lineCount = content.split("\n").length;
|
|
23421
25437
|
const parsed = parseFile(content, language);
|
|
23422
25438
|
const definitions = depth === "overview" ? parsed.definitions.filter((d) => d.exported) : parsed.definitions;
|
|
@@ -23449,23 +25465,23 @@ Examples:
|
|
|
23449
25465
|
});
|
|
23450
25466
|
var codebaseMapTools = [codebaseMapTool];
|
|
23451
25467
|
init_paths();
|
|
23452
|
-
var
|
|
23453
|
-
var
|
|
25468
|
+
var fs29 = await import('fs/promises');
|
|
25469
|
+
var path27 = await import('path');
|
|
23454
25470
|
var crypto2 = await import('crypto');
|
|
23455
|
-
var GLOBAL_MEMORIES_DIR =
|
|
25471
|
+
var GLOBAL_MEMORIES_DIR = path27.join(COCO_HOME, "memories");
|
|
23456
25472
|
var PROJECT_MEMORIES_DIR = ".coco/memories";
|
|
23457
25473
|
var DEFAULT_MAX_MEMORIES = 1e3;
|
|
23458
25474
|
async function ensureDir2(dirPath) {
|
|
23459
|
-
await
|
|
25475
|
+
await fs29.mkdir(dirPath, { recursive: true });
|
|
23460
25476
|
}
|
|
23461
25477
|
function getMemoriesDir(scope) {
|
|
23462
25478
|
return scope === "global" ? GLOBAL_MEMORIES_DIR : PROJECT_MEMORIES_DIR;
|
|
23463
25479
|
}
|
|
23464
25480
|
async function loadIndex(scope) {
|
|
23465
25481
|
const dir = getMemoriesDir(scope);
|
|
23466
|
-
const indexPath =
|
|
25482
|
+
const indexPath = path27.join(dir, "index.json");
|
|
23467
25483
|
try {
|
|
23468
|
-
const content = await
|
|
25484
|
+
const content = await fs29.readFile(indexPath, "utf-8");
|
|
23469
25485
|
return JSON.parse(content);
|
|
23470
25486
|
} catch {
|
|
23471
25487
|
return [];
|
|
@@ -23474,14 +25490,14 @@ async function loadIndex(scope) {
|
|
|
23474
25490
|
async function saveIndex(scope, index) {
|
|
23475
25491
|
const dir = getMemoriesDir(scope);
|
|
23476
25492
|
await ensureDir2(dir);
|
|
23477
|
-
const indexPath =
|
|
23478
|
-
await
|
|
25493
|
+
const indexPath = path27.join(dir, "index.json");
|
|
25494
|
+
await fs29.writeFile(indexPath, JSON.stringify(index, null, 2), "utf-8");
|
|
23479
25495
|
}
|
|
23480
25496
|
async function loadMemory(scope, id) {
|
|
23481
25497
|
const dir = getMemoriesDir(scope);
|
|
23482
|
-
const memPath =
|
|
25498
|
+
const memPath = path27.join(dir, `${id}.json`);
|
|
23483
25499
|
try {
|
|
23484
|
-
const content = await
|
|
25500
|
+
const content = await fs29.readFile(memPath, "utf-8");
|
|
23485
25501
|
return JSON.parse(content);
|
|
23486
25502
|
} catch {
|
|
23487
25503
|
return null;
|
|
@@ -23490,8 +25506,8 @@ async function loadMemory(scope, id) {
|
|
|
23490
25506
|
async function saveMemory(scope, memory) {
|
|
23491
25507
|
const dir = getMemoriesDir(scope);
|
|
23492
25508
|
await ensureDir2(dir);
|
|
23493
|
-
const memPath =
|
|
23494
|
-
await
|
|
25509
|
+
const memPath = path27.join(dir, `${memory.id}.json`);
|
|
25510
|
+
await fs29.writeFile(memPath, JSON.stringify(memory, null, 2), "utf-8");
|
|
23495
25511
|
}
|
|
23496
25512
|
var createMemoryTool = defineTool({
|
|
23497
25513
|
name: "create_memory",
|
|
@@ -23643,17 +25659,17 @@ Examples:
|
|
|
23643
25659
|
}
|
|
23644
25660
|
});
|
|
23645
25661
|
var memoryTools = [createMemoryTool, recallMemoryTool, listMemoriesTool];
|
|
23646
|
-
var
|
|
25662
|
+
var fs30 = await import('fs/promises');
|
|
23647
25663
|
var crypto3 = await import('crypto');
|
|
23648
25664
|
var CHECKPOINT_FILE = ".coco/checkpoints.json";
|
|
23649
25665
|
var DEFAULT_MAX_CHECKPOINTS = 50;
|
|
23650
25666
|
var STASH_PREFIX = "coco-cp";
|
|
23651
25667
|
async function ensureCocoDir() {
|
|
23652
|
-
await
|
|
25668
|
+
await fs30.mkdir(".coco", { recursive: true });
|
|
23653
25669
|
}
|
|
23654
25670
|
async function loadCheckpoints() {
|
|
23655
25671
|
try {
|
|
23656
|
-
const content = await
|
|
25672
|
+
const content = await fs30.readFile(CHECKPOINT_FILE, "utf-8");
|
|
23657
25673
|
return JSON.parse(content);
|
|
23658
25674
|
} catch {
|
|
23659
25675
|
return [];
|
|
@@ -23661,7 +25677,7 @@ async function loadCheckpoints() {
|
|
|
23661
25677
|
}
|
|
23662
25678
|
async function saveCheckpoints(checkpoints) {
|
|
23663
25679
|
await ensureCocoDir();
|
|
23664
|
-
await
|
|
25680
|
+
await fs30.writeFile(CHECKPOINT_FILE, JSON.stringify(checkpoints, null, 2), "utf-8");
|
|
23665
25681
|
}
|
|
23666
25682
|
async function execGit(args) {
|
|
23667
25683
|
const { execaCommand } = await import('execa');
|
|
@@ -23821,9 +25837,9 @@ Examples:
|
|
|
23821
25837
|
}
|
|
23822
25838
|
});
|
|
23823
25839
|
var checkpointTools = [createCheckpointTool, restoreCheckpointTool, listCheckpointsTool];
|
|
23824
|
-
var
|
|
23825
|
-
var
|
|
23826
|
-
var { glob:
|
|
25840
|
+
var fs31 = await import('fs/promises');
|
|
25841
|
+
var path28 = await import('path');
|
|
25842
|
+
var { glob: glob13 } = await import('glob');
|
|
23827
25843
|
var INDEX_DIR = ".coco/search-index";
|
|
23828
25844
|
var DEFAULT_CHUNK_SIZE = 20;
|
|
23829
25845
|
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -23948,20 +25964,20 @@ async function getEmbedding(text9) {
|
|
|
23948
25964
|
}
|
|
23949
25965
|
async function loadIndex2(indexDir) {
|
|
23950
25966
|
try {
|
|
23951
|
-
const indexPath =
|
|
23952
|
-
const content = await
|
|
25967
|
+
const indexPath = path28.join(indexDir, "index.json");
|
|
25968
|
+
const content = await fs31.readFile(indexPath, "utf-8");
|
|
23953
25969
|
return JSON.parse(content);
|
|
23954
25970
|
} catch {
|
|
23955
25971
|
return null;
|
|
23956
25972
|
}
|
|
23957
25973
|
}
|
|
23958
25974
|
async function saveIndex2(indexDir, index) {
|
|
23959
|
-
await
|
|
23960
|
-
const indexPath =
|
|
23961
|
-
await
|
|
25975
|
+
await fs31.mkdir(indexDir, { recursive: true });
|
|
25976
|
+
const indexPath = path28.join(indexDir, "index.json");
|
|
25977
|
+
await fs31.writeFile(indexPath, JSON.stringify(index), "utf-8");
|
|
23962
25978
|
}
|
|
23963
25979
|
function isBinary(filePath) {
|
|
23964
|
-
return BINARY_EXTENSIONS.has(
|
|
25980
|
+
return BINARY_EXTENSIONS.has(path28.extname(filePath).toLowerCase());
|
|
23965
25981
|
}
|
|
23966
25982
|
var semanticSearchTool = defineTool({
|
|
23967
25983
|
name: "semantic_search",
|
|
@@ -23986,12 +26002,12 @@ Examples:
|
|
|
23986
26002
|
const effectivePath = rootPath ?? ".";
|
|
23987
26003
|
const effectiveMaxResults = maxResults ?? 10;
|
|
23988
26004
|
const effectiveThreshold = threshold ?? 0.3;
|
|
23989
|
-
const absPath =
|
|
23990
|
-
const indexDir =
|
|
26005
|
+
const absPath = path28.resolve(effectivePath);
|
|
26006
|
+
const indexDir = path28.join(absPath, INDEX_DIR);
|
|
23991
26007
|
let index = reindex ? null : await loadIndex2(indexDir);
|
|
23992
26008
|
if (!index) {
|
|
23993
26009
|
const pattern = include ?? "**/*";
|
|
23994
|
-
const files = await
|
|
26010
|
+
const files = await glob13(pattern, {
|
|
23995
26011
|
cwd: absPath,
|
|
23996
26012
|
ignore: DEFAULT_EXCLUDES2,
|
|
23997
26013
|
nodir: true,
|
|
@@ -24000,10 +26016,10 @@ Examples:
|
|
|
24000
26016
|
const chunks = [];
|
|
24001
26017
|
for (const file of files) {
|
|
24002
26018
|
if (isBinary(file)) continue;
|
|
24003
|
-
const fullPath =
|
|
26019
|
+
const fullPath = path28.join(absPath, file);
|
|
24004
26020
|
try {
|
|
24005
|
-
const stat2 = await
|
|
24006
|
-
const content = await
|
|
26021
|
+
const stat2 = await fs31.stat(fullPath);
|
|
26022
|
+
const content = await fs31.readFile(fullPath, "utf-8");
|
|
24007
26023
|
if (content.length > 1e5) continue;
|
|
24008
26024
|
const fileChunks = chunkContent(content, DEFAULT_CHUNK_SIZE);
|
|
24009
26025
|
for (const chunk of fileChunks) {
|
|
@@ -24062,12 +26078,12 @@ Examples:
|
|
|
24062
26078
|
}
|
|
24063
26079
|
});
|
|
24064
26080
|
var semanticSearchTools = [semanticSearchTool];
|
|
24065
|
-
var
|
|
24066
|
-
var
|
|
24067
|
-
var { glob:
|
|
26081
|
+
var fs32 = await import('fs/promises');
|
|
26082
|
+
var path29 = await import('path');
|
|
26083
|
+
var { glob: glob14 } = await import('glob');
|
|
24068
26084
|
async function parseClassRelationships(rootPath, include) {
|
|
24069
26085
|
const pattern = include ?? "**/*.{ts,tsx,js,jsx}";
|
|
24070
|
-
const files = await
|
|
26086
|
+
const files = await glob14(pattern, {
|
|
24071
26087
|
cwd: rootPath,
|
|
24072
26088
|
ignore: ["**/node_modules/**", "**/dist/**", "**/*.test.*", "**/*.d.ts"],
|
|
24073
26089
|
nodir: true
|
|
@@ -24076,7 +26092,7 @@ async function parseClassRelationships(rootPath, include) {
|
|
|
24076
26092
|
const interfaces = [];
|
|
24077
26093
|
for (const file of files.slice(0, 100)) {
|
|
24078
26094
|
try {
|
|
24079
|
-
const content = await
|
|
26095
|
+
const content = await fs32.readFile(path29.join(rootPath, file), "utf-8");
|
|
24080
26096
|
const lines = content.split("\n");
|
|
24081
26097
|
for (let i = 0; i < lines.length; i++) {
|
|
24082
26098
|
const line = lines[i];
|
|
@@ -24195,14 +26211,14 @@ async function generateClassDiagram(rootPath, include) {
|
|
|
24195
26211
|
};
|
|
24196
26212
|
}
|
|
24197
26213
|
async function generateArchitectureDiagram(rootPath) {
|
|
24198
|
-
const entries = await
|
|
26214
|
+
const entries = await fs32.readdir(rootPath, { withFileTypes: true });
|
|
24199
26215
|
const dirs = entries.filter(
|
|
24200
26216
|
(e) => e.isDirectory() && !e.name.startsWith(".") && !["node_modules", "dist", "build", "coverage", "__pycache__", "target"].includes(e.name)
|
|
24201
26217
|
);
|
|
24202
26218
|
const lines = ["graph TD"];
|
|
24203
26219
|
let nodeCount = 0;
|
|
24204
26220
|
let edgeCount = 0;
|
|
24205
|
-
const rootName =
|
|
26221
|
+
const rootName = path29.basename(rootPath);
|
|
24206
26222
|
lines.push(` ROOT["${rootName}"]`);
|
|
24207
26223
|
nodeCount++;
|
|
24208
26224
|
for (const dir of dirs) {
|
|
@@ -24212,7 +26228,7 @@ async function generateArchitectureDiagram(rootPath) {
|
|
|
24212
26228
|
nodeCount++;
|
|
24213
26229
|
edgeCount++;
|
|
24214
26230
|
try {
|
|
24215
|
-
const subEntries = await
|
|
26231
|
+
const subEntries = await fs32.readdir(path29.join(rootPath, dir.name), {
|
|
24216
26232
|
withFileTypes: true
|
|
24217
26233
|
});
|
|
24218
26234
|
const subDirs = subEntries.filter(
|
|
@@ -24335,7 +26351,7 @@ Examples:
|
|
|
24335
26351
|
tool: "generate_diagram"
|
|
24336
26352
|
});
|
|
24337
26353
|
}
|
|
24338
|
-
const absPath = rootPath ?
|
|
26354
|
+
const absPath = rootPath ? path29.resolve(rootPath) : process.cwd();
|
|
24339
26355
|
switch (type) {
|
|
24340
26356
|
case "class":
|
|
24341
26357
|
return generateClassDiagram(absPath, include);
|
|
@@ -24396,8 +26412,8 @@ Examples:
|
|
|
24396
26412
|
}
|
|
24397
26413
|
});
|
|
24398
26414
|
var diagramTools = [generateDiagramTool];
|
|
24399
|
-
var
|
|
24400
|
-
var
|
|
26415
|
+
var fs33 = await import('fs/promises');
|
|
26416
|
+
var path30 = await import('path');
|
|
24401
26417
|
var DEFAULT_MAX_PAGES = 20;
|
|
24402
26418
|
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
24403
26419
|
function parsePageRange(rangeStr, totalPages) {
|
|
@@ -24432,9 +26448,9 @@ Examples:
|
|
|
24432
26448
|
}),
|
|
24433
26449
|
async execute({ path: filePath, pages, maxPages }) {
|
|
24434
26450
|
const startTime = performance.now();
|
|
24435
|
-
const absPath =
|
|
26451
|
+
const absPath = path30.resolve(filePath);
|
|
24436
26452
|
try {
|
|
24437
|
-
const stat2 = await
|
|
26453
|
+
const stat2 = await fs33.stat(absPath);
|
|
24438
26454
|
if (!stat2.isFile()) {
|
|
24439
26455
|
throw new ToolError(`Path is not a file: ${absPath}`, {
|
|
24440
26456
|
tool: "read_pdf"
|
|
@@ -24465,7 +26481,7 @@ Examples:
|
|
|
24465
26481
|
}
|
|
24466
26482
|
try {
|
|
24467
26483
|
const pdfParse = await import('pdf-parse');
|
|
24468
|
-
const dataBuffer = await
|
|
26484
|
+
const dataBuffer = await fs33.readFile(absPath);
|
|
24469
26485
|
const pdfData = await pdfParse.default(dataBuffer, {
|
|
24470
26486
|
max: maxPages
|
|
24471
26487
|
});
|
|
@@ -24511,8 +26527,8 @@ Examples:
|
|
|
24511
26527
|
}
|
|
24512
26528
|
});
|
|
24513
26529
|
var pdfTools = [readPdfTool];
|
|
24514
|
-
var
|
|
24515
|
-
var
|
|
26530
|
+
var fs34 = await import('fs/promises');
|
|
26531
|
+
var path31 = await import('path');
|
|
24516
26532
|
var SUPPORTED_FORMATS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
|
|
24517
26533
|
var MAX_IMAGE_SIZE = 20 * 1024 * 1024;
|
|
24518
26534
|
var MIME_TYPES = {
|
|
@@ -24540,8 +26556,15 @@ Examples:
|
|
|
24540
26556
|
async execute({ path: filePath, prompt, provider }) {
|
|
24541
26557
|
const startTime = performance.now();
|
|
24542
26558
|
const effectivePrompt = prompt ?? "Describe this image in detail. If it's code or a UI, identify the key elements.";
|
|
24543
|
-
const absPath =
|
|
24544
|
-
const
|
|
26559
|
+
const absPath = path31.resolve(filePath);
|
|
26560
|
+
const cwd = process.cwd();
|
|
26561
|
+
if (!absPath.startsWith(cwd + path31.sep) && absPath !== cwd) {
|
|
26562
|
+
throw new ToolError(
|
|
26563
|
+
`Path traversal denied: '${filePath}' resolves outside the project directory`,
|
|
26564
|
+
{ tool: "read_image" }
|
|
26565
|
+
);
|
|
26566
|
+
}
|
|
26567
|
+
const ext = path31.extname(absPath).toLowerCase();
|
|
24545
26568
|
if (!SUPPORTED_FORMATS.has(ext)) {
|
|
24546
26569
|
throw new ToolError(
|
|
24547
26570
|
`Unsupported image format '${ext}'. Supported: ${Array.from(SUPPORTED_FORMATS).join(", ")}`,
|
|
@@ -24549,7 +26572,7 @@ Examples:
|
|
|
24549
26572
|
);
|
|
24550
26573
|
}
|
|
24551
26574
|
try {
|
|
24552
|
-
const stat2 = await
|
|
26575
|
+
const stat2 = await fs34.stat(absPath);
|
|
24553
26576
|
if (!stat2.isFile()) {
|
|
24554
26577
|
throw new ToolError(`Path is not a file: ${absPath}`, {
|
|
24555
26578
|
tool: "read_image"
|
|
@@ -24570,7 +26593,7 @@ Examples:
|
|
|
24570
26593
|
if (error instanceof ToolError) throw error;
|
|
24571
26594
|
throw error;
|
|
24572
26595
|
}
|
|
24573
|
-
const imageBuffer = await
|
|
26596
|
+
const imageBuffer = await fs34.readFile(absPath);
|
|
24574
26597
|
const base64 = imageBuffer.toString("base64");
|
|
24575
26598
|
const mimeType = MIME_TYPES[ext] ?? "image/png";
|
|
24576
26599
|
const selectedProvider = provider ?? "anthropic";
|
|
@@ -24683,7 +26706,7 @@ Examples:
|
|
|
24683
26706
|
}
|
|
24684
26707
|
});
|
|
24685
26708
|
var imageTools = [readImageTool];
|
|
24686
|
-
var
|
|
26709
|
+
var path32 = await import('path');
|
|
24687
26710
|
var DANGEROUS_PATTERNS2 = [
|
|
24688
26711
|
/\bDROP\s+(?:TABLE|DATABASE|INDEX|VIEW)\b/i,
|
|
24689
26712
|
/\bTRUNCATE\b/i,
|
|
@@ -24714,7 +26737,7 @@ Examples:
|
|
|
24714
26737
|
async execute({ database, query, params, readonly: isReadonlyParam }) {
|
|
24715
26738
|
const isReadonly = isReadonlyParam ?? true;
|
|
24716
26739
|
const startTime = performance.now();
|
|
24717
|
-
const absPath =
|
|
26740
|
+
const absPath = path32.resolve(database);
|
|
24718
26741
|
if (isReadonly && isDangerousSql(query)) {
|
|
24719
26742
|
throw new ToolError(
|
|
24720
26743
|
"Write operations (INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE) are blocked in readonly mode. Set readonly: false to allow writes.",
|
|
@@ -24787,7 +26810,7 @@ Examples:
|
|
|
24787
26810
|
}),
|
|
24788
26811
|
async execute({ database, table }) {
|
|
24789
26812
|
const startTime = performance.now();
|
|
24790
|
-
const absPath =
|
|
26813
|
+
const absPath = path32.resolve(database);
|
|
24791
26814
|
try {
|
|
24792
26815
|
const { default: Database } = await import('better-sqlite3');
|
|
24793
26816
|
const db = new Database(absPath, { readonly: true, fileMustExist: true });
|
|
@@ -24958,14 +26981,14 @@ var findMissingImportsTool = defineTool({
|
|
|
24958
26981
|
}
|
|
24959
26982
|
});
|
|
24960
26983
|
var astValidatorTools = [validateCodeTool, findMissingImportsTool];
|
|
24961
|
-
var
|
|
24962
|
-
var
|
|
26984
|
+
var fs35 = await import('fs/promises');
|
|
26985
|
+
var path33 = await import('path');
|
|
24963
26986
|
var AnalyzeFileSchema = z.object({
|
|
24964
26987
|
filePath: z.string().describe("Path to file to analyze"),
|
|
24965
26988
|
includeAst: z.boolean().default(false).describe("Include AST in result")
|
|
24966
26989
|
});
|
|
24967
26990
|
async function analyzeFile(filePath, includeAst = false) {
|
|
24968
|
-
const content = await
|
|
26991
|
+
const content = await fs35.readFile(filePath, "utf-8");
|
|
24969
26992
|
const lines = content.split("\n").length;
|
|
24970
26993
|
const functions = [];
|
|
24971
26994
|
const classes = [];
|
|
@@ -25056,8 +27079,8 @@ async function analyzeFile(filePath, includeAst = false) {
|
|
|
25056
27079
|
};
|
|
25057
27080
|
}
|
|
25058
27081
|
async function analyzeDirectory(dirPath) {
|
|
25059
|
-
const { glob:
|
|
25060
|
-
const files = await
|
|
27082
|
+
const { glob: glob15 } = await import('glob');
|
|
27083
|
+
const files = await glob15("**/*.{ts,tsx,js,jsx}", {
|
|
25061
27084
|
cwd: dirPath,
|
|
25062
27085
|
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
|
|
25063
27086
|
absolute: true
|
|
@@ -25069,10 +27092,10 @@ async function analyzeDirectory(dirPath) {
|
|
|
25069
27092
|
try {
|
|
25070
27093
|
const analysis = await analyzeFile(file, false);
|
|
25071
27094
|
totalLines += analysis.lines;
|
|
25072
|
-
const ext =
|
|
27095
|
+
const ext = path33.extname(file);
|
|
25073
27096
|
filesByType[ext] = (filesByType[ext] || 0) + 1;
|
|
25074
27097
|
fileStats.push({
|
|
25075
|
-
file:
|
|
27098
|
+
file: path33.relative(dirPath, file),
|
|
25076
27099
|
lines: analysis.lines,
|
|
25077
27100
|
complexity: analysis.complexity.cyclomatic
|
|
25078
27101
|
});
|
|
@@ -25132,8 +27155,8 @@ var codeAnalyzerTools = [analyzeFileTool, analyzeDirectoryTool];
|
|
|
25132
27155
|
var AgentTaskQueue = class {
|
|
25133
27156
|
tasks = /* @__PURE__ */ new Map();
|
|
25134
27157
|
completionOrder = [];
|
|
25135
|
-
addTask(task) {
|
|
25136
|
-
const id = `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
27158
|
+
addTask(task, idOverride) {
|
|
27159
|
+
const id = idOverride ?? `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
25137
27160
|
this.tasks.set(id, {
|
|
25138
27161
|
...task,
|
|
25139
27162
|
id,
|
|
@@ -25179,10 +27202,20 @@ var AgentTaskQueue = class {
|
|
|
25179
27202
|
};
|
|
25180
27203
|
function planExecution(tasks, strategy) {
|
|
25181
27204
|
const taskGraph = /* @__PURE__ */ new Map();
|
|
27205
|
+
const unresolvedDependencies = [];
|
|
25182
27206
|
tasks.forEach((task, idx) => {
|
|
25183
|
-
const id = `task-${idx}`;
|
|
27207
|
+
const id = task.id ?? `task-${idx}`;
|
|
25184
27208
|
taskGraph.set(id, task.dependencies || []);
|
|
25185
27209
|
});
|
|
27210
|
+
const taskIds = new Set(taskGraph.keys());
|
|
27211
|
+
for (const [id, deps] of taskGraph) {
|
|
27212
|
+
const filtered = deps.filter((dep) => {
|
|
27213
|
+
if (taskIds.has(dep)) return true;
|
|
27214
|
+
unresolvedDependencies.push({ taskId: id, dependency: dep });
|
|
27215
|
+
return false;
|
|
27216
|
+
});
|
|
27217
|
+
taskGraph.set(id, filtered);
|
|
27218
|
+
}
|
|
25186
27219
|
let plan = [];
|
|
25187
27220
|
let estimatedTime = 0;
|
|
25188
27221
|
let maxParallelism = 1;
|
|
@@ -25200,7 +27233,11 @@ function planExecution(tasks, strategy) {
|
|
|
25200
27233
|
break;
|
|
25201
27234
|
}
|
|
25202
27235
|
case "priority-based": {
|
|
25203
|
-
const
|
|
27236
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
27237
|
+
const sorted = tasks.map((t, idx) => ({
|
|
27238
|
+
id: t.id ?? `task-${idx}`,
|
|
27239
|
+
priority: t.priority ?? "medium"
|
|
27240
|
+
})).sort((a, b) => (priorityOrder[a.priority] ?? 1) - (priorityOrder[b.priority] ?? 1));
|
|
25204
27241
|
plan = sorted.map((t) => t.id);
|
|
25205
27242
|
estimatedTime = tasks.length * 80;
|
|
25206
27243
|
maxParallelism = 3;
|
|
@@ -25229,7 +27266,7 @@ function planExecution(tasks, strategy) {
|
|
|
25229
27266
|
break;
|
|
25230
27267
|
}
|
|
25231
27268
|
}
|
|
25232
|
-
return { plan, estimatedTime, maxParallelism };
|
|
27269
|
+
return { plan, estimatedTime, maxParallelism, unresolvedDependencies };
|
|
25233
27270
|
}
|
|
25234
27271
|
var createAgentPlanTool = defineTool({
|
|
25235
27272
|
name: "createAgentPlan",
|
|
@@ -25248,15 +27285,35 @@ var createAgentPlanTool = defineTool({
|
|
|
25248
27285
|
async execute(input) {
|
|
25249
27286
|
const typedInput = input;
|
|
25250
27287
|
const queue = new AgentTaskQueue();
|
|
25251
|
-
|
|
25252
|
-
|
|
25253
|
-
|
|
25254
|
-
|
|
25255
|
-
|
|
25256
|
-
|
|
25257
|
-
|
|
27288
|
+
const indexIdMap = /* @__PURE__ */ new Map();
|
|
27289
|
+
for (let idx = 0; idx < typedInput.tasks.length; idx++) {
|
|
27290
|
+
indexIdMap.set(idx, `task-${idx}`);
|
|
27291
|
+
}
|
|
27292
|
+
const normalizeDependency = (dep) => {
|
|
27293
|
+
if (indexIdMap.has(Number(dep))) {
|
|
27294
|
+
return indexIdMap.get(Number(dep));
|
|
27295
|
+
}
|
|
27296
|
+
return dep;
|
|
27297
|
+
};
|
|
27298
|
+
for (const [idx, task] of typedInput.tasks.entries()) {
|
|
27299
|
+
const id = indexIdMap.get(idx);
|
|
27300
|
+
const normalizedDependencies = (task.dependencies || []).map(normalizeDependency);
|
|
27301
|
+
queue.addTask(
|
|
27302
|
+
{
|
|
27303
|
+
description: task.description,
|
|
27304
|
+
priority: task.priority,
|
|
27305
|
+
estimatedDuration: 100,
|
|
27306
|
+
dependencies: normalizedDependencies
|
|
27307
|
+
},
|
|
27308
|
+
id
|
|
27309
|
+
);
|
|
25258
27310
|
}
|
|
25259
|
-
const
|
|
27311
|
+
const plannedTasks = typedInput.tasks.map((task, idx) => ({
|
|
27312
|
+
id: indexIdMap.get(idx),
|
|
27313
|
+
description: task.description,
|
|
27314
|
+
dependencies: (task.dependencies || []).map(normalizeDependency)
|
|
27315
|
+
}));
|
|
27316
|
+
const executionPlan = planExecution(plannedTasks, typedInput.strategy);
|
|
25260
27317
|
return {
|
|
25261
27318
|
planId: `plan-${Date.now()}`,
|
|
25262
27319
|
strategy: typedInput.strategy,
|
|
@@ -25264,6 +27321,7 @@ var createAgentPlanTool = defineTool({
|
|
|
25264
27321
|
executionOrder: executionPlan.plan,
|
|
25265
27322
|
estimatedTime: executionPlan.estimatedTime,
|
|
25266
27323
|
maxParallelism: executionPlan.maxParallelism,
|
|
27324
|
+
unresolvedDependencies: executionPlan.unresolvedDependencies,
|
|
25267
27325
|
tasks: queue.getTasks().map((t) => ({
|
|
25268
27326
|
id: t.id,
|
|
25269
27327
|
description: t.description,
|
|
@@ -25276,30 +27334,59 @@ var createAgentPlanTool = defineTool({
|
|
|
25276
27334
|
});
|
|
25277
27335
|
var delegateTaskTool = defineTool({
|
|
25278
27336
|
name: "delegateTask",
|
|
25279
|
-
description: "Delegate a task to a
|
|
27337
|
+
description: "Delegate a task to a specialized sub-agent with real LLM tool-use execution.",
|
|
25280
27338
|
category: "build",
|
|
25281
27339
|
parameters: z.object({
|
|
25282
27340
|
taskId: z.string(),
|
|
27341
|
+
task: z.string().describe("Description of the task for the agent to execute"),
|
|
25283
27342
|
agentRole: z.enum(["researcher", "coder", "reviewer", "tester", "optimizer"]).default("coder"),
|
|
25284
|
-
context: z.string().optional()
|
|
27343
|
+
context: z.string().optional(),
|
|
27344
|
+
maxTurns: z.number().default(10)
|
|
25285
27345
|
}),
|
|
25286
27346
|
async execute(input) {
|
|
25287
27347
|
const typedInput = input;
|
|
27348
|
+
const provider = getAgentProvider();
|
|
27349
|
+
const toolRegistry = getAgentToolRegistry();
|
|
27350
|
+
if (!provider || !toolRegistry) {
|
|
27351
|
+
return {
|
|
27352
|
+
agentId: `agent-${Date.now()}-${typedInput.agentRole}`,
|
|
27353
|
+
taskId: typedInput.taskId,
|
|
27354
|
+
role: typedInput.agentRole,
|
|
27355
|
+
status: "unavailable",
|
|
27356
|
+
message: "Agent provider not initialized. Call setAgentProvider() during orchestrator startup.",
|
|
27357
|
+
success: false
|
|
27358
|
+
};
|
|
27359
|
+
}
|
|
25288
27360
|
const agentId = `agent-${Date.now()}-${typedInput.agentRole}`;
|
|
27361
|
+
const executor = new AgentExecutor(provider, toolRegistry);
|
|
27362
|
+
const roleDef = AGENT_ROLES[typedInput.agentRole];
|
|
27363
|
+
if (!roleDef) {
|
|
27364
|
+
return {
|
|
27365
|
+
agentId,
|
|
27366
|
+
taskId: typedInput.taskId,
|
|
27367
|
+
role: typedInput.agentRole,
|
|
27368
|
+
status: "error",
|
|
27369
|
+
message: `Unknown agent role: ${typedInput.agentRole}`,
|
|
27370
|
+
success: false
|
|
27371
|
+
};
|
|
27372
|
+
}
|
|
27373
|
+
const agentDef = { ...roleDef, maxTurns: typedInput.maxTurns };
|
|
27374
|
+
const result = await executor.execute(agentDef, {
|
|
27375
|
+
id: typedInput.taskId,
|
|
27376
|
+
description: typedInput.task,
|
|
27377
|
+
context: typedInput.context ? { userContext: typedInput.context } : void 0
|
|
27378
|
+
});
|
|
25289
27379
|
return {
|
|
25290
27380
|
agentId,
|
|
25291
27381
|
taskId: typedInput.taskId,
|
|
25292
27382
|
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."
|
|
27383
|
+
status: result.success ? "completed" : "failed",
|
|
27384
|
+
output: result.output,
|
|
27385
|
+
success: result.success,
|
|
27386
|
+
turns: result.turns,
|
|
27387
|
+
toolsUsed: result.toolsUsed,
|
|
27388
|
+
tokensUsed: result.tokensUsed,
|
|
27389
|
+
duration: result.duration
|
|
25303
27390
|
};
|
|
25304
27391
|
}
|
|
25305
27392
|
});
|
|
@@ -25335,10 +27422,14 @@ var aggregateResultsTool = defineTool({
|
|
|
25335
27422
|
const winner = Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0];
|
|
25336
27423
|
aggregatedOutput = winner ? winner[0] : "";
|
|
25337
27424
|
break;
|
|
25338
|
-
case "best":
|
|
25339
|
-
const
|
|
25340
|
-
|
|
27425
|
+
case "best": {
|
|
27426
|
+
const successRate = typedInput.results.length > 0 ? completed.length / typedInput.results.length : 0;
|
|
27427
|
+
const bestResult = completed.length > 0 ? completed[0] : void 0;
|
|
27428
|
+
aggregatedOutput = bestResult ? `[Success rate: ${Math.round(successRate * 100)}%]
|
|
27429
|
+
|
|
27430
|
+
${bestResult.output}` : "";
|
|
25341
27431
|
break;
|
|
27432
|
+
}
|
|
25342
27433
|
case "summary":
|
|
25343
27434
|
aggregatedOutput = `Completed: ${completed.length}, Failed: ${failed.length}
|
|
25344
27435
|
|
|
@@ -25360,13 +27451,13 @@ ${completed.map((r) => `- ${r.agentId}: Success`).join("\n")}`;
|
|
|
25360
27451
|
}
|
|
25361
27452
|
});
|
|
25362
27453
|
var agentCoordinatorTools = [createAgentPlanTool, delegateTaskTool, aggregateResultsTool];
|
|
25363
|
-
var
|
|
27454
|
+
var fs36 = await import('fs/promises');
|
|
25364
27455
|
var SuggestImprovementsSchema = z.object({
|
|
25365
27456
|
filePath: z.string().describe("File to analyze for improvement suggestions"),
|
|
25366
27457
|
context: z.string().optional().describe("Additional context about the code")
|
|
25367
27458
|
});
|
|
25368
27459
|
async function analyzeAndSuggest(filePath, _context) {
|
|
25369
|
-
const content = await
|
|
27460
|
+
const content = await fs36.readFile(filePath, "utf-8");
|
|
25370
27461
|
const lines = content.split("\n");
|
|
25371
27462
|
const suggestions = [];
|
|
25372
27463
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -25412,7 +27503,8 @@ async function analyzeAndSuggest(filePath, _context) {
|
|
|
25412
27503
|
reasoning: "'any' bypasses all type checking and can hide bugs"
|
|
25413
27504
|
});
|
|
25414
27505
|
}
|
|
25415
|
-
|
|
27506
|
+
const trimmedLine = line.trim();
|
|
27507
|
+
if (trimmedLine.endsWith("catch (error) {") || trimmedLine.endsWith("catch (e) {") || trimmedLine === "catch (error) {" || trimmedLine === "catch (e) {") {
|
|
25416
27508
|
const nextLine = lines[i + 1];
|
|
25417
27509
|
if (nextLine && nextLine.trim() === "}") {
|
|
25418
27510
|
suggestions.push({
|
|
@@ -25457,7 +27549,7 @@ async function analyzeAndSuggest(filePath, _context) {
|
|
|
25457
27549
|
if (filePath.endsWith(".ts") && !filePath.includes("test") && !filePath.includes(".d.ts") && line.includes("export ")) {
|
|
25458
27550
|
const testPath = filePath.replace(".ts", ".test.ts");
|
|
25459
27551
|
try {
|
|
25460
|
-
await
|
|
27552
|
+
await fs36.access(testPath);
|
|
25461
27553
|
} catch {
|
|
25462
27554
|
suggestions.push({
|
|
25463
27555
|
type: "testing",
|
|
@@ -25514,7 +27606,7 @@ var calculateCodeScoreTool = defineTool({
|
|
|
25514
27606
|
async execute(input) {
|
|
25515
27607
|
const { filePath } = input;
|
|
25516
27608
|
const suggestions = await analyzeAndSuggest(filePath);
|
|
25517
|
-
const content = await
|
|
27609
|
+
const content = await fs36.readFile(filePath, "utf-8");
|
|
25518
27610
|
const lines = content.split("\n");
|
|
25519
27611
|
const nonEmptyLines = lines.filter((l) => l.trim()).length;
|
|
25520
27612
|
let score = 100;
|
|
@@ -25548,8 +27640,8 @@ var calculateCodeScoreTool = defineTool({
|
|
|
25548
27640
|
}
|
|
25549
27641
|
});
|
|
25550
27642
|
var smartSuggestionsTools = [suggestImprovementsTool, calculateCodeScoreTool];
|
|
25551
|
-
var
|
|
25552
|
-
var
|
|
27643
|
+
var fs37 = await import('fs/promises');
|
|
27644
|
+
var path34 = await import('path');
|
|
25553
27645
|
var ContextMemoryStore = class {
|
|
25554
27646
|
items = /* @__PURE__ */ new Map();
|
|
25555
27647
|
learnings = /* @__PURE__ */ new Map();
|
|
@@ -25561,7 +27653,7 @@ var ContextMemoryStore = class {
|
|
|
25561
27653
|
}
|
|
25562
27654
|
async load() {
|
|
25563
27655
|
try {
|
|
25564
|
-
const content = await
|
|
27656
|
+
const content = await fs37.readFile(this.storePath, "utf-8");
|
|
25565
27657
|
const data = JSON.parse(content);
|
|
25566
27658
|
this.items = new Map(Object.entries(data.items || {}));
|
|
25567
27659
|
this.learnings = new Map(Object.entries(data.learnings || {}));
|
|
@@ -25569,15 +27661,15 @@ var ContextMemoryStore = class {
|
|
|
25569
27661
|
}
|
|
25570
27662
|
}
|
|
25571
27663
|
async save() {
|
|
25572
|
-
const dir =
|
|
25573
|
-
await
|
|
27664
|
+
const dir = path34.dirname(this.storePath);
|
|
27665
|
+
await fs37.mkdir(dir, { recursive: true });
|
|
25574
27666
|
const data = {
|
|
25575
27667
|
sessionId: this.sessionId,
|
|
25576
27668
|
items: Object.fromEntries(this.items),
|
|
25577
27669
|
learnings: Object.fromEntries(this.learnings),
|
|
25578
27670
|
savedAt: Date.now()
|
|
25579
27671
|
};
|
|
25580
|
-
await
|
|
27672
|
+
await fs37.writeFile(this.storePath, JSON.stringify(data, null, 2));
|
|
25581
27673
|
}
|
|
25582
27674
|
addContext(id, item) {
|
|
25583
27675
|
this.items.set(id, item);
|
|
@@ -25742,11 +27834,11 @@ var contextEnhancerTools = [
|
|
|
25742
27834
|
recordLearningTool,
|
|
25743
27835
|
getLearnedPatternsTool
|
|
25744
27836
|
];
|
|
25745
|
-
var
|
|
25746
|
-
var
|
|
27837
|
+
var fs38 = await import('fs/promises');
|
|
27838
|
+
var path35 = await import('path');
|
|
25747
27839
|
async function discoverSkills(skillsDir) {
|
|
25748
27840
|
try {
|
|
25749
|
-
const files = await
|
|
27841
|
+
const files = await fs38.readdir(skillsDir);
|
|
25750
27842
|
return files.filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
|
|
25751
27843
|
} catch {
|
|
25752
27844
|
return [];
|
|
@@ -25754,12 +27846,12 @@ async function discoverSkills(skillsDir) {
|
|
|
25754
27846
|
}
|
|
25755
27847
|
async function loadSkillMetadata(skillPath) {
|
|
25756
27848
|
try {
|
|
25757
|
-
const content = await
|
|
27849
|
+
const content = await fs38.readFile(skillPath, "utf-8");
|
|
25758
27850
|
const nameMatch = content.match(/@name\s+(\S+)/);
|
|
25759
27851
|
const descMatch = content.match(/@description\s+(.+)/);
|
|
25760
27852
|
const versionMatch = content.match(/@version\s+(\S+)/);
|
|
25761
27853
|
return {
|
|
25762
|
-
name: nameMatch?.[1] ||
|
|
27854
|
+
name: nameMatch?.[1] || path35.basename(skillPath, path35.extname(skillPath)),
|
|
25763
27855
|
description: descMatch?.[1] || "No description",
|
|
25764
27856
|
version: versionMatch?.[1] || "1.0.0",
|
|
25765
27857
|
dependencies: []
|
|
@@ -25803,7 +27895,7 @@ var discoverSkillsTool = defineTool({
|
|
|
25803
27895
|
const { skillsDir } = input;
|
|
25804
27896
|
const skills = await discoverSkills(skillsDir);
|
|
25805
27897
|
const metadata = await Promise.all(
|
|
25806
|
-
skills.map((s) => loadSkillMetadata(
|
|
27898
|
+
skills.map((s) => loadSkillMetadata(path35.join(skillsDir, s)))
|
|
25807
27899
|
);
|
|
25808
27900
|
return {
|
|
25809
27901
|
skillsDir,
|
|
@@ -26532,6 +28624,24 @@ var pc = chalk12;
|
|
|
26532
28624
|
});
|
|
26533
28625
|
|
|
26534
28626
|
// src/cli/repl/index.ts
|
|
28627
|
+
function visualWidth(str) {
|
|
28628
|
+
const stripped = str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
28629
|
+
let width = 0;
|
|
28630
|
+
for (const char of stripped) {
|
|
28631
|
+
const cp = char.codePointAt(0) || 0;
|
|
28632
|
+
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) {
|
|
28633
|
+
width += 2;
|
|
28634
|
+
} else if (
|
|
28635
|
+
// CJK Unified Ideographs and other wide characters
|
|
28636
|
+
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
|
|
28637
|
+
) {
|
|
28638
|
+
width += 2;
|
|
28639
|
+
} else {
|
|
28640
|
+
width += 1;
|
|
28641
|
+
}
|
|
28642
|
+
}
|
|
28643
|
+
return width;
|
|
28644
|
+
}
|
|
26535
28645
|
async function startRepl(options = {}) {
|
|
26536
28646
|
const projectPath = options.projectPath ?? process.cwd();
|
|
26537
28647
|
const session = createSession(projectPath, options.config);
|
|
@@ -26576,9 +28686,22 @@ async function startRepl(options = {}) {
|
|
|
26576
28686
|
}
|
|
26577
28687
|
await loadCocoModePreference();
|
|
26578
28688
|
const toolRegistry = createFullToolRegistry();
|
|
28689
|
+
setAgentProvider(provider);
|
|
28690
|
+
setAgentToolRegistry(toolRegistry);
|
|
26579
28691
|
const inputHandler = createInputHandler();
|
|
26580
28692
|
const intentRecognizer = createIntentRecognizer();
|
|
26581
28693
|
await printWelcome(session);
|
|
28694
|
+
const cleanupTerminal = () => {
|
|
28695
|
+
process.stdout.write("\x1B[?2004l");
|
|
28696
|
+
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
28697
|
+
process.stdin.setRawMode(false);
|
|
28698
|
+
}
|
|
28699
|
+
};
|
|
28700
|
+
process.on("exit", cleanupTerminal);
|
|
28701
|
+
process.on("SIGTERM", () => {
|
|
28702
|
+
cleanupTerminal();
|
|
28703
|
+
process.exit(0);
|
|
28704
|
+
});
|
|
26582
28705
|
while (true) {
|
|
26583
28706
|
const input = await inputHandler.prompt();
|
|
26584
28707
|
if (input === null && !hasPendingImage()) {
|
|
@@ -26658,6 +28781,14 @@ async function startRepl(options = {}) {
|
|
|
26658
28781
|
activeSpinner.start();
|
|
26659
28782
|
}
|
|
26660
28783
|
};
|
|
28784
|
+
const abortController = new AbortController();
|
|
28785
|
+
let wasAborted = false;
|
|
28786
|
+
const sigintHandler = () => {
|
|
28787
|
+
wasAborted = true;
|
|
28788
|
+
abortController.abort();
|
|
28789
|
+
clearSpinner();
|
|
28790
|
+
renderInfo("\nOperation cancelled");
|
|
28791
|
+
};
|
|
26661
28792
|
try {
|
|
26662
28793
|
if (typeof agentMessage === "string" && !isCocoMode() && !wasHintShown() && looksLikeFeatureRequest(agentMessage)) {
|
|
26663
28794
|
markHintShown();
|
|
@@ -26670,14 +28801,6 @@ async function startRepl(options = {}) {
|
|
|
26670
28801
|
session.config.agent.systemPrompt = originalSystemPrompt + "\n" + getCocoModeSystemPrompt();
|
|
26671
28802
|
}
|
|
26672
28803
|
inputHandler.pause();
|
|
26673
|
-
const abortController = new AbortController();
|
|
26674
|
-
let wasAborted = false;
|
|
26675
|
-
const sigintHandler = () => {
|
|
26676
|
-
wasAborted = true;
|
|
26677
|
-
abortController.abort();
|
|
26678
|
-
clearSpinner();
|
|
26679
|
-
renderInfo("\nOperation cancelled");
|
|
26680
|
-
};
|
|
26681
28804
|
process.once("SIGINT", sigintHandler);
|
|
26682
28805
|
const result = await executeAgentTurn(session, agentMessage, provider, toolRegistry, {
|
|
26683
28806
|
onStream: renderStreamChunk,
|
|
@@ -26766,6 +28889,7 @@ async function startRepl(options = {}) {
|
|
|
26766
28889
|
console.log();
|
|
26767
28890
|
} catch (error) {
|
|
26768
28891
|
clearSpinner();
|
|
28892
|
+
process.off("SIGINT", sigintHandler);
|
|
26769
28893
|
if (error instanceof Error && error.name === "AbortError") {
|
|
26770
28894
|
continue;
|
|
26771
28895
|
}
|
|
@@ -26796,23 +28920,39 @@ async function startRepl(options = {}) {
|
|
|
26796
28920
|
inputHandler.close();
|
|
26797
28921
|
}
|
|
26798
28922
|
async function printWelcome(session) {
|
|
28923
|
+
const providerType = session.config.provider.type;
|
|
28924
|
+
const model = session.config.provider.model || "default";
|
|
28925
|
+
const isReturningUser = existsSync(path20__default.join(session.projectPath, ".coco"));
|
|
28926
|
+
if (isReturningUser) {
|
|
28927
|
+
const versionStr = chalk12.dim(`v${VERSION}`);
|
|
28928
|
+
const providerStr = chalk12.dim(`${providerType}/${model}`);
|
|
28929
|
+
console.log(
|
|
28930
|
+
`
|
|
28931
|
+
\u{1F965} Coco ${versionStr} ${chalk12.dim("\u2502")} ${providerStr} ${chalk12.dim("\u2502")} ${chalk12.yellow("/help")}
|
|
28932
|
+
`
|
|
28933
|
+
);
|
|
28934
|
+
return;
|
|
28935
|
+
}
|
|
26799
28936
|
const trustStore = createTrustStore();
|
|
26800
28937
|
await trustStore.init();
|
|
26801
28938
|
const trustLevel = trustStore.getLevel(session.projectPath);
|
|
26802
28939
|
const boxWidth = 41;
|
|
26803
28940
|
const innerWidth = boxWidth - 4;
|
|
26804
|
-
const
|
|
28941
|
+
const titleContent = "\u{1F965} CORBAT-COCO";
|
|
26805
28942
|
const versionText = `v${VERSION}`;
|
|
26806
|
-
const
|
|
28943
|
+
const titleContentVisualWidth = visualWidth(titleContent);
|
|
28944
|
+
const versionVisualWidth = visualWidth(versionText);
|
|
28945
|
+
const titlePadding = innerWidth - titleContentVisualWidth - versionVisualWidth;
|
|
26807
28946
|
const subtitleText = "open source \u2022 corbat.tech";
|
|
26808
|
-
const
|
|
28947
|
+
const subtitleVisualWidth = visualWidth(subtitleText);
|
|
28948
|
+
const subtitlePadding = innerWidth - subtitleVisualWidth;
|
|
26809
28949
|
console.log();
|
|
26810
28950
|
console.log(chalk12.magenta(" \u256D" + "\u2500".repeat(boxWidth - 2) + "\u256E"));
|
|
26811
28951
|
console.log(
|
|
26812
|
-
chalk12.magenta(" \u2502 ") + "\u{1F965} " + chalk12.bold.white(
|
|
28952
|
+
chalk12.magenta(" \u2502 ") + "\u{1F965} " + chalk12.bold.white("CORBAT-COCO") + " ".repeat(Math.max(0, titlePadding)) + chalk12.dim(versionText) + chalk12.magenta(" \u2502")
|
|
26813
28953
|
);
|
|
26814
28954
|
console.log(
|
|
26815
|
-
chalk12.magenta(" \u2502 ") + chalk12.dim(subtitleText) + " ".repeat(subtitlePadding) + chalk12.magenta(" \u2502")
|
|
28955
|
+
chalk12.magenta(" \u2502 ") + chalk12.dim(subtitleText) + " ".repeat(Math.max(0, subtitlePadding)) + chalk12.magenta(" \u2502")
|
|
26816
28956
|
);
|
|
26817
28957
|
console.log(chalk12.magenta(" \u2570" + "\u2500".repeat(boxWidth - 2) + "\u256F"));
|
|
26818
28958
|
const updateInfo = await checkForUpdates();
|
|
@@ -27067,7 +29207,7 @@ async function main() {
|
|
|
27067
29207
|
await program.parseAsync(process.argv);
|
|
27068
29208
|
}
|
|
27069
29209
|
main().catch((error) => {
|
|
27070
|
-
console.error(
|
|
29210
|
+
console.error(formatError(error));
|
|
27071
29211
|
process.exit(1);
|
|
27072
29212
|
});
|
|
27073
29213
|
//# sourceMappingURL=index.js.map
|