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.
Files changed (304) hide show
  1. package/CHANGELOG.md +198 -0
  2. package/README.md +709 -519
  3. package/clients/__tests__/file-time.test.js +216 -0
  4. package/clients/__tests__/file-time.test.ts +276 -0
  5. package/clients/__tests__/format-service.test.js +245 -0
  6. package/clients/__tests__/format-service.test.ts +339 -0
  7. package/clients/__tests__/formatters.test.js +271 -0
  8. package/clients/__tests__/formatters.test.ts +401 -0
  9. package/clients/amain-types.js +164 -0
  10. package/clients/amain-types.ts +165 -0
  11. package/clients/architect-client.js +56 -12
  12. package/clients/architect-client.ts +81 -16
  13. package/clients/ast-grep-client.js +2 -2
  14. package/clients/ast-grep-client.ts +14 -39
  15. package/clients/ast-grep-parser.ts +1 -1
  16. package/clients/ast-grep-rule-manager.js +8 -0
  17. package/clients/ast-grep-rule-manager.ts +10 -1
  18. package/clients/ast-grep-types.js +9 -0
  19. package/clients/ast-grep-types.ts +106 -0
  20. package/clients/auto-loop.js +10 -0
  21. package/clients/auto-loop.ts +14 -1
  22. package/clients/biome-client.js +81 -19
  23. package/clients/biome-client.ts +103 -22
  24. package/clients/bus/bus.js +191 -0
  25. package/clients/bus/bus.ts +251 -0
  26. package/clients/bus/events.js +214 -0
  27. package/clients/bus/events.ts +279 -0
  28. package/clients/bus/index.js +8 -0
  29. package/clients/bus/index.ts +9 -0
  30. package/clients/bus/integration.js +158 -0
  31. package/clients/bus/integration.ts +214 -0
  32. package/clients/complexity-client.js +13 -7
  33. package/clients/complexity-client.ts +13 -7
  34. package/clients/config-validator.js +465 -0
  35. package/clients/config-validator.ts +558 -0
  36. package/clients/dependency-checker.js +4 -10
  37. package/clients/dependency-checker.ts +4 -10
  38. package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
  39. package/clients/dispatch/__tests__/autofix-integration.test.ts +300 -0
  40. package/clients/dispatch/__tests__/runner-registration.test.js +236 -0
  41. package/clients/dispatch/__tests__/runner-registration.test.ts +282 -0
  42. package/clients/dispatch/bus-dispatcher.js +177 -0
  43. package/clients/dispatch/bus-dispatcher.ts +251 -0
  44. package/clients/dispatch/dispatcher.edge.test.js +82 -0
  45. package/clients/dispatch/dispatcher.edge.test.ts +100 -0
  46. package/clients/dispatch/dispatcher.format.test.js +46 -0
  47. package/clients/dispatch/dispatcher.format.test.ts +58 -0
  48. package/clients/dispatch/dispatcher.inline.test.js +74 -0
  49. package/clients/dispatch/dispatcher.inline.test.ts +93 -0
  50. package/clients/dispatch/dispatcher.js +19 -53
  51. package/clients/dispatch/dispatcher.ts +20 -67
  52. package/clients/dispatch/plan.js +9 -4
  53. package/clients/dispatch/plan.ts +9 -4
  54. package/clients/dispatch/runners/architect.js +21 -7
  55. package/clients/dispatch/runners/architect.test.js +138 -0
  56. package/clients/dispatch/runners/architect.test.ts +162 -0
  57. package/clients/dispatch/runners/architect.ts +22 -7
  58. package/clients/dispatch/runners/ast-grep-napi.js +462 -0
  59. package/clients/dispatch/runners/ast-grep-napi.test.js +111 -0
  60. package/clients/dispatch/runners/ast-grep-napi.test.ts +133 -0
  61. package/clients/dispatch/runners/ast-grep-napi.ts +506 -0
  62. package/clients/dispatch/runners/ast-grep.js +62 -19
  63. package/clients/dispatch/runners/ast-grep.ts +70 -18
  64. package/clients/dispatch/runners/biome.js +29 -53
  65. package/clients/dispatch/runners/biome.ts +29 -63
  66. package/clients/dispatch/runners/config-validation.js +67 -0
  67. package/clients/dispatch/runners/config-validation.ts +82 -0
  68. package/clients/dispatch/runners/go-vet.js +4 -28
  69. package/clients/dispatch/runners/go-vet.ts +4 -32
  70. package/clients/dispatch/runners/index.js +30 -10
  71. package/clients/dispatch/runners/index.ts +30 -10
  72. package/clients/dispatch/runners/oxlint.js +141 -0
  73. package/clients/dispatch/runners/oxlint.test.js +230 -0
  74. package/clients/dispatch/runners/oxlint.test.ts +303 -0
  75. package/clients/dispatch/runners/oxlint.ts +175 -0
  76. package/clients/dispatch/runners/pyright.js +40 -70
  77. package/clients/dispatch/runners/pyright.test.js +16 -2
  78. package/clients/dispatch/runners/pyright.test.ts +14 -2
  79. package/clients/dispatch/runners/pyright.ts +48 -91
  80. package/clients/dispatch/runners/python-slop.js +97 -0
  81. package/clients/dispatch/runners/python-slop.test.js +203 -0
  82. package/clients/dispatch/runners/python-slop.test.ts +298 -0
  83. package/clients/dispatch/runners/python-slop.ts +124 -0
  84. package/clients/dispatch/runners/ruff.js +18 -71
  85. package/clients/dispatch/runners/ruff.ts +19 -79
  86. package/clients/dispatch/runners/rust-clippy.js +28 -32
  87. package/clients/dispatch/runners/rust-clippy.ts +29 -31
  88. package/clients/dispatch/runners/scan_codebase.test.js +89 -0
  89. package/clients/dispatch/runners/scan_codebase.test.ts +105 -0
  90. package/clients/dispatch/runners/shellcheck.js +147 -0
  91. package/clients/dispatch/runners/shellcheck.test.js +98 -0
  92. package/clients/dispatch/runners/shellcheck.test.ts +129 -0
  93. package/clients/dispatch/runners/shellcheck.ts +188 -0
  94. package/clients/dispatch/runners/similarity.js +230 -0
  95. package/clients/dispatch/runners/similarity.ts +339 -0
  96. package/clients/dispatch/runners/spellcheck.js +106 -0
  97. package/clients/dispatch/runners/spellcheck.test.js +158 -0
  98. package/clients/dispatch/runners/spellcheck.test.ts +214 -0
  99. package/clients/dispatch/runners/spellcheck.ts +136 -0
  100. package/clients/dispatch/runners/tree-sitter.js +107 -0
  101. package/clients/dispatch/runners/tree-sitter.ts +135 -0
  102. package/clients/dispatch/runners/ts-lsp.js +104 -33
  103. package/clients/dispatch/runners/ts-lsp.ts +120 -38
  104. package/clients/dispatch/runners/ts-slop.js +113 -0
  105. package/clients/dispatch/runners/ts-slop.test.js +180 -0
  106. package/clients/dispatch/runners/ts-slop.test.ts +230 -0
  107. package/clients/dispatch/runners/ts-slop.ts +142 -0
  108. package/clients/dispatch/runners/utils/diagnostic-parsers.js +134 -0
  109. package/clients/dispatch/runners/utils/diagnostic-parsers.ts +186 -0
  110. package/clients/dispatch/runners/utils/runner-helpers.js +115 -0
  111. package/clients/dispatch/runners/utils/runner-helpers.ts +167 -0
  112. package/clients/dispatch/runners/utils.js +2 -4
  113. package/clients/dispatch/runners/utils.ts +2 -4
  114. package/clients/dispatch/types.ts +1 -1
  115. package/clients/dispatch/utils/format-utils.js +49 -0
  116. package/clients/dispatch/utils/format-utils.ts +60 -0
  117. package/clients/dogfood.test.js +201 -0
  118. package/clients/dogfood.test.ts +269 -0
  119. package/clients/file-time.js +152 -0
  120. package/clients/file-time.ts +208 -0
  121. package/clients/file-utils.js +40 -0
  122. package/clients/file-utils.ts +44 -0
  123. package/clients/fix-scanners.js +10 -20
  124. package/clients/fix-scanners.ts +10 -22
  125. package/clients/format-service.js +172 -0
  126. package/clients/format-service.ts +254 -0
  127. package/clients/formatters.js +435 -0
  128. package/clients/formatters.ts +508 -0
  129. package/clients/go-client.js +5 -14
  130. package/clients/go-client.ts +5 -13
  131. package/clients/installer/index.js +356 -0
  132. package/clients/installer/index.ts +426 -0
  133. package/clients/jscpd-client.js +11 -9
  134. package/clients/jscpd-client.ts +12 -8
  135. package/clients/knip-client.js +3 -7
  136. package/clients/knip-client.ts +3 -6
  137. package/clients/lsp/__tests__/client.test.js +325 -0
  138. package/clients/lsp/__tests__/client.test.ts +434 -0
  139. package/clients/lsp/__tests__/config.test.js +166 -0
  140. package/clients/lsp/__tests__/config.test.ts +209 -0
  141. package/clients/lsp/__tests__/error-recovery.test.js +213 -0
  142. package/clients/lsp/__tests__/error-recovery.test.ts +279 -0
  143. package/clients/lsp/__tests__/integration.test.js +127 -0
  144. package/clients/lsp/__tests__/integration.test.ts +160 -0
  145. package/clients/lsp/__tests__/launch.test.js +260 -0
  146. package/clients/lsp/__tests__/launch.test.ts +329 -0
  147. package/clients/lsp/__tests__/server.test.js +259 -0
  148. package/clients/lsp/__tests__/server.test.ts +332 -0
  149. package/clients/lsp/__tests__/service.test.js +417 -0
  150. package/clients/lsp/__tests__/service.test.ts +499 -0
  151. package/clients/lsp/client.js +235 -0
  152. package/clients/lsp/client.ts +328 -0
  153. package/clients/lsp/config.js +115 -0
  154. package/clients/lsp/config.ts +149 -0
  155. package/clients/lsp/index.js +222 -0
  156. package/clients/lsp/index.ts +280 -0
  157. package/clients/lsp/installer/index.js +391 -0
  158. package/clients/lsp/interactive-install.js +210 -0
  159. package/clients/lsp/interactive-install.ts +251 -0
  160. package/clients/lsp/language.js +170 -0
  161. package/clients/lsp/language.ts +216 -0
  162. package/clients/lsp/launch.js +174 -0
  163. package/clients/lsp/launch.ts +240 -0
  164. package/clients/lsp/lsp/launch.js +116 -0
  165. package/clients/lsp/lsp/server.js +532 -0
  166. package/clients/lsp/lsp-index.js +10 -0
  167. package/clients/lsp/lsp-index.ts +11 -0
  168. package/clients/lsp/path-utils.js +48 -0
  169. package/clients/lsp/path-utils.ts +52 -0
  170. package/clients/lsp/server.js +615 -0
  171. package/clients/lsp/server.ts +800 -0
  172. package/clients/lsp/test-py-spawn/requirements.txt +1 -0
  173. package/clients/lsp/test-py-spawn/test.py +3 -0
  174. package/clients/lsp/test-py-svc/requirements.txt +1 -0
  175. package/clients/lsp/test-py-svc/test.py +3 -0
  176. package/clients/lsp/test-python-project/requirements.txt +1 -0
  177. package/clients/lsp/test-python-project/test.py +5 -0
  178. package/clients/metrics-history.js +2 -2
  179. package/clients/metrics-history.ts +2 -2
  180. package/clients/production-readiness.js +522 -0
  181. package/clients/production-readiness.ts +556 -0
  182. package/clients/project-index.js +255 -0
  183. package/clients/project-index.ts +383 -0
  184. package/clients/project-metadata.js +531 -0
  185. package/clients/project-metadata.ts +624 -0
  186. package/clients/ruff-client.js +56 -16
  187. package/clients/ruff-client.ts +72 -15
  188. package/clients/runner-tracker.js +152 -0
  189. package/clients/runner-tracker.ts +213 -0
  190. package/clients/rust-client.js +4 -11
  191. package/clients/rust-client.ts +5 -11
  192. package/clients/safe-spawn.js +96 -0
  193. package/clients/safe-spawn.ts +128 -0
  194. package/clients/scan-architectural-debt.js +3 -6
  195. package/clients/scan-architectural-debt.ts +3 -6
  196. package/clients/scan-utils.js +5 -20
  197. package/clients/scan-utils.ts +5 -29
  198. package/clients/secrets-scanner.js +3 -17
  199. package/clients/secrets-scanner.ts +4 -20
  200. package/clients/services/__tests__/effect-integration.test.js +86 -0
  201. package/clients/services/__tests__/effect-integration.test.ts +111 -0
  202. package/clients/services/effect-integration.js +194 -0
  203. package/clients/services/effect-integration.ts +268 -0
  204. package/clients/services/index.js +7 -0
  205. package/clients/services/index.ts +8 -0
  206. package/clients/services/runner-service.js +105 -0
  207. package/clients/services/runner-service.ts +179 -0
  208. package/clients/sg-runner.js +87 -13
  209. package/clients/sg-runner.ts +97 -13
  210. package/clients/state-matrix.js +160 -0
  211. package/clients/state-matrix.ts +202 -0
  212. package/clients/subprocess-client.js +10 -9
  213. package/clients/subprocess-client.ts +10 -8
  214. package/clients/test-runner-client.js +3 -7
  215. package/clients/test-runner-client.ts +3 -6
  216. package/clients/tool-availability.js +4 -10
  217. package/clients/tool-availability.ts +4 -9
  218. package/clients/tree-sitter-client.js +564 -0
  219. package/clients/tree-sitter-client.ts +797 -0
  220. package/clients/tree-sitter-query-loader.js +355 -0
  221. package/clients/tree-sitter-query-loader.ts +425 -0
  222. package/clients/type-coverage-client.js +3 -7
  223. package/clients/type-coverage-client.ts +3 -6
  224. package/clients/typescript-client.codefix.test.js +157 -0
  225. package/clients/typescript-client.codefix.test.ts +186 -0
  226. package/clients/typescript-client.js +43 -0
  227. package/clients/typescript-client.ts +98 -0
  228. package/commands/booboo.js +799 -219
  229. package/commands/booboo.ts +1004 -225
  230. package/commands/clients/ast-grep-client.js +250 -0
  231. package/commands/clients/ast-grep-parser.js +86 -0
  232. package/commands/clients/ast-grep-rule-manager.js +91 -0
  233. package/commands/clients/ast-grep-types.js +9 -0
  234. package/commands/clients/biome-client.js +380 -0
  235. package/commands/clients/complexity-client.js +667 -0
  236. package/commands/clients/file-kinds.js +177 -0
  237. package/commands/clients/file-utils.js +40 -0
  238. package/commands/clients/jscpd-client.js +169 -0
  239. package/commands/clients/knip-client.js +211 -0
  240. package/commands/clients/ruff-client.js +297 -0
  241. package/commands/clients/safe-spawn.js +88 -0
  242. package/commands/clients/scan-utils.js +83 -0
  243. package/commands/clients/sg-runner.js +190 -0
  244. package/commands/clients/types.js +11 -0
  245. package/commands/clients/typescript-client.js +505 -0
  246. package/commands/fix-from-booboo.js +398 -0
  247. package/commands/fix-from-booboo.ts +485 -0
  248. package/commands/fix-simplified.js +618 -0
  249. package/commands/fix-simplified.ts +768 -0
  250. package/commands/rate.js +10 -14
  251. package/commands/rate.ts +9 -16
  252. package/default-architect.yaml +59 -15
  253. package/index.ts +342 -429
  254. package/package.json +16 -3
  255. package/rules/ast-grep-rules/rules/empty-catch.yml +38 -13
  256. package/rules/ast-grep-rules/rules/no-array-constructor.yml +1 -0
  257. package/rules/ast-grep-rules/rules/no-debugger.yml +2 -0
  258. package/rules/python-slop-rules/.sgconfig.yml +4 -0
  259. package/rules/python-slop-rules/rules/slop-rules.yml +647 -0
  260. package/rules/tree-sitter-queries/python/bare-except.yml +54 -0
  261. package/rules/tree-sitter-queries/python/eval-exec.yml +50 -0
  262. package/rules/tree-sitter-queries/python/is-vs-equals.yml +60 -0
  263. package/rules/tree-sitter-queries/python/mutable-default-arg.yml +57 -0
  264. package/rules/tree-sitter-queries/python/unreachable-except.yml +60 -0
  265. package/rules/tree-sitter-queries/python/wildcard-import.yml +46 -0
  266. package/rules/tree-sitter-queries/tsx/dangerously-set-inner-html.yml +63 -0
  267. package/rules/tree-sitter-queries/typescript/await-in-loop.yml +56 -0
  268. package/rules/tree-sitter-queries/typescript/console-statement.yml +47 -0
  269. package/rules/tree-sitter-queries/typescript/debugger.yml +47 -0
  270. package/rules/tree-sitter-queries/typescript/deep-nesting.yml +117 -0
  271. package/rules/tree-sitter-queries/typescript/deep-promise-chain.yml +73 -0
  272. package/rules/tree-sitter-queries/typescript/empty-catch.yml +64 -0
  273. package/rules/tree-sitter-queries/typescript/eval.yml +48 -0
  274. package/rules/tree-sitter-queries/typescript/hardcoded-secrets.yml +78 -0
  275. package/rules/tree-sitter-queries/typescript/long-parameter-list.yml +62 -0
  276. package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +49 -0
  277. package/rules/tree-sitter-queries/typescript/nested-ternary.yml +45 -0
  278. package/rules/ts-slop-rules/.sgconfig.yml +4 -0
  279. package/rules/ts-slop-rules/rules/in-correct-optional-input-type.yml +10 -0
  280. package/rules/ts-slop-rules/rules/jwt-no-verify.yml +13 -0
  281. package/rules/ts-slop-rules/rules/no-architecture-violation.yml +10 -0
  282. package/rules/ts-slop-rules/rules/no-case-declarations.yml +10 -0
  283. package/rules/ts-slop-rules/rules/no-dangerously-set-inner-html.yml +10 -0
  284. package/rules/ts-slop-rules/rules/no-debugger.yml +10 -0
  285. package/rules/ts-slop-rules/rules/no-dupe-args.yml +10 -0
  286. package/rules/ts-slop-rules/rules/no-dupe-class-members.yml +10 -0
  287. package/rules/ts-slop-rules/rules/no-dupe-keys.yml +10 -0
  288. package/rules/ts-slop-rules/rules/no-eval.yml +13 -0
  289. package/rules/ts-slop-rules/rules/no-hardcoded-secrets.yml +12 -0
  290. package/rules/ts-slop-rules/rules/no-implied-eval.yml +12 -0
  291. package/rules/ts-slop-rules/rules/no-inner-html.yml +13 -0
  292. package/rules/ts-slop-rules/rules/no-javascript-url.yml +10 -0
  293. package/rules/ts-slop-rules/rules/no-mutable-default.yml +10 -0
  294. package/rules/ts-slop-rules/rules/no-nested-links.yml +12 -0
  295. package/rules/ts-slop-rules/rules/no-new-symbol.yml +10 -0
  296. package/rules/ts-slop-rules/rules/no-new-wrappers.yml +13 -0
  297. package/rules/ts-slop-rules/rules/no-open-redirect.yml +16 -0
  298. package/rules/ts-slop-rules/rules/slop-rules.yml +455 -0
  299. package/rules/ts-slop-rules/rules/weak-rsa-key.yml +12 -0
  300. package/skills/ast-grep/SKILL.md +182 -0
  301. package/clients/dispatch/runners/secrets.js +0 -109
  302. package/commands/fix.js +0 -244
  303. package/commands/fix.ts +0 -373
  304. 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
+ }