@vibecodeqa/cli 0.19.0 → 0.20.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.
@@ -136,7 +136,7 @@ export const CHECK_META = {
136
136
  label: "Architecture",
137
137
  category: "Architecture",
138
138
  priority: "high",
139
- weight: 6,
139
+ weight: 5,
140
140
  description: "Analyzes the import graph to detect structural problems: circular dependencies, god modules (imported by >50% of files), orphan modules (dead code), high fan-out (importing too many modules), and connector modules (high coupling). Generates an SVG architecture diagram.",
141
141
  risk: "Circular dependencies create build order issues and make refactoring impossible without breaking changes. God modules become bottlenecks — any change ripples through the entire codebase. High coupling means you can't change one module without testing everything it touches.",
142
142
  recommendation: "Break circular deps by extracting shared types to a separate file. Split god modules by concern. Reduce fan-out by co-locating related code. Use dependency injection for loose coupling.",
@@ -146,7 +146,7 @@ export const CHECK_META = {
146
146
  label: "Confusion Index",
147
147
  category: "LLM Readiness",
148
148
  priority: "high",
149
- weight: 7,
149
+ weight: 6,
150
150
  description: "Measures naming ambiguity that causes LLMs to misunderstand or edit the wrong code. Checks: file name confusability (Levenshtein distance + synonym detection), generic function/variable names, export name collisions across files, and ambiguous abbreviations.",
151
151
  risk: "GPT-4o drops 28.6 percentage points on code summarization when names are ambiguous (arXiv:2510.03178). LLMs editing similar-named files is the #1 reported failure mode in AI-assisted development. Generic names like process(), handle(), data cause models to misinterpret intent.",
152
152
  recommendation: "Use descriptive, unique names. Avoid synonym files (utils.ts + helpers.ts — pick one). Avoid generic exports. Disambiguate abbreviations (use 'authentication' not 'auth' if both auth meanings exist in the codebase).",
@@ -156,7 +156,7 @@ export const CHECK_META = {
156
156
  label: "Context Locality",
157
157
  category: "LLM Readiness",
158
158
  priority: "high",
159
- weight: 6,
159
+ weight: 5,
160
160
  description: "Measures how self-contained code is for LLM consumption. Checks: token density per file, import count, circular dependencies, and context sinks (files that import many modules but export little). Based on the finding that LLMs lose 30%+ accuracy for information in the middle of long contexts.",
161
161
  risk: "Files over ~4000 tokens exceed the 'sweet spot' for LLM attention (Liu et al. 2023 'Lost in the Middle'). Circular dependencies create infinite loops in LLM code navigation. Heavy import chains force LLMs to load many files, burning context window budget (Chroma 'Context Rot' 2025).",
162
162
  recommendation: "Keep files under 400 lines / 4000 tokens. Limit imports to <15 per file. Break circular dependencies. Co-locate related code to reduce cross-file jumps.",
@@ -191,6 +191,16 @@ export const CHECK_META = {
191
191
  risk: "Barrel files (index.ts re-exports) prevent bundlers from tree-shaking unused code, bloating bundles by 2-10x. Heavy dependencies like moment.js add 300KB when date-fns does the same in 7KB. Static imports of visualization libraries delay initial page load.",
192
192
  recommendation: "Replace barrel re-exports with direct imports. Swap heavy deps for lighter alternatives. Use dynamic import() for large libraries only needed on interaction. Prefer zero-runtime CSS (Tailwind, CSS Modules) over styled-components.",
193
193
  },
194
+ "best-practices": {
195
+ name: "best-practices",
196
+ label: "Best Practices",
197
+ category: "Quality",
198
+ priority: "medium",
199
+ weight: 3,
200
+ description: "Advisory check for industry-standard CI/CD, supply chain, and repo hygiene practices. Checks: GitHub Actions with explicit permissions, OIDC instead of long-lived tokens, pinned action SHAs, frozen lockfile in CI, committed lockfile, engine constraints, SECURITY.md, CODEOWNERS, CONTRIBUTING.md, .env.example, pre-commit hooks, automated dependency updates (Dependabot/Renovate).",
201
+ risk: "Missing CI/CD practices lead to supply chain attacks (tj-actions breach affected 23,000 repos in 2025). Long-lived tokens can be stolen from CI logs. Unpinned actions allow tag-poisoning. No lockfile means non-reproducible builds. No SECURITY.md means vulnerabilities go unreported.",
202
+ recommendation: "Pin third-party actions to SHA. Use OIDC trusted publishing instead of tokens. Set explicit permissions in workflows. Add SECURITY.md, CODEOWNERS, and CONTRIBUTING.md. Configure Dependabot or Renovate for automated dependency updates. Add pre-commit hooks.",
203
+ },
194
204
  "doc-coherence": {
195
205
  name: "doc-coherence",
196
206
  label: "Doc Coherence",
package/dist/cli.js CHANGED
@@ -5,6 +5,7 @@ import { join, resolve } from "node:path";
5
5
  import { detectRepoUrl, detectStack } from "./detect.js";
6
6
  import { generatePages } from "./report/html.js";
7
7
  import { runArchitecture } from "./runners/architecture.js";
8
+ import { runBestPractices } from "./runners/best-practices.js";
8
9
  import { runComplexity } from "./runners/complexity.js";
9
10
  import { runConfusion } from "./runners/confusion.js";
10
11
  import { runContext } from "./runners/context.js";
@@ -79,6 +80,7 @@ async function main() {
79
80
  { name: "react", fn: () => runReact(cwd, stack) },
80
81
  { name: "accessibility", fn: () => runAccessibility(cwd) },
81
82
  { name: "docs", fn: () => runDocs(cwd) },
83
+ { name: "best-practices", fn: () => runBestPractices(cwd) },
82
84
  // Testing
83
85
  { name: "testing", fn: () => runTesting(cwd, stack, skipTests) },
84
86
  // Security
@@ -16,7 +16,7 @@ import { categoryPage, filesPage, issuesPage, overviewPage } from "./pages.js";
16
16
  import { CSS } from "./styles.js";
17
17
  export const GROUPS = [
18
18
  { id: "foundations", label: "Foundations", file: "foundations.html", checks: ["structure", "lint", "types", "type-safety", "standards"] },
19
- { id: "quality", label: "Quality", file: "quality.html", checks: ["complexity", "duplication", "error-handling", "react", "accessibility", "docs"] },
19
+ { id: "quality", label: "Quality", file: "quality.html", checks: ["complexity", "duplication", "error-handling", "react", "accessibility", "docs", "best-practices"] },
20
20
  { id: "testing", label: "Testing", file: "testing.html", checks: ["testing"] },
21
21
  { id: "arch", label: "Architecture", file: "architecture.html", checks: ["architecture", "performance"] },
22
22
  { id: "security", label: "Security", file: "security.html", checks: ["secrets", "security", "dependencies"] },
@@ -0,0 +1,13 @@
1
+ /** Best Practices advisory — checks for industry-standard CI/CD, repo hygiene, and supply chain practices.
2
+ *
3
+ * Unlike other checks, this doesn't penalize harshly — it advises.
4
+ * Issues are "info" and "warning" severity, not errors.
5
+ *
6
+ * Categories:
7
+ * 1. CI/CD — workflows, OIDC, pinned actions, permissions
8
+ * 2. Supply chain — lockfile, provenance, dependency pinning
9
+ * 3. Repo hygiene — branch protection signals, CODEOWNERS, security policy
10
+ * 4. Developer experience — contributing guide, .env.example, scripts
11
+ */
12
+ import type { CheckResult } from "../types.js";
13
+ export declare function runBestPractices(cwd: string): CheckResult;
@@ -0,0 +1,196 @@
1
+ /** Best Practices advisory — checks for industry-standard CI/CD, repo hygiene, and supply chain practices.
2
+ *
3
+ * Unlike other checks, this doesn't penalize harshly — it advises.
4
+ * Issues are "info" and "warning" severity, not errors.
5
+ *
6
+ * Categories:
7
+ * 1. CI/CD — workflows, OIDC, pinned actions, permissions
8
+ * 2. Supply chain — lockfile, provenance, dependency pinning
9
+ * 3. Repo hygiene — branch protection signals, CODEOWNERS, security policy
10
+ * 4. Developer experience — contributing guide, .env.example, scripts
11
+ */
12
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { readDeps } from "../fs-utils.js";
15
+ import { gradeFromScore } from "../types.js";
16
+ export function runBestPractices(cwd) {
17
+ const start = Date.now();
18
+ const issues = [];
19
+ let practices = 0;
20
+ let followed = 0;
21
+ const has = (f) => existsSync(join(cwd, f));
22
+ const read = (f) => { try {
23
+ return readFileSync(join(cwd, f), "utf-8");
24
+ }
25
+ catch {
26
+ return "";
27
+ } };
28
+ // ── 1. CI/CD Best Practices ──
29
+ // Check for GitHub Actions workflows
30
+ const hasWorkflows = has(".github/workflows");
31
+ practices++;
32
+ if (hasWorkflows) {
33
+ followed++;
34
+ const workflows = readdirSync(join(cwd, ".github/workflows")).filter(f => f.endsWith(".yml") || f.endsWith(".yaml"));
35
+ for (const wf of workflows) {
36
+ const content = read(`.github/workflows/${wf}`);
37
+ // Check: actions pinned to SHA (not @v4, @main)
38
+ const actionUses = content.match(/uses:\s*([^\n]+)/g) || [];
39
+ const unpinned = actionUses.filter(u => !u.includes("@") || (!u.match(/@[a-f0-9]{40}/) && !u.includes("@sha")));
40
+ // Only flag third-party actions (not actions/*)
41
+ const unpinnedThirdParty = unpinned.filter(u => !u.includes("actions/") && !u.includes("pnpm/"));
42
+ if (unpinnedThirdParty.length > 0) {
43
+ issues.push({
44
+ severity: "info",
45
+ message: `${wf}: ${unpinnedThirdParty.length} third-party actions not pinned to SHA — vulnerable to supply chain attacks`,
46
+ file: `.github/workflows/${wf}`,
47
+ rule: "pin-actions-to-sha",
48
+ });
49
+ }
50
+ // Check: minimal permissions
51
+ practices++;
52
+ if (content.includes("permissions:")) {
53
+ followed++;
54
+ }
55
+ else {
56
+ issues.push({
57
+ severity: "warning",
58
+ message: `${wf}: no explicit permissions block — defaults to read-write (overly permissive)`,
59
+ file: `.github/workflows/${wf}`,
60
+ rule: "explicit-permissions",
61
+ });
62
+ }
63
+ // Check: OIDC for publishing (no long-lived tokens)
64
+ if (content.includes("publish") || content.includes("deploy")) {
65
+ practices++;
66
+ if (content.includes("id-token: write")) {
67
+ followed++;
68
+ }
69
+ else if (content.includes("NPM_TOKEN") || content.includes("DEPLOY_TOKEN") || content.includes("AWS_ACCESS_KEY")) {
70
+ issues.push({
71
+ severity: "info",
72
+ message: `${wf}: uses long-lived secret tokens — consider OIDC trusted publishing/workload identity`,
73
+ file: `.github/workflows/${wf}`,
74
+ rule: "prefer-oidc",
75
+ });
76
+ }
77
+ }
78
+ // Check: frozen lockfile in CI
79
+ practices++;
80
+ if (content.includes("--frozen-lockfile") || content.includes("--ci") || content.includes("npm ci")) {
81
+ followed++;
82
+ }
83
+ else if (content.includes("install")) {
84
+ issues.push({
85
+ severity: "info",
86
+ message: `${wf}: CI install without frozen lockfile — builds may not be reproducible`,
87
+ file: `.github/workflows/${wf}`,
88
+ rule: "frozen-lockfile-ci",
89
+ });
90
+ }
91
+ }
92
+ }
93
+ else {
94
+ issues.push({
95
+ severity: "warning",
96
+ message: "No .github/workflows/ — no CI/CD automation. Add GitHub Actions for automated testing and deployment.",
97
+ rule: "no-ci",
98
+ });
99
+ }
100
+ // ── 2. Supply Chain ──
101
+ // Lockfile committed
102
+ practices++;
103
+ const hasLockfile = has("pnpm-lock.yaml") || has("package-lock.json") || has("yarn.lock") || has("bun.lockb") || has("pubspec.lock");
104
+ if (hasLockfile) {
105
+ followed++;
106
+ }
107
+ else {
108
+ issues.push({ severity: "warning", message: "No lockfile committed — dependency versions not deterministic", rule: "lockfile" });
109
+ }
110
+ // .npmrc or package.json engine constraints
111
+ practices++;
112
+ const pkg = read("package.json");
113
+ if (pkg.includes('"engines"') || has(".nvmrc") || has(".node-version") || has(".tool-versions")) {
114
+ followed++;
115
+ }
116
+ else {
117
+ issues.push({ severity: "info", message: "No engine constraints (engines in package.json or .nvmrc) — Node version not pinned", rule: "pin-node-version" });
118
+ }
119
+ // npm provenance / package.json has repository field
120
+ if (pkg) {
121
+ practices++;
122
+ if (pkg.includes('"repository"')) {
123
+ followed++;
124
+ }
125
+ else {
126
+ issues.push({ severity: "info", message: "package.json missing repository field — provenance attestation won't link to source", rule: "repository-field" });
127
+ }
128
+ }
129
+ // ── 3. Repo Hygiene ──
130
+ // SECURITY.md or security policy
131
+ practices++;
132
+ if (has("SECURITY.md") || has(".github/SECURITY.md")) {
133
+ followed++;
134
+ }
135
+ else {
136
+ issues.push({ severity: "info", message: "No SECURITY.md — users don't know how to report vulnerabilities", rule: "security-policy" });
137
+ }
138
+ // CODEOWNERS
139
+ practices++;
140
+ if (has("CODEOWNERS") || has(".github/CODEOWNERS") || has("docs/CODEOWNERS")) {
141
+ followed++;
142
+ }
143
+ else {
144
+ issues.push({ severity: "info", message: "No CODEOWNERS file — no mandatory reviewers for critical paths", rule: "codeowners" });
145
+ }
146
+ // Branch protection signal: require PR (check for documented merge strategy)
147
+ practices++;
148
+ if (has("CONTRIBUTING.md") || has(".github/CONTRIBUTING.md")) {
149
+ followed++;
150
+ }
151
+ else {
152
+ issues.push({ severity: "info", message: "No CONTRIBUTING.md — onboarding is harder for new contributors", rule: "contributing-guide" });
153
+ }
154
+ // ── 4. Developer Experience ──
155
+ // .env.example
156
+ practices++;
157
+ const hasEnvFiles = has(".env") || has(".env.local") || has(".env.development");
158
+ if (hasEnvFiles && !has(".env.example")) {
159
+ issues.push({ severity: "info", message: "Has .env files but no .env.example — new developers won't know what vars are needed", rule: "env-example" });
160
+ }
161
+ else {
162
+ followed++;
163
+ }
164
+ // Pre-commit hooks (husky, lefthook, lint-staged)
165
+ practices++;
166
+ const deps = readDeps(cwd);
167
+ if (deps.husky || deps.lefthook || deps["lint-staged"] || has(".husky") || has(".lefthook.yml")) {
168
+ followed++;
169
+ }
170
+ else {
171
+ issues.push({ severity: "info", message: "No pre-commit hooks (husky/lefthook) — lint/format not enforced before commit", rule: "pre-commit-hooks" });
172
+ }
173
+ // Renovate/Dependabot for automated dependency updates
174
+ practices++;
175
+ if (has(".github/dependabot.yml") || has("renovate.json") || has(".renovaterc") || has(".renovaterc.json")) {
176
+ followed++;
177
+ }
178
+ else {
179
+ issues.push({ severity: "info", message: "No Dependabot/Renovate — dependency updates are manual and often forgotten", rule: "automated-deps" });
180
+ }
181
+ // ── Score ──
182
+ const pct = practices > 0 ? Math.round((followed / practices) * 100) : 100;
183
+ const score = pct;
184
+ return {
185
+ name: "best-practices",
186
+ score,
187
+ grade: gradeFromScore(score),
188
+ details: {
189
+ practicesChecked: practices,
190
+ practicesFollowed: followed,
191
+ adherence: `${pct}%`,
192
+ },
193
+ issues,
194
+ duration: Date.now() - start,
195
+ };
196
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecodeqa/cli",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Code health scanner for the AI coding era. 21 checks, zero config, full report.",
5
5
  "type": "module",
6
6
  "bin": {