@corbat-tech/coco 1.9.0 → 2.0.0

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