pi-lens 2.2.9 โ 3.0.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.
- package/CHANGELOG.md +198 -0
- package/README.md +709 -519
- package/clients/__tests__/file-time.test.js +216 -0
- package/clients/__tests__/file-time.test.ts +276 -0
- package/clients/__tests__/format-service.test.js +245 -0
- package/clients/__tests__/format-service.test.ts +339 -0
- package/clients/__tests__/formatters.test.js +271 -0
- package/clients/__tests__/formatters.test.ts +401 -0
- package/clients/amain-types.js +164 -0
- package/clients/amain-types.ts +165 -0
- package/clients/architect-client.js +56 -12
- package/clients/architect-client.ts +81 -16
- package/clients/ast-grep-client.js +2 -2
- package/clients/ast-grep-client.ts +14 -39
- package/clients/ast-grep-parser.ts +1 -1
- package/clients/ast-grep-rule-manager.js +8 -0
- package/clients/ast-grep-rule-manager.ts +10 -1
- package/clients/ast-grep-types.js +9 -0
- package/clients/ast-grep-types.ts +106 -0
- package/clients/auto-loop.js +10 -0
- package/clients/auto-loop.ts +14 -1
- package/clients/biome-client.js +81 -19
- package/clients/biome-client.ts +103 -22
- package/clients/bus/bus.js +191 -0
- package/clients/bus/bus.ts +251 -0
- package/clients/bus/events.js +214 -0
- package/clients/bus/events.ts +279 -0
- package/clients/bus/index.js +8 -0
- package/clients/bus/index.ts +9 -0
- package/clients/bus/integration.js +158 -0
- package/clients/bus/integration.ts +214 -0
- package/clients/complexity-client.js +13 -7
- package/clients/complexity-client.ts +13 -7
- package/clients/config-validator.js +465 -0
- package/clients/config-validator.ts +558 -0
- package/clients/dependency-checker.js +4 -10
- package/clients/dependency-checker.ts +4 -10
- package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
- package/clients/dispatch/__tests__/autofix-integration.test.ts +300 -0
- package/clients/dispatch/__tests__/runner-registration.test.js +236 -0
- package/clients/dispatch/__tests__/runner-registration.test.ts +282 -0
- package/clients/dispatch/bus-dispatcher.js +177 -0
- package/clients/dispatch/bus-dispatcher.ts +251 -0
- package/clients/dispatch/dispatcher.edge.test.js +82 -0
- package/clients/dispatch/dispatcher.edge.test.ts +100 -0
- package/clients/dispatch/dispatcher.format.test.js +46 -0
- package/clients/dispatch/dispatcher.format.test.ts +58 -0
- package/clients/dispatch/dispatcher.inline.test.js +74 -0
- package/clients/dispatch/dispatcher.inline.test.ts +93 -0
- package/clients/dispatch/dispatcher.js +19 -53
- package/clients/dispatch/dispatcher.ts +20 -67
- package/clients/dispatch/plan.js +9 -4
- package/clients/dispatch/plan.ts +9 -4
- package/clients/dispatch/runners/architect.js +21 -7
- package/clients/dispatch/runners/architect.test.js +138 -0
- package/clients/dispatch/runners/architect.test.ts +162 -0
- package/clients/dispatch/runners/architect.ts +22 -7
- package/clients/dispatch/runners/ast-grep-napi.js +462 -0
- package/clients/dispatch/runners/ast-grep-napi.test.js +111 -0
- package/clients/dispatch/runners/ast-grep-napi.test.ts +133 -0
- package/clients/dispatch/runners/ast-grep-napi.ts +506 -0
- package/clients/dispatch/runners/ast-grep.js +62 -19
- package/clients/dispatch/runners/ast-grep.ts +70 -18
- package/clients/dispatch/runners/biome.js +29 -53
- package/clients/dispatch/runners/biome.ts +29 -63
- package/clients/dispatch/runners/config-validation.js +67 -0
- package/clients/dispatch/runners/config-validation.ts +82 -0
- package/clients/dispatch/runners/go-vet.js +4 -28
- package/clients/dispatch/runners/go-vet.ts +4 -32
- package/clients/dispatch/runners/index.js +30 -10
- package/clients/dispatch/runners/index.ts +30 -10
- package/clients/dispatch/runners/oxlint.js +141 -0
- package/clients/dispatch/runners/oxlint.test.js +230 -0
- package/clients/dispatch/runners/oxlint.test.ts +303 -0
- package/clients/dispatch/runners/oxlint.ts +175 -0
- package/clients/dispatch/runners/pyright.js +40 -70
- package/clients/dispatch/runners/pyright.test.js +16 -2
- package/clients/dispatch/runners/pyright.test.ts +14 -2
- package/clients/dispatch/runners/pyright.ts +48 -91
- package/clients/dispatch/runners/python-slop.js +97 -0
- package/clients/dispatch/runners/python-slop.test.js +203 -0
- package/clients/dispatch/runners/python-slop.test.ts +298 -0
- package/clients/dispatch/runners/python-slop.ts +124 -0
- package/clients/dispatch/runners/ruff.js +18 -71
- package/clients/dispatch/runners/ruff.ts +19 -79
- package/clients/dispatch/runners/rust-clippy.js +28 -32
- package/clients/dispatch/runners/rust-clippy.ts +29 -31
- package/clients/dispatch/runners/scan_codebase.test.js +89 -0
- package/clients/dispatch/runners/scan_codebase.test.ts +105 -0
- package/clients/dispatch/runners/shellcheck.js +147 -0
- package/clients/dispatch/runners/shellcheck.test.js +98 -0
- package/clients/dispatch/runners/shellcheck.test.ts +129 -0
- package/clients/dispatch/runners/shellcheck.ts +188 -0
- package/clients/dispatch/runners/similarity.js +230 -0
- package/clients/dispatch/runners/similarity.ts +339 -0
- package/clients/dispatch/runners/spellcheck.js +106 -0
- package/clients/dispatch/runners/spellcheck.test.js +158 -0
- package/clients/dispatch/runners/spellcheck.test.ts +214 -0
- package/clients/dispatch/runners/spellcheck.ts +136 -0
- package/clients/dispatch/runners/tree-sitter.js +107 -0
- package/clients/dispatch/runners/tree-sitter.ts +135 -0
- package/clients/dispatch/runners/ts-lsp.js +104 -33
- package/clients/dispatch/runners/ts-lsp.ts +120 -38
- package/clients/dispatch/runners/ts-slop.js +113 -0
- package/clients/dispatch/runners/ts-slop.test.js +180 -0
- package/clients/dispatch/runners/ts-slop.test.ts +230 -0
- package/clients/dispatch/runners/ts-slop.ts +142 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +134 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.ts +186 -0
- package/clients/dispatch/runners/utils/runner-helpers.js +115 -0
- package/clients/dispatch/runners/utils/runner-helpers.ts +167 -0
- package/clients/dispatch/runners/utils.js +2 -4
- package/clients/dispatch/runners/utils.ts +2 -4
- package/clients/dispatch/types.ts +1 -1
- package/clients/dispatch/utils/format-utils.js +49 -0
- package/clients/dispatch/utils/format-utils.ts +60 -0
- package/clients/dogfood.test.js +201 -0
- package/clients/dogfood.test.ts +269 -0
- package/clients/file-time.js +152 -0
- package/clients/file-time.ts +208 -0
- package/clients/file-utils.js +40 -0
- package/clients/file-utils.ts +44 -0
- package/clients/fix-scanners.js +10 -20
- package/clients/fix-scanners.ts +10 -22
- package/clients/format-service.js +172 -0
- package/clients/format-service.ts +254 -0
- package/clients/formatters.js +435 -0
- package/clients/formatters.ts +508 -0
- package/clients/go-client.js +5 -14
- package/clients/go-client.ts +5 -13
- package/clients/installer/index.js +356 -0
- package/clients/installer/index.ts +426 -0
- package/clients/jscpd-client.js +11 -9
- package/clients/jscpd-client.ts +12 -8
- package/clients/knip-client.js +3 -7
- package/clients/knip-client.ts +3 -6
- package/clients/lsp/__tests__/client.test.js +325 -0
- package/clients/lsp/__tests__/client.test.ts +434 -0
- package/clients/lsp/__tests__/config.test.js +166 -0
- package/clients/lsp/__tests__/config.test.ts +209 -0
- package/clients/lsp/__tests__/error-recovery.test.js +213 -0
- package/clients/lsp/__tests__/error-recovery.test.ts +279 -0
- package/clients/lsp/__tests__/integration.test.js +127 -0
- package/clients/lsp/__tests__/integration.test.ts +160 -0
- package/clients/lsp/__tests__/launch.test.js +260 -0
- package/clients/lsp/__tests__/launch.test.ts +329 -0
- package/clients/lsp/__tests__/server.test.js +259 -0
- package/clients/lsp/__tests__/server.test.ts +332 -0
- package/clients/lsp/__tests__/service.test.js +417 -0
- package/clients/lsp/__tests__/service.test.ts +499 -0
- package/clients/lsp/client.js +235 -0
- package/clients/lsp/client.ts +328 -0
- package/clients/lsp/config.js +115 -0
- package/clients/lsp/config.ts +149 -0
- package/clients/lsp/index.js +222 -0
- package/clients/lsp/index.ts +280 -0
- package/clients/lsp/installer/index.js +391 -0
- package/clients/lsp/interactive-install.js +210 -0
- package/clients/lsp/interactive-install.ts +251 -0
- package/clients/lsp/language.js +170 -0
- package/clients/lsp/language.ts +216 -0
- package/clients/lsp/launch.js +174 -0
- package/clients/lsp/launch.ts +240 -0
- package/clients/lsp/lsp/launch.js +116 -0
- package/clients/lsp/lsp/server.js +532 -0
- package/clients/lsp/lsp-index.js +10 -0
- package/clients/lsp/lsp-index.ts +11 -0
- package/clients/lsp/path-utils.js +48 -0
- package/clients/lsp/path-utils.ts +52 -0
- package/clients/lsp/server.js +615 -0
- package/clients/lsp/server.ts +800 -0
- package/clients/lsp/test-py-spawn/requirements.txt +1 -0
- package/clients/lsp/test-py-spawn/test.py +3 -0
- package/clients/lsp/test-py-svc/requirements.txt +1 -0
- package/clients/lsp/test-py-svc/test.py +3 -0
- package/clients/lsp/test-python-project/requirements.txt +1 -0
- package/clients/lsp/test-python-project/test.py +5 -0
- package/clients/metrics-history.js +2 -2
- package/clients/metrics-history.ts +2 -2
- package/clients/production-readiness.js +522 -0
- package/clients/production-readiness.ts +556 -0
- package/clients/project-index.js +255 -0
- package/clients/project-index.ts +383 -0
- package/clients/project-metadata.js +531 -0
- package/clients/project-metadata.ts +624 -0
- package/clients/ruff-client.js +56 -16
- package/clients/ruff-client.ts +72 -15
- package/clients/runner-tracker.js +152 -0
- package/clients/runner-tracker.ts +213 -0
- package/clients/rust-client.js +4 -11
- package/clients/rust-client.ts +5 -11
- package/clients/safe-spawn.js +96 -0
- package/clients/safe-spawn.ts +128 -0
- package/clients/scan-architectural-debt.js +3 -6
- package/clients/scan-architectural-debt.ts +3 -6
- package/clients/scan-utils.js +5 -20
- package/clients/scan-utils.ts +5 -29
- package/clients/secrets-scanner.js +3 -17
- package/clients/secrets-scanner.ts +4 -20
- package/clients/services/__tests__/effect-integration.test.js +86 -0
- package/clients/services/__tests__/effect-integration.test.ts +111 -0
- package/clients/services/effect-integration.js +194 -0
- package/clients/services/effect-integration.ts +268 -0
- package/clients/services/index.js +7 -0
- package/clients/services/index.ts +8 -0
- package/clients/services/runner-service.js +105 -0
- package/clients/services/runner-service.ts +179 -0
- package/clients/sg-runner.js +87 -13
- package/clients/sg-runner.ts +97 -13
- package/clients/state-matrix.js +160 -0
- package/clients/state-matrix.ts +202 -0
- package/clients/subprocess-client.js +10 -9
- package/clients/subprocess-client.ts +10 -8
- package/clients/test-runner-client.js +3 -7
- package/clients/test-runner-client.ts +3 -6
- package/clients/tool-availability.js +4 -10
- package/clients/tool-availability.ts +4 -9
- package/clients/tree-sitter-client.js +564 -0
- package/clients/tree-sitter-client.ts +797 -0
- package/clients/tree-sitter-query-loader.js +355 -0
- package/clients/tree-sitter-query-loader.ts +425 -0
- package/clients/type-coverage-client.js +3 -7
- package/clients/type-coverage-client.ts +3 -6
- package/clients/typescript-client.codefix.test.js +157 -0
- package/clients/typescript-client.codefix.test.ts +186 -0
- package/clients/typescript-client.js +43 -0
- package/clients/typescript-client.ts +98 -0
- package/commands/booboo.js +799 -219
- package/commands/booboo.ts +1004 -225
- package/commands/clients/ast-grep-client.js +250 -0
- package/commands/clients/ast-grep-parser.js +86 -0
- package/commands/clients/ast-grep-rule-manager.js +91 -0
- package/commands/clients/ast-grep-types.js +9 -0
- package/commands/clients/biome-client.js +380 -0
- package/commands/clients/complexity-client.js +667 -0
- package/commands/clients/file-kinds.js +177 -0
- package/commands/clients/file-utils.js +40 -0
- package/commands/clients/jscpd-client.js +169 -0
- package/commands/clients/knip-client.js +211 -0
- package/commands/clients/ruff-client.js +297 -0
- package/commands/clients/safe-spawn.js +88 -0
- package/commands/clients/scan-utils.js +83 -0
- package/commands/clients/sg-runner.js +190 -0
- package/commands/clients/types.js +11 -0
- package/commands/clients/typescript-client.js +505 -0
- package/commands/fix-from-booboo.js +398 -0
- package/commands/fix-from-booboo.ts +485 -0
- package/commands/fix-simplified.js +618 -0
- package/commands/fix-simplified.ts +768 -0
- package/commands/rate.js +10 -14
- package/commands/rate.ts +9 -16
- package/default-architect.yaml +59 -15
- package/index.ts +342 -429
- package/package.json +16 -3
- package/rules/ast-grep-rules/rules/empty-catch.yml +38 -13
- package/rules/ast-grep-rules/rules/no-array-constructor.yml +1 -0
- package/rules/ast-grep-rules/rules/no-debugger.yml +2 -0
- package/rules/python-slop-rules/.sgconfig.yml +4 -0
- package/rules/python-slop-rules/rules/slop-rules.yml +647 -0
- package/rules/tree-sitter-queries/python/bare-except.yml +54 -0
- package/rules/tree-sitter-queries/python/eval-exec.yml +50 -0
- package/rules/tree-sitter-queries/python/is-vs-equals.yml +60 -0
- package/rules/tree-sitter-queries/python/mutable-default-arg.yml +57 -0
- package/rules/tree-sitter-queries/python/unreachable-except.yml +60 -0
- package/rules/tree-sitter-queries/python/wildcard-import.yml +46 -0
- package/rules/tree-sitter-queries/tsx/dangerously-set-inner-html.yml +63 -0
- package/rules/tree-sitter-queries/typescript/await-in-loop.yml +56 -0
- package/rules/tree-sitter-queries/typescript/console-statement.yml +47 -0
- package/rules/tree-sitter-queries/typescript/debugger.yml +47 -0
- package/rules/tree-sitter-queries/typescript/deep-nesting.yml +117 -0
- package/rules/tree-sitter-queries/typescript/deep-promise-chain.yml +73 -0
- package/rules/tree-sitter-queries/typescript/empty-catch.yml +64 -0
- package/rules/tree-sitter-queries/typescript/eval.yml +48 -0
- package/rules/tree-sitter-queries/typescript/hardcoded-secrets.yml +78 -0
- package/rules/tree-sitter-queries/typescript/long-parameter-list.yml +62 -0
- package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +49 -0
- package/rules/tree-sitter-queries/typescript/nested-ternary.yml +45 -0
- package/rules/ts-slop-rules/.sgconfig.yml +4 -0
- package/rules/ts-slop-rules/rules/in-correct-optional-input-type.yml +10 -0
- package/rules/ts-slop-rules/rules/jwt-no-verify.yml +13 -0
- package/rules/ts-slop-rules/rules/no-architecture-violation.yml +10 -0
- package/rules/ts-slop-rules/rules/no-case-declarations.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dangerously-set-inner-html.yml +10 -0
- package/rules/ts-slop-rules/rules/no-debugger.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-args.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-class-members.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-keys.yml +10 -0
- package/rules/ts-slop-rules/rules/no-eval.yml +13 -0
- package/rules/ts-slop-rules/rules/no-hardcoded-secrets.yml +12 -0
- package/rules/ts-slop-rules/rules/no-implied-eval.yml +12 -0
- package/rules/ts-slop-rules/rules/no-inner-html.yml +13 -0
- package/rules/ts-slop-rules/rules/no-javascript-url.yml +10 -0
- package/rules/ts-slop-rules/rules/no-mutable-default.yml +10 -0
- package/rules/ts-slop-rules/rules/no-nested-links.yml +12 -0
- package/rules/ts-slop-rules/rules/no-new-symbol.yml +10 -0
- package/rules/ts-slop-rules/rules/no-new-wrappers.yml +13 -0
- package/rules/ts-slop-rules/rules/no-open-redirect.yml +16 -0
- package/rules/ts-slop-rules/rules/slop-rules.yml +455 -0
- package/rules/ts-slop-rules/rules/weak-rsa-key.yml +12 -0
- package/skills/ast-grep/SKILL.md +182 -0
- package/clients/dispatch/runners/secrets.js +0 -109
- package/commands/fix.js +0 -244
- package/commands/fix.ts +0 -373
- package/rules/ast-grep-rules/rules/no-lonely-if.yml +0 -13
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production Readiness Runner for pi-lens
|
|
3
|
+
*
|
|
4
|
+
* Inspired by pi-validate - validates project is production-ready
|
|
5
|
+
* Categories: CODE, TESTS, DOCS, CONFIG, DEPLOY
|
|
6
|
+
* Each category scored 0-100, weighted total calculated
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { EXCLUDED_DIRS } from "./file-utils.js";
|
|
12
|
+
|
|
13
|
+
// --- Types ---
|
|
14
|
+
|
|
15
|
+
export interface ProductionReadinessResult {
|
|
16
|
+
overallScore: number;
|
|
17
|
+
grade: "A" | "B" | "C" | "D" | "F";
|
|
18
|
+
categories: {
|
|
19
|
+
code: CategoryResult;
|
|
20
|
+
tests: CategoryResult;
|
|
21
|
+
docs: CategoryResult;
|
|
22
|
+
config: CategoryResult;
|
|
23
|
+
deploy: CategoryResult;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CategoryResult {
|
|
28
|
+
score: number;
|
|
29
|
+
weight: number;
|
|
30
|
+
issues: string[];
|
|
31
|
+
details: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- Constants ---
|
|
35
|
+
|
|
36
|
+
const WEIGHTS: Record<string, number> = {
|
|
37
|
+
code: 0.30, // 30% - console.log, TODO, empty catch, as any, debugger
|
|
38
|
+
tests: 0.20, // 20% - test files, test cases, framework config
|
|
39
|
+
docs: 0.20, // 20% - README, LICENSE, CHANGELOG, pkg metadata
|
|
40
|
+
config: 0.15, // 15% - gitignore, tsconfig, package.json, no node_modules
|
|
41
|
+
deploy: 0.15, // 15% - clean git, version set, build script, entry point
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// --- Main Entry Point ---
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Run production readiness validation on a project
|
|
48
|
+
*/
|
|
49
|
+
export function validateProductionReadiness(targetPath: string): ProductionReadinessResult {
|
|
50
|
+
const categories = {
|
|
51
|
+
code: validateCode(targetPath),
|
|
52
|
+
tests: validateTests(targetPath),
|
|
53
|
+
docs: validateDocs(targetPath),
|
|
54
|
+
config: validateConfig(targetPath),
|
|
55
|
+
deploy: validateDeploy(targetPath),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Calculate weighted score
|
|
59
|
+
const overallScore = Math.round(
|
|
60
|
+
categories.code.score * WEIGHTS.code +
|
|
61
|
+
categories.tests.score * WEIGHTS.tests +
|
|
62
|
+
categories.docs.score * WEIGHTS.docs +
|
|
63
|
+
categories.config.score * WEIGHTS.config +
|
|
64
|
+
categories.deploy.score * WEIGHTS.deploy
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
overallScore,
|
|
69
|
+
grade: scoreToGrade(overallScore),
|
|
70
|
+
categories,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// --- Category Validators ---
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* CODE: Check for production anti-patterns
|
|
78
|
+
*/
|
|
79
|
+
function validateCode(root: string): CategoryResult {
|
|
80
|
+
const issues: string[] = [];
|
|
81
|
+
const details: string[] = [];
|
|
82
|
+
|
|
83
|
+
const sourceFiles = findSourceFiles(root);
|
|
84
|
+
if (sourceFiles.length === 0) {
|
|
85
|
+
issues.push("No source files found");
|
|
86
|
+
return { score: 0, weight: WEIGHTS.code, issues, details };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
details.push(`${sourceFiles.length} source files`);
|
|
90
|
+
|
|
91
|
+
let totalLines = 0;
|
|
92
|
+
let consoleLogs = 0;
|
|
93
|
+
let todos = 0;
|
|
94
|
+
let emptyCatches = 0;
|
|
95
|
+
let anyCasts = 0;
|
|
96
|
+
let debuggers = 0;
|
|
97
|
+
|
|
98
|
+
for (const file of sourceFiles) {
|
|
99
|
+
try {
|
|
100
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
101
|
+
const lines = content.split("\n");
|
|
102
|
+
totalLines += lines.length;
|
|
103
|
+
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
// Skip comments for some checks
|
|
106
|
+
const codePart = line.replace(/\/\/.*$/g, "");
|
|
107
|
+
|
|
108
|
+
if (codePart.match(/console\.(log|debug|info|warn)\(/)) consoleLogs++;
|
|
109
|
+
if (codePart.match(/\/\/\s*(TODO|FIXME|HACK|XXX|BUG)/i)) todos++;
|
|
110
|
+
if (codePart.match(/catch\s*\([^)]*\)\s*\{\s*\}/)) emptyCatches++;
|
|
111
|
+
if (codePart.match(/as\s+any\b/) && !line.includes("// biome-ignore")) anyCasts++;
|
|
112
|
+
if (codePart.match(/\bdebugger\b/)) debuggers++;
|
|
113
|
+
}
|
|
114
|
+
} catch {}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
details.push(`${totalLines} total lines`);
|
|
118
|
+
|
|
119
|
+
// Build issues list
|
|
120
|
+
if (consoleLogs > 0) issues.push(`${consoleLogs} console.log/debug statements`);
|
|
121
|
+
if (todos > 0) issues.push(`${todos} TODO/FIXME comments`);
|
|
122
|
+
if (emptyCatches > 0) issues.push(`${emptyCatches} empty catch blocks`);
|
|
123
|
+
if (anyCasts > 3) issues.push(`${anyCasts} 'as any' casts (${anyCasts > 10 ? "many" : "some"})`);
|
|
124
|
+
if (debuggers > 0) issues.push(`${debuggers} debugger statements!`);
|
|
125
|
+
|
|
126
|
+
// Calculate score
|
|
127
|
+
let score = 100;
|
|
128
|
+
score -= Math.min(20, consoleLogs * 2);
|
|
129
|
+
score -= Math.min(10, todos);
|
|
130
|
+
score -= Math.min(15, emptyCatches * 5);
|
|
131
|
+
score -= Math.min(10, Math.max(0, anyCasts - 3));
|
|
132
|
+
score -= debuggers * 15;
|
|
133
|
+
|
|
134
|
+
return { score: Math.max(0, score), weight: WEIGHTS.code, issues, details };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* TESTS: Check test coverage and configuration
|
|
139
|
+
*/
|
|
140
|
+
function validateTests(root: string): CategoryResult {
|
|
141
|
+
const issues: string[] = [];
|
|
142
|
+
const details: string[] = [];
|
|
143
|
+
|
|
144
|
+
// Look for test files
|
|
145
|
+
const testFiles = findTestFiles(root);
|
|
146
|
+
const hasTestFiles = testFiles.length > 0;
|
|
147
|
+
|
|
148
|
+
// Look for test framework config
|
|
149
|
+
const testFramework = detectTestFramework(root);
|
|
150
|
+
const hasTestFramework = testFramework !== null;
|
|
151
|
+
|
|
152
|
+
// Count test cases (rough estimate)
|
|
153
|
+
let testCases = 0;
|
|
154
|
+
for (const file of testFiles) {
|
|
155
|
+
try {
|
|
156
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
157
|
+
// Match common test patterns
|
|
158
|
+
const matches = content.match(/\b(it|test|describe|spec|Scenario)\s*\(/g);
|
|
159
|
+
if (matches) testCases += matches.length;
|
|
160
|
+
} catch {}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
details.push(`${testFiles.length} test files`);
|
|
164
|
+
details.push(`${testCases} test cases (approximate)`);
|
|
165
|
+
if (testFramework) details.push(`Framework: ${testFramework}`);
|
|
166
|
+
|
|
167
|
+
// Build issues
|
|
168
|
+
if (!hasTestFiles) issues.push("No test files found");
|
|
169
|
+
else if (testFiles.length < 2) issues.push("Only 1 test file (consider adding more)");
|
|
170
|
+
|
|
171
|
+
if (!hasTestFramework) issues.push("No test framework configuration detected");
|
|
172
|
+
|
|
173
|
+
if (testCases === 0 && hasTestFiles) issues.push("Test files found but no test cases detected");
|
|
174
|
+
|
|
175
|
+
// Calculate score
|
|
176
|
+
let score = 100;
|
|
177
|
+
if (!hasTestFiles) score -= 50;
|
|
178
|
+
else if (testFiles.length < 2) score -= 10;
|
|
179
|
+
|
|
180
|
+
if (!hasTestFramework) score -= 25;
|
|
181
|
+
if (testCases === 0 && hasTestFiles) score -= 15;
|
|
182
|
+
|
|
183
|
+
return { score: Math.max(0, score), weight: WEIGHTS.tests, issues, details };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* DOCS: Check documentation completeness
|
|
188
|
+
*/
|
|
189
|
+
function validateDocs(root: string): CategoryResult {
|
|
190
|
+
const issues: string[] = [];
|
|
191
|
+
const details: string[] = [];
|
|
192
|
+
|
|
193
|
+
const required = [
|
|
194
|
+
{ file: "README.md", name: "README" },
|
|
195
|
+
{ file: "LICENSE", name: "LICENSE (or LICENSE.md)" },
|
|
196
|
+
{ file: "CHANGELOG.md", name: "CHANGELOG" },
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
const found: string[] = [];
|
|
200
|
+
const missing: string[] = [];
|
|
201
|
+
|
|
202
|
+
for (const { file, name } of required) {
|
|
203
|
+
const exists = fs.existsSync(path.join(root, file)) ||
|
|
204
|
+
(file === "LICENSE" && fs.existsSync(path.join(root, "LICENSE.md")));
|
|
205
|
+
if (exists) {
|
|
206
|
+
found.push(name);
|
|
207
|
+
} else {
|
|
208
|
+
missing.push(name);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Check package.json for metadata (Node projects)
|
|
213
|
+
const packageJsonPath = path.join(root, "package.json");
|
|
214
|
+
let hasMetadata = false;
|
|
215
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
216
|
+
try {
|
|
217
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
218
|
+
hasMetadata = !!(pkg.description && pkg.author);
|
|
219
|
+
if (hasMetadata) details.push("package.json has description and author");
|
|
220
|
+
else issues.push("package.json missing description or author");
|
|
221
|
+
} catch {}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
details.push(`Found: ${found.join(", ") || "none"}`);
|
|
225
|
+
if (missing.length > 0) details.push(`Missing: ${missing.join(", ")}`);
|
|
226
|
+
|
|
227
|
+
// Build issues
|
|
228
|
+
if (missing.includes("README")) issues.push("No README.md found");
|
|
229
|
+
if (missing.includes("LICENSE (or LICENSE.md)")) issues.push("No LICENSE file found");
|
|
230
|
+
if (missing.includes("CHANGELOG")) issues.push("No CHANGELOG.md found (recommended)");
|
|
231
|
+
|
|
232
|
+
// Calculate score
|
|
233
|
+
let score = 100;
|
|
234
|
+
if (missing.includes("README")) score -= 40;
|
|
235
|
+
if (missing.includes("LICENSE (or LICENSE.md)")) score -= 30;
|
|
236
|
+
if (missing.includes("CHANGELOG")) score -= 10;
|
|
237
|
+
if (!hasMetadata) score -= 10;
|
|
238
|
+
|
|
239
|
+
return { score: Math.max(0, score), weight: WEIGHTS.docs, issues, details };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* CONFIG: Check configuration files
|
|
244
|
+
*/
|
|
245
|
+
function validateConfig(root: string): CategoryResult {
|
|
246
|
+
const issues: string[] = [];
|
|
247
|
+
const details: string[] = [];
|
|
248
|
+
|
|
249
|
+
// Essential config files
|
|
250
|
+
const checks = [
|
|
251
|
+
{ file: ".gitignore", critical: true },
|
|
252
|
+
{ file: "tsconfig.json", critical: false },
|
|
253
|
+
{ file: "package.json", critical: true },
|
|
254
|
+
{ file: ".pi-lens", critical: false, dir: true },
|
|
255
|
+
];
|
|
256
|
+
|
|
257
|
+
const found: string[] = [];
|
|
258
|
+
|
|
259
|
+
for (const { file, critical, dir } of checks) {
|
|
260
|
+
const filePath = path.join(root, file);
|
|
261
|
+
const exists = dir ? fs.existsSync(filePath) : fs.existsSync(filePath);
|
|
262
|
+
if (exists) {
|
|
263
|
+
found.push(file);
|
|
264
|
+
} else if (critical) {
|
|
265
|
+
issues.push(`Missing ${file}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Check for node_modules in git (common mistake)
|
|
270
|
+
const gitignorePath = path.join(root, ".gitignore");
|
|
271
|
+
if (fs.existsSync(gitignorePath)) {
|
|
272
|
+
try {
|
|
273
|
+
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
274
|
+
if (content.includes("node_modules")) {
|
|
275
|
+
details.push(".gitignore excludes node_modules");
|
|
276
|
+
} else {
|
|
277
|
+
issues.push(".gitignore does not exclude node_modules");
|
|
278
|
+
}
|
|
279
|
+
} catch {}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
details.push(`Config files: ${found.join(", ")}`);
|
|
283
|
+
|
|
284
|
+
// Calculate score
|
|
285
|
+
let score = 100;
|
|
286
|
+
if (!found.includes(".gitignore")) score -= 30;
|
|
287
|
+
if (!found.includes("package.json") && !fs.existsSync(path.join(root, "Cargo.toml")) &&
|
|
288
|
+
!fs.existsSync(path.join(root, "pyproject.toml"))) {
|
|
289
|
+
score -= 20; // No package manifest at all
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return { score: Math.max(0, score), weight: WEIGHTS.config, issues, details };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* DEPLOY: Check deploy readiness
|
|
297
|
+
*/
|
|
298
|
+
function validateDeploy(root: string): CategoryResult {
|
|
299
|
+
const issues: string[] = [];
|
|
300
|
+
const details: string[] = [];
|
|
301
|
+
|
|
302
|
+
// Check git status
|
|
303
|
+
let hasUncommitted = false;
|
|
304
|
+
let hasCleanGit = false;
|
|
305
|
+
|
|
306
|
+
if (fs.existsSync(path.join(root, ".git"))) {
|
|
307
|
+
hasCleanGit = true;
|
|
308
|
+
details.push("Git repository initialized");
|
|
309
|
+
|
|
310
|
+
// Check for uncommitted changes (best effort)
|
|
311
|
+
try {
|
|
312
|
+
// This requires git to be available - skip if not
|
|
313
|
+
} catch {}
|
|
314
|
+
} else {
|
|
315
|
+
issues.push("No git repository found");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Check version is set (Node projects)
|
|
319
|
+
const packageJsonPath = path.join(root, "package.json");
|
|
320
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
321
|
+
try {
|
|
322
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
323
|
+
if (pkg.version && pkg.version !== "0.0.0" && pkg.version !== "1.0.0") {
|
|
324
|
+
details.push(`Version: ${pkg.version}`);
|
|
325
|
+
} else {
|
|
326
|
+
issues.push("Version not set or is default (0.0.0)");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Check for build script
|
|
330
|
+
const hasBuildScript = pkg.scripts &&
|
|
331
|
+
(pkg.scripts.build || pkg.scripts.compile || pkg.scripts["build:prod"]);
|
|
332
|
+
if (hasBuildScript) {
|
|
333
|
+
details.push("Build script defined");
|
|
334
|
+
} else {
|
|
335
|
+
issues.push("No build script found in package.json");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Check for entry point
|
|
339
|
+
if (pkg.main || pkg.module || pkg.exports) {
|
|
340
|
+
details.push(`Entry: ${pkg.main || pkg.module || "defined via exports"}`);
|
|
341
|
+
} else {
|
|
342
|
+
issues.push("No entry point (main/module) defined");
|
|
343
|
+
}
|
|
344
|
+
} catch {}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Check for Dockerfile or deploy config
|
|
348
|
+
const hasDocker = fs.existsSync(path.join(root, "Dockerfile")) ||
|
|
349
|
+
fs.existsSync(path.join(root, "docker-compose.yml"));
|
|
350
|
+
const hasCI = fs.existsSync(path.join(root, ".github", "workflows")) ||
|
|
351
|
+
fs.existsSync(path.join(root, ".gitlab-ci.yml")) ||
|
|
352
|
+
fs.existsSync(path.join(root, "azure-pipelines.yml"));
|
|
353
|
+
|
|
354
|
+
if (hasDocker) details.push("Dockerfile present");
|
|
355
|
+
if (hasCI) details.push("CI/CD config present");
|
|
356
|
+
|
|
357
|
+
// Calculate score
|
|
358
|
+
let score = 100;
|
|
359
|
+
if (!hasCleanGit) score -= 30;
|
|
360
|
+
if (hasUncommitted) score -= 20;
|
|
361
|
+
if (issues.some(i => i.includes("version"))) score -= 15;
|
|
362
|
+
if (issues.some(i => i.includes("build script"))) score -= 10;
|
|
363
|
+
if (issues.some(i => i.includes("entry point"))) score -= 10;
|
|
364
|
+
|
|
365
|
+
return { score: Math.max(0, score), weight: WEIGHTS.deploy, issues, details };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// --- Utilities ---
|
|
369
|
+
|
|
370
|
+
function scoreToGrade(score: number): "A" | "B" | "C" | "D" | "F" {
|
|
371
|
+
if (score >= 90) return "A";
|
|
372
|
+
if (score >= 80) return "B";
|
|
373
|
+
if (score >= 70) return "C";
|
|
374
|
+
if (score >= 60) return "D";
|
|
375
|
+
return "F";
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function findSourceFiles(root: string): string[] {
|
|
379
|
+
const files: string[] = [];
|
|
380
|
+
const exts = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".go", ".rs", ".java"];
|
|
381
|
+
|
|
382
|
+
const scan = (dir: string) => {
|
|
383
|
+
try {
|
|
384
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
385
|
+
for (const entry of entries) {
|
|
386
|
+
const full = path.join(dir, entry.name);
|
|
387
|
+
if (entry.isDirectory()) {
|
|
388
|
+
if (EXCLUDED_DIRS.includes(entry.name)) continue;
|
|
389
|
+
scan(full);
|
|
390
|
+
} else if (exts.some(ext => entry.name.endsWith(ext)) && !entry.name.includes(".test.") && !entry.name.includes(".spec.")) {
|
|
391
|
+
files.push(full);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
} catch {}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
scan(root);
|
|
398
|
+
return files;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function findTestFiles(root: string): string[] {
|
|
402
|
+
const files: string[] = [];
|
|
403
|
+
const testPatterns = [".test.", ".spec.", "_test.", "_spec.", "Test.", "Spec."];
|
|
404
|
+
const testDirs = ["__tests__", "tests", "test", "specs", "spec"];
|
|
405
|
+
|
|
406
|
+
const scan = (dir: string) => {
|
|
407
|
+
try {
|
|
408
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
409
|
+
for (const entry of entries) {
|
|
410
|
+
const full = path.join(dir, entry.name);
|
|
411
|
+
if (entry.isDirectory()) {
|
|
412
|
+
if (EXCLUDED_DIRS.includes(entry.name)) continue;
|
|
413
|
+
// Check if it's a test directory
|
|
414
|
+
if (testDirs.includes(entry.name) || entry.name.endsWith("-tests")) {
|
|
415
|
+
// Collect all files in test directories
|
|
416
|
+
const testFiles = findAllFiles(full);
|
|
417
|
+
files.push(...testFiles);
|
|
418
|
+
} else {
|
|
419
|
+
scan(full);
|
|
420
|
+
}
|
|
421
|
+
} else if (testPatterns.some(p => entry.name.includes(p))) {
|
|
422
|
+
files.push(full);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
} catch {}
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
scan(root);
|
|
429
|
+
return [...new Set(files)]; // Deduplicate
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function findAllFiles(dir: string): string[] {
|
|
433
|
+
const files: string[] = [];
|
|
434
|
+
try {
|
|
435
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
436
|
+
for (const entry of entries) {
|
|
437
|
+
const full = path.join(dir, entry.name);
|
|
438
|
+
if (entry.isDirectory()) {
|
|
439
|
+
files.push(...findAllFiles(full));
|
|
440
|
+
} else {
|
|
441
|
+
files.push(full);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
} catch {}
|
|
445
|
+
return files;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function detectTestFramework(root: string): string | null {
|
|
449
|
+
const pkgPath = path.join(root, "package.json");
|
|
450
|
+
if (fs.existsSync(pkgPath)) {
|
|
451
|
+
try {
|
|
452
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
453
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
454
|
+
|
|
455
|
+
if (deps["vitest"]) return "vitest";
|
|
456
|
+
if (deps["jest"]) return "jest";
|
|
457
|
+
if (deps["mocha"]) return "mocha";
|
|
458
|
+
if (deps["ava"]) return "ava";
|
|
459
|
+
if (deps["tap"]) return "tap";
|
|
460
|
+
if (deps["@playwright/test"]) return "playwright";
|
|
461
|
+
if (deps["cypress"]) return "cypress";
|
|
462
|
+
if (deps["pytest"] || fs.existsSync(path.join(root, "pytest.ini"))) return "pytest";
|
|
463
|
+
} catch {}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Check for pytest
|
|
467
|
+
if (fs.existsSync(path.join(root, "pytest.ini")) ||
|
|
468
|
+
fs.existsSync(path.join(root, "pyproject.toml"))) {
|
|
469
|
+
return "pytest";
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Check for cargo test
|
|
473
|
+
if (fs.existsSync(path.join(root, "Cargo.toml"))) {
|
|
474
|
+
return "cargo test";
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Check for go test
|
|
478
|
+
if (fs.existsSync(path.join(root, "go.mod"))) {
|
|
479
|
+
return "go test";
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// --- Formatting ---
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Format production readiness result for display
|
|
489
|
+
*/
|
|
490
|
+
export function formatReadinessResult(result: ProductionReadinessResult): string {
|
|
491
|
+
const lines: string[] = [];
|
|
492
|
+
|
|
493
|
+
// Header with score and grade
|
|
494
|
+
const gradeColor = result.grade === "A" ? "๐ข" :
|
|
495
|
+
result.grade === "B" ? "๐ข" :
|
|
496
|
+
result.grade === "C" ? "๐ก" :
|
|
497
|
+
result.grade === "D" ? "๐ " : "๐ด";
|
|
498
|
+
|
|
499
|
+
lines.push(`${gradeColor} Production Readiness: ${result.overallScore}/100 (Grade ${result.grade})`);
|
|
500
|
+
lines.push("");
|
|
501
|
+
|
|
502
|
+
// Categories
|
|
503
|
+
const categories = [
|
|
504
|
+
{ key: "code", name: "Code Quality", emoji: "๐" },
|
|
505
|
+
{ key: "tests", name: "Tests", emoji: "๐งช" },
|
|
506
|
+
{ key: "docs", name: "Documentation", emoji: "๐" },
|
|
507
|
+
{ key: "config", name: "Configuration", emoji: "โ๏ธ" },
|
|
508
|
+
{ key: "deploy", name: "Deploy Readiness", emoji: "๐" },
|
|
509
|
+
] as const;
|
|
510
|
+
|
|
511
|
+
for (const { key, name, emoji } of categories) {
|
|
512
|
+
const cat = result.categories[key];
|
|
513
|
+
const status = cat.score >= 80 ? "โ
" : cat.score >= 60 ? "โ ๏ธ" : "โ";
|
|
514
|
+
lines.push(`${emoji} ${name}: ${cat.score}/100 ${status}`);
|
|
515
|
+
|
|
516
|
+
for (const detail of cat.details) {
|
|
517
|
+
lines.push(` ${detail}`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
for (const issue of cat.issues) {
|
|
521
|
+
lines.push(` โ ${issue}`);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (cat.details.length === 0 && cat.issues.length === 0) {
|
|
525
|
+
lines.push(` โ
No issues`);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
lines.push("");
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return lines.join("\n");
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Get critical issues that must be fixed before production
|
|
536
|
+
*/
|
|
537
|
+
export function getCriticalIssues(result: ProductionReadinessResult): string[] {
|
|
538
|
+
const critical: string[] = [];
|
|
539
|
+
|
|
540
|
+
for (const [key, cat] of Object.entries(result.categories)) {
|
|
541
|
+
for (const issue of cat.issues) {
|
|
542
|
+
// Score critical based on category and issue severity
|
|
543
|
+
if (key === "code" && issue.includes("debugger")) {
|
|
544
|
+
critical.push(`[CRITICAL] ${issue}`);
|
|
545
|
+
} else if (key === "tests" && !result.categories.tests.score) {
|
|
546
|
+
critical.push(`[CRITICAL] No tests found`);
|
|
547
|
+
} else if (key === "docs" && issue.includes("README")) {
|
|
548
|
+
critical.push(`[IMPORTANT] ${issue}`);
|
|
549
|
+
} else if (cat.score < 50) {
|
|
550
|
+
critical.push(`[${key.toUpperCase()}] ${issue}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return [...new Set(critical)]; // Deduplicate
|
|
556
|
+
}
|