@vibecodeqa/cli 0.39.0 → 0.40.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.
@@ -0,0 +1,27 @@
1
+ /** AI-powered code fixing — uses Claude to fix issues found by vcqa scan.
2
+ *
3
+ * Auth: ANTHROPIC_API_KEY (direct) or VCQA_PRO_KEY (via api.vibecodeqa.online).
4
+ */
5
+ import type { CheckResult } from "./types.js";
6
+ export interface FixableIssue {
7
+ check: string;
8
+ file: string;
9
+ line: number;
10
+ rule: string;
11
+ message: string;
12
+ suggestion: string | null;
13
+ }
14
+ export interface AiFixResult {
15
+ file: string;
16
+ line: number;
17
+ check: string;
18
+ message: string;
19
+ explanation: string;
20
+ applied: boolean;
21
+ }
22
+ /** Collect fixable issues from scan results, optionally filtered by check name. */
23
+ export declare function collectFixableIssues(checks: CheckResult[], suggestFix: (check: string, rule: string, message: string) => string | null, checkFilter?: string): FixableIssue[];
24
+ /** Fix issues using AI. Returns results for each attempted fix. */
25
+ export declare function aiFixIssues(cwd: string, issues: FixableIssue[], opts: {
26
+ dryRun: boolean;
27
+ }): Promise<AiFixResult[]>;
package/dist/ai-fix.js ADDED
@@ -0,0 +1,216 @@
1
+ /** AI-powered code fixing — uses Claude to fix issues found by vcqa scan.
2
+ *
3
+ * Auth: ANTHROPIC_API_KEY (direct) or VCQA_PRO_KEY (via api.vibecodeqa.online).
4
+ */
5
+ import { readFileSync, writeFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { getCheckMeta } from "./check-meta.js";
8
+ /** Collect fixable issues from scan results, optionally filtered by check name. */
9
+ export function collectFixableIssues(checks, suggestFix, checkFilter) {
10
+ const issues = [];
11
+ for (const c of checks) {
12
+ if (checkFilter && c.name !== checkFilter)
13
+ continue;
14
+ for (const iss of c.issues) {
15
+ if (!iss.file || typeof iss.file !== "string" || !iss.line)
16
+ continue;
17
+ if (iss.severity === "info")
18
+ continue;
19
+ issues.push({
20
+ check: c.name,
21
+ file: iss.file,
22
+ line: iss.line,
23
+ rule: iss.rule || "",
24
+ message: iss.message,
25
+ suggestion: suggestFix(c.name, iss.rule || "", iss.message),
26
+ });
27
+ }
28
+ }
29
+ return issues;
30
+ }
31
+ /** Fix issues using AI. Returns results for each attempted fix. */
32
+ export async function aiFixIssues(cwd, issues, opts) {
33
+ const apiKey = process.env.ANTHROPIC_API_KEY || "";
34
+ const proKey = process.env.VCQA_PRO_KEY || "";
35
+ if (!apiKey && !proKey) {
36
+ console.log(" \x1b[31mNo API key found.\x1b[0m Set ANTHROPIC_API_KEY or VCQA_PRO_KEY.");
37
+ console.log(" \x1b[2mANTHROPIC_API_KEY — direct Claude API (recommended)\x1b[0m");
38
+ console.log(" \x1b[2mVCQA_PRO_KEY — via api.vibecodeqa.online\x1b[0m");
39
+ return [];
40
+ }
41
+ const results = [];
42
+ // Process files in order, one issue at a time to avoid drift
43
+ const sorted = [...issues].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
44
+ // Limit to 10 fixes per run to avoid burning tokens
45
+ const batch = sorted.slice(0, 10);
46
+ if (sorted.length > 10) {
47
+ console.log(` \x1b[2mFixing 10 of ${sorted.length} issues (run again for more)\x1b[0m`);
48
+ }
49
+ for (const issue of batch) {
50
+ const absPath = join(cwd, issue.file);
51
+ let content;
52
+ try {
53
+ content = readFileSync(absPath, "utf-8");
54
+ }
55
+ catch {
56
+ results.push({ ...issue, explanation: "file not readable", applied: false });
57
+ continue;
58
+ }
59
+ const lines = content.split("\n");
60
+ const meta = getCheckMeta(issue.check);
61
+ // Extract context: ±20 lines around the issue
62
+ const start = Math.max(0, issue.line - 21);
63
+ const end = Math.min(lines.length, issue.line + 20);
64
+ const contextLines = lines.slice(start, end);
65
+ const numbered = contextLines.map((l, i) => `${start + i + 1}| ${l}`).join("\n");
66
+ const prompt = buildFixPrompt(issue, meta, numbered, issue.file);
67
+ process.stdout.write(` ${issue.file}:${issue.line} \x1b[2m${issue.message.slice(0, 50)}\x1b[0m `);
68
+ const fix = apiKey
69
+ ? await callAnthropicDirect(prompt, apiKey)
70
+ : await callVcqaProxy(prompt, proKey);
71
+ if (!fix) {
72
+ console.log("\x1b[33mskip\x1b[0m");
73
+ results.push({ ...issue, explanation: "AI could not generate fix", applied: false });
74
+ continue;
75
+ }
76
+ if (opts.dryRun) {
77
+ console.log("\x1b[36mdry-run\x1b[0m");
78
+ console.log(` \x1b[2m${fix.explanation}\x1b[0m`);
79
+ results.push({ ...issue, explanation: fix.explanation, applied: false });
80
+ continue;
81
+ }
82
+ // Apply search/replace
83
+ const searchNormalized = fix.search.trim();
84
+ if (!content.includes(searchNormalized)) {
85
+ console.log("\x1b[33mskip (no match)\x1b[0m");
86
+ results.push({ ...issue, explanation: "search text not found in file", applied: false });
87
+ continue;
88
+ }
89
+ const idx = content.indexOf(searchNormalized);
90
+ const updated = content.slice(0, idx) + fix.replace.trim() + content.slice(idx + searchNormalized.length);
91
+ writeFileSync(absPath, updated);
92
+ console.log("\x1b[32mfixed\x1b[0m");
93
+ console.log(` \x1b[2m${fix.explanation}\x1b[0m`);
94
+ results.push({ ...issue, explanation: fix.explanation, applied: true });
95
+ }
96
+ return results;
97
+ }
98
+ function buildFixPrompt(issue, meta, codeContext, filePath) {
99
+ const ext = filePath.split(".").pop() || "ts";
100
+ const lang = ext === "vue" ? "vue" : ext === "svelte" ? "svelte" : ext === "dart" ? "dart" : "typescript";
101
+ let prompt = `Fix this code quality issue. Return a JSON object with search/replace strings.
102
+
103
+ ## Issue
104
+ - Check: ${issue.check}
105
+ - Rule: ${issue.rule || "n/a"}
106
+ - Message: ${issue.message}
107
+ - File: ${filePath}:${issue.line}
108
+
109
+ ## Why this matters
110
+ ${meta.risk}
111
+
112
+ ## Recommended approach
113
+ ${meta.recommendation}`;
114
+ if (issue.suggestion) {
115
+ prompt += `\n\nSpecific suggestion: ${issue.suggestion}`;
116
+ }
117
+ prompt += `
118
+
119
+ ## Code context (${filePath})
120
+ \`\`\`${lang}
121
+ ${codeContext}
122
+ \`\`\`
123
+
124
+ ## Instructions
125
+ Return ONLY a valid JSON object (no markdown, no explanation outside JSON):
126
+ {
127
+ "search": "exact lines from the code above that need to change (copy verbatim, preserve whitespace)",
128
+ "replace": "the fixed replacement code (same indentation)",
129
+ "explanation": "one sentence: what changed and why"
130
+ }
131
+
132
+ Rules:
133
+ - The "search" string MUST appear verbatim in the file — copy it exactly
134
+ - Keep the fix minimal — change only what's needed to resolve the issue
135
+ - Preserve surrounding code, comments, and formatting
136
+ - Do not add imports unless absolutely required
137
+ - Match the existing code style`;
138
+ return prompt;
139
+ }
140
+ let _apiErrorShown = false;
141
+ async function callAnthropicDirect(prompt, apiKey) {
142
+ try {
143
+ const res = await fetch("https://api.anthropic.com/v1/messages", {
144
+ method: "POST",
145
+ headers: {
146
+ "Content-Type": "application/json",
147
+ "x-api-key": apiKey,
148
+ "anthropic-version": "2023-06-01",
149
+ },
150
+ body: JSON.stringify({
151
+ model: "claude-haiku-4-5-20251001",
152
+ max_tokens: 1024,
153
+ messages: [{ role: "user", content: prompt }],
154
+ }),
155
+ });
156
+ if (!res.ok) {
157
+ if (!_apiErrorShown) {
158
+ const err = (await res.json().catch(() => null));
159
+ console.log(`\n \x1b[31mAPI error:\x1b[0m ${err?.error?.message || `HTTP ${res.status}`}\n`);
160
+ _apiErrorShown = true;
161
+ }
162
+ return null;
163
+ }
164
+ const data = (await res.json());
165
+ const text = data.content?.find((c) => c.type === "text")?.text;
166
+ if (!text)
167
+ return null;
168
+ return parseFixResponse(text);
169
+ }
170
+ catch {
171
+ return null;
172
+ }
173
+ }
174
+ async function callVcqaProxy(prompt, proKey) {
175
+ try {
176
+ const res = await fetch("https://api.vibecodeqa.online/api/pro/fix", {
177
+ method: "POST",
178
+ headers: {
179
+ "Content-Type": "application/json",
180
+ Authorization: `Bearer ${proKey}`,
181
+ },
182
+ body: JSON.stringify({ prompt }),
183
+ });
184
+ if (!res.ok)
185
+ return null;
186
+ const data = (await res.json());
187
+ if (!data.search || !data.replace)
188
+ return null;
189
+ return { search: data.search, replace: data.replace, explanation: data.explanation || "" };
190
+ }
191
+ catch {
192
+ return null;
193
+ }
194
+ }
195
+ function parseFixResponse(text) {
196
+ try {
197
+ // Try to extract JSON from the response (handle markdown fences)
198
+ let json = text.trim();
199
+ const fenceMatch = json.match(/```(?:json)?\s*([\s\S]*?)```/);
200
+ if (fenceMatch)
201
+ json = fenceMatch[1].trim();
202
+ const parsed = JSON.parse(json);
203
+ if (!parsed.search || typeof parsed.search !== "string")
204
+ return null;
205
+ if (!parsed.replace || typeof parsed.replace !== "string")
206
+ return null;
207
+ return {
208
+ search: parsed.search,
209
+ replace: parsed.replace,
210
+ explanation: parsed.explanation || "",
211
+ };
212
+ }
213
+ catch {
214
+ return null;
215
+ }
216
+ }
package/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  /** vibe-check — code health scanner for the AI coding era. */
3
3
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
4
4
  import { join, resolve } from "node:path";
5
+ import { aiFixIssues, collectFixableIssues } from "./ai-fix.js";
5
6
  import { getCheckMeta } from "./check-meta.js";
6
7
  import { getCheckIgnore, isCheckEnabled, loadConfig } from "./config.js";
7
8
  import { detectRepoUrl, detectStack, detectWorkspace } from "./detect.js";
@@ -387,6 +388,9 @@ function printHelp() {
387
388
  \x1b[1mCommands:\x1b[0m
388
389
  init [path] Set up CI workflow + recommended configs
389
390
  fix [path] Auto-fix (.gitignore, strict mode, biome/eslint, suggestions)
391
+ --ai Use Claude to fix remaining issues (needs ANTHROPIC_API_KEY)
392
+ --check NAME Only fix issues from a specific check (e.g. --check security)
393
+ --dry-run Show what AI would fix without applying changes
390
394
  explain [check] Deep-dive explanation of a check (what/risk/fix)
391
395
  monitor [path] Live quality control panel — re-scans on file changes
392
396
 
@@ -411,6 +415,9 @@ function printHelp() {
411
415
  npx @vibecodeqa/cli # scan current directory
412
416
  npx @vibecodeqa/cli init # set up CI + configs
413
417
  npx @vibecodeqa/cli fix # auto-fix what's fixable
418
+ npx @vibecodeqa/cli fix --ai # AI-powered fix (uses Claude)
419
+ npx @vibecodeqa/cli fix --ai --check security # fix only security issues
420
+ npx @vibecodeqa/cli fix --ai --dry-run # preview AI fixes without applying
414
421
  npx @vibecodeqa/cli --skip-tests --top # fast scan with top issues
415
422
  npx @vibecodeqa/cli --ci --fail-under 80 # CI with quality gate
416
423
  `);
@@ -548,9 +555,9 @@ async function runExplain(checkName) {
548
555
  console.log("");
549
556
  }
550
557
  // ── fix command ──
551
- async function runFix(cwd) {
558
+ async function runFix(cwd, opts = {}) {
552
559
  console.log("");
553
- console.log(` \x1b[1m\x1b[38;5;141mvcqa fix\x1b[0m`);
560
+ console.log(` \x1b[1m\x1b[38;5;141mvcqa fix${opts.ai ? " --ai" : ""}${opts.dryRun ? " --dry-run" : ""}${opts.checkFilter ? ` --check ${opts.checkFilter}` : ""}\x1b[0m`);
554
561
  console.log(` \x1b[2m${cwd}\x1b[0m`);
555
562
  console.log("");
556
563
  validateCwd(cwd);
@@ -621,7 +628,39 @@ async function runFix(cwd) {
621
628
  const isDart = enrichedStack.language === "dart";
622
629
  const checks = await runChecks(cwd, enrichedStack, workspace, true, isDart, true);
623
630
  const score = computeScore(checks);
624
- // Collect actionable issues with fix suggestions
631
+ // AI-powered fix mode
632
+ if (opts.ai) {
633
+ const aiIssues = collectFixableIssues(checks, suggestFix, opts.checkFilter);
634
+ if (aiIssues.length === 0) {
635
+ console.log(" \x1b[2mNo fixable issues found.\x1b[0m");
636
+ }
637
+ else {
638
+ console.log(` \x1b[1mAI fixing ${Math.min(aiIssues.length, 10)} issues${opts.dryRun ? " (dry run)" : ""}...\x1b[0m`);
639
+ console.log("");
640
+ const results = await aiFixIssues(cwd, aiIssues, { dryRun: opts.dryRun || false });
641
+ const applied = results.filter((r) => r.applied).length;
642
+ if (applied > 0) {
643
+ // Re-scan to show new score
644
+ console.log("");
645
+ console.log(" \x1b[1mRe-scanning...\x1b[0m");
646
+ const reChecks = await runChecks(cwd, enrichedStack, workspace, true, isDart, true);
647
+ const newScore = computeScore(reChecks);
648
+ const newGrade = gradeFromScore(newScore);
649
+ const delta = newScore - score;
650
+ console.log(` Score: \x1b[${newScore >= 75 ? "32" : newScore >= 60 ? "33" : "31"}m${newGrade} ${newScore}/100\x1b[0m${delta > 0 ? ` \x1b[32m(+${delta})\x1b[0m` : ""}`);
651
+ console.log(` \x1b[32m${applied} AI fix(es) applied.\x1b[0m Re-run \x1b[1mnpx @vibecodeqa/cli\x1b[0m for full report.`);
652
+ }
653
+ else {
654
+ const grade = gradeFromScore(score);
655
+ console.log(`\n Score: \x1b[${score >= 75 ? "32" : score >= 60 ? "33" : "31"}m${grade} ${score}/100\x1b[0m`);
656
+ if (opts.dryRun)
657
+ console.log(" \x1b[2mDry run — no files modified. Remove --dry-run to apply.\x1b[0m");
658
+ }
659
+ }
660
+ console.log("");
661
+ return;
662
+ }
663
+ // Collect actionable issues with fix suggestions (non-AI mode)
625
664
  const fixable = [];
626
665
  for (const c of checks) {
627
666
  for (const iss of c.issues) {
@@ -826,8 +865,13 @@ async function main() {
826
865
  return;
827
866
  }
828
867
  if (args[0] === "fix") {
829
- const path = args.slice(1).find((a) => !a.startsWith("-")) || ".";
830
- await runFix(resolve(path));
868
+ const fixArgs = args.slice(1);
869
+ const path = fixArgs.find((a) => !a.startsWith("-")) || ".";
870
+ const aiMode = fixArgs.includes("--ai");
871
+ const dryRun = fixArgs.includes("--dry-run");
872
+ const checkIdx = fixArgs.indexOf("--check");
873
+ const checkFilter = checkIdx !== -1 ? fixArgs[checkIdx + 1] : undefined;
874
+ await runFix(resolve(path), { ai: aiMode, dryRun, checkFilter });
831
875
  return;
832
876
  }
833
877
  if (args[0] === "explain") {
@@ -907,11 +951,11 @@ async function main() {
907
951
  emitAnnotations(report);
908
952
  }
909
953
  if (flags.uploadMode) {
910
- await handleUpload(report, cwd, jsonOnly);
954
+ await handleUpload(report, cwd, quietMode);
911
955
  }
912
956
  if (flags.prComment) {
913
957
  const posted = await postPRComment(report, trend, cwd);
914
- if (!jsonOnly) {
958
+ if (!quietMode) {
915
959
  if (posted)
916
960
  console.log(" \x1b[32m\u2713 PR comment posted\x1b[0m");
917
961
  else
@@ -921,12 +965,12 @@ async function main() {
921
965
  // CI exit code: fail if score below threshold (skip in watch mode)
922
966
  const failUnder = flags.failUnder ?? (ciMode ? 60 : (config.failUnder ?? 0));
923
967
  if (failUnder > 0 && score < failUnder && !watchMode) {
924
- if (!jsonOnly)
968
+ if (!quietMode)
925
969
  console.log(` \x1b[31mFailing: score ${score} < ${failUnder}\x1b[0m\n`);
926
970
  process.exit(1);
927
971
  }
928
972
  // Non-blocking update check (don't slow down the scan)
929
- if (!jsonOnly && !ciMode && !watchMode && !process.env.VCQA_NO_UPDATE_CHECK) {
973
+ if (!quietMode && !ciMode && !watchMode && !process.env.VCQA_NO_UPDATE_CHECK) {
930
974
  checkForUpdate(VERSION).catch(() => { });
931
975
  }
932
976
  if (watchMode) {
package/dist/fs-utils.js CHANGED
@@ -168,7 +168,7 @@ function walk(dir, cwd, out, exts) {
168
168
  });
169
169
  }
170
170
  catch {
171
- continue; // broken symlink, deleted file, or permission denied
171
+ /* broken symlink, deleted file, or permission denied */
172
172
  }
173
173
  }
174
174
  }
@@ -1,3 +1,5 @@
1
- /** Secret detection — delegates to gitleaks when available, falls back to built-in regex. */
1
+ /** Secret detection — delegates to gitleaks when available; otherwise scans with
2
+ * secretlint's recommended ruleset (broad coverage) PLUS our own patterns (which
3
+ * add LLM keys — OpenAI/Anthropic — that secretlint's preset doesn't cover). */
2
4
  import type { CheckResult } from "../types.js";
3
- export declare function runSecrets(cwd: string): CheckResult;
5
+ export declare function runSecrets(cwd: string): Promise<CheckResult>;
@@ -1,9 +1,20 @@
1
- /** Secret detection — delegates to gitleaks when available, falls back to built-in regex. */
1
+ /** Secret detection — delegates to gitleaks when available; otherwise scans with
2
+ * secretlint's recommended ruleset (broad coverage) PLUS our own patterns (which
3
+ * add LLM keys — OpenAI/Anthropic — that secretlint's preset doesn't cover). */
2
4
  import { existsSync, readFileSync } from "node:fs";
3
5
  import { join } from "node:path";
6
+ import { lintSource } from "@secretlint/core";
7
+ import { creator as secretlintPreset } from "@secretlint/secretlint-rule-preset-recommend";
4
8
  import { collectAllFiles } from "../fs-utils.js";
5
9
  import { gradeFromScore } from "../types.js";
6
10
  import { run } from "./exec.js";
11
+ const SECRETLINT_CONFIG = { rules: [{ id: "@secretlint/secretlint-rule-preset-recommend", rule: secretlintPreset }] };
12
+ /** Human-readable kind from a secretlint message, without leaking the secret value. */
13
+ function secretlintKind(msg) {
14
+ if (msg.messageId)
15
+ return msg.messageId.replace(/_/g, " ").toLowerCase();
16
+ return (msg.ruleId ?? "secret").replace(/.*secretlint-rule-/, "");
17
+ }
7
18
  /** Try running gitleaks for secret detection. Returns true if gitleaks ran. */
8
19
  function tryGitleaks(cwd, issues) {
9
20
  const { stdout, ok } = run("gitleaks detect --no-git --report-format json --report-path /dev/stdout 2>/dev/null", cwd, 30_000);
@@ -58,37 +69,61 @@ const SECRET_PATTERNS = [
58
69
  pattern: /(?:password|secret|api_key|apikey|token|auth)\s*[:=]\s*['"][A-Za-z0-9+/=]{20,}['"]/,
59
70
  },
60
71
  ];
61
- export function runSecrets(cwd) {
62
- const start = Date.now();
63
- const issues = [];
64
- // Try gitleaks first (industry standard, 800+ patterns)
65
- const gitleaksResult = tryGitleaks(cwd, issues);
66
- const tool = gitleaksResult ? "gitleaks" : "built-in";
67
- if (!gitleaksResult) {
68
- // Fallback: built-in regex patterns
69
- const sourceFiles = collectAllFiles(cwd, { extraExts: true });
70
- for (const sf of sourceFiles) {
71
- if (sf.isTest || sf.path.includes("__mock"))
72
+ function scanPatterns(files, add) {
73
+ for (const sf of files) {
74
+ const lines = sf.content.split("\n");
75
+ for (let i = 0; i < lines.length; i++) {
76
+ const line = lines[i];
77
+ if (line.trim().startsWith("//") || line.trim().startsWith("*"))
72
78
  continue;
73
- const lines = sf.content.split("\n");
74
- for (let i = 0; i < lines.length; i++) {
75
- const line = lines[i];
76
- if (line.trim().startsWith("//") || line.trim().startsWith("*"))
77
- continue;
78
- for (const { name, pattern } of SECRET_PATTERNS) {
79
- if (pattern.test(line)) {
80
- issues.push({
81
- severity: "error",
82
- message: `Possible ${name}`,
83
- file: sf.path,
84
- line: i + 1,
85
- rule: "secret-detected",
86
- });
87
- }
88
- }
79
+ for (const { name, pattern } of SECRET_PATTERNS) {
80
+ if (pattern.test(line))
81
+ add({ severity: "error", message: `Possible ${name}`, file: sf.path, line: i + 1, rule: "secret-detected" });
89
82
  }
90
83
  }
91
84
  }
85
+ }
86
+ async function scanSecretlint(files, add) {
87
+ for (const sf of files) {
88
+ try {
89
+ const result = await lintSource({
90
+ source: { filePath: sf.path, content: sf.content, contentType: "text" },
91
+ options: { config: SECRETLINT_CONFIG },
92
+ });
93
+ for (const m of result.messages) {
94
+ add({ severity: "error", message: `Possible ${secretlintKind(m)} — remove and rotate`, file: sf.path, line: m.loc?.start?.line ?? 1, rule: "secret-detected" });
95
+ }
96
+ }
97
+ catch {
98
+ /* unparseable file — skip */
99
+ }
100
+ }
101
+ }
102
+ /** Built-in fallback when gitleaks isn't installed: curated patterns first (nice
103
+ * names + LLM keys, winning dedup), then secretlint's broad ruleset augments. */
104
+ async function scanFallback(cwd) {
105
+ const files = collectAllFiles(cwd, { extraExts: true }).filter((sf) => !sf.isTest && !sf.path.includes("__mock"));
106
+ const issues = [];
107
+ const seen = new Set();
108
+ const add = (iss) => {
109
+ const key = `${iss.file}:${iss.line}`;
110
+ if (!seen.has(key)) {
111
+ seen.add(key);
112
+ issues.push(iss);
113
+ }
114
+ };
115
+ scanPatterns(files, add);
116
+ await scanSecretlint(files, add);
117
+ return issues;
118
+ }
119
+ export async function runSecrets(cwd) {
120
+ const start = Date.now();
121
+ const issues = [];
122
+ // Try gitleaks first (industry standard, 800+ patterns)
123
+ const gitleaksResult = tryGitleaks(cwd, issues);
124
+ const tool = gitleaksResult ? "gitleaks" : "secretlint";
125
+ if (!gitleaksResult)
126
+ issues.push(...(await scanFallback(cwd)));
92
127
  // ── .env file audit ──
93
128
  const envFiles = [".env", ".env.local", ".env.production", ".env.development"];
94
129
  const gitignore = existsSync(join(cwd, ".gitignore")) ? readFileSync(join(cwd, ".gitignore"), "utf-8") : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecodeqa/cli",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "description": "Code health scanner for the AI coding era. 25 checks, zero config, full report.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -54,6 +54,8 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@jscpd/core": "^4.2.4",
57
+ "@secretlint/core": "^13.0.2",
58
+ "@secretlint/secretlint-rule-preset-recommend": "^13.0.2",
57
59
  "dependency-cruiser": "^17.4.3",
58
60
  "ink": "^5.2.1",
59
61
  "react": "^18.3.1"