@vibecodeqa/cli 0.16.0 → 0.18.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.
Files changed (47) hide show
  1. package/README.md +73 -63
  2. package/dist/check-meta.d.ts +1 -0
  3. package/dist/check-meta.js +58 -6
  4. package/dist/cli.js +48 -10
  5. package/dist/detect.js +24 -2
  6. package/dist/fs-utils.d.ts +4 -0
  7. package/dist/fs-utils.js +12 -6
  8. package/dist/report/html.d.ts +18 -9
  9. package/dist/report/html.js +108 -68
  10. package/dist/report/pages.d.ts +4 -4
  11. package/dist/report/pages.js +165 -115
  12. package/dist/report/sarif.d.ts +3 -0
  13. package/dist/report/sarif.js +67 -0
  14. package/dist/report/styles.d.ts +1 -1
  15. package/dist/report/styles.js +105 -33
  16. package/dist/report/svg.d.ts +17 -0
  17. package/dist/report/svg.js +99 -0
  18. package/dist/runners/accessibility.d.ts +3 -0
  19. package/dist/runners/accessibility.js +85 -0
  20. package/dist/runners/architecture.d.ts +2 -0
  21. package/dist/runners/architecture.js +232 -20
  22. package/dist/runners/code-coherence.d.ts +17 -0
  23. package/dist/runners/code-coherence.js +39 -0
  24. package/dist/runners/complexity.js +7 -37
  25. package/dist/runners/confusion.js +3 -31
  26. package/dist/runners/context.js +9 -40
  27. package/dist/runners/dependencies.js +28 -0
  28. package/dist/runners/doc-coherence.d.ts +14 -0
  29. package/dist/runners/doc-coherence.js +48 -0
  30. package/dist/runners/docs.js +7 -32
  31. package/dist/runners/duplication.js +9 -37
  32. package/dist/runners/lint.js +17 -0
  33. package/dist/runners/performance.d.ts +10 -0
  34. package/dist/runners/performance.js +174 -0
  35. package/dist/runners/react.d.ts +3 -0
  36. package/dist/runners/react.js +86 -0
  37. package/dist/runners/secrets.js +8 -29
  38. package/dist/runners/security.js +15 -38
  39. package/dist/runners/standards.js +3 -36
  40. package/dist/runners/structure.js +35 -55
  41. package/dist/runners/testing.js +2 -36
  42. package/dist/runners/type-safety.d.ts +1 -1
  43. package/dist/runners/type-safety.js +19 -37
  44. package/dist/runners/types-check.d.ts +1 -1
  45. package/dist/runners/types-check.js +38 -20
  46. package/dist/types.d.ts +5 -5
  47. package/package.json +11 -10
@@ -1,6 +1,7 @@
1
1
  /** Code standards check — naming conventions, anti-patterns, config hygiene. */
