@vibecodeqa/cli 0.19.0 → 0.20.1
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/check-meta.js +13 -3
- package/dist/cli.js +2 -0
- package/dist/report/html.js +1 -1
- package/dist/runners/best-practices.d.ts +13 -0
- package/dist/runners/best-practices.js +319 -0
- package/package.json +1 -1
package/dist/check-meta.js
CHANGED
|
@@ -136,7 +136,7 @@ export const CHECK_META = {
|
|
|
136
136
|
label: "Architecture",
|
|
137
137
|
category: "Architecture",
|
|
138
138
|
priority: "high",
|
|
139
|
-
weight:
|
|
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:
|
|
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:
|
|
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
|
package/dist/report/html.js
CHANGED
|
@@ -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,319 @@
|
|
|
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
|
+
// ── 5. Code Quality Tooling ──
|
|
182
|
+
// Linter configured
|
|
183
|
+
practices++;
|
|
184
|
+
if (has("biome.json") || has(".eslintrc.json") || has(".eslintrc.js") || has("eslint.config.js") || has("eslint.config.ts") || has("analysis_options.yaml")) {
|
|
185
|
+
followed++;
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
issues.push({ severity: "warning", message: "No linter config (ESLint/Biome/dart analyze) — code style not enforced", rule: "linter-config" });
|
|
189
|
+
}
|
|
190
|
+
// Formatter configured
|
|
191
|
+
practices++;
|
|
192
|
+
if (has("biome.json") || has(".prettierrc") || has(".prettierrc.json") || has("prettier.config.js") || has(".editorconfig")) {
|
|
193
|
+
followed++;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
issues.push({ severity: "info", message: "No formatter config (Prettier/Biome/.editorconfig) — inconsistent code formatting", rule: "formatter-config" });
|
|
197
|
+
}
|
|
198
|
+
// TypeScript strict mode
|
|
199
|
+
practices++;
|
|
200
|
+
const tsconfig = read("tsconfig.json");
|
|
201
|
+
if (!tsconfig || tsconfig.includes('"strict": true') || tsconfig.includes('"strict":true')) {
|
|
202
|
+
followed++;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
issues.push({ severity: "info", message: "TypeScript strict mode not enabled — allows implicit any and null errors", rule: "ts-strict-mode" });
|
|
206
|
+
}
|
|
207
|
+
// ── 6. Testing Best Practices ──
|
|
208
|
+
// Test script exists
|
|
209
|
+
practices++;
|
|
210
|
+
if (pkg.includes('"test"') || has("pubspec.yaml")) {
|
|
211
|
+
followed++;
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
issues.push({ severity: "warning", message: "No test script in package.json — testing not configured", rule: "test-script" });
|
|
215
|
+
}
|
|
216
|
+
// Coverage configured
|
|
217
|
+
practices++;
|
|
218
|
+
if (pkg.includes("coverage") || has("vitest.config.ts") || has("jest.config.ts") || has("jest.config.js")) {
|
|
219
|
+
followed++;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
issues.push({ severity: "info", message: "No test coverage configuration — coverage thresholds not enforced", rule: "coverage-config" });
|
|
223
|
+
}
|
|
224
|
+
// ── 7. Docker / Deployment ──
|
|
225
|
+
// Dockerfile best practices (if Docker is used)
|
|
226
|
+
if (has("Dockerfile") || has("docker-compose.yml") || has("docker-compose.yaml")) {
|
|
227
|
+
practices++;
|
|
228
|
+
const dockerfile = read("Dockerfile");
|
|
229
|
+
if (dockerfile.includes("FROM") && !dockerfile.includes("latest")) {
|
|
230
|
+
followed++;
|
|
231
|
+
}
|
|
232
|
+
else if (dockerfile.includes(":latest")) {
|
|
233
|
+
issues.push({ severity: "warning", message: "Dockerfile uses :latest tag — pin to a specific version for reproducible builds", rule: "docker-pin-version" });
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
followed++;
|
|
237
|
+
}
|
|
238
|
+
// Multi-stage build
|
|
239
|
+
practices++;
|
|
240
|
+
const fromCount = (dockerfile.match(/^FROM /gm) || []).length;
|
|
241
|
+
if (fromCount >= 2) {
|
|
242
|
+
followed++;
|
|
243
|
+
}
|
|
244
|
+
else if (dockerfile.length > 100) {
|
|
245
|
+
issues.push({ severity: "info", message: "Dockerfile is single-stage — consider multi-stage to reduce image size", rule: "docker-multi-stage" });
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
followed++;
|
|
249
|
+
}
|
|
250
|
+
// .dockerignore
|
|
251
|
+
practices++;
|
|
252
|
+
if (has(".dockerignore")) {
|
|
253
|
+
followed++;
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
issues.push({ severity: "info", message: "No .dockerignore — node_modules and build artifacts will bloat Docker image", rule: "dockerignore" });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// ── 8. Git Practices ──
|
|
260
|
+
// .gitignore is comprehensive
|
|
261
|
+
practices++;
|
|
262
|
+
const gitignore = read(".gitignore");
|
|
263
|
+
if (gitignore.includes("node_modules") || gitignore.includes(".dart_tool") || gitignore.includes("build/")) {
|
|
264
|
+
followed++;
|
|
265
|
+
}
|
|
266
|
+
else if (gitignore) {
|
|
267
|
+
issues.push({ severity: "info", message: ".gitignore exists but may be incomplete — ensure build artifacts are excluded", rule: "gitignore-complete" });
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
followed++; // no gitignore = handled by structure check
|
|
271
|
+
}
|
|
272
|
+
// Conventional commits signal (commitlint or similar)
|
|
273
|
+
practices++;
|
|
274
|
+
if (deps.commitlint || deps["@commitlint/cli"] || has("commitlint.config.js") || has(".commitlintrc.json") || has(".changeset")) {
|
|
275
|
+
followed++;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
issues.push({ severity: "info", message: "No commit convention enforcement (commitlint/changesets) — changelog generation is manual", rule: "conventional-commits" });
|
|
279
|
+
}
|
|
280
|
+
// ── 9. Monitoring & Observability ──
|
|
281
|
+
// Error tracking (Sentry, Bugsnag, etc.)
|
|
282
|
+
practices++;
|
|
283
|
+
if (deps["@sentry/node"] || deps["@sentry/react"] || deps["@sentry/browser"] || deps.bugsnag || deps["@bugsnag/js"]) {
|
|
284
|
+
followed++;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
issues.push({ severity: "info", message: "No error tracking (Sentry/Bugsnag) — production errors may go unnoticed", rule: "error-tracking" });
|
|
288
|
+
}
|
|
289
|
+
// ── 10. API & Configuration ──
|
|
290
|
+
// Environment validation (zod, joi, envalid)
|
|
291
|
+
practices++;
|
|
292
|
+
if (deps.zod || deps.joi || deps.envalid || deps["@t3-oss/env-core"] || deps["@t3-oss/env-nextjs"]) {
|
|
293
|
+
followed++;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
const hasEnvUsage = pkg.includes("process.env") || read("src/index.ts").includes("process.env") || read("src/main.ts").includes("process.env");
|
|
297
|
+
if (hasEnvUsage) {
|
|
298
|
+
issues.push({ severity: "info", message: "Uses env vars but no validation library (zod/envalid) — missing vars crash at runtime", rule: "env-validation" });
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
followed++;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// ── Score ──
|
|
305
|
+
const pct = practices > 0 ? Math.round((followed / practices) * 100) : 100;
|
|
306
|
+
const score = pct;
|
|
307
|
+
return {
|
|
308
|
+
name: "best-practices",
|
|
309
|
+
score,
|
|
310
|
+
grade: gradeFromScore(score),
|
|
311
|
+
details: {
|
|
312
|
+
practicesChecked: practices,
|
|
313
|
+
practicesFollowed: followed,
|
|
314
|
+
adherence: `${pct}%`,
|
|
315
|
+
},
|
|
316
|
+
issues,
|
|
317
|
+
duration: Date.now() - start,
|
|
318
|
+
};
|
|
319
|
+
}
|