@corbat-tech/coco 1.9.0 → 2.0.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 +43 -6
- package/dist/cli/index.js +29313 -24118
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +1932 -372
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from 'os';
|
|
2
|
-
import * as
|
|
3
|
-
import
|
|
2
|
+
import * as path15 from 'path';
|
|
3
|
+
import path15__default, { dirname, join, basename, resolve } from 'path';
|
|
4
4
|
import * as fs4 from 'fs';
|
|
5
5
|
import fs4__default, { readFileSync, constants } from 'fs';
|
|
6
6
|
import * as fs14 from 'fs/promises';
|
|
@@ -76,7 +76,9 @@ var init_paths = __esm({
|
|
|
76
76
|
/** Checkpoints directory: ~/.coco/checkpoints/ */
|
|
77
77
|
checkpoints: join(COCO_HOME, "checkpoints"),
|
|
78
78
|
/** Search index directory: ~/.coco/search-index/ */
|
|
79
|
-
searchIndex: join(COCO_HOME, "search-index")
|
|
79
|
+
searchIndex: join(COCO_HOME, "search-index"),
|
|
80
|
+
/** Global skills directory: ~/.coco/skills/ */
|
|
81
|
+
skills: join(COCO_HOME, "skills")
|
|
80
82
|
};
|
|
81
83
|
({
|
|
82
84
|
/** Old config location */
|
|
@@ -88,7 +90,7 @@ function loadGlobalCocoEnv() {
|
|
|
88
90
|
try {
|
|
89
91
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
90
92
|
if (!home) return;
|
|
91
|
-
const globalEnvPath =
|
|
93
|
+
const globalEnvPath = path15.join(home, ".coco", ".env");
|
|
92
94
|
const content = fs4.readFileSync(globalEnvPath, "utf-8");
|
|
93
95
|
for (const line of content.split("\n")) {
|
|
94
96
|
const trimmed = line.trim();
|
|
@@ -122,6 +124,18 @@ function getApiKey(provider) {
|
|
|
122
124
|
return process.env["OLLAMA_API_KEY"] ?? "ollama";
|
|
123
125
|
case "codex":
|
|
124
126
|
return void 0;
|
|
127
|
+
case "groq":
|
|
128
|
+
return process.env["GROQ_API_KEY"];
|
|
129
|
+
case "openrouter":
|
|
130
|
+
return process.env["OPENROUTER_API_KEY"];
|
|
131
|
+
case "mistral":
|
|
132
|
+
return process.env["MISTRAL_API_KEY"];
|
|
133
|
+
case "deepseek":
|
|
134
|
+
return process.env["DEEPSEEK_API_KEY"];
|
|
135
|
+
case "together":
|
|
136
|
+
return process.env["TOGETHER_API_KEY"];
|
|
137
|
+
case "huggingface":
|
|
138
|
+
return process.env["HF_TOKEN"] ?? process.env["HUGGINGFACE_API_KEY"];
|
|
125
139
|
default:
|
|
126
140
|
return void 0;
|
|
127
141
|
}
|
|
@@ -140,6 +154,18 @@ function getBaseUrl(provider) {
|
|
|
140
154
|
return process.env["OLLAMA_BASE_URL"] ?? "http://localhost:11434/v1";
|
|
141
155
|
case "codex":
|
|
142
156
|
return "https://chatgpt.com/backend-api/codex/responses";
|
|
157
|
+
case "groq":
|
|
158
|
+
return process.env["GROQ_BASE_URL"] ?? "https://api.groq.com/openai/v1";
|
|
159
|
+
case "openrouter":
|
|
160
|
+
return process.env["OPENROUTER_BASE_URL"] ?? "https://openrouter.ai/api/v1";
|
|
161
|
+
case "mistral":
|
|
162
|
+
return process.env["MISTRAL_BASE_URL"] ?? "https://api.mistral.ai/v1";
|
|
163
|
+
case "deepseek":
|
|
164
|
+
return process.env["DEEPSEEK_BASE_URL"] ?? "https://api.deepseek.com/v1";
|
|
165
|
+
case "together":
|
|
166
|
+
return process.env["TOGETHER_BASE_URL"] ?? "https://api.together.xyz/v1";
|
|
167
|
+
case "huggingface":
|
|
168
|
+
return process.env["HF_BASE_URL"] ?? "https://api-inference.huggingface.co/v1";
|
|
143
169
|
default:
|
|
144
170
|
return void 0;
|
|
145
171
|
}
|
|
@@ -160,21 +186,49 @@ function getDefaultModel(provider) {
|
|
|
160
186
|
return process.env["OLLAMA_MODEL"] ?? "llama3.1";
|
|
161
187
|
case "codex":
|
|
162
188
|
return process.env["CODEX_MODEL"] ?? "gpt-5.3-codex";
|
|
189
|
+
case "groq":
|
|
190
|
+
return process.env["GROQ_MODEL"] ?? "llama-3.3-70b-versatile";
|
|
191
|
+
case "openrouter":
|
|
192
|
+
return process.env["OPENROUTER_MODEL"] ?? "anthropic/claude-opus-4-6";
|
|
193
|
+
case "mistral":
|
|
194
|
+
return process.env["MISTRAL_MODEL"] ?? "codestral-latest";
|
|
195
|
+
case "deepseek":
|
|
196
|
+
return process.env["DEEPSEEK_MODEL"] ?? "deepseek-coder";
|
|
197
|
+
case "together":
|
|
198
|
+
return process.env["TOGETHER_MODEL"] ?? "Qwen/Qwen2.5-Coder-32B-Instruct";
|
|
199
|
+
case "huggingface":
|
|
200
|
+
return process.env["HF_MODEL"] ?? "Qwen/Qwen2.5-Coder-32B-Instruct";
|
|
163
201
|
default:
|
|
164
202
|
return "gpt-5.3-codex";
|
|
165
203
|
}
|
|
166
204
|
}
|
|
167
205
|
function getDefaultProvider() {
|
|
168
206
|
const provider = process.env["COCO_PROVIDER"]?.toLowerCase();
|
|
169
|
-
if (provider &&
|
|
207
|
+
if (provider && VALID_PROVIDERS.includes(provider)) {
|
|
170
208
|
return provider;
|
|
171
209
|
}
|
|
172
210
|
return "anthropic";
|
|
173
211
|
}
|
|
212
|
+
var VALID_PROVIDERS;
|
|
174
213
|
var init_env = __esm({
|
|
175
214
|
"src/config/env.ts"() {
|
|
176
215
|
init_paths();
|
|
177
216
|
loadGlobalCocoEnv();
|
|
217
|
+
VALID_PROVIDERS = [
|
|
218
|
+
"anthropic",
|
|
219
|
+
"openai",
|
|
220
|
+
"codex",
|
|
221
|
+
"gemini",
|
|
222
|
+
"kimi",
|
|
223
|
+
"lmstudio",
|
|
224
|
+
"ollama",
|
|
225
|
+
"groq",
|
|
226
|
+
"openrouter",
|
|
227
|
+
"mistral",
|
|
228
|
+
"deepseek",
|
|
229
|
+
"together",
|
|
230
|
+
"huggingface"
|
|
231
|
+
];
|
|
178
232
|
({
|
|
179
233
|
provider: getDefaultProvider(),
|
|
180
234
|
getApiKey,
|
|
@@ -187,10 +241,10 @@ function getAllowedPaths() {
|
|
|
187
241
|
return [...sessionAllowedPaths];
|
|
188
242
|
}
|
|
189
243
|
function isWithinAllowedPath(absolutePath, operation) {
|
|
190
|
-
const normalizedTarget =
|
|
244
|
+
const normalizedTarget = path15__default.normalize(absolutePath);
|
|
191
245
|
for (const entry of sessionAllowedPaths) {
|
|
192
|
-
const normalizedAllowed =
|
|
193
|
-
if (normalizedTarget === normalizedAllowed || normalizedTarget.startsWith(normalizedAllowed +
|
|
246
|
+
const normalizedAllowed = path15__default.normalize(entry.path);
|
|
247
|
+
if (normalizedTarget === normalizedAllowed || normalizedTarget.startsWith(normalizedAllowed + path15__default.sep)) {
|
|
194
248
|
if (operation === "read") return true;
|
|
195
249
|
if (entry.level === "write") return true;
|
|
196
250
|
}
|
|
@@ -198,8 +252,8 @@ function isWithinAllowedPath(absolutePath, operation) {
|
|
|
198
252
|
return false;
|
|
199
253
|
}
|
|
200
254
|
function addAllowedPathToSession(dirPath, level) {
|
|
201
|
-
const absolute =
|
|
202
|
-
if (sessionAllowedPaths.some((e) =>
|
|
255
|
+
const absolute = path15__default.resolve(dirPath);
|
|
256
|
+
if (sessionAllowedPaths.some((e) => path15__default.normalize(e.path) === path15__default.normalize(absolute))) {
|
|
203
257
|
return;
|
|
204
258
|
}
|
|
205
259
|
sessionAllowedPaths.push({
|
|
@@ -210,14 +264,14 @@ function addAllowedPathToSession(dirPath, level) {
|
|
|
210
264
|
}
|
|
211
265
|
async function persistAllowedPath(dirPath, level) {
|
|
212
266
|
if (!currentProjectPath) return;
|
|
213
|
-
const absolute =
|
|
267
|
+
const absolute = path15__default.resolve(dirPath);
|
|
214
268
|
const store = await loadStore();
|
|
215
269
|
if (!store.projects[currentProjectPath]) {
|
|
216
270
|
store.projects[currentProjectPath] = [];
|
|
217
271
|
}
|
|
218
272
|
const entries = store.projects[currentProjectPath];
|
|
219
|
-
const normalized =
|
|
220
|
-
if (entries.some((e) =>
|
|
273
|
+
const normalized = path15__default.normalize(absolute);
|
|
274
|
+
if (entries.some((e) => path15__default.normalize(e.path) === normalized)) {
|
|
221
275
|
return;
|
|
222
276
|
}
|
|
223
277
|
entries.push({
|
|
@@ -237,7 +291,7 @@ async function loadStore() {
|
|
|
237
291
|
}
|
|
238
292
|
async function saveStore(store) {
|
|
239
293
|
try {
|
|
240
|
-
await fs14__default.mkdir(
|
|
294
|
+
await fs14__default.mkdir(path15__default.dirname(STORE_FILE), { recursive: true });
|
|
241
295
|
await fs14__default.writeFile(STORE_FILE, JSON.stringify(store, null, 2), "utf-8");
|
|
242
296
|
} catch {
|
|
243
297
|
}
|
|
@@ -246,7 +300,7 @@ var STORE_FILE, DEFAULT_STORE, sessionAllowedPaths, currentProjectPath;
|
|
|
246
300
|
var init_allowed_paths = __esm({
|
|
247
301
|
"src/tools/allowed-paths.ts"() {
|
|
248
302
|
init_paths();
|
|
249
|
-
STORE_FILE =
|
|
303
|
+
STORE_FILE = path15__default.join(CONFIG_PATHS.home, "allowed-paths.json");
|
|
250
304
|
DEFAULT_STORE = {
|
|
251
305
|
version: 1,
|
|
252
306
|
projects: {}
|
|
@@ -327,7 +381,7 @@ __export(allow_path_prompt_exports, {
|
|
|
327
381
|
promptAllowPath: () => promptAllowPath
|
|
328
382
|
});
|
|
329
383
|
async function promptAllowPath(dirPath) {
|
|
330
|
-
const absolute =
|
|
384
|
+
const absolute = path15__default.resolve(dirPath);
|
|
331
385
|
console.log();
|
|
332
386
|
console.log(chalk3.yellow(" \u26A0 Access denied \u2014 path is outside the project directory"));
|
|
333
387
|
console.log(chalk3.dim(` \u{1F4C1} ${absolute}`));
|
|
@@ -1705,13 +1759,13 @@ function createSpecificationGenerator(llm, config) {
|
|
|
1705
1759
|
return new SpecificationGenerator(llm, config);
|
|
1706
1760
|
}
|
|
1707
1761
|
function getPersistencePaths(projectPath) {
|
|
1708
|
-
const baseDir =
|
|
1762
|
+
const baseDir = path15__default.join(projectPath, ".coco", "spec");
|
|
1709
1763
|
return {
|
|
1710
1764
|
baseDir,
|
|
1711
|
-
sessionFile:
|
|
1712
|
-
specFile:
|
|
1713
|
-
conversationLog:
|
|
1714
|
-
checkpointFile:
|
|
1765
|
+
sessionFile: path15__default.join(baseDir, "discovery-session.json"),
|
|
1766
|
+
specFile: path15__default.join(baseDir, "spec.md"),
|
|
1767
|
+
conversationLog: path15__default.join(baseDir, "conversation.jsonl"),
|
|
1768
|
+
checkpointFile: path15__default.join(baseDir, "checkpoint.json")
|
|
1715
1769
|
};
|
|
1716
1770
|
}
|
|
1717
1771
|
var SessionPersistence = class {
|
|
@@ -3741,7 +3795,7 @@ var OrchestrateExecutor = class {
|
|
|
3741
3795
|
}
|
|
3742
3796
|
async loadSpecification(projectPath) {
|
|
3743
3797
|
try {
|
|
3744
|
-
const jsonPath =
|
|
3798
|
+
const jsonPath = path15__default.join(projectPath, ".coco", "spec", "spec.json");
|
|
3745
3799
|
const jsonContent = await fs14__default.readFile(jsonPath, "utf-8");
|
|
3746
3800
|
return JSON.parse(jsonContent);
|
|
3747
3801
|
} catch {
|
|
@@ -3753,7 +3807,7 @@ var OrchestrateExecutor = class {
|
|
|
3753
3807
|
version: "1.0.0",
|
|
3754
3808
|
generatedAt: /* @__PURE__ */ new Date(),
|
|
3755
3809
|
overview: {
|
|
3756
|
-
name:
|
|
3810
|
+
name: path15__default.basename(projectPath),
|
|
3757
3811
|
description: "Project specification",
|
|
3758
3812
|
goals: [],
|
|
3759
3813
|
targetUsers: ["developers"],
|
|
@@ -3780,52 +3834,52 @@ var OrchestrateExecutor = class {
|
|
|
3780
3834
|
};
|
|
3781
3835
|
}
|
|
3782
3836
|
async saveArchitecture(projectPath, architecture) {
|
|
3783
|
-
const dir =
|
|
3837
|
+
const dir = path15__default.join(projectPath, ".coco", "architecture");
|
|
3784
3838
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
3785
|
-
const mdPath =
|
|
3839
|
+
const mdPath = path15__default.join(dir, "ARCHITECTURE.md");
|
|
3786
3840
|
await fs14__default.writeFile(mdPath, generateArchitectureMarkdown(architecture), "utf-8");
|
|
3787
|
-
const jsonPath =
|
|
3841
|
+
const jsonPath = path15__default.join(dir, "architecture.json");
|
|
3788
3842
|
await fs14__default.writeFile(jsonPath, JSON.stringify(architecture, null, 2), "utf-8");
|
|
3789
3843
|
return mdPath;
|
|
3790
3844
|
}
|
|
3791
3845
|
async saveADRs(projectPath, adrs) {
|
|
3792
|
-
const dir =
|
|
3846
|
+
const dir = path15__default.join(projectPath, ".coco", "architecture", "adrs");
|
|
3793
3847
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
3794
3848
|
const paths = [];
|
|
3795
|
-
const indexPath =
|
|
3849
|
+
const indexPath = path15__default.join(dir, "README.md");
|
|
3796
3850
|
await fs14__default.writeFile(indexPath, generateADRIndexMarkdown(adrs), "utf-8");
|
|
3797
3851
|
paths.push(indexPath);
|
|
3798
3852
|
for (const adr of adrs) {
|
|
3799
3853
|
const filename = getADRFilename(adr);
|
|
3800
|
-
const adrPath =
|
|
3854
|
+
const adrPath = path15__default.join(dir, filename);
|
|
3801
3855
|
await fs14__default.writeFile(adrPath, generateADRMarkdown(adr), "utf-8");
|
|
3802
3856
|
paths.push(adrPath);
|
|
3803
3857
|
}
|
|
3804
3858
|
return paths;
|
|
3805
3859
|
}
|
|
3806
3860
|
async saveBacklog(projectPath, backlogResult) {
|
|
3807
|
-
const dir =
|
|
3861
|
+
const dir = path15__default.join(projectPath, ".coco", "planning");
|
|
3808
3862
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
3809
|
-
const mdPath =
|
|
3863
|
+
const mdPath = path15__default.join(dir, "BACKLOG.md");
|
|
3810
3864
|
await fs14__default.writeFile(mdPath, generateBacklogMarkdown(backlogResult.backlog), "utf-8");
|
|
3811
|
-
const jsonPath =
|
|
3865
|
+
const jsonPath = path15__default.join(dir, "backlog.json");
|
|
3812
3866
|
await fs14__default.writeFile(jsonPath, JSON.stringify(backlogResult, null, 2), "utf-8");
|
|
3813
3867
|
return mdPath;
|
|
3814
3868
|
}
|
|
3815
3869
|
async saveSprint(projectPath, sprint, backlogResult) {
|
|
3816
|
-
const dir =
|
|
3870
|
+
const dir = path15__default.join(projectPath, ".coco", "planning", "sprints");
|
|
3817
3871
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
3818
3872
|
const filename = `${sprint.id}.md`;
|
|
3819
|
-
const sprintPath =
|
|
3873
|
+
const sprintPath = path15__default.join(dir, filename);
|
|
3820
3874
|
await fs14__default.writeFile(sprintPath, generateSprintMarkdown(sprint, backlogResult.backlog), "utf-8");
|
|
3821
|
-
const jsonPath =
|
|
3875
|
+
const jsonPath = path15__default.join(dir, `${sprint.id}.json`);
|
|
3822
3876
|
await fs14__default.writeFile(jsonPath, JSON.stringify(sprint, null, 2), "utf-8");
|
|
3823
3877
|
return sprintPath;
|
|
3824
3878
|
}
|
|
3825
3879
|
async saveDiagram(projectPath, id, mermaid) {
|
|
3826
|
-
const dir =
|
|
3880
|
+
const dir = path15__default.join(projectPath, ".coco", "architecture", "diagrams");
|
|
3827
3881
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
3828
|
-
const diagramPath =
|
|
3882
|
+
const diagramPath = path15__default.join(dir, `${id}.mmd`);
|
|
3829
3883
|
await fs14__default.writeFile(diagramPath, mermaid, "utf-8");
|
|
3830
3884
|
return diagramPath;
|
|
3831
3885
|
}
|
|
@@ -3875,7 +3929,11 @@ var DEFAULT_QUALITY_THRESHOLDS = {
|
|
|
3875
3929
|
target: {
|
|
3876
3930
|
overall: 95,
|
|
3877
3931
|
testCoverage: 90
|
|
3878
|
-
}
|
|
3932
|
+
},
|
|
3933
|
+
convergenceThreshold: 2,
|
|
3934
|
+
maxIterations: 10,
|
|
3935
|
+
minIterations: 2
|
|
3936
|
+
};
|
|
3879
3937
|
async function detectTestFramework(projectPath) {
|
|
3880
3938
|
try {
|
|
3881
3939
|
const pkgPath = join(projectPath, "package.json");
|
|
@@ -3966,10 +4024,10 @@ var CoverageAnalyzer = class {
|
|
|
3966
4024
|
join(this.projectPath, ".coverage", "coverage-summary.json"),
|
|
3967
4025
|
join(this.projectPath, "coverage", "lcov-report", "coverage-summary.json")
|
|
3968
4026
|
];
|
|
3969
|
-
for (const
|
|
4027
|
+
for (const path38 of possiblePaths) {
|
|
3970
4028
|
try {
|
|
3971
|
-
await access(
|
|
3972
|
-
const content = await readFile(
|
|
4029
|
+
await access(path38, constants.R_OK);
|
|
4030
|
+
const content = await readFile(path38, "utf-8");
|
|
3973
4031
|
const report = JSON.parse(content);
|
|
3974
4032
|
return parseCoverageSummary(report);
|
|
3975
4033
|
} catch {
|
|
@@ -4057,7 +4115,7 @@ var SECURITY_PATTERNS = [
|
|
|
4057
4115
|
},
|
|
4058
4116
|
// Command Injection
|
|
4059
4117
|
{
|
|
4060
|
-
regex: /exec\s*\(/
|
|
4118
|
+
regex: /(?:^|[\s;,({])exec\s*\(/gm,
|
|
4061
4119
|
severity: "critical",
|
|
4062
4120
|
type: "Command Injection",
|
|
4063
4121
|
message: "Use of exec() can execute shell commands with user input",
|
|
@@ -4178,6 +4236,26 @@ var SECURITY_PATTERNS = [
|
|
|
4178
4236
|
recommendation: "Validate JSON structure with JSON schema or Zod before parsing."
|
|
4179
4237
|
}
|
|
4180
4238
|
];
|
|
4239
|
+
function computeSecurityScore(vulnerabilities) {
|
|
4240
|
+
let score = 100;
|
|
4241
|
+
for (const vuln of vulnerabilities) {
|
|
4242
|
+
switch (vuln.severity) {
|
|
4243
|
+
case "critical":
|
|
4244
|
+
score -= 25;
|
|
4245
|
+
break;
|
|
4246
|
+
case "high":
|
|
4247
|
+
score -= 10;
|
|
4248
|
+
break;
|
|
4249
|
+
case "medium":
|
|
4250
|
+
score -= 5;
|
|
4251
|
+
break;
|
|
4252
|
+
case "low":
|
|
4253
|
+
score -= 2;
|
|
4254
|
+
break;
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4257
|
+
return Math.max(0, score);
|
|
4258
|
+
}
|
|
4181
4259
|
var PatternSecurityScanner = class {
|
|
4182
4260
|
/**
|
|
4183
4261
|
* Scan files for security vulnerabilities using pattern matching
|
|
@@ -4228,32 +4306,8 @@ var PatternSecurityScanner = class {
|
|
|
4228
4306
|
const lastLine = lines[lines.length - 1] ?? "";
|
|
4229
4307
|
return lastLine.length + 1;
|
|
4230
4308
|
}
|
|
4231
|
-
/**
|
|
4232
|
-
* Calculate security score based on vulnerabilities
|
|
4233
|
-
* Critical: -25 points each
|
|
4234
|
-
* High: -10 points each
|
|
4235
|
-
* Medium: -5 points each
|
|
4236
|
-
* Low: -2 points each
|
|
4237
|
-
*/
|
|
4238
4309
|
calculateScore(vulnerabilities) {
|
|
4239
|
-
|
|
4240
|
-
for (const vuln of vulnerabilities) {
|
|
4241
|
-
switch (vuln.severity) {
|
|
4242
|
-
case "critical":
|
|
4243
|
-
score -= 25;
|
|
4244
|
-
break;
|
|
4245
|
-
case "high":
|
|
4246
|
-
score -= 10;
|
|
4247
|
-
break;
|
|
4248
|
-
case "medium":
|
|
4249
|
-
score -= 5;
|
|
4250
|
-
break;
|
|
4251
|
-
case "low":
|
|
4252
|
-
score -= 2;
|
|
4253
|
-
break;
|
|
4254
|
-
}
|
|
4255
|
-
}
|
|
4256
|
-
return Math.max(0, score);
|
|
4310
|
+
return computeSecurityScore(vulnerabilities);
|
|
4257
4311
|
}
|
|
4258
4312
|
};
|
|
4259
4313
|
var SnykSecurityScanner = class {
|
|
@@ -4309,24 +4363,7 @@ var SnykSecurityScanner = class {
|
|
|
4309
4363
|
}));
|
|
4310
4364
|
}
|
|
4311
4365
|
calculateScore(vulnerabilities) {
|
|
4312
|
-
|
|
4313
|
-
for (const vuln of vulnerabilities) {
|
|
4314
|
-
switch (vuln.severity) {
|
|
4315
|
-
case "critical":
|
|
4316
|
-
score -= 25;
|
|
4317
|
-
break;
|
|
4318
|
-
case "high":
|
|
4319
|
-
score -= 10;
|
|
4320
|
-
break;
|
|
4321
|
-
case "medium":
|
|
4322
|
-
score -= 5;
|
|
4323
|
-
break;
|
|
4324
|
-
case "low":
|
|
4325
|
-
score -= 2;
|
|
4326
|
-
break;
|
|
4327
|
-
}
|
|
4328
|
-
}
|
|
4329
|
-
return Math.max(0, score);
|
|
4366
|
+
return computeSecurityScore(vulnerabilities);
|
|
4330
4367
|
}
|
|
4331
4368
|
};
|
|
4332
4369
|
var CompositeSecurityScanner = class {
|
|
@@ -4646,6 +4683,17 @@ var BuildVerifier = class {
|
|
|
4646
4683
|
stderr: ""
|
|
4647
4684
|
};
|
|
4648
4685
|
}
|
|
4686
|
+
const SAFE_BUILD_PATTERN = /^(npm|pnpm|yarn|bun)\s+(run\s+)?[\w:.-]+$|^npx\s+tsc(\s+--[\w-]+)*$/;
|
|
4687
|
+
if (!SAFE_BUILD_PATTERN.test(buildCommand.trim())) {
|
|
4688
|
+
return {
|
|
4689
|
+
success: false,
|
|
4690
|
+
errors: [{ file: "", line: 0, column: 0, message: `Unsafe build command rejected: ${buildCommand}` }],
|
|
4691
|
+
warnings: [],
|
|
4692
|
+
duration: Date.now() - startTime,
|
|
4693
|
+
stdout: "",
|
|
4694
|
+
stderr: ""
|
|
4695
|
+
};
|
|
4696
|
+
}
|
|
4649
4697
|
const { stdout, stderr } = await execAsync(buildCommand, {
|
|
4650
4698
|
cwd: this.projectPath,
|
|
4651
4699
|
timeout: 12e4,
|
|
@@ -4687,7 +4735,7 @@ var BuildVerifier = class {
|
|
|
4687
4735
|
async verifyTypes() {
|
|
4688
4736
|
const startTime = Date.now();
|
|
4689
4737
|
try {
|
|
4690
|
-
const hasTsConfig = await this.fileExists(
|
|
4738
|
+
const hasTsConfig = await this.fileExists(path15.join(this.projectPath, "tsconfig.json"));
|
|
4691
4739
|
if (!hasTsConfig) {
|
|
4692
4740
|
return {
|
|
4693
4741
|
success: true,
|
|
@@ -4737,7 +4785,7 @@ var BuildVerifier = class {
|
|
|
4737
4785
|
*/
|
|
4738
4786
|
async detectBuildCommand() {
|
|
4739
4787
|
try {
|
|
4740
|
-
const packageJsonPath =
|
|
4788
|
+
const packageJsonPath = path15.join(this.projectPath, "package.json");
|
|
4741
4789
|
const content = await fs14.readFile(packageJsonPath, "utf-8");
|
|
4742
4790
|
const packageJson = JSON.parse(content);
|
|
4743
4791
|
if (packageJson.scripts?.build) {
|
|
@@ -5154,10 +5202,6 @@ function analyzeRobustnessPatterns(ast) {
|
|
|
5154
5202
|
if (currentFunctionHasTryCatch) {
|
|
5155
5203
|
functionsWithTryCatch++;
|
|
5156
5204
|
}
|
|
5157
|
-
if (isFunctionNode) {
|
|
5158
|
-
functions--;
|
|
5159
|
-
functions++;
|
|
5160
|
-
}
|
|
5161
5205
|
insideFunction = previousInsideFunction;
|
|
5162
5206
|
currentFunctionHasTryCatch = previousHasTryCatch;
|
|
5163
5207
|
return;
|
|
@@ -5991,125 +6035,1501 @@ function analyzeMaintainabilityPatterns(ast, filePath) {
|
|
|
5991
6035
|
}
|
|
5992
6036
|
}
|
|
5993
6037
|
}
|
|
5994
|
-
traverse(ast);
|
|
5995
|
-
return {
|
|
5996
|
-
lineCount: 0,
|
|
5997
|
-
// Will be set separately from content
|
|
5998
|
-
functionCount,
|
|
5999
|
-
importCount,
|
|
6000
|
-
crossBoundaryImportCount
|
|
6001
|
-
};
|
|
6002
|
-
}
|
|
6003
|
-
function isCrossBoundaryImport(node, _filePath) {
|
|
6004
|
-
if (node.source.type !== "Literal" || typeof node.source.value !== "string") {
|
|
6005
|
-
return false;
|
|
6006
|
-
}
|
|
6007
|
-
const importPath = node.source.value;
|
|
6008
|
-
if (importPath.startsWith("node:")) {
|
|
6009
|
-
return false;
|
|
6038
|
+
traverse(ast);
|
|
6039
|
+
return {
|
|
6040
|
+
lineCount: 0,
|
|
6041
|
+
// Will be set separately from content
|
|
6042
|
+
functionCount,
|
|
6043
|
+
importCount,
|
|
6044
|
+
crossBoundaryImportCount
|
|
6045
|
+
};
|
|
6046
|
+
}
|
|
6047
|
+
function isCrossBoundaryImport(node, _filePath) {
|
|
6048
|
+
if (node.source.type !== "Literal" || typeof node.source.value !== "string") {
|
|
6049
|
+
return false;
|
|
6050
|
+
}
|
|
6051
|
+
const importPath = node.source.value;
|
|
6052
|
+
if (importPath.startsWith("node:")) {
|
|
6053
|
+
return false;
|
|
6054
|
+
}
|
|
6055
|
+
if (!importPath.startsWith(".")) {
|
|
6056
|
+
return false;
|
|
6057
|
+
}
|
|
6058
|
+
if (importPath.startsWith("../")) {
|
|
6059
|
+
return true;
|
|
6060
|
+
}
|
|
6061
|
+
if (importPath.startsWith("./")) {
|
|
6062
|
+
const relativePath = importPath.slice(2);
|
|
6063
|
+
return relativePath.includes("/");
|
|
6064
|
+
}
|
|
6065
|
+
return false;
|
|
6066
|
+
}
|
|
6067
|
+
function countLines(content) {
|
|
6068
|
+
return content.split("\n").length;
|
|
6069
|
+
}
|
|
6070
|
+
var MaintainabilityAnalyzer = class {
|
|
6071
|
+
constructor(projectPath) {
|
|
6072
|
+
this.projectPath = projectPath;
|
|
6073
|
+
}
|
|
6074
|
+
/**
|
|
6075
|
+
* Analyze maintainability of project files
|
|
6076
|
+
*/
|
|
6077
|
+
async analyze(files) {
|
|
6078
|
+
const targetFiles = files ?? await this.findSourceFiles();
|
|
6079
|
+
if (targetFiles.length === 0) {
|
|
6080
|
+
return {
|
|
6081
|
+
score: 100,
|
|
6082
|
+
fileLengthScore: 100,
|
|
6083
|
+
functionCountScore: 100,
|
|
6084
|
+
dependencyCountScore: 100,
|
|
6085
|
+
couplingScore: 100,
|
|
6086
|
+
averageFileLength: 0,
|
|
6087
|
+
averageFunctionsPerFile: 0,
|
|
6088
|
+
averageImportsPerFile: 0,
|
|
6089
|
+
fileCount: 0,
|
|
6090
|
+
details: "No files to analyze"
|
|
6091
|
+
};
|
|
6092
|
+
}
|
|
6093
|
+
let totalLines = 0;
|
|
6094
|
+
let totalFunctions = 0;
|
|
6095
|
+
let totalImports = 0;
|
|
6096
|
+
let totalCrossBoundaryImports = 0;
|
|
6097
|
+
let filesAnalyzed = 0;
|
|
6098
|
+
for (const file of targetFiles) {
|
|
6099
|
+
try {
|
|
6100
|
+
const content = await readFile(file, "utf-8");
|
|
6101
|
+
const lineCount = countLines(content);
|
|
6102
|
+
const ast = parse(content, {
|
|
6103
|
+
loc: true,
|
|
6104
|
+
range: true,
|
|
6105
|
+
jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
|
|
6106
|
+
});
|
|
6107
|
+
const result = analyzeMaintainabilityPatterns(ast, file);
|
|
6108
|
+
totalLines += lineCount;
|
|
6109
|
+
totalFunctions += result.functionCount;
|
|
6110
|
+
totalImports += result.importCount;
|
|
6111
|
+
totalCrossBoundaryImports += result.crossBoundaryImportCount;
|
|
6112
|
+
filesAnalyzed++;
|
|
6113
|
+
} catch {
|
|
6114
|
+
}
|
|
6115
|
+
}
|
|
6116
|
+
const averageFileLength = filesAnalyzed > 0 ? totalLines / filesAnalyzed : 0;
|
|
6117
|
+
const averageFunctionsPerFile = filesAnalyzed > 0 ? totalFunctions / filesAnalyzed : 0;
|
|
6118
|
+
const averageImportsPerFile = filesAnalyzed > 0 ? totalImports / filesAnalyzed : 0;
|
|
6119
|
+
const fileLengthScore = Math.max(0, Math.min(100, 100 - (averageFileLength - 200) * 0.33));
|
|
6120
|
+
const functionCountScore = Math.max(0, Math.min(100, 100 - (averageFunctionsPerFile - 10) * 5));
|
|
6121
|
+
const dependencyCountScore = Math.max(0, Math.min(100, 100 - (averageImportsPerFile - 5) * 5));
|
|
6122
|
+
const crossBoundaryRatio = totalImports > 0 ? totalCrossBoundaryImports / totalImports : 0;
|
|
6123
|
+
const couplingScore = Math.max(0, Math.min(100, 100 - crossBoundaryRatio * 100 * 0.5));
|
|
6124
|
+
const score = Math.round(
|
|
6125
|
+
fileLengthScore * 0.3 + functionCountScore * 0.25 + dependencyCountScore * 0.25 + couplingScore * 0.2
|
|
6126
|
+
);
|
|
6127
|
+
const details = [
|
|
6128
|
+
`${Math.round(averageFileLength)} avg lines/file`,
|
|
6129
|
+
`${averageFunctionsPerFile.toFixed(1)} avg functions/file`,
|
|
6130
|
+
`${averageImportsPerFile.toFixed(1)} avg imports/file`,
|
|
6131
|
+
`${Math.round(crossBoundaryRatio * 100)}% cross-boundary coupling`
|
|
6132
|
+
].join(", ");
|
|
6133
|
+
return {
|
|
6134
|
+
score,
|
|
6135
|
+
fileLengthScore,
|
|
6136
|
+
functionCountScore,
|
|
6137
|
+
dependencyCountScore,
|
|
6138
|
+
couplingScore,
|
|
6139
|
+
averageFileLength,
|
|
6140
|
+
averageFunctionsPerFile,
|
|
6141
|
+
averageImportsPerFile,
|
|
6142
|
+
fileCount: filesAnalyzed,
|
|
6143
|
+
details
|
|
6144
|
+
};
|
|
6145
|
+
}
|
|
6146
|
+
async findSourceFiles() {
|
|
6147
|
+
return glob("**/*.{ts,js,tsx,jsx}", {
|
|
6148
|
+
cwd: this.projectPath,
|
|
6149
|
+
absolute: true,
|
|
6150
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
|
|
6151
|
+
});
|
|
6152
|
+
}
|
|
6153
|
+
};
|
|
6154
|
+
var PROJECT_CONFIG_FILENAME = ".coco.config.json";
|
|
6155
|
+
var QualityWeightsOverrideSchema = z.object({
|
|
6156
|
+
correctness: z.number().min(0).max(1),
|
|
6157
|
+
completeness: z.number().min(0).max(1),
|
|
6158
|
+
robustness: z.number().min(0).max(1),
|
|
6159
|
+
readability: z.number().min(0).max(1),
|
|
6160
|
+
maintainability: z.number().min(0).max(1),
|
|
6161
|
+
complexity: z.number().min(0).max(1),
|
|
6162
|
+
duplication: z.number().min(0).max(1),
|
|
6163
|
+
testCoverage: z.number().min(0).max(1),
|
|
6164
|
+
testQuality: z.number().min(0).max(1),
|
|
6165
|
+
security: z.number().min(0).max(1),
|
|
6166
|
+
documentation: z.number().min(0).max(1),
|
|
6167
|
+
style: z.number().min(0).max(1)
|
|
6168
|
+
}).partial();
|
|
6169
|
+
var ProjectQualityOverridesSchema = z.object({
|
|
6170
|
+
/** Minimum overall score (0–100). Default: 85 */
|
|
6171
|
+
minScore: z.number().min(0).max(100).optional(),
|
|
6172
|
+
/** Minimum test-coverage percentage (0–100). Default: 80 */
|
|
6173
|
+
minCoverage: z.number().min(0).max(100).optional(),
|
|
6174
|
+
/** Maximum convergence iterations. Default: 10 */
|
|
6175
|
+
maxIterations: z.number().min(1).max(50).optional(),
|
|
6176
|
+
/** Required security score (0–100). Default: 100 */
|
|
6177
|
+
securityThreshold: z.number().min(0).max(100).optional(),
|
|
6178
|
+
/** Per-dimension weight overrides */
|
|
6179
|
+
weights: QualityWeightsOverrideSchema.optional(),
|
|
6180
|
+
/** Stored but not yet enforced by analyzers — reserved for a future release. */
|
|
6181
|
+
ignoreRules: z.array(z.string()).optional(),
|
|
6182
|
+
/** Stored but not yet enforced by analyzers — reserved for a future release. */
|
|
6183
|
+
ignoreFiles: z.array(z.string()).optional()
|
|
6184
|
+
});
|
|
6185
|
+
var ProjectAnalyzersConfigSchema = z.object({
|
|
6186
|
+
/** Restrict analysis to these language IDs (default: auto-detect) */
|
|
6187
|
+
enabledLanguages: z.array(z.string()).optional(),
|
|
6188
|
+
/** Java-specific options */
|
|
6189
|
+
java: z.object({
|
|
6190
|
+
/** Minimum line coverage expected from JaCoCo report */
|
|
6191
|
+
minCoverage: z.number().min(0).max(100).optional(),
|
|
6192
|
+
/** Custom path to jacoco.xml relative to project root */
|
|
6193
|
+
reportPath: z.string().optional()
|
|
6194
|
+
}).optional(),
|
|
6195
|
+
/** React-specific options */
|
|
6196
|
+
react: z.object({
|
|
6197
|
+
/** Run accessibility (a11y) checks. Default: true */
|
|
6198
|
+
checkA11y: z.boolean().optional(),
|
|
6199
|
+
/** Enforce React Rules of Hooks. Default: true */
|
|
6200
|
+
checkHooks: z.boolean().optional(),
|
|
6201
|
+
/** Run component-quality checks. Default: true */
|
|
6202
|
+
checkComponents: z.boolean().optional()
|
|
6203
|
+
}).optional()
|
|
6204
|
+
});
|
|
6205
|
+
var ProjectConfigSchema = z.object({
|
|
6206
|
+
/** Human-readable project name */
|
|
6207
|
+
name: z.string().optional(),
|
|
6208
|
+
/** SemVer string for the config schema itself */
|
|
6209
|
+
version: z.string().optional(),
|
|
6210
|
+
/** Short project description */
|
|
6211
|
+
description: z.string().optional(),
|
|
6212
|
+
/**
|
|
6213
|
+
* Primary project language ID.
|
|
6214
|
+
* Used when auto-detection is ambiguous.
|
|
6215
|
+
* @example "typescript" | "java" | "react-typescript"
|
|
6216
|
+
*/
|
|
6217
|
+
language: z.string().optional(),
|
|
6218
|
+
/** Quality threshold and weight overrides */
|
|
6219
|
+
quality: ProjectQualityOverridesSchema.optional(),
|
|
6220
|
+
/** Analyzer-specific settings */
|
|
6221
|
+
analyzers: ProjectAnalyzersConfigSchema.optional(),
|
|
6222
|
+
/**
|
|
6223
|
+
* Path to a base config to inherit from (relative to this file).
|
|
6224
|
+
* Merged shallowly; this file wins on conflicts.
|
|
6225
|
+
* @note extend is resolved only one level deep — chaining (A extends B extends C) is not supported.
|
|
6226
|
+
* @example "../shared/.coco.config.json"
|
|
6227
|
+
*/
|
|
6228
|
+
extend: z.string().optional()
|
|
6229
|
+
});
|
|
6230
|
+
function getProjectConfigPath(projectPath) {
|
|
6231
|
+
return join(resolve(projectPath), PROJECT_CONFIG_FILENAME);
|
|
6232
|
+
}
|
|
6233
|
+
async function loadProjectConfig(projectPath) {
|
|
6234
|
+
const configPath = getProjectConfigPath(projectPath);
|
|
6235
|
+
let raw;
|
|
6236
|
+
try {
|
|
6237
|
+
raw = await readFile(configPath, "utf-8");
|
|
6238
|
+
} catch (err) {
|
|
6239
|
+
if (err.code === "ENOENT") return null;
|
|
6240
|
+
throw err;
|
|
6241
|
+
}
|
|
6242
|
+
const parsed = JSON.parse(raw);
|
|
6243
|
+
const result = ProjectConfigSchema.safeParse(parsed);
|
|
6244
|
+
if (!result.success) {
|
|
6245
|
+
throw new Error(
|
|
6246
|
+
`Invalid ${PROJECT_CONFIG_FILENAME} at ${configPath}: ${result.error.message}`
|
|
6247
|
+
);
|
|
6248
|
+
}
|
|
6249
|
+
let config = result.data;
|
|
6250
|
+
if (config.extend) {
|
|
6251
|
+
const basePath = resolve(dirname(configPath), config.extend);
|
|
6252
|
+
let baseRaw;
|
|
6253
|
+
try {
|
|
6254
|
+
baseRaw = await readFile(basePath, "utf-8");
|
|
6255
|
+
} catch (err) {
|
|
6256
|
+
throw new Error(
|
|
6257
|
+
`Cannot extend "${config.extend}" \u2014 file not found at ${basePath}: ${String(err)}`
|
|
6258
|
+
);
|
|
6259
|
+
}
|
|
6260
|
+
const baseResult = ProjectConfigSchema.safeParse(JSON.parse(baseRaw));
|
|
6261
|
+
if (!baseResult.success) {
|
|
6262
|
+
throw new Error(
|
|
6263
|
+
`Invalid base config at "${config.extend}" (resolved to ${basePath}): ${baseResult.error.message}`
|
|
6264
|
+
);
|
|
6265
|
+
}
|
|
6266
|
+
config = mergeProjectConfigs(baseResult.data, config);
|
|
6267
|
+
}
|
|
6268
|
+
return config;
|
|
6269
|
+
}
|
|
6270
|
+
function mergeProjectConfigs(base, override) {
|
|
6271
|
+
const hasQuality = base.quality !== void 0 || override.quality !== void 0;
|
|
6272
|
+
const hasAnalyzers = base.analyzers !== void 0 || override.analyzers !== void 0;
|
|
6273
|
+
return {
|
|
6274
|
+
...base,
|
|
6275
|
+
...override,
|
|
6276
|
+
quality: hasQuality ? {
|
|
6277
|
+
...base.quality,
|
|
6278
|
+
...override.quality,
|
|
6279
|
+
weights: base.quality?.weights !== void 0 || override.quality?.weights !== void 0 ? { ...base.quality?.weights, ...override.quality?.weights } : void 0,
|
|
6280
|
+
ignoreRules: [
|
|
6281
|
+
...base.quality?.ignoreRules ?? [],
|
|
6282
|
+
...override.quality?.ignoreRules ?? []
|
|
6283
|
+
],
|
|
6284
|
+
ignoreFiles: [
|
|
6285
|
+
...base.quality?.ignoreFiles ?? [],
|
|
6286
|
+
...override.quality?.ignoreFiles ?? []
|
|
6287
|
+
]
|
|
6288
|
+
} : void 0,
|
|
6289
|
+
analyzers: hasAnalyzers ? {
|
|
6290
|
+
...base.analyzers,
|
|
6291
|
+
...override.analyzers,
|
|
6292
|
+
java: base.analyzers?.java !== void 0 || override.analyzers?.java !== void 0 ? { ...base.analyzers?.java, ...override.analyzers?.java } : void 0,
|
|
6293
|
+
react: base.analyzers?.react !== void 0 || override.analyzers?.react !== void 0 ? { ...base.analyzers?.react, ...override.analyzers?.react } : void 0
|
|
6294
|
+
} : void 0
|
|
6295
|
+
};
|
|
6296
|
+
}
|
|
6297
|
+
|
|
6298
|
+
// src/quality/quality-bridge.ts
|
|
6299
|
+
({
|
|
6300
|
+
minScore: DEFAULT_QUALITY_THRESHOLDS.minimum.overall,
|
|
6301
|
+
targetScore: DEFAULT_QUALITY_THRESHOLDS.target.overall,
|
|
6302
|
+
maxIterations: DEFAULT_QUALITY_THRESHOLDS.maxIterations,
|
|
6303
|
+
stableDeltaThreshold: DEFAULT_QUALITY_THRESHOLDS.convergenceThreshold
|
|
6304
|
+
});
|
|
6305
|
+
function thresholdsFromProjectConfig(config) {
|
|
6306
|
+
const q = config.quality;
|
|
6307
|
+
if (!q) return {};
|
|
6308
|
+
const result = {};
|
|
6309
|
+
const hasMinimum = q.minScore !== void 0 || q.minCoverage !== void 0 || q.securityThreshold !== void 0;
|
|
6310
|
+
if (hasMinimum) {
|
|
6311
|
+
result.minimum = {
|
|
6312
|
+
overall: q.minScore ?? DEFAULT_QUALITY_THRESHOLDS.minimum.overall,
|
|
6313
|
+
testCoverage: q.minCoverage ?? DEFAULT_QUALITY_THRESHOLDS.minimum.testCoverage,
|
|
6314
|
+
security: q.securityThreshold ?? DEFAULT_QUALITY_THRESHOLDS.minimum.security
|
|
6315
|
+
};
|
|
6316
|
+
}
|
|
6317
|
+
if (q.maxIterations !== void 0) {
|
|
6318
|
+
result.maxIterations = q.maxIterations;
|
|
6319
|
+
result.convergenceThreshold = DEFAULT_QUALITY_THRESHOLDS.convergenceThreshold;
|
|
6320
|
+
}
|
|
6321
|
+
return result;
|
|
6322
|
+
}
|
|
6323
|
+
function mergeThresholds(base, overrides) {
|
|
6324
|
+
return {
|
|
6325
|
+
...base,
|
|
6326
|
+
...overrides,
|
|
6327
|
+
minimum: overrides.minimum ? { ...base.minimum, ...overrides.minimum } : base.minimum,
|
|
6328
|
+
target: overrides.target ? { ...base.target, ...overrides.target } : base.target
|
|
6329
|
+
};
|
|
6330
|
+
}
|
|
6331
|
+
function resolvedThresholds(projectConfig) {
|
|
6332
|
+
if (!projectConfig) return DEFAULT_QUALITY_THRESHOLDS;
|
|
6333
|
+
return mergeThresholds(
|
|
6334
|
+
DEFAULT_QUALITY_THRESHOLDS,
|
|
6335
|
+
thresholdsFromProjectConfig(projectConfig)
|
|
6336
|
+
);
|
|
6337
|
+
}
|
|
6338
|
+
function weightsFromProjectConfig(config) {
|
|
6339
|
+
const overrides = config.quality?.weights;
|
|
6340
|
+
if (!overrides || Object.keys(overrides).length === 0) {
|
|
6341
|
+
return DEFAULT_QUALITY_WEIGHTS;
|
|
6342
|
+
}
|
|
6343
|
+
const merged = { ...DEFAULT_QUALITY_WEIGHTS, ...overrides };
|
|
6344
|
+
const total = Object.values(merged).reduce((s, v) => s + v, 0);
|
|
6345
|
+
if (total === 0) return DEFAULT_QUALITY_WEIGHTS;
|
|
6346
|
+
return Object.fromEntries(
|
|
6347
|
+
Object.entries(merged).map(([k, v]) => [k, v / total])
|
|
6348
|
+
);
|
|
6349
|
+
}
|
|
6350
|
+
function resolvedWeights(projectConfig) {
|
|
6351
|
+
if (!projectConfig) return DEFAULT_QUALITY_WEIGHTS;
|
|
6352
|
+
return weightsFromProjectConfig(projectConfig);
|
|
6353
|
+
}
|
|
6354
|
+
var EXTENSION_MAP = {
|
|
6355
|
+
".ts": "typescript",
|
|
6356
|
+
".d.ts": "typescript",
|
|
6357
|
+
".tsx": "react-typescript",
|
|
6358
|
+
".js": "javascript",
|
|
6359
|
+
".mjs": "javascript",
|
|
6360
|
+
".cjs": "javascript",
|
|
6361
|
+
".jsx": "react-javascript",
|
|
6362
|
+
".java": "java",
|
|
6363
|
+
".py": "python",
|
|
6364
|
+
".go": "go",
|
|
6365
|
+
".rs": "rust"
|
|
6366
|
+
};
|
|
6367
|
+
function detectLanguage(filePath, content) {
|
|
6368
|
+
if (!filePath) return "unknown";
|
|
6369
|
+
const ext = getFileExtension(filePath);
|
|
6370
|
+
const langByExt = EXTENSION_MAP[ext];
|
|
6371
|
+
if (!langByExt) return "unknown";
|
|
6372
|
+
return langByExt;
|
|
6373
|
+
}
|
|
6374
|
+
function detectProjectLanguage(files) {
|
|
6375
|
+
if (!files.length) {
|
|
6376
|
+
return { language: "unknown", confidence: 0, evidence: [] };
|
|
6377
|
+
}
|
|
6378
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6379
|
+
let totalSourceFiles = 0;
|
|
6380
|
+
for (const file of files) {
|
|
6381
|
+
const lang = detectLanguage(file);
|
|
6382
|
+
if (lang !== "unknown") {
|
|
6383
|
+
counts.set(lang, (counts.get(lang) ?? 0) + 1);
|
|
6384
|
+
totalSourceFiles++;
|
|
6385
|
+
}
|
|
6386
|
+
}
|
|
6387
|
+
if (totalSourceFiles === 0) {
|
|
6388
|
+
return { language: "unknown", confidence: 0, evidence: [] };
|
|
6389
|
+
}
|
|
6390
|
+
let maxCount = 0;
|
|
6391
|
+
let dominant = "unknown";
|
|
6392
|
+
for (const [lang, count] of counts) {
|
|
6393
|
+
if (count > maxCount) {
|
|
6394
|
+
maxCount = count;
|
|
6395
|
+
dominant = lang;
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6398
|
+
const confidence = maxCount / totalSourceFiles;
|
|
6399
|
+
const evidence = buildEvidence(dominant, counts, totalSourceFiles, files);
|
|
6400
|
+
const tsxCount = counts.get("react-typescript") ?? 0;
|
|
6401
|
+
const tsCount = counts.get("typescript") ?? 0;
|
|
6402
|
+
if (dominant === "typescript" && tsxCount > 0 && tsxCount >= tsCount * 0.3) {
|
|
6403
|
+
return {
|
|
6404
|
+
language: "react-typescript",
|
|
6405
|
+
confidence,
|
|
6406
|
+
evidence: [...evidence, `${tsxCount} React (.tsx) files detected`]
|
|
6407
|
+
};
|
|
6408
|
+
}
|
|
6409
|
+
return { language: dominant, confidence, evidence };
|
|
6410
|
+
}
|
|
6411
|
+
function getFileExtension(filePath) {
|
|
6412
|
+
const base = path15.basename(filePath);
|
|
6413
|
+
if (base.endsWith(".d.ts")) return ".d.ts";
|
|
6414
|
+
return path15.extname(filePath).toLowerCase();
|
|
6415
|
+
}
|
|
6416
|
+
function buildEvidence(dominant, counts, totalSourceFiles, files) {
|
|
6417
|
+
const evidence = [];
|
|
6418
|
+
const dominantCount = counts.get(dominant) ?? 0;
|
|
6419
|
+
evidence.push(
|
|
6420
|
+
`${dominantCount} of ${totalSourceFiles} source files are ${dominant}`
|
|
6421
|
+
);
|
|
6422
|
+
const configFiles = ["tsconfig.json", "pom.xml", "build.gradle", "Cargo.toml", "go.mod"];
|
|
6423
|
+
for (const cfg of configFiles) {
|
|
6424
|
+
if (files.some((f) => path15.basename(f) === cfg)) {
|
|
6425
|
+
evidence.push(`Found ${cfg}`);
|
|
6426
|
+
}
|
|
6427
|
+
}
|
|
6428
|
+
return evidence;
|
|
6429
|
+
}
|
|
6430
|
+
async function findJavaFiles(projectPath, options) {
|
|
6431
|
+
const { includeTests = true, srcPattern = "**/*.java" } = options ?? {};
|
|
6432
|
+
const ignore = ["**/node_modules/**", "**/target/**", "**/build/**"];
|
|
6433
|
+
if (!includeTests) {
|
|
6434
|
+
ignore.push("**/*Test*.java", "**/*Spec*.java");
|
|
6435
|
+
}
|
|
6436
|
+
return glob(srcPattern, {
|
|
6437
|
+
cwd: projectPath,
|
|
6438
|
+
absolute: true,
|
|
6439
|
+
ignore
|
|
6440
|
+
});
|
|
6441
|
+
}
|
|
6442
|
+
var BRANCH_KEYWORDS = /\b(if|else if|for|while|do|case|catch|&&|\|\||\?)\b/g;
|
|
6443
|
+
var METHOD_PATTERN = /(?:public|private|protected|static|final|synchronized|abstract)\s+[\w<>[\]]+\s+\w+\s*\([^)]*\)\s*(?:throws\s+[\w,\s]+)?\s*\{/g;
|
|
6444
|
+
var JavaComplexityAnalyzer = class {
|
|
6445
|
+
constructor(projectPath) {
|
|
6446
|
+
this.projectPath = projectPath;
|
|
6447
|
+
}
|
|
6448
|
+
async analyze(files) {
|
|
6449
|
+
const javaFiles = files ?? await findJavaFiles(this.projectPath);
|
|
6450
|
+
if (!javaFiles.length) {
|
|
6451
|
+
return { score: 100, totalMethods: 0, averageComplexity: 1, complexMethods: [] };
|
|
6452
|
+
}
|
|
6453
|
+
const fileContents = await Promise.all(
|
|
6454
|
+
javaFiles.map(async (f) => ({
|
|
6455
|
+
path: f,
|
|
6456
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6457
|
+
}))
|
|
6458
|
+
);
|
|
6459
|
+
return this.analyzeContent(fileContents);
|
|
6460
|
+
}
|
|
6461
|
+
analyzeContent(files) {
|
|
6462
|
+
if (!files.length) {
|
|
6463
|
+
return { score: 100, totalMethods: 0, averageComplexity: 1, complexMethods: [] };
|
|
6464
|
+
}
|
|
6465
|
+
let totalComplexity = 0;
|
|
6466
|
+
let totalMethods = 0;
|
|
6467
|
+
const complexMethods = [];
|
|
6468
|
+
for (const { path: filePath, content } of files) {
|
|
6469
|
+
const methods = this.extractMethodBlocks(content);
|
|
6470
|
+
for (const method of methods) {
|
|
6471
|
+
const complexity = this.calculateComplexity(method.body);
|
|
6472
|
+
totalComplexity += complexity;
|
|
6473
|
+
totalMethods++;
|
|
6474
|
+
if (complexity > 10) {
|
|
6475
|
+
complexMethods.push({ method: method.name, file: filePath, complexity });
|
|
6476
|
+
}
|
|
6477
|
+
}
|
|
6478
|
+
}
|
|
6479
|
+
const averageComplexity = totalMethods > 0 ? totalComplexity / totalMethods : 1;
|
|
6480
|
+
const score = Math.max(0, Math.min(100, Math.round(100 - (averageComplexity - 1) * 6.5)));
|
|
6481
|
+
return { score, totalMethods, averageComplexity, complexMethods };
|
|
6482
|
+
}
|
|
6483
|
+
extractMethodBlocks(content) {
|
|
6484
|
+
const methods = [];
|
|
6485
|
+
const methodRegex = new RegExp(METHOD_PATTERN.source, "g");
|
|
6486
|
+
let match;
|
|
6487
|
+
while ((match = methodRegex.exec(content)) !== null) {
|
|
6488
|
+
const nameMatch = /\s(\w+)\s*\(/.exec(match[0]);
|
|
6489
|
+
const methodName = nameMatch ? nameMatch[1] ?? "unknown" : "unknown";
|
|
6490
|
+
const body = this.extractBlock(content, match.index + match[0].length - 1);
|
|
6491
|
+
methods.push({ name: methodName, body });
|
|
6492
|
+
}
|
|
6493
|
+
return methods;
|
|
6494
|
+
}
|
|
6495
|
+
extractBlock(content, openBraceIndex) {
|
|
6496
|
+
let depth = 1;
|
|
6497
|
+
let i = openBraceIndex + 1;
|
|
6498
|
+
while (i < content.length && depth > 0) {
|
|
6499
|
+
if (content[i] === "{") depth++;
|
|
6500
|
+
else if (content[i] === "}") depth--;
|
|
6501
|
+
i++;
|
|
6502
|
+
}
|
|
6503
|
+
return content.slice(openBraceIndex, i);
|
|
6504
|
+
}
|
|
6505
|
+
calculateComplexity(body) {
|
|
6506
|
+
const matches = body.match(BRANCH_KEYWORDS) ?? [];
|
|
6507
|
+
return 1 + matches.length;
|
|
6508
|
+
}
|
|
6509
|
+
};
|
|
6510
|
+
var JAVA_SECURITY_PATTERNS = [
|
|
6511
|
+
// SQL Injection — string concatenation in any execute/executeQuery/executeUpdate call
|
|
6512
|
+
{
|
|
6513
|
+
regex: /\.execute[A-Za-z]*\s*\(\s*["'][^"']*["']\s*\+/,
|
|
6514
|
+
severity: "critical",
|
|
6515
|
+
type: "SQL Injection",
|
|
6516
|
+
description: "String concatenation in SQL query \u2014 vulnerable to injection",
|
|
6517
|
+
recommendation: "Use PreparedStatement with parameterized queries",
|
|
6518
|
+
cwe: "CWE-89"
|
|
6519
|
+
},
|
|
6520
|
+
{
|
|
6521
|
+
regex: /createQuery\s*\(\s*["'].*\+|createNativeQuery\s*\(\s*["'].*\+/,
|
|
6522
|
+
severity: "critical",
|
|
6523
|
+
type: "SQL Injection (JPQL)",
|
|
6524
|
+
description: "String concatenation in JPQL/native query",
|
|
6525
|
+
recommendation: "Use named parameters with setParameter()",
|
|
6526
|
+
cwe: "CWE-89"
|
|
6527
|
+
},
|
|
6528
|
+
// Hardcoded Credentials
|
|
6529
|
+
{
|
|
6530
|
+
regex: /(?:password|passwd|secret|apiKey|api_key)\s*=\s*["'][^"']{4,}["']/i,
|
|
6531
|
+
severity: "high",
|
|
6532
|
+
type: "Hardcoded Credential",
|
|
6533
|
+
description: "Hardcoded credential or secret detected",
|
|
6534
|
+
recommendation: "Store credentials in environment variables or a secrets manager",
|
|
6535
|
+
cwe: "CWE-798"
|
|
6536
|
+
},
|
|
6537
|
+
// Unsafe Deserialization
|
|
6538
|
+
{
|
|
6539
|
+
regex: /new\s+(?:java\.io\.)?ObjectInputStream/,
|
|
6540
|
+
severity: "high",
|
|
6541
|
+
type: "Unsafe Deserialization",
|
|
6542
|
+
description: "Unsafe Java deserialization can lead to RCE",
|
|
6543
|
+
recommendation: "Use safer serialization formats (JSON) or whitelist classes",
|
|
6544
|
+
cwe: "CWE-502"
|
|
6545
|
+
},
|
|
6546
|
+
// Path Traversal
|
|
6547
|
+
{
|
|
6548
|
+
regex: /new\s+(?:java\.io\.)?File\s*\(\s*(?:request\.|user|input)/,
|
|
6549
|
+
severity: "high",
|
|
6550
|
+
type: "Path Traversal",
|
|
6551
|
+
description: "File path constructed from user input",
|
|
6552
|
+
recommendation: "Sanitize and validate file paths; use Paths.get() with canonical path check",
|
|
6553
|
+
cwe: "CWE-22"
|
|
6554
|
+
},
|
|
6555
|
+
// Command Injection
|
|
6556
|
+
{
|
|
6557
|
+
regex: /Runtime\.getRuntime\(\)\.exec\s*\(\s*[^"]/,
|
|
6558
|
+
severity: "critical",
|
|
6559
|
+
type: "Command Injection",
|
|
6560
|
+
description: "Dynamic command execution \u2014 vulnerable to injection",
|
|
6561
|
+
recommendation: "Use ProcessBuilder with a fixed command array",
|
|
6562
|
+
cwe: "CWE-78"
|
|
6563
|
+
},
|
|
6564
|
+
// XXE
|
|
6565
|
+
{
|
|
6566
|
+
regex: /DocumentBuilderFactory\s*\.\s*newInstance\s*\(\s*\)/,
|
|
6567
|
+
severity: "high",
|
|
6568
|
+
type: "XML External Entity (XXE)",
|
|
6569
|
+
description: "XML parsing without disabling external entities",
|
|
6570
|
+
recommendation: "Disable external entities: factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)",
|
|
6571
|
+
cwe: "CWE-611"
|
|
6572
|
+
},
|
|
6573
|
+
// Insecure Random
|
|
6574
|
+
{
|
|
6575
|
+
regex: /new\s+(?:java\.util\.)?Random\s*\(\s*\)(?!\s*\/\/.*secure)/,
|
|
6576
|
+
severity: "medium",
|
|
6577
|
+
type: "Insecure Random",
|
|
6578
|
+
description: "java.util.Random is not cryptographically secure",
|
|
6579
|
+
recommendation: "Use SecureRandom for security-sensitive operations",
|
|
6580
|
+
cwe: "CWE-338"
|
|
6581
|
+
}
|
|
6582
|
+
];
|
|
6583
|
+
var JavaSecurityAnalyzer = class {
|
|
6584
|
+
constructor(projectPath) {
|
|
6585
|
+
this.projectPath = projectPath;
|
|
6586
|
+
}
|
|
6587
|
+
async analyze(files) {
|
|
6588
|
+
const javaFiles = files ?? await findJavaFiles(this.projectPath, { includeTests: false });
|
|
6589
|
+
if (!javaFiles.length) {
|
|
6590
|
+
return { score: 100, vulnerabilities: [] };
|
|
6591
|
+
}
|
|
6592
|
+
const fileContents = await Promise.all(
|
|
6593
|
+
javaFiles.map(async (f) => ({
|
|
6594
|
+
path: f,
|
|
6595
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6596
|
+
}))
|
|
6597
|
+
);
|
|
6598
|
+
return this.analyzeContent(fileContents);
|
|
6599
|
+
}
|
|
6600
|
+
analyzeContent(files) {
|
|
6601
|
+
if (!files.length) return { score: 100, vulnerabilities: [] };
|
|
6602
|
+
const vulnerabilities = [];
|
|
6603
|
+
for (const { path: filePath, content } of files) {
|
|
6604
|
+
const lines = content.split("\n");
|
|
6605
|
+
for (const pattern of JAVA_SECURITY_PATTERNS) {
|
|
6606
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6607
|
+
const line = lines[i] ?? "";
|
|
6608
|
+
if (pattern.regex.test(line)) {
|
|
6609
|
+
vulnerabilities.push({
|
|
6610
|
+
severity: pattern.severity,
|
|
6611
|
+
type: pattern.type,
|
|
6612
|
+
file: filePath,
|
|
6613
|
+
line: i + 1,
|
|
6614
|
+
description: pattern.description,
|
|
6615
|
+
recommendation: pattern.recommendation,
|
|
6616
|
+
cwe: pattern.cwe
|
|
6617
|
+
});
|
|
6618
|
+
}
|
|
6619
|
+
}
|
|
6620
|
+
}
|
|
6621
|
+
}
|
|
6622
|
+
const score = this.calculateScore(vulnerabilities);
|
|
6623
|
+
return { score, vulnerabilities };
|
|
6624
|
+
}
|
|
6625
|
+
calculateScore(vulns) {
|
|
6626
|
+
let score = 100;
|
|
6627
|
+
for (const v of vulns) {
|
|
6628
|
+
switch (v.severity) {
|
|
6629
|
+
case "critical":
|
|
6630
|
+
score -= 30;
|
|
6631
|
+
break;
|
|
6632
|
+
case "high":
|
|
6633
|
+
score -= 15;
|
|
6634
|
+
break;
|
|
6635
|
+
case "medium":
|
|
6636
|
+
score -= 7;
|
|
6637
|
+
break;
|
|
6638
|
+
case "low":
|
|
6639
|
+
score -= 3;
|
|
6640
|
+
break;
|
|
6641
|
+
}
|
|
6642
|
+
}
|
|
6643
|
+
return Math.max(0, score);
|
|
6644
|
+
}
|
|
6645
|
+
};
|
|
6646
|
+
var MAX_LINE_LENGTH = 120;
|
|
6647
|
+
var JavaStyleAnalyzer = class {
|
|
6648
|
+
constructor(projectPath) {
|
|
6649
|
+
this.projectPath = projectPath;
|
|
6650
|
+
}
|
|
6651
|
+
async analyze(files) {
|
|
6652
|
+
const javaFiles = files ?? await findJavaFiles(this.projectPath);
|
|
6653
|
+
if (!javaFiles.length) return { score: 100, violations: [] };
|
|
6654
|
+
const fileContents = await Promise.all(
|
|
6655
|
+
javaFiles.map(async (f) => ({
|
|
6656
|
+
path: f,
|
|
6657
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6658
|
+
}))
|
|
6659
|
+
);
|
|
6660
|
+
return this.analyzeContent(fileContents);
|
|
6661
|
+
}
|
|
6662
|
+
analyzeContent(files) {
|
|
6663
|
+
if (!files.length) return { score: 100, violations: [] };
|
|
6664
|
+
const violations = [];
|
|
6665
|
+
for (const { path: filePath, content } of files) {
|
|
6666
|
+
violations.push(...this.checkFile(filePath, content));
|
|
6667
|
+
}
|
|
6668
|
+
const deduction = violations.reduce(
|
|
6669
|
+
(sum, v) => sum + (v.severity === "error" ? 10 : 5),
|
|
6670
|
+
0
|
|
6671
|
+
);
|
|
6672
|
+
const score = Math.max(0, 100 - deduction);
|
|
6673
|
+
return { score, violations };
|
|
6674
|
+
}
|
|
6675
|
+
checkFile(filePath, content) {
|
|
6676
|
+
const violations = [];
|
|
6677
|
+
const lines = content.split("\n");
|
|
6678
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6679
|
+
const line = lines[i] ?? "";
|
|
6680
|
+
const lineNum = i + 1;
|
|
6681
|
+
if (line.length > MAX_LINE_LENGTH) {
|
|
6682
|
+
violations.push({
|
|
6683
|
+
rule: "LineLength",
|
|
6684
|
+
file: filePath,
|
|
6685
|
+
line: lineNum,
|
|
6686
|
+
message: `Line length ${line.length} exceeds ${MAX_LINE_LENGTH} characters`,
|
|
6687
|
+
severity: "warning"
|
|
6688
|
+
});
|
|
6689
|
+
}
|
|
6690
|
+
const classMatch = /^(?:public|private|protected)?\s*(?:class|interface|enum|record)\s+([a-z]\w*)/.exec(line);
|
|
6691
|
+
if (classMatch) {
|
|
6692
|
+
violations.push({
|
|
6693
|
+
rule: "TypeName",
|
|
6694
|
+
file: filePath,
|
|
6695
|
+
line: lineNum,
|
|
6696
|
+
message: `Type name '${classMatch[1]}' should start with uppercase letter (PascalCase)`,
|
|
6697
|
+
severity: "error"
|
|
6698
|
+
});
|
|
6699
|
+
}
|
|
6700
|
+
const methodMatch = /\b(?:public|private|protected|static)\s+(?!class|interface|enum|record|new\b)(?:void|[\w<>[\]]+)\s+([A-Z]\w*)\s*\(/.exec(line);
|
|
6701
|
+
if (methodMatch && !line.trim().startsWith("class") && !line.includes("class ")) {
|
|
6702
|
+
violations.push({
|
|
6703
|
+
rule: "MethodName",
|
|
6704
|
+
file: filePath,
|
|
6705
|
+
line: lineNum,
|
|
6706
|
+
message: `Method name '${methodMatch[1]}' should start with lowercase letter (camelCase)`,
|
|
6707
|
+
severity: "error"
|
|
6708
|
+
});
|
|
6709
|
+
}
|
|
6710
|
+
const constantMatch = /\bpublic\s+static\s+final\s+\w+\s+([a-z][a-zA-Z]+)\s*=/.exec(line);
|
|
6711
|
+
if (constantMatch) {
|
|
6712
|
+
violations.push({
|
|
6713
|
+
rule: "ConstantName",
|
|
6714
|
+
file: filePath,
|
|
6715
|
+
line: lineNum,
|
|
6716
|
+
message: `Constant '${constantMatch[1]}' should be UPPER_SNAKE_CASE`,
|
|
6717
|
+
severity: "warning"
|
|
6718
|
+
});
|
|
6719
|
+
}
|
|
6720
|
+
const paramsMatch = /\w+\s+\w+\s*\(([^)]+)\)/.exec(line);
|
|
6721
|
+
if (paramsMatch) {
|
|
6722
|
+
const paramCount = (paramsMatch[1] ?? "").split(",").length;
|
|
6723
|
+
if (paramCount > 5) {
|
|
6724
|
+
violations.push({
|
|
6725
|
+
rule: "ParameterNumber",
|
|
6726
|
+
file: filePath,
|
|
6727
|
+
line: lineNum,
|
|
6728
|
+
message: `Method has ${paramCount} parameters (max recommended: 5)`,
|
|
6729
|
+
severity: "warning"
|
|
6730
|
+
});
|
|
6731
|
+
}
|
|
6732
|
+
}
|
|
6733
|
+
if (/\)\{/.test(line) || /\belse\{/.test(line)) {
|
|
6734
|
+
violations.push({
|
|
6735
|
+
rule: "WhitespaceAround",
|
|
6736
|
+
file: filePath,
|
|
6737
|
+
line: lineNum,
|
|
6738
|
+
message: "Missing space before '{'",
|
|
6739
|
+
severity: "warning"
|
|
6740
|
+
});
|
|
6741
|
+
}
|
|
6742
|
+
}
|
|
6743
|
+
return violations;
|
|
6744
|
+
}
|
|
6745
|
+
};
|
|
6746
|
+
var JavaDocumentationAnalyzer = class {
|
|
6747
|
+
constructor(projectPath) {
|
|
6748
|
+
this.projectPath = projectPath;
|
|
6749
|
+
}
|
|
6750
|
+
async analyze(files) {
|
|
6751
|
+
const javaFiles = files ?? await findJavaFiles(this.projectPath, { srcPattern: "src/main/**/*.java" });
|
|
6752
|
+
if (!javaFiles.length) {
|
|
6753
|
+
return { score: 100, javadocCoverage: 1, totalMethods: 0, documentedMethods: 0, undocumentedPublicMethods: [] };
|
|
6754
|
+
}
|
|
6755
|
+
const fileContents = await Promise.all(
|
|
6756
|
+
javaFiles.map(async (f) => ({
|
|
6757
|
+
path: f,
|
|
6758
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6759
|
+
}))
|
|
6760
|
+
);
|
|
6761
|
+
return this.analyzeContent(fileContents);
|
|
6762
|
+
}
|
|
6763
|
+
analyzeContent(files) {
|
|
6764
|
+
if (!files.length) {
|
|
6765
|
+
return { score: 100, javadocCoverage: 1, totalMethods: 0, documentedMethods: 0, undocumentedPublicMethods: [] };
|
|
6766
|
+
}
|
|
6767
|
+
let totalMethods = 0;
|
|
6768
|
+
let documentedMethods = 0;
|
|
6769
|
+
const undocumentedPublicMethods = [];
|
|
6770
|
+
for (const { content } of files) {
|
|
6771
|
+
const lines = content.split("\n");
|
|
6772
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6773
|
+
const line = lines[i] ?? "";
|
|
6774
|
+
if (/\b(?:public)\s+(?!class|interface|enum|record)[\w<>[\]]+\s+\w+\s*\(/.test(line)) {
|
|
6775
|
+
totalMethods++;
|
|
6776
|
+
const isDocumented = this.hasPrecedingJavadoc(lines, i);
|
|
6777
|
+
if (isDocumented) {
|
|
6778
|
+
documentedMethods++;
|
|
6779
|
+
} else {
|
|
6780
|
+
const nameMatch = /\s(\w+)\s*\(/.exec(line);
|
|
6781
|
+
if (nameMatch) undocumentedPublicMethods.push(nameMatch[1] ?? "unknown");
|
|
6782
|
+
}
|
|
6783
|
+
}
|
|
6784
|
+
}
|
|
6785
|
+
}
|
|
6786
|
+
const javadocCoverage = totalMethods > 0 ? documentedMethods / totalMethods : 1;
|
|
6787
|
+
const score = Math.round(javadocCoverage * 100);
|
|
6788
|
+
return { score, javadocCoverage, totalMethods, documentedMethods, undocumentedPublicMethods };
|
|
6789
|
+
}
|
|
6790
|
+
hasPrecedingJavadoc(lines, methodLineIndex) {
|
|
6791
|
+
for (let i = methodLineIndex - 1; i >= 0; i--) {
|
|
6792
|
+
const prevLine = (lines[i] ?? "").trim();
|
|
6793
|
+
if (prevLine === "") continue;
|
|
6794
|
+
if (prevLine.endsWith("*/")) {
|
|
6795
|
+
for (let j = i; j >= 0; j--) {
|
|
6796
|
+
const docLine = (lines[j] ?? "").trim();
|
|
6797
|
+
if (docLine.startsWith("/**")) return true;
|
|
6798
|
+
if (!docLine.startsWith("*") && !docLine.startsWith("/**")) break;
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
break;
|
|
6802
|
+
}
|
|
6803
|
+
return false;
|
|
6804
|
+
}
|
|
6805
|
+
};
|
|
6806
|
+
var JACOCO_REPORT_PATHS = [
|
|
6807
|
+
"target/site/jacoco/jacoco.xml",
|
|
6808
|
+
"build/reports/jacoco/test/jacocoTestReport.xml",
|
|
6809
|
+
"build/reports/jacoco/jacocoTestReport.xml"
|
|
6810
|
+
];
|
|
6811
|
+
var JavaCoverageAnalyzer = class {
|
|
6812
|
+
constructor(projectPath) {
|
|
6813
|
+
this.projectPath = projectPath;
|
|
6814
|
+
}
|
|
6815
|
+
async analyze() {
|
|
6816
|
+
for (const reportPath of JACOCO_REPORT_PATHS) {
|
|
6817
|
+
try {
|
|
6818
|
+
const xml2 = await readFile(join(this.projectPath, reportPath), "utf-8");
|
|
6819
|
+
const result = this.parseJacocoXml(xml2);
|
|
6820
|
+
return { ...result, reportFound: true };
|
|
6821
|
+
} catch {
|
|
6822
|
+
}
|
|
6823
|
+
}
|
|
6824
|
+
return { score: 50, lineCoverage: 0, branchCoverage: 0, methodCoverage: 0, reportFound: false };
|
|
6825
|
+
}
|
|
6826
|
+
parseJacocoXml(xml2) {
|
|
6827
|
+
if (!xml2.trim()) {
|
|
6828
|
+
return { score: 0, lineCoverage: 0, branchCoverage: 0, methodCoverage: 0, reportFound: false };
|
|
6829
|
+
}
|
|
6830
|
+
const lineCoverage = this.extractCoverage(xml2, "LINE");
|
|
6831
|
+
const branchCoverage = this.extractCoverage(xml2, "BRANCH");
|
|
6832
|
+
const methodCoverage = this.extractCoverage(xml2, "METHOD");
|
|
6833
|
+
const score = Math.round(lineCoverage * 0.5 + branchCoverage * 0.35 + methodCoverage * 0.15);
|
|
6834
|
+
return { score, lineCoverage, branchCoverage, methodCoverage, reportFound: true };
|
|
6835
|
+
}
|
|
6836
|
+
extractCoverage(xml2, type) {
|
|
6837
|
+
const regex = new RegExp(
|
|
6838
|
+
`<counter\\s+type="${type}"\\s+missed="(\\d+)"\\s+covered="(\\d+)"`
|
|
6839
|
+
);
|
|
6840
|
+
const match = regex.exec(xml2);
|
|
6841
|
+
if (!match) return 0;
|
|
6842
|
+
const missed = parseInt(match[1] ?? "0", 10);
|
|
6843
|
+
const covered = parseInt(match[2] ?? "0", 10);
|
|
6844
|
+
const total = missed + covered;
|
|
6845
|
+
return total > 0 ? Math.round(covered / total * 100) : 0;
|
|
6846
|
+
}
|
|
6847
|
+
};
|
|
6848
|
+
function registerJavaAnalyzers(registry, projectPath) {
|
|
6849
|
+
const complexityAnalyzer = new JavaComplexityAnalyzer(projectPath);
|
|
6850
|
+
const securityAnalyzer = new JavaSecurityAnalyzer(projectPath);
|
|
6851
|
+
const styleAnalyzer = new JavaStyleAnalyzer(projectPath);
|
|
6852
|
+
const documentationAnalyzer = new JavaDocumentationAnalyzer(projectPath);
|
|
6853
|
+
const coverageAnalyzer = new JavaCoverageAnalyzer(projectPath);
|
|
6854
|
+
registry.register({
|
|
6855
|
+
dimensionId: "complexity",
|
|
6856
|
+
language: "java",
|
|
6857
|
+
async analyze(input) {
|
|
6858
|
+
const result = await complexityAnalyzer.analyze(input.files);
|
|
6859
|
+
return { score: result.score, issues: [] };
|
|
6860
|
+
}
|
|
6861
|
+
});
|
|
6862
|
+
registry.register({
|
|
6863
|
+
dimensionId: "security",
|
|
6864
|
+
language: "java",
|
|
6865
|
+
async analyze(input) {
|
|
6866
|
+
const contents = await Promise.all(
|
|
6867
|
+
input.files.map(async (f) => ({
|
|
6868
|
+
path: f,
|
|
6869
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6870
|
+
}))
|
|
6871
|
+
);
|
|
6872
|
+
const result = securityAnalyzer.analyzeContent(contents);
|
|
6873
|
+
return {
|
|
6874
|
+
score: result.score,
|
|
6875
|
+
issues: result.vulnerabilities.map((v) => ({
|
|
6876
|
+
dimension: "security",
|
|
6877
|
+
severity: v.severity === "critical" ? "critical" : "major",
|
|
6878
|
+
message: `[${v.type}] ${v.description}`,
|
|
6879
|
+
file: v.file,
|
|
6880
|
+
line: v.line,
|
|
6881
|
+
suggestion: v.recommendation
|
|
6882
|
+
}))
|
|
6883
|
+
};
|
|
6884
|
+
}
|
|
6885
|
+
});
|
|
6886
|
+
registry.register({
|
|
6887
|
+
dimensionId: "style",
|
|
6888
|
+
language: "java",
|
|
6889
|
+
async analyze(input) {
|
|
6890
|
+
const contents = await Promise.all(
|
|
6891
|
+
input.files.map(async (f) => ({
|
|
6892
|
+
path: f,
|
|
6893
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6894
|
+
}))
|
|
6895
|
+
);
|
|
6896
|
+
const result = styleAnalyzer.analyzeContent(contents);
|
|
6897
|
+
return { score: result.score, issues: [] };
|
|
6898
|
+
}
|
|
6899
|
+
});
|
|
6900
|
+
registry.register({
|
|
6901
|
+
dimensionId: "documentation",
|
|
6902
|
+
language: "java",
|
|
6903
|
+
async analyze(input) {
|
|
6904
|
+
const contents = await Promise.all(
|
|
6905
|
+
input.files.map(async (f) => ({
|
|
6906
|
+
path: f,
|
|
6907
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6908
|
+
}))
|
|
6909
|
+
);
|
|
6910
|
+
const result = documentationAnalyzer.analyzeContent(contents);
|
|
6911
|
+
return { score: result.score, issues: [] };
|
|
6912
|
+
}
|
|
6913
|
+
});
|
|
6914
|
+
registry.register({
|
|
6915
|
+
dimensionId: "testCoverage",
|
|
6916
|
+
language: "java",
|
|
6917
|
+
async analyze() {
|
|
6918
|
+
const result = await coverageAnalyzer.analyze();
|
|
6919
|
+
return { score: result.score, issues: [] };
|
|
6920
|
+
}
|
|
6921
|
+
});
|
|
6922
|
+
}
|
|
6923
|
+
async function loadFiles(files) {
|
|
6924
|
+
return Promise.all(
|
|
6925
|
+
files.map(async (f) => ({
|
|
6926
|
+
path: f,
|
|
6927
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6928
|
+
}))
|
|
6929
|
+
);
|
|
6930
|
+
}
|
|
6931
|
+
async function findReactFiles(projectPath, pattern = "**/*.{tsx,jsx}") {
|
|
6932
|
+
return glob(pattern, {
|
|
6933
|
+
cwd: projectPath,
|
|
6934
|
+
absolute: true,
|
|
6935
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*"]
|
|
6936
|
+
});
|
|
6937
|
+
}
|
|
6938
|
+
var ReactComponentAnalyzer = class {
|
|
6939
|
+
constructor(projectPath) {
|
|
6940
|
+
this.projectPath = projectPath;
|
|
6941
|
+
}
|
|
6942
|
+
async analyze(files) {
|
|
6943
|
+
const reactFiles = files ?? await findReactFiles(this.projectPath);
|
|
6944
|
+
if (!reactFiles.length) return { score: 100, totalComponents: 0, issues: [] };
|
|
6945
|
+
const fileContents = await loadFiles(reactFiles);
|
|
6946
|
+
return this.analyzeContent(fileContents);
|
|
6947
|
+
}
|
|
6948
|
+
analyzeContent(files) {
|
|
6949
|
+
if (!files.length) return { score: 100, totalComponents: 0, issues: [] };
|
|
6950
|
+
const issues = [];
|
|
6951
|
+
let totalComponents = 0;
|
|
6952
|
+
for (const { path: filePath, content } of files) {
|
|
6953
|
+
const lines = content.split("\n");
|
|
6954
|
+
let inComponent = false;
|
|
6955
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6956
|
+
const line = lines[i] ?? "";
|
|
6957
|
+
const lineNum = i + 1;
|
|
6958
|
+
if (/\bfunction\s+[A-Z]\w*\s*\(|const\s+[A-Z]\w*\s*=\s*(?:\([^)]*\)|[a-z]\w*)\s*=>/.test(line)) {
|
|
6959
|
+
totalComponents++;
|
|
6960
|
+
inComponent = true;
|
|
6961
|
+
}
|
|
6962
|
+
if (/\.map\s*\(/.test(line)) {
|
|
6963
|
+
const block = lines.slice(i, i + 4).join("\n");
|
|
6964
|
+
if (/<[a-zA-Z]/.test(block) && !/key\s*=/.test(block)) {
|
|
6965
|
+
issues.push({
|
|
6966
|
+
rule: "react/missing-key",
|
|
6967
|
+
file: filePath,
|
|
6968
|
+
line: lineNum,
|
|
6969
|
+
message: "Missing 'key' prop in list rendering \u2014 elements need unique keys",
|
|
6970
|
+
severity: "error"
|
|
6971
|
+
});
|
|
6972
|
+
}
|
|
6973
|
+
}
|
|
6974
|
+
const lowerFnMatch = /\bfunction\s+([a-z]\w*)\s*\([^)]*\)\s*\{/.exec(line);
|
|
6975
|
+
if (lowerFnMatch && inComponent) {
|
|
6976
|
+
const nextContent = lines.slice(i, i + 20).join("\n");
|
|
6977
|
+
if (/<[A-Z]|return\s*\(/.test(nextContent)) {
|
|
6978
|
+
issues.push({
|
|
6979
|
+
rule: "react/component-naming",
|
|
6980
|
+
file: filePath,
|
|
6981
|
+
line: lineNum,
|
|
6982
|
+
message: `Component '${lowerFnMatch[1]}' should use PascalCase naming`,
|
|
6983
|
+
severity: "warning"
|
|
6984
|
+
});
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6987
|
+
if (/\bfunction\s+[A-Z]\w*\s*\(\s*props\s*\)/.test(line)) {
|
|
6988
|
+
issues.push({
|
|
6989
|
+
rule: "react/prop-types",
|
|
6990
|
+
file: filePath,
|
|
6991
|
+
line: lineNum,
|
|
6992
|
+
message: "Component props are not typed \u2014 use a TypeScript interface or destructure with types",
|
|
6993
|
+
severity: "error"
|
|
6994
|
+
});
|
|
6995
|
+
}
|
|
6996
|
+
if (/^\s*export\s+(?:default\s+)?function\s+[A-Z]\w*/.test(line)) {
|
|
6997
|
+
let prevLineIdx = i - 1;
|
|
6998
|
+
while (prevLineIdx >= 0 && (lines[prevLineIdx] ?? "").trim() === "") {
|
|
6999
|
+
prevLineIdx--;
|
|
7000
|
+
}
|
|
7001
|
+
const prevLine = (lines[prevLineIdx] ?? "").trim();
|
|
7002
|
+
if (!prevLine.endsWith("*/")) {
|
|
7003
|
+
issues.push({
|
|
7004
|
+
rule: "react/missing-jsdoc",
|
|
7005
|
+
file: filePath,
|
|
7006
|
+
line: lineNum,
|
|
7007
|
+
message: "Exported component missing JSDoc comment",
|
|
7008
|
+
severity: "warning"
|
|
7009
|
+
});
|
|
7010
|
+
}
|
|
7011
|
+
}
|
|
7012
|
+
if (/document\.getElementById|document\.querySelector|document\.createElement/.test(line)) {
|
|
7013
|
+
issues.push({
|
|
7014
|
+
rule: "react/no-direct-dom-manipulation",
|
|
7015
|
+
file: filePath,
|
|
7016
|
+
line: lineNum,
|
|
7017
|
+
message: "Avoid direct DOM manipulation in React \u2014 use refs or state instead",
|
|
7018
|
+
severity: "warning"
|
|
7019
|
+
});
|
|
7020
|
+
}
|
|
7021
|
+
if (/dangerouslySetInnerHTML/.test(line) && !/sanitize|DOMPurify|xss/.test(line)) {
|
|
7022
|
+
issues.push({
|
|
7023
|
+
rule: "react/no-danger",
|
|
7024
|
+
file: filePath,
|
|
7025
|
+
line: lineNum,
|
|
7026
|
+
message: "dangerouslySetInnerHTML can lead to XSS \u2014 ensure content is sanitized",
|
|
7027
|
+
severity: "error"
|
|
7028
|
+
});
|
|
7029
|
+
}
|
|
7030
|
+
}
|
|
7031
|
+
}
|
|
7032
|
+
const deduction = issues.reduce((s, i) => s + (i.severity === "error" ? 10 : 5), 0);
|
|
7033
|
+
const score = Math.max(0, 100 - deduction);
|
|
7034
|
+
return { score, totalComponents, issues };
|
|
7035
|
+
}
|
|
7036
|
+
};
|
|
7037
|
+
var ReactA11yAnalyzer = class {
|
|
7038
|
+
constructor(projectPath) {
|
|
7039
|
+
this.projectPath = projectPath;
|
|
7040
|
+
}
|
|
7041
|
+
async analyze(files) {
|
|
7042
|
+
const reactFiles = files ?? await findReactFiles(this.projectPath);
|
|
7043
|
+
if (!reactFiles.length) return { score: 100, violations: [] };
|
|
7044
|
+
const fileContents = await loadFiles(reactFiles);
|
|
7045
|
+
return this.analyzeContent(fileContents);
|
|
7046
|
+
}
|
|
7047
|
+
analyzeContent(files) {
|
|
7048
|
+
if (!files.length) return { score: 100, violations: [] };
|
|
7049
|
+
const violations = [];
|
|
7050
|
+
for (const { path: filePath, content } of files) {
|
|
7051
|
+
violations.push(...this.checkFile(filePath, content));
|
|
7052
|
+
}
|
|
7053
|
+
const deduction = violations.reduce((s, v) => s + (v.severity === "error" ? 12 : 6), 0);
|
|
7054
|
+
const score = Math.max(0, 100 - deduction);
|
|
7055
|
+
return { score, violations };
|
|
7056
|
+
}
|
|
7057
|
+
checkFile(filePath, content) {
|
|
7058
|
+
const violations = [];
|
|
7059
|
+
const lines = content.split("\n");
|
|
7060
|
+
for (let i = 0; i < lines.length; i++) {
|
|
7061
|
+
const line = lines[i] ?? "";
|
|
7062
|
+
const lineNum = i + 1;
|
|
7063
|
+
if (/<img\b/.test(line)) {
|
|
7064
|
+
const imgBlock = lines.slice(i, Math.min(i + 4, lines.length)).join("\n");
|
|
7065
|
+
if (!/alt\s*=/.test(imgBlock)) {
|
|
7066
|
+
violations.push({
|
|
7067
|
+
rule: "jsx-a11y/alt-text",
|
|
7068
|
+
file: filePath,
|
|
7069
|
+
line: lineNum,
|
|
7070
|
+
message: "<img> element missing 'alt' attribute",
|
|
7071
|
+
severity: "error",
|
|
7072
|
+
wcag: "WCAG 1.1.1"
|
|
7073
|
+
});
|
|
7074
|
+
}
|
|
7075
|
+
}
|
|
7076
|
+
if (/<a\b/.test(line)) {
|
|
7077
|
+
const aBlock = lines.slice(i, Math.min(i + 4, lines.length)).join("\n");
|
|
7078
|
+
if (!/href\s*=/.test(aBlock) && !/ role\s*=/.test(aBlock)) {
|
|
7079
|
+
violations.push({
|
|
7080
|
+
rule: "jsx-a11y/anchor-has-content",
|
|
7081
|
+
file: filePath,
|
|
7082
|
+
line: lineNum,
|
|
7083
|
+
message: "<a> element missing href \u2014 use a <button> for non-navigation actions",
|
|
7084
|
+
severity: "error",
|
|
7085
|
+
wcag: "WCAG 2.1.1"
|
|
7086
|
+
});
|
|
7087
|
+
}
|
|
7088
|
+
}
|
|
7089
|
+
if (/<(?:div|span)\b[^>]*onClick[^>]*>/.test(line)) {
|
|
7090
|
+
const hasKeyboardSupport = /onKey(?:Down|Up|Press)|role\s*=\s*["'](?:button|link|menuitem)|tabIndex/.test(line);
|
|
7091
|
+
if (!hasKeyboardSupport) {
|
|
7092
|
+
const context = lines.slice(Math.max(0, i - 1), i + 3).join(" ");
|
|
7093
|
+
if (!/onKey(?:Down|Up|Press)|tabIndex/.test(context)) {
|
|
7094
|
+
violations.push({
|
|
7095
|
+
rule: "jsx-a11y/interactive-supports-focus",
|
|
7096
|
+
file: filePath,
|
|
7097
|
+
line: lineNum,
|
|
7098
|
+
message: "Non-interactive element with onClick handler \u2014 add keyboard support (onKeyDown, tabIndex, role)",
|
|
7099
|
+
severity: "warning",
|
|
7100
|
+
wcag: "WCAG 2.1.1"
|
|
7101
|
+
});
|
|
7102
|
+
}
|
|
7103
|
+
}
|
|
7104
|
+
}
|
|
7105
|
+
if (/<input\b[^>]*>/.test(line) && !/aria-label|aria-labelledby|id\s*=/.test(line)) {
|
|
7106
|
+
violations.push({
|
|
7107
|
+
rule: "jsx-a11y/label-association",
|
|
7108
|
+
file: filePath,
|
|
7109
|
+
line: lineNum,
|
|
7110
|
+
message: "<input> missing label association (aria-label, aria-labelledby, or id for <label>)",
|
|
7111
|
+
severity: "warning",
|
|
7112
|
+
wcag: "WCAG 1.3.1"
|
|
7113
|
+
});
|
|
7114
|
+
}
|
|
7115
|
+
if (/<video\b[^>]*autoPlay[^>]*>/.test(line) && !/controls/.test(line)) {
|
|
7116
|
+
violations.push({
|
|
7117
|
+
rule: "jsx-a11y/media-has-caption",
|
|
7118
|
+
file: filePath,
|
|
7119
|
+
line: lineNum,
|
|
7120
|
+
message: "Autoplaying video without controls \u2014 add controls attribute",
|
|
7121
|
+
severity: "warning",
|
|
7122
|
+
wcag: "WCAG 1.2.2"
|
|
7123
|
+
});
|
|
7124
|
+
}
|
|
7125
|
+
}
|
|
7126
|
+
return violations;
|
|
7127
|
+
}
|
|
7128
|
+
};
|
|
7129
|
+
var ReactHookAnalyzer = class {
|
|
7130
|
+
constructor(projectPath) {
|
|
7131
|
+
this.projectPath = projectPath;
|
|
7132
|
+
}
|
|
7133
|
+
async analyze(files) {
|
|
7134
|
+
const reactFiles = files ?? await findReactFiles(this.projectPath, "**/*.{tsx,jsx,ts,js}");
|
|
7135
|
+
if (!reactFiles.length) return { score: 100, violations: [] };
|
|
7136
|
+
const fileContents = await loadFiles(reactFiles);
|
|
7137
|
+
return this.analyzeContent(fileContents);
|
|
7138
|
+
}
|
|
7139
|
+
analyzeContent(files) {
|
|
7140
|
+
if (!files.length) return { score: 100, violations: [] };
|
|
7141
|
+
const violations = [];
|
|
7142
|
+
for (const { path: filePath, content } of files) {
|
|
7143
|
+
violations.push(...this.checkFile(filePath, content));
|
|
7144
|
+
}
|
|
7145
|
+
const deduction = violations.reduce((s, v) => s + (v.severity === "error" ? 15 : 7), 0);
|
|
7146
|
+
const score = Math.max(0, 100 - deduction);
|
|
7147
|
+
return { score, violations };
|
|
7148
|
+
}
|
|
7149
|
+
checkFile(filePath, content) {
|
|
7150
|
+
const violations = [];
|
|
7151
|
+
const lines = content.split("\n");
|
|
7152
|
+
let conditionalDepth = 0;
|
|
7153
|
+
let loopDepth = 0;
|
|
7154
|
+
for (let i = 0; i < lines.length; i++) {
|
|
7155
|
+
const line = lines[i] ?? "";
|
|
7156
|
+
const lineNum = i + 1;
|
|
7157
|
+
const trimmed = line.trim();
|
|
7158
|
+
const openBraces = (trimmed.match(/\{/g) ?? []).length;
|
|
7159
|
+
const closeBraces = (trimmed.match(/\}/g) ?? []).length;
|
|
7160
|
+
if (/^\s*(?:if\s*\(|else\s+if\s*\(|else\s*\{)/.test(line) && openBraces > closeBraces) {
|
|
7161
|
+
conditionalDepth++;
|
|
7162
|
+
}
|
|
7163
|
+
if (/^\s*(?:for\s*\(|while\s*\()/.test(line) && openBraces > closeBraces) {
|
|
7164
|
+
loopDepth++;
|
|
7165
|
+
}
|
|
7166
|
+
if (/\.forEach\s*\(/.test(line) && openBraces > closeBraces) {
|
|
7167
|
+
loopDepth++;
|
|
7168
|
+
}
|
|
7169
|
+
if (/\buseEffect\s*\(/.test(line)) {
|
|
7170
|
+
const block = lines.slice(i, i + 5).join("\n");
|
|
7171
|
+
if (!/,\s*\[/.test(block)) {
|
|
7172
|
+
violations.push({
|
|
7173
|
+
rule: "react-hooks/exhaustive-deps",
|
|
7174
|
+
file: filePath,
|
|
7175
|
+
line: lineNum,
|
|
7176
|
+
message: "useEffect without dependency array \u2014 runs on every render (possibly unintentional)",
|
|
7177
|
+
severity: "warning"
|
|
7178
|
+
});
|
|
7179
|
+
}
|
|
7180
|
+
}
|
|
7181
|
+
if (conditionalDepth > 0 && /\buse[A-Z]\w*\s*\(/.test(line)) {
|
|
7182
|
+
violations.push({
|
|
7183
|
+
rule: "react-hooks/rules-of-hooks",
|
|
7184
|
+
file: filePath,
|
|
7185
|
+
line: lineNum,
|
|
7186
|
+
message: "Hook called inside conditional \u2014 violates Rules of Hooks",
|
|
7187
|
+
severity: "error"
|
|
7188
|
+
});
|
|
7189
|
+
}
|
|
7190
|
+
if (loopDepth > 0 && /\buse[A-Z]\w*\s*\(/.test(line)) {
|
|
7191
|
+
violations.push({
|
|
7192
|
+
rule: "react-hooks/rules-of-hooks",
|
|
7193
|
+
file: filePath,
|
|
7194
|
+
line: lineNum,
|
|
7195
|
+
message: "Hook called inside loop \u2014 violates Rules of Hooks",
|
|
7196
|
+
severity: "error"
|
|
7197
|
+
});
|
|
7198
|
+
}
|
|
7199
|
+
const netBraces = openBraces - closeBraces;
|
|
7200
|
+
if (netBraces < 0) {
|
|
7201
|
+
if (conditionalDepth > 0) conditionalDepth = Math.max(0, conditionalDepth + netBraces);
|
|
7202
|
+
if (loopDepth > 0) loopDepth = Math.max(0, loopDepth + netBraces);
|
|
7203
|
+
}
|
|
7204
|
+
}
|
|
7205
|
+
return violations;
|
|
7206
|
+
}
|
|
7207
|
+
};
|
|
7208
|
+
function registerReactAnalyzers(registry, projectPath) {
|
|
7209
|
+
const componentAnalyzer = new ReactComponentAnalyzer(projectPath);
|
|
7210
|
+
const a11yAnalyzer = new ReactA11yAnalyzer(projectPath);
|
|
7211
|
+
const hookAnalyzer = new ReactHookAnalyzer(projectPath);
|
|
7212
|
+
for (const lang of ["react-typescript", "react-javascript"]) {
|
|
7213
|
+
registry.register({
|
|
7214
|
+
dimensionId: "style",
|
|
7215
|
+
language: lang,
|
|
7216
|
+
async analyze(input) {
|
|
7217
|
+
const contents = await Promise.all(
|
|
7218
|
+
input.files.map(async (f) => ({
|
|
7219
|
+
path: f,
|
|
7220
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
7221
|
+
}))
|
|
7222
|
+
);
|
|
7223
|
+
const result = componentAnalyzer.analyzeContent(contents);
|
|
7224
|
+
return {
|
|
7225
|
+
score: result.score,
|
|
7226
|
+
issues: result.issues.map((i) => ({
|
|
7227
|
+
dimension: "style",
|
|
7228
|
+
severity: i.severity === "error" ? "major" : "minor",
|
|
7229
|
+
message: i.message,
|
|
7230
|
+
file: i.file,
|
|
7231
|
+
line: i.line
|
|
7232
|
+
}))
|
|
7233
|
+
};
|
|
7234
|
+
}
|
|
7235
|
+
});
|
|
7236
|
+
registry.register({
|
|
7237
|
+
dimensionId: "robustness",
|
|
7238
|
+
language: lang,
|
|
7239
|
+
async analyze(input) {
|
|
7240
|
+
const contents = await Promise.all(
|
|
7241
|
+
input.files.map(async (f) => ({
|
|
7242
|
+
path: f,
|
|
7243
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
7244
|
+
}))
|
|
7245
|
+
);
|
|
7246
|
+
const result = a11yAnalyzer.analyzeContent(contents);
|
|
7247
|
+
return {
|
|
7248
|
+
score: result.score,
|
|
7249
|
+
issues: result.violations.map((v) => ({
|
|
7250
|
+
dimension: "robustness",
|
|
7251
|
+
severity: v.severity === "error" ? "major" : "minor",
|
|
7252
|
+
message: v.message,
|
|
7253
|
+
file: v.file,
|
|
7254
|
+
line: v.line
|
|
7255
|
+
}))
|
|
7256
|
+
};
|
|
7257
|
+
}
|
|
7258
|
+
});
|
|
7259
|
+
registry.register({
|
|
7260
|
+
dimensionId: "correctness",
|
|
7261
|
+
language: lang,
|
|
7262
|
+
async analyze(input) {
|
|
7263
|
+
const contents = await Promise.all(
|
|
7264
|
+
input.files.map(async (f) => ({
|
|
7265
|
+
path: f,
|
|
7266
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
7267
|
+
}))
|
|
7268
|
+
);
|
|
7269
|
+
const result = hookAnalyzer.analyzeContent(contents);
|
|
7270
|
+
return {
|
|
7271
|
+
score: result.score,
|
|
7272
|
+
issues: result.violations.map((v) => ({
|
|
7273
|
+
dimension: "correctness",
|
|
7274
|
+
severity: v.severity === "error" ? "critical" : "minor",
|
|
7275
|
+
message: v.message,
|
|
7276
|
+
file: v.file,
|
|
7277
|
+
line: v.line
|
|
7278
|
+
}))
|
|
7279
|
+
};
|
|
7280
|
+
}
|
|
7281
|
+
});
|
|
6010
7282
|
}
|
|
6011
|
-
|
|
6012
|
-
|
|
7283
|
+
}
|
|
7284
|
+
var DimensionRegistry = class {
|
|
7285
|
+
analyzers = [];
|
|
7286
|
+
/**
|
|
7287
|
+
* Register a dimension analyzer plugin.
|
|
7288
|
+
* If an analyzer for the same (language, dimensionId) pair already exists,
|
|
7289
|
+
* it is replaced — preventing order-dependent duplicate accumulation.
|
|
7290
|
+
*/
|
|
7291
|
+
register(analyzer) {
|
|
7292
|
+
const existing = this.analyzers.findIndex(
|
|
7293
|
+
(a) => a.language === analyzer.language && a.dimensionId === analyzer.dimensionId
|
|
7294
|
+
);
|
|
7295
|
+
if (existing !== -1) {
|
|
7296
|
+
this.analyzers[existing] = analyzer;
|
|
7297
|
+
} else {
|
|
7298
|
+
this.analyzers.push(analyzer);
|
|
7299
|
+
}
|
|
6013
7300
|
}
|
|
6014
|
-
|
|
6015
|
-
|
|
7301
|
+
/**
|
|
7302
|
+
* Get all analyzers applicable for a given language.
|
|
7303
|
+
* Includes both language-specific analyzers and "all" analyzers.
|
|
7304
|
+
*
|
|
7305
|
+
* @param language - Target language
|
|
7306
|
+
* @param dimensionId - Optional filter for a specific dimension
|
|
7307
|
+
*/
|
|
7308
|
+
getAnalyzers(language, dimensionId) {
|
|
7309
|
+
return this.analyzers.filter(
|
|
7310
|
+
(a) => (a.language === language || a.language === "all") && (dimensionId === void 0 || a.dimensionId === dimensionId)
|
|
7311
|
+
);
|
|
6016
7312
|
}
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
7313
|
+
/**
|
|
7314
|
+
* Check if any analyzers are registered for a given language
|
|
7315
|
+
* (includes "all" analyzers)
|
|
7316
|
+
*/
|
|
7317
|
+
hasAnalyzers(language) {
|
|
7318
|
+
return this.analyzers.some((a) => a.language === language || a.language === "all");
|
|
6020
7319
|
}
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
7320
|
+
/**
|
|
7321
|
+
* Get all languages that have registered analyzers
|
|
7322
|
+
* (excludes the "all" pseudo-language)
|
|
7323
|
+
*/
|
|
7324
|
+
getSupportedLanguages() {
|
|
7325
|
+
const languages = /* @__PURE__ */ new Set();
|
|
7326
|
+
for (const a of this.analyzers) {
|
|
7327
|
+
if (a.language !== "all") {
|
|
7328
|
+
languages.add(a.language);
|
|
7329
|
+
}
|
|
7330
|
+
}
|
|
7331
|
+
return Array.from(languages);
|
|
6029
7332
|
}
|
|
6030
7333
|
/**
|
|
6031
|
-
*
|
|
7334
|
+
* Run all matching analyzers for the given input and return combined results.
|
|
7335
|
+
* Analyzers run in parallel for performance.
|
|
6032
7336
|
*/
|
|
6033
|
-
async analyze(
|
|
6034
|
-
const
|
|
6035
|
-
if (
|
|
7337
|
+
async analyze(input) {
|
|
7338
|
+
const matching = this.getAnalyzers(input.language);
|
|
7339
|
+
if (!matching.length) return [];
|
|
7340
|
+
const settled = await Promise.allSettled(
|
|
7341
|
+
matching.map(
|
|
7342
|
+
(analyzer) => analyzer.analyze(input).then((result) => ({ dimensionId: analyzer.dimensionId, result }))
|
|
7343
|
+
)
|
|
7344
|
+
);
|
|
7345
|
+
return settled.filter((s) => {
|
|
7346
|
+
if (s.status === "rejected") {
|
|
7347
|
+
process.stderr.write(`[DimensionRegistry] Analyzer failed: ${String(s.reason)}
|
|
7348
|
+
`);
|
|
7349
|
+
}
|
|
7350
|
+
return s.status === "fulfilled";
|
|
7351
|
+
}).map((s) => s.value);
|
|
7352
|
+
}
|
|
7353
|
+
};
|
|
7354
|
+
function wrapAnalyzer(dimensionId, language, analyzerFn) {
|
|
7355
|
+
return {
|
|
7356
|
+
dimensionId,
|
|
7357
|
+
language,
|
|
7358
|
+
async analyze(input) {
|
|
7359
|
+
const raw = await analyzerFn(input);
|
|
6036
7360
|
return {
|
|
6037
|
-
score:
|
|
6038
|
-
|
|
6039
|
-
functionCountScore: 100,
|
|
6040
|
-
dependencyCountScore: 100,
|
|
6041
|
-
couplingScore: 100,
|
|
6042
|
-
averageFileLength: 0,
|
|
6043
|
-
averageFunctionsPerFile: 0,
|
|
6044
|
-
averageImportsPerFile: 0,
|
|
6045
|
-
fileCount: 0,
|
|
6046
|
-
details: "No files to analyze"
|
|
7361
|
+
score: raw.score,
|
|
7362
|
+
issues: raw.issues ?? []
|
|
6047
7363
|
};
|
|
6048
7364
|
}
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
7365
|
+
};
|
|
7366
|
+
}
|
|
7367
|
+
function createDefaultRegistry(projectPath) {
|
|
7368
|
+
const registry = new DimensionRegistry();
|
|
7369
|
+
const coverageAnalyzer = new CoverageAnalyzer(projectPath);
|
|
7370
|
+
const securityScanner = new CompositeSecurityScanner(projectPath, false);
|
|
7371
|
+
const complexityAnalyzer = new ComplexityAnalyzer(projectPath);
|
|
7372
|
+
const duplicationAnalyzer = new DuplicationAnalyzer(projectPath);
|
|
7373
|
+
const correctnessAnalyzer = new CorrectnessAnalyzer(projectPath);
|
|
7374
|
+
const completenessAnalyzer = new CompletenessAnalyzer(projectPath);
|
|
7375
|
+
const robustnessAnalyzer = new RobustnessAnalyzer(projectPath);
|
|
7376
|
+
const testQualityAnalyzer = new TestQualityAnalyzer(projectPath);
|
|
7377
|
+
const documentationAnalyzer = new DocumentationAnalyzer(projectPath);
|
|
7378
|
+
const styleAnalyzer = new StyleAnalyzer(projectPath);
|
|
7379
|
+
const readabilityAnalyzer = new ReadabilityAnalyzer(projectPath);
|
|
7380
|
+
const maintainabilityAnalyzer = new MaintainabilityAnalyzer(projectPath);
|
|
7381
|
+
const baseLangs = ["typescript", "javascript"];
|
|
7382
|
+
const reactLangs = ["react-typescript", "react-javascript"];
|
|
7383
|
+
for (const lang of baseLangs) {
|
|
7384
|
+
registry.register(
|
|
7385
|
+
wrapAnalyzer("correctness", lang, async () => {
|
|
7386
|
+
const r = await correctnessAnalyzer.analyze();
|
|
7387
|
+
return { score: r.score };
|
|
7388
|
+
})
|
|
7389
|
+
);
|
|
7390
|
+
registry.register(
|
|
7391
|
+
wrapAnalyzer("completeness", lang, async (input) => {
|
|
7392
|
+
const r = await completenessAnalyzer.analyze(input.files);
|
|
7393
|
+
return { score: r.score };
|
|
7394
|
+
})
|
|
7395
|
+
);
|
|
7396
|
+
registry.register(
|
|
7397
|
+
wrapAnalyzer("robustness", lang, async (input) => {
|
|
7398
|
+
const r = await robustnessAnalyzer.analyze(input.files);
|
|
7399
|
+
return { score: r.score };
|
|
7400
|
+
})
|
|
7401
|
+
);
|
|
7402
|
+
registry.register(
|
|
7403
|
+
wrapAnalyzer("readability", lang, async (input) => {
|
|
7404
|
+
const r = await readabilityAnalyzer.analyze(input.files);
|
|
7405
|
+
return { score: r.score };
|
|
7406
|
+
})
|
|
7407
|
+
);
|
|
7408
|
+
registry.register(
|
|
7409
|
+
wrapAnalyzer("maintainability", lang, async (input) => {
|
|
7410
|
+
const r = await maintainabilityAnalyzer.analyze(input.files);
|
|
7411
|
+
return { score: r.score };
|
|
7412
|
+
})
|
|
7413
|
+
);
|
|
7414
|
+
registry.register(
|
|
7415
|
+
wrapAnalyzer("complexity", lang, async (input) => {
|
|
7416
|
+
const r = await complexityAnalyzer.analyze(input.files);
|
|
7417
|
+
return { score: r.score };
|
|
7418
|
+
})
|
|
7419
|
+
);
|
|
7420
|
+
registry.register(
|
|
7421
|
+
wrapAnalyzer("duplication", lang, async (input) => {
|
|
7422
|
+
const r = await duplicationAnalyzer.analyze(input.files);
|
|
7423
|
+
return { score: Math.max(0, 100 - r.percentage) };
|
|
7424
|
+
})
|
|
7425
|
+
);
|
|
7426
|
+
registry.register(
|
|
7427
|
+
wrapAnalyzer("testCoverage", lang, async () => {
|
|
7428
|
+
const r = await coverageAnalyzer.analyze();
|
|
7429
|
+
return { score: r?.lines.percentage ?? 0 };
|
|
7430
|
+
})
|
|
7431
|
+
);
|
|
7432
|
+
registry.register(
|
|
7433
|
+
wrapAnalyzer("testQuality", lang, async () => {
|
|
7434
|
+
const r = await testQualityAnalyzer.analyze();
|
|
7435
|
+
return { score: r.score };
|
|
7436
|
+
})
|
|
7437
|
+
);
|
|
7438
|
+
registry.register(
|
|
7439
|
+
wrapAnalyzer("security", lang, async (input) => {
|
|
7440
|
+
const fileContents = await Promise.all(
|
|
7441
|
+
input.files.map(async (f) => ({
|
|
7442
|
+
path: f,
|
|
7443
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
7444
|
+
}))
|
|
7445
|
+
);
|
|
7446
|
+
const r = await securityScanner.scan(fileContents);
|
|
7447
|
+
return { score: r.score };
|
|
7448
|
+
})
|
|
7449
|
+
);
|
|
7450
|
+
registry.register(
|
|
7451
|
+
wrapAnalyzer("documentation", lang, async (input) => {
|
|
7452
|
+
const r = await documentationAnalyzer.analyze(input.files);
|
|
7453
|
+
return { score: r.score };
|
|
7454
|
+
})
|
|
7455
|
+
);
|
|
7456
|
+
registry.register(
|
|
7457
|
+
wrapAnalyzer("style", lang, async () => {
|
|
7458
|
+
const r = await styleAnalyzer.analyze();
|
|
7459
|
+
return { score: r.score };
|
|
7460
|
+
})
|
|
6082
7461
|
);
|
|
6083
|
-
const details = [
|
|
6084
|
-
`${Math.round(averageFileLength)} avg lines/file`,
|
|
6085
|
-
`${averageFunctionsPerFile.toFixed(1)} avg functions/file`,
|
|
6086
|
-
`${averageImportsPerFile.toFixed(1)} avg imports/file`,
|
|
6087
|
-
`${Math.round(crossBoundaryRatio * 100)}% cross-boundary coupling`
|
|
6088
|
-
].join(", ");
|
|
6089
|
-
return {
|
|
6090
|
-
score,
|
|
6091
|
-
fileLengthScore,
|
|
6092
|
-
functionCountScore,
|
|
6093
|
-
dependencyCountScore,
|
|
6094
|
-
couplingScore,
|
|
6095
|
-
averageFileLength,
|
|
6096
|
-
averageFunctionsPerFile,
|
|
6097
|
-
averageImportsPerFile,
|
|
6098
|
-
fileCount: filesAnalyzed,
|
|
6099
|
-
details
|
|
6100
|
-
};
|
|
6101
7462
|
}
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
7463
|
+
for (const lang of reactLangs) {
|
|
7464
|
+
registry.register(
|
|
7465
|
+
wrapAnalyzer("completeness", lang, async (input) => {
|
|
7466
|
+
const r = await completenessAnalyzer.analyze(input.files);
|
|
7467
|
+
return { score: r.score };
|
|
7468
|
+
})
|
|
7469
|
+
);
|
|
7470
|
+
registry.register(
|
|
7471
|
+
wrapAnalyzer("readability", lang, async (input) => {
|
|
7472
|
+
const r = await readabilityAnalyzer.analyze(input.files);
|
|
7473
|
+
return { score: r.score };
|
|
7474
|
+
})
|
|
7475
|
+
);
|
|
7476
|
+
registry.register(
|
|
7477
|
+
wrapAnalyzer("maintainability", lang, async (input) => {
|
|
7478
|
+
const r = await maintainabilityAnalyzer.analyze(input.files);
|
|
7479
|
+
return { score: r.score };
|
|
7480
|
+
})
|
|
7481
|
+
);
|
|
7482
|
+
registry.register(
|
|
7483
|
+
wrapAnalyzer("complexity", lang, async (input) => {
|
|
7484
|
+
const r = await complexityAnalyzer.analyze(input.files);
|
|
7485
|
+
return { score: r.score };
|
|
7486
|
+
})
|
|
7487
|
+
);
|
|
7488
|
+
registry.register(
|
|
7489
|
+
wrapAnalyzer("duplication", lang, async (input) => {
|
|
7490
|
+
const r = await duplicationAnalyzer.analyze(input.files);
|
|
7491
|
+
return { score: Math.max(0, 100 - r.percentage) };
|
|
7492
|
+
})
|
|
7493
|
+
);
|
|
7494
|
+
registry.register(
|
|
7495
|
+
wrapAnalyzer("testCoverage", lang, async () => {
|
|
7496
|
+
const r = await coverageAnalyzer.analyze();
|
|
7497
|
+
return { score: r?.lines.percentage ?? 0 };
|
|
7498
|
+
})
|
|
7499
|
+
);
|
|
7500
|
+
registry.register(
|
|
7501
|
+
wrapAnalyzer("testQuality", lang, async () => {
|
|
7502
|
+
const r = await testQualityAnalyzer.analyze();
|
|
7503
|
+
return { score: r.score };
|
|
7504
|
+
})
|
|
7505
|
+
);
|
|
7506
|
+
registry.register(
|
|
7507
|
+
wrapAnalyzer("security", lang, async (input) => {
|
|
7508
|
+
const fileContents = await Promise.all(
|
|
7509
|
+
input.files.map(async (f) => ({
|
|
7510
|
+
path: f,
|
|
7511
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
7512
|
+
}))
|
|
7513
|
+
);
|
|
7514
|
+
const r = await securityScanner.scan(fileContents);
|
|
7515
|
+
return { score: r.score };
|
|
7516
|
+
})
|
|
7517
|
+
);
|
|
7518
|
+
registry.register(
|
|
7519
|
+
wrapAnalyzer("documentation", lang, async (input) => {
|
|
7520
|
+
const r = await documentationAnalyzer.analyze(input.files);
|
|
7521
|
+
return { score: r.score };
|
|
7522
|
+
})
|
|
7523
|
+
);
|
|
6108
7524
|
}
|
|
6109
|
-
|
|
7525
|
+
return registry;
|
|
7526
|
+
}
|
|
7527
|
+
|
|
7528
|
+
// src/quality/evaluator.ts
|
|
6110
7529
|
var QualityEvaluator = class {
|
|
6111
|
-
constructor(projectPath, useSnyk = false) {
|
|
7530
|
+
constructor(projectPath, useSnyk = false, registry) {
|
|
6112
7531
|
this.projectPath = projectPath;
|
|
7532
|
+
this.registry = registry;
|
|
6113
7533
|
this.coverageAnalyzer = new CoverageAnalyzer(projectPath);
|
|
6114
7534
|
this.securityScanner = new CompositeSecurityScanner(projectPath, useSnyk);
|
|
6115
7535
|
this.complexityAnalyzer = new ComplexityAnalyzer(projectPath);
|
|
@@ -6145,7 +7565,7 @@ var QualityEvaluator = class {
|
|
|
6145
7565
|
const fileContents = await Promise.all(
|
|
6146
7566
|
targetFiles.map(async (file) => ({
|
|
6147
7567
|
path: file,
|
|
6148
|
-
content: await readFile(file, "utf-8")
|
|
7568
|
+
content: await readFile(file, "utf-8").catch(() => "")
|
|
6149
7569
|
}))
|
|
6150
7570
|
);
|
|
6151
7571
|
const [
|
|
@@ -6189,8 +7609,27 @@ var QualityEvaluator = class {
|
|
|
6189
7609
|
testQuality: testQualityResult.score,
|
|
6190
7610
|
documentation: documentationResult.score
|
|
6191
7611
|
};
|
|
7612
|
+
if (this.registry) {
|
|
7613
|
+
const { language } = detectProjectLanguage(targetFiles);
|
|
7614
|
+
const registryResults = await this.registry.analyze({
|
|
7615
|
+
projectPath: this.projectPath,
|
|
7616
|
+
files: targetFiles,
|
|
7617
|
+
language
|
|
7618
|
+
});
|
|
7619
|
+
for (const { dimensionId, result } of registryResults) {
|
|
7620
|
+
if (dimensionId in dimensions) {
|
|
7621
|
+
dimensions[dimensionId] = result.score;
|
|
7622
|
+
}
|
|
7623
|
+
}
|
|
7624
|
+
}
|
|
7625
|
+
let projectConfig = null;
|
|
7626
|
+
try {
|
|
7627
|
+
projectConfig = await loadProjectConfig(this.projectPath);
|
|
7628
|
+
} catch {
|
|
7629
|
+
}
|
|
7630
|
+
const weights = resolvedWeights(projectConfig);
|
|
6192
7631
|
const overall = Object.entries(dimensions).reduce((sum, [key, value]) => {
|
|
6193
|
-
const weight =
|
|
7632
|
+
const weight = weights[key] ?? 0;
|
|
6194
7633
|
return sum + value * weight;
|
|
6195
7634
|
}, 0);
|
|
6196
7635
|
const scores = {
|
|
@@ -6208,13 +7647,15 @@ var QualityEvaluator = class {
|
|
|
6208
7647
|
documentationResult
|
|
6209
7648
|
);
|
|
6210
7649
|
const suggestions = this.generateSuggestions(dimensions);
|
|
6211
|
-
const
|
|
6212
|
-
const
|
|
7650
|
+
const thresholds = resolvedThresholds(projectConfig);
|
|
7651
|
+
const meetsMinimum = scores.overall >= thresholds.minimum.overall && dimensions.testCoverage >= thresholds.minimum.testCoverage && dimensions.security >= thresholds.minimum.security;
|
|
7652
|
+
const meetsTarget = scores.overall >= thresholds.target.overall && dimensions.testCoverage >= thresholds.target.testCoverage;
|
|
7653
|
+
const converged = scores.overall >= thresholds.target.overall;
|
|
6213
7654
|
return {
|
|
6214
7655
|
scores,
|
|
6215
7656
|
meetsMinimum,
|
|
6216
7657
|
meetsTarget,
|
|
6217
|
-
converged
|
|
7658
|
+
converged,
|
|
6218
7659
|
issues,
|
|
6219
7660
|
suggestions
|
|
6220
7661
|
};
|
|
@@ -6363,8 +7804,11 @@ var QualityEvaluator = class {
|
|
|
6363
7804
|
});
|
|
6364
7805
|
}
|
|
6365
7806
|
};
|
|
6366
|
-
function
|
|
6367
|
-
|
|
7807
|
+
function createQualityEvaluatorWithRegistry(projectPath, useSnyk) {
|
|
7808
|
+
const registry = createDefaultRegistry(projectPath);
|
|
7809
|
+
registerJavaAnalyzers(registry, projectPath);
|
|
7810
|
+
registerReactAnalyzers(registry, projectPath);
|
|
7811
|
+
return new QualityEvaluator(projectPath, useSnyk ?? false, registry);
|
|
6368
7812
|
}
|
|
6369
7813
|
|
|
6370
7814
|
// src/phases/complete/prompts.ts
|
|
@@ -6674,7 +8118,7 @@ function setupFileLogging(logger, logDir, name) {
|
|
|
6674
8118
|
if (!fs4__default.existsSync(logDir)) {
|
|
6675
8119
|
fs4__default.mkdirSync(logDir, { recursive: true });
|
|
6676
8120
|
}
|
|
6677
|
-
const logFile =
|
|
8121
|
+
const logFile = path15__default.join(logDir, `${name}.log`);
|
|
6678
8122
|
logger.attachTransport((logObj) => {
|
|
6679
8123
|
const line = JSON.stringify(logObj) + "\n";
|
|
6680
8124
|
fs4__default.appendFileSync(logFile, line);
|
|
@@ -8057,13 +9501,13 @@ var CompleteExecutor = class {
|
|
|
8057
9501
|
*/
|
|
8058
9502
|
async checkpoint(context) {
|
|
8059
9503
|
if (this.checkpointState && this.currentSprint) {
|
|
8060
|
-
const checkpointPath =
|
|
9504
|
+
const checkpointPath = path15__default.join(
|
|
8061
9505
|
context.projectPath,
|
|
8062
9506
|
".coco",
|
|
8063
9507
|
"checkpoints",
|
|
8064
9508
|
`complete-${this.currentSprint.id}.json`
|
|
8065
9509
|
);
|
|
8066
|
-
await fs14__default.mkdir(
|
|
9510
|
+
await fs14__default.mkdir(path15__default.dirname(checkpointPath), { recursive: true });
|
|
8067
9511
|
await fs14__default.writeFile(checkpointPath, JSON.stringify(this.checkpointState, null, 2), "utf-8");
|
|
8068
9512
|
}
|
|
8069
9513
|
return {
|
|
@@ -8084,7 +9528,7 @@ var CompleteExecutor = class {
|
|
|
8084
9528
|
const sprintId = checkpoint.resumePoint;
|
|
8085
9529
|
if (sprintId === "start") return;
|
|
8086
9530
|
try {
|
|
8087
|
-
const checkpointPath =
|
|
9531
|
+
const checkpointPath = path15__default.join(
|
|
8088
9532
|
context.projectPath,
|
|
8089
9533
|
".coco",
|
|
8090
9534
|
"checkpoints",
|
|
@@ -8337,8 +9781,8 @@ var CompleteExecutor = class {
|
|
|
8337
9781
|
};
|
|
8338
9782
|
const saveFiles = async (files) => {
|
|
8339
9783
|
for (const file of files) {
|
|
8340
|
-
const filePath =
|
|
8341
|
-
const dir =
|
|
9784
|
+
const filePath = path15__default.join(context.projectPath, file.path);
|
|
9785
|
+
const dir = path15__default.dirname(filePath);
|
|
8342
9786
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
8343
9787
|
if (file.action === "delete") {
|
|
8344
9788
|
await fs14__default.unlink(filePath).catch(() => {
|
|
@@ -8470,7 +9914,7 @@ var CompleteExecutor = class {
|
|
|
8470
9914
|
*/
|
|
8471
9915
|
async loadBacklog(projectPath) {
|
|
8472
9916
|
try {
|
|
8473
|
-
const backlogPath =
|
|
9917
|
+
const backlogPath = path15__default.join(projectPath, ".coco", "planning", "backlog.json");
|
|
8474
9918
|
const content = await fs14__default.readFile(backlogPath, "utf-8");
|
|
8475
9919
|
const data = JSON.parse(content);
|
|
8476
9920
|
return data.backlog;
|
|
@@ -8483,11 +9927,11 @@ var CompleteExecutor = class {
|
|
|
8483
9927
|
*/
|
|
8484
9928
|
async loadCurrentSprint(projectPath) {
|
|
8485
9929
|
try {
|
|
8486
|
-
const sprintsDir =
|
|
9930
|
+
const sprintsDir = path15__default.join(projectPath, ".coco", "planning", "sprints");
|
|
8487
9931
|
const files = await fs14__default.readdir(sprintsDir);
|
|
8488
9932
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
8489
9933
|
if (jsonFiles.length === 0) return null;
|
|
8490
|
-
const sprintPath =
|
|
9934
|
+
const sprintPath = path15__default.join(sprintsDir, jsonFiles[0] || "");
|
|
8491
9935
|
const content = await fs14__default.readFile(sprintPath, "utf-8");
|
|
8492
9936
|
const sprint = JSON.parse(content);
|
|
8493
9937
|
sprint.startDate = new Date(sprint.startDate);
|
|
@@ -8500,11 +9944,11 @@ var CompleteExecutor = class {
|
|
|
8500
9944
|
* Save sprint results
|
|
8501
9945
|
*/
|
|
8502
9946
|
async saveSprintResults(projectPath, result) {
|
|
8503
|
-
const resultsDir =
|
|
9947
|
+
const resultsDir = path15__default.join(projectPath, ".coco", "results");
|
|
8504
9948
|
await fs14__default.mkdir(resultsDir, { recursive: true });
|
|
8505
|
-
const resultsPath =
|
|
9949
|
+
const resultsPath = path15__default.join(resultsDir, `${result.sprintId}-results.json`);
|
|
8506
9950
|
await fs14__default.writeFile(resultsPath, JSON.stringify(result, null, 2), "utf-8");
|
|
8507
|
-
const mdPath =
|
|
9951
|
+
const mdPath = path15__default.join(resultsDir, `${result.sprintId}-results.md`);
|
|
8508
9952
|
await fs14__default.writeFile(mdPath, this.generateResultsMarkdown(result), "utf-8");
|
|
8509
9953
|
return resultsPath;
|
|
8510
9954
|
}
|
|
@@ -9729,8 +11173,8 @@ var OutputExecutor = class {
|
|
|
9729
11173
|
const cicdGenerator = new CICDGenerator(metadata, cicdConfig);
|
|
9730
11174
|
const cicdFiles = cicdGenerator.generate();
|
|
9731
11175
|
for (const file of cicdFiles) {
|
|
9732
|
-
const filePath =
|
|
9733
|
-
await this.ensureDir(
|
|
11176
|
+
const filePath = path15__default.join(context.projectPath, file.path);
|
|
11177
|
+
await this.ensureDir(path15__default.dirname(filePath));
|
|
9734
11178
|
await fs14__default.writeFile(filePath, file.content, "utf-8");
|
|
9735
11179
|
artifacts.push({
|
|
9736
11180
|
type: "cicd",
|
|
@@ -9741,7 +11185,7 @@ var OutputExecutor = class {
|
|
|
9741
11185
|
if (this.config.docker.enabled) {
|
|
9742
11186
|
const dockerGenerator = new DockerGenerator(metadata);
|
|
9743
11187
|
const dockerfile2 = dockerGenerator.generateDockerfile();
|
|
9744
|
-
const dockerfilePath =
|
|
11188
|
+
const dockerfilePath = path15__default.join(context.projectPath, "Dockerfile");
|
|
9745
11189
|
await fs14__default.writeFile(dockerfilePath, dockerfile2, "utf-8");
|
|
9746
11190
|
artifacts.push({
|
|
9747
11191
|
type: "deployment",
|
|
@@ -9749,7 +11193,7 @@ var OutputExecutor = class {
|
|
|
9749
11193
|
description: "Dockerfile"
|
|
9750
11194
|
});
|
|
9751
11195
|
const dockerignore = dockerGenerator.generateDockerignore();
|
|
9752
|
-
const dockerignorePath =
|
|
11196
|
+
const dockerignorePath = path15__default.join(context.projectPath, ".dockerignore");
|
|
9753
11197
|
await fs14__default.writeFile(dockerignorePath, dockerignore, "utf-8");
|
|
9754
11198
|
artifacts.push({
|
|
9755
11199
|
type: "deployment",
|
|
@@ -9758,7 +11202,7 @@ var OutputExecutor = class {
|
|
|
9758
11202
|
});
|
|
9759
11203
|
if (this.config.docker.compose) {
|
|
9760
11204
|
const compose = dockerGenerator.generateDockerCompose();
|
|
9761
|
-
const composePath =
|
|
11205
|
+
const composePath = path15__default.join(context.projectPath, "docker-compose.yml");
|
|
9762
11206
|
await fs14__default.writeFile(composePath, compose, "utf-8");
|
|
9763
11207
|
artifacts.push({
|
|
9764
11208
|
type: "deployment",
|
|
@@ -9770,7 +11214,7 @@ var OutputExecutor = class {
|
|
|
9770
11214
|
const docsGenerator = new DocsGenerator(metadata);
|
|
9771
11215
|
const docs = docsGenerator.generate();
|
|
9772
11216
|
if (this.config.docs.readme) {
|
|
9773
|
-
const readmePath =
|
|
11217
|
+
const readmePath = path15__default.join(context.projectPath, "README.md");
|
|
9774
11218
|
await fs14__default.writeFile(readmePath, docs.readme, "utf-8");
|
|
9775
11219
|
artifacts.push({
|
|
9776
11220
|
type: "documentation",
|
|
@@ -9779,7 +11223,7 @@ var OutputExecutor = class {
|
|
|
9779
11223
|
});
|
|
9780
11224
|
}
|
|
9781
11225
|
if (this.config.docs.contributing) {
|
|
9782
|
-
const contributingPath =
|
|
11226
|
+
const contributingPath = path15__default.join(context.projectPath, "CONTRIBUTING.md");
|
|
9783
11227
|
await fs14__default.writeFile(contributingPath, docs.contributing, "utf-8");
|
|
9784
11228
|
artifacts.push({
|
|
9785
11229
|
type: "documentation",
|
|
@@ -9788,7 +11232,7 @@ var OutputExecutor = class {
|
|
|
9788
11232
|
});
|
|
9789
11233
|
}
|
|
9790
11234
|
if (this.config.docs.changelog) {
|
|
9791
|
-
const changelogPath =
|
|
11235
|
+
const changelogPath = path15__default.join(context.projectPath, "CHANGELOG.md");
|
|
9792
11236
|
await fs14__default.writeFile(changelogPath, docs.changelog, "utf-8");
|
|
9793
11237
|
artifacts.push({
|
|
9794
11238
|
type: "documentation",
|
|
@@ -9797,10 +11241,10 @@ var OutputExecutor = class {
|
|
|
9797
11241
|
});
|
|
9798
11242
|
}
|
|
9799
11243
|
if (this.config.docs.api) {
|
|
9800
|
-
const docsDir =
|
|
11244
|
+
const docsDir = path15__default.join(context.projectPath, "docs");
|
|
9801
11245
|
await this.ensureDir(docsDir);
|
|
9802
11246
|
if (docs.api) {
|
|
9803
|
-
const apiPath =
|
|
11247
|
+
const apiPath = path15__default.join(docsDir, "api.md");
|
|
9804
11248
|
await fs14__default.writeFile(apiPath, docs.api, "utf-8");
|
|
9805
11249
|
artifacts.push({
|
|
9806
11250
|
type: "documentation",
|
|
@@ -9809,7 +11253,7 @@ var OutputExecutor = class {
|
|
|
9809
11253
|
});
|
|
9810
11254
|
}
|
|
9811
11255
|
if (docs.deployment) {
|
|
9812
|
-
const deployPath =
|
|
11256
|
+
const deployPath = path15__default.join(docsDir, "deployment.md");
|
|
9813
11257
|
await fs14__default.writeFile(deployPath, docs.deployment, "utf-8");
|
|
9814
11258
|
artifacts.push({
|
|
9815
11259
|
type: "documentation",
|
|
@@ -9818,7 +11262,7 @@ var OutputExecutor = class {
|
|
|
9818
11262
|
});
|
|
9819
11263
|
}
|
|
9820
11264
|
if (docs.development) {
|
|
9821
|
-
const devPath =
|
|
11265
|
+
const devPath = path15__default.join(docsDir, "development.md");
|
|
9822
11266
|
await fs14__default.writeFile(devPath, docs.development, "utf-8");
|
|
9823
11267
|
artifacts.push({
|
|
9824
11268
|
type: "documentation",
|
|
@@ -9884,16 +11328,16 @@ var OutputExecutor = class {
|
|
|
9884
11328
|
*/
|
|
9885
11329
|
async loadMetadata(projectPath) {
|
|
9886
11330
|
try {
|
|
9887
|
-
const packagePath =
|
|
11331
|
+
const packagePath = path15__default.join(projectPath, "package.json");
|
|
9888
11332
|
const content = await fs14__default.readFile(packagePath, "utf-8");
|
|
9889
11333
|
const pkg = JSON.parse(content);
|
|
9890
11334
|
let packageManager = "npm";
|
|
9891
11335
|
try {
|
|
9892
|
-
await fs14__default.access(
|
|
11336
|
+
await fs14__default.access(path15__default.join(projectPath, "pnpm-lock.yaml"));
|
|
9893
11337
|
packageManager = "pnpm";
|
|
9894
11338
|
} catch {
|
|
9895
11339
|
try {
|
|
9896
|
-
await fs14__default.access(
|
|
11340
|
+
await fs14__default.access(path15__default.join(projectPath, "yarn.lock"));
|
|
9897
11341
|
packageManager = "yarn";
|
|
9898
11342
|
} catch {
|
|
9899
11343
|
}
|
|
@@ -9905,7 +11349,7 @@ var OutputExecutor = class {
|
|
|
9905
11349
|
repository = pkg.repository.url;
|
|
9906
11350
|
}
|
|
9907
11351
|
return {
|
|
9908
|
-
name: pkg.name ||
|
|
11352
|
+
name: pkg.name || path15__default.basename(projectPath),
|
|
9909
11353
|
description: pkg.description || "",
|
|
9910
11354
|
version: pkg.version || "0.1.0",
|
|
9911
11355
|
language: "typescript",
|
|
@@ -9919,7 +11363,7 @@ var OutputExecutor = class {
|
|
|
9919
11363
|
};
|
|
9920
11364
|
} catch {
|
|
9921
11365
|
return {
|
|
9922
|
-
name:
|
|
11366
|
+
name: path15__default.basename(projectPath),
|
|
9923
11367
|
description: "",
|
|
9924
11368
|
version: "0.1.0",
|
|
9925
11369
|
language: "typescript",
|
|
@@ -9951,7 +11395,7 @@ var DEFAULT_RETRY_CONFIG = {
|
|
|
9951
11395
|
jitterFactor: 0.1
|
|
9952
11396
|
};
|
|
9953
11397
|
function sleep(ms) {
|
|
9954
|
-
return new Promise((
|
|
11398
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
9955
11399
|
}
|
|
9956
11400
|
function calculateDelay(baseDelay, jitterFactor, maxDelay) {
|
|
9957
11401
|
const jitter = baseDelay * jitterFactor * (Math.random() * 2 - 1);
|
|
@@ -10454,7 +11898,32 @@ var CONTEXT_WINDOWS2 = {
|
|
|
10454
11898
|
// Mistral models
|
|
10455
11899
|
"mistral-7b": 32768,
|
|
10456
11900
|
"mistral-nemo": 128e3,
|
|
10457
|
-
"mixtral-8x7b": 32768
|
|
11901
|
+
"mixtral-8x7b": 32768,
|
|
11902
|
+
// Mistral AI API models
|
|
11903
|
+
"codestral-latest": 32768,
|
|
11904
|
+
"mistral-large-latest": 131072,
|
|
11905
|
+
"mistral-small-latest": 131072,
|
|
11906
|
+
"open-mixtral-8x22b": 65536,
|
|
11907
|
+
// Groq-hosted models (same models, fast inference)
|
|
11908
|
+
"llama-3.3-70b-versatile": 128e3,
|
|
11909
|
+
"llama-3.1-8b-instant": 128e3,
|
|
11910
|
+
"mixtral-8x7b-32768": 32768,
|
|
11911
|
+
"gemma2-9b-it": 8192,
|
|
11912
|
+
// DeepSeek API models
|
|
11913
|
+
"deepseek-chat": 65536,
|
|
11914
|
+
"deepseek-reasoner": 65536,
|
|
11915
|
+
// Together AI model IDs
|
|
11916
|
+
"Qwen/Qwen2.5-Coder-32B-Instruct": 32768,
|
|
11917
|
+
"meta-llama/Meta-Llama-3.1-70B-Instruct": 128e3,
|
|
11918
|
+
"mistralai/Mixtral-8x7B-Instruct-v0.1": 32768,
|
|
11919
|
+
// HuggingFace model IDs
|
|
11920
|
+
"meta-llama/Llama-3.3-70B-Instruct": 128e3,
|
|
11921
|
+
"microsoft/Phi-4": 16384,
|
|
11922
|
+
// OpenRouter model IDs
|
|
11923
|
+
"anthropic/claude-opus-4-6": 2e5,
|
|
11924
|
+
"openai/gpt-5.3-codex": 4e5,
|
|
11925
|
+
"google/gemini-3-flash-preview": 1e6,
|
|
11926
|
+
"meta-llama/llama-3.3-70b-instruct": 128e3
|
|
10458
11927
|
};
|
|
10459
11928
|
var MODELS_WITHOUT_TEMPERATURE = [
|
|
10460
11929
|
"o1",
|
|
@@ -10741,7 +12210,7 @@ var OpenAIProvider = class {
|
|
|
10741
12210
|
input = JSON.parse(repaired);
|
|
10742
12211
|
console.log(`[${this.name}] \u2713 Successfully repaired JSON for ${builder.name}`);
|
|
10743
12212
|
}
|
|
10744
|
-
} catch
|
|
12213
|
+
} catch {
|
|
10745
12214
|
console.error(
|
|
10746
12215
|
`[${this.name}] Cannot repair JSON for ${builder.name}, using empty object`
|
|
10747
12216
|
);
|
|
@@ -11148,11 +12617,11 @@ async function refreshAccessToken(provider, refreshToken) {
|
|
|
11148
12617
|
}
|
|
11149
12618
|
function getTokenStoragePath(provider) {
|
|
11150
12619
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
11151
|
-
return
|
|
12620
|
+
return path15.join(home, ".coco", "tokens", `${provider}.json`);
|
|
11152
12621
|
}
|
|
11153
12622
|
async function saveTokens(provider, tokens) {
|
|
11154
12623
|
const filePath = getTokenStoragePath(provider);
|
|
11155
|
-
const dir =
|
|
12624
|
+
const dir = path15.dirname(filePath);
|
|
11156
12625
|
await fs14.mkdir(dir, { recursive: true, mode: 448 });
|
|
11157
12626
|
await fs14.writeFile(filePath, JSON.stringify(tokens, null, 2), { mode: 384 });
|
|
11158
12627
|
}
|
|
@@ -11197,6 +12666,17 @@ async function getValidAccessToken(provider) {
|
|
|
11197
12666
|
}
|
|
11198
12667
|
return { accessToken: tokens.accessToken, isNew: false };
|
|
11199
12668
|
}
|
|
12669
|
+
function detectWSL() {
|
|
12670
|
+
if (process.env.WSL_DISTRO_NAME || process.env.WSLENV) return true;
|
|
12671
|
+
try {
|
|
12672
|
+
return /microsoft/i.test(readFileSync("/proc/version", "utf-8"));
|
|
12673
|
+
} catch {
|
|
12674
|
+
return false;
|
|
12675
|
+
}
|
|
12676
|
+
}
|
|
12677
|
+
var isWSL = detectWSL();
|
|
12678
|
+
|
|
12679
|
+
// src/auth/flow.ts
|
|
11200
12680
|
promisify(execFile);
|
|
11201
12681
|
var execAsync2 = promisify(exec);
|
|
11202
12682
|
async function getADCAccessToken() {
|
|
@@ -11492,7 +12972,7 @@ var CodexProvider = class {
|
|
|
11492
12972
|
const chunk = content.slice(i, i + chunkSize);
|
|
11493
12973
|
yield { type: "text", text: chunk };
|
|
11494
12974
|
if (i + chunkSize < content.length) {
|
|
11495
|
-
await new Promise((
|
|
12975
|
+
await new Promise((resolve3) => setTimeout(resolve3, 5));
|
|
11496
12976
|
}
|
|
11497
12977
|
}
|
|
11498
12978
|
}
|
|
@@ -11987,6 +13467,30 @@ async function createProvider(type, config = {}) {
|
|
|
11987
13467
|
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "http://localhost:11434/v1";
|
|
11988
13468
|
mergedConfig.apiKey = mergedConfig.apiKey ?? "ollama";
|
|
11989
13469
|
break;
|
|
13470
|
+
case "groq":
|
|
13471
|
+
provider = new OpenAIProvider("groq", "Groq");
|
|
13472
|
+
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "https://api.groq.com/openai/v1";
|
|
13473
|
+
break;
|
|
13474
|
+
case "openrouter":
|
|
13475
|
+
provider = new OpenAIProvider("openrouter", "OpenRouter");
|
|
13476
|
+
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "https://openrouter.ai/api/v1";
|
|
13477
|
+
break;
|
|
13478
|
+
case "mistral":
|
|
13479
|
+
provider = new OpenAIProvider("mistral", "Mistral AI");
|
|
13480
|
+
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "https://api.mistral.ai/v1";
|
|
13481
|
+
break;
|
|
13482
|
+
case "deepseek":
|
|
13483
|
+
provider = new OpenAIProvider("deepseek", "DeepSeek");
|
|
13484
|
+
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "https://api.deepseek.com/v1";
|
|
13485
|
+
break;
|
|
13486
|
+
case "together":
|
|
13487
|
+
provider = new OpenAIProvider("together", "Together AI");
|
|
13488
|
+
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "https://api.together.xyz/v1";
|
|
13489
|
+
break;
|
|
13490
|
+
case "huggingface":
|
|
13491
|
+
provider = new OpenAIProvider("huggingface", "HuggingFace Inference");
|
|
13492
|
+
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "https://api-inference.huggingface.co/v1";
|
|
13493
|
+
break;
|
|
11990
13494
|
default:
|
|
11991
13495
|
throw new ProviderError(`Unknown provider type: ${type}`, {
|
|
11992
13496
|
provider: type
|
|
@@ -12185,28 +13689,28 @@ async function createPhaseContext(config, state) {
|
|
|
12185
13689
|
};
|
|
12186
13690
|
const tools = {
|
|
12187
13691
|
file: {
|
|
12188
|
-
async read(
|
|
13692
|
+
async read(path38) {
|
|
12189
13693
|
const fs36 = await import('fs/promises');
|
|
12190
|
-
return fs36.readFile(
|
|
13694
|
+
return fs36.readFile(path38, "utf-8");
|
|
12191
13695
|
},
|
|
12192
|
-
async write(
|
|
13696
|
+
async write(path38, content) {
|
|
12193
13697
|
const fs36 = await import('fs/promises');
|
|
12194
13698
|
const nodePath = await import('path');
|
|
12195
|
-
await fs36.mkdir(nodePath.dirname(
|
|
12196
|
-
await fs36.writeFile(
|
|
13699
|
+
await fs36.mkdir(nodePath.dirname(path38), { recursive: true });
|
|
13700
|
+
await fs36.writeFile(path38, content, "utf-8");
|
|
12197
13701
|
},
|
|
12198
|
-
async exists(
|
|
13702
|
+
async exists(path38) {
|
|
12199
13703
|
const fs36 = await import('fs/promises');
|
|
12200
13704
|
try {
|
|
12201
|
-
await fs36.access(
|
|
13705
|
+
await fs36.access(path38);
|
|
12202
13706
|
return true;
|
|
12203
13707
|
} catch {
|
|
12204
13708
|
return false;
|
|
12205
13709
|
}
|
|
12206
13710
|
},
|
|
12207
13711
|
async glob(pattern) {
|
|
12208
|
-
const { glob:
|
|
12209
|
-
return
|
|
13712
|
+
const { glob: glob17 } = await import('glob');
|
|
13713
|
+
return glob17(pattern, { cwd: state.path });
|
|
12210
13714
|
}
|
|
12211
13715
|
},
|
|
12212
13716
|
bash: {
|
|
@@ -12478,7 +13982,21 @@ function generateId() {
|
|
|
12478
13982
|
return `proj_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
|
|
12479
13983
|
}
|
|
12480
13984
|
var ProviderConfigSchema = z.object({
|
|
12481
|
-
type: z.enum([
|
|
13985
|
+
type: z.enum([
|
|
13986
|
+
"anthropic",
|
|
13987
|
+
"openai",
|
|
13988
|
+
"codex",
|
|
13989
|
+
"gemini",
|
|
13990
|
+
"kimi",
|
|
13991
|
+
"lmstudio",
|
|
13992
|
+
"ollama",
|
|
13993
|
+
"groq",
|
|
13994
|
+
"openrouter",
|
|
13995
|
+
"mistral",
|
|
13996
|
+
"deepseek",
|
|
13997
|
+
"together",
|
|
13998
|
+
"huggingface"
|
|
13999
|
+
]).default("anthropic"),
|
|
12482
14000
|
apiKey: z.string().optional(),
|
|
12483
14001
|
model: z.string().default("claude-sonnet-4-20250514"),
|
|
12484
14002
|
maxTokens: z.number().min(1).max(2e5).default(8192),
|
|
@@ -12509,7 +14027,7 @@ var StackConfigSchema = z.object({
|
|
|
12509
14027
|
profile: z.string().optional()
|
|
12510
14028
|
// Custom profile path
|
|
12511
14029
|
});
|
|
12512
|
-
var
|
|
14030
|
+
var ProjectConfigSchema2 = z.object({
|
|
12513
14031
|
name: z.string().min(1),
|
|
12514
14032
|
version: z.string().default("0.1.0"),
|
|
12515
14033
|
description: z.string().optional()
|
|
@@ -12582,8 +14100,22 @@ var ShipConfigSchema = z.object({
|
|
|
12582
14100
|
/** CI check poll interval in ms (default 15 seconds) */
|
|
12583
14101
|
ciCheckPollMs: z.number().default(15e3)
|
|
12584
14102
|
});
|
|
14103
|
+
var SkillsConfigSchema = z.object({
|
|
14104
|
+
/** Enable/disable skills system */
|
|
14105
|
+
enabled: z.boolean().default(true),
|
|
14106
|
+
/** Override global skills directory */
|
|
14107
|
+
globalDir: z.string().optional(),
|
|
14108
|
+
/** Override project skills directory */
|
|
14109
|
+
projectDir: z.string().optional(),
|
|
14110
|
+
/** Auto-activate skills based on user messages */
|
|
14111
|
+
autoActivate: z.boolean().default(true),
|
|
14112
|
+
/** Maximum concurrent active markdown skills */
|
|
14113
|
+
maxActiveSkills: z.number().min(1).max(10).default(3),
|
|
14114
|
+
/** List of skill IDs to disable */
|
|
14115
|
+
disabled: z.array(z.string()).default([])
|
|
14116
|
+
});
|
|
12585
14117
|
var CocoConfigSchema = z.object({
|
|
12586
|
-
project:
|
|
14118
|
+
project: ProjectConfigSchema2,
|
|
12587
14119
|
provider: ProviderConfigSchema.default({
|
|
12588
14120
|
type: "anthropic",
|
|
12589
14121
|
model: "claude-sonnet-4-20250514",
|
|
@@ -12609,7 +14141,8 @@ var CocoConfigSchema = z.object({
|
|
|
12609
14141
|
integrations: IntegrationsConfigSchema.optional(),
|
|
12610
14142
|
mcp: MCPConfigSchema.optional(),
|
|
12611
14143
|
tools: ToolsConfigSchema.optional(),
|
|
12612
|
-
ship: ShipConfigSchema.optional()
|
|
14144
|
+
ship: ShipConfigSchema.optional(),
|
|
14145
|
+
skills: SkillsConfigSchema.optional()
|
|
12613
14146
|
});
|
|
12614
14147
|
function createDefaultConfigObject(projectName, language = "typescript") {
|
|
12615
14148
|
return {
|
|
@@ -12652,7 +14185,7 @@ async function loadConfig(configPath) {
|
|
|
12652
14185
|
if (globalConfig) {
|
|
12653
14186
|
config = deepMergeConfig(config, globalConfig);
|
|
12654
14187
|
}
|
|
12655
|
-
const projectConfigPath = configPath ||
|
|
14188
|
+
const projectConfigPath = configPath || getProjectConfigPath2();
|
|
12656
14189
|
const projectConfig = await loadConfigFile(projectConfigPath);
|
|
12657
14190
|
if (projectConfig) {
|
|
12658
14191
|
config = deepMergeConfig(config, projectConfig);
|
|
@@ -12717,8 +14250,8 @@ function deepMergeConfig(base, override) {
|
|
|
12717
14250
|
...base.tools || override.tools ? { tools: { ...base.tools, ...override.tools } } : {}
|
|
12718
14251
|
};
|
|
12719
14252
|
}
|
|
12720
|
-
function
|
|
12721
|
-
return
|
|
14253
|
+
function getProjectConfigPath2() {
|
|
14254
|
+
return path15__default.join(process.cwd(), ".coco", "config.json");
|
|
12722
14255
|
}
|
|
12723
14256
|
async function saveConfig(config, configPath, global = false) {
|
|
12724
14257
|
const result = CocoConfigSchema.safeParse(config);
|
|
@@ -12729,11 +14262,11 @@ async function saveConfig(config, configPath, global = false) {
|
|
|
12729
14262
|
}));
|
|
12730
14263
|
throw new ConfigError("Cannot save invalid configuration", {
|
|
12731
14264
|
issues,
|
|
12732
|
-
configPath: configPath ||
|
|
14265
|
+
configPath: configPath || getProjectConfigPath2()
|
|
12733
14266
|
});
|
|
12734
14267
|
}
|
|
12735
|
-
const resolvedPath = configPath || (global ? CONFIG_PATHS.config :
|
|
12736
|
-
const dir =
|
|
14268
|
+
const resolvedPath = configPath || (global ? CONFIG_PATHS.config : getProjectConfigPath2());
|
|
14269
|
+
const dir = path15__default.dirname(resolvedPath);
|
|
12737
14270
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
12738
14271
|
const content = JSON.stringify(result.data, null, 2);
|
|
12739
14272
|
await fs14__default.writeFile(resolvedPath, content, "utf-8");
|
|
@@ -12752,7 +14285,7 @@ async function configExists(configPath, scope = "any") {
|
|
|
12752
14285
|
}
|
|
12753
14286
|
if (scope === "project" || scope === "any") {
|
|
12754
14287
|
try {
|
|
12755
|
-
await fs14__default.access(
|
|
14288
|
+
await fs14__default.access(getProjectConfigPath2());
|
|
12756
14289
|
return true;
|
|
12757
14290
|
} catch {
|
|
12758
14291
|
if (scope === "project") return false;
|
|
@@ -12797,7 +14330,7 @@ function hasNullByte(str) {
|
|
|
12797
14330
|
}
|
|
12798
14331
|
function normalizePath(filePath) {
|
|
12799
14332
|
let normalized = filePath.replace(/\0/g, "");
|
|
12800
|
-
normalized =
|
|
14333
|
+
normalized = path15__default.normalize(normalized);
|
|
12801
14334
|
return normalized;
|
|
12802
14335
|
}
|
|
12803
14336
|
function isPathAllowed(filePath, operation) {
|
|
@@ -12805,31 +14338,31 @@ function isPathAllowed(filePath, operation) {
|
|
|
12805
14338
|
return { allowed: false, reason: "Path contains invalid characters" };
|
|
12806
14339
|
}
|
|
12807
14340
|
const normalized = normalizePath(filePath);
|
|
12808
|
-
const absolute =
|
|
14341
|
+
const absolute = path15__default.resolve(normalized);
|
|
12809
14342
|
const cwd = process.cwd();
|
|
12810
14343
|
for (const blocked of BLOCKED_PATHS) {
|
|
12811
|
-
const normalizedBlocked =
|
|
12812
|
-
if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked +
|
|
14344
|
+
const normalizedBlocked = path15__default.normalize(blocked);
|
|
14345
|
+
if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path15__default.sep)) {
|
|
12813
14346
|
return { allowed: false, reason: `Access to system path '${blocked}' is not allowed` };
|
|
12814
14347
|
}
|
|
12815
14348
|
}
|
|
12816
14349
|
const home = process.env.HOME;
|
|
12817
14350
|
if (home) {
|
|
12818
|
-
const normalizedHome =
|
|
12819
|
-
const normalizedCwd =
|
|
14351
|
+
const normalizedHome = path15__default.normalize(home);
|
|
14352
|
+
const normalizedCwd = path15__default.normalize(cwd);
|
|
12820
14353
|
if (absolute.startsWith(normalizedHome) && !absolute.startsWith(normalizedCwd)) {
|
|
12821
14354
|
if (isWithinAllowedPath(absolute, operation)) ; else if (operation === "read") {
|
|
12822
14355
|
const allowedHomeReads = [".gitconfig", ".zshrc", ".bashrc"];
|
|
12823
|
-
const
|
|
12824
|
-
if (!allowedHomeReads.includes(
|
|
12825
|
-
const targetDir =
|
|
14356
|
+
const basename5 = path15__default.basename(absolute);
|
|
14357
|
+
if (!allowedHomeReads.includes(basename5)) {
|
|
14358
|
+
const targetDir = path15__default.dirname(absolute);
|
|
12826
14359
|
return {
|
|
12827
14360
|
allowed: false,
|
|
12828
14361
|
reason: `Reading files outside project directory is not allowed. Use /allow-path ${targetDir} to grant access.`
|
|
12829
14362
|
};
|
|
12830
14363
|
}
|
|
12831
14364
|
} else {
|
|
12832
|
-
const targetDir =
|
|
14365
|
+
const targetDir = path15__default.dirname(absolute);
|
|
12833
14366
|
return {
|
|
12834
14367
|
allowed: false,
|
|
12835
14368
|
reason: `${operation} operations outside project directory are not allowed. Use /allow-path ${targetDir} to grant access.`
|
|
@@ -12838,12 +14371,12 @@ function isPathAllowed(filePath, operation) {
|
|
|
12838
14371
|
}
|
|
12839
14372
|
}
|
|
12840
14373
|
if (operation === "write" || operation === "delete") {
|
|
12841
|
-
const
|
|
14374
|
+
const basename5 = path15__default.basename(absolute);
|
|
12842
14375
|
for (const pattern of SENSITIVE_PATTERNS) {
|
|
12843
|
-
if (pattern.test(
|
|
14376
|
+
if (pattern.test(basename5)) {
|
|
12844
14377
|
return {
|
|
12845
14378
|
allowed: false,
|
|
12846
|
-
reason: `Operation on sensitive file '${
|
|
14379
|
+
reason: `Operation on sensitive file '${basename5}' requires explicit confirmation`
|
|
12847
14380
|
};
|
|
12848
14381
|
}
|
|
12849
14382
|
}
|
|
@@ -12874,7 +14407,7 @@ Examples:
|
|
|
12874
14407
|
async execute({ path: filePath, encoding, maxSize }) {
|
|
12875
14408
|
validatePath(filePath, "read");
|
|
12876
14409
|
try {
|
|
12877
|
-
const absolutePath =
|
|
14410
|
+
const absolutePath = path15__default.resolve(filePath);
|
|
12878
14411
|
const stats = await fs14__default.stat(absolutePath);
|
|
12879
14412
|
const maxBytes = maxSize ?? DEFAULT_MAX_FILE_SIZE;
|
|
12880
14413
|
let truncated = false;
|
|
@@ -12925,7 +14458,7 @@ Examples:
|
|
|
12925
14458
|
async execute({ path: filePath, content, createDirs, dryRun }) {
|
|
12926
14459
|
validatePath(filePath, "write");
|
|
12927
14460
|
try {
|
|
12928
|
-
const absolutePath =
|
|
14461
|
+
const absolutePath = path15__default.resolve(filePath);
|
|
12929
14462
|
let wouldCreate = false;
|
|
12930
14463
|
try {
|
|
12931
14464
|
await fs14__default.access(absolutePath);
|
|
@@ -12941,7 +14474,7 @@ Examples:
|
|
|
12941
14474
|
};
|
|
12942
14475
|
}
|
|
12943
14476
|
if (createDirs) {
|
|
12944
|
-
await fs14__default.mkdir(
|
|
14477
|
+
await fs14__default.mkdir(path15__default.dirname(absolutePath), { recursive: true });
|
|
12945
14478
|
}
|
|
12946
14479
|
await fs14__default.writeFile(absolutePath, content, "utf-8");
|
|
12947
14480
|
const stats = await fs14__default.stat(absolutePath);
|
|
@@ -12979,7 +14512,7 @@ Examples:
|
|
|
12979
14512
|
async execute({ path: filePath, oldText, newText, all, dryRun }) {
|
|
12980
14513
|
validatePath(filePath, "write");
|
|
12981
14514
|
try {
|
|
12982
|
-
const absolutePath =
|
|
14515
|
+
const absolutePath = path15__default.resolve(filePath);
|
|
12983
14516
|
let content = await fs14__default.readFile(absolutePath, "utf-8");
|
|
12984
14517
|
let replacements = 0;
|
|
12985
14518
|
if (all) {
|
|
@@ -13068,7 +14601,7 @@ Examples:
|
|
|
13068
14601
|
}),
|
|
13069
14602
|
async execute({ path: filePath }) {
|
|
13070
14603
|
try {
|
|
13071
|
-
const absolutePath =
|
|
14604
|
+
const absolutePath = path15__default.resolve(filePath);
|
|
13072
14605
|
const stats = await fs14__default.stat(absolutePath);
|
|
13073
14606
|
return {
|
|
13074
14607
|
exists: true,
|
|
@@ -13099,12 +14632,12 @@ Examples:
|
|
|
13099
14632
|
}),
|
|
13100
14633
|
async execute({ path: dirPath, recursive }) {
|
|
13101
14634
|
try {
|
|
13102
|
-
const absolutePath =
|
|
14635
|
+
const absolutePath = path15__default.resolve(dirPath);
|
|
13103
14636
|
const entries = [];
|
|
13104
14637
|
async function listDir(dir, prefix = "") {
|
|
13105
14638
|
const items = await fs14__default.readdir(dir, { withFileTypes: true });
|
|
13106
14639
|
for (const item of items) {
|
|
13107
|
-
const fullPath =
|
|
14640
|
+
const fullPath = path15__default.join(dir, item.name);
|
|
13108
14641
|
const relativePath = prefix ? `${prefix}/${item.name}` : item.name;
|
|
13109
14642
|
if (item.isDirectory()) {
|
|
13110
14643
|
entries.push({ name: relativePath, type: "directory" });
|
|
@@ -13151,7 +14684,7 @@ Examples:
|
|
|
13151
14684
|
}
|
|
13152
14685
|
validatePath(filePath, "delete");
|
|
13153
14686
|
try {
|
|
13154
|
-
const absolutePath =
|
|
14687
|
+
const absolutePath = path15__default.resolve(filePath);
|
|
13155
14688
|
const stats = await fs14__default.stat(absolutePath);
|
|
13156
14689
|
if (stats.isDirectory()) {
|
|
13157
14690
|
if (!recursive) {
|
|
@@ -13167,7 +14700,7 @@ Examples:
|
|
|
13167
14700
|
} catch (error) {
|
|
13168
14701
|
if (error instanceof ToolError) throw error;
|
|
13169
14702
|
if (error.code === "ENOENT") {
|
|
13170
|
-
return { deleted: false, path:
|
|
14703
|
+
return { deleted: false, path: path15__default.resolve(filePath) };
|
|
13171
14704
|
}
|
|
13172
14705
|
throw new FileSystemError(`Failed to delete: ${filePath}`, {
|
|
13173
14706
|
path: filePath,
|
|
@@ -13195,8 +14728,8 @@ Examples:
|
|
|
13195
14728
|
validatePath(source, "read");
|
|
13196
14729
|
validatePath(destination, "write");
|
|
13197
14730
|
try {
|
|
13198
|
-
const srcPath =
|
|
13199
|
-
const destPath =
|
|
14731
|
+
const srcPath = path15__default.resolve(source);
|
|
14732
|
+
const destPath = path15__default.resolve(destination);
|
|
13200
14733
|
if (!overwrite) {
|
|
13201
14734
|
try {
|
|
13202
14735
|
await fs14__default.access(destPath);
|
|
@@ -13212,7 +14745,7 @@ Examples:
|
|
|
13212
14745
|
}
|
|
13213
14746
|
}
|
|
13214
14747
|
}
|
|
13215
|
-
await fs14__default.mkdir(
|
|
14748
|
+
await fs14__default.mkdir(path15__default.dirname(destPath), { recursive: true });
|
|
13216
14749
|
await fs14__default.copyFile(srcPath, destPath);
|
|
13217
14750
|
const stats = await fs14__default.stat(destPath);
|
|
13218
14751
|
return {
|
|
@@ -13248,8 +14781,8 @@ Examples:
|
|
|
13248
14781
|
validatePath(source, "delete");
|
|
13249
14782
|
validatePath(destination, "write");
|
|
13250
14783
|
try {
|
|
13251
|
-
const srcPath =
|
|
13252
|
-
const destPath =
|
|
14784
|
+
const srcPath = path15__default.resolve(source);
|
|
14785
|
+
const destPath = path15__default.resolve(destination);
|
|
13253
14786
|
if (!overwrite) {
|
|
13254
14787
|
try {
|
|
13255
14788
|
await fs14__default.access(destPath);
|
|
@@ -13265,7 +14798,7 @@ Examples:
|
|
|
13265
14798
|
}
|
|
13266
14799
|
}
|
|
13267
14800
|
}
|
|
13268
|
-
await fs14__default.mkdir(
|
|
14801
|
+
await fs14__default.mkdir(path15__default.dirname(destPath), { recursive: true });
|
|
13269
14802
|
await fs14__default.rename(srcPath, destPath);
|
|
13270
14803
|
return {
|
|
13271
14804
|
source: srcPath,
|
|
@@ -13300,10 +14833,10 @@ Examples:
|
|
|
13300
14833
|
}),
|
|
13301
14834
|
async execute({ path: dirPath, depth, showHidden, dirsOnly }) {
|
|
13302
14835
|
try {
|
|
13303
|
-
const absolutePath =
|
|
14836
|
+
const absolutePath = path15__default.resolve(dirPath ?? ".");
|
|
13304
14837
|
let totalFiles = 0;
|
|
13305
14838
|
let totalDirs = 0;
|
|
13306
|
-
const lines = [
|
|
14839
|
+
const lines = [path15__default.basename(absolutePath) + "/"];
|
|
13307
14840
|
async function buildTree(dir, prefix, currentDepth) {
|
|
13308
14841
|
if (currentDepth > (depth ?? 4)) return;
|
|
13309
14842
|
let items = await fs14__default.readdir(dir, { withFileTypes: true });
|
|
@@ -13326,7 +14859,7 @@ Examples:
|
|
|
13326
14859
|
if (item.isDirectory()) {
|
|
13327
14860
|
totalDirs++;
|
|
13328
14861
|
lines.push(`${prefix}${connector}${item.name}/`);
|
|
13329
|
-
await buildTree(
|
|
14862
|
+
await buildTree(path15__default.join(dir, item.name), prefix + childPrefix, currentDepth + 1);
|
|
13330
14863
|
} else {
|
|
13331
14864
|
totalFiles++;
|
|
13332
14865
|
lines.push(`${prefix}${connector}${item.name}`);
|
|
@@ -14287,7 +15820,7 @@ var checkAgentCapabilityTool = defineTool({
|
|
|
14287
15820
|
var simpleAgentTools = [spawnSimpleAgentTool, checkAgentCapabilityTool];
|
|
14288
15821
|
async function detectTestFramework2(cwd) {
|
|
14289
15822
|
try {
|
|
14290
|
-
const pkgPath =
|
|
15823
|
+
const pkgPath = path15__default.join(cwd, "package.json");
|
|
14291
15824
|
const pkgContent = await fs14__default.readFile(pkgPath, "utf-8");
|
|
14292
15825
|
const pkg = JSON.parse(pkgContent);
|
|
14293
15826
|
const deps = {
|
|
@@ -14464,9 +15997,9 @@ Examples:
|
|
|
14464
15997
|
const projectDir = cwd ?? process.cwd();
|
|
14465
15998
|
try {
|
|
14466
15999
|
const coverageLocations = [
|
|
14467
|
-
|
|
14468
|
-
|
|
14469
|
-
|
|
16000
|
+
path15__default.join(projectDir, "coverage", "coverage-summary.json"),
|
|
16001
|
+
path15__default.join(projectDir, "coverage", "coverage-final.json"),
|
|
16002
|
+
path15__default.join(projectDir, ".nyc_output", "coverage-summary.json")
|
|
14470
16003
|
];
|
|
14471
16004
|
for (const location of coverageLocations) {
|
|
14472
16005
|
try {
|
|
@@ -14522,7 +16055,7 @@ Examples:
|
|
|
14522
16055
|
var testTools = [runTestsTool, getCoverageTool, runTestFileTool];
|
|
14523
16056
|
async function detectLinter2(cwd) {
|
|
14524
16057
|
try {
|
|
14525
|
-
const pkgPath =
|
|
16058
|
+
const pkgPath = path15__default.join(cwd, "package.json");
|
|
14526
16059
|
const pkgContent = await fs14__default.readFile(pkgPath, "utf-8");
|
|
14527
16060
|
const pkg = JSON.parse(pkgContent);
|
|
14528
16061
|
const deps = {
|
|
@@ -14710,8 +16243,8 @@ Examples:
|
|
|
14710
16243
|
}
|
|
14711
16244
|
});
|
|
14712
16245
|
async function findSourceFiles(cwd) {
|
|
14713
|
-
const { glob:
|
|
14714
|
-
return
|
|
16246
|
+
const { glob: glob17 } = await import('glob');
|
|
16247
|
+
return glob17("src/**/*.{ts,js,tsx,jsx}", {
|
|
14715
16248
|
cwd,
|
|
14716
16249
|
absolute: true,
|
|
14717
16250
|
ignore: ["**/*.test.*", "**/*.spec.*", "**/node_modules/**"]
|
|
@@ -14785,7 +16318,7 @@ Examples:
|
|
|
14785
16318
|
async execute({ cwd, files, useSnyk }) {
|
|
14786
16319
|
const projectDir = cwd ?? process.cwd();
|
|
14787
16320
|
try {
|
|
14788
|
-
const evaluator =
|
|
16321
|
+
const evaluator = createQualityEvaluatorWithRegistry(projectDir, useSnyk);
|
|
14789
16322
|
const evaluation = await evaluator.evaluate(files);
|
|
14790
16323
|
return evaluation.scores;
|
|
14791
16324
|
} catch (error) {
|
|
@@ -14828,7 +16361,7 @@ Examples:
|
|
|
14828
16361
|
caseSensitive,
|
|
14829
16362
|
wholeWord
|
|
14830
16363
|
}) {
|
|
14831
|
-
const targetPath = searchPath ?
|
|
16364
|
+
const targetPath = searchPath ? path15__default.resolve(searchPath) : process.cwd();
|
|
14832
16365
|
const matches = [];
|
|
14833
16366
|
let filesSearched = 0;
|
|
14834
16367
|
const filesWithMatches = /* @__PURE__ */ new Set();
|
|
@@ -14895,7 +16428,7 @@ Examples:
|
|
|
14895
16428
|
contextAfter.push(lines[j] ?? "");
|
|
14896
16429
|
}
|
|
14897
16430
|
matches.push({
|
|
14898
|
-
file:
|
|
16431
|
+
file: path15__default.relative(process.cwd(), file),
|
|
14899
16432
|
line: i + 1,
|
|
14900
16433
|
column: match.index + 1,
|
|
14901
16434
|
content: line,
|
|
@@ -14946,7 +16479,7 @@ Examples:
|
|
|
14946
16479
|
}),
|
|
14947
16480
|
async execute({ file, pattern, caseSensitive }) {
|
|
14948
16481
|
try {
|
|
14949
|
-
const absolutePath =
|
|
16482
|
+
const absolutePath = path15__default.resolve(file);
|
|
14950
16483
|
const content = await fs14__default.readFile(absolutePath, "utf-8");
|
|
14951
16484
|
const lines = content.split("\n");
|
|
14952
16485
|
const matches = [];
|
|
@@ -15123,7 +16656,7 @@ async function detectPackageManager(cwd) {
|
|
|
15123
16656
|
];
|
|
15124
16657
|
for (const { file, pm } of lockfiles) {
|
|
15125
16658
|
try {
|
|
15126
|
-
await fs14__default.access(
|
|
16659
|
+
await fs14__default.access(path15__default.join(cwd, file));
|
|
15127
16660
|
return pm;
|
|
15128
16661
|
} catch {
|
|
15129
16662
|
}
|
|
@@ -15388,7 +16921,7 @@ ${message}
|
|
|
15388
16921
|
});
|
|
15389
16922
|
try {
|
|
15390
16923
|
try {
|
|
15391
|
-
await fs14__default.access(
|
|
16924
|
+
await fs14__default.access(path15__default.join(projectDir, "Makefile"));
|
|
15392
16925
|
} catch {
|
|
15393
16926
|
throw new ToolError("No Makefile found in directory", { tool: "make" });
|
|
15394
16927
|
}
|
|
@@ -15560,7 +17093,32 @@ CONFIG_PATHS.home;
|
|
|
15560
17093
|
|
|
15561
17094
|
// src/cli/repl/session.ts
|
|
15562
17095
|
init_paths();
|
|
15563
|
-
|
|
17096
|
+
z.object({
|
|
17097
|
+
name: z.string().min(1).max(64),
|
|
17098
|
+
description: z.string().min(1).max(1024),
|
|
17099
|
+
version: z.string().default("1.0.0"),
|
|
17100
|
+
license: z.string().optional(),
|
|
17101
|
+
globs: z.union([z.string(), z.array(z.string())]).optional(),
|
|
17102
|
+
// skills.sh standard fields
|
|
17103
|
+
"disable-model-invocation": z.boolean().optional(),
|
|
17104
|
+
"allowed-tools": z.union([z.string(), z.array(z.string())]).optional(),
|
|
17105
|
+
"argument-hint": z.string().optional(),
|
|
17106
|
+
compatibility: z.string().max(500).optional(),
|
|
17107
|
+
model: z.string().optional(),
|
|
17108
|
+
context: z.enum(["fork", "agent", "inline"]).optional(),
|
|
17109
|
+
// Top-level tags/author (skills.sh style) — also accepted inside metadata
|
|
17110
|
+
tags: z.union([z.string(), z.array(z.string())]).optional(),
|
|
17111
|
+
author: z.string().optional(),
|
|
17112
|
+
// Nested metadata (Coco style)
|
|
17113
|
+
metadata: z.object({
|
|
17114
|
+
author: z.string().optional(),
|
|
17115
|
+
tags: z.array(z.string()).optional(),
|
|
17116
|
+
category: z.string().optional()
|
|
17117
|
+
}).passthrough().optional()
|
|
17118
|
+
});
|
|
17119
|
+
|
|
17120
|
+
// src/cli/repl/session.ts
|
|
17121
|
+
path15__default.dirname(CONFIG_PATHS.trustedTools);
|
|
15564
17122
|
CONFIG_PATHS.trustedTools;
|
|
15565
17123
|
|
|
15566
17124
|
// src/cli/repl/recommended-permissions.ts
|
|
@@ -15862,7 +17420,7 @@ async function enforceRateLimit() {
|
|
|
15862
17420
|
const now = Date.now();
|
|
15863
17421
|
const elapsed = now - lastRequestTime;
|
|
15864
17422
|
if (elapsed < MIN_REQUEST_INTERVAL_MS) {
|
|
15865
|
-
await new Promise((
|
|
17423
|
+
await new Promise((resolve3) => setTimeout(resolve3, MIN_REQUEST_INTERVAL_MS - elapsed));
|
|
15866
17424
|
}
|
|
15867
17425
|
lastRequestTime = Date.now();
|
|
15868
17426
|
}
|
|
@@ -16669,7 +18227,7 @@ var getTerminalWidth = () => process.stdout.columns || 80;
|
|
|
16669
18227
|
function stripAnsi(str) {
|
|
16670
18228
|
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
16671
18229
|
}
|
|
16672
|
-
function
|
|
18230
|
+
function detectLanguage2(filePath) {
|
|
16673
18231
|
const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
|
|
16674
18232
|
const extMap = {
|
|
16675
18233
|
ts: "typescript",
|
|
@@ -16714,7 +18272,7 @@ function renderDiff(diff, options) {
|
|
|
16714
18272
|
}
|
|
16715
18273
|
function renderFileBlock(file, opts) {
|
|
16716
18274
|
const { maxWidth, showLineNumbers, compact } = opts;
|
|
16717
|
-
const lang =
|
|
18275
|
+
const lang = detectLanguage2(file.path);
|
|
16718
18276
|
const contentWidth = Math.max(1, maxWidth - 4);
|
|
16719
18277
|
const typeLabel = file.type === "modified" ? "modified" : file.type === "added" ? "new file" : file.type === "deleted" ? "deleted" : `renamed from ${file.oldPath}`;
|
|
16720
18278
|
const statsLabel = ` +${file.additions} -${file.deletions}`;
|
|
@@ -16862,9 +18420,9 @@ async function fileExists(filePath) {
|
|
|
16862
18420
|
return false;
|
|
16863
18421
|
}
|
|
16864
18422
|
}
|
|
16865
|
-
async function fileExists2(
|
|
18423
|
+
async function fileExists2(path38) {
|
|
16866
18424
|
try {
|
|
16867
|
-
await access(
|
|
18425
|
+
await access(path38);
|
|
16868
18426
|
return true;
|
|
16869
18427
|
} catch {
|
|
16870
18428
|
return false;
|
|
@@ -17119,7 +18677,7 @@ async function checkTestCoverage(diff, cwd) {
|
|
|
17119
18677
|
);
|
|
17120
18678
|
if (!hasTestChange) {
|
|
17121
18679
|
const ext = src.path.match(/\.(ts|tsx|js|jsx)$/)?.[0] ?? ".ts";
|
|
17122
|
-
const testExists = await fileExists(
|
|
18680
|
+
const testExists = await fileExists(path15__default.join(cwd, `${baseName}.test${ext}`)) || await fileExists(path15__default.join(cwd, `${baseName}.spec${ext}`));
|
|
17123
18681
|
if (testExists) {
|
|
17124
18682
|
if (src.additions >= TEST_COVERAGE_LARGE_CHANGE_THRESHOLD) {
|
|
17125
18683
|
findings.push({
|
|
@@ -17338,8 +18896,8 @@ Examples:
|
|
|
17338
18896
|
});
|
|
17339
18897
|
var reviewTools = [reviewCodeTool];
|
|
17340
18898
|
var fs23 = await import('fs/promises');
|
|
17341
|
-
var
|
|
17342
|
-
var { glob:
|
|
18899
|
+
var path25 = await import('path');
|
|
18900
|
+
var { glob: glob14 } = await import('glob');
|
|
17343
18901
|
var DEFAULT_MAX_FILES = 200;
|
|
17344
18902
|
var LANGUAGE_EXTENSIONS = {
|
|
17345
18903
|
typescript: [".ts", ".tsx", ".mts", ".cts"],
|
|
@@ -17363,8 +18921,8 @@ var DEFAULT_EXCLUDES = [
|
|
|
17363
18921
|
"**/*.min.*",
|
|
17364
18922
|
"**/*.d.ts"
|
|
17365
18923
|
];
|
|
17366
|
-
function
|
|
17367
|
-
const ext =
|
|
18924
|
+
function detectLanguage3(filePath) {
|
|
18925
|
+
const ext = path25.extname(filePath).toLowerCase();
|
|
17368
18926
|
for (const [lang, extensions] of Object.entries(LANGUAGE_EXTENSIONS)) {
|
|
17369
18927
|
if (extensions.includes(ext)) return lang;
|
|
17370
18928
|
}
|
|
@@ -17773,7 +19331,7 @@ Examples:
|
|
|
17773
19331
|
}),
|
|
17774
19332
|
async execute({ path: rootPath, include, exclude, languages, maxFiles, depth }) {
|
|
17775
19333
|
const startTime = performance.now();
|
|
17776
|
-
const absPath =
|
|
19334
|
+
const absPath = path25.resolve(rootPath);
|
|
17777
19335
|
try {
|
|
17778
19336
|
const stat2 = await fs23.stat(absPath);
|
|
17779
19337
|
if (!stat2.isDirectory()) {
|
|
@@ -17800,7 +19358,7 @@ Examples:
|
|
|
17800
19358
|
pattern = `**/*{${allExts.join(",")}}`;
|
|
17801
19359
|
}
|
|
17802
19360
|
const excludePatterns = [...DEFAULT_EXCLUDES, ...exclude ?? []];
|
|
17803
|
-
const files = await
|
|
19361
|
+
const files = await glob14(pattern, {
|
|
17804
19362
|
cwd: absPath,
|
|
17805
19363
|
ignore: excludePatterns,
|
|
17806
19364
|
nodir: true,
|
|
@@ -17812,8 +19370,8 @@ Examples:
|
|
|
17812
19370
|
let totalDefinitions = 0;
|
|
17813
19371
|
let exportedSymbols = 0;
|
|
17814
19372
|
for (const file of limitedFiles) {
|
|
17815
|
-
const fullPath =
|
|
17816
|
-
const language =
|
|
19373
|
+
const fullPath = path25.join(absPath, file);
|
|
19374
|
+
const language = detectLanguage3(file);
|
|
17817
19375
|
if (!language) continue;
|
|
17818
19376
|
if (languages && !languages.includes(language)) {
|
|
17819
19377
|
continue;
|
|
@@ -17853,9 +19411,9 @@ Examples:
|
|
|
17853
19411
|
var codebaseMapTools = [codebaseMapTool];
|
|
17854
19412
|
init_paths();
|
|
17855
19413
|
var fs24 = await import('fs/promises');
|
|
17856
|
-
var
|
|
19414
|
+
var path26 = await import('path');
|
|
17857
19415
|
var crypto3 = await import('crypto');
|
|
17858
|
-
var GLOBAL_MEMORIES_DIR =
|
|
19416
|
+
var GLOBAL_MEMORIES_DIR = path26.join(COCO_HOME, "memories");
|
|
17859
19417
|
var PROJECT_MEMORIES_DIR = ".coco/memories";
|
|
17860
19418
|
var DEFAULT_MAX_MEMORIES = 1e3;
|
|
17861
19419
|
async function ensureDir(dirPath) {
|
|
@@ -17866,7 +19424,7 @@ function getMemoriesDir(scope) {
|
|
|
17866
19424
|
}
|
|
17867
19425
|
async function loadIndex(scope) {
|
|
17868
19426
|
const dir = getMemoriesDir(scope);
|
|
17869
|
-
const indexPath =
|
|
19427
|
+
const indexPath = path26.join(dir, "index.json");
|
|
17870
19428
|
try {
|
|
17871
19429
|
const content = await fs24.readFile(indexPath, "utf-8");
|
|
17872
19430
|
return JSON.parse(content);
|
|
@@ -17877,12 +19435,12 @@ async function loadIndex(scope) {
|
|
|
17877
19435
|
async function saveIndex(scope, index) {
|
|
17878
19436
|
const dir = getMemoriesDir(scope);
|
|
17879
19437
|
await ensureDir(dir);
|
|
17880
|
-
const indexPath =
|
|
19438
|
+
const indexPath = path26.join(dir, "index.json");
|
|
17881
19439
|
await fs24.writeFile(indexPath, JSON.stringify(index, null, 2), "utf-8");
|
|
17882
19440
|
}
|
|
17883
19441
|
async function loadMemory(scope, id) {
|
|
17884
19442
|
const dir = getMemoriesDir(scope);
|
|
17885
|
-
const memPath =
|
|
19443
|
+
const memPath = path26.join(dir, `${id}.json`);
|
|
17886
19444
|
try {
|
|
17887
19445
|
const content = await fs24.readFile(memPath, "utf-8");
|
|
17888
19446
|
return JSON.parse(content);
|
|
@@ -17893,7 +19451,7 @@ async function loadMemory(scope, id) {
|
|
|
17893
19451
|
async function saveMemory(scope, memory) {
|
|
17894
19452
|
const dir = getMemoriesDir(scope);
|
|
17895
19453
|
await ensureDir(dir);
|
|
17896
|
-
const memPath =
|
|
19454
|
+
const memPath = path26.join(dir, `${memory.id}.json`);
|
|
17897
19455
|
await fs24.writeFile(memPath, JSON.stringify(memory, null, 2), "utf-8");
|
|
17898
19456
|
}
|
|
17899
19457
|
var createMemoryTool = defineTool({
|
|
@@ -18225,8 +19783,8 @@ Examples:
|
|
|
18225
19783
|
});
|
|
18226
19784
|
var checkpointTools = [createCheckpointTool, restoreCheckpointTool, listCheckpointsTool];
|
|
18227
19785
|
var fs26 = await import('fs/promises');
|
|
18228
|
-
var
|
|
18229
|
-
var { glob:
|
|
19786
|
+
var path27 = await import('path');
|
|
19787
|
+
var { glob: glob15 } = await import('glob');
|
|
18230
19788
|
var INDEX_DIR = ".coco/search-index";
|
|
18231
19789
|
var DEFAULT_CHUNK_SIZE = 20;
|
|
18232
19790
|
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -18351,7 +19909,7 @@ async function getEmbedding(text) {
|
|
|
18351
19909
|
}
|
|
18352
19910
|
async function loadIndex2(indexDir) {
|
|
18353
19911
|
try {
|
|
18354
|
-
const indexPath =
|
|
19912
|
+
const indexPath = path27.join(indexDir, "index.json");
|
|
18355
19913
|
const content = await fs26.readFile(indexPath, "utf-8");
|
|
18356
19914
|
return JSON.parse(content);
|
|
18357
19915
|
} catch {
|
|
@@ -18360,11 +19918,11 @@ async function loadIndex2(indexDir) {
|
|
|
18360
19918
|
}
|
|
18361
19919
|
async function saveIndex2(indexDir, index) {
|
|
18362
19920
|
await fs26.mkdir(indexDir, { recursive: true });
|
|
18363
|
-
const indexPath =
|
|
19921
|
+
const indexPath = path27.join(indexDir, "index.json");
|
|
18364
19922
|
await fs26.writeFile(indexPath, JSON.stringify(index), "utf-8");
|
|
18365
19923
|
}
|
|
18366
19924
|
function isBinary(filePath) {
|
|
18367
|
-
return BINARY_EXTENSIONS.has(
|
|
19925
|
+
return BINARY_EXTENSIONS.has(path27.extname(filePath).toLowerCase());
|
|
18368
19926
|
}
|
|
18369
19927
|
var semanticSearchTool = defineTool({
|
|
18370
19928
|
name: "semantic_search",
|
|
@@ -18389,12 +19947,12 @@ Examples:
|
|
|
18389
19947
|
const effectivePath = rootPath ?? ".";
|
|
18390
19948
|
const effectiveMaxResults = maxResults ?? 10;
|
|
18391
19949
|
const effectiveThreshold = threshold ?? 0.3;
|
|
18392
|
-
const absPath =
|
|
18393
|
-
const indexDir =
|
|
19950
|
+
const absPath = path27.resolve(effectivePath);
|
|
19951
|
+
const indexDir = path27.join(absPath, INDEX_DIR);
|
|
18394
19952
|
let index = reindex ? null : await loadIndex2(indexDir);
|
|
18395
19953
|
if (!index) {
|
|
18396
19954
|
const pattern = include ?? "**/*";
|
|
18397
|
-
const files = await
|
|
19955
|
+
const files = await glob15(pattern, {
|
|
18398
19956
|
cwd: absPath,
|
|
18399
19957
|
ignore: DEFAULT_EXCLUDES2,
|
|
18400
19958
|
nodir: true,
|
|
@@ -18403,7 +19961,7 @@ Examples:
|
|
|
18403
19961
|
const chunks = [];
|
|
18404
19962
|
for (const file of files) {
|
|
18405
19963
|
if (isBinary(file)) continue;
|
|
18406
|
-
const fullPath =
|
|
19964
|
+
const fullPath = path27.join(absPath, file);
|
|
18407
19965
|
try {
|
|
18408
19966
|
const stat2 = await fs26.stat(fullPath);
|
|
18409
19967
|
const content = await fs26.readFile(fullPath, "utf-8");
|
|
@@ -18466,11 +20024,11 @@ Examples:
|
|
|
18466
20024
|
});
|
|
18467
20025
|
var semanticSearchTools = [semanticSearchTool];
|
|
18468
20026
|
var fs27 = await import('fs/promises');
|
|
18469
|
-
var
|
|
18470
|
-
var { glob:
|
|
20027
|
+
var path28 = await import('path');
|
|
20028
|
+
var { glob: glob16 } = await import('glob');
|
|
18471
20029
|
async function parseClassRelationships(rootPath, include) {
|
|
18472
20030
|
const pattern = include ?? "**/*.{ts,tsx,js,jsx}";
|
|
18473
|
-
const files = await
|
|
20031
|
+
const files = await glob16(pattern, {
|
|
18474
20032
|
cwd: rootPath,
|
|
18475
20033
|
ignore: ["**/node_modules/**", "**/dist/**", "**/*.test.*", "**/*.d.ts"],
|
|
18476
20034
|
nodir: true
|
|
@@ -18479,7 +20037,7 @@ async function parseClassRelationships(rootPath, include) {
|
|
|
18479
20037
|
const interfaces = [];
|
|
18480
20038
|
for (const file of files.slice(0, 100)) {
|
|
18481
20039
|
try {
|
|
18482
|
-
const content = await fs27.readFile(
|
|
20040
|
+
const content = await fs27.readFile(path28.join(rootPath, file), "utf-8");
|
|
18483
20041
|
const lines = content.split("\n");
|
|
18484
20042
|
for (let i = 0; i < lines.length; i++) {
|
|
18485
20043
|
const line = lines[i];
|
|
@@ -18605,7 +20163,7 @@ async function generateArchitectureDiagram(rootPath) {
|
|
|
18605
20163
|
const lines = ["graph TD"];
|
|
18606
20164
|
let nodeCount = 0;
|
|
18607
20165
|
let edgeCount = 0;
|
|
18608
|
-
const rootName =
|
|
20166
|
+
const rootName = path28.basename(rootPath);
|
|
18609
20167
|
lines.push(` ROOT["${rootName}"]`);
|
|
18610
20168
|
nodeCount++;
|
|
18611
20169
|
for (const dir of dirs) {
|
|
@@ -18615,7 +20173,7 @@ async function generateArchitectureDiagram(rootPath) {
|
|
|
18615
20173
|
nodeCount++;
|
|
18616
20174
|
edgeCount++;
|
|
18617
20175
|
try {
|
|
18618
|
-
const subEntries = await fs27.readdir(
|
|
20176
|
+
const subEntries = await fs27.readdir(path28.join(rootPath, dir.name), {
|
|
18619
20177
|
withFileTypes: true
|
|
18620
20178
|
});
|
|
18621
20179
|
const subDirs = subEntries.filter(
|
|
@@ -18738,7 +20296,7 @@ Examples:
|
|
|
18738
20296
|
tool: "generate_diagram"
|
|
18739
20297
|
});
|
|
18740
20298
|
}
|
|
18741
|
-
const absPath = rootPath ?
|
|
20299
|
+
const absPath = rootPath ? path28.resolve(rootPath) : process.cwd();
|
|
18742
20300
|
switch (type) {
|
|
18743
20301
|
case "class":
|
|
18744
20302
|
return generateClassDiagram(absPath, include);
|
|
@@ -18800,7 +20358,7 @@ Examples:
|
|
|
18800
20358
|
});
|
|
18801
20359
|
var diagramTools = [generateDiagramTool];
|
|
18802
20360
|
var fs28 = await import('fs/promises');
|
|
18803
|
-
var
|
|
20361
|
+
var path29 = await import('path');
|
|
18804
20362
|
var DEFAULT_MAX_PAGES = 20;
|
|
18805
20363
|
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
18806
20364
|
function parsePageRange(rangeStr, totalPages) {
|
|
@@ -18835,7 +20393,7 @@ Examples:
|
|
|
18835
20393
|
}),
|
|
18836
20394
|
async execute({ path: filePath, pages, maxPages }) {
|
|
18837
20395
|
const startTime = performance.now();
|
|
18838
|
-
const absPath =
|
|
20396
|
+
const absPath = path29.resolve(filePath);
|
|
18839
20397
|
try {
|
|
18840
20398
|
const stat2 = await fs28.stat(absPath);
|
|
18841
20399
|
if (!stat2.isFile()) {
|
|
@@ -18915,7 +20473,7 @@ Examples:
|
|
|
18915
20473
|
});
|
|
18916
20474
|
var pdfTools = [readPdfTool];
|
|
18917
20475
|
var fs29 = await import('fs/promises');
|
|
18918
|
-
var
|
|
20476
|
+
var path30 = await import('path');
|
|
18919
20477
|
var SUPPORTED_FORMATS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
|
|
18920
20478
|
var MAX_IMAGE_SIZE = 20 * 1024 * 1024;
|
|
18921
20479
|
var MIME_TYPES = {
|
|
@@ -18943,15 +20501,15 @@ Examples:
|
|
|
18943
20501
|
async execute({ path: filePath, prompt, provider }) {
|
|
18944
20502
|
const startTime = performance.now();
|
|
18945
20503
|
const effectivePrompt = prompt ?? "Describe this image in detail. If it's code or a UI, identify the key elements.";
|
|
18946
|
-
const absPath =
|
|
20504
|
+
const absPath = path30.resolve(filePath);
|
|
18947
20505
|
const cwd = process.cwd();
|
|
18948
|
-
if (!absPath.startsWith(cwd +
|
|
20506
|
+
if (!absPath.startsWith(cwd + path30.sep) && absPath !== cwd) {
|
|
18949
20507
|
throw new ToolError(
|
|
18950
20508
|
`Path traversal denied: '${filePath}' resolves outside the project directory`,
|
|
18951
20509
|
{ tool: "read_image" }
|
|
18952
20510
|
);
|
|
18953
20511
|
}
|
|
18954
|
-
const ext =
|
|
20512
|
+
const ext = path30.extname(absPath).toLowerCase();
|
|
18955
20513
|
if (!SUPPORTED_FORMATS.has(ext)) {
|
|
18956
20514
|
throw new ToolError(
|
|
18957
20515
|
`Unsupported image format '${ext}'. Supported: ${Array.from(SUPPORTED_FORMATS).join(", ")}`,
|
|
@@ -19093,7 +20651,7 @@ Examples:
|
|
|
19093
20651
|
}
|
|
19094
20652
|
});
|
|
19095
20653
|
var imageTools = [readImageTool];
|
|
19096
|
-
var
|
|
20654
|
+
var path31 = await import('path');
|
|
19097
20655
|
var DANGEROUS_PATTERNS2 = [
|
|
19098
20656
|
/\bDROP\s+(?:TABLE|DATABASE|INDEX|VIEW)\b/i,
|
|
19099
20657
|
/\bTRUNCATE\b/i,
|
|
@@ -19124,7 +20682,7 @@ Examples:
|
|
|
19124
20682
|
async execute({ database, query, params, readonly: isReadonlyParam }) {
|
|
19125
20683
|
const isReadonly = isReadonlyParam ?? true;
|
|
19126
20684
|
const startTime = performance.now();
|
|
19127
|
-
const absPath =
|
|
20685
|
+
const absPath = path31.resolve(database);
|
|
19128
20686
|
if (isReadonly && isDangerousSql(query)) {
|
|
19129
20687
|
throw new ToolError(
|
|
19130
20688
|
"Write operations (INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE) are blocked in readonly mode. Set readonly: false to allow writes.",
|
|
@@ -19197,7 +20755,7 @@ Examples:
|
|
|
19197
20755
|
}),
|
|
19198
20756
|
async execute({ database, table }) {
|
|
19199
20757
|
const startTime = performance.now();
|
|
19200
|
-
const absPath =
|
|
20758
|
+
const absPath = path31.resolve(database);
|
|
19201
20759
|
try {
|
|
19202
20760
|
const { default: Database } = await import('better-sqlite3');
|
|
19203
20761
|
const db = new Database(absPath, { readonly: true, fileMustExist: true });
|
|
@@ -19251,7 +20809,7 @@ Examples:
|
|
|
19251
20809
|
});
|
|
19252
20810
|
var databaseTools = [sqlQueryTool, inspectSchemaTool];
|
|
19253
20811
|
var fs30 = await import('fs/promises');
|
|
19254
|
-
var
|
|
20812
|
+
var path32 = await import('path');
|
|
19255
20813
|
var AnalyzeFileSchema = z.object({
|
|
19256
20814
|
filePath: z.string().describe("Path to file to analyze"),
|
|
19257
20815
|
includeAst: z.boolean().default(false).describe("Include AST in result")
|
|
@@ -19348,8 +20906,8 @@ async function analyzeFile(filePath, includeAst = false) {
|
|
|
19348
20906
|
};
|
|
19349
20907
|
}
|
|
19350
20908
|
async function analyzeDirectory(dirPath) {
|
|
19351
|
-
const { glob:
|
|
19352
|
-
const files = await
|
|
20909
|
+
const { glob: glob17 } = await import('glob');
|
|
20910
|
+
const files = await glob17("**/*.{ts,tsx,js,jsx}", {
|
|
19353
20911
|
cwd: dirPath,
|
|
19354
20912
|
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
|
|
19355
20913
|
absolute: true
|
|
@@ -19361,10 +20919,10 @@ async function analyzeDirectory(dirPath) {
|
|
|
19361
20919
|
try {
|
|
19362
20920
|
const analysis = await analyzeFile(file, false);
|
|
19363
20921
|
totalLines += analysis.lines;
|
|
19364
|
-
const ext =
|
|
20922
|
+
const ext = path32.extname(file);
|
|
19365
20923
|
filesByType[ext] = (filesByType[ext] || 0) + 1;
|
|
19366
20924
|
fileStats.push({
|
|
19367
|
-
file:
|
|
20925
|
+
file: path32.relative(dirPath, file),
|
|
19368
20926
|
lines: analysis.lines,
|
|
19369
20927
|
complexity: analysis.complexity.cyclomatic
|
|
19370
20928
|
});
|
|
@@ -19877,7 +21435,7 @@ var calculateCodeScoreTool = defineTool({
|
|
|
19877
21435
|
});
|
|
19878
21436
|
var smartSuggestionsTools = [suggestImprovementsTool, calculateCodeScoreTool];
|
|
19879
21437
|
var fs32 = await import('fs/promises');
|
|
19880
|
-
var
|
|
21438
|
+
var path33 = await import('path');
|
|
19881
21439
|
var ContextMemoryStore = class {
|
|
19882
21440
|
items = /* @__PURE__ */ new Map();
|
|
19883
21441
|
learnings = /* @__PURE__ */ new Map();
|
|
@@ -19897,7 +21455,7 @@ var ContextMemoryStore = class {
|
|
|
19897
21455
|
}
|
|
19898
21456
|
}
|
|
19899
21457
|
async save() {
|
|
19900
|
-
const dir =
|
|
21458
|
+
const dir = path33.dirname(this.storePath);
|
|
19901
21459
|
await fs32.mkdir(dir, { recursive: true });
|
|
19902
21460
|
const data = {
|
|
19903
21461
|
sessionId: this.sessionId,
|
|
@@ -20071,7 +21629,7 @@ var contextEnhancerTools = [
|
|
|
20071
21629
|
getLearnedPatternsTool
|
|
20072
21630
|
];
|
|
20073
21631
|
var fs33 = await import('fs/promises');
|
|
20074
|
-
var
|
|
21632
|
+
var path34 = await import('path');
|
|
20075
21633
|
async function discoverSkills(skillsDir) {
|
|
20076
21634
|
try {
|
|
20077
21635
|
const files = await fs33.readdir(skillsDir);
|
|
@@ -20087,7 +21645,7 @@ async function loadSkillMetadata(skillPath) {
|
|
|
20087
21645
|
const descMatch = content.match(/@description\s+(.+)/);
|
|
20088
21646
|
const versionMatch = content.match(/@version\s+(\S+)/);
|
|
20089
21647
|
return {
|
|
20090
|
-
name: nameMatch?.[1] ||
|
|
21648
|
+
name: nameMatch?.[1] || path34.basename(skillPath, path34.extname(skillPath)),
|
|
20091
21649
|
description: descMatch?.[1] || "No description",
|
|
20092
21650
|
version: versionMatch?.[1] || "1.0.0",
|
|
20093
21651
|
dependencies: []
|
|
@@ -20131,7 +21689,7 @@ var discoverSkillsTool = defineTool({
|
|
|
20131
21689
|
const { skillsDir } = input;
|
|
20132
21690
|
const skills = await discoverSkills(skillsDir);
|
|
20133
21691
|
const metadata = await Promise.all(
|
|
20134
|
-
skills.map((s) => loadSkillMetadata(
|
|
21692
|
+
skills.map((s) => loadSkillMetadata(path34.join(skillsDir, s)))
|
|
20135
21693
|
);
|
|
20136
21694
|
return {
|
|
20137
21695
|
skillsDir,
|
|
@@ -20622,16 +22180,18 @@ var DANGEROUS_ARG_PATTERNS = [
|
|
|
20622
22180
|
/\beval\s+/,
|
|
20623
22181
|
/\bcurl\s+.*\|\s*(ba)?sh/
|
|
20624
22182
|
];
|
|
20625
|
-
function
|
|
20626
|
-
|
|
22183
|
+
function getSystemOpen(target) {
|
|
22184
|
+
if (process.platform === "darwin") return { cmd: "open", args: [target] };
|
|
22185
|
+
if (isWSL) return { cmd: "cmd.exe", args: ["/c", "start", "", target] };
|
|
22186
|
+
return { cmd: "xdg-open", args: [target] };
|
|
20627
22187
|
}
|
|
20628
22188
|
function hasNullByte2(str) {
|
|
20629
22189
|
return str.includes("\0");
|
|
20630
22190
|
}
|
|
20631
22191
|
function isBlockedPath(absolute) {
|
|
20632
22192
|
for (const blocked of BLOCKED_PATHS2) {
|
|
20633
|
-
const normalizedBlocked =
|
|
20634
|
-
if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked +
|
|
22193
|
+
const normalizedBlocked = path15__default.normalize(blocked);
|
|
22194
|
+
if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path15__default.sep)) {
|
|
20635
22195
|
return blocked;
|
|
20636
22196
|
}
|
|
20637
22197
|
}
|
|
@@ -20689,7 +22249,7 @@ Examples:
|
|
|
20689
22249
|
throw new ToolError("Invalid file path", { tool: "open_file" });
|
|
20690
22250
|
}
|
|
20691
22251
|
const workDir = cwd ?? process.cwd();
|
|
20692
|
-
const absolute =
|
|
22252
|
+
const absolute = path15__default.isAbsolute(filePath) ? path15__default.normalize(filePath) : path15__default.resolve(workDir, filePath);
|
|
20693
22253
|
const blockedBy = isBlockedPath(absolute);
|
|
20694
22254
|
if (blockedBy) {
|
|
20695
22255
|
throw new ToolError(`Access to system path '${blockedBy}' is not allowed`, {
|
|
@@ -20702,8 +22262,8 @@ Examples:
|
|
|
20702
22262
|
throw new ToolError(`File not found: ${absolute}`, { tool: "open_file" });
|
|
20703
22263
|
}
|
|
20704
22264
|
if (mode === "open") {
|
|
20705
|
-
const cmd =
|
|
20706
|
-
await execa(cmd,
|
|
22265
|
+
const { cmd, args: args2 } = getSystemOpen(absolute);
|
|
22266
|
+
await execa(cmd, args2, { timeout: 1e4 });
|
|
20707
22267
|
return {
|
|
20708
22268
|
action: "opened",
|
|
20709
22269
|
path: absolute,
|
|
@@ -20712,14 +22272,14 @@ Examples:
|
|
|
20712
22272
|
};
|
|
20713
22273
|
}
|
|
20714
22274
|
if (isBlockedExecFile(absolute)) {
|
|
20715
|
-
throw new ToolError(`Execution of sensitive file is blocked: ${
|
|
22275
|
+
throw new ToolError(`Execution of sensitive file is blocked: ${path15__default.basename(absolute)}`, {
|
|
20716
22276
|
tool: "open_file"
|
|
20717
22277
|
});
|
|
20718
22278
|
}
|
|
20719
22279
|
if (args.length > 0 && hasDangerousArgs(args)) {
|
|
20720
22280
|
throw new ToolError("Arguments contain dangerous patterns", { tool: "open_file" });
|
|
20721
22281
|
}
|
|
20722
|
-
const ext =
|
|
22282
|
+
const ext = path15__default.extname(absolute);
|
|
20723
22283
|
const interpreter = getInterpreter(ext);
|
|
20724
22284
|
const executable = await isExecutable(absolute);
|
|
20725
22285
|
let command;
|
|
@@ -20732,7 +22292,7 @@ Examples:
|
|
|
20732
22292
|
cmdArgs = [...args];
|
|
20733
22293
|
} else {
|
|
20734
22294
|
throw new ToolError(
|
|
20735
|
-
`Cannot execute '${
|
|
22295
|
+
`Cannot execute '${path15__default.basename(absolute)}': no known interpreter for '${ext || "(no extension)"}' and file is not executable`,
|
|
20736
22296
|
{ tool: "open_file" }
|
|
20737
22297
|
);
|
|
20738
22298
|
}
|
|
@@ -20784,7 +22344,7 @@ Examples:
|
|
|
20784
22344
|
reason: z.string().optional().describe("Why access is needed (shown to user for context)")
|
|
20785
22345
|
}),
|
|
20786
22346
|
async execute({ path: dirPath, reason }) {
|
|
20787
|
-
const absolute =
|
|
22347
|
+
const absolute = path15__default.resolve(dirPath);
|
|
20788
22348
|
if (isWithinAllowedPath(absolute, "read")) {
|
|
20789
22349
|
return {
|
|
20790
22350
|
authorized: true,
|
|
@@ -20793,8 +22353,8 @@ Examples:
|
|
|
20793
22353
|
};
|
|
20794
22354
|
}
|
|
20795
22355
|
for (const blocked of BLOCKED_SYSTEM_PATHS) {
|
|
20796
|
-
const normalizedBlocked =
|
|
20797
|
-
if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked +
|
|
22356
|
+
const normalizedBlocked = path15__default.normalize(blocked);
|
|
22357
|
+
if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path15__default.sep)) {
|
|
20798
22358
|
return {
|
|
20799
22359
|
authorized: false,
|
|
20800
22360
|
path: absolute,
|
|
@@ -20803,7 +22363,7 @@ Examples:
|
|
|
20803
22363
|
}
|
|
20804
22364
|
}
|
|
20805
22365
|
const cwd = process.cwd();
|
|
20806
|
-
if (absolute ===
|
|
22366
|
+
if (absolute === path15__default.normalize(cwd) || absolute.startsWith(path15__default.normalize(cwd) + path15__default.sep)) {
|
|
20807
22367
|
return {
|
|
20808
22368
|
authorized: true,
|
|
20809
22369
|
path: absolute,
|
|
@@ -20827,7 +22387,7 @@ Examples:
|
|
|
20827
22387
|
};
|
|
20828
22388
|
}
|
|
20829
22389
|
const existing = getAllowedPaths();
|
|
20830
|
-
if (existing.some((e) =>
|
|
22390
|
+
if (existing.some((e) => path15__default.normalize(e.path) === path15__default.normalize(absolute))) {
|
|
20831
22391
|
return {
|
|
20832
22392
|
authorized: true,
|
|
20833
22393
|
path: absolute,
|