@corbat-tech/coco 1.9.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +351 -170
- package/dist/cli/index.js +31331 -24231
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +27 -2
- package/dist/index.js +2020 -382
- 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();
|
|
@@ -116,12 +118,26 @@ function getApiKey(provider) {
|
|
|
116
118
|
return process.env["GEMINI_API_KEY"] ?? process.env["GOOGLE_API_KEY"];
|
|
117
119
|
case "kimi":
|
|
118
120
|
return process.env["KIMI_API_KEY"] ?? process.env["MOONSHOT_API_KEY"];
|
|
121
|
+
case "kimi-code":
|
|
122
|
+
return process.env["KIMI_CODE_API_KEY"];
|
|
119
123
|
case "lmstudio":
|
|
120
124
|
return process.env["LMSTUDIO_API_KEY"] ?? "lm-studio";
|
|
121
125
|
case "ollama":
|
|
122
126
|
return process.env["OLLAMA_API_KEY"] ?? "ollama";
|
|
123
127
|
case "codex":
|
|
124
128
|
return void 0;
|
|
129
|
+
case "groq":
|
|
130
|
+
return process.env["GROQ_API_KEY"];
|
|
131
|
+
case "openrouter":
|
|
132
|
+
return process.env["OPENROUTER_API_KEY"];
|
|
133
|
+
case "mistral":
|
|
134
|
+
return process.env["MISTRAL_API_KEY"];
|
|
135
|
+
case "deepseek":
|
|
136
|
+
return process.env["DEEPSEEK_API_KEY"];
|
|
137
|
+
case "together":
|
|
138
|
+
return process.env["TOGETHER_API_KEY"];
|
|
139
|
+
case "huggingface":
|
|
140
|
+
return process.env["HF_TOKEN"] ?? process.env["HUGGINGFACE_API_KEY"];
|
|
125
141
|
default:
|
|
126
142
|
return void 0;
|
|
127
143
|
}
|
|
@@ -134,12 +150,26 @@ function getBaseUrl(provider) {
|
|
|
134
150
|
return process.env["OPENAI_BASE_URL"];
|
|
135
151
|
case "kimi":
|
|
136
152
|
return process.env["KIMI_BASE_URL"] ?? "https://api.moonshot.ai/v1";
|
|
153
|
+
case "kimi-code":
|
|
154
|
+
return process.env["KIMI_CODE_BASE_URL"] ?? "https://api.kimi.com/coding/v1";
|
|
137
155
|
case "lmstudio":
|
|
138
156
|
return process.env["LMSTUDIO_BASE_URL"] ?? "http://localhost:1234/v1";
|
|
139
157
|
case "ollama":
|
|
140
158
|
return process.env["OLLAMA_BASE_URL"] ?? "http://localhost:11434/v1";
|
|
141
159
|
case "codex":
|
|
142
160
|
return "https://chatgpt.com/backend-api/codex/responses";
|
|
161
|
+
case "groq":
|
|
162
|
+
return process.env["GROQ_BASE_URL"] ?? "https://api.groq.com/openai/v1";
|
|
163
|
+
case "openrouter":
|
|
164
|
+
return process.env["OPENROUTER_BASE_URL"] ?? "https://openrouter.ai/api/v1";
|
|
165
|
+
case "mistral":
|
|
166
|
+
return process.env["MISTRAL_BASE_URL"] ?? "https://api.mistral.ai/v1";
|
|
167
|
+
case "deepseek":
|
|
168
|
+
return process.env["DEEPSEEK_BASE_URL"] ?? "https://api.deepseek.com/v1";
|
|
169
|
+
case "together":
|
|
170
|
+
return process.env["TOGETHER_BASE_URL"] ?? "https://api.together.xyz/v1";
|
|
171
|
+
case "huggingface":
|
|
172
|
+
return process.env["HF_BASE_URL"] ?? "https://api-inference.huggingface.co/v1";
|
|
143
173
|
default:
|
|
144
174
|
return void 0;
|
|
145
175
|
}
|
|
@@ -154,27 +184,58 @@ function getDefaultModel(provider) {
|
|
|
154
184
|
return process.env["GEMINI_MODEL"] ?? "gemini-3-flash-preview";
|
|
155
185
|
case "kimi":
|
|
156
186
|
return process.env["KIMI_MODEL"] ?? "kimi-k2.5";
|
|
187
|
+
case "kimi-code":
|
|
188
|
+
return process.env["KIMI_CODE_MODEL"] ?? "kimi-for-coding";
|
|
157
189
|
case "lmstudio":
|
|
158
190
|
return process.env["LMSTUDIO_MODEL"] ?? "local-model";
|
|
159
191
|
case "ollama":
|
|
160
192
|
return process.env["OLLAMA_MODEL"] ?? "llama3.1";
|
|
161
193
|
case "codex":
|
|
162
194
|
return process.env["CODEX_MODEL"] ?? "gpt-5.3-codex";
|
|
195
|
+
case "groq":
|
|
196
|
+
return process.env["GROQ_MODEL"] ?? "llama-3.3-70b-versatile";
|
|
197
|
+
case "openrouter":
|
|
198
|
+
return process.env["OPENROUTER_MODEL"] ?? "anthropic/claude-opus-4-6";
|
|
199
|
+
case "mistral":
|
|
200
|
+
return process.env["MISTRAL_MODEL"] ?? "codestral-latest";
|
|
201
|
+
case "deepseek":
|
|
202
|
+
return process.env["DEEPSEEK_MODEL"] ?? "deepseek-coder";
|
|
203
|
+
case "together":
|
|
204
|
+
return process.env["TOGETHER_MODEL"] ?? "Qwen/Qwen2.5-Coder-32B-Instruct";
|
|
205
|
+
case "huggingface":
|
|
206
|
+
return process.env["HF_MODEL"] ?? "Qwen/Qwen2.5-Coder-32B-Instruct";
|
|
163
207
|
default:
|
|
164
208
|
return "gpt-5.3-codex";
|
|
165
209
|
}
|
|
166
210
|
}
|
|
167
211
|
function getDefaultProvider() {
|
|
168
212
|
const provider = process.env["COCO_PROVIDER"]?.toLowerCase();
|
|
169
|
-
if (provider &&
|
|
213
|
+
if (provider && VALID_PROVIDERS.includes(provider)) {
|
|
170
214
|
return provider;
|
|
171
215
|
}
|
|
172
216
|
return "anthropic";
|
|
173
217
|
}
|
|
218
|
+
var VALID_PROVIDERS;
|
|
174
219
|
var init_env = __esm({
|
|
175
220
|
"src/config/env.ts"() {
|
|
176
221
|
init_paths();
|
|
177
222
|
loadGlobalCocoEnv();
|
|
223
|
+
VALID_PROVIDERS = [
|
|
224
|
+
"anthropic",
|
|
225
|
+
"openai",
|
|
226
|
+
"codex",
|
|
227
|
+
"gemini",
|
|
228
|
+
"kimi",
|
|
229
|
+
"kimi-code",
|
|
230
|
+
"lmstudio",
|
|
231
|
+
"ollama",
|
|
232
|
+
"groq",
|
|
233
|
+
"openrouter",
|
|
234
|
+
"mistral",
|
|
235
|
+
"deepseek",
|
|
236
|
+
"together",
|
|
237
|
+
"huggingface"
|
|
238
|
+
];
|
|
178
239
|
({
|
|
179
240
|
provider: getDefaultProvider(),
|
|
180
241
|
getApiKey,
|
|
@@ -187,10 +248,10 @@ function getAllowedPaths() {
|
|
|
187
248
|
return [...sessionAllowedPaths];
|
|
188
249
|
}
|
|
189
250
|
function isWithinAllowedPath(absolutePath, operation) {
|
|
190
|
-
const normalizedTarget =
|
|
251
|
+
const normalizedTarget = path15__default.normalize(absolutePath);
|
|
191
252
|
for (const entry of sessionAllowedPaths) {
|
|
192
|
-
const normalizedAllowed =
|
|
193
|
-
if (normalizedTarget === normalizedAllowed || normalizedTarget.startsWith(normalizedAllowed +
|
|
253
|
+
const normalizedAllowed = path15__default.normalize(entry.path);
|
|
254
|
+
if (normalizedTarget === normalizedAllowed || normalizedTarget.startsWith(normalizedAllowed + path15__default.sep)) {
|
|
194
255
|
if (operation === "read") return true;
|
|
195
256
|
if (entry.level === "write") return true;
|
|
196
257
|
}
|
|
@@ -198,8 +259,8 @@ function isWithinAllowedPath(absolutePath, operation) {
|
|
|
198
259
|
return false;
|
|
199
260
|
}
|
|
200
261
|
function addAllowedPathToSession(dirPath, level) {
|
|
201
|
-
const absolute =
|
|
202
|
-
if (sessionAllowedPaths.some((e) =>
|
|
262
|
+
const absolute = path15__default.resolve(dirPath);
|
|
263
|
+
if (sessionAllowedPaths.some((e) => path15__default.normalize(e.path) === path15__default.normalize(absolute))) {
|
|
203
264
|
return;
|
|
204
265
|
}
|
|
205
266
|
sessionAllowedPaths.push({
|
|
@@ -210,14 +271,14 @@ function addAllowedPathToSession(dirPath, level) {
|
|
|
210
271
|
}
|
|
211
272
|
async function persistAllowedPath(dirPath, level) {
|
|
212
273
|
if (!currentProjectPath) return;
|
|
213
|
-
const absolute =
|
|
274
|
+
const absolute = path15__default.resolve(dirPath);
|
|
214
275
|
const store = await loadStore();
|
|
215
276
|
if (!store.projects[currentProjectPath]) {
|
|
216
277
|
store.projects[currentProjectPath] = [];
|
|
217
278
|
}
|
|
218
279
|
const entries = store.projects[currentProjectPath];
|
|
219
|
-
const normalized =
|
|
220
|
-
if (entries.some((e) =>
|
|
280
|
+
const normalized = path15__default.normalize(absolute);
|
|
281
|
+
if (entries.some((e) => path15__default.normalize(e.path) === normalized)) {
|
|
221
282
|
return;
|
|
222
283
|
}
|
|
223
284
|
entries.push({
|
|
@@ -237,7 +298,7 @@ async function loadStore() {
|
|
|
237
298
|
}
|
|
238
299
|
async function saveStore(store) {
|
|
239
300
|
try {
|
|
240
|
-
await fs14__default.mkdir(
|
|
301
|
+
await fs14__default.mkdir(path15__default.dirname(STORE_FILE), { recursive: true });
|
|
241
302
|
await fs14__default.writeFile(STORE_FILE, JSON.stringify(store, null, 2), "utf-8");
|
|
242
303
|
} catch {
|
|
243
304
|
}
|
|
@@ -246,7 +307,7 @@ var STORE_FILE, DEFAULT_STORE, sessionAllowedPaths, currentProjectPath;
|
|
|
246
307
|
var init_allowed_paths = __esm({
|
|
247
308
|
"src/tools/allowed-paths.ts"() {
|
|
248
309
|
init_paths();
|
|
249
|
-
STORE_FILE =
|
|
310
|
+
STORE_FILE = path15__default.join(CONFIG_PATHS.home, "allowed-paths.json");
|
|
250
311
|
DEFAULT_STORE = {
|
|
251
312
|
version: 1,
|
|
252
313
|
projects: {}
|
|
@@ -327,7 +388,7 @@ __export(allow_path_prompt_exports, {
|
|
|
327
388
|
promptAllowPath: () => promptAllowPath
|
|
328
389
|
});
|
|
329
390
|
async function promptAllowPath(dirPath) {
|
|
330
|
-
const absolute =
|
|
391
|
+
const absolute = path15__default.resolve(dirPath);
|
|
331
392
|
console.log();
|
|
332
393
|
console.log(chalk3.yellow(" \u26A0 Access denied \u2014 path is outside the project directory"));
|
|
333
394
|
console.log(chalk3.dim(` \u{1F4C1} ${absolute}`));
|
|
@@ -1705,13 +1766,13 @@ function createSpecificationGenerator(llm, config) {
|
|
|
1705
1766
|
return new SpecificationGenerator(llm, config);
|
|
1706
1767
|
}
|
|
1707
1768
|
function getPersistencePaths(projectPath) {
|
|
1708
|
-
const baseDir =
|
|
1769
|
+
const baseDir = path15__default.join(projectPath, ".coco", "spec");
|
|
1709
1770
|
return {
|
|
1710
1771
|
baseDir,
|
|
1711
|
-
sessionFile:
|
|
1712
|
-
specFile:
|
|
1713
|
-
conversationLog:
|
|
1714
|
-
checkpointFile:
|
|
1772
|
+
sessionFile: path15__default.join(baseDir, "discovery-session.json"),
|
|
1773
|
+
specFile: path15__default.join(baseDir, "spec.md"),
|
|
1774
|
+
conversationLog: path15__default.join(baseDir, "conversation.jsonl"),
|
|
1775
|
+
checkpointFile: path15__default.join(baseDir, "checkpoint.json")
|
|
1715
1776
|
};
|
|
1716
1777
|
}
|
|
1717
1778
|
var SessionPersistence = class {
|
|
@@ -3741,7 +3802,7 @@ var OrchestrateExecutor = class {
|
|
|
3741
3802
|
}
|
|
3742
3803
|
async loadSpecification(projectPath) {
|
|
3743
3804
|
try {
|
|
3744
|
-
const jsonPath =
|
|
3805
|
+
const jsonPath = path15__default.join(projectPath, ".coco", "spec", "spec.json");
|
|
3745
3806
|
const jsonContent = await fs14__default.readFile(jsonPath, "utf-8");
|
|
3746
3807
|
return JSON.parse(jsonContent);
|
|
3747
3808
|
} catch {
|
|
@@ -3753,7 +3814,7 @@ var OrchestrateExecutor = class {
|
|
|
3753
3814
|
version: "1.0.0",
|
|
3754
3815
|
generatedAt: /* @__PURE__ */ new Date(),
|
|
3755
3816
|
overview: {
|
|
3756
|
-
name:
|
|
3817
|
+
name: path15__default.basename(projectPath),
|
|
3757
3818
|
description: "Project specification",
|
|
3758
3819
|
goals: [],
|
|
3759
3820
|
targetUsers: ["developers"],
|
|
@@ -3780,52 +3841,52 @@ var OrchestrateExecutor = class {
|
|
|
3780
3841
|
};
|
|
3781
3842
|
}
|
|
3782
3843
|
async saveArchitecture(projectPath, architecture) {
|
|
3783
|
-
const dir =
|
|
3844
|
+
const dir = path15__default.join(projectPath, ".coco", "architecture");
|
|
3784
3845
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
3785
|
-
const mdPath =
|
|
3846
|
+
const mdPath = path15__default.join(dir, "ARCHITECTURE.md");
|
|
3786
3847
|
await fs14__default.writeFile(mdPath, generateArchitectureMarkdown(architecture), "utf-8");
|
|
3787
|
-
const jsonPath =
|
|
3848
|
+
const jsonPath = path15__default.join(dir, "architecture.json");
|
|
3788
3849
|
await fs14__default.writeFile(jsonPath, JSON.stringify(architecture, null, 2), "utf-8");
|
|
3789
3850
|
return mdPath;
|
|
3790
3851
|
}
|
|
3791
3852
|
async saveADRs(projectPath, adrs) {
|
|
3792
|
-
const dir =
|
|
3853
|
+
const dir = path15__default.join(projectPath, ".coco", "architecture", "adrs");
|
|
3793
3854
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
3794
3855
|
const paths = [];
|
|
3795
|
-
const indexPath =
|
|
3856
|
+
const indexPath = path15__default.join(dir, "README.md");
|
|
3796
3857
|
await fs14__default.writeFile(indexPath, generateADRIndexMarkdown(adrs), "utf-8");
|
|
3797
3858
|
paths.push(indexPath);
|
|
3798
3859
|
for (const adr of adrs) {
|
|
3799
3860
|
const filename = getADRFilename(adr);
|
|
3800
|
-
const adrPath =
|
|
3861
|
+
const adrPath = path15__default.join(dir, filename);
|
|
3801
3862
|
await fs14__default.writeFile(adrPath, generateADRMarkdown(adr), "utf-8");
|
|
3802
3863
|
paths.push(adrPath);
|
|
3803
3864
|
}
|
|
3804
3865
|
return paths;
|
|
3805
3866
|
}
|
|
3806
3867
|
async saveBacklog(projectPath, backlogResult) {
|
|
3807
|
-
const dir =
|
|
3868
|
+
const dir = path15__default.join(projectPath, ".coco", "planning");
|
|
3808
3869
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
3809
|
-
const mdPath =
|
|
3870
|
+
const mdPath = path15__default.join(dir, "BACKLOG.md");
|
|
3810
3871
|
await fs14__default.writeFile(mdPath, generateBacklogMarkdown(backlogResult.backlog), "utf-8");
|
|
3811
|
-
const jsonPath =
|
|
3872
|
+
const jsonPath = path15__default.join(dir, "backlog.json");
|
|
3812
3873
|
await fs14__default.writeFile(jsonPath, JSON.stringify(backlogResult, null, 2), "utf-8");
|
|
3813
3874
|
return mdPath;
|
|
3814
3875
|
}
|
|
3815
3876
|
async saveSprint(projectPath, sprint, backlogResult) {
|
|
3816
|
-
const dir =
|
|
3877
|
+
const dir = path15__default.join(projectPath, ".coco", "planning", "sprints");
|
|
3817
3878
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
3818
3879
|
const filename = `${sprint.id}.md`;
|
|
3819
|
-
const sprintPath =
|
|
3880
|
+
const sprintPath = path15__default.join(dir, filename);
|
|
3820
3881
|
await fs14__default.writeFile(sprintPath, generateSprintMarkdown(sprint, backlogResult.backlog), "utf-8");
|
|
3821
|
-
const jsonPath =
|
|
3882
|
+
const jsonPath = path15__default.join(dir, `${sprint.id}.json`);
|
|
3822
3883
|
await fs14__default.writeFile(jsonPath, JSON.stringify(sprint, null, 2), "utf-8");
|
|
3823
3884
|
return sprintPath;
|
|
3824
3885
|
}
|
|
3825
3886
|
async saveDiagram(projectPath, id, mermaid) {
|
|
3826
|
-
const dir =
|
|
3887
|
+
const dir = path15__default.join(projectPath, ".coco", "architecture", "diagrams");
|
|
3827
3888
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
3828
|
-
const diagramPath =
|
|
3889
|
+
const diagramPath = path15__default.join(dir, `${id}.mmd`);
|
|
3829
3890
|
await fs14__default.writeFile(diagramPath, mermaid, "utf-8");
|
|
3830
3891
|
return diagramPath;
|
|
3831
3892
|
}
|
|
@@ -3875,7 +3936,11 @@ var DEFAULT_QUALITY_THRESHOLDS = {
|
|
|
3875
3936
|
target: {
|
|
3876
3937
|
overall: 95,
|
|
3877
3938
|
testCoverage: 90
|
|
3878
|
-
}
|
|
3939
|
+
},
|
|
3940
|
+
convergenceThreshold: 2,
|
|
3941
|
+
maxIterations: 10,
|
|
3942
|
+
minIterations: 2
|
|
3943
|
+
};
|
|
3879
3944
|
async function detectTestFramework(projectPath) {
|
|
3880
3945
|
try {
|
|
3881
3946
|
const pkgPath = join(projectPath, "package.json");
|
|
@@ -3966,10 +4031,10 @@ var CoverageAnalyzer = class {
|
|
|
3966
4031
|
join(this.projectPath, ".coverage", "coverage-summary.json"),
|
|
3967
4032
|
join(this.projectPath, "coverage", "lcov-report", "coverage-summary.json")
|
|
3968
4033
|
];
|
|
3969
|
-
for (const
|
|
4034
|
+
for (const path38 of possiblePaths) {
|
|
3970
4035
|
try {
|
|
3971
|
-
await access(
|
|
3972
|
-
const content = await readFile(
|
|
4036
|
+
await access(path38, constants.R_OK);
|
|
4037
|
+
const content = await readFile(path38, "utf-8");
|
|
3973
4038
|
const report = JSON.parse(content);
|
|
3974
4039
|
return parseCoverageSummary(report);
|
|
3975
4040
|
} catch {
|
|
@@ -4057,7 +4122,7 @@ var SECURITY_PATTERNS = [
|
|
|
4057
4122
|
},
|
|
4058
4123
|
// Command Injection
|
|
4059
4124
|
{
|
|
4060
|
-
regex: /exec\s*\(/
|
|
4125
|
+
regex: /(?:^|[\s;,({])exec\s*\(/gm,
|
|
4061
4126
|
severity: "critical",
|
|
4062
4127
|
type: "Command Injection",
|
|
4063
4128
|
message: "Use of exec() can execute shell commands with user input",
|
|
@@ -4178,6 +4243,26 @@ var SECURITY_PATTERNS = [
|
|
|
4178
4243
|
recommendation: "Validate JSON structure with JSON schema or Zod before parsing."
|
|
4179
4244
|
}
|
|
4180
4245
|
];
|
|
4246
|
+
function computeSecurityScore(vulnerabilities) {
|
|
4247
|
+
let score = 100;
|
|
4248
|
+
for (const vuln of vulnerabilities) {
|
|
4249
|
+
switch (vuln.severity) {
|
|
4250
|
+
case "critical":
|
|
4251
|
+
score -= 25;
|
|
4252
|
+
break;
|
|
4253
|
+
case "high":
|
|
4254
|
+
score -= 10;
|
|
4255
|
+
break;
|
|
4256
|
+
case "medium":
|
|
4257
|
+
score -= 5;
|
|
4258
|
+
break;
|
|
4259
|
+
case "low":
|
|
4260
|
+
score -= 2;
|
|
4261
|
+
break;
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
return Math.max(0, score);
|
|
4265
|
+
}
|
|
4181
4266
|
var PatternSecurityScanner = class {
|
|
4182
4267
|
/**
|
|
4183
4268
|
* Scan files for security vulnerabilities using pattern matching
|
|
@@ -4228,32 +4313,8 @@ var PatternSecurityScanner = class {
|
|
|
4228
4313
|
const lastLine = lines[lines.length - 1] ?? "";
|
|
4229
4314
|
return lastLine.length + 1;
|
|
4230
4315
|
}
|
|
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
4316
|
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);
|
|
4317
|
+
return computeSecurityScore(vulnerabilities);
|
|
4257
4318
|
}
|
|
4258
4319
|
};
|
|
4259
4320
|
var SnykSecurityScanner = class {
|
|
@@ -4309,24 +4370,7 @@ var SnykSecurityScanner = class {
|
|
|
4309
4370
|
}));
|
|
4310
4371
|
}
|
|
4311
4372
|
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);
|
|
4373
|
+
return computeSecurityScore(vulnerabilities);
|
|
4330
4374
|
}
|
|
4331
4375
|
};
|
|
4332
4376
|
var CompositeSecurityScanner = class {
|
|
@@ -4646,6 +4690,24 @@ var BuildVerifier = class {
|
|
|
4646
4690
|
stderr: ""
|
|
4647
4691
|
};
|
|
4648
4692
|
}
|
|
4693
|
+
const SAFE_BUILD_PATTERN = /^(npm|pnpm|yarn|bun)\s+(run\s+)?[\w:.-]+$|^npx\s+tsc(\s+--[\w-]+)*$/;
|
|
4694
|
+
if (!SAFE_BUILD_PATTERN.test(buildCommand.trim())) {
|
|
4695
|
+
return {
|
|
4696
|
+
success: false,
|
|
4697
|
+
errors: [
|
|
4698
|
+
{
|
|
4699
|
+
file: "",
|
|
4700
|
+
line: 0,
|
|
4701
|
+
column: 0,
|
|
4702
|
+
message: `Unsafe build command rejected: ${buildCommand}`
|
|
4703
|
+
}
|
|
4704
|
+
],
|
|
4705
|
+
warnings: [],
|
|
4706
|
+
duration: Date.now() - startTime,
|
|
4707
|
+
stdout: "",
|
|
4708
|
+
stderr: ""
|
|
4709
|
+
};
|
|
4710
|
+
}
|
|
4649
4711
|
const { stdout, stderr } = await execAsync(buildCommand, {
|
|
4650
4712
|
cwd: this.projectPath,
|
|
4651
4713
|
timeout: 12e4,
|
|
@@ -4687,7 +4749,7 @@ var BuildVerifier = class {
|
|
|
4687
4749
|
async verifyTypes() {
|
|
4688
4750
|
const startTime = Date.now();
|
|
4689
4751
|
try {
|
|
4690
|
-
const hasTsConfig = await this.fileExists(
|
|
4752
|
+
const hasTsConfig = await this.fileExists(path15.join(this.projectPath, "tsconfig.json"));
|
|
4691
4753
|
if (!hasTsConfig) {
|
|
4692
4754
|
return {
|
|
4693
4755
|
success: true,
|
|
@@ -4737,7 +4799,7 @@ var BuildVerifier = class {
|
|
|
4737
4799
|
*/
|
|
4738
4800
|
async detectBuildCommand() {
|
|
4739
4801
|
try {
|
|
4740
|
-
const packageJsonPath =
|
|
4802
|
+
const packageJsonPath = path15.join(this.projectPath, "package.json");
|
|
4741
4803
|
const content = await fs14.readFile(packageJsonPath, "utf-8");
|
|
4742
4804
|
const packageJson = JSON.parse(content);
|
|
4743
4805
|
if (packageJson.scripts?.build) {
|
|
@@ -5154,10 +5216,6 @@ function analyzeRobustnessPatterns(ast) {
|
|
|
5154
5216
|
if (currentFunctionHasTryCatch) {
|
|
5155
5217
|
functionsWithTryCatch++;
|
|
5156
5218
|
}
|
|
5157
|
-
if (isFunctionNode) {
|
|
5158
|
-
functions--;
|
|
5159
|
-
functions++;
|
|
5160
|
-
}
|
|
5161
5219
|
insideFunction = previousInsideFunction;
|
|
5162
5220
|
currentFunctionHasTryCatch = previousHasTryCatch;
|
|
5163
5221
|
return;
|
|
@@ -5989,127 +6047,1511 @@ function analyzeMaintainabilityPatterns(ast, filePath) {
|
|
|
5989
6047
|
traverse(child);
|
|
5990
6048
|
}
|
|
5991
6049
|
}
|
|
5992
|
-
}
|
|
6050
|
+
}
|
|
6051
|
+
}
|
|
6052
|
+
traverse(ast);
|
|
6053
|
+
return {
|
|
6054
|
+
lineCount: 0,
|
|
6055
|
+
// Will be set separately from content
|
|
6056
|
+
functionCount,
|
|
6057
|
+
importCount,
|
|
6058
|
+
crossBoundaryImportCount
|
|
6059
|
+
};
|
|
6060
|
+
}
|
|
6061
|
+
function isCrossBoundaryImport(node, _filePath) {
|
|
6062
|
+
if (node.source.type !== "Literal" || typeof node.source.value !== "string") {
|
|
6063
|
+
return false;
|
|
6064
|
+
}
|
|
6065
|
+
const importPath = node.source.value;
|
|
6066
|
+
if (importPath.startsWith("node:")) {
|
|
6067
|
+
return false;
|
|
6068
|
+
}
|
|
6069
|
+
if (!importPath.startsWith(".")) {
|
|
6070
|
+
return false;
|
|
6071
|
+
}
|
|
6072
|
+
if (importPath.startsWith("../")) {
|
|
6073
|
+
return true;
|
|
6074
|
+
}
|
|
6075
|
+
if (importPath.startsWith("./")) {
|
|
6076
|
+
const relativePath = importPath.slice(2);
|
|
6077
|
+
return relativePath.includes("/");
|
|
6078
|
+
}
|
|
6079
|
+
return false;
|
|
6080
|
+
}
|
|
6081
|
+
function countLines(content) {
|
|
6082
|
+
return content.split("\n").length;
|
|
6083
|
+
}
|
|
6084
|
+
var MaintainabilityAnalyzer = class {
|
|
6085
|
+
constructor(projectPath) {
|
|
6086
|
+
this.projectPath = projectPath;
|
|
6087
|
+
}
|
|
6088
|
+
/**
|
|
6089
|
+
* Analyze maintainability of project files
|
|
6090
|
+
*/
|
|
6091
|
+
async analyze(files) {
|
|
6092
|
+
const targetFiles = files ?? await this.findSourceFiles();
|
|
6093
|
+
if (targetFiles.length === 0) {
|
|
6094
|
+
return {
|
|
6095
|
+
score: 100,
|
|
6096
|
+
fileLengthScore: 100,
|
|
6097
|
+
functionCountScore: 100,
|
|
6098
|
+
dependencyCountScore: 100,
|
|
6099
|
+
couplingScore: 100,
|
|
6100
|
+
averageFileLength: 0,
|
|
6101
|
+
averageFunctionsPerFile: 0,
|
|
6102
|
+
averageImportsPerFile: 0,
|
|
6103
|
+
fileCount: 0,
|
|
6104
|
+
details: "No files to analyze"
|
|
6105
|
+
};
|
|
6106
|
+
}
|
|
6107
|
+
let totalLines = 0;
|
|
6108
|
+
let totalFunctions = 0;
|
|
6109
|
+
let totalImports = 0;
|
|
6110
|
+
let totalCrossBoundaryImports = 0;
|
|
6111
|
+
let filesAnalyzed = 0;
|
|
6112
|
+
for (const file of targetFiles) {
|
|
6113
|
+
try {
|
|
6114
|
+
const content = await readFile(file, "utf-8");
|
|
6115
|
+
const lineCount = countLines(content);
|
|
6116
|
+
const ast = parse(content, {
|
|
6117
|
+
loc: true,
|
|
6118
|
+
range: true,
|
|
6119
|
+
jsx: file.endsWith(".tsx") || file.endsWith(".jsx")
|
|
6120
|
+
});
|
|
6121
|
+
const result = analyzeMaintainabilityPatterns(ast, file);
|
|
6122
|
+
totalLines += lineCount;
|
|
6123
|
+
totalFunctions += result.functionCount;
|
|
6124
|
+
totalImports += result.importCount;
|
|
6125
|
+
totalCrossBoundaryImports += result.crossBoundaryImportCount;
|
|
6126
|
+
filesAnalyzed++;
|
|
6127
|
+
} catch {
|
|
6128
|
+
}
|
|
6129
|
+
}
|
|
6130
|
+
const averageFileLength = filesAnalyzed > 0 ? totalLines / filesAnalyzed : 0;
|
|
6131
|
+
const averageFunctionsPerFile = filesAnalyzed > 0 ? totalFunctions / filesAnalyzed : 0;
|
|
6132
|
+
const averageImportsPerFile = filesAnalyzed > 0 ? totalImports / filesAnalyzed : 0;
|
|
6133
|
+
const fileLengthScore = Math.max(0, Math.min(100, 100 - (averageFileLength - 200) * 0.33));
|
|
6134
|
+
const functionCountScore = Math.max(0, Math.min(100, 100 - (averageFunctionsPerFile - 10) * 5));
|
|
6135
|
+
const dependencyCountScore = Math.max(0, Math.min(100, 100 - (averageImportsPerFile - 5) * 5));
|
|
6136
|
+
const crossBoundaryRatio = totalImports > 0 ? totalCrossBoundaryImports / totalImports : 0;
|
|
6137
|
+
const couplingScore = Math.max(0, Math.min(100, 100 - crossBoundaryRatio * 100 * 0.5));
|
|
6138
|
+
const score = Math.round(
|
|
6139
|
+
fileLengthScore * 0.3 + functionCountScore * 0.25 + dependencyCountScore * 0.25 + couplingScore * 0.2
|
|
6140
|
+
);
|
|
6141
|
+
const details = [
|
|
6142
|
+
`${Math.round(averageFileLength)} avg lines/file`,
|
|
6143
|
+
`${averageFunctionsPerFile.toFixed(1)} avg functions/file`,
|
|
6144
|
+
`${averageImportsPerFile.toFixed(1)} avg imports/file`,
|
|
6145
|
+
`${Math.round(crossBoundaryRatio * 100)}% cross-boundary coupling`
|
|
6146
|
+
].join(", ");
|
|
6147
|
+
return {
|
|
6148
|
+
score,
|
|
6149
|
+
fileLengthScore,
|
|
6150
|
+
functionCountScore,
|
|
6151
|
+
dependencyCountScore,
|
|
6152
|
+
couplingScore,
|
|
6153
|
+
averageFileLength,
|
|
6154
|
+
averageFunctionsPerFile,
|
|
6155
|
+
averageImportsPerFile,
|
|
6156
|
+
fileCount: filesAnalyzed,
|
|
6157
|
+
details
|
|
6158
|
+
};
|
|
6159
|
+
}
|
|
6160
|
+
async findSourceFiles() {
|
|
6161
|
+
return glob("**/*.{ts,js,tsx,jsx}", {
|
|
6162
|
+
cwd: this.projectPath,
|
|
6163
|
+
absolute: true,
|
|
6164
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/build/**"]
|
|
6165
|
+
});
|
|
6166
|
+
}
|
|
6167
|
+
};
|
|
6168
|
+
var PROJECT_CONFIG_FILENAME = ".coco.config.json";
|
|
6169
|
+
var QualityWeightsOverrideSchema = z.object({
|
|
6170
|
+
correctness: z.number().min(0).max(1),
|
|
6171
|
+
completeness: z.number().min(0).max(1),
|
|
6172
|
+
robustness: z.number().min(0).max(1),
|
|
6173
|
+
readability: z.number().min(0).max(1),
|
|
6174
|
+
maintainability: z.number().min(0).max(1),
|
|
6175
|
+
complexity: z.number().min(0).max(1),
|
|
6176
|
+
duplication: z.number().min(0).max(1),
|
|
6177
|
+
testCoverage: z.number().min(0).max(1),
|
|
6178
|
+
testQuality: z.number().min(0).max(1),
|
|
6179
|
+
security: z.number().min(0).max(1),
|
|
6180
|
+
documentation: z.number().min(0).max(1),
|
|
6181
|
+
style: z.number().min(0).max(1)
|
|
6182
|
+
}).partial();
|
|
6183
|
+
var ProjectQualityOverridesSchema = z.object({
|
|
6184
|
+
/** Minimum overall score (0–100). Default: 85 */
|
|
6185
|
+
minScore: z.number().min(0).max(100).optional(),
|
|
6186
|
+
/** Minimum test-coverage percentage (0–100). Default: 80 */
|
|
6187
|
+
minCoverage: z.number().min(0).max(100).optional(),
|
|
6188
|
+
/** Maximum convergence iterations. Default: 10 */
|
|
6189
|
+
maxIterations: z.number().min(1).max(50).optional(),
|
|
6190
|
+
/** Required security score (0–100). Default: 100 */
|
|
6191
|
+
securityThreshold: z.number().min(0).max(100).optional(),
|
|
6192
|
+
/** Per-dimension weight overrides */
|
|
6193
|
+
weights: QualityWeightsOverrideSchema.optional(),
|
|
6194
|
+
/** Stored but not yet enforced by analyzers — reserved for a future release. */
|
|
6195
|
+
ignoreRules: z.array(z.string()).optional(),
|
|
6196
|
+
/** Stored but not yet enforced by analyzers — reserved for a future release. */
|
|
6197
|
+
ignoreFiles: z.array(z.string()).optional()
|
|
6198
|
+
});
|
|
6199
|
+
var ProjectAnalyzersConfigSchema = z.object({
|
|
6200
|
+
/** Restrict analysis to these language IDs (default: auto-detect) */
|
|
6201
|
+
enabledLanguages: z.array(z.string()).optional(),
|
|
6202
|
+
/** Java-specific options */
|
|
6203
|
+
java: z.object({
|
|
6204
|
+
/** Minimum line coverage expected from JaCoCo report */
|
|
6205
|
+
minCoverage: z.number().min(0).max(100).optional(),
|
|
6206
|
+
/** Custom path to jacoco.xml relative to project root */
|
|
6207
|
+
reportPath: z.string().optional()
|
|
6208
|
+
}).optional(),
|
|
6209
|
+
/** React-specific options */
|
|
6210
|
+
react: z.object({
|
|
6211
|
+
/** Run accessibility (a11y) checks. Default: true */
|
|
6212
|
+
checkA11y: z.boolean().optional(),
|
|
6213
|
+
/** Enforce React Rules of Hooks. Default: true */
|
|
6214
|
+
checkHooks: z.boolean().optional(),
|
|
6215
|
+
/** Run component-quality checks. Default: true */
|
|
6216
|
+
checkComponents: z.boolean().optional()
|
|
6217
|
+
}).optional()
|
|
6218
|
+
});
|
|
6219
|
+
var ProjectConfigSchema = z.object({
|
|
6220
|
+
/** Human-readable project name */
|
|
6221
|
+
name: z.string().optional(),
|
|
6222
|
+
/** SemVer string for the config schema itself */
|
|
6223
|
+
version: z.string().optional(),
|
|
6224
|
+
/** Short project description */
|
|
6225
|
+
description: z.string().optional(),
|
|
6226
|
+
/**
|
|
6227
|
+
* Primary project language ID.
|
|
6228
|
+
* Used when auto-detection is ambiguous.
|
|
6229
|
+
* @example "typescript" | "java" | "react-typescript"
|
|
6230
|
+
*/
|
|
6231
|
+
language: z.string().optional(),
|
|
6232
|
+
/** Quality threshold and weight overrides */
|
|
6233
|
+
quality: ProjectQualityOverridesSchema.optional(),
|
|
6234
|
+
/** Analyzer-specific settings */
|
|
6235
|
+
analyzers: ProjectAnalyzersConfigSchema.optional(),
|
|
6236
|
+
/**
|
|
6237
|
+
* Path to a base config to inherit from (relative to this file).
|
|
6238
|
+
* Merged shallowly; this file wins on conflicts.
|
|
6239
|
+
* @note extend is resolved only one level deep — chaining (A extends B extends C) is not supported.
|
|
6240
|
+
* @example "../shared/.coco.config.json"
|
|
6241
|
+
*/
|
|
6242
|
+
extend: z.string().optional()
|
|
6243
|
+
});
|
|
6244
|
+
function getProjectConfigPath(projectPath) {
|
|
6245
|
+
return join(resolve(projectPath), PROJECT_CONFIG_FILENAME);
|
|
6246
|
+
}
|
|
6247
|
+
async function loadProjectConfig(projectPath) {
|
|
6248
|
+
const configPath = getProjectConfigPath(projectPath);
|
|
6249
|
+
let raw;
|
|
6250
|
+
try {
|
|
6251
|
+
raw = await readFile(configPath, "utf-8");
|
|
6252
|
+
} catch (err) {
|
|
6253
|
+
if (err.code === "ENOENT") return null;
|
|
6254
|
+
throw err;
|
|
6255
|
+
}
|
|
6256
|
+
const parsed = JSON.parse(raw);
|
|
6257
|
+
const result = ProjectConfigSchema.safeParse(parsed);
|
|
6258
|
+
if (!result.success) {
|
|
6259
|
+
throw new Error(`Invalid ${PROJECT_CONFIG_FILENAME} at ${configPath}: ${result.error.message}`);
|
|
6260
|
+
}
|
|
6261
|
+
let config = result.data;
|
|
6262
|
+
if (config.extend) {
|
|
6263
|
+
const basePath = resolve(dirname(configPath), config.extend);
|
|
6264
|
+
let baseRaw;
|
|
6265
|
+
try {
|
|
6266
|
+
baseRaw = await readFile(basePath, "utf-8");
|
|
6267
|
+
} catch (err) {
|
|
6268
|
+
throw new Error(
|
|
6269
|
+
`Cannot extend "${config.extend}" \u2014 file not found at ${basePath}: ${String(err)}`
|
|
6270
|
+
);
|
|
6271
|
+
}
|
|
6272
|
+
const baseResult = ProjectConfigSchema.safeParse(JSON.parse(baseRaw));
|
|
6273
|
+
if (!baseResult.success) {
|
|
6274
|
+
throw new Error(
|
|
6275
|
+
`Invalid base config at "${config.extend}" (resolved to ${basePath}): ${baseResult.error.message}`
|
|
6276
|
+
);
|
|
6277
|
+
}
|
|
6278
|
+
config = mergeProjectConfigs(baseResult.data, config);
|
|
6279
|
+
}
|
|
6280
|
+
return config;
|
|
6281
|
+
}
|
|
6282
|
+
function mergeProjectConfigs(base, override) {
|
|
6283
|
+
const hasQuality = base.quality !== void 0 || override.quality !== void 0;
|
|
6284
|
+
const hasAnalyzers = base.analyzers !== void 0 || override.analyzers !== void 0;
|
|
6285
|
+
return {
|
|
6286
|
+
...base,
|
|
6287
|
+
...override,
|
|
6288
|
+
quality: hasQuality ? {
|
|
6289
|
+
...base.quality,
|
|
6290
|
+
...override.quality,
|
|
6291
|
+
weights: base.quality?.weights !== void 0 || override.quality?.weights !== void 0 ? { ...base.quality?.weights, ...override.quality?.weights } : void 0,
|
|
6292
|
+
ignoreRules: [
|
|
6293
|
+
...base.quality?.ignoreRules ?? [],
|
|
6294
|
+
...override.quality?.ignoreRules ?? []
|
|
6295
|
+
],
|
|
6296
|
+
ignoreFiles: [
|
|
6297
|
+
...base.quality?.ignoreFiles ?? [],
|
|
6298
|
+
...override.quality?.ignoreFiles ?? []
|
|
6299
|
+
]
|
|
6300
|
+
} : void 0,
|
|
6301
|
+
analyzers: hasAnalyzers ? {
|
|
6302
|
+
...base.analyzers,
|
|
6303
|
+
...override.analyzers,
|
|
6304
|
+
java: base.analyzers?.java !== void 0 || override.analyzers?.java !== void 0 ? { ...base.analyzers?.java, ...override.analyzers?.java } : void 0,
|
|
6305
|
+
react: base.analyzers?.react !== void 0 || override.analyzers?.react !== void 0 ? { ...base.analyzers?.react, ...override.analyzers?.react } : void 0
|
|
6306
|
+
} : void 0
|
|
6307
|
+
};
|
|
6308
|
+
}
|
|
6309
|
+
|
|
6310
|
+
// src/quality/quality-bridge.ts
|
|
6311
|
+
({
|
|
6312
|
+
minScore: DEFAULT_QUALITY_THRESHOLDS.minimum.overall,
|
|
6313
|
+
targetScore: DEFAULT_QUALITY_THRESHOLDS.target.overall,
|
|
6314
|
+
maxIterations: DEFAULT_QUALITY_THRESHOLDS.maxIterations,
|
|
6315
|
+
stableDeltaThreshold: DEFAULT_QUALITY_THRESHOLDS.convergenceThreshold
|
|
6316
|
+
});
|
|
6317
|
+
function thresholdsFromProjectConfig(config) {
|
|
6318
|
+
const q = config.quality;
|
|
6319
|
+
if (!q) return {};
|
|
6320
|
+
const result = {};
|
|
6321
|
+
const hasMinimum = q.minScore !== void 0 || q.minCoverage !== void 0 || q.securityThreshold !== void 0;
|
|
6322
|
+
if (hasMinimum) {
|
|
6323
|
+
result.minimum = {
|
|
6324
|
+
overall: q.minScore ?? DEFAULT_QUALITY_THRESHOLDS.minimum.overall,
|
|
6325
|
+
testCoverage: q.minCoverage ?? DEFAULT_QUALITY_THRESHOLDS.minimum.testCoverage,
|
|
6326
|
+
security: q.securityThreshold ?? DEFAULT_QUALITY_THRESHOLDS.minimum.security
|
|
6327
|
+
};
|
|
6328
|
+
}
|
|
6329
|
+
if (q.maxIterations !== void 0) {
|
|
6330
|
+
result.maxIterations = q.maxIterations;
|
|
6331
|
+
result.convergenceThreshold = DEFAULT_QUALITY_THRESHOLDS.convergenceThreshold;
|
|
6332
|
+
}
|
|
6333
|
+
return result;
|
|
6334
|
+
}
|
|
6335
|
+
function mergeThresholds(base, overrides) {
|
|
6336
|
+
return {
|
|
6337
|
+
...base,
|
|
6338
|
+
...overrides,
|
|
6339
|
+
minimum: overrides.minimum ? { ...base.minimum, ...overrides.minimum } : base.minimum,
|
|
6340
|
+
target: overrides.target ? { ...base.target, ...overrides.target } : base.target
|
|
6341
|
+
};
|
|
6342
|
+
}
|
|
6343
|
+
function resolvedThresholds(projectConfig) {
|
|
6344
|
+
if (!projectConfig) return DEFAULT_QUALITY_THRESHOLDS;
|
|
6345
|
+
return mergeThresholds(DEFAULT_QUALITY_THRESHOLDS, thresholdsFromProjectConfig(projectConfig));
|
|
6346
|
+
}
|
|
6347
|
+
function weightsFromProjectConfig(config) {
|
|
6348
|
+
const overrides = config.quality?.weights;
|
|
6349
|
+
if (!overrides || Object.keys(overrides).length === 0) {
|
|
6350
|
+
return DEFAULT_QUALITY_WEIGHTS;
|
|
6351
|
+
}
|
|
6352
|
+
const merged = { ...DEFAULT_QUALITY_WEIGHTS, ...overrides };
|
|
6353
|
+
const total = Object.values(merged).reduce((s, v) => s + v, 0);
|
|
6354
|
+
if (total === 0) return DEFAULT_QUALITY_WEIGHTS;
|
|
6355
|
+
return Object.fromEntries(
|
|
6356
|
+
Object.entries(merged).map(([k, v]) => [k, v / total])
|
|
6357
|
+
);
|
|
6358
|
+
}
|
|
6359
|
+
function resolvedWeights(projectConfig) {
|
|
6360
|
+
if (!projectConfig) return DEFAULT_QUALITY_WEIGHTS;
|
|
6361
|
+
return weightsFromProjectConfig(projectConfig);
|
|
6362
|
+
}
|
|
6363
|
+
var EXTENSION_MAP = {
|
|
6364
|
+
".ts": "typescript",
|
|
6365
|
+
".d.ts": "typescript",
|
|
6366
|
+
".tsx": "react-typescript",
|
|
6367
|
+
".js": "javascript",
|
|
6368
|
+
".mjs": "javascript",
|
|
6369
|
+
".cjs": "javascript",
|
|
6370
|
+
".jsx": "react-javascript",
|
|
6371
|
+
".java": "java",
|
|
6372
|
+
".py": "python",
|
|
6373
|
+
".go": "go",
|
|
6374
|
+
".rs": "rust"
|
|
6375
|
+
};
|
|
6376
|
+
function detectLanguage(filePath, content) {
|
|
6377
|
+
if (!filePath) return "unknown";
|
|
6378
|
+
const ext = getFileExtension(filePath);
|
|
6379
|
+
const langByExt = EXTENSION_MAP[ext];
|
|
6380
|
+
if (!langByExt) return "unknown";
|
|
6381
|
+
return langByExt;
|
|
6382
|
+
}
|
|
6383
|
+
function detectProjectLanguage(files) {
|
|
6384
|
+
if (!files.length) {
|
|
6385
|
+
return { language: "unknown", confidence: 0, evidence: [] };
|
|
6386
|
+
}
|
|
6387
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6388
|
+
let totalSourceFiles = 0;
|
|
6389
|
+
for (const file of files) {
|
|
6390
|
+
const lang = detectLanguage(file);
|
|
6391
|
+
if (lang !== "unknown") {
|
|
6392
|
+
counts.set(lang, (counts.get(lang) ?? 0) + 1);
|
|
6393
|
+
totalSourceFiles++;
|
|
6394
|
+
}
|
|
6395
|
+
}
|
|
6396
|
+
if (totalSourceFiles === 0) {
|
|
6397
|
+
return { language: "unknown", confidence: 0, evidence: [] };
|
|
6398
|
+
}
|
|
6399
|
+
let maxCount = 0;
|
|
6400
|
+
let dominant = "unknown";
|
|
6401
|
+
for (const [lang, count] of counts) {
|
|
6402
|
+
if (count > maxCount) {
|
|
6403
|
+
maxCount = count;
|
|
6404
|
+
dominant = lang;
|
|
6405
|
+
}
|
|
6406
|
+
}
|
|
6407
|
+
const confidence = maxCount / totalSourceFiles;
|
|
6408
|
+
const evidence = buildEvidence(dominant, counts, totalSourceFiles, files);
|
|
6409
|
+
const tsxCount = counts.get("react-typescript") ?? 0;
|
|
6410
|
+
const tsCount = counts.get("typescript") ?? 0;
|
|
6411
|
+
if (dominant === "typescript" && tsxCount > 0 && tsxCount >= tsCount * 0.3) {
|
|
6412
|
+
return {
|
|
6413
|
+
language: "react-typescript",
|
|
6414
|
+
confidence,
|
|
6415
|
+
evidence: [...evidence, `${tsxCount} React (.tsx) files detected`]
|
|
6416
|
+
};
|
|
6417
|
+
}
|
|
6418
|
+
return { language: dominant, confidence, evidence };
|
|
6419
|
+
}
|
|
6420
|
+
function getFileExtension(filePath) {
|
|
6421
|
+
const base = path15.basename(filePath);
|
|
6422
|
+
if (base.endsWith(".d.ts")) return ".d.ts";
|
|
6423
|
+
return path15.extname(filePath).toLowerCase();
|
|
6424
|
+
}
|
|
6425
|
+
function buildEvidence(dominant, counts, totalSourceFiles, files) {
|
|
6426
|
+
const evidence = [];
|
|
6427
|
+
const dominantCount = counts.get(dominant) ?? 0;
|
|
6428
|
+
evidence.push(`${dominantCount} of ${totalSourceFiles} source files are ${dominant}`);
|
|
6429
|
+
const configFiles = ["tsconfig.json", "pom.xml", "build.gradle", "Cargo.toml", "go.mod"];
|
|
6430
|
+
for (const cfg of configFiles) {
|
|
6431
|
+
if (files.some((f) => path15.basename(f) === cfg)) {
|
|
6432
|
+
evidence.push(`Found ${cfg}`);
|
|
6433
|
+
}
|
|
6434
|
+
}
|
|
6435
|
+
return evidence;
|
|
6436
|
+
}
|
|
6437
|
+
async function findJavaFiles(projectPath, options) {
|
|
6438
|
+
const { includeTests = true, srcPattern = "**/*.java" } = options ?? {};
|
|
6439
|
+
const ignore = ["**/node_modules/**", "**/target/**", "**/build/**"];
|
|
6440
|
+
if (!includeTests) {
|
|
6441
|
+
ignore.push("**/*Test*.java", "**/*Spec*.java");
|
|
6442
|
+
}
|
|
6443
|
+
return glob(srcPattern, {
|
|
6444
|
+
cwd: projectPath,
|
|
6445
|
+
absolute: true,
|
|
6446
|
+
ignore
|
|
6447
|
+
});
|
|
6448
|
+
}
|
|
6449
|
+
var BRANCH_KEYWORDS = /\b(if|else if|for|while|do|case|catch|&&|\|\||\?)\b/g;
|
|
6450
|
+
var METHOD_PATTERN = /(?:public|private|protected|static|final|synchronized|abstract)\s+[\w<>[\]]+\s+\w+\s*\([^)]*\)\s*(?:throws\s+[\w,\s]+)?\s*\{/g;
|
|
6451
|
+
var JavaComplexityAnalyzer = class {
|
|
6452
|
+
constructor(projectPath) {
|
|
6453
|
+
this.projectPath = projectPath;
|
|
6454
|
+
}
|
|
6455
|
+
async analyze(files) {
|
|
6456
|
+
const javaFiles = files ?? await findJavaFiles(this.projectPath);
|
|
6457
|
+
if (!javaFiles.length) {
|
|
6458
|
+
return { score: 100, totalMethods: 0, averageComplexity: 1, complexMethods: [] };
|
|
6459
|
+
}
|
|
6460
|
+
const fileContents = await Promise.all(
|
|
6461
|
+
javaFiles.map(async (f) => ({
|
|
6462
|
+
path: f,
|
|
6463
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6464
|
+
}))
|
|
6465
|
+
);
|
|
6466
|
+
return this.analyzeContent(fileContents);
|
|
6467
|
+
}
|
|
6468
|
+
analyzeContent(files) {
|
|
6469
|
+
if (!files.length) {
|
|
6470
|
+
return { score: 100, totalMethods: 0, averageComplexity: 1, complexMethods: [] };
|
|
6471
|
+
}
|
|
6472
|
+
let totalComplexity = 0;
|
|
6473
|
+
let totalMethods = 0;
|
|
6474
|
+
const complexMethods = [];
|
|
6475
|
+
for (const { path: filePath, content } of files) {
|
|
6476
|
+
const methods = this.extractMethodBlocks(content);
|
|
6477
|
+
for (const method of methods) {
|
|
6478
|
+
const complexity = this.calculateComplexity(method.body);
|
|
6479
|
+
totalComplexity += complexity;
|
|
6480
|
+
totalMethods++;
|
|
6481
|
+
if (complexity > 10) {
|
|
6482
|
+
complexMethods.push({ method: method.name, file: filePath, complexity });
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
const averageComplexity = totalMethods > 0 ? totalComplexity / totalMethods : 1;
|
|
6487
|
+
const score = Math.max(0, Math.min(100, Math.round(100 - (averageComplexity - 1) * 6.5)));
|
|
6488
|
+
return { score, totalMethods, averageComplexity, complexMethods };
|
|
6489
|
+
}
|
|
6490
|
+
extractMethodBlocks(content) {
|
|
6491
|
+
const methods = [];
|
|
6492
|
+
const methodRegex = new RegExp(METHOD_PATTERN.source, "g");
|
|
6493
|
+
let match;
|
|
6494
|
+
while ((match = methodRegex.exec(content)) !== null) {
|
|
6495
|
+
const nameMatch = /\s(\w+)\s*\(/.exec(match[0]);
|
|
6496
|
+
const methodName = nameMatch ? nameMatch[1] ?? "unknown" : "unknown";
|
|
6497
|
+
const body = this.extractBlock(content, match.index + match[0].length - 1);
|
|
6498
|
+
methods.push({ name: methodName, body });
|
|
6499
|
+
}
|
|
6500
|
+
return methods;
|
|
6501
|
+
}
|
|
6502
|
+
extractBlock(content, openBraceIndex) {
|
|
6503
|
+
let depth = 1;
|
|
6504
|
+
let i = openBraceIndex + 1;
|
|
6505
|
+
while (i < content.length && depth > 0) {
|
|
6506
|
+
if (content[i] === "{") depth++;
|
|
6507
|
+
else if (content[i] === "}") depth--;
|
|
6508
|
+
i++;
|
|
6509
|
+
}
|
|
6510
|
+
return content.slice(openBraceIndex, i);
|
|
6511
|
+
}
|
|
6512
|
+
calculateComplexity(body) {
|
|
6513
|
+
const matches = body.match(BRANCH_KEYWORDS) ?? [];
|
|
6514
|
+
return 1 + matches.length;
|
|
6515
|
+
}
|
|
6516
|
+
};
|
|
6517
|
+
var JAVA_SECURITY_PATTERNS = [
|
|
6518
|
+
// SQL Injection — string concatenation in any execute/executeQuery/executeUpdate call
|
|
6519
|
+
{
|
|
6520
|
+
regex: /\.execute[A-Za-z]*\s*\(\s*["'][^"']*["']\s*\+/,
|
|
6521
|
+
severity: "critical",
|
|
6522
|
+
type: "SQL Injection",
|
|
6523
|
+
description: "String concatenation in SQL query \u2014 vulnerable to injection",
|
|
6524
|
+
recommendation: "Use PreparedStatement with parameterized queries",
|
|
6525
|
+
cwe: "CWE-89"
|
|
6526
|
+
},
|
|
6527
|
+
{
|
|
6528
|
+
regex: /createQuery\s*\(\s*["'].*\+|createNativeQuery\s*\(\s*["'].*\+/,
|
|
6529
|
+
severity: "critical",
|
|
6530
|
+
type: "SQL Injection (JPQL)",
|
|
6531
|
+
description: "String concatenation in JPQL/native query",
|
|
6532
|
+
recommendation: "Use named parameters with setParameter()",
|
|
6533
|
+
cwe: "CWE-89"
|
|
6534
|
+
},
|
|
6535
|
+
// Hardcoded Credentials
|
|
6536
|
+
{
|
|
6537
|
+
regex: /(?:password|passwd|secret|apiKey|api_key)\s*=\s*["'][^"']{4,}["']/i,
|
|
6538
|
+
severity: "high",
|
|
6539
|
+
type: "Hardcoded Credential",
|
|
6540
|
+
description: "Hardcoded credential or secret detected",
|
|
6541
|
+
recommendation: "Store credentials in environment variables or a secrets manager",
|
|
6542
|
+
cwe: "CWE-798"
|
|
6543
|
+
},
|
|
6544
|
+
// Unsafe Deserialization
|
|
6545
|
+
{
|
|
6546
|
+
regex: /new\s+(?:java\.io\.)?ObjectInputStream/,
|
|
6547
|
+
severity: "high",
|
|
6548
|
+
type: "Unsafe Deserialization",
|
|
6549
|
+
description: "Unsafe Java deserialization can lead to RCE",
|
|
6550
|
+
recommendation: "Use safer serialization formats (JSON) or whitelist classes",
|
|
6551
|
+
cwe: "CWE-502"
|
|
6552
|
+
},
|
|
6553
|
+
// Path Traversal
|
|
6554
|
+
{
|
|
6555
|
+
regex: /new\s+(?:java\.io\.)?File\s*\(\s*(?:request\.|user|input)/,
|
|
6556
|
+
severity: "high",
|
|
6557
|
+
type: "Path Traversal",
|
|
6558
|
+
description: "File path constructed from user input",
|
|
6559
|
+
recommendation: "Sanitize and validate file paths; use Paths.get() with canonical path check",
|
|
6560
|
+
cwe: "CWE-22"
|
|
6561
|
+
},
|
|
6562
|
+
// Command Injection
|
|
6563
|
+
{
|
|
6564
|
+
regex: /Runtime\.getRuntime\(\)\.exec\s*\(\s*[^"]/,
|
|
6565
|
+
severity: "critical",
|
|
6566
|
+
type: "Command Injection",
|
|
6567
|
+
description: "Dynamic command execution \u2014 vulnerable to injection",
|
|
6568
|
+
recommendation: "Use ProcessBuilder with a fixed command array",
|
|
6569
|
+
cwe: "CWE-78"
|
|
6570
|
+
},
|
|
6571
|
+
// XXE
|
|
6572
|
+
{
|
|
6573
|
+
regex: /DocumentBuilderFactory\s*\.\s*newInstance\s*\(\s*\)/,
|
|
6574
|
+
severity: "high",
|
|
6575
|
+
type: "XML External Entity (XXE)",
|
|
6576
|
+
description: "XML parsing without disabling external entities",
|
|
6577
|
+
recommendation: "Disable external entities: factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)",
|
|
6578
|
+
cwe: "CWE-611"
|
|
6579
|
+
},
|
|
6580
|
+
// Insecure Random
|
|
6581
|
+
{
|
|
6582
|
+
regex: /new\s+(?:java\.util\.)?Random\s*\(\s*\)(?!\s*\/\/.*secure)/,
|
|
6583
|
+
severity: "medium",
|
|
6584
|
+
type: "Insecure Random",
|
|
6585
|
+
description: "java.util.Random is not cryptographically secure",
|
|
6586
|
+
recommendation: "Use SecureRandom for security-sensitive operations",
|
|
6587
|
+
cwe: "CWE-338"
|
|
6588
|
+
}
|
|
6589
|
+
];
|
|
6590
|
+
var JavaSecurityAnalyzer = class {
|
|
6591
|
+
constructor(projectPath) {
|
|
6592
|
+
this.projectPath = projectPath;
|
|
6593
|
+
}
|
|
6594
|
+
async analyze(files) {
|
|
6595
|
+
const javaFiles = files ?? await findJavaFiles(this.projectPath, { includeTests: false });
|
|
6596
|
+
if (!javaFiles.length) {
|
|
6597
|
+
return { score: 100, vulnerabilities: [] };
|
|
6598
|
+
}
|
|
6599
|
+
const fileContents = await Promise.all(
|
|
6600
|
+
javaFiles.map(async (f) => ({
|
|
6601
|
+
path: f,
|
|
6602
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6603
|
+
}))
|
|
6604
|
+
);
|
|
6605
|
+
return this.analyzeContent(fileContents);
|
|
6606
|
+
}
|
|
6607
|
+
analyzeContent(files) {
|
|
6608
|
+
if (!files.length) return { score: 100, vulnerabilities: [] };
|
|
6609
|
+
const vulnerabilities = [];
|
|
6610
|
+
for (const { path: filePath, content } of files) {
|
|
6611
|
+
const lines = content.split("\n");
|
|
6612
|
+
for (const pattern of JAVA_SECURITY_PATTERNS) {
|
|
6613
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6614
|
+
const line = lines[i] ?? "";
|
|
6615
|
+
if (pattern.regex.test(line)) {
|
|
6616
|
+
vulnerabilities.push({
|
|
6617
|
+
severity: pattern.severity,
|
|
6618
|
+
type: pattern.type,
|
|
6619
|
+
file: filePath,
|
|
6620
|
+
line: i + 1,
|
|
6621
|
+
description: pattern.description,
|
|
6622
|
+
recommendation: pattern.recommendation,
|
|
6623
|
+
cwe: pattern.cwe
|
|
6624
|
+
});
|
|
6625
|
+
}
|
|
6626
|
+
}
|
|
6627
|
+
}
|
|
6628
|
+
}
|
|
6629
|
+
const score = this.calculateScore(vulnerabilities);
|
|
6630
|
+
return { score, vulnerabilities };
|
|
6631
|
+
}
|
|
6632
|
+
calculateScore(vulns) {
|
|
6633
|
+
let score = 100;
|
|
6634
|
+
for (const v of vulns) {
|
|
6635
|
+
switch (v.severity) {
|
|
6636
|
+
case "critical":
|
|
6637
|
+
score -= 30;
|
|
6638
|
+
break;
|
|
6639
|
+
case "high":
|
|
6640
|
+
score -= 15;
|
|
6641
|
+
break;
|
|
6642
|
+
case "medium":
|
|
6643
|
+
score -= 7;
|
|
6644
|
+
break;
|
|
6645
|
+
case "low":
|
|
6646
|
+
score -= 3;
|
|
6647
|
+
break;
|
|
6648
|
+
}
|
|
6649
|
+
}
|
|
6650
|
+
return Math.max(0, score);
|
|
6651
|
+
}
|
|
6652
|
+
};
|
|
6653
|
+
var MAX_LINE_LENGTH = 120;
|
|
6654
|
+
var JavaStyleAnalyzer = class {
|
|
6655
|
+
constructor(projectPath) {
|
|
6656
|
+
this.projectPath = projectPath;
|
|
6657
|
+
}
|
|
6658
|
+
async analyze(files) {
|
|
6659
|
+
const javaFiles = files ?? await findJavaFiles(this.projectPath);
|
|
6660
|
+
if (!javaFiles.length) return { score: 100, violations: [] };
|
|
6661
|
+
const fileContents = await Promise.all(
|
|
6662
|
+
javaFiles.map(async (f) => ({
|
|
6663
|
+
path: f,
|
|
6664
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6665
|
+
}))
|
|
6666
|
+
);
|
|
6667
|
+
return this.analyzeContent(fileContents);
|
|
6668
|
+
}
|
|
6669
|
+
analyzeContent(files) {
|
|
6670
|
+
if (!files.length) return { score: 100, violations: [] };
|
|
6671
|
+
const violations = [];
|
|
6672
|
+
for (const { path: filePath, content } of files) {
|
|
6673
|
+
violations.push(...this.checkFile(filePath, content));
|
|
6674
|
+
}
|
|
6675
|
+
const deduction = violations.reduce((sum, v) => sum + (v.severity === "error" ? 10 : 5), 0);
|
|
6676
|
+
const score = Math.max(0, 100 - deduction);
|
|
6677
|
+
return { score, violations };
|
|
6678
|
+
}
|
|
6679
|
+
checkFile(filePath, content) {
|
|
6680
|
+
const violations = [];
|
|
6681
|
+
const lines = content.split("\n");
|
|
6682
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6683
|
+
const line = lines[i] ?? "";
|
|
6684
|
+
const lineNum = i + 1;
|
|
6685
|
+
if (line.length > MAX_LINE_LENGTH) {
|
|
6686
|
+
violations.push({
|
|
6687
|
+
rule: "LineLength",
|
|
6688
|
+
file: filePath,
|
|
6689
|
+
line: lineNum,
|
|
6690
|
+
message: `Line length ${line.length} exceeds ${MAX_LINE_LENGTH} characters`,
|
|
6691
|
+
severity: "warning"
|
|
6692
|
+
});
|
|
6693
|
+
}
|
|
6694
|
+
const classMatch = /^(?:public|private|protected)?\s*(?:class|interface|enum|record)\s+([a-z]\w*)/.exec(line);
|
|
6695
|
+
if (classMatch) {
|
|
6696
|
+
violations.push({
|
|
6697
|
+
rule: "TypeName",
|
|
6698
|
+
file: filePath,
|
|
6699
|
+
line: lineNum,
|
|
6700
|
+
message: `Type name '${classMatch[1]}' should start with uppercase letter (PascalCase)`,
|
|
6701
|
+
severity: "error"
|
|
6702
|
+
});
|
|
6703
|
+
}
|
|
6704
|
+
const methodMatch = /\b(?:public|private|protected|static)\s+(?!class|interface|enum|record|new\b)(?:void|[\w<>[\]]+)\s+([A-Z]\w*)\s*\(/.exec(
|
|
6705
|
+
line
|
|
6706
|
+
);
|
|
6707
|
+
if (methodMatch && !line.trim().startsWith("class") && !line.includes("class ")) {
|
|
6708
|
+
violations.push({
|
|
6709
|
+
rule: "MethodName",
|
|
6710
|
+
file: filePath,
|
|
6711
|
+
line: lineNum,
|
|
6712
|
+
message: `Method name '${methodMatch[1]}' should start with lowercase letter (camelCase)`,
|
|
6713
|
+
severity: "error"
|
|
6714
|
+
});
|
|
6715
|
+
}
|
|
6716
|
+
const constantMatch = /\bpublic\s+static\s+final\s+\w+\s+([a-z][a-zA-Z]+)\s*=/.exec(line);
|
|
6717
|
+
if (constantMatch) {
|
|
6718
|
+
violations.push({
|
|
6719
|
+
rule: "ConstantName",
|
|
6720
|
+
file: filePath,
|
|
6721
|
+
line: lineNum,
|
|
6722
|
+
message: `Constant '${constantMatch[1]}' should be UPPER_SNAKE_CASE`,
|
|
6723
|
+
severity: "warning"
|
|
6724
|
+
});
|
|
6725
|
+
}
|
|
6726
|
+
const paramsMatch = /\w+\s+\w+\s*\(([^)]+)\)/.exec(line);
|
|
6727
|
+
if (paramsMatch) {
|
|
6728
|
+
const paramCount = (paramsMatch[1] ?? "").split(",").length;
|
|
6729
|
+
if (paramCount > 5) {
|
|
6730
|
+
violations.push({
|
|
6731
|
+
rule: "ParameterNumber",
|
|
6732
|
+
file: filePath,
|
|
6733
|
+
line: lineNum,
|
|
6734
|
+
message: `Method has ${paramCount} parameters (max recommended: 5)`,
|
|
6735
|
+
severity: "warning"
|
|
6736
|
+
});
|
|
6737
|
+
}
|
|
6738
|
+
}
|
|
6739
|
+
if (/\)\{/.test(line) || /\belse\{/.test(line)) {
|
|
6740
|
+
violations.push({
|
|
6741
|
+
rule: "WhitespaceAround",
|
|
6742
|
+
file: filePath,
|
|
6743
|
+
line: lineNum,
|
|
6744
|
+
message: "Missing space before '{'",
|
|
6745
|
+
severity: "warning"
|
|
6746
|
+
});
|
|
6747
|
+
}
|
|
6748
|
+
}
|
|
6749
|
+
return violations;
|
|
6750
|
+
}
|
|
6751
|
+
};
|
|
6752
|
+
var JavaDocumentationAnalyzer = class {
|
|
6753
|
+
constructor(projectPath) {
|
|
6754
|
+
this.projectPath = projectPath;
|
|
6755
|
+
}
|
|
6756
|
+
async analyze(files) {
|
|
6757
|
+
const javaFiles = files ?? await findJavaFiles(this.projectPath, { srcPattern: "src/main/**/*.java" });
|
|
6758
|
+
if (!javaFiles.length) {
|
|
6759
|
+
return {
|
|
6760
|
+
score: 100,
|
|
6761
|
+
javadocCoverage: 1,
|
|
6762
|
+
totalMethods: 0,
|
|
6763
|
+
documentedMethods: 0,
|
|
6764
|
+
undocumentedPublicMethods: []
|
|
6765
|
+
};
|
|
6766
|
+
}
|
|
6767
|
+
const fileContents = await Promise.all(
|
|
6768
|
+
javaFiles.map(async (f) => ({
|
|
6769
|
+
path: f,
|
|
6770
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6771
|
+
}))
|
|
6772
|
+
);
|
|
6773
|
+
return this.analyzeContent(fileContents);
|
|
6774
|
+
}
|
|
6775
|
+
analyzeContent(files) {
|
|
6776
|
+
if (!files.length) {
|
|
6777
|
+
return {
|
|
6778
|
+
score: 100,
|
|
6779
|
+
javadocCoverage: 1,
|
|
6780
|
+
totalMethods: 0,
|
|
6781
|
+
documentedMethods: 0,
|
|
6782
|
+
undocumentedPublicMethods: []
|
|
6783
|
+
};
|
|
6784
|
+
}
|
|
6785
|
+
let totalMethods = 0;
|
|
6786
|
+
let documentedMethods = 0;
|
|
6787
|
+
const undocumentedPublicMethods = [];
|
|
6788
|
+
for (const { content } of files) {
|
|
6789
|
+
const lines = content.split("\n");
|
|
6790
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6791
|
+
const line = lines[i] ?? "";
|
|
6792
|
+
if (/\b(?:public)\s+(?!class|interface|enum|record)[\w<>[\]]+\s+\w+\s*\(/.test(line)) {
|
|
6793
|
+
totalMethods++;
|
|
6794
|
+
const isDocumented = this.hasPrecedingJavadoc(lines, i);
|
|
6795
|
+
if (isDocumented) {
|
|
6796
|
+
documentedMethods++;
|
|
6797
|
+
} else {
|
|
6798
|
+
const nameMatch = /\s(\w+)\s*\(/.exec(line);
|
|
6799
|
+
if (nameMatch) undocumentedPublicMethods.push(nameMatch[1] ?? "unknown");
|
|
6800
|
+
}
|
|
6801
|
+
}
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
6804
|
+
const javadocCoverage = totalMethods > 0 ? documentedMethods / totalMethods : 1;
|
|
6805
|
+
const score = Math.round(javadocCoverage * 100);
|
|
6806
|
+
return { score, javadocCoverage, totalMethods, documentedMethods, undocumentedPublicMethods };
|
|
6807
|
+
}
|
|
6808
|
+
hasPrecedingJavadoc(lines, methodLineIndex) {
|
|
6809
|
+
for (let i = methodLineIndex - 1; i >= 0; i--) {
|
|
6810
|
+
const prevLine = (lines[i] ?? "").trim();
|
|
6811
|
+
if (prevLine === "") continue;
|
|
6812
|
+
if (prevLine.endsWith("*/")) {
|
|
6813
|
+
for (let j = i; j >= 0; j--) {
|
|
6814
|
+
const docLine = (lines[j] ?? "").trim();
|
|
6815
|
+
if (docLine.startsWith("/**")) return true;
|
|
6816
|
+
if (!docLine.startsWith("*") && !docLine.startsWith("/**")) break;
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6819
|
+
break;
|
|
6820
|
+
}
|
|
6821
|
+
return false;
|
|
6822
|
+
}
|
|
6823
|
+
};
|
|
6824
|
+
var JACOCO_REPORT_PATHS = [
|
|
6825
|
+
"target/site/jacoco/jacoco.xml",
|
|
6826
|
+
"build/reports/jacoco/test/jacocoTestReport.xml",
|
|
6827
|
+
"build/reports/jacoco/jacocoTestReport.xml"
|
|
6828
|
+
];
|
|
6829
|
+
var JavaCoverageAnalyzer = class {
|
|
6830
|
+
constructor(projectPath) {
|
|
6831
|
+
this.projectPath = projectPath;
|
|
6832
|
+
}
|
|
6833
|
+
async analyze() {
|
|
6834
|
+
for (const reportPath of JACOCO_REPORT_PATHS) {
|
|
6835
|
+
try {
|
|
6836
|
+
const xml2 = await readFile(join(this.projectPath, reportPath), "utf-8");
|
|
6837
|
+
const result = this.parseJacocoXml(xml2);
|
|
6838
|
+
return { ...result, reportFound: true };
|
|
6839
|
+
} catch {
|
|
6840
|
+
}
|
|
6841
|
+
}
|
|
6842
|
+
return { score: 50, lineCoverage: 0, branchCoverage: 0, methodCoverage: 0, reportFound: false };
|
|
6843
|
+
}
|
|
6844
|
+
parseJacocoXml(xml2) {
|
|
6845
|
+
if (!xml2.trim()) {
|
|
6846
|
+
return {
|
|
6847
|
+
score: 0,
|
|
6848
|
+
lineCoverage: 0,
|
|
6849
|
+
branchCoverage: 0,
|
|
6850
|
+
methodCoverage: 0,
|
|
6851
|
+
reportFound: false
|
|
6852
|
+
};
|
|
6853
|
+
}
|
|
6854
|
+
const lineCoverage = this.extractCoverage(xml2, "LINE");
|
|
6855
|
+
const branchCoverage = this.extractCoverage(xml2, "BRANCH");
|
|
6856
|
+
const methodCoverage = this.extractCoverage(xml2, "METHOD");
|
|
6857
|
+
const score = Math.round(lineCoverage * 0.5 + branchCoverage * 0.35 + methodCoverage * 0.15);
|
|
6858
|
+
return { score, lineCoverage, branchCoverage, methodCoverage, reportFound: true };
|
|
6859
|
+
}
|
|
6860
|
+
extractCoverage(xml2, type) {
|
|
6861
|
+
const regex = new RegExp(`<counter\\s+type="${type}"\\s+missed="(\\d+)"\\s+covered="(\\d+)"`);
|
|
6862
|
+
const match = regex.exec(xml2);
|
|
6863
|
+
if (!match) return 0;
|
|
6864
|
+
const missed = parseInt(match[1] ?? "0", 10);
|
|
6865
|
+
const covered = parseInt(match[2] ?? "0", 10);
|
|
6866
|
+
const total = missed + covered;
|
|
6867
|
+
return total > 0 ? Math.round(covered / total * 100) : 0;
|
|
6868
|
+
}
|
|
6869
|
+
};
|
|
6870
|
+
function registerJavaAnalyzers(registry, projectPath) {
|
|
6871
|
+
const complexityAnalyzer = new JavaComplexityAnalyzer(projectPath);
|
|
6872
|
+
const securityAnalyzer = new JavaSecurityAnalyzer(projectPath);
|
|
6873
|
+
const styleAnalyzer = new JavaStyleAnalyzer(projectPath);
|
|
6874
|
+
const documentationAnalyzer = new JavaDocumentationAnalyzer(projectPath);
|
|
6875
|
+
const coverageAnalyzer = new JavaCoverageAnalyzer(projectPath);
|
|
6876
|
+
registry.register({
|
|
6877
|
+
dimensionId: "complexity",
|
|
6878
|
+
language: "java",
|
|
6879
|
+
async analyze(input) {
|
|
6880
|
+
const result = await complexityAnalyzer.analyze(input.files);
|
|
6881
|
+
return { score: result.score, issues: [] };
|
|
6882
|
+
}
|
|
6883
|
+
});
|
|
6884
|
+
registry.register({
|
|
6885
|
+
dimensionId: "security",
|
|
6886
|
+
language: "java",
|
|
6887
|
+
async analyze(input) {
|
|
6888
|
+
const contents = await Promise.all(
|
|
6889
|
+
input.files.map(async (f) => ({
|
|
6890
|
+
path: f,
|
|
6891
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6892
|
+
}))
|
|
6893
|
+
);
|
|
6894
|
+
const result = securityAnalyzer.analyzeContent(contents);
|
|
6895
|
+
return {
|
|
6896
|
+
score: result.score,
|
|
6897
|
+
issues: result.vulnerabilities.map((v) => ({
|
|
6898
|
+
dimension: "security",
|
|
6899
|
+
severity: v.severity === "critical" ? "critical" : "major",
|
|
6900
|
+
message: `[${v.type}] ${v.description}`,
|
|
6901
|
+
file: v.file,
|
|
6902
|
+
line: v.line,
|
|
6903
|
+
suggestion: v.recommendation
|
|
6904
|
+
}))
|
|
6905
|
+
};
|
|
6906
|
+
}
|
|
6907
|
+
});
|
|
6908
|
+
registry.register({
|
|
6909
|
+
dimensionId: "style",
|
|
6910
|
+
language: "java",
|
|
6911
|
+
async analyze(input) {
|
|
6912
|
+
const contents = await Promise.all(
|
|
6913
|
+
input.files.map(async (f) => ({
|
|
6914
|
+
path: f,
|
|
6915
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6916
|
+
}))
|
|
6917
|
+
);
|
|
6918
|
+
const result = styleAnalyzer.analyzeContent(contents);
|
|
6919
|
+
return { score: result.score, issues: [] };
|
|
6920
|
+
}
|
|
6921
|
+
});
|
|
6922
|
+
registry.register({
|
|
6923
|
+
dimensionId: "documentation",
|
|
6924
|
+
language: "java",
|
|
6925
|
+
async analyze(input) {
|
|
6926
|
+
const contents = await Promise.all(
|
|
6927
|
+
input.files.map(async (f) => ({
|
|
6928
|
+
path: f,
|
|
6929
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6930
|
+
}))
|
|
6931
|
+
);
|
|
6932
|
+
const result = documentationAnalyzer.analyzeContent(contents);
|
|
6933
|
+
return { score: result.score, issues: [] };
|
|
6934
|
+
}
|
|
6935
|
+
});
|
|
6936
|
+
registry.register({
|
|
6937
|
+
dimensionId: "testCoverage",
|
|
6938
|
+
language: "java",
|
|
6939
|
+
async analyze() {
|
|
6940
|
+
const result = await coverageAnalyzer.analyze();
|
|
6941
|
+
return { score: result.score, issues: [] };
|
|
6942
|
+
}
|
|
6943
|
+
});
|
|
6944
|
+
}
|
|
6945
|
+
async function loadFiles(files) {
|
|
6946
|
+
return Promise.all(
|
|
6947
|
+
files.map(async (f) => ({
|
|
6948
|
+
path: f,
|
|
6949
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
6950
|
+
}))
|
|
6951
|
+
);
|
|
6952
|
+
}
|
|
6953
|
+
async function findReactFiles(projectPath, pattern = "**/*.{tsx,jsx}") {
|
|
6954
|
+
return glob(pattern, {
|
|
6955
|
+
cwd: projectPath,
|
|
6956
|
+
absolute: true,
|
|
6957
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*"]
|
|
6958
|
+
});
|
|
6959
|
+
}
|
|
6960
|
+
var ReactComponentAnalyzer = class {
|
|
6961
|
+
constructor(projectPath) {
|
|
6962
|
+
this.projectPath = projectPath;
|
|
6963
|
+
}
|
|
6964
|
+
async analyze(files) {
|
|
6965
|
+
const reactFiles = files ?? await findReactFiles(this.projectPath);
|
|
6966
|
+
if (!reactFiles.length) return { score: 100, totalComponents: 0, issues: [] };
|
|
6967
|
+
const fileContents = await loadFiles(reactFiles);
|
|
6968
|
+
return this.analyzeContent(fileContents);
|
|
6969
|
+
}
|
|
6970
|
+
analyzeContent(files) {
|
|
6971
|
+
if (!files.length) return { score: 100, totalComponents: 0, issues: [] };
|
|
6972
|
+
const issues = [];
|
|
6973
|
+
let totalComponents = 0;
|
|
6974
|
+
for (const { path: filePath, content } of files) {
|
|
6975
|
+
const lines = content.split("\n");
|
|
6976
|
+
let inComponent = false;
|
|
6977
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6978
|
+
const line = lines[i] ?? "";
|
|
6979
|
+
const lineNum = i + 1;
|
|
6980
|
+
if (/\bfunction\s+[A-Z]\w*\s*\(|const\s+[A-Z]\w*\s*=\s*(?:\([^)]*\)|[a-z]\w*)\s*=>/.test(line)) {
|
|
6981
|
+
totalComponents++;
|
|
6982
|
+
inComponent = true;
|
|
6983
|
+
}
|
|
6984
|
+
if (/\.map\s*\(/.test(line)) {
|
|
6985
|
+
const block = lines.slice(i, i + 4).join("\n");
|
|
6986
|
+
if (/<[a-zA-Z]/.test(block) && !/key\s*=/.test(block)) {
|
|
6987
|
+
issues.push({
|
|
6988
|
+
rule: "react/missing-key",
|
|
6989
|
+
file: filePath,
|
|
6990
|
+
line: lineNum,
|
|
6991
|
+
message: "Missing 'key' prop in list rendering \u2014 elements need unique keys",
|
|
6992
|
+
severity: "error"
|
|
6993
|
+
});
|
|
6994
|
+
}
|
|
6995
|
+
}
|
|
6996
|
+
const lowerFnMatch = /\bfunction\s+([a-z]\w*)\s*\([^)]*\)\s*\{/.exec(line);
|
|
6997
|
+
if (lowerFnMatch && inComponent) {
|
|
6998
|
+
const nextContent = lines.slice(i, i + 20).join("\n");
|
|
6999
|
+
if (/<[A-Z]|return\s*\(/.test(nextContent)) {
|
|
7000
|
+
issues.push({
|
|
7001
|
+
rule: "react/component-naming",
|
|
7002
|
+
file: filePath,
|
|
7003
|
+
line: lineNum,
|
|
7004
|
+
message: `Component '${lowerFnMatch[1]}' should use PascalCase naming`,
|
|
7005
|
+
severity: "warning"
|
|
7006
|
+
});
|
|
7007
|
+
}
|
|
7008
|
+
}
|
|
7009
|
+
if (/\bfunction\s+[A-Z]\w*\s*\(\s*props\s*\)/.test(line)) {
|
|
7010
|
+
issues.push({
|
|
7011
|
+
rule: "react/prop-types",
|
|
7012
|
+
file: filePath,
|
|
7013
|
+
line: lineNum,
|
|
7014
|
+
message: "Component props are not typed \u2014 use a TypeScript interface or destructure with types",
|
|
7015
|
+
severity: "error"
|
|
7016
|
+
});
|
|
7017
|
+
}
|
|
7018
|
+
if (/^\s*export\s+(?:default\s+)?function\s+[A-Z]\w*/.test(line)) {
|
|
7019
|
+
let prevLineIdx = i - 1;
|
|
7020
|
+
while (prevLineIdx >= 0 && (lines[prevLineIdx] ?? "").trim() === "") {
|
|
7021
|
+
prevLineIdx--;
|
|
7022
|
+
}
|
|
7023
|
+
const prevLine = (lines[prevLineIdx] ?? "").trim();
|
|
7024
|
+
if (!prevLine.endsWith("*/")) {
|
|
7025
|
+
issues.push({
|
|
7026
|
+
rule: "react/missing-jsdoc",
|
|
7027
|
+
file: filePath,
|
|
7028
|
+
line: lineNum,
|
|
7029
|
+
message: "Exported component missing JSDoc comment",
|
|
7030
|
+
severity: "warning"
|
|
7031
|
+
});
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
7034
|
+
if (/document\.getElementById|document\.querySelector|document\.createElement/.test(line)) {
|
|
7035
|
+
issues.push({
|
|
7036
|
+
rule: "react/no-direct-dom-manipulation",
|
|
7037
|
+
file: filePath,
|
|
7038
|
+
line: lineNum,
|
|
7039
|
+
message: "Avoid direct DOM manipulation in React \u2014 use refs or state instead",
|
|
7040
|
+
severity: "warning"
|
|
7041
|
+
});
|
|
7042
|
+
}
|
|
7043
|
+
if (/dangerouslySetInnerHTML/.test(line) && !/sanitize|DOMPurify|xss/.test(line)) {
|
|
7044
|
+
issues.push({
|
|
7045
|
+
rule: "react/no-danger",
|
|
7046
|
+
file: filePath,
|
|
7047
|
+
line: lineNum,
|
|
7048
|
+
message: "dangerouslySetInnerHTML can lead to XSS \u2014 ensure content is sanitized",
|
|
7049
|
+
severity: "error"
|
|
7050
|
+
});
|
|
7051
|
+
}
|
|
7052
|
+
}
|
|
7053
|
+
}
|
|
7054
|
+
const deduction = issues.reduce((s, i) => s + (i.severity === "error" ? 10 : 5), 0);
|
|
7055
|
+
const score = Math.max(0, 100 - deduction);
|
|
7056
|
+
return { score, totalComponents, issues };
|
|
7057
|
+
}
|
|
7058
|
+
};
|
|
7059
|
+
var ReactA11yAnalyzer = class {
|
|
7060
|
+
constructor(projectPath) {
|
|
7061
|
+
this.projectPath = projectPath;
|
|
7062
|
+
}
|
|
7063
|
+
async analyze(files) {
|
|
7064
|
+
const reactFiles = files ?? await findReactFiles(this.projectPath);
|
|
7065
|
+
if (!reactFiles.length) return { score: 100, violations: [] };
|
|
7066
|
+
const fileContents = await loadFiles(reactFiles);
|
|
7067
|
+
return this.analyzeContent(fileContents);
|
|
7068
|
+
}
|
|
7069
|
+
analyzeContent(files) {
|
|
7070
|
+
if (!files.length) return { score: 100, violations: [] };
|
|
7071
|
+
const violations = [];
|
|
7072
|
+
for (const { path: filePath, content } of files) {
|
|
7073
|
+
violations.push(...this.checkFile(filePath, content));
|
|
7074
|
+
}
|
|
7075
|
+
const deduction = violations.reduce((s, v) => s + (v.severity === "error" ? 12 : 6), 0);
|
|
7076
|
+
const score = Math.max(0, 100 - deduction);
|
|
7077
|
+
return { score, violations };
|
|
7078
|
+
}
|
|
7079
|
+
checkFile(filePath, content) {
|
|
7080
|
+
const violations = [];
|
|
7081
|
+
const lines = content.split("\n");
|
|
7082
|
+
for (let i = 0; i < lines.length; i++) {
|
|
7083
|
+
const line = lines[i] ?? "";
|
|
7084
|
+
const lineNum = i + 1;
|
|
7085
|
+
if (/<img\b/.test(line)) {
|
|
7086
|
+
const imgBlock = lines.slice(i, Math.min(i + 4, lines.length)).join("\n");
|
|
7087
|
+
if (!/alt\s*=/.test(imgBlock)) {
|
|
7088
|
+
violations.push({
|
|
7089
|
+
rule: "jsx-a11y/alt-text",
|
|
7090
|
+
file: filePath,
|
|
7091
|
+
line: lineNum,
|
|
7092
|
+
message: "<img> element missing 'alt' attribute",
|
|
7093
|
+
severity: "error",
|
|
7094
|
+
wcag: "WCAG 1.1.1"
|
|
7095
|
+
});
|
|
7096
|
+
}
|
|
7097
|
+
}
|
|
7098
|
+
if (/<a\b/.test(line)) {
|
|
7099
|
+
const aBlock = lines.slice(i, Math.min(i + 4, lines.length)).join("\n");
|
|
7100
|
+
if (!/href\s*=/.test(aBlock) && !/ role\s*=/.test(aBlock)) {
|
|
7101
|
+
violations.push({
|
|
7102
|
+
rule: "jsx-a11y/anchor-has-content",
|
|
7103
|
+
file: filePath,
|
|
7104
|
+
line: lineNum,
|
|
7105
|
+
message: "<a> element missing href \u2014 use a <button> for non-navigation actions",
|
|
7106
|
+
severity: "error",
|
|
7107
|
+
wcag: "WCAG 2.1.1"
|
|
7108
|
+
});
|
|
7109
|
+
}
|
|
7110
|
+
}
|
|
7111
|
+
if (/<(?:div|span)\b[^>]*onClick[^>]*>/.test(line)) {
|
|
7112
|
+
const hasKeyboardSupport = /onKey(?:Down|Up|Press)|role\s*=\s*["'](?:button|link|menuitem)|tabIndex/.test(line);
|
|
7113
|
+
if (!hasKeyboardSupport) {
|
|
7114
|
+
const context = lines.slice(Math.max(0, i - 1), i + 3).join(" ");
|
|
7115
|
+
if (!/onKey(?:Down|Up|Press)|tabIndex/.test(context)) {
|
|
7116
|
+
violations.push({
|
|
7117
|
+
rule: "jsx-a11y/interactive-supports-focus",
|
|
7118
|
+
file: filePath,
|
|
7119
|
+
line: lineNum,
|
|
7120
|
+
message: "Non-interactive element with onClick handler \u2014 add keyboard support (onKeyDown, tabIndex, role)",
|
|
7121
|
+
severity: "warning",
|
|
7122
|
+
wcag: "WCAG 2.1.1"
|
|
7123
|
+
});
|
|
7124
|
+
}
|
|
7125
|
+
}
|
|
7126
|
+
}
|
|
7127
|
+
if (/<input\b[^>]*>/.test(line) && !/aria-label|aria-labelledby|id\s*=/.test(line)) {
|
|
7128
|
+
violations.push({
|
|
7129
|
+
rule: "jsx-a11y/label-association",
|
|
7130
|
+
file: filePath,
|
|
7131
|
+
line: lineNum,
|
|
7132
|
+
message: "<input> missing label association (aria-label, aria-labelledby, or id for <label>)",
|
|
7133
|
+
severity: "warning",
|
|
7134
|
+
wcag: "WCAG 1.3.1"
|
|
7135
|
+
});
|
|
7136
|
+
}
|
|
7137
|
+
if (/<video\b[^>]*autoPlay[^>]*>/.test(line) && !/controls/.test(line)) {
|
|
7138
|
+
violations.push({
|
|
7139
|
+
rule: "jsx-a11y/media-has-caption",
|
|
7140
|
+
file: filePath,
|
|
7141
|
+
line: lineNum,
|
|
7142
|
+
message: "Autoplaying video without controls \u2014 add controls attribute",
|
|
7143
|
+
severity: "warning",
|
|
7144
|
+
wcag: "WCAG 1.2.2"
|
|
7145
|
+
});
|
|
7146
|
+
}
|
|
7147
|
+
}
|
|
7148
|
+
return violations;
|
|
7149
|
+
}
|
|
7150
|
+
};
|
|
7151
|
+
var ReactHookAnalyzer = class {
|
|
7152
|
+
constructor(projectPath) {
|
|
7153
|
+
this.projectPath = projectPath;
|
|
7154
|
+
}
|
|
7155
|
+
async analyze(files) {
|
|
7156
|
+
const reactFiles = files ?? await findReactFiles(this.projectPath, "**/*.{tsx,jsx,ts,js}");
|
|
7157
|
+
if (!reactFiles.length) return { score: 100, violations: [] };
|
|
7158
|
+
const fileContents = await loadFiles(reactFiles);
|
|
7159
|
+
return this.analyzeContent(fileContents);
|
|
7160
|
+
}
|
|
7161
|
+
analyzeContent(files) {
|
|
7162
|
+
if (!files.length) return { score: 100, violations: [] };
|
|
7163
|
+
const violations = [];
|
|
7164
|
+
for (const { path: filePath, content } of files) {
|
|
7165
|
+
violations.push(...this.checkFile(filePath, content));
|
|
7166
|
+
}
|
|
7167
|
+
const deduction = violations.reduce((s, v) => s + (v.severity === "error" ? 15 : 7), 0);
|
|
7168
|
+
const score = Math.max(0, 100 - deduction);
|
|
7169
|
+
return { score, violations };
|
|
7170
|
+
}
|
|
7171
|
+
checkFile(filePath, content) {
|
|
7172
|
+
const violations = [];
|
|
7173
|
+
const lines = content.split("\n");
|
|
7174
|
+
let conditionalDepth = 0;
|
|
7175
|
+
let loopDepth = 0;
|
|
7176
|
+
for (let i = 0; i < lines.length; i++) {
|
|
7177
|
+
const line = lines[i] ?? "";
|
|
7178
|
+
const lineNum = i + 1;
|
|
7179
|
+
const trimmed = line.trim();
|
|
7180
|
+
const openBraces = (trimmed.match(/\{/g) ?? []).length;
|
|
7181
|
+
const closeBraces = (trimmed.match(/\}/g) ?? []).length;
|
|
7182
|
+
if (/^\s*(?:if\s*\(|else\s+if\s*\(|else\s*\{)/.test(line) && openBraces > closeBraces) {
|
|
7183
|
+
conditionalDepth++;
|
|
7184
|
+
}
|
|
7185
|
+
if (/^\s*(?:for\s*\(|while\s*\()/.test(line) && openBraces > closeBraces) {
|
|
7186
|
+
loopDepth++;
|
|
7187
|
+
}
|
|
7188
|
+
if (/\.forEach\s*\(/.test(line) && openBraces > closeBraces) {
|
|
7189
|
+
loopDepth++;
|
|
7190
|
+
}
|
|
7191
|
+
if (/\buseEffect\s*\(/.test(line)) {
|
|
7192
|
+
const block = lines.slice(i, i + 5).join("\n");
|
|
7193
|
+
if (!/,\s*\[/.test(block)) {
|
|
7194
|
+
violations.push({
|
|
7195
|
+
rule: "react-hooks/exhaustive-deps",
|
|
7196
|
+
file: filePath,
|
|
7197
|
+
line: lineNum,
|
|
7198
|
+
message: "useEffect without dependency array \u2014 runs on every render (possibly unintentional)",
|
|
7199
|
+
severity: "warning"
|
|
7200
|
+
});
|
|
7201
|
+
}
|
|
7202
|
+
}
|
|
7203
|
+
if (conditionalDepth > 0 && /\buse[A-Z]\w*\s*\(/.test(line)) {
|
|
7204
|
+
violations.push({
|
|
7205
|
+
rule: "react-hooks/rules-of-hooks",
|
|
7206
|
+
file: filePath,
|
|
7207
|
+
line: lineNum,
|
|
7208
|
+
message: "Hook called inside conditional \u2014 violates Rules of Hooks",
|
|
7209
|
+
severity: "error"
|
|
7210
|
+
});
|
|
7211
|
+
}
|
|
7212
|
+
if (loopDepth > 0 && /\buse[A-Z]\w*\s*\(/.test(line)) {
|
|
7213
|
+
violations.push({
|
|
7214
|
+
rule: "react-hooks/rules-of-hooks",
|
|
7215
|
+
file: filePath,
|
|
7216
|
+
line: lineNum,
|
|
7217
|
+
message: "Hook called inside loop \u2014 violates Rules of Hooks",
|
|
7218
|
+
severity: "error"
|
|
7219
|
+
});
|
|
7220
|
+
}
|
|
7221
|
+
const netBraces = openBraces - closeBraces;
|
|
7222
|
+
if (netBraces < 0) {
|
|
7223
|
+
if (conditionalDepth > 0) conditionalDepth = Math.max(0, conditionalDepth + netBraces);
|
|
7224
|
+
if (loopDepth > 0) loopDepth = Math.max(0, loopDepth + netBraces);
|
|
7225
|
+
}
|
|
7226
|
+
}
|
|
7227
|
+
return violations;
|
|
7228
|
+
}
|
|
7229
|
+
};
|
|
7230
|
+
function registerReactAnalyzers(registry, projectPath) {
|
|
7231
|
+
const componentAnalyzer = new ReactComponentAnalyzer(projectPath);
|
|
7232
|
+
const a11yAnalyzer = new ReactA11yAnalyzer(projectPath);
|
|
7233
|
+
const hookAnalyzer = new ReactHookAnalyzer(projectPath);
|
|
7234
|
+
for (const lang of ["react-typescript", "react-javascript"]) {
|
|
7235
|
+
registry.register({
|
|
7236
|
+
dimensionId: "style",
|
|
7237
|
+
language: lang,
|
|
7238
|
+
async analyze(input) {
|
|
7239
|
+
const contents = await Promise.all(
|
|
7240
|
+
input.files.map(async (f) => ({
|
|
7241
|
+
path: f,
|
|
7242
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
7243
|
+
}))
|
|
7244
|
+
);
|
|
7245
|
+
const result = componentAnalyzer.analyzeContent(contents);
|
|
7246
|
+
return {
|
|
7247
|
+
score: result.score,
|
|
7248
|
+
issues: result.issues.map((i) => ({
|
|
7249
|
+
dimension: "style",
|
|
7250
|
+
severity: i.severity === "error" ? "major" : "minor",
|
|
7251
|
+
message: i.message,
|
|
7252
|
+
file: i.file,
|
|
7253
|
+
line: i.line
|
|
7254
|
+
}))
|
|
7255
|
+
};
|
|
7256
|
+
}
|
|
7257
|
+
});
|
|
7258
|
+
registry.register({
|
|
7259
|
+
dimensionId: "robustness",
|
|
7260
|
+
language: lang,
|
|
7261
|
+
async analyze(input) {
|
|
7262
|
+
const contents = await Promise.all(
|
|
7263
|
+
input.files.map(async (f) => ({
|
|
7264
|
+
path: f,
|
|
7265
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
7266
|
+
}))
|
|
7267
|
+
);
|
|
7268
|
+
const result = a11yAnalyzer.analyzeContent(contents);
|
|
7269
|
+
return {
|
|
7270
|
+
score: result.score,
|
|
7271
|
+
issues: result.violations.map((v) => ({
|
|
7272
|
+
dimension: "robustness",
|
|
7273
|
+
severity: v.severity === "error" ? "major" : "minor",
|
|
7274
|
+
message: v.message,
|
|
7275
|
+
file: v.file,
|
|
7276
|
+
line: v.line
|
|
7277
|
+
}))
|
|
7278
|
+
};
|
|
7279
|
+
}
|
|
7280
|
+
});
|
|
7281
|
+
registry.register({
|
|
7282
|
+
dimensionId: "correctness",
|
|
7283
|
+
language: lang,
|
|
7284
|
+
async analyze(input) {
|
|
7285
|
+
const contents = await Promise.all(
|
|
7286
|
+
input.files.map(async (f) => ({
|
|
7287
|
+
path: f,
|
|
7288
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
7289
|
+
}))
|
|
7290
|
+
);
|
|
7291
|
+
const result = hookAnalyzer.analyzeContent(contents);
|
|
7292
|
+
return {
|
|
7293
|
+
score: result.score,
|
|
7294
|
+
issues: result.violations.map((v) => ({
|
|
7295
|
+
dimension: "correctness",
|
|
7296
|
+
severity: v.severity === "error" ? "critical" : "minor",
|
|
7297
|
+
message: v.message,
|
|
7298
|
+
file: v.file,
|
|
7299
|
+
line: v.line
|
|
7300
|
+
}))
|
|
7301
|
+
};
|
|
7302
|
+
}
|
|
7303
|
+
});
|
|
5993
7304
|
}
|
|
5994
|
-
traverse(ast);
|
|
5995
|
-
return {
|
|
5996
|
-
lineCount: 0,
|
|
5997
|
-
// Will be set separately from content
|
|
5998
|
-
functionCount,
|
|
5999
|
-
importCount,
|
|
6000
|
-
crossBoundaryImportCount
|
|
6001
|
-
};
|
|
6002
7305
|
}
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
7306
|
+
var DimensionRegistry = class {
|
|
7307
|
+
analyzers = [];
|
|
7308
|
+
/**
|
|
7309
|
+
* Register a dimension analyzer plugin.
|
|
7310
|
+
* If an analyzer for the same (language, dimensionId) pair already exists,
|
|
7311
|
+
* it is replaced — preventing order-dependent duplicate accumulation.
|
|
7312
|
+
*/
|
|
7313
|
+
register(analyzer) {
|
|
7314
|
+
const existing = this.analyzers.findIndex(
|
|
7315
|
+
(a) => a.language === analyzer.language && a.dimensionId === analyzer.dimensionId
|
|
7316
|
+
);
|
|
7317
|
+
if (existing !== -1) {
|
|
7318
|
+
this.analyzers[existing] = analyzer;
|
|
7319
|
+
} else {
|
|
7320
|
+
this.analyzers.push(analyzer);
|
|
7321
|
+
}
|
|
6013
7322
|
}
|
|
6014
|
-
|
|
6015
|
-
|
|
7323
|
+
/**
|
|
7324
|
+
* Get all analyzers applicable for a given language.
|
|
7325
|
+
* Includes both language-specific analyzers and "all" analyzers.
|
|
7326
|
+
*
|
|
7327
|
+
* @param language - Target language
|
|
7328
|
+
* @param dimensionId - Optional filter for a specific dimension
|
|
7329
|
+
*/
|
|
7330
|
+
getAnalyzers(language, dimensionId) {
|
|
7331
|
+
return this.analyzers.filter(
|
|
7332
|
+
(a) => (a.language === language || a.language === "all") && (dimensionId === void 0 || a.dimensionId === dimensionId)
|
|
7333
|
+
);
|
|
6016
7334
|
}
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
7335
|
+
/**
|
|
7336
|
+
* Check if any analyzers are registered for a given language
|
|
7337
|
+
* (includes "all" analyzers)
|
|
7338
|
+
*/
|
|
7339
|
+
hasAnalyzers(language) {
|
|
7340
|
+
return this.analyzers.some((a) => a.language === language || a.language === "all");
|
|
6020
7341
|
}
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
7342
|
+
/**
|
|
7343
|
+
* Get all languages that have registered analyzers
|
|
7344
|
+
* (excludes the "all" pseudo-language)
|
|
7345
|
+
*/
|
|
7346
|
+
getSupportedLanguages() {
|
|
7347
|
+
const languages = /* @__PURE__ */ new Set();
|
|
7348
|
+
for (const a of this.analyzers) {
|
|
7349
|
+
if (a.language !== "all") {
|
|
7350
|
+
languages.add(a.language);
|
|
7351
|
+
}
|
|
7352
|
+
}
|
|
7353
|
+
return Array.from(languages);
|
|
6029
7354
|
}
|
|
6030
7355
|
/**
|
|
6031
|
-
*
|
|
7356
|
+
* Run all matching analyzers for the given input and return combined results.
|
|
7357
|
+
* Analyzers run in parallel for performance.
|
|
6032
7358
|
*/
|
|
6033
|
-
async analyze(
|
|
6034
|
-
const
|
|
6035
|
-
if (
|
|
7359
|
+
async analyze(input) {
|
|
7360
|
+
const matching = this.getAnalyzers(input.language);
|
|
7361
|
+
if (!matching.length) return [];
|
|
7362
|
+
const settled = await Promise.allSettled(
|
|
7363
|
+
matching.map(
|
|
7364
|
+
(analyzer) => analyzer.analyze(input).then((result) => ({ dimensionId: analyzer.dimensionId, result }))
|
|
7365
|
+
)
|
|
7366
|
+
);
|
|
7367
|
+
return settled.filter((s) => {
|
|
7368
|
+
if (s.status === "rejected") {
|
|
7369
|
+
process.stderr.write(`[DimensionRegistry] Analyzer failed: ${String(s.reason)}
|
|
7370
|
+
`);
|
|
7371
|
+
}
|
|
7372
|
+
return s.status === "fulfilled";
|
|
7373
|
+
}).map((s) => s.value);
|
|
7374
|
+
}
|
|
7375
|
+
};
|
|
7376
|
+
function wrapAnalyzer(dimensionId, language, analyzerFn) {
|
|
7377
|
+
return {
|
|
7378
|
+
dimensionId,
|
|
7379
|
+
language,
|
|
7380
|
+
async analyze(input) {
|
|
7381
|
+
const raw = await analyzerFn(input);
|
|
6036
7382
|
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"
|
|
7383
|
+
score: raw.score,
|
|
7384
|
+
issues: raw.issues ?? []
|
|
6047
7385
|
};
|
|
6048
7386
|
}
|
|
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
|
-
|
|
7387
|
+
};
|
|
7388
|
+
}
|
|
7389
|
+
function createDefaultRegistry(projectPath) {
|
|
7390
|
+
const registry = new DimensionRegistry();
|
|
7391
|
+
const coverageAnalyzer = new CoverageAnalyzer(projectPath);
|
|
7392
|
+
const securityScanner = new CompositeSecurityScanner(projectPath, false);
|
|
7393
|
+
const complexityAnalyzer = new ComplexityAnalyzer(projectPath);
|
|
7394
|
+
const duplicationAnalyzer = new DuplicationAnalyzer(projectPath);
|
|
7395
|
+
const correctnessAnalyzer = new CorrectnessAnalyzer(projectPath);
|
|
7396
|
+
const completenessAnalyzer = new CompletenessAnalyzer(projectPath);
|
|
7397
|
+
const robustnessAnalyzer = new RobustnessAnalyzer(projectPath);
|
|
7398
|
+
const testQualityAnalyzer = new TestQualityAnalyzer(projectPath);
|
|
7399
|
+
const documentationAnalyzer = new DocumentationAnalyzer(projectPath);
|
|
7400
|
+
const styleAnalyzer = new StyleAnalyzer(projectPath);
|
|
7401
|
+
const readabilityAnalyzer = new ReadabilityAnalyzer(projectPath);
|
|
7402
|
+
const maintainabilityAnalyzer = new MaintainabilityAnalyzer(projectPath);
|
|
7403
|
+
const baseLangs = ["typescript", "javascript"];
|
|
7404
|
+
const reactLangs = ["react-typescript", "react-javascript"];
|
|
7405
|
+
for (const lang of baseLangs) {
|
|
7406
|
+
registry.register(
|
|
7407
|
+
wrapAnalyzer("correctness", lang, async () => {
|
|
7408
|
+
const r = await correctnessAnalyzer.analyze();
|
|
7409
|
+
return { score: r.score };
|
|
7410
|
+
})
|
|
7411
|
+
);
|
|
7412
|
+
registry.register(
|
|
7413
|
+
wrapAnalyzer("completeness", lang, async (input) => {
|
|
7414
|
+
const r = await completenessAnalyzer.analyze(input.files);
|
|
7415
|
+
return { score: r.score };
|
|
7416
|
+
})
|
|
7417
|
+
);
|
|
7418
|
+
registry.register(
|
|
7419
|
+
wrapAnalyzer("robustness", lang, async (input) => {
|
|
7420
|
+
const r = await robustnessAnalyzer.analyze(input.files);
|
|
7421
|
+
return { score: r.score };
|
|
7422
|
+
})
|
|
7423
|
+
);
|
|
7424
|
+
registry.register(
|
|
7425
|
+
wrapAnalyzer("readability", lang, async (input) => {
|
|
7426
|
+
const r = await readabilityAnalyzer.analyze(input.files);
|
|
7427
|
+
return { score: r.score };
|
|
7428
|
+
})
|
|
7429
|
+
);
|
|
7430
|
+
registry.register(
|
|
7431
|
+
wrapAnalyzer("maintainability", lang, async (input) => {
|
|
7432
|
+
const r = await maintainabilityAnalyzer.analyze(input.files);
|
|
7433
|
+
return { score: r.score };
|
|
7434
|
+
})
|
|
7435
|
+
);
|
|
7436
|
+
registry.register(
|
|
7437
|
+
wrapAnalyzer("complexity", lang, async (input) => {
|
|
7438
|
+
const r = await complexityAnalyzer.analyze(input.files);
|
|
7439
|
+
return { score: r.score };
|
|
7440
|
+
})
|
|
7441
|
+
);
|
|
7442
|
+
registry.register(
|
|
7443
|
+
wrapAnalyzer("duplication", lang, async (input) => {
|
|
7444
|
+
const r = await duplicationAnalyzer.analyze(input.files);
|
|
7445
|
+
return { score: Math.max(0, 100 - r.percentage) };
|
|
7446
|
+
})
|
|
7447
|
+
);
|
|
7448
|
+
registry.register(
|
|
7449
|
+
wrapAnalyzer("testCoverage", lang, async () => {
|
|
7450
|
+
const r = await coverageAnalyzer.analyze();
|
|
7451
|
+
return { score: r?.lines.percentage ?? 0 };
|
|
7452
|
+
})
|
|
7453
|
+
);
|
|
7454
|
+
registry.register(
|
|
7455
|
+
wrapAnalyzer("testQuality", lang, async () => {
|
|
7456
|
+
const r = await testQualityAnalyzer.analyze();
|
|
7457
|
+
return { score: r.score };
|
|
7458
|
+
})
|
|
7459
|
+
);
|
|
7460
|
+
registry.register(
|
|
7461
|
+
wrapAnalyzer("security", lang, async (input) => {
|
|
7462
|
+
const fileContents = await Promise.all(
|
|
7463
|
+
input.files.map(async (f) => ({
|
|
7464
|
+
path: f,
|
|
7465
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
7466
|
+
}))
|
|
7467
|
+
);
|
|
7468
|
+
const r = await securityScanner.scan(fileContents);
|
|
7469
|
+
return { score: r.score };
|
|
7470
|
+
})
|
|
7471
|
+
);
|
|
7472
|
+
registry.register(
|
|
7473
|
+
wrapAnalyzer("documentation", lang, async (input) => {
|
|
7474
|
+
const r = await documentationAnalyzer.analyze(input.files);
|
|
7475
|
+
return { score: r.score };
|
|
7476
|
+
})
|
|
7477
|
+
);
|
|
7478
|
+
registry.register(
|
|
7479
|
+
wrapAnalyzer("style", lang, async () => {
|
|
7480
|
+
const r = await styleAnalyzer.analyze();
|
|
7481
|
+
return { score: r.score };
|
|
7482
|
+
})
|
|
6082
7483
|
);
|
|
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
7484
|
}
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
7485
|
+
for (const lang of reactLangs) {
|
|
7486
|
+
registry.register(
|
|
7487
|
+
wrapAnalyzer("completeness", lang, async (input) => {
|
|
7488
|
+
const r = await completenessAnalyzer.analyze(input.files);
|
|
7489
|
+
return { score: r.score };
|
|
7490
|
+
})
|
|
7491
|
+
);
|
|
7492
|
+
registry.register(
|
|
7493
|
+
wrapAnalyzer("readability", lang, async (input) => {
|
|
7494
|
+
const r = await readabilityAnalyzer.analyze(input.files);
|
|
7495
|
+
return { score: r.score };
|
|
7496
|
+
})
|
|
7497
|
+
);
|
|
7498
|
+
registry.register(
|
|
7499
|
+
wrapAnalyzer("maintainability", lang, async (input) => {
|
|
7500
|
+
const r = await maintainabilityAnalyzer.analyze(input.files);
|
|
7501
|
+
return { score: r.score };
|
|
7502
|
+
})
|
|
7503
|
+
);
|
|
7504
|
+
registry.register(
|
|
7505
|
+
wrapAnalyzer("complexity", lang, async (input) => {
|
|
7506
|
+
const r = await complexityAnalyzer.analyze(input.files);
|
|
7507
|
+
return { score: r.score };
|
|
7508
|
+
})
|
|
7509
|
+
);
|
|
7510
|
+
registry.register(
|
|
7511
|
+
wrapAnalyzer("duplication", lang, async (input) => {
|
|
7512
|
+
const r = await duplicationAnalyzer.analyze(input.files);
|
|
7513
|
+
return { score: Math.max(0, 100 - r.percentage) };
|
|
7514
|
+
})
|
|
7515
|
+
);
|
|
7516
|
+
registry.register(
|
|
7517
|
+
wrapAnalyzer("testCoverage", lang, async () => {
|
|
7518
|
+
const r = await coverageAnalyzer.analyze();
|
|
7519
|
+
return { score: r?.lines.percentage ?? 0 };
|
|
7520
|
+
})
|
|
7521
|
+
);
|
|
7522
|
+
registry.register(
|
|
7523
|
+
wrapAnalyzer("testQuality", lang, async () => {
|
|
7524
|
+
const r = await testQualityAnalyzer.analyze();
|
|
7525
|
+
return { score: r.score };
|
|
7526
|
+
})
|
|
7527
|
+
);
|
|
7528
|
+
registry.register(
|
|
7529
|
+
wrapAnalyzer("security", lang, async (input) => {
|
|
7530
|
+
const fileContents = await Promise.all(
|
|
7531
|
+
input.files.map(async (f) => ({
|
|
7532
|
+
path: f,
|
|
7533
|
+
content: await readFile(f, "utf-8").catch(() => "")
|
|
7534
|
+
}))
|
|
7535
|
+
);
|
|
7536
|
+
const r = await securityScanner.scan(fileContents);
|
|
7537
|
+
return { score: r.score };
|
|
7538
|
+
})
|
|
7539
|
+
);
|
|
7540
|
+
registry.register(
|
|
7541
|
+
wrapAnalyzer("documentation", lang, async (input) => {
|
|
7542
|
+
const r = await documentationAnalyzer.analyze(input.files);
|
|
7543
|
+
return { score: r.score };
|
|
7544
|
+
})
|
|
7545
|
+
);
|
|
6108
7546
|
}
|
|
6109
|
-
|
|
7547
|
+
return registry;
|
|
7548
|
+
}
|
|
7549
|
+
|
|
7550
|
+
// src/quality/evaluator.ts
|
|
6110
7551
|
var QualityEvaluator = class {
|
|
6111
|
-
constructor(projectPath, useSnyk = false) {
|
|
7552
|
+
constructor(projectPath, useSnyk = false, registry) {
|
|
6112
7553
|
this.projectPath = projectPath;
|
|
7554
|
+
this.registry = registry;
|
|
6113
7555
|
this.coverageAnalyzer = new CoverageAnalyzer(projectPath);
|
|
6114
7556
|
this.securityScanner = new CompositeSecurityScanner(projectPath, useSnyk);
|
|
6115
7557
|
this.complexityAnalyzer = new ComplexityAnalyzer(projectPath);
|
|
@@ -6145,7 +7587,7 @@ var QualityEvaluator = class {
|
|
|
6145
7587
|
const fileContents = await Promise.all(
|
|
6146
7588
|
targetFiles.map(async (file) => ({
|
|
6147
7589
|
path: file,
|
|
6148
|
-
content: await readFile(file, "utf-8")
|
|
7590
|
+
content: await readFile(file, "utf-8").catch(() => "")
|
|
6149
7591
|
}))
|
|
6150
7592
|
);
|
|
6151
7593
|
const [
|
|
@@ -6189,8 +7631,27 @@ var QualityEvaluator = class {
|
|
|
6189
7631
|
testQuality: testQualityResult.score,
|
|
6190
7632
|
documentation: documentationResult.score
|
|
6191
7633
|
};
|
|
7634
|
+
if (this.registry) {
|
|
7635
|
+
const { language } = detectProjectLanguage(targetFiles);
|
|
7636
|
+
const registryResults = await this.registry.analyze({
|
|
7637
|
+
projectPath: this.projectPath,
|
|
7638
|
+
files: targetFiles,
|
|
7639
|
+
language
|
|
7640
|
+
});
|
|
7641
|
+
for (const { dimensionId, result } of registryResults) {
|
|
7642
|
+
if (dimensionId in dimensions) {
|
|
7643
|
+
dimensions[dimensionId] = result.score;
|
|
7644
|
+
}
|
|
7645
|
+
}
|
|
7646
|
+
}
|
|
7647
|
+
let projectConfig = null;
|
|
7648
|
+
try {
|
|
7649
|
+
projectConfig = await loadProjectConfig(this.projectPath);
|
|
7650
|
+
} catch {
|
|
7651
|
+
}
|
|
7652
|
+
const weights = resolvedWeights(projectConfig);
|
|
6192
7653
|
const overall = Object.entries(dimensions).reduce((sum, [key, value]) => {
|
|
6193
|
-
const weight =
|
|
7654
|
+
const weight = weights[key] ?? 0;
|
|
6194
7655
|
return sum + value * weight;
|
|
6195
7656
|
}, 0);
|
|
6196
7657
|
const scores = {
|
|
@@ -6208,13 +7669,15 @@ var QualityEvaluator = class {
|
|
|
6208
7669
|
documentationResult
|
|
6209
7670
|
);
|
|
6210
7671
|
const suggestions = this.generateSuggestions(dimensions);
|
|
6211
|
-
const
|
|
6212
|
-
const
|
|
7672
|
+
const thresholds = resolvedThresholds(projectConfig);
|
|
7673
|
+
const meetsMinimum = scores.overall >= thresholds.minimum.overall && dimensions.testCoverage >= thresholds.minimum.testCoverage && dimensions.security >= thresholds.minimum.security;
|
|
7674
|
+
const meetsTarget = scores.overall >= thresholds.target.overall && dimensions.testCoverage >= thresholds.target.testCoverage;
|
|
7675
|
+
const converged = scores.overall >= thresholds.target.overall;
|
|
6213
7676
|
return {
|
|
6214
7677
|
scores,
|
|
6215
7678
|
meetsMinimum,
|
|
6216
7679
|
meetsTarget,
|
|
6217
|
-
converged
|
|
7680
|
+
converged,
|
|
6218
7681
|
issues,
|
|
6219
7682
|
suggestions
|
|
6220
7683
|
};
|
|
@@ -6363,8 +7826,11 @@ var QualityEvaluator = class {
|
|
|
6363
7826
|
});
|
|
6364
7827
|
}
|
|
6365
7828
|
};
|
|
6366
|
-
function
|
|
6367
|
-
|
|
7829
|
+
function createQualityEvaluatorWithRegistry(projectPath, useSnyk) {
|
|
7830
|
+
const registry = createDefaultRegistry(projectPath);
|
|
7831
|
+
registerJavaAnalyzers(registry, projectPath);
|
|
7832
|
+
registerReactAnalyzers(registry, projectPath);
|
|
7833
|
+
return new QualityEvaluator(projectPath, useSnyk ?? false, registry);
|
|
6368
7834
|
}
|
|
6369
7835
|
|
|
6370
7836
|
// src/phases/complete/prompts.ts
|
|
@@ -6674,7 +8140,7 @@ function setupFileLogging(logger, logDir, name) {
|
|
|
6674
8140
|
if (!fs4__default.existsSync(logDir)) {
|
|
6675
8141
|
fs4__default.mkdirSync(logDir, { recursive: true });
|
|
6676
8142
|
}
|
|
6677
|
-
const logFile =
|
|
8143
|
+
const logFile = path15__default.join(logDir, `${name}.log`);
|
|
6678
8144
|
logger.attachTransport((logObj) => {
|
|
6679
8145
|
const line = JSON.stringify(logObj) + "\n";
|
|
6680
8146
|
fs4__default.appendFileSync(logFile, line);
|
|
@@ -8057,13 +9523,13 @@ var CompleteExecutor = class {
|
|
|
8057
9523
|
*/
|
|
8058
9524
|
async checkpoint(context) {
|
|
8059
9525
|
if (this.checkpointState && this.currentSprint) {
|
|
8060
|
-
const checkpointPath =
|
|
9526
|
+
const checkpointPath = path15__default.join(
|
|
8061
9527
|
context.projectPath,
|
|
8062
9528
|
".coco",
|
|
8063
9529
|
"checkpoints",
|
|
8064
9530
|
`complete-${this.currentSprint.id}.json`
|
|
8065
9531
|
);
|
|
8066
|
-
await fs14__default.mkdir(
|
|
9532
|
+
await fs14__default.mkdir(path15__default.dirname(checkpointPath), { recursive: true });
|
|
8067
9533
|
await fs14__default.writeFile(checkpointPath, JSON.stringify(this.checkpointState, null, 2), "utf-8");
|
|
8068
9534
|
}
|
|
8069
9535
|
return {
|
|
@@ -8084,7 +9550,7 @@ var CompleteExecutor = class {
|
|
|
8084
9550
|
const sprintId = checkpoint.resumePoint;
|
|
8085
9551
|
if (sprintId === "start") return;
|
|
8086
9552
|
try {
|
|
8087
|
-
const checkpointPath =
|
|
9553
|
+
const checkpointPath = path15__default.join(
|
|
8088
9554
|
context.projectPath,
|
|
8089
9555
|
".coco",
|
|
8090
9556
|
"checkpoints",
|
|
@@ -8337,8 +9803,8 @@ var CompleteExecutor = class {
|
|
|
8337
9803
|
};
|
|
8338
9804
|
const saveFiles = async (files) => {
|
|
8339
9805
|
for (const file of files) {
|
|
8340
|
-
const filePath =
|
|
8341
|
-
const dir =
|
|
9806
|
+
const filePath = path15__default.join(context.projectPath, file.path);
|
|
9807
|
+
const dir = path15__default.dirname(filePath);
|
|
8342
9808
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
8343
9809
|
if (file.action === "delete") {
|
|
8344
9810
|
await fs14__default.unlink(filePath).catch(() => {
|
|
@@ -8470,7 +9936,7 @@ var CompleteExecutor = class {
|
|
|
8470
9936
|
*/
|
|
8471
9937
|
async loadBacklog(projectPath) {
|
|
8472
9938
|
try {
|
|
8473
|
-
const backlogPath =
|
|
9939
|
+
const backlogPath = path15__default.join(projectPath, ".coco", "planning", "backlog.json");
|
|
8474
9940
|
const content = await fs14__default.readFile(backlogPath, "utf-8");
|
|
8475
9941
|
const data = JSON.parse(content);
|
|
8476
9942
|
return data.backlog;
|
|
@@ -8483,11 +9949,11 @@ var CompleteExecutor = class {
|
|
|
8483
9949
|
*/
|
|
8484
9950
|
async loadCurrentSprint(projectPath) {
|
|
8485
9951
|
try {
|
|
8486
|
-
const sprintsDir =
|
|
9952
|
+
const sprintsDir = path15__default.join(projectPath, ".coco", "planning", "sprints");
|
|
8487
9953
|
const files = await fs14__default.readdir(sprintsDir);
|
|
8488
9954
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
8489
9955
|
if (jsonFiles.length === 0) return null;
|
|
8490
|
-
const sprintPath =
|
|
9956
|
+
const sprintPath = path15__default.join(sprintsDir, jsonFiles[0] || "");
|
|
8491
9957
|
const content = await fs14__default.readFile(sprintPath, "utf-8");
|
|
8492
9958
|
const sprint = JSON.parse(content);
|
|
8493
9959
|
sprint.startDate = new Date(sprint.startDate);
|
|
@@ -8500,11 +9966,11 @@ var CompleteExecutor = class {
|
|
|
8500
9966
|
* Save sprint results
|
|
8501
9967
|
*/
|
|
8502
9968
|
async saveSprintResults(projectPath, result) {
|
|
8503
|
-
const resultsDir =
|
|
9969
|
+
const resultsDir = path15__default.join(projectPath, ".coco", "results");
|
|
8504
9970
|
await fs14__default.mkdir(resultsDir, { recursive: true });
|
|
8505
|
-
const resultsPath =
|
|
9971
|
+
const resultsPath = path15__default.join(resultsDir, `${result.sprintId}-results.json`);
|
|
8506
9972
|
await fs14__default.writeFile(resultsPath, JSON.stringify(result, null, 2), "utf-8");
|
|
8507
|
-
const mdPath =
|
|
9973
|
+
const mdPath = path15__default.join(resultsDir, `${result.sprintId}-results.md`);
|
|
8508
9974
|
await fs14__default.writeFile(mdPath, this.generateResultsMarkdown(result), "utf-8");
|
|
8509
9975
|
return resultsPath;
|
|
8510
9976
|
}
|
|
@@ -9729,8 +11195,8 @@ var OutputExecutor = class {
|
|
|
9729
11195
|
const cicdGenerator = new CICDGenerator(metadata, cicdConfig);
|
|
9730
11196
|
const cicdFiles = cicdGenerator.generate();
|
|
9731
11197
|
for (const file of cicdFiles) {
|
|
9732
|
-
const filePath =
|
|
9733
|
-
await this.ensureDir(
|
|
11198
|
+
const filePath = path15__default.join(context.projectPath, file.path);
|
|
11199
|
+
await this.ensureDir(path15__default.dirname(filePath));
|
|
9734
11200
|
await fs14__default.writeFile(filePath, file.content, "utf-8");
|
|
9735
11201
|
artifacts.push({
|
|
9736
11202
|
type: "cicd",
|
|
@@ -9741,7 +11207,7 @@ var OutputExecutor = class {
|
|
|
9741
11207
|
if (this.config.docker.enabled) {
|
|
9742
11208
|
const dockerGenerator = new DockerGenerator(metadata);
|
|
9743
11209
|
const dockerfile2 = dockerGenerator.generateDockerfile();
|
|
9744
|
-
const dockerfilePath =
|
|
11210
|
+
const dockerfilePath = path15__default.join(context.projectPath, "Dockerfile");
|
|
9745
11211
|
await fs14__default.writeFile(dockerfilePath, dockerfile2, "utf-8");
|
|
9746
11212
|
artifacts.push({
|
|
9747
11213
|
type: "deployment",
|
|
@@ -9749,7 +11215,7 @@ var OutputExecutor = class {
|
|
|
9749
11215
|
description: "Dockerfile"
|
|
9750
11216
|
});
|
|
9751
11217
|
const dockerignore = dockerGenerator.generateDockerignore();
|
|
9752
|
-
const dockerignorePath =
|
|
11218
|
+
const dockerignorePath = path15__default.join(context.projectPath, ".dockerignore");
|
|
9753
11219
|
await fs14__default.writeFile(dockerignorePath, dockerignore, "utf-8");
|
|
9754
11220
|
artifacts.push({
|
|
9755
11221
|
type: "deployment",
|
|
@@ -9758,7 +11224,7 @@ var OutputExecutor = class {
|
|
|
9758
11224
|
});
|
|
9759
11225
|
if (this.config.docker.compose) {
|
|
9760
11226
|
const compose = dockerGenerator.generateDockerCompose();
|
|
9761
|
-
const composePath =
|
|
11227
|
+
const composePath = path15__default.join(context.projectPath, "docker-compose.yml");
|
|
9762
11228
|
await fs14__default.writeFile(composePath, compose, "utf-8");
|
|
9763
11229
|
artifacts.push({
|
|
9764
11230
|
type: "deployment",
|
|
@@ -9770,7 +11236,7 @@ var OutputExecutor = class {
|
|
|
9770
11236
|
const docsGenerator = new DocsGenerator(metadata);
|
|
9771
11237
|
const docs = docsGenerator.generate();
|
|
9772
11238
|
if (this.config.docs.readme) {
|
|
9773
|
-
const readmePath =
|
|
11239
|
+
const readmePath = path15__default.join(context.projectPath, "README.md");
|
|
9774
11240
|
await fs14__default.writeFile(readmePath, docs.readme, "utf-8");
|
|
9775
11241
|
artifacts.push({
|
|
9776
11242
|
type: "documentation",
|
|
@@ -9779,7 +11245,7 @@ var OutputExecutor = class {
|
|
|
9779
11245
|
});
|
|
9780
11246
|
}
|
|
9781
11247
|
if (this.config.docs.contributing) {
|
|
9782
|
-
const contributingPath =
|
|
11248
|
+
const contributingPath = path15__default.join(context.projectPath, "CONTRIBUTING.md");
|
|
9783
11249
|
await fs14__default.writeFile(contributingPath, docs.contributing, "utf-8");
|
|
9784
11250
|
artifacts.push({
|
|
9785
11251
|
type: "documentation",
|
|
@@ -9788,7 +11254,7 @@ var OutputExecutor = class {
|
|
|
9788
11254
|
});
|
|
9789
11255
|
}
|
|
9790
11256
|
if (this.config.docs.changelog) {
|
|
9791
|
-
const changelogPath =
|
|
11257
|
+
const changelogPath = path15__default.join(context.projectPath, "CHANGELOG.md");
|
|
9792
11258
|
await fs14__default.writeFile(changelogPath, docs.changelog, "utf-8");
|
|
9793
11259
|
artifacts.push({
|
|
9794
11260
|
type: "documentation",
|
|
@@ -9797,10 +11263,10 @@ var OutputExecutor = class {
|
|
|
9797
11263
|
});
|
|
9798
11264
|
}
|
|
9799
11265
|
if (this.config.docs.api) {
|
|
9800
|
-
const docsDir =
|
|
11266
|
+
const docsDir = path15__default.join(context.projectPath, "docs");
|
|
9801
11267
|
await this.ensureDir(docsDir);
|
|
9802
11268
|
if (docs.api) {
|
|
9803
|
-
const apiPath =
|
|
11269
|
+
const apiPath = path15__default.join(docsDir, "api.md");
|
|
9804
11270
|
await fs14__default.writeFile(apiPath, docs.api, "utf-8");
|
|
9805
11271
|
artifacts.push({
|
|
9806
11272
|
type: "documentation",
|
|
@@ -9809,7 +11275,7 @@ var OutputExecutor = class {
|
|
|
9809
11275
|
});
|
|
9810
11276
|
}
|
|
9811
11277
|
if (docs.deployment) {
|
|
9812
|
-
const deployPath =
|
|
11278
|
+
const deployPath = path15__default.join(docsDir, "deployment.md");
|
|
9813
11279
|
await fs14__default.writeFile(deployPath, docs.deployment, "utf-8");
|
|
9814
11280
|
artifacts.push({
|
|
9815
11281
|
type: "documentation",
|
|
@@ -9818,7 +11284,7 @@ var OutputExecutor = class {
|
|
|
9818
11284
|
});
|
|
9819
11285
|
}
|
|
9820
11286
|
if (docs.development) {
|
|
9821
|
-
const devPath =
|
|
11287
|
+
const devPath = path15__default.join(docsDir, "development.md");
|
|
9822
11288
|
await fs14__default.writeFile(devPath, docs.development, "utf-8");
|
|
9823
11289
|
artifacts.push({
|
|
9824
11290
|
type: "documentation",
|
|
@@ -9884,16 +11350,16 @@ var OutputExecutor = class {
|
|
|
9884
11350
|
*/
|
|
9885
11351
|
async loadMetadata(projectPath) {
|
|
9886
11352
|
try {
|
|
9887
|
-
const packagePath =
|
|
11353
|
+
const packagePath = path15__default.join(projectPath, "package.json");
|
|
9888
11354
|
const content = await fs14__default.readFile(packagePath, "utf-8");
|
|
9889
11355
|
const pkg = JSON.parse(content);
|
|
9890
11356
|
let packageManager = "npm";
|
|
9891
11357
|
try {
|
|
9892
|
-
await fs14__default.access(
|
|
11358
|
+
await fs14__default.access(path15__default.join(projectPath, "pnpm-lock.yaml"));
|
|
9893
11359
|
packageManager = "pnpm";
|
|
9894
11360
|
} catch {
|
|
9895
11361
|
try {
|
|
9896
|
-
await fs14__default.access(
|
|
11362
|
+
await fs14__default.access(path15__default.join(projectPath, "yarn.lock"));
|
|
9897
11363
|
packageManager = "yarn";
|
|
9898
11364
|
} catch {
|
|
9899
11365
|
}
|
|
@@ -9905,7 +11371,7 @@ var OutputExecutor = class {
|
|
|
9905
11371
|
repository = pkg.repository.url;
|
|
9906
11372
|
}
|
|
9907
11373
|
return {
|
|
9908
|
-
name: pkg.name ||
|
|
11374
|
+
name: pkg.name || path15__default.basename(projectPath),
|
|
9909
11375
|
description: pkg.description || "",
|
|
9910
11376
|
version: pkg.version || "0.1.0",
|
|
9911
11377
|
language: "typescript",
|
|
@@ -9919,7 +11385,7 @@ var OutputExecutor = class {
|
|
|
9919
11385
|
};
|
|
9920
11386
|
} catch {
|
|
9921
11387
|
return {
|
|
9922
|
-
name:
|
|
11388
|
+
name: path15__default.basename(projectPath),
|
|
9923
11389
|
description: "",
|
|
9924
11390
|
version: "0.1.0",
|
|
9925
11391
|
language: "typescript",
|
|
@@ -9951,7 +11417,7 @@ var DEFAULT_RETRY_CONFIG = {
|
|
|
9951
11417
|
jitterFactor: 0.1
|
|
9952
11418
|
};
|
|
9953
11419
|
function sleep(ms) {
|
|
9954
|
-
return new Promise((
|
|
11420
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
9955
11421
|
}
|
|
9956
11422
|
function calculateDelay(baseDelay, jitterFactor, maxDelay) {
|
|
9957
11423
|
const jitter = baseDelay * jitterFactor * (Math.random() * 2 - 1);
|
|
@@ -10054,7 +11520,7 @@ var AnthropicProvider = class {
|
|
|
10054
11520
|
model: options?.model ?? this.config.model ?? DEFAULT_MODEL,
|
|
10055
11521
|
max_tokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
|
|
10056
11522
|
temperature: options?.temperature ?? this.config.temperature ?? 0,
|
|
10057
|
-
system: options?.system,
|
|
11523
|
+
system: this.extractSystem(messages, options?.system),
|
|
10058
11524
|
messages: this.convertMessages(messages),
|
|
10059
11525
|
stop_sequences: options?.stopSequences
|
|
10060
11526
|
});
|
|
@@ -10084,7 +11550,7 @@ var AnthropicProvider = class {
|
|
|
10084
11550
|
model: options?.model ?? this.config.model ?? DEFAULT_MODEL,
|
|
10085
11551
|
max_tokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
|
|
10086
11552
|
temperature: options?.temperature ?? this.config.temperature ?? 0,
|
|
10087
|
-
system: options?.system,
|
|
11553
|
+
system: this.extractSystem(messages, options?.system),
|
|
10088
11554
|
messages: this.convertMessages(messages),
|
|
10089
11555
|
tools: this.convertTools(options.tools),
|
|
10090
11556
|
tool_choice: options.toolChoice ? this.convertToolChoice(options.toolChoice) : void 0
|
|
@@ -10116,7 +11582,7 @@ var AnthropicProvider = class {
|
|
|
10116
11582
|
model: options?.model ?? this.config.model ?? DEFAULT_MODEL,
|
|
10117
11583
|
max_tokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
|
|
10118
11584
|
temperature: options?.temperature ?? this.config.temperature ?? 0,
|
|
10119
|
-
system: options?.system,
|
|
11585
|
+
system: this.extractSystem(messages, options?.system),
|
|
10120
11586
|
messages: this.convertMessages(messages)
|
|
10121
11587
|
});
|
|
10122
11588
|
for await (const event of stream) {
|
|
@@ -10142,7 +11608,7 @@ var AnthropicProvider = class {
|
|
|
10142
11608
|
model: options?.model ?? this.config.model ?? DEFAULT_MODEL,
|
|
10143
11609
|
max_tokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
|
|
10144
11610
|
temperature: options?.temperature ?? this.config.temperature ?? 0,
|
|
10145
|
-
system: options?.system,
|
|
11611
|
+
system: this.extractSystem(messages, options?.system),
|
|
10146
11612
|
messages: this.convertMessages(messages),
|
|
10147
11613
|
tools: this.convertTools(options.tools),
|
|
10148
11614
|
tool_choice: options.toolChoice ? this.convertToolChoice(options.toolChoice) : void 0
|
|
@@ -10265,6 +11731,22 @@ var AnthropicProvider = class {
|
|
|
10265
11731
|
});
|
|
10266
11732
|
}
|
|
10267
11733
|
}
|
|
11734
|
+
/**
|
|
11735
|
+
* Extract system prompt from messages array or options.
|
|
11736
|
+
*
|
|
11737
|
+
* The agent-loop passes the system message as the first element of the
|
|
11738
|
+
* messages array (role: "system"). convertMessages() strips it out because
|
|
11739
|
+
* Anthropic requires it as a top-level parameter — but all callers forgot
|
|
11740
|
+
* to also pass it via options.system. This helper bridges that gap.
|
|
11741
|
+
*/
|
|
11742
|
+
extractSystem(messages, optionsSystem) {
|
|
11743
|
+
if (optionsSystem !== void 0) return optionsSystem;
|
|
11744
|
+
const systemMsg = messages.find((m) => m.role === "system");
|
|
11745
|
+
if (!systemMsg) return void 0;
|
|
11746
|
+
if (typeof systemMsg.content === "string") return systemMsg.content;
|
|
11747
|
+
const text = systemMsg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
11748
|
+
return text || void 0;
|
|
11749
|
+
}
|
|
10268
11750
|
/**
|
|
10269
11751
|
* Convert messages to Anthropic format
|
|
10270
11752
|
*/
|
|
@@ -10425,6 +11907,7 @@ var CONTEXT_WINDOWS2 = {
|
|
|
10425
11907
|
"kimi-k2.5": 262144,
|
|
10426
11908
|
"kimi-k2-0324": 131072,
|
|
10427
11909
|
"kimi-latest": 131072,
|
|
11910
|
+
"kimi-for-coding": 131072,
|
|
10428
11911
|
"moonshot-v1-8k": 8e3,
|
|
10429
11912
|
"moonshot-v1-32k": 32e3,
|
|
10430
11913
|
"moonshot-v1-128k": 128e3,
|
|
@@ -10454,7 +11937,32 @@ var CONTEXT_WINDOWS2 = {
|
|
|
10454
11937
|
// Mistral models
|
|
10455
11938
|
"mistral-7b": 32768,
|
|
10456
11939
|
"mistral-nemo": 128e3,
|
|
10457
|
-
"mixtral-8x7b": 32768
|
|
11940
|
+
"mixtral-8x7b": 32768,
|
|
11941
|
+
// Mistral AI API models
|
|
11942
|
+
"codestral-latest": 32768,
|
|
11943
|
+
"mistral-large-latest": 131072,
|
|
11944
|
+
"mistral-small-latest": 131072,
|
|
11945
|
+
"open-mixtral-8x22b": 65536,
|
|
11946
|
+
// Groq-hosted models (same models, fast inference)
|
|
11947
|
+
"llama-3.3-70b-versatile": 128e3,
|
|
11948
|
+
"llama-3.1-8b-instant": 128e3,
|
|
11949
|
+
"mixtral-8x7b-32768": 32768,
|
|
11950
|
+
"gemma2-9b-it": 8192,
|
|
11951
|
+
// DeepSeek API models
|
|
11952
|
+
"deepseek-chat": 65536,
|
|
11953
|
+
"deepseek-reasoner": 65536,
|
|
11954
|
+
// Together AI model IDs
|
|
11955
|
+
"Qwen/Qwen2.5-Coder-32B-Instruct": 32768,
|
|
11956
|
+
"meta-llama/Meta-Llama-3.1-70B-Instruct": 128e3,
|
|
11957
|
+
"mistralai/Mixtral-8x7B-Instruct-v0.1": 32768,
|
|
11958
|
+
// HuggingFace model IDs
|
|
11959
|
+
"meta-llama/Llama-3.3-70B-Instruct": 128e3,
|
|
11960
|
+
"microsoft/Phi-4": 16384,
|
|
11961
|
+
// OpenRouter model IDs
|
|
11962
|
+
"anthropic/claude-opus-4-6": 2e5,
|
|
11963
|
+
"openai/gpt-5.3-codex": 4e5,
|
|
11964
|
+
"google/gemini-3-flash-preview": 1e6,
|
|
11965
|
+
"meta-llama/llama-3.3-70b-instruct": 128e3
|
|
10458
11966
|
};
|
|
10459
11967
|
var MODELS_WITHOUT_TEMPERATURE = [
|
|
10460
11968
|
"o1",
|
|
@@ -10476,7 +11984,12 @@ var LOCAL_MODEL_PATTERNS = [
|
|
|
10476
11984
|
"gemma",
|
|
10477
11985
|
"starcoder"
|
|
10478
11986
|
];
|
|
10479
|
-
var MODELS_WITH_THINKING_MODE = [
|
|
11987
|
+
var MODELS_WITH_THINKING_MODE = [
|
|
11988
|
+
"kimi-k2.5",
|
|
11989
|
+
"kimi-k2-0324",
|
|
11990
|
+
"kimi-latest",
|
|
11991
|
+
"kimi-for-coding"
|
|
11992
|
+
];
|
|
10480
11993
|
var OpenAIProvider = class {
|
|
10481
11994
|
id;
|
|
10482
11995
|
name;
|
|
@@ -10741,7 +12254,7 @@ var OpenAIProvider = class {
|
|
|
10741
12254
|
input = JSON.parse(repaired);
|
|
10742
12255
|
console.log(`[${this.name}] \u2713 Successfully repaired JSON for ${builder.name}`);
|
|
10743
12256
|
}
|
|
10744
|
-
} catch
|
|
12257
|
+
} catch {
|
|
10745
12258
|
console.error(
|
|
10746
12259
|
`[${this.name}] Cannot repair JSON for ${builder.name}, using empty object`
|
|
10747
12260
|
);
|
|
@@ -11095,6 +12608,20 @@ function createKimiProvider(config) {
|
|
|
11095
12608
|
}
|
|
11096
12609
|
return provider;
|
|
11097
12610
|
}
|
|
12611
|
+
function createKimiCodeProvider(config) {
|
|
12612
|
+
const provider = new OpenAIProvider("kimi-code", "Kimi Code");
|
|
12613
|
+
const kimiCodeConfig = {
|
|
12614
|
+
...config,
|
|
12615
|
+
baseUrl: config?.baseUrl ?? process.env["KIMI_CODE_BASE_URL"] ?? "https://api.kimi.com/coding/v1",
|
|
12616
|
+
apiKey: config?.apiKey ?? process.env["KIMI_CODE_API_KEY"],
|
|
12617
|
+
model: config?.model ?? "kimi-for-coding"
|
|
12618
|
+
};
|
|
12619
|
+
if (kimiCodeConfig.apiKey) {
|
|
12620
|
+
provider.initialize(kimiCodeConfig).catch(() => {
|
|
12621
|
+
});
|
|
12622
|
+
}
|
|
12623
|
+
return provider;
|
|
12624
|
+
}
|
|
11098
12625
|
var OAUTH_CONFIGS = {
|
|
11099
12626
|
/**
|
|
11100
12627
|
* OpenAI OAuth (ChatGPT Plus/Pro subscriptions)
|
|
@@ -11148,11 +12675,11 @@ async function refreshAccessToken(provider, refreshToken) {
|
|
|
11148
12675
|
}
|
|
11149
12676
|
function getTokenStoragePath(provider) {
|
|
11150
12677
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
11151
|
-
return
|
|
12678
|
+
return path15.join(home, ".coco", "tokens", `${provider}.json`);
|
|
11152
12679
|
}
|
|
11153
12680
|
async function saveTokens(provider, tokens) {
|
|
11154
12681
|
const filePath = getTokenStoragePath(provider);
|
|
11155
|
-
const dir =
|
|
12682
|
+
const dir = path15.dirname(filePath);
|
|
11156
12683
|
await fs14.mkdir(dir, { recursive: true, mode: 448 });
|
|
11157
12684
|
await fs14.writeFile(filePath, JSON.stringify(tokens, null, 2), { mode: 384 });
|
|
11158
12685
|
}
|
|
@@ -11197,6 +12724,17 @@ async function getValidAccessToken(provider) {
|
|
|
11197
12724
|
}
|
|
11198
12725
|
return { accessToken: tokens.accessToken, isNew: false };
|
|
11199
12726
|
}
|
|
12727
|
+
function detectWSL() {
|
|
12728
|
+
if (process.env.WSL_DISTRO_NAME || process.env.WSLENV) return true;
|
|
12729
|
+
try {
|
|
12730
|
+
return /microsoft/i.test(readFileSync("/proc/version", "utf-8"));
|
|
12731
|
+
} catch {
|
|
12732
|
+
return false;
|
|
12733
|
+
}
|
|
12734
|
+
}
|
|
12735
|
+
var isWSL = detectWSL();
|
|
12736
|
+
|
|
12737
|
+
// src/auth/flow.ts
|
|
11200
12738
|
promisify(execFile);
|
|
11201
12739
|
var execAsync2 = promisify(exec);
|
|
11202
12740
|
async function getADCAccessToken() {
|
|
@@ -11492,7 +13030,7 @@ var CodexProvider = class {
|
|
|
11492
13030
|
const chunk = content.slice(i, i + chunkSize);
|
|
11493
13031
|
yield { type: "text", text: chunk };
|
|
11494
13032
|
if (i + chunkSize < content.length) {
|
|
11495
|
-
await new Promise((
|
|
13033
|
+
await new Promise((resolve3) => setTimeout(resolve3, 5));
|
|
11496
13034
|
}
|
|
11497
13035
|
}
|
|
11498
13036
|
}
|
|
@@ -11587,7 +13125,7 @@ var GeminiProvider = class {
|
|
|
11587
13125
|
temperature: options?.temperature ?? this.config.temperature ?? 0,
|
|
11588
13126
|
stopSequences: options?.stopSequences
|
|
11589
13127
|
},
|
|
11590
|
-
systemInstruction: options?.system
|
|
13128
|
+
systemInstruction: this.extractSystem(messages, options?.system)
|
|
11591
13129
|
});
|
|
11592
13130
|
const { history, lastMessage } = this.convertMessages(messages);
|
|
11593
13131
|
const chat = model.startChat({ history });
|
|
@@ -11615,7 +13153,7 @@ var GeminiProvider = class {
|
|
|
11615
13153
|
maxOutputTokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
|
|
11616
13154
|
temperature: options?.temperature ?? this.config.temperature ?? 0
|
|
11617
13155
|
},
|
|
11618
|
-
systemInstruction: options?.system,
|
|
13156
|
+
systemInstruction: this.extractSystem(messages, options?.system),
|
|
11619
13157
|
tools,
|
|
11620
13158
|
toolConfig: {
|
|
11621
13159
|
functionCallingConfig: {
|
|
@@ -11644,7 +13182,7 @@ var GeminiProvider = class {
|
|
|
11644
13182
|
maxOutputTokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
|
|
11645
13183
|
temperature: options?.temperature ?? this.config.temperature ?? 0
|
|
11646
13184
|
},
|
|
11647
|
-
systemInstruction: options?.system
|
|
13185
|
+
systemInstruction: this.extractSystem(messages, options?.system)
|
|
11648
13186
|
});
|
|
11649
13187
|
const { history, lastMessage } = this.convertMessages(messages);
|
|
11650
13188
|
const chat = model.startChat({ history });
|
|
@@ -11678,7 +13216,7 @@ var GeminiProvider = class {
|
|
|
11678
13216
|
maxOutputTokens: options?.maxTokens ?? this.config.maxTokens ?? 8192,
|
|
11679
13217
|
temperature: options?.temperature ?? this.config.temperature ?? 0
|
|
11680
13218
|
},
|
|
11681
|
-
systemInstruction: options?.system,
|
|
13219
|
+
systemInstruction: this.extractSystem(messages, options?.system),
|
|
11682
13220
|
tools,
|
|
11683
13221
|
toolConfig: {
|
|
11684
13222
|
functionCallingConfig: {
|
|
@@ -11779,6 +13317,21 @@ var GeminiProvider = class {
|
|
|
11779
13317
|
});
|
|
11780
13318
|
}
|
|
11781
13319
|
}
|
|
13320
|
+
/**
|
|
13321
|
+
* Extract system prompt from messages array or options.
|
|
13322
|
+
*
|
|
13323
|
+
* convertMessages() skips system-role messages ("handled via systemInstruction"),
|
|
13324
|
+
* but all callers forgot to also pass it via options.system. This helper bridges
|
|
13325
|
+
* that gap — mirrors the same fix applied to AnthropicProvider.
|
|
13326
|
+
*/
|
|
13327
|
+
extractSystem(messages, optionsSystem) {
|
|
13328
|
+
if (optionsSystem !== void 0) return optionsSystem;
|
|
13329
|
+
const systemMsg = messages.find((m) => m.role === "system");
|
|
13330
|
+
if (!systemMsg) return void 0;
|
|
13331
|
+
if (typeof systemMsg.content === "string") return systemMsg.content;
|
|
13332
|
+
const text = systemMsg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
13333
|
+
return text || void 0;
|
|
13334
|
+
}
|
|
11782
13335
|
/**
|
|
11783
13336
|
* Convert messages to Gemini format
|
|
11784
13337
|
*/
|
|
@@ -11977,6 +13530,10 @@ async function createProvider(type, config = {}) {
|
|
|
11977
13530
|
provider = createKimiProvider(mergedConfig);
|
|
11978
13531
|
await provider.initialize(mergedConfig);
|
|
11979
13532
|
return provider;
|
|
13533
|
+
case "kimi-code":
|
|
13534
|
+
provider = createKimiCodeProvider(mergedConfig);
|
|
13535
|
+
await provider.initialize(mergedConfig);
|
|
13536
|
+
return provider;
|
|
11980
13537
|
case "lmstudio":
|
|
11981
13538
|
provider = new OpenAIProvider("lmstudio", "LM Studio");
|
|
11982
13539
|
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "http://localhost:1234/v1";
|
|
@@ -11987,6 +13544,30 @@ async function createProvider(type, config = {}) {
|
|
|
11987
13544
|
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "http://localhost:11434/v1";
|
|
11988
13545
|
mergedConfig.apiKey = mergedConfig.apiKey ?? "ollama";
|
|
11989
13546
|
break;
|
|
13547
|
+
case "groq":
|
|
13548
|
+
provider = new OpenAIProvider("groq", "Groq");
|
|
13549
|
+
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "https://api.groq.com/openai/v1";
|
|
13550
|
+
break;
|
|
13551
|
+
case "openrouter":
|
|
13552
|
+
provider = new OpenAIProvider("openrouter", "OpenRouter");
|
|
13553
|
+
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "https://openrouter.ai/api/v1";
|
|
13554
|
+
break;
|
|
13555
|
+
case "mistral":
|
|
13556
|
+
provider = new OpenAIProvider("mistral", "Mistral AI");
|
|
13557
|
+
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "https://api.mistral.ai/v1";
|
|
13558
|
+
break;
|
|
13559
|
+
case "deepseek":
|
|
13560
|
+
provider = new OpenAIProvider("deepseek", "DeepSeek");
|
|
13561
|
+
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "https://api.deepseek.com/v1";
|
|
13562
|
+
break;
|
|
13563
|
+
case "together":
|
|
13564
|
+
provider = new OpenAIProvider("together", "Together AI");
|
|
13565
|
+
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "https://api.together.xyz/v1";
|
|
13566
|
+
break;
|
|
13567
|
+
case "huggingface":
|
|
13568
|
+
provider = new OpenAIProvider("huggingface", "HuggingFace Inference");
|
|
13569
|
+
mergedConfig.baseUrl = mergedConfig.baseUrl ?? "https://api-inference.huggingface.co/v1";
|
|
13570
|
+
break;
|
|
11990
13571
|
default:
|
|
11991
13572
|
throw new ProviderError(`Unknown provider type: ${type}`, {
|
|
11992
13573
|
provider: type
|
|
@@ -12185,28 +13766,28 @@ async function createPhaseContext(config, state) {
|
|
|
12185
13766
|
};
|
|
12186
13767
|
const tools = {
|
|
12187
13768
|
file: {
|
|
12188
|
-
async read(
|
|
13769
|
+
async read(path38) {
|
|
12189
13770
|
const fs36 = await import('fs/promises');
|
|
12190
|
-
return fs36.readFile(
|
|
13771
|
+
return fs36.readFile(path38, "utf-8");
|
|
12191
13772
|
},
|
|
12192
|
-
async write(
|
|
13773
|
+
async write(path38, content) {
|
|
12193
13774
|
const fs36 = await import('fs/promises');
|
|
12194
13775
|
const nodePath = await import('path');
|
|
12195
|
-
await fs36.mkdir(nodePath.dirname(
|
|
12196
|
-
await fs36.writeFile(
|
|
13776
|
+
await fs36.mkdir(nodePath.dirname(path38), { recursive: true });
|
|
13777
|
+
await fs36.writeFile(path38, content, "utf-8");
|
|
12197
13778
|
},
|
|
12198
|
-
async exists(
|
|
13779
|
+
async exists(path38) {
|
|
12199
13780
|
const fs36 = await import('fs/promises');
|
|
12200
13781
|
try {
|
|
12201
|
-
await fs36.access(
|
|
13782
|
+
await fs36.access(path38);
|
|
12202
13783
|
return true;
|
|
12203
13784
|
} catch {
|
|
12204
13785
|
return false;
|
|
12205
13786
|
}
|
|
12206
13787
|
},
|
|
12207
13788
|
async glob(pattern) {
|
|
12208
|
-
const { glob:
|
|
12209
|
-
return
|
|
13789
|
+
const { glob: glob17 } = await import('glob');
|
|
13790
|
+
return glob17(pattern, { cwd: state.path });
|
|
12210
13791
|
}
|
|
12211
13792
|
},
|
|
12212
13793
|
bash: {
|
|
@@ -12478,7 +14059,22 @@ function generateId() {
|
|
|
12478
14059
|
return `proj_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
|
|
12479
14060
|
}
|
|
12480
14061
|
var ProviderConfigSchema = z.object({
|
|
12481
|
-
type: z.enum([
|
|
14062
|
+
type: z.enum([
|
|
14063
|
+
"anthropic",
|
|
14064
|
+
"openai",
|
|
14065
|
+
"codex",
|
|
14066
|
+
"gemini",
|
|
14067
|
+
"kimi",
|
|
14068
|
+
"kimi-code",
|
|
14069
|
+
"lmstudio",
|
|
14070
|
+
"ollama",
|
|
14071
|
+
"groq",
|
|
14072
|
+
"openrouter",
|
|
14073
|
+
"mistral",
|
|
14074
|
+
"deepseek",
|
|
14075
|
+
"together",
|
|
14076
|
+
"huggingface"
|
|
14077
|
+
]).default("anthropic"),
|
|
12482
14078
|
apiKey: z.string().optional(),
|
|
12483
14079
|
model: z.string().default("claude-sonnet-4-20250514"),
|
|
12484
14080
|
maxTokens: z.number().min(1).max(2e5).default(8192),
|
|
@@ -12509,7 +14105,7 @@ var StackConfigSchema = z.object({
|
|
|
12509
14105
|
profile: z.string().optional()
|
|
12510
14106
|
// Custom profile path
|
|
12511
14107
|
});
|
|
12512
|
-
var
|
|
14108
|
+
var ProjectConfigSchema2 = z.object({
|
|
12513
14109
|
name: z.string().min(1),
|
|
12514
14110
|
version: z.string().default("0.1.0"),
|
|
12515
14111
|
description: z.string().optional()
|
|
@@ -12582,8 +14178,22 @@ var ShipConfigSchema = z.object({
|
|
|
12582
14178
|
/** CI check poll interval in ms (default 15 seconds) */
|
|
12583
14179
|
ciCheckPollMs: z.number().default(15e3)
|
|
12584
14180
|
});
|
|
14181
|
+
var SkillsConfigSchema = z.object({
|
|
14182
|
+
/** Enable/disable skills system */
|
|
14183
|
+
enabled: z.boolean().default(true),
|
|
14184
|
+
/** Override global skills directory */
|
|
14185
|
+
globalDir: z.string().optional(),
|
|
14186
|
+
/** Override project skills directory */
|
|
14187
|
+
projectDir: z.string().optional(),
|
|
14188
|
+
/** Auto-activate skills based on user messages */
|
|
14189
|
+
autoActivate: z.boolean().default(true),
|
|
14190
|
+
/** Maximum concurrent active markdown skills */
|
|
14191
|
+
maxActiveSkills: z.number().min(1).max(10).default(3),
|
|
14192
|
+
/** List of skill IDs to disable */
|
|
14193
|
+
disabled: z.array(z.string()).default([])
|
|
14194
|
+
});
|
|
12585
14195
|
var CocoConfigSchema = z.object({
|
|
12586
|
-
project:
|
|
14196
|
+
project: ProjectConfigSchema2,
|
|
12587
14197
|
provider: ProviderConfigSchema.default({
|
|
12588
14198
|
type: "anthropic",
|
|
12589
14199
|
model: "claude-sonnet-4-20250514",
|
|
@@ -12609,7 +14219,8 @@ var CocoConfigSchema = z.object({
|
|
|
12609
14219
|
integrations: IntegrationsConfigSchema.optional(),
|
|
12610
14220
|
mcp: MCPConfigSchema.optional(),
|
|
12611
14221
|
tools: ToolsConfigSchema.optional(),
|
|
12612
|
-
ship: ShipConfigSchema.optional()
|
|
14222
|
+
ship: ShipConfigSchema.optional(),
|
|
14223
|
+
skills: SkillsConfigSchema.optional()
|
|
12613
14224
|
});
|
|
12614
14225
|
function createDefaultConfigObject(projectName, language = "typescript") {
|
|
12615
14226
|
return {
|
|
@@ -12652,7 +14263,7 @@ async function loadConfig(configPath) {
|
|
|
12652
14263
|
if (globalConfig) {
|
|
12653
14264
|
config = deepMergeConfig(config, globalConfig);
|
|
12654
14265
|
}
|
|
12655
|
-
const projectConfigPath = configPath ||
|
|
14266
|
+
const projectConfigPath = configPath || getProjectConfigPath2();
|
|
12656
14267
|
const projectConfig = await loadConfigFile(projectConfigPath);
|
|
12657
14268
|
if (projectConfig) {
|
|
12658
14269
|
config = deepMergeConfig(config, projectConfig);
|
|
@@ -12717,8 +14328,8 @@ function deepMergeConfig(base, override) {
|
|
|
12717
14328
|
...base.tools || override.tools ? { tools: { ...base.tools, ...override.tools } } : {}
|
|
12718
14329
|
};
|
|
12719
14330
|
}
|
|
12720
|
-
function
|
|
12721
|
-
return
|
|
14331
|
+
function getProjectConfigPath2() {
|
|
14332
|
+
return path15__default.join(process.cwd(), ".coco", "config.json");
|
|
12722
14333
|
}
|
|
12723
14334
|
async function saveConfig(config, configPath, global = false) {
|
|
12724
14335
|
const result = CocoConfigSchema.safeParse(config);
|
|
@@ -12729,11 +14340,11 @@ async function saveConfig(config, configPath, global = false) {
|
|
|
12729
14340
|
}));
|
|
12730
14341
|
throw new ConfigError("Cannot save invalid configuration", {
|
|
12731
14342
|
issues,
|
|
12732
|
-
configPath: configPath ||
|
|
14343
|
+
configPath: configPath || getProjectConfigPath2()
|
|
12733
14344
|
});
|
|
12734
14345
|
}
|
|
12735
|
-
const resolvedPath = configPath || (global ? CONFIG_PATHS.config :
|
|
12736
|
-
const dir =
|
|
14346
|
+
const resolvedPath = configPath || (global ? CONFIG_PATHS.config : getProjectConfigPath2());
|
|
14347
|
+
const dir = path15__default.dirname(resolvedPath);
|
|
12737
14348
|
await fs14__default.mkdir(dir, { recursive: true });
|
|
12738
14349
|
const content = JSON.stringify(result.data, null, 2);
|
|
12739
14350
|
await fs14__default.writeFile(resolvedPath, content, "utf-8");
|
|
@@ -12752,7 +14363,7 @@ async function configExists(configPath, scope = "any") {
|
|
|
12752
14363
|
}
|
|
12753
14364
|
if (scope === "project" || scope === "any") {
|
|
12754
14365
|
try {
|
|
12755
|
-
await fs14__default.access(
|
|
14366
|
+
await fs14__default.access(getProjectConfigPath2());
|
|
12756
14367
|
return true;
|
|
12757
14368
|
} catch {
|
|
12758
14369
|
if (scope === "project") return false;
|
|
@@ -12797,7 +14408,7 @@ function hasNullByte(str) {
|
|
|
12797
14408
|
}
|
|
12798
14409
|
function normalizePath(filePath) {
|
|
12799
14410
|
let normalized = filePath.replace(/\0/g, "");
|
|
12800
|
-
normalized =
|
|
14411
|
+
normalized = path15__default.normalize(normalized);
|
|
12801
14412
|
return normalized;
|
|
12802
14413
|
}
|
|
12803
14414
|
function isPathAllowed(filePath, operation) {
|
|
@@ -12805,31 +14416,31 @@ function isPathAllowed(filePath, operation) {
|
|
|
12805
14416
|
return { allowed: false, reason: "Path contains invalid characters" };
|
|
12806
14417
|
}
|
|
12807
14418
|
const normalized = normalizePath(filePath);
|
|
12808
|
-
const absolute =
|
|
14419
|
+
const absolute = path15__default.resolve(normalized);
|
|
12809
14420
|
const cwd = process.cwd();
|
|
12810
14421
|
for (const blocked of BLOCKED_PATHS) {
|
|
12811
|
-
const normalizedBlocked =
|
|
12812
|
-
if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked +
|
|
14422
|
+
const normalizedBlocked = path15__default.normalize(blocked);
|
|
14423
|
+
if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path15__default.sep)) {
|
|
12813
14424
|
return { allowed: false, reason: `Access to system path '${blocked}' is not allowed` };
|
|
12814
14425
|
}
|
|
12815
14426
|
}
|
|
12816
14427
|
const home = process.env.HOME;
|
|
12817
14428
|
if (home) {
|
|
12818
|
-
const normalizedHome =
|
|
12819
|
-
const normalizedCwd =
|
|
14429
|
+
const normalizedHome = path15__default.normalize(home);
|
|
14430
|
+
const normalizedCwd = path15__default.normalize(cwd);
|
|
12820
14431
|
if (absolute.startsWith(normalizedHome) && !absolute.startsWith(normalizedCwd)) {
|
|
12821
14432
|
if (isWithinAllowedPath(absolute, operation)) ; else if (operation === "read") {
|
|
12822
14433
|
const allowedHomeReads = [".gitconfig", ".zshrc", ".bashrc"];
|
|
12823
|
-
const
|
|
12824
|
-
if (!allowedHomeReads.includes(
|
|
12825
|
-
const targetDir =
|
|
14434
|
+
const basename5 = path15__default.basename(absolute);
|
|
14435
|
+
if (!allowedHomeReads.includes(basename5)) {
|
|
14436
|
+
const targetDir = path15__default.dirname(absolute);
|
|
12826
14437
|
return {
|
|
12827
14438
|
allowed: false,
|
|
12828
14439
|
reason: `Reading files outside project directory is not allowed. Use /allow-path ${targetDir} to grant access.`
|
|
12829
14440
|
};
|
|
12830
14441
|
}
|
|
12831
14442
|
} else {
|
|
12832
|
-
const targetDir =
|
|
14443
|
+
const targetDir = path15__default.dirname(absolute);
|
|
12833
14444
|
return {
|
|
12834
14445
|
allowed: false,
|
|
12835
14446
|
reason: `${operation} operations outside project directory are not allowed. Use /allow-path ${targetDir} to grant access.`
|
|
@@ -12838,12 +14449,12 @@ function isPathAllowed(filePath, operation) {
|
|
|
12838
14449
|
}
|
|
12839
14450
|
}
|
|
12840
14451
|
if (operation === "write" || operation === "delete") {
|
|
12841
|
-
const
|
|
14452
|
+
const basename5 = path15__default.basename(absolute);
|
|
12842
14453
|
for (const pattern of SENSITIVE_PATTERNS) {
|
|
12843
|
-
if (pattern.test(
|
|
14454
|
+
if (pattern.test(basename5)) {
|
|
12844
14455
|
return {
|
|
12845
14456
|
allowed: false,
|
|
12846
|
-
reason: `Operation on sensitive file '${
|
|
14457
|
+
reason: `Operation on sensitive file '${basename5}' requires explicit confirmation`
|
|
12847
14458
|
};
|
|
12848
14459
|
}
|
|
12849
14460
|
}
|
|
@@ -12874,7 +14485,7 @@ Examples:
|
|
|
12874
14485
|
async execute({ path: filePath, encoding, maxSize }) {
|
|
12875
14486
|
validatePath(filePath, "read");
|
|
12876
14487
|
try {
|
|
12877
|
-
const absolutePath =
|
|
14488
|
+
const absolutePath = path15__default.resolve(filePath);
|
|
12878
14489
|
const stats = await fs14__default.stat(absolutePath);
|
|
12879
14490
|
const maxBytes = maxSize ?? DEFAULT_MAX_FILE_SIZE;
|
|
12880
14491
|
let truncated = false;
|
|
@@ -12925,7 +14536,7 @@ Examples:
|
|
|
12925
14536
|
async execute({ path: filePath, content, createDirs, dryRun }) {
|
|
12926
14537
|
validatePath(filePath, "write");
|
|
12927
14538
|
try {
|
|
12928
|
-
const absolutePath =
|
|
14539
|
+
const absolutePath = path15__default.resolve(filePath);
|
|
12929
14540
|
let wouldCreate = false;
|
|
12930
14541
|
try {
|
|
12931
14542
|
await fs14__default.access(absolutePath);
|
|
@@ -12941,7 +14552,7 @@ Examples:
|
|
|
12941
14552
|
};
|
|
12942
14553
|
}
|
|
12943
14554
|
if (createDirs) {
|
|
12944
|
-
await fs14__default.mkdir(
|
|
14555
|
+
await fs14__default.mkdir(path15__default.dirname(absolutePath), { recursive: true });
|
|
12945
14556
|
}
|
|
12946
14557
|
await fs14__default.writeFile(absolutePath, content, "utf-8");
|
|
12947
14558
|
const stats = await fs14__default.stat(absolutePath);
|
|
@@ -12979,7 +14590,7 @@ Examples:
|
|
|
12979
14590
|
async execute({ path: filePath, oldText, newText, all, dryRun }) {
|
|
12980
14591
|
validatePath(filePath, "write");
|
|
12981
14592
|
try {
|
|
12982
|
-
const absolutePath =
|
|
14593
|
+
const absolutePath = path15__default.resolve(filePath);
|
|
12983
14594
|
let content = await fs14__default.readFile(absolutePath, "utf-8");
|
|
12984
14595
|
let replacements = 0;
|
|
12985
14596
|
if (all) {
|
|
@@ -13068,7 +14679,7 @@ Examples:
|
|
|
13068
14679
|
}),
|
|
13069
14680
|
async execute({ path: filePath }) {
|
|
13070
14681
|
try {
|
|
13071
|
-
const absolutePath =
|
|
14682
|
+
const absolutePath = path15__default.resolve(filePath);
|
|
13072
14683
|
const stats = await fs14__default.stat(absolutePath);
|
|
13073
14684
|
return {
|
|
13074
14685
|
exists: true,
|
|
@@ -13099,12 +14710,12 @@ Examples:
|
|
|
13099
14710
|
}),
|
|
13100
14711
|
async execute({ path: dirPath, recursive }) {
|
|
13101
14712
|
try {
|
|
13102
|
-
const absolutePath =
|
|
14713
|
+
const absolutePath = path15__default.resolve(dirPath);
|
|
13103
14714
|
const entries = [];
|
|
13104
14715
|
async function listDir(dir, prefix = "") {
|
|
13105
14716
|
const items = await fs14__default.readdir(dir, { withFileTypes: true });
|
|
13106
14717
|
for (const item of items) {
|
|
13107
|
-
const fullPath =
|
|
14718
|
+
const fullPath = path15__default.join(dir, item.name);
|
|
13108
14719
|
const relativePath = prefix ? `${prefix}/${item.name}` : item.name;
|
|
13109
14720
|
if (item.isDirectory()) {
|
|
13110
14721
|
entries.push({ name: relativePath, type: "directory" });
|
|
@@ -13151,7 +14762,7 @@ Examples:
|
|
|
13151
14762
|
}
|
|
13152
14763
|
validatePath(filePath, "delete");
|
|
13153
14764
|
try {
|
|
13154
|
-
const absolutePath =
|
|
14765
|
+
const absolutePath = path15__default.resolve(filePath);
|
|
13155
14766
|
const stats = await fs14__default.stat(absolutePath);
|
|
13156
14767
|
if (stats.isDirectory()) {
|
|
13157
14768
|
if (!recursive) {
|
|
@@ -13167,7 +14778,7 @@ Examples:
|
|
|
13167
14778
|
} catch (error) {
|
|
13168
14779
|
if (error instanceof ToolError) throw error;
|
|
13169
14780
|
if (error.code === "ENOENT") {
|
|
13170
|
-
return { deleted: false, path:
|
|
14781
|
+
return { deleted: false, path: path15__default.resolve(filePath) };
|
|
13171
14782
|
}
|
|
13172
14783
|
throw new FileSystemError(`Failed to delete: ${filePath}`, {
|
|
13173
14784
|
path: filePath,
|
|
@@ -13195,8 +14806,8 @@ Examples:
|
|
|
13195
14806
|
validatePath(source, "read");
|
|
13196
14807
|
validatePath(destination, "write");
|
|
13197
14808
|
try {
|
|
13198
|
-
const srcPath =
|
|
13199
|
-
const destPath =
|
|
14809
|
+
const srcPath = path15__default.resolve(source);
|
|
14810
|
+
const destPath = path15__default.resolve(destination);
|
|
13200
14811
|
if (!overwrite) {
|
|
13201
14812
|
try {
|
|
13202
14813
|
await fs14__default.access(destPath);
|
|
@@ -13212,7 +14823,7 @@ Examples:
|
|
|
13212
14823
|
}
|
|
13213
14824
|
}
|
|
13214
14825
|
}
|
|
13215
|
-
await fs14__default.mkdir(
|
|
14826
|
+
await fs14__default.mkdir(path15__default.dirname(destPath), { recursive: true });
|
|
13216
14827
|
await fs14__default.copyFile(srcPath, destPath);
|
|
13217
14828
|
const stats = await fs14__default.stat(destPath);
|
|
13218
14829
|
return {
|
|
@@ -13248,8 +14859,8 @@ Examples:
|
|
|
13248
14859
|
validatePath(source, "delete");
|
|
13249
14860
|
validatePath(destination, "write");
|
|
13250
14861
|
try {
|
|
13251
|
-
const srcPath =
|
|
13252
|
-
const destPath =
|
|
14862
|
+
const srcPath = path15__default.resolve(source);
|
|
14863
|
+
const destPath = path15__default.resolve(destination);
|
|
13253
14864
|
if (!overwrite) {
|
|
13254
14865
|
try {
|
|
13255
14866
|
await fs14__default.access(destPath);
|
|
@@ -13265,7 +14876,7 @@ Examples:
|
|
|
13265
14876
|
}
|
|
13266
14877
|
}
|
|
13267
14878
|
}
|
|
13268
|
-
await fs14__default.mkdir(
|
|
14879
|
+
await fs14__default.mkdir(path15__default.dirname(destPath), { recursive: true });
|
|
13269
14880
|
await fs14__default.rename(srcPath, destPath);
|
|
13270
14881
|
return {
|
|
13271
14882
|
source: srcPath,
|
|
@@ -13300,10 +14911,10 @@ Examples:
|
|
|
13300
14911
|
}),
|
|
13301
14912
|
async execute({ path: dirPath, depth, showHidden, dirsOnly }) {
|
|
13302
14913
|
try {
|
|
13303
|
-
const absolutePath =
|
|
14914
|
+
const absolutePath = path15__default.resolve(dirPath ?? ".");
|
|
13304
14915
|
let totalFiles = 0;
|
|
13305
14916
|
let totalDirs = 0;
|
|
13306
|
-
const lines = [
|
|
14917
|
+
const lines = [path15__default.basename(absolutePath) + "/"];
|
|
13307
14918
|
async function buildTree(dir, prefix, currentDepth) {
|
|
13308
14919
|
if (currentDepth > (depth ?? 4)) return;
|
|
13309
14920
|
let items = await fs14__default.readdir(dir, { withFileTypes: true });
|
|
@@ -13326,7 +14937,7 @@ Examples:
|
|
|
13326
14937
|
if (item.isDirectory()) {
|
|
13327
14938
|
totalDirs++;
|
|
13328
14939
|
lines.push(`${prefix}${connector}${item.name}/`);
|
|
13329
|
-
await buildTree(
|
|
14940
|
+
await buildTree(path15__default.join(dir, item.name), prefix + childPrefix, currentDepth + 1);
|
|
13330
14941
|
} else {
|
|
13331
14942
|
totalFiles++;
|
|
13332
14943
|
lines.push(`${prefix}${connector}${item.name}`);
|
|
@@ -14287,7 +15898,7 @@ var checkAgentCapabilityTool = defineTool({
|
|
|
14287
15898
|
var simpleAgentTools = [spawnSimpleAgentTool, checkAgentCapabilityTool];
|
|
14288
15899
|
async function detectTestFramework2(cwd) {
|
|
14289
15900
|
try {
|
|
14290
|
-
const pkgPath =
|
|
15901
|
+
const pkgPath = path15__default.join(cwd, "package.json");
|
|
14291
15902
|
const pkgContent = await fs14__default.readFile(pkgPath, "utf-8");
|
|
14292
15903
|
const pkg = JSON.parse(pkgContent);
|
|
14293
15904
|
const deps = {
|
|
@@ -14464,9 +16075,9 @@ Examples:
|
|
|
14464
16075
|
const projectDir = cwd ?? process.cwd();
|
|
14465
16076
|
try {
|
|
14466
16077
|
const coverageLocations = [
|
|
14467
|
-
|
|
14468
|
-
|
|
14469
|
-
|
|
16078
|
+
path15__default.join(projectDir, "coverage", "coverage-summary.json"),
|
|
16079
|
+
path15__default.join(projectDir, "coverage", "coverage-final.json"),
|
|
16080
|
+
path15__default.join(projectDir, ".nyc_output", "coverage-summary.json")
|
|
14470
16081
|
];
|
|
14471
16082
|
for (const location of coverageLocations) {
|
|
14472
16083
|
try {
|
|
@@ -14522,7 +16133,7 @@ Examples:
|
|
|
14522
16133
|
var testTools = [runTestsTool, getCoverageTool, runTestFileTool];
|
|
14523
16134
|
async function detectLinter2(cwd) {
|
|
14524
16135
|
try {
|
|
14525
|
-
const pkgPath =
|
|
16136
|
+
const pkgPath = path15__default.join(cwd, "package.json");
|
|
14526
16137
|
const pkgContent = await fs14__default.readFile(pkgPath, "utf-8");
|
|
14527
16138
|
const pkg = JSON.parse(pkgContent);
|
|
14528
16139
|
const deps = {
|
|
@@ -14710,8 +16321,8 @@ Examples:
|
|
|
14710
16321
|
}
|
|
14711
16322
|
});
|
|
14712
16323
|
async function findSourceFiles(cwd) {
|
|
14713
|
-
const { glob:
|
|
14714
|
-
return
|
|
16324
|
+
const { glob: glob17 } = await import('glob');
|
|
16325
|
+
return glob17("src/**/*.{ts,js,tsx,jsx}", {
|
|
14715
16326
|
cwd,
|
|
14716
16327
|
absolute: true,
|
|
14717
16328
|
ignore: ["**/*.test.*", "**/*.spec.*", "**/node_modules/**"]
|
|
@@ -14785,7 +16396,7 @@ Examples:
|
|
|
14785
16396
|
async execute({ cwd, files, useSnyk }) {
|
|
14786
16397
|
const projectDir = cwd ?? process.cwd();
|
|
14787
16398
|
try {
|
|
14788
|
-
const evaluator =
|
|
16399
|
+
const evaluator = createQualityEvaluatorWithRegistry(projectDir, useSnyk);
|
|
14789
16400
|
const evaluation = await evaluator.evaluate(files);
|
|
14790
16401
|
return evaluation.scores;
|
|
14791
16402
|
} catch (error) {
|
|
@@ -14828,7 +16439,7 @@ Examples:
|
|
|
14828
16439
|
caseSensitive,
|
|
14829
16440
|
wholeWord
|
|
14830
16441
|
}) {
|
|
14831
|
-
const targetPath = searchPath ?
|
|
16442
|
+
const targetPath = searchPath ? path15__default.resolve(searchPath) : process.cwd();
|
|
14832
16443
|
const matches = [];
|
|
14833
16444
|
let filesSearched = 0;
|
|
14834
16445
|
const filesWithMatches = /* @__PURE__ */ new Set();
|
|
@@ -14895,7 +16506,7 @@ Examples:
|
|
|
14895
16506
|
contextAfter.push(lines[j] ?? "");
|
|
14896
16507
|
}
|
|
14897
16508
|
matches.push({
|
|
14898
|
-
file:
|
|
16509
|
+
file: path15__default.relative(process.cwd(), file),
|
|
14899
16510
|
line: i + 1,
|
|
14900
16511
|
column: match.index + 1,
|
|
14901
16512
|
content: line,
|
|
@@ -14946,7 +16557,7 @@ Examples:
|
|
|
14946
16557
|
}),
|
|
14947
16558
|
async execute({ file, pattern, caseSensitive }) {
|
|
14948
16559
|
try {
|
|
14949
|
-
const absolutePath =
|
|
16560
|
+
const absolutePath = path15__default.resolve(file);
|
|
14950
16561
|
const content = await fs14__default.readFile(absolutePath, "utf-8");
|
|
14951
16562
|
const lines = content.split("\n");
|
|
14952
16563
|
const matches = [];
|
|
@@ -15123,7 +16734,7 @@ async function detectPackageManager(cwd) {
|
|
|
15123
16734
|
];
|
|
15124
16735
|
for (const { file, pm } of lockfiles) {
|
|
15125
16736
|
try {
|
|
15126
|
-
await fs14__default.access(
|
|
16737
|
+
await fs14__default.access(path15__default.join(cwd, file));
|
|
15127
16738
|
return pm;
|
|
15128
16739
|
} catch {
|
|
15129
16740
|
}
|
|
@@ -15388,7 +16999,7 @@ ${message}
|
|
|
15388
16999
|
});
|
|
15389
17000
|
try {
|
|
15390
17001
|
try {
|
|
15391
|
-
await fs14__default.access(
|
|
17002
|
+
await fs14__default.access(path15__default.join(projectDir, "Makefile"));
|
|
15392
17003
|
} catch {
|
|
15393
17004
|
throw new ToolError("No Makefile found in directory", { tool: "make" });
|
|
15394
17005
|
}
|
|
@@ -15560,7 +17171,32 @@ CONFIG_PATHS.home;
|
|
|
15560
17171
|
|
|
15561
17172
|
// src/cli/repl/session.ts
|
|
15562
17173
|
init_paths();
|
|
15563
|
-
|
|
17174
|
+
z.object({
|
|
17175
|
+
name: z.string().min(1).max(64),
|
|
17176
|
+
description: z.string().min(1).max(1024),
|
|
17177
|
+
version: z.string().default("1.0.0"),
|
|
17178
|
+
license: z.string().optional(),
|
|
17179
|
+
globs: z.union([z.string(), z.array(z.string())]).optional(),
|
|
17180
|
+
// skills.sh standard fields
|
|
17181
|
+
"disable-model-invocation": z.boolean().optional(),
|
|
17182
|
+
"allowed-tools": z.union([z.string(), z.array(z.string())]).optional(),
|
|
17183
|
+
"argument-hint": z.string().optional(),
|
|
17184
|
+
compatibility: z.string().max(500).optional(),
|
|
17185
|
+
model: z.string().optional(),
|
|
17186
|
+
context: z.enum(["fork", "agent", "inline"]).optional(),
|
|
17187
|
+
// Top-level tags/author (skills.sh style) — also accepted inside metadata
|
|
17188
|
+
tags: z.union([z.string(), z.array(z.string())]).optional(),
|
|
17189
|
+
author: z.string().optional(),
|
|
17190
|
+
// Nested metadata (Coco style)
|
|
17191
|
+
metadata: z.object({
|
|
17192
|
+
author: z.string().optional(),
|
|
17193
|
+
tags: z.array(z.string()).optional(),
|
|
17194
|
+
category: z.string().optional()
|
|
17195
|
+
}).passthrough().optional()
|
|
17196
|
+
});
|
|
17197
|
+
|
|
17198
|
+
// src/cli/repl/session.ts
|
|
17199
|
+
path15__default.dirname(CONFIG_PATHS.trustedTools);
|
|
15564
17200
|
CONFIG_PATHS.trustedTools;
|
|
15565
17201
|
|
|
15566
17202
|
// src/cli/repl/recommended-permissions.ts
|
|
@@ -15862,7 +17498,7 @@ async function enforceRateLimit() {
|
|
|
15862
17498
|
const now = Date.now();
|
|
15863
17499
|
const elapsed = now - lastRequestTime;
|
|
15864
17500
|
if (elapsed < MIN_REQUEST_INTERVAL_MS) {
|
|
15865
|
-
await new Promise((
|
|
17501
|
+
await new Promise((resolve3) => setTimeout(resolve3, MIN_REQUEST_INTERVAL_MS - elapsed));
|
|
15866
17502
|
}
|
|
15867
17503
|
lastRequestTime = Date.now();
|
|
15868
17504
|
}
|
|
@@ -16669,7 +18305,7 @@ var getTerminalWidth = () => process.stdout.columns || 80;
|
|
|
16669
18305
|
function stripAnsi(str) {
|
|
16670
18306
|
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
16671
18307
|
}
|
|
16672
|
-
function
|
|
18308
|
+
function detectLanguage2(filePath) {
|
|
16673
18309
|
const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
|
|
16674
18310
|
const extMap = {
|
|
16675
18311
|
ts: "typescript",
|
|
@@ -16714,7 +18350,7 @@ function renderDiff(diff, options) {
|
|
|
16714
18350
|
}
|
|
16715
18351
|
function renderFileBlock(file, opts) {
|
|
16716
18352
|
const { maxWidth, showLineNumbers, compact } = opts;
|
|
16717
|
-
const lang =
|
|
18353
|
+
const lang = detectLanguage2(file.path);
|
|
16718
18354
|
const contentWidth = Math.max(1, maxWidth - 4);
|
|
16719
18355
|
const typeLabel = file.type === "modified" ? "modified" : file.type === "added" ? "new file" : file.type === "deleted" ? "deleted" : `renamed from ${file.oldPath}`;
|
|
16720
18356
|
const statsLabel = ` +${file.additions} -${file.deletions}`;
|
|
@@ -16862,9 +18498,9 @@ async function fileExists(filePath) {
|
|
|
16862
18498
|
return false;
|
|
16863
18499
|
}
|
|
16864
18500
|
}
|
|
16865
|
-
async function fileExists2(
|
|
18501
|
+
async function fileExists2(path38) {
|
|
16866
18502
|
try {
|
|
16867
|
-
await access(
|
|
18503
|
+
await access(path38);
|
|
16868
18504
|
return true;
|
|
16869
18505
|
} catch {
|
|
16870
18506
|
return false;
|
|
@@ -17119,7 +18755,7 @@ async function checkTestCoverage(diff, cwd) {
|
|
|
17119
18755
|
);
|
|
17120
18756
|
if (!hasTestChange) {
|
|
17121
18757
|
const ext = src.path.match(/\.(ts|tsx|js|jsx)$/)?.[0] ?? ".ts";
|
|
17122
|
-
const testExists = await fileExists(
|
|
18758
|
+
const testExists = await fileExists(path15__default.join(cwd, `${baseName}.test${ext}`)) || await fileExists(path15__default.join(cwd, `${baseName}.spec${ext}`));
|
|
17123
18759
|
if (testExists) {
|
|
17124
18760
|
if (src.additions >= TEST_COVERAGE_LARGE_CHANGE_THRESHOLD) {
|
|
17125
18761
|
findings.push({
|
|
@@ -17338,8 +18974,8 @@ Examples:
|
|
|
17338
18974
|
});
|
|
17339
18975
|
var reviewTools = [reviewCodeTool];
|
|
17340
18976
|
var fs23 = await import('fs/promises');
|
|
17341
|
-
var
|
|
17342
|
-
var { glob:
|
|
18977
|
+
var path25 = await import('path');
|
|
18978
|
+
var { glob: glob14 } = await import('glob');
|
|
17343
18979
|
var DEFAULT_MAX_FILES = 200;
|
|
17344
18980
|
var LANGUAGE_EXTENSIONS = {
|
|
17345
18981
|
typescript: [".ts", ".tsx", ".mts", ".cts"],
|
|
@@ -17363,8 +18999,8 @@ var DEFAULT_EXCLUDES = [
|
|
|
17363
18999
|
"**/*.min.*",
|
|
17364
19000
|
"**/*.d.ts"
|
|
17365
19001
|
];
|
|
17366
|
-
function
|
|
17367
|
-
const ext =
|
|
19002
|
+
function detectLanguage3(filePath) {
|
|
19003
|
+
const ext = path25.extname(filePath).toLowerCase();
|
|
17368
19004
|
for (const [lang, extensions] of Object.entries(LANGUAGE_EXTENSIONS)) {
|
|
17369
19005
|
if (extensions.includes(ext)) return lang;
|
|
17370
19006
|
}
|
|
@@ -17773,7 +19409,7 @@ Examples:
|
|
|
17773
19409
|
}),
|
|
17774
19410
|
async execute({ path: rootPath, include, exclude, languages, maxFiles, depth }) {
|
|
17775
19411
|
const startTime = performance.now();
|
|
17776
|
-
const absPath =
|
|
19412
|
+
const absPath = path25.resolve(rootPath);
|
|
17777
19413
|
try {
|
|
17778
19414
|
const stat2 = await fs23.stat(absPath);
|
|
17779
19415
|
if (!stat2.isDirectory()) {
|
|
@@ -17800,7 +19436,7 @@ Examples:
|
|
|
17800
19436
|
pattern = `**/*{${allExts.join(",")}}`;
|
|
17801
19437
|
}
|
|
17802
19438
|
const excludePatterns = [...DEFAULT_EXCLUDES, ...exclude ?? []];
|
|
17803
|
-
const files = await
|
|
19439
|
+
const files = await glob14(pattern, {
|
|
17804
19440
|
cwd: absPath,
|
|
17805
19441
|
ignore: excludePatterns,
|
|
17806
19442
|
nodir: true,
|
|
@@ -17812,8 +19448,8 @@ Examples:
|
|
|
17812
19448
|
let totalDefinitions = 0;
|
|
17813
19449
|
let exportedSymbols = 0;
|
|
17814
19450
|
for (const file of limitedFiles) {
|
|
17815
|
-
const fullPath =
|
|
17816
|
-
const language =
|
|
19451
|
+
const fullPath = path25.join(absPath, file);
|
|
19452
|
+
const language = detectLanguage3(file);
|
|
17817
19453
|
if (!language) continue;
|
|
17818
19454
|
if (languages && !languages.includes(language)) {
|
|
17819
19455
|
continue;
|
|
@@ -17853,9 +19489,9 @@ Examples:
|
|
|
17853
19489
|
var codebaseMapTools = [codebaseMapTool];
|
|
17854
19490
|
init_paths();
|
|
17855
19491
|
var fs24 = await import('fs/promises');
|
|
17856
|
-
var
|
|
19492
|
+
var path26 = await import('path');
|
|
17857
19493
|
var crypto3 = await import('crypto');
|
|
17858
|
-
var GLOBAL_MEMORIES_DIR =
|
|
19494
|
+
var GLOBAL_MEMORIES_DIR = path26.join(COCO_HOME, "memories");
|
|
17859
19495
|
var PROJECT_MEMORIES_DIR = ".coco/memories";
|
|
17860
19496
|
var DEFAULT_MAX_MEMORIES = 1e3;
|
|
17861
19497
|
async function ensureDir(dirPath) {
|
|
@@ -17866,7 +19502,7 @@ function getMemoriesDir(scope) {
|
|
|
17866
19502
|
}
|
|
17867
19503
|
async function loadIndex(scope) {
|
|
17868
19504
|
const dir = getMemoriesDir(scope);
|
|
17869
|
-
const indexPath =
|
|
19505
|
+
const indexPath = path26.join(dir, "index.json");
|
|
17870
19506
|
try {
|
|
17871
19507
|
const content = await fs24.readFile(indexPath, "utf-8");
|
|
17872
19508
|
return JSON.parse(content);
|
|
@@ -17877,12 +19513,12 @@ async function loadIndex(scope) {
|
|
|
17877
19513
|
async function saveIndex(scope, index) {
|
|
17878
19514
|
const dir = getMemoriesDir(scope);
|
|
17879
19515
|
await ensureDir(dir);
|
|
17880
|
-
const indexPath =
|
|
19516
|
+
const indexPath = path26.join(dir, "index.json");
|
|
17881
19517
|
await fs24.writeFile(indexPath, JSON.stringify(index, null, 2), "utf-8");
|
|
17882
19518
|
}
|
|
17883
19519
|
async function loadMemory(scope, id) {
|
|
17884
19520
|
const dir = getMemoriesDir(scope);
|
|
17885
|
-
const memPath =
|
|
19521
|
+
const memPath = path26.join(dir, `${id}.json`);
|
|
17886
19522
|
try {
|
|
17887
19523
|
const content = await fs24.readFile(memPath, "utf-8");
|
|
17888
19524
|
return JSON.parse(content);
|
|
@@ -17893,7 +19529,7 @@ async function loadMemory(scope, id) {
|
|
|
17893
19529
|
async function saveMemory(scope, memory) {
|
|
17894
19530
|
const dir = getMemoriesDir(scope);
|
|
17895
19531
|
await ensureDir(dir);
|
|
17896
|
-
const memPath =
|
|
19532
|
+
const memPath = path26.join(dir, `${memory.id}.json`);
|
|
17897
19533
|
await fs24.writeFile(memPath, JSON.stringify(memory, null, 2), "utf-8");
|
|
17898
19534
|
}
|
|
17899
19535
|
var createMemoryTool = defineTool({
|
|
@@ -18225,8 +19861,8 @@ Examples:
|
|
|
18225
19861
|
});
|
|
18226
19862
|
var checkpointTools = [createCheckpointTool, restoreCheckpointTool, listCheckpointsTool];
|
|
18227
19863
|
var fs26 = await import('fs/promises');
|
|
18228
|
-
var
|
|
18229
|
-
var { glob:
|
|
19864
|
+
var path27 = await import('path');
|
|
19865
|
+
var { glob: glob15 } = await import('glob');
|
|
18230
19866
|
var INDEX_DIR = ".coco/search-index";
|
|
18231
19867
|
var DEFAULT_CHUNK_SIZE = 20;
|
|
18232
19868
|
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -18351,7 +19987,7 @@ async function getEmbedding(text) {
|
|
|
18351
19987
|
}
|
|
18352
19988
|
async function loadIndex2(indexDir) {
|
|
18353
19989
|
try {
|
|
18354
|
-
const indexPath =
|
|
19990
|
+
const indexPath = path27.join(indexDir, "index.json");
|
|
18355
19991
|
const content = await fs26.readFile(indexPath, "utf-8");
|
|
18356
19992
|
return JSON.parse(content);
|
|
18357
19993
|
} catch {
|
|
@@ -18360,11 +19996,11 @@ async function loadIndex2(indexDir) {
|
|
|
18360
19996
|
}
|
|
18361
19997
|
async function saveIndex2(indexDir, index) {
|
|
18362
19998
|
await fs26.mkdir(indexDir, { recursive: true });
|
|
18363
|
-
const indexPath =
|
|
19999
|
+
const indexPath = path27.join(indexDir, "index.json");
|
|
18364
20000
|
await fs26.writeFile(indexPath, JSON.stringify(index), "utf-8");
|
|
18365
20001
|
}
|
|
18366
20002
|
function isBinary(filePath) {
|
|
18367
|
-
return BINARY_EXTENSIONS.has(
|
|
20003
|
+
return BINARY_EXTENSIONS.has(path27.extname(filePath).toLowerCase());
|
|
18368
20004
|
}
|
|
18369
20005
|
var semanticSearchTool = defineTool({
|
|
18370
20006
|
name: "semantic_search",
|
|
@@ -18389,12 +20025,12 @@ Examples:
|
|
|
18389
20025
|
const effectivePath = rootPath ?? ".";
|
|
18390
20026
|
const effectiveMaxResults = maxResults ?? 10;
|
|
18391
20027
|
const effectiveThreshold = threshold ?? 0.3;
|
|
18392
|
-
const absPath =
|
|
18393
|
-
const indexDir =
|
|
20028
|
+
const absPath = path27.resolve(effectivePath);
|
|
20029
|
+
const indexDir = path27.join(absPath, INDEX_DIR);
|
|
18394
20030
|
let index = reindex ? null : await loadIndex2(indexDir);
|
|
18395
20031
|
if (!index) {
|
|
18396
20032
|
const pattern = include ?? "**/*";
|
|
18397
|
-
const files = await
|
|
20033
|
+
const files = await glob15(pattern, {
|
|
18398
20034
|
cwd: absPath,
|
|
18399
20035
|
ignore: DEFAULT_EXCLUDES2,
|
|
18400
20036
|
nodir: true,
|
|
@@ -18403,7 +20039,7 @@ Examples:
|
|
|
18403
20039
|
const chunks = [];
|
|
18404
20040
|
for (const file of files) {
|
|
18405
20041
|
if (isBinary(file)) continue;
|
|
18406
|
-
const fullPath =
|
|
20042
|
+
const fullPath = path27.join(absPath, file);
|
|
18407
20043
|
try {
|
|
18408
20044
|
const stat2 = await fs26.stat(fullPath);
|
|
18409
20045
|
const content = await fs26.readFile(fullPath, "utf-8");
|
|
@@ -18466,11 +20102,11 @@ Examples:
|
|
|
18466
20102
|
});
|
|
18467
20103
|
var semanticSearchTools = [semanticSearchTool];
|
|
18468
20104
|
var fs27 = await import('fs/promises');
|
|
18469
|
-
var
|
|
18470
|
-
var { glob:
|
|
20105
|
+
var path28 = await import('path');
|
|
20106
|
+
var { glob: glob16 } = await import('glob');
|
|
18471
20107
|
async function parseClassRelationships(rootPath, include) {
|
|
18472
20108
|
const pattern = include ?? "**/*.{ts,tsx,js,jsx}";
|
|
18473
|
-
const files = await
|
|
20109
|
+
const files = await glob16(pattern, {
|
|
18474
20110
|
cwd: rootPath,
|
|
18475
20111
|
ignore: ["**/node_modules/**", "**/dist/**", "**/*.test.*", "**/*.d.ts"],
|
|
18476
20112
|
nodir: true
|
|
@@ -18479,7 +20115,7 @@ async function parseClassRelationships(rootPath, include) {
|
|
|
18479
20115
|
const interfaces = [];
|
|
18480
20116
|
for (const file of files.slice(0, 100)) {
|
|
18481
20117
|
try {
|
|
18482
|
-
const content = await fs27.readFile(
|
|
20118
|
+
const content = await fs27.readFile(path28.join(rootPath, file), "utf-8");
|
|
18483
20119
|
const lines = content.split("\n");
|
|
18484
20120
|
for (let i = 0; i < lines.length; i++) {
|
|
18485
20121
|
const line = lines[i];
|
|
@@ -18605,7 +20241,7 @@ async function generateArchitectureDiagram(rootPath) {
|
|
|
18605
20241
|
const lines = ["graph TD"];
|
|
18606
20242
|
let nodeCount = 0;
|
|
18607
20243
|
let edgeCount = 0;
|
|
18608
|
-
const rootName =
|
|
20244
|
+
const rootName = path28.basename(rootPath);
|
|
18609
20245
|
lines.push(` ROOT["${rootName}"]`);
|
|
18610
20246
|
nodeCount++;
|
|
18611
20247
|
for (const dir of dirs) {
|
|
@@ -18615,7 +20251,7 @@ async function generateArchitectureDiagram(rootPath) {
|
|
|
18615
20251
|
nodeCount++;
|
|
18616
20252
|
edgeCount++;
|
|
18617
20253
|
try {
|
|
18618
|
-
const subEntries = await fs27.readdir(
|
|
20254
|
+
const subEntries = await fs27.readdir(path28.join(rootPath, dir.name), {
|
|
18619
20255
|
withFileTypes: true
|
|
18620
20256
|
});
|
|
18621
20257
|
const subDirs = subEntries.filter(
|
|
@@ -18738,7 +20374,7 @@ Examples:
|
|
|
18738
20374
|
tool: "generate_diagram"
|
|
18739
20375
|
});
|
|
18740
20376
|
}
|
|
18741
|
-
const absPath = rootPath ?
|
|
20377
|
+
const absPath = rootPath ? path28.resolve(rootPath) : process.cwd();
|
|
18742
20378
|
switch (type) {
|
|
18743
20379
|
case "class":
|
|
18744
20380
|
return generateClassDiagram(absPath, include);
|
|
@@ -18800,7 +20436,7 @@ Examples:
|
|
|
18800
20436
|
});
|
|
18801
20437
|
var diagramTools = [generateDiagramTool];
|
|
18802
20438
|
var fs28 = await import('fs/promises');
|
|
18803
|
-
var
|
|
20439
|
+
var path29 = await import('path');
|
|
18804
20440
|
var DEFAULT_MAX_PAGES = 20;
|
|
18805
20441
|
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
18806
20442
|
function parsePageRange(rangeStr, totalPages) {
|
|
@@ -18835,7 +20471,7 @@ Examples:
|
|
|
18835
20471
|
}),
|
|
18836
20472
|
async execute({ path: filePath, pages, maxPages }) {
|
|
18837
20473
|
const startTime = performance.now();
|
|
18838
|
-
const absPath =
|
|
20474
|
+
const absPath = path29.resolve(filePath);
|
|
18839
20475
|
try {
|
|
18840
20476
|
const stat2 = await fs28.stat(absPath);
|
|
18841
20477
|
if (!stat2.isFile()) {
|
|
@@ -18915,7 +20551,7 @@ Examples:
|
|
|
18915
20551
|
});
|
|
18916
20552
|
var pdfTools = [readPdfTool];
|
|
18917
20553
|
var fs29 = await import('fs/promises');
|
|
18918
|
-
var
|
|
20554
|
+
var path30 = await import('path');
|
|
18919
20555
|
var SUPPORTED_FORMATS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
|
|
18920
20556
|
var MAX_IMAGE_SIZE = 20 * 1024 * 1024;
|
|
18921
20557
|
var MIME_TYPES = {
|
|
@@ -18943,15 +20579,15 @@ Examples:
|
|
|
18943
20579
|
async execute({ path: filePath, prompt, provider }) {
|
|
18944
20580
|
const startTime = performance.now();
|
|
18945
20581
|
const effectivePrompt = prompt ?? "Describe this image in detail. If it's code or a UI, identify the key elements.";
|
|
18946
|
-
const absPath =
|
|
20582
|
+
const absPath = path30.resolve(filePath);
|
|
18947
20583
|
const cwd = process.cwd();
|
|
18948
|
-
if (!absPath.startsWith(cwd +
|
|
20584
|
+
if (!absPath.startsWith(cwd + path30.sep) && absPath !== cwd) {
|
|
18949
20585
|
throw new ToolError(
|
|
18950
20586
|
`Path traversal denied: '${filePath}' resolves outside the project directory`,
|
|
18951
20587
|
{ tool: "read_image" }
|
|
18952
20588
|
);
|
|
18953
20589
|
}
|
|
18954
|
-
const ext =
|
|
20590
|
+
const ext = path30.extname(absPath).toLowerCase();
|
|
18955
20591
|
if (!SUPPORTED_FORMATS.has(ext)) {
|
|
18956
20592
|
throw new ToolError(
|
|
18957
20593
|
`Unsupported image format '${ext}'. Supported: ${Array.from(SUPPORTED_FORMATS).join(", ")}`,
|
|
@@ -19093,7 +20729,7 @@ Examples:
|
|
|
19093
20729
|
}
|
|
19094
20730
|
});
|
|
19095
20731
|
var imageTools = [readImageTool];
|
|
19096
|
-
var
|
|
20732
|
+
var path31 = await import('path');
|
|
19097
20733
|
var DANGEROUS_PATTERNS2 = [
|
|
19098
20734
|
/\bDROP\s+(?:TABLE|DATABASE|INDEX|VIEW)\b/i,
|
|
19099
20735
|
/\bTRUNCATE\b/i,
|
|
@@ -19124,7 +20760,7 @@ Examples:
|
|
|
19124
20760
|
async execute({ database, query, params, readonly: isReadonlyParam }) {
|
|
19125
20761
|
const isReadonly = isReadonlyParam ?? true;
|
|
19126
20762
|
const startTime = performance.now();
|
|
19127
|
-
const absPath =
|
|
20763
|
+
const absPath = path31.resolve(database);
|
|
19128
20764
|
if (isReadonly && isDangerousSql(query)) {
|
|
19129
20765
|
throw new ToolError(
|
|
19130
20766
|
"Write operations (INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE) are blocked in readonly mode. Set readonly: false to allow writes.",
|
|
@@ -19197,7 +20833,7 @@ Examples:
|
|
|
19197
20833
|
}),
|
|
19198
20834
|
async execute({ database, table }) {
|
|
19199
20835
|
const startTime = performance.now();
|
|
19200
|
-
const absPath =
|
|
20836
|
+
const absPath = path31.resolve(database);
|
|
19201
20837
|
try {
|
|
19202
20838
|
const { default: Database } = await import('better-sqlite3');
|
|
19203
20839
|
const db = new Database(absPath, { readonly: true, fileMustExist: true });
|
|
@@ -19251,7 +20887,7 @@ Examples:
|
|
|
19251
20887
|
});
|
|
19252
20888
|
var databaseTools = [sqlQueryTool, inspectSchemaTool];
|
|
19253
20889
|
var fs30 = await import('fs/promises');
|
|
19254
|
-
var
|
|
20890
|
+
var path32 = await import('path');
|
|
19255
20891
|
var AnalyzeFileSchema = z.object({
|
|
19256
20892
|
filePath: z.string().describe("Path to file to analyze"),
|
|
19257
20893
|
includeAst: z.boolean().default(false).describe("Include AST in result")
|
|
@@ -19348,8 +20984,8 @@ async function analyzeFile(filePath, includeAst = false) {
|
|
|
19348
20984
|
};
|
|
19349
20985
|
}
|
|
19350
20986
|
async function analyzeDirectory(dirPath) {
|
|
19351
|
-
const { glob:
|
|
19352
|
-
const files = await
|
|
20987
|
+
const { glob: glob17 } = await import('glob');
|
|
20988
|
+
const files = await glob17("**/*.{ts,tsx,js,jsx}", {
|
|
19353
20989
|
cwd: dirPath,
|
|
19354
20990
|
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
|
|
19355
20991
|
absolute: true
|
|
@@ -19361,10 +20997,10 @@ async function analyzeDirectory(dirPath) {
|
|
|
19361
20997
|
try {
|
|
19362
20998
|
const analysis = await analyzeFile(file, false);
|
|
19363
20999
|
totalLines += analysis.lines;
|
|
19364
|
-
const ext =
|
|
21000
|
+
const ext = path32.extname(file);
|
|
19365
21001
|
filesByType[ext] = (filesByType[ext] || 0) + 1;
|
|
19366
21002
|
fileStats.push({
|
|
19367
|
-
file:
|
|
21003
|
+
file: path32.relative(dirPath, file),
|
|
19368
21004
|
lines: analysis.lines,
|
|
19369
21005
|
complexity: analysis.complexity.cyclomatic
|
|
19370
21006
|
});
|
|
@@ -19877,7 +21513,7 @@ var calculateCodeScoreTool = defineTool({
|
|
|
19877
21513
|
});
|
|
19878
21514
|
var smartSuggestionsTools = [suggestImprovementsTool, calculateCodeScoreTool];
|
|
19879
21515
|
var fs32 = await import('fs/promises');
|
|
19880
|
-
var
|
|
21516
|
+
var path33 = await import('path');
|
|
19881
21517
|
var ContextMemoryStore = class {
|
|
19882
21518
|
items = /* @__PURE__ */ new Map();
|
|
19883
21519
|
learnings = /* @__PURE__ */ new Map();
|
|
@@ -19897,7 +21533,7 @@ var ContextMemoryStore = class {
|
|
|
19897
21533
|
}
|
|
19898
21534
|
}
|
|
19899
21535
|
async save() {
|
|
19900
|
-
const dir =
|
|
21536
|
+
const dir = path33.dirname(this.storePath);
|
|
19901
21537
|
await fs32.mkdir(dir, { recursive: true });
|
|
19902
21538
|
const data = {
|
|
19903
21539
|
sessionId: this.sessionId,
|
|
@@ -20071,7 +21707,7 @@ var contextEnhancerTools = [
|
|
|
20071
21707
|
getLearnedPatternsTool
|
|
20072
21708
|
];
|
|
20073
21709
|
var fs33 = await import('fs/promises');
|
|
20074
|
-
var
|
|
21710
|
+
var path34 = await import('path');
|
|
20075
21711
|
async function discoverSkills(skillsDir) {
|
|
20076
21712
|
try {
|
|
20077
21713
|
const files = await fs33.readdir(skillsDir);
|
|
@@ -20087,7 +21723,7 @@ async function loadSkillMetadata(skillPath) {
|
|
|
20087
21723
|
const descMatch = content.match(/@description\s+(.+)/);
|
|
20088
21724
|
const versionMatch = content.match(/@version\s+(\S+)/);
|
|
20089
21725
|
return {
|
|
20090
|
-
name: nameMatch?.[1] ||
|
|
21726
|
+
name: nameMatch?.[1] || path34.basename(skillPath, path34.extname(skillPath)),
|
|
20091
21727
|
description: descMatch?.[1] || "No description",
|
|
20092
21728
|
version: versionMatch?.[1] || "1.0.0",
|
|
20093
21729
|
dependencies: []
|
|
@@ -20131,7 +21767,7 @@ var discoverSkillsTool = defineTool({
|
|
|
20131
21767
|
const { skillsDir } = input;
|
|
20132
21768
|
const skills = await discoverSkills(skillsDir);
|
|
20133
21769
|
const metadata = await Promise.all(
|
|
20134
|
-
skills.map((s) => loadSkillMetadata(
|
|
21770
|
+
skills.map((s) => loadSkillMetadata(path34.join(skillsDir, s)))
|
|
20135
21771
|
);
|
|
20136
21772
|
return {
|
|
20137
21773
|
skillsDir,
|
|
@@ -20622,16 +22258,18 @@ var DANGEROUS_ARG_PATTERNS = [
|
|
|
20622
22258
|
/\beval\s+/,
|
|
20623
22259
|
/\bcurl\s+.*\|\s*(ba)?sh/
|
|
20624
22260
|
];
|
|
20625
|
-
function
|
|
20626
|
-
|
|
22261
|
+
function getSystemOpen(target) {
|
|
22262
|
+
if (process.platform === "darwin") return { cmd: "open", args: [target] };
|
|
22263
|
+
if (isWSL) return { cmd: "cmd.exe", args: ["/c", "start", "", target] };
|
|
22264
|
+
return { cmd: "xdg-open", args: [target] };
|
|
20627
22265
|
}
|
|
20628
22266
|
function hasNullByte2(str) {
|
|
20629
22267
|
return str.includes("\0");
|
|
20630
22268
|
}
|
|
20631
22269
|
function isBlockedPath(absolute) {
|
|
20632
22270
|
for (const blocked of BLOCKED_PATHS2) {
|
|
20633
|
-
const normalizedBlocked =
|
|
20634
|
-
if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked +
|
|
22271
|
+
const normalizedBlocked = path15__default.normalize(blocked);
|
|
22272
|
+
if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path15__default.sep)) {
|
|
20635
22273
|
return blocked;
|
|
20636
22274
|
}
|
|
20637
22275
|
}
|
|
@@ -20689,7 +22327,7 @@ Examples:
|
|
|
20689
22327
|
throw new ToolError("Invalid file path", { tool: "open_file" });
|
|
20690
22328
|
}
|
|
20691
22329
|
const workDir = cwd ?? process.cwd();
|
|
20692
|
-
const absolute =
|
|
22330
|
+
const absolute = path15__default.isAbsolute(filePath) ? path15__default.normalize(filePath) : path15__default.resolve(workDir, filePath);
|
|
20693
22331
|
const blockedBy = isBlockedPath(absolute);
|
|
20694
22332
|
if (blockedBy) {
|
|
20695
22333
|
throw new ToolError(`Access to system path '${blockedBy}' is not allowed`, {
|
|
@@ -20702,8 +22340,8 @@ Examples:
|
|
|
20702
22340
|
throw new ToolError(`File not found: ${absolute}`, { tool: "open_file" });
|
|
20703
22341
|
}
|
|
20704
22342
|
if (mode === "open") {
|
|
20705
|
-
const cmd =
|
|
20706
|
-
await execa(cmd,
|
|
22343
|
+
const { cmd, args: args2 } = getSystemOpen(absolute);
|
|
22344
|
+
await execa(cmd, args2, { timeout: 1e4 });
|
|
20707
22345
|
return {
|
|
20708
22346
|
action: "opened",
|
|
20709
22347
|
path: absolute,
|
|
@@ -20712,14 +22350,14 @@ Examples:
|
|
|
20712
22350
|
};
|
|
20713
22351
|
}
|
|
20714
22352
|
if (isBlockedExecFile(absolute)) {
|
|
20715
|
-
throw new ToolError(`Execution of sensitive file is blocked: ${
|
|
22353
|
+
throw new ToolError(`Execution of sensitive file is blocked: ${path15__default.basename(absolute)}`, {
|
|
20716
22354
|
tool: "open_file"
|
|
20717
22355
|
});
|
|
20718
22356
|
}
|
|
20719
22357
|
if (args.length > 0 && hasDangerousArgs(args)) {
|
|
20720
22358
|
throw new ToolError("Arguments contain dangerous patterns", { tool: "open_file" });
|
|
20721
22359
|
}
|
|
20722
|
-
const ext =
|
|
22360
|
+
const ext = path15__default.extname(absolute);
|
|
20723
22361
|
const interpreter = getInterpreter(ext);
|
|
20724
22362
|
const executable = await isExecutable(absolute);
|
|
20725
22363
|
let command;
|
|
@@ -20732,7 +22370,7 @@ Examples:
|
|
|
20732
22370
|
cmdArgs = [...args];
|
|
20733
22371
|
} else {
|
|
20734
22372
|
throw new ToolError(
|
|
20735
|
-
`Cannot execute '${
|
|
22373
|
+
`Cannot execute '${path15__default.basename(absolute)}': no known interpreter for '${ext || "(no extension)"}' and file is not executable`,
|
|
20736
22374
|
{ tool: "open_file" }
|
|
20737
22375
|
);
|
|
20738
22376
|
}
|
|
@@ -20784,7 +22422,7 @@ Examples:
|
|
|
20784
22422
|
reason: z.string().optional().describe("Why access is needed (shown to user for context)")
|
|
20785
22423
|
}),
|
|
20786
22424
|
async execute({ path: dirPath, reason }) {
|
|
20787
|
-
const absolute =
|
|
22425
|
+
const absolute = path15__default.resolve(dirPath);
|
|
20788
22426
|
if (isWithinAllowedPath(absolute, "read")) {
|
|
20789
22427
|
return {
|
|
20790
22428
|
authorized: true,
|
|
@@ -20793,8 +22431,8 @@ Examples:
|
|
|
20793
22431
|
};
|
|
20794
22432
|
}
|
|
20795
22433
|
for (const blocked of BLOCKED_SYSTEM_PATHS) {
|
|
20796
|
-
const normalizedBlocked =
|
|
20797
|
-
if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked +
|
|
22434
|
+
const normalizedBlocked = path15__default.normalize(blocked);
|
|
22435
|
+
if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path15__default.sep)) {
|
|
20798
22436
|
return {
|
|
20799
22437
|
authorized: false,
|
|
20800
22438
|
path: absolute,
|
|
@@ -20803,7 +22441,7 @@ Examples:
|
|
|
20803
22441
|
}
|
|
20804
22442
|
}
|
|
20805
22443
|
const cwd = process.cwd();
|
|
20806
|
-
if (absolute ===
|
|
22444
|
+
if (absolute === path15__default.normalize(cwd) || absolute.startsWith(path15__default.normalize(cwd) + path15__default.sep)) {
|
|
20807
22445
|
return {
|
|
20808
22446
|
authorized: true,
|
|
20809
22447
|
path: absolute,
|
|
@@ -20827,7 +22465,7 @@ Examples:
|
|
|
20827
22465
|
};
|
|
20828
22466
|
}
|
|
20829
22467
|
const existing = getAllowedPaths();
|
|
20830
|
-
if (existing.some((e) =>
|
|
22468
|
+
if (existing.some((e) => path15__default.normalize(e.path) === path15__default.normalize(absolute))) {
|
|
20831
22469
|
return {
|
|
20832
22470
|
authorized: true,
|
|
20833
22471
|
path: absolute,
|