@vibecodeqa/cli 0.13.0 → 0.14.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.
@@ -1,19 +1,40 @@
1
1
  /** Code standards check — naming conventions, anti-patterns, config hygiene. */
2
- import { readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { readdirSync, readFileSync, statSync } from "node:fs";
3
3
  import { basename, extname, join } from "node:path";
4
4
  import { gradeFromScore } from "../types.js";
5
5
  const CODE_SMELLS = [
6
- { name: "console.log", pattern: /\bconsole\.(log|debug|info)\s*\(/, severity: "warning", message: "console.log in production code", exclude: /\/\/ ?ok|eslint-disable|biome-ignore/ },
6
+ {
7
+ name: "console.log",
8
+ pattern: /\bconsole\.(log|debug|info)\s*\(/,
9
+ severity: "warning",
10
+ message: "console.log in production code",
11
+ exclude: /\/\/ ?ok|eslint-disable|biome-ignore/,
12
+ },
7
13
  { name: "var keyword", pattern: /\bvar\s+\w/, severity: "error", message: "Use const/let instead of var" },
8
14
  { name: "loose equality", pattern: /[^!=]==[^=]/, severity: "warning", message: "Use === instead of ==", exclude: /['"]use strict['"]/ },
9
15
  { name: "eval()", pattern: /\beval\s*\(/, severity: "error", message: "eval() is a security risk — never use it" },
10
16
  { name: "new Function()", pattern: /new\s+Function\s*\(/, severity: "error", message: "new Function() is equivalent to eval()" },
11
- { name: "innerHTML assignment", pattern: /\.innerHTML\s*=/, severity: "warning", message: "innerHTML is an XSS vector — use textContent or DOM APIs" },
12
- { name: "dangerouslySetInnerHTML", pattern: /dangerouslySetInnerHTML/, severity: "error", message: "dangerouslySetInnerHTML bypasses React's XSS protection" },
17
+ {
18
+ name: "innerHTML assignment",
19
+ pattern: /\.innerHTML\s*=/,
20
+ severity: "warning",
21
+ message: "innerHTML is an XSS vector — use textContent or DOM APIs",
22
+ },
23
+ {
24
+ name: "dangerouslySetInnerHTML",
25
+ pattern: /dangerouslySetInnerHTML/,
26
+ severity: "error",
27
+ message: "dangerouslySetInnerHTML bypasses React's XSS protection",
28
+ },
13
29
  { name: "document.write", pattern: /document\.write\s*\(/, severity: "error", message: "document.write blocks rendering" },
14
30
  { name: "http:// URL", pattern: /['"]http:\/\/(?!localhost|127\.0\.0\.1)/, severity: "warning", message: "Non-HTTPS URL — use https://" },
15
31
  { name: "TODO/FIXME", pattern: /\b(TODO|FIXME|HACK|XXX)\b/, severity: "warning", message: "Unresolved TODO/FIXME comment" },
16
- { name: "magic number", pattern: /(?:timeout|delay|interval|limit|max|min)\s*[:=]\s*\d{4,}(?!\d)/, severity: "warning", message: "Large magic number — consider a named constant" },
32
+ {
33
+ name: "magic number",
34
+ pattern: /(?:timeout|delay|interval|limit|max|min)\s*[:=]\s*\d{4,}(?!\d)/,
35
+ severity: "warning",
36
+ message: "Large magic number — consider a named constant",
37
+ },
17
38
  ];
18
39
  export function runStandards(cwd, stack) {
19
40
  const start = Date.now();
@@ -25,7 +46,9 @@ export function runStandards(cwd, stack) {
25
46
  try {
26
47
  collectFiles(join(cwd, dir), cwd, files);
27
48
  }
28
- catch { /* dir doesn't exist */ }
49
+ catch {
50
+ /* dir doesn't exist */
51
+ }
29
52
  }
30
53
  // ── File naming conventions ──
31
54
  let namingViolations = 0;
@@ -49,7 +72,12 @@ export function runStandards(cwd, stack) {
49
72
  // PascalCase .ts file (not a component) — unusual
50
73
  // Only flag if it's not a class file
51
74
  if (!/export (default )?class /.test(f.content)) {
52
- issues.push({ severity: "warning", message: `TS file uses PascalCase but doesn't export a class: ${name}`, file: f.path, rule: "file-naming" });
75
+ issues.push({
76
+ severity: "warning",
77
+ message: `TS file uses PascalCase but doesn't export a class: ${name}`,
78
+ file: f.path,
79
+ rule: "file-naming",
80
+ });
53
81
  }
54
82
  }
55
83
  }
@@ -71,11 +99,17 @@ export function runStandards(cwd, stack) {
71
99
  const lines = f.content.split("\n");
72
100
  for (let i = 0; i < lines.length; i++) {
73
101
  const line = lines[i];
74
- if (line.trim().startsWith("//") || line.trim().startsWith("*"))
102
+ const trimmed = line.trim();
103
+ if (trimmed.startsWith("//") || trimmed.startsWith("*"))
104
+ continue;
105
+ if (/\bpattern\s*:|name:\s*["']|message:\s*["']|description:\s*["']|risk:\s*["']|recommendation:\s*["']/.test(trimmed))
75
106
  continue;
76
107
  for (const check of CODE_SMELLS) {
108
+ // Skip console.log in CLI entry points (intentional output)
109
+ if (check.name === "console.log" && (f.path.includes("cli.") || f.path.includes("bin/")))
110
+ continue;
77
111
  if (check.pattern.test(line)) {
78
- if (check.exclude && check.exclude.test(line))
112
+ if (check.exclude?.test(line))
79
113
  continue;
80
114
  smellCount++;
81
115
  issues.push({ severity: check.severity, message: check.message, file: f.path, line: i + 1, rule: check.name });
@@ -94,10 +128,16 @@ export function runStandards(cwd, stack) {
94
128
  if (tsconfig.compilerOptions?.strict === true)
95
129
  strictFound = true;
96
130
  }
97
- catch { /* no tsconfig */ }
131
+ catch {
132
+ /* no tsconfig */
133
+ }
98
134
  }
99
135
  if (!strictFound) {
100
- issues.push({ severity: "warning", message: "TypeScript strict mode not enabled — add \"strict\": true to tsconfig", rule: "ts-strict" });
136
+ issues.push({
137
+ severity: "warning",
138
+ message: 'TypeScript strict mode not enabled — add "strict": true to tsconfig',
139
+ rule: "ts-strict",
140
+ });
101
141
  }
102
142
  }
103
143
  // Tailwind: check for inline styles when TW is available
@@ -111,7 +151,11 @@ export function runStandards(cwd, stack) {
111
151
  inlineStyles += matches.length;
112
152
  }
113
153
  if (inlineStyles > 10) {
114
- issues.push({ severity: "warning", message: `${inlineStyles} inline style objects in TSX — prefer Tailwind classes`, rule: "prefer-tailwind" });
154
+ issues.push({
155
+ severity: "warning",
156
+ message: `${inlineStyles} inline style objects in TSX — prefer Tailwind classes`,
157
+ rule: "prefer-tailwind",
158
+ });
115
159
  }
116
160
  }
117
161
  const errors = issues.filter((i) => i.severity === "error").length;
@@ -137,7 +181,7 @@ function collectFiles(dir, cwd, out) {
137
181
  else {
138
182
  const ext = extname(entry);
139
183
  if ([".ts", ".tsx", ".js", ".jsx"].includes(ext) && !entry.includes(".test.") && !entry.includes(".spec.")) {
140
- out.push({ path: full.replace(cwd + "/", ""), content: readFileSync(full, "utf-8") });
184
+ out.push({ path: full.replace(`${cwd}/`, ""), content: readFileSync(full, "utf-8") });
141
185
  }
142
186
  }
143
187
  }
@@ -1,6 +1,6 @@
1
1
  /** Project structure check — does the repo have standard files and conventions? */
2
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
- import { join, extname } from "node:path";
2
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
+ import { extname, join } from "node:path";
4
4
  import { gradeFromScore } from "../types.js";
5
5
  const EXPECTED_FILES = [
6
6
  { name: "package.json", path: "package.json", required: true, description: "Package manifest" },
@@ -54,7 +54,11 @@ export function runStructure(cwd, stack) {
54
54
  issues.push({ severity: "error", message: `No test files found (${srcCount} source files with zero tests)`, rule: "no-tests" });
55
55
  }
56
56
  else if (testRatio < 0.3 && srcCount > 3) {
57
- issues.push({ severity: "warning", message: `Low test-to-source ratio: ${testCount} tests for ${srcCount} source files (${Math.round(testRatio * 100)}%)`, rule: "low-test-ratio" });
57
+ issues.push({
58
+ severity: "warning",
59
+ message: `Low test-to-source ratio: ${testCount} tests for ${srcCount} source files (${Math.round(testRatio * 100)}%)`,
60
+ rule: "low-test-ratio",
61
+ });
58
62
  }
59
63
  // Check package.json has essential scripts
60
64
  try {
@@ -65,7 +69,9 @@ export function runStructure(cwd, stack) {
65
69
  if (!scripts.build && !scripts.dev)
66
70
  issues.push({ severity: "info", message: "No 'build' or 'dev' script in package.json", rule: "no-build-script" });
67
71
  }
68
- catch { /* no package.json or parse error */ }
72
+ catch {
73
+ /* no package.json or parse error */
74
+ }
69
75
  const errors = issues.filter((i) => i.severity === "error").length;
70
76
  const warnings = issues.filter((i) => i.severity === "warning").length;
71
77
  const score = Math.max(0, Math.min(100, 100 - errors * 15 - warnings * 5));
@@ -73,7 +79,7 @@ export function runStructure(cwd, stack) {
73
79
  name: "structure",
74
80
  score,
75
81
  grade: gradeFromScore(score),
76
- details: { found, missing, srcFiles: srcCount, testFiles: testCount, testRatio: Math.round(testRatio * 100) + "%" },
82
+ details: { found, missing, srcFiles: srcCount, testFiles: testCount, testRatio: `${Math.round(testRatio * 100)}%` },
77
83
  issues,
78
84
  duration: Date.now() - start,
79
85
  };
@@ -84,7 +90,9 @@ function collectAll(cwd, src, test) {
84
90
  try {
85
91
  walk(join(cwd, dir), src, test);
86
92
  }
87
- catch { /* dir doesn't exist */ }
93
+ catch {
94
+ /* dir doesn't exist */
95
+ }
88
96
  }
89
97
  }
90
98
  function walk(dir, src, test) {
@@ -8,7 +8,7 @@
8
8
  * 5. Test quality — naming, assertions, mocking patterns, snapshot smell
9
9
  * 6. Pyramid balance — right ratio of fast-to-slow tests
10
10
  */
11
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
11
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
12
12
  import { basename, extname, join } from "node:path";
13
13
  import { gradeFromScore } from "../types.js";
14
14
  import { run } from "./exec.js";
@@ -36,7 +36,7 @@ function classifyTestFile(relPath, content) {
36
36
  return "unit";
37
37
  }
38
38
  function countPatterns(content) {
39
- const assertions = (content.match(/\bexpect\s*\(/g) || []).length + (content.match(/\bassert[\.(]/g) || []).length;
39
+ const assertions = (content.match(/\bexpect\s*\(/g) || []).length + (content.match(/\bassert[.(]/g) || []).length;
40
40
  const mocks = (content.match(/\b(vi\.fn|jest\.fn|vi\.mock|jest\.mock|vi\.spyOn|jest\.spyOn|sinon\.(stub|spy|mock)|\.mockResolvedValue|\.mockReturnValue|\.mockImplementation)\b/g) || []).length;
41
41
  const snapshots = (content.match(/\btoMatchSnapshot|toMatchInlineSnapshot\b/g) || []).length;
42
42
  const describes = (content.match(/\bdescribe\s*\(/g) || []).length;
@@ -67,10 +67,16 @@ function walkTests(dir, cwd, out) {
67
67
  const ext = extname(entry);
68
68
  if (![".ts", ".tsx", ".js", ".jsx"].includes(ext))
69
69
  continue;
70
- if (!entry.includes(".test.") && !entry.includes(".spec.") && !entry.includes(".e2e.") && !entry.includes(".int.") && !dir.includes("__tests__") && !dir.includes("/e2e") && !dir.includes("/test"))
70
+ if (!entry.includes(".test.") &&
71
+ !entry.includes(".spec.") &&
72
+ !entry.includes(".e2e.") &&
73
+ !entry.includes(".int.") &&
74
+ !dir.includes("__tests__") &&
75
+ !dir.includes("/e2e") &&
76
+ !dir.includes("/test"))
71
77
  continue;
72
78
  const content = readFileSync(full, "utf-8");
73
- const relPath = full.replace(cwd + "/", "");
79
+ const relPath = full.replace(`${cwd}/`, "");
74
80
  const layer = classifyTestFile(relPath, content);
75
81
  const patterns = countPatterns(content);
76
82
  out.push({
@@ -88,7 +94,9 @@ function findSourceFiles(cwd) {
88
94
  try {
89
95
  walkSource(join(cwd, dir), cwd, files);
90
96
  }
91
- catch { /* dir doesn't exist */ }
97
+ catch {
98
+ /* dir doesn't exist */
99
+ }
92
100
  }
93
101
  return files;
94
102
  }
@@ -103,7 +111,7 @@ function walkSource(dir, cwd, out) {
103
111
  else {
104
112
  const ext = extname(entry);
105
113
  if ([".ts", ".tsx", ".js", ".jsx"].includes(ext) && !entry.includes(".test.") && !entry.includes(".spec.")) {
106
- out.push(full.replace(cwd + "/", ""));
114
+ out.push(full.replace(`${cwd}/`, ""));
107
115
  }
108
116
  }
109
117
  }
@@ -175,7 +183,9 @@ function collectCoverage(cwd, stack) {
175
183
  };
176
184
  }
177
185
  }
178
- catch { /* parse failed */ }
186
+ catch {
187
+ /* parse failed */
188
+ }
179
189
  }
180
190
  }
181
191
  return null;
@@ -184,9 +194,7 @@ function collectCoverage(cwd, stack) {
184
194
  function executeTests(cwd, stack) {
185
195
  if (stack.testRunner === "none")
186
196
  return null;
187
- const cmd = stack.testRunner === "vitest"
188
- ? "npx vitest run --reporter=json 2>/dev/null || true"
189
- : "npx jest --json 2>/dev/null || true";
197
+ const cmd = stack.testRunner === "vitest" ? "npx vitest run --reporter=json 2>/dev/null || true" : "npx jest --json 2>/dev/null || true";
190
198
  const { stdout } = run(cmd, cwd, 120_000);
191
199
  try {
192
200
  const jsonStart = stdout.indexOf("{");
@@ -199,7 +207,9 @@ function executeTests(cwd, stack) {
199
207
  };
200
208
  }
201
209
  }
202
- catch { /* parse failed */ }
210
+ catch {
211
+ /* parse failed */
212
+ }
203
213
  return null;
204
214
  }
205
215
  function analyzeQuality(testFiles) {
@@ -253,10 +263,30 @@ export function runTesting(cwd, stack, skipExec) {
253
263
  for (const f of testFiles)
254
264
  layers[f.layer].push(f);
255
265
  const pyramid = {
256
- unit: { present: layers.unit.length > 0, files: layers.unit.length, assertions: layers.unit.reduce((s, f) => s + f.assertions, 0), score: 0 },
257
- integration: { present: layers.integration.length > 0, files: layers.integration.length, assertions: layers.integration.reduce((s, f) => s + f.assertions, 0), score: 0 },
258
- component: { present: layers.component.length > 0, files: layers.component.length, assertions: layers.component.reduce((s, f) => s + f.assertions, 0), score: 0 },
259
- e2e: { present: layers.e2e.length > 0, files: layers.e2e.length, assertions: layers.e2e.reduce((s, f) => s + f.assertions, 0), score: 0 },
266
+ unit: {
267
+ present: layers.unit.length > 0,
268
+ files: layers.unit.length,
269
+ assertions: layers.unit.reduce((s, f) => s + f.assertions, 0),
270
+ score: 0,
271
+ },
272
+ integration: {
273
+ present: layers.integration.length > 0,
274
+ files: layers.integration.length,
275
+ assertions: layers.integration.reduce((s, f) => s + f.assertions, 0),
276
+ score: 0,
277
+ },
278
+ component: {
279
+ present: layers.component.length > 0,
280
+ files: layers.component.length,
281
+ assertions: layers.component.reduce((s, f) => s + f.assertions, 0),
282
+ score: 0,
283
+ },
284
+ e2e: {
285
+ present: layers.e2e.length > 0,
286
+ files: layers.e2e.length,
287
+ assertions: layers.e2e.reduce((s, f) => s + f.assertions, 0),
288
+ score: 0,
289
+ },
260
290
  };
261
291
  // 3. E2E tool detection
262
292
  const e2eTool = detectE2E(cwd);
@@ -267,14 +297,26 @@ export function runTesting(cwd, stack, skipExec) {
267
297
  issues.push({ severity: "error", message: "No unit tests found — every project needs unit tests", rule: "no-unit-tests" });
268
298
  }
269
299
  if (!pyramid.integration.present && srcFiles.length > 5) {
270
- issues.push({ severity: "warning", message: "No integration tests — consider testing service boundaries", rule: "no-integration-tests" });
300
+ issues.push({
301
+ severity: "warning",
302
+ message: "No integration tests — consider testing service boundaries",
303
+ rule: "no-integration-tests",
304
+ });
271
305
  }
272
306
  if (needsComponent && !pyramid.component.present) {
273
- issues.push({ severity: "warning", message: `${stack.framework} project with no component tests — test your UI components`, rule: "no-component-tests" });
307
+ issues.push({
308
+ severity: "warning",
309
+ message: `${stack.framework} project with no component tests — test your UI components`,
310
+ rule: "no-component-tests",
311
+ });
274
312
  }
275
313
  if (needsE2E && !pyramid.e2e.present) {
276
314
  if (e2eTool.tool === "none") {
277
- issues.push({ severity: "warning", message: "No E2E test framework (Playwright/Cypress) — critical user flows untested", rule: "no-e2e-framework" });
315
+ issues.push({
316
+ severity: "warning",
317
+ message: "No E2E test framework (Playwright/Cypress) — critical user flows untested",
318
+ rule: "no-e2e-framework",
319
+ });
278
320
  }
279
321
  else if (!e2eTool.configured) {
280
322
  issues.push({ severity: "info", message: `${e2eTool.tool} installed but not configured`, rule: "e2e-not-configured" });
@@ -285,7 +327,7 @@ export function runTesting(cwd, stack, skipExec) {
285
327
  }
286
328
  // 5. File pairing analysis
287
329
  const pairing = computePairing(srcFiles, testFiles);
288
- const pairingPct = srcFiles.length > 0 ? Math.round(((pairing.paired) / Math.max(1, srcFiles.length - countUntestable(srcFiles))) * 100) : 100;
330
+ const pairingPct = srcFiles.length > 0 ? Math.round((pairing.paired / Math.max(1, srcFiles.length - countUntestable(srcFiles))) * 100) : 100;
289
331
  if (pairingPct < 30) {
290
332
  issues.push({ severity: "warning", message: `Only ${pairingPct}% of source files have matching test files`, rule: "low-test-pairing" });
291
333
  }
@@ -299,13 +341,25 @@ export function runTesting(cwd, stack, skipExec) {
299
341
  // 6. Test quality analysis
300
342
  const quality = analyzeQuality(testFiles);
301
343
  if (quality.avgAssertionsPerTest < 1) {
302
- issues.push({ severity: "warning", message: `Low assertion density: ${quality.avgAssertionsPerTest} assertions/test (aim for 2+)`, rule: "low-assertions" });
344
+ issues.push({
345
+ severity: "warning",
346
+ message: `Low assertion density: ${quality.avgAssertionsPerTest} assertions/test (aim for 2+)`,
347
+ rule: "low-assertions",
348
+ });
303
349
  }
304
350
  if (quality.mockRatio > 0.5) {
305
- issues.push({ severity: "warning", message: `High mock ratio: ${quality.mockRatio} mocks per assertion — tests may not reflect real behavior`, rule: "over-mocked" });
351
+ issues.push({
352
+ severity: "warning",
353
+ message: `High mock ratio: ${quality.mockRatio} mocks per assertion — tests may not reflect real behavior`,
354
+ rule: "over-mocked",
355
+ });
306
356
  }
307
357
  if (quality.snapshotRatio > 0.3) {
308
- issues.push({ severity: "warning", message: `${Math.round(quality.snapshotRatio * 100)}% of assertions are snapshots — brittle, prefer explicit assertions`, rule: "snapshot-heavy" });
358
+ issues.push({
359
+ severity: "warning",
360
+ message: `${Math.round(quality.snapshotRatio * 100)}% of assertions are snapshots — brittle, prefer explicit assertions`,
361
+ rule: "snapshot-heavy",
362
+ });
309
363
  }
310
364
  // 7. Execute tests + collect coverage (unless --skip-tests)
311
365
  let execution = null;
@@ -321,17 +375,30 @@ export function runTesting(cwd, stack, skipExec) {
321
375
  }
322
376
  if (coverage) {
323
377
  if (coverage.branches < 50) {
324
- issues.push({ severity: "warning", message: `Branch coverage ${coverage.branches}% — below 50% threshold`, rule: "low-branch-coverage" });
378
+ issues.push({
379
+ severity: "warning",
380
+ message: `Branch coverage ${coverage.branches}% — below 50% threshold`,
381
+ rule: "low-branch-coverage",
382
+ });
325
383
  }
326
384
  if (coverage.statements < 60) {
327
- issues.push({ severity: "warning", message: `Statement coverage ${coverage.statements}% — below 60% threshold`, rule: "low-statement-coverage" });
385
+ issues.push({
386
+ severity: "warning",
387
+ message: `Statement coverage ${coverage.statements}% — below 60% threshold`,
388
+ rule: "low-statement-coverage",
389
+ });
328
390
  }
329
391
  }
330
392
  }
331
393
  // 8. Compute composite score
332
394
  let score = 0;
333
395
  // Pyramid presence (30 points)
334
- const layerCount = [pyramid.unit.present, pyramid.integration.present, pyramid.component.present && needsComponent, pyramid.e2e.present && needsE2E].filter(Boolean).length;
396
+ const layerCount = [
397
+ pyramid.unit.present,
398
+ pyramid.integration.present,
399
+ pyramid.component.present && needsComponent,
400
+ pyramid.e2e.present && needsE2E,
401
+ ].filter(Boolean).length;
335
402
  const expectedLayers = 1 + (srcFiles.length > 5 ? 1 : 0) + (needsComponent ? 1 : 0) + (needsE2E ? 1 : 0);
336
403
  const pyramidScore = expectedLayers > 0 ? Math.round((layerCount / expectedLayers) * 30) : 30;
337
404
  score += pyramidScore;
@@ -376,18 +443,20 @@ export function runTesting(cwd, stack, skipExec) {
376
443
  component: pyramid.component.files,
377
444
  e2e: pyramid.e2e.files,
378
445
  },
379
- layersPresent: layerCount + "/" + expectedLayers,
446
+ layersPresent: `${layerCount}/${expectedLayers}`,
380
447
  e2eTool: e2eTool.tool,
381
448
  testFiles: testFiles.length,
382
449
  srcFiles: srcFiles.length,
383
- pairing: pairingPct + "%",
450
+ pairing: `${pairingPct}%`,
384
451
  quality: {
385
452
  assertionsPerTest: quality.avgAssertionsPerTest,
386
453
  mockRatio: quality.mockRatio,
387
454
  snapshotRatio: quality.snapshotRatio,
388
455
  },
389
456
  ...(execution ? { passed: execution.passed, failed: execution.failed, total: execution.total } : {}),
390
- ...(coverage ? { coverage: { stmts: coverage.statements, branches: coverage.branches, lines: coverage.lines, fns: coverage.functions } } : {}),
457
+ ...(coverage
458
+ ? { coverage: { stmts: coverage.statements, branches: coverage.branches, lines: coverage.lines, fns: coverage.functions } }
459
+ : {}),
391
460
  },
392
461
  issues,
393
462
  duration: Date.now() - start,
@@ -21,18 +21,31 @@ export function runTypeSafety(cwd) {
21
21
  try {
22
22
  collectFiles(join(cwd, dir), files);
23
23
  }
24
- catch { /* dir doesn't exist */ }
24
+ catch {
25
+ /* dir doesn't exist */
26
+ }
25
27
  }
26
28
  if (files.length === 0) {
27
- return { name: "type-safety", score: 100, grade: "A", details: { skipped: true, reason: "no source files" }, issues: [], duration: Date.now() - start };
29
+ return {
30
+ name: "type-safety",
31
+ score: 100,
32
+ grade: "A",
33
+ details: { skipped: true, reason: "no source files" },
34
+ issues: [],
35
+ duration: Date.now() - start,
36
+ };
28
37
  }
29
38
  for (const file of files) {
30
39
  const content = readFileSync(file, "utf-8");
31
- const relPath = file.replace(cwd + "/", "");
40
+ const relPath = file.replace(`${cwd}/`, "");
32
41
  const lines = content.split("\n");
33
42
  for (let i = 0; i < lines.length; i++) {
34
43
  const line = lines[i];
35
- if (line.trim().startsWith("//"))
44
+ const trimmed = line.trim();
45
+ if (trimmed.startsWith("//") || trimmed.startsWith("*"))
46
+ continue;
47
+ // Skip pattern definition lines (prevents false positives when scanning own code)
48
+ if (/\bpattern\s*:|name:\s*["']|message:\s*["']|description:\s*["']|risk:\s*["']|recommendation:\s*["']/.test(trimmed))
36
49
  continue;
37
50
  for (const p of PATTERNS) {
38
51
  const matches = line.match(p.pattern);
@@ -6,8 +6,7 @@ import { run } from "./exec.js";
6
6
  export function runTypeCheck(cwd) {
7
7
  const start = Date.now();
8
8
  const issues = [];
9
- if (!existsSync(join(cwd, "tsconfig.json")) &&
10
- !existsSync(join(cwd, "tsconfig.app.json"))) {
9
+ if (!existsSync(join(cwd, "tsconfig.json")) && !existsSync(join(cwd, "tsconfig.app.json"))) {
11
10
  return {
12
11
  name: "types",
13
12
  score: 0,
@@ -25,7 +24,7 @@ export function runTypeCheck(cwd) {
25
24
  issues.push({
26
25
  severity: "error",
27
26
  file: match[1],
28
- line: parseInt(match[2]),
27
+ line: parseInt(match[2], 10),
29
28
  rule: match[3],
30
29
  message: match[4],
31
30
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecodeqa/cli",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Code health scanner for the AI coding era. 15 checks, zero config, full report.",
5
5
  "type": "module",
6
6
  "bin": {