2
- import { readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { readFileSync } from "node:fs";
3
3
  import { basename, extname, join } from "node:path";
4
+ import { getProductionFiles, readDeps } from "../fs-utils.js";
4
5
  import { gradeFromScore } from "../types.js";
5
6
  const CODE_SMELLS = [
6
7
  {
@@ -40,16 +41,7 @@ export function runStandards(cwd, stack) {
40
41
  const start = Date.now();
41
42
  const issues = [];
42
43
  // Collect source files
43
- const files = [];
44
- const dirs = ["src", "web/src"];
45
- for (const dir of dirs) {
46
- try {
47
- collectFiles(join(cwd, dir), cwd, files);
48
- }
49
- catch {
50
- /* dir doesn't exist */
51
- }
52
- }
44
+ const files = getProductionFiles(cwd);
53
45
  // ── File naming conventions ──
54
46
  let namingViolations = 0;
55
47
  for (const f of files) {
@@ -170,28 +162,3 @@ export function runStandards(cwd, stack) {
170
162
  duration: Date.now() - start,
171
163
  };
172
164
  }
173
- function collectFiles(dir, cwd, out) {
174
- for (const entry of readdirSync(dir)) {
175
- if (entry === "node_modules" || entry === "dist" || entry === ".git")
176
- continue;
177
- const full = join(dir, entry);
178
- if (statSync(full).isDirectory()) {
179
- collectFiles(full, cwd, out);
180
- }
181
- else {
182
- const ext = extname(entry);
183
- if ([".ts", ".tsx", ".js", ".jsx"].includes(ext) && !entry.includes(".test.") && !entry.includes(".spec.")) {
184
- out.push({ path: full.replace(`${cwd}/`, ""), content: readFileSync(full, "utf-8") });
185
- }
186
- }
187
- }
188
- }
189
- function readDeps(cwd) {
190
- try {
191
- const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf-8"));
192
- return { ...pkg.dependencies, ...pkg.devDependencies };
193
- }
194
- catch {
195
- return {};
196
- }
197
- }
@@ -1,19 +1,29 @@
1
1
  /** Project structure check — does the repo have standard files and conventions? */
2
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
- import { extname, join } from "node:path";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { collectSourceFiles } from "../fs-utils.js";
4
5
  import { gradeFromScore } from "../types.js";
5
- const EXPECTED_FILES = [
6
+ const NODE_FILES = [
6
7
  { name: "package.json", path: "package.json", required: true, description: "Package manifest" },
7
8
  { name: "tsconfig.json", path: "tsconfig.json", required: false, description: "TypeScript configuration" },
8
9
  { name: "LICENSE", path: "LICENSE", required: true, description: "Open source license" },
9
10
  { name: ".gitignore", path: ".gitignore", required: true, description: "Git ignore rules" },
10
11
  { name: "README.md", path: "README.md", required: false, description: "Project documentation" },
11
12
  ];
13
+ const DART_FILES = [
14
+ { name: "pubspec.yaml", path: "pubspec.yaml", required: true, description: "Dart package manifest" },
15
+ { name: "analysis_options.yaml", path: "analysis_options.yaml", required: true, description: "Dart analysis options" },
16
+ { name: "LICENSE", path: "LICENSE", required: true, description: "Open source license" },
17
+ { name: ".gitignore", path: ".gitignore", required: true, description: "Git ignore rules" },
18
+ { name: "README.md", path: "README.md", required: false, description: "Project documentation" },
19
+ ];
12
20
  export function runStructure(cwd, stack) {
13
21
  const start = Date.now();
14
22
  const issues = [];
15
23
  const found = [];
16
24
  const missing = [];
25
+ const isDart = stack.language === "dart";
26
+ const EXPECTED_FILES = isDart ? DART_FILES : NODE_FILES;
17
27
  // Check standard files
18
28
  for (const fc of EXPECTED_FILES) {
19
29
  // tsconfig is required only for TS projects
@@ -31,24 +41,24 @@ export function runStructure(cwd, stack) {
31
41
  }
32
42
  }
33
43
  // Check for lockfile
34
- const hasLock = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"].some((f) => existsSync(join(cwd, f)));
44
+ const lockfiles = isDart ? ["pubspec.lock"] : ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"];
45
+ const hasLock = lockfiles.some((f) => existsSync(join(cwd, f)));
35
46
  if (hasLock) {
36
47
  found.push("lockfile");
37
48
  }
38
49
  else {
39
50
  issues.push({ severity: "warning", message: "No lockfile found — builds may not be reproducible", rule: "missing-lockfile" });
40
51
  }
41
- // Check for src directory
42
- const hasSrc = existsSync(join(cwd, "src")) || existsSync(join(cwd, "web/src"));
52
+ // Check for source directory
53
+ const srcDirs = isDart ? ["lib"] : ["src", "web/src"];
54
+ const hasSrc = srcDirs.some((d) => existsSync(join(cwd, d)));
43
55
  if (!hasSrc) {
44
- issues.push({ severity: "error", message: "No src/ directory found", rule: "no-src" });
56
+ issues.push({ severity: "error", message: `No ${srcDirs[0]}/ directory found`, rule: "no-src" });
45
57
  }
46
58
  // Count source vs test files
47
- const srcFiles = [];
48
- const testFiles = [];
49
- collectAll(cwd, srcFiles, testFiles);
50
- const srcCount = srcFiles.length;
51
- const testCount = testFiles.length;
59
+ const allFiles = collectSourceFiles(cwd, { includeTests: true });
60
+ const srcCount = allFiles.filter((f) => !f.isTest).length;
61
+ const testCount = allFiles.filter((f) => f.isTest).length;
52
62
  const testRatio = srcCount > 0 ? testCount / srcCount : 0;
53
63
  if (testCount === 0 && srcCount > 0) {
54
64
  issues.push({ severity: "error", message: `No test files found (${srcCount} source files with zero tests)`, rule: "no-tests" });
@@ -60,17 +70,19 @@ export function runStructure(cwd, stack) {
60
70
  rule: "low-test-ratio",
61
71
  });
62
72
  }
63
- // Check package.json has essential scripts
64
- try {
65
- const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf-8"));
66
- const scripts = pkg.scripts || {};
67
- if (!scripts.test)
68
- issues.push({ severity: "warning", message: "No 'test' script in package.json", rule: "no-test-script" });
69
- if (!scripts.build && !scripts.dev)
70
- issues.push({ severity: "info", message: "No 'build' or 'dev' script in package.json", rule: "no-build-script" });
71
- }
72
- catch {
73
- /* no package.json or parse error */
73
+ // Check manifest has essential config
74
+ if (!isDart) {
75
+ try {
76
+ const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf-8"));
77
+ const scripts = pkg.scripts || {};
78
+ if (!scripts.test)
79
+ issues.push({ severity: "warning", message: "No 'test' script in package.json", rule: "no-test-script" });
80
+ if (!scripts.build && !scripts.dev)
81
+ issues.push({ severity: "info", message: "No 'build' or 'dev' script in package.json", rule: "no-build-script" });
82
+ }
83
+ catch {
84
+ /* no package.json or parse error */
85
+ }
74
86
  }
75
87
  const errors = issues.filter((i) => i.severity === "error").length;
76
88
  const warnings = issues.filter((i) => i.severity === "warning").length;
@@ -84,35 +96,3 @@ export function runStructure(cwd, stack) {
84
96
  duration: Date.now() - start,
85
97
  };
86
98
  }
87
- function collectAll(cwd, src, test) {
88
- const dirs = ["src", "web/src"];
89
- for (const dir of dirs) {
90
- try {
91
- walk(join(cwd, dir), src, test);
92
- }
93
- catch {
94
- /* dir doesn't exist */
95
- }
96
- }
97
- }
98
- function walk(dir, src, test) {
99
- for (const entry of readdirSync(dir)) {
100
- if (entry === "node_modules" || entry === "dist")
101
- continue;
102
- const full = join(dir, entry);
103
- if (statSync(full).isDirectory()) {
104
- walk(full, src, test);
105
- }
106
- else {
107
- const ext = extname(entry);
108
- if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
109
- if (entry.includes(".test.") || entry.includes(".spec.")) {
110
- test.push(full);
111
- }
112
- else {
113
- src.push(full);
114
- }
115
- }
116
- }
117
- }
118
- }
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
12
12
  import { basename, extname, join } from "node:path";
13
+ import { getProductionFiles, readDeps } from "../fs-utils.js";
13
14
  import { gradeFromScore } from "../types.js";
14
15
  import { run } from "./exec.js";
15
16
  // ── Classification rules ──
@@ -88,33 +89,7 @@ function walkTests(dir, cwd, out) {
88
89
  }
89
90
  }
90
91
  function findSourceFiles(cwd) {
91
- const files = [];
92
- const dirs = ["src", "web/src"];
93
- for (const dir of dirs) {
94
- try {
95
- walkSource(join(cwd, dir), cwd, files);
96
- }
97
- catch {
98
- /* dir doesn't exist */
99
- }
100
- }
101
- return files;
102
- }
103
- function walkSource(dir, cwd, out) {
104
- for (const entry of readdirSync(dir)) {
105
- if (entry === "node_modules" || entry === "dist")
106
- continue;
107
- const full = join(dir, entry);
108
- if (statSync(full).isDirectory()) {
109
- walkSource(full, cwd, out);
110
- }
111
- else {
112
- const ext = extname(entry);
113
- if ([".ts", ".tsx", ".js", ".jsx"].includes(ext) && !entry.includes(".test.") && !entry.includes(".spec.")) {
114
- out.push(full.replace(`${cwd}/`, ""));
115
- }
116
- }
117
- }
92
+ return getProductionFiles(cwd).map((f) => f.path);
118
93
  }
119
94
  // ── Pairing analysis ──
120
95
  function computePairing(srcFiles, testFiles) {
@@ -151,15 +126,6 @@ function detectE2E(cwd) {
151
126
  }
152
127
  return { tool: "none", configured: false };
153
128
  }
154
- function readDeps(cwd) {
155
- try {
156
- const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf-8"));
157
- return { ...pkg.dependencies, ...pkg.devDependencies };
158
- }
159
- catch {
160
- return {};
161
- }
162
- }
163
129
  // ── Coverage collection ──
164
130
  function collectCoverage(cwd, stack) {
165
131
  if (stack.testRunner === "none")
@@ -1,3 +1,3 @@
1
1
  /** Type safety check — count unsafe patterns: `as any`, explicit `any`, non-null assertions. */
2
2
  import type { CheckResult } from "../types.js";
3
- export declare function runTypeSafety(cwd: string): CheckResult;
3
+ export declare function runTypeSafety(cwd: string, isDart?: boolean): CheckResult;
@@ -1,8 +1,8 @@
1
1
  /** Type safety check — count unsafe patterns: `as any`, explicit `any`, non-null assertions. */
2
- import { readdirSync, readFileSync, statSync } from "node:fs";
3
- import { extname, join } from "node:path";
2
+ import { getProductionFiles } from "../fs-utils.js";
4
3
  import { gradeFromScore } from "../types.js";
5
- const PATTERNS = [
4
+ // TypeScript unsafe patterns
5
+ const TS_PATTERNS = [
6
6
  { name: "as any", pattern: /\bas any\b/g, severity: "warning", weight: 2 },
7
7
  { name: ": any", pattern: /:\s*any\b/g, severity: "warning", weight: 1 },
8
8
  { name: "non-null assertion (!.)", pattern: /\w+!\./g, severity: "info", weight: 0.5 },
@@ -10,22 +10,22 @@ const PATTERNS = [
10
10
  { name: "@ts-expect-error", pattern: /@ts-expect-error/g, severity: "warning", weight: 2 },
11
11
  { name: "@ts-nocheck", pattern: /@ts-nocheck/g, severity: "error", weight: 10 },
12
12
  ];
13
- export function runTypeSafety(cwd) {
13
+ // Dart unsafe patterns
14
+ const DART_PATTERNS = [
15
+ { name: "dynamic type", pattern: /\bdynamic\b/g, severity: "warning", weight: 1 },
16
+ { name: "as dynamic", pattern: /\bas dynamic\b/g, severity: "warning", weight: 2 },
17
+ { name: "// ignore:", pattern: /\/\/\s*ignore:/g, severity: "error", weight: 5 },
18
+ { name: "// ignore_for_file:", pattern: /\/\/\s*ignore_for_file:/g, severity: "error", weight: 10 },
19
+ { name: "late keyword", pattern: /\blate\s+(?!final)/g, severity: "info", weight: 0.5 },
20
+ ];
21
+ export function runTypeSafety(cwd, isDart = false) {
14
22
  const start = Date.now();
15
23
  const issues = [];
16
24
  const counts = {};
17
25
  let totalPenalty = 0;
18
- const files = [];
19
- const dirs = ["src", "web/src"];
20
- for (const dir of dirs) {
21
- try {
22
- collectFiles(join(cwd, dir), files);
23
- }
24
- catch {
25
- /* dir doesn't exist */
26
- }
27
- }
28
- if (files.length === 0) {
26
+ const PATTERNS = isDart ? DART_PATTERNS : TS_PATTERNS;
27
+ const sourceFiles = getProductionFiles(cwd);
28
+ if (sourceFiles.length === 0) {
29
29
  return {
30
30
  name: "type-safety",
31
31
  score: 100,
@@ -35,10 +35,8 @@ export function runTypeSafety(cwd) {
35
35
  duration: Date.now() - start,
36
36
  };
37
37
  }
38
- for (const file of files) {
39
- const content = readFileSync(file, "utf-8");
40
- const relPath = file.replace(`${cwd}/`, "");
41
- const lines = content.split("\n");
38
+ for (const sf of sourceFiles) {
39
+ const lines = sf.content.split("\n");
42
40
  for (let i = 0; i < lines.length; i++) {
43
41
  const line = lines[i];
44
42
  const trimmed = line.trim();
@@ -53,7 +51,7 @@ export function runTypeSafety(cwd) {
53
51
  counts[p.name] = (counts[p.name] || 0) + matches.length;
54
52
  totalPenalty += p.weight * matches.length;
55
53
  for (const _m of matches) {
56
- issues.push({ severity: p.severity, message: p.name, file: relPath, line: i + 1, rule: "unsafe-type" });
54
+ issues.push({ severity: p.severity, message: p.name, file: sf.path, line: i + 1, rule: "unsafe-type" });
57
55
  }
58
56
  }
59
57
  }
@@ -64,24 +62,8 @@ export function runTypeSafety(cwd) {
64
62
  name: "type-safety",
65
63
  score,
66
64
  grade: gradeFromScore(score),
67
- details: { ...counts, filesScanned: files.length, totalUnsafe: issues.length },
65
+ details: { ...counts, filesScanned: sourceFiles.length, totalUnsafe: issues.length },
68
66
  issues,
69
67
  duration: Date.now() - start,
70
68
  };
71
69
  }
72
- function collectFiles(dir, out) {
73
- for (const entry of readdirSync(dir)) {
74
- if (entry === "node_modules" || entry === "dist")
75
- continue;
76
- const full = join(dir, entry);
77
- if (statSync(full).isDirectory()) {
78
- collectFiles(full, out);
79
- }
80
- else {
81
- const ext = extname(entry);
82
- if ((ext === ".ts" || ext === ".tsx") && !entry.includes(".test.") && !entry.includes(".spec.")) {
83
- out.push(full);
84
- }
85
- }
86
- }
87
- }
@@ -1,3 +1,3 @@
1
1
  /** TypeScript type checking runner. */
2
2
  import type { CheckResult } from "../types.js";
3
- export declare function runTypeCheck(cwd: string): CheckResult;
3
+ export declare function runTypeCheck(cwd: string, isDart?: boolean): CheckResult;
@@ -3,33 +3,51 @@ import { existsSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { gradeFromScore } from "../types.js";
5
5
  import { run } from "./exec.js";
6
- export function runTypeCheck(cwd) {
6
+ export function runTypeCheck(cwd, isDart = false) {
7
7
  const start = Date.now();
8
8
  const issues = [];
9
- if (!existsSync(join(cwd, "tsconfig.json")) && !existsSync(join(cwd, "tsconfig.app.json"))) {
10
- return {
11
- name: "types",
12
- score: 0,
13
- grade: "F",
14
- details: { skipped: true, reason: "no tsconfig.json" },
15
- issues: [],
16
- duration: Date.now() - start,
17
- };
18
- }
19
- const { stdout } = run("npx tsc --noEmit 2>&1 || true", cwd, 30_000);
20
- const lines = stdout.split("\n");
21
- for (const line of lines) {
22
- const match = line.match(/^(.+)\((\d+),\d+\): error (TS\d+): (.+)/);
23
- if (match) {
9
+ if (isDart) {
10
+ // Dart uses dart analyze for type checking — errors are type errors
11
+ const { stdout } = run("dart analyze --format=machine 2>/dev/null || true", cwd, 30_000);
12
+ for (const line of stdout.split("\n")) {
13
+ const parts = line.split("|");
14
+ if (parts.length < 8 || parts[0] !== "ERROR")
15
+ continue;
24
16
  issues.push({
25
17
  severity: "error",
26
- file: match[1],
27
- line: parseInt(match[2], 10),
28
- rule: match[3],
29
- message: match[4],
18
+ file: parts[3],
19
+ line: parseInt(parts[4], 10) || undefined,
20
+ rule: parts[2],
21
+ message: parts[7],
30
22
  });
31
23
  }
32
24
  }
25
+ else {
26
+ if (!existsSync(join(cwd, "tsconfig.json")) && !existsSync(join(cwd, "tsconfig.app.json"))) {
27
+ return {
28
+ name: "types",
29
+ score: 0,
30
+ grade: "F",
31
+ details: { skipped: true, reason: "no tsconfig.json" },
32
+ issues: [],
33
+ duration: Date.now() - start,
34
+ };
35
+ }
36
+ const { stdout } = run("npx tsc --noEmit 2>&1 || true", cwd, 30_000);
37
+ const lines = stdout.split("\n");
38
+ for (const line of lines) {
39
+ const match = line.match(/^(.+)\((\d+),\d+\): error (TS\d+): (.+)/);
40
+ if (match) {
41
+ issues.push({
42
+ severity: "error",
43
+ file: match[1],
44
+ line: parseInt(match[2], 10),
45
+ rule: match[3],
46
+ message: match[4],
47
+ });
48
+ }
49
+ }
50
+ }
33
51
  const errorCount = issues.length;
34
52
  const score = errorCount === 0 ? 100 : Math.max(0, 100 - errorCount * 5);
35
53
  return {
package/dist/types.d.ts CHANGED
@@ -30,11 +30,11 @@ export interface VibeReport {
30
30
  };
31
31
  }
32
32
  export interface StackInfo {
33
- language: "typescript" | "javascript" | "unknown";
34
- framework: "react" | "vue" | "svelte" | "none" | "unknown";
33
+ language: "typescript" | "javascript" | "dart" | "unknown";
34
+ framework: "react" | "vue" | "svelte" | "flutter" | "none" | "unknown";
35
35
  bundler: "vite" | "webpack" | "esbuild" | "none" | "unknown";
36
- testRunner: "vitest" | "jest" | "none" | "unknown";
37
- linter: "biome" | "eslint" | "none" | "unknown";
38
- packageManager: "pnpm" | "npm" | "yarn" | "bun" | "unknown";
36
+ testRunner: "vitest" | "jest" | "flutter_test" | "dart_test" | "none" | "unknown";
37
+ linter: "biome" | "eslint" | "dart_analyze" | "none" | "unknown";
38
+ packageManager: "pnpm" | "npm" | "yarn" | "bun" | "pub" | "unknown";
39
39
  }
40
40
  export declare function gradeFromScore(score: number): "A" | "B" | "C" | "D" | "F";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vibecodeqa/cli",
3
- "version": "0.16.0",
4
- "description": "Code health scanner for the AI coding era. 15 checks, zero config, full report.",
3
+ "version": "0.18.0",
4
+ "description": "Code health scanner for the AI coding era. 21 checks, zero config, full report.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "vcqa": "./dist/cli.js",
@@ -12,6 +12,13 @@
12
12
  "LICENSE",
13
13
  "README.md"
14
14
  ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "dev": "tsc --watch",
18
+ "test": "vitest run",
19
+ "lint": "biome check src/",
20
+ "prepublishOnly": "tsc && chmod +x dist/cli.js"
21
+ },
15
22
  "keywords": [
16
23
  "code-quality",
17
24
  "code-health",
@@ -32,7 +39,7 @@
32
39
  "license": "MIT",
33
40
  "repository": {
34
41
  "type": "git",
35
- "url": "https://github.com/freeappstore-online/vibe-check"
42
+ "url": "https://github.com/vibecodeqa/cli"
36
43
  },
37
44
  "homepage": "https://vibecodeqa.online",
38
45
  "engines": {
@@ -43,11 +50,5 @@
43
50
  "@types/node": "^25.8.0",
44
51
  "typescript": "^5.8.3",
45
52
  "vitest": "^4.1.6"
46
- },
47
- "scripts": {
48
- "build": "tsc",
49
- "dev": "tsc --watch",
50
- "test": "vitest run",
51
- "lint": "biome check src/"
52
53
  }
53
- }
54
+ }