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 @@
1
+ # test
@@ -0,0 +1,3 @@
1
+ def test():
2
+ x = "hi" + 5
3
+ return x
@@ -0,0 +1 @@
1
+ # test
@@ -0,0 +1,3 @@
1
+ def test():
2
+ x = "hello" + 5
3
+ return x
@@ -0,0 +1,5 @@
1
+
2
+ def test():
3
+ x = 1
4
+ y = "hello" + 5 # Type error
5
+ return x
@@ -280,7 +280,7 @@ export function computeTDI(history) {
280
280
  }
281
281
  let totalMI = 0;
282
282
  let totalCognitive = 0;
283
- let totalNesting = 0;
283
+ let _totalNesting = 0;
284
284
  let filesWithDebt = 0;
285
285
  let debtFromMI = 0;
286
286
  let debtFromCognitive = 0;
@@ -289,7 +289,7 @@ export function computeTDI(history) {
289
289
  const snap = file.latest;
290
290
  totalMI += snap.mi;
291
291
  totalCognitive += snap.cognitive;
292
- totalNesting += snap.nesting;
292
+ _totalNesting += snap.nesting;
293
293
  // Accumulate debt points
294
294
  let fileDebt = 0;
295
295
  // MI debt: 0 at MI=100, max at MI=0
@@ -385,7 +385,7 @@ export function computeTDI(history: MetricsHistory): ProjectTDI {
385
385
 
386
386
  let totalMI = 0;
387
387
  let totalCognitive = 0;
388
- let totalNesting = 0;
388
+ let _totalNesting = 0;
389
389
  let filesWithDebt = 0;
390
390
  let debtFromMI = 0;
391
391
  let debtFromCognitive = 0;
@@ -395,7 +395,7 @@ export function computeTDI(history: MetricsHistory): ProjectTDI {
395
395
  const snap = file.latest;
396
396
  totalMI += snap.mi;
397
397
  totalCognitive += snap.cognitive;
398
- totalNesting += snap.nesting;
398
+ _totalNesting += snap.nesting;
399
399
 
400
400
  // Accumulate debt points
401
401
  let fileDebt = 0;
@@ -0,0 +1,522 @@
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
+ import * as fs from "node:fs";
9
+ import * as path from "node:path";
10
+ import { EXCLUDED_DIRS } from "./file-utils.js";
11
+ // --- Constants ---
12
+ const WEIGHTS = {
13
+ code: 0.30, // 30% - console.log, TODO, empty catch, as any, debugger
14
+ tests: 0.20, // 20% - test files, test cases, framework config
15
+ docs: 0.20, // 20% - README, LICENSE, CHANGELOG, pkg metadata
16
+ config: 0.15, // 15% - gitignore, tsconfig, package.json, no node_modules
17
+ deploy: 0.15, // 15% - clean git, version set, build script, entry point
18
+ };
19
+ // --- Main Entry Point ---
20
+ /**
21
+ * Run production readiness validation on a project
22
+ */
23
+ export function validateProductionReadiness(targetPath) {
24
+ const categories = {
25
+ code: validateCode(targetPath),
26
+ tests: validateTests(targetPath),
27
+ docs: validateDocs(targetPath),
28
+ config: validateConfig(targetPath),
29
+ deploy: validateDeploy(targetPath),
30
+ };
31
+ // Calculate weighted score
32
+ const overallScore = Math.round(categories.code.score * WEIGHTS.code +
33
+ categories.tests.score * WEIGHTS.tests +
34
+ categories.docs.score * WEIGHTS.docs +
35
+ categories.config.score * WEIGHTS.config +
36
+ categories.deploy.score * WEIGHTS.deploy);
37
+ return {
38
+ overallScore,
39
+ grade: scoreToGrade(overallScore),
40
+ categories,
41
+ };
42
+ }
43
+ // --- Category Validators ---
44
+ /**
45
+ * CODE: Check for production anti-patterns
46
+ */
47
+ function validateCode(root) {
48
+ const issues = [];
49
+ const details = [];
50
+ const sourceFiles = findSourceFiles(root);
51
+ if (sourceFiles.length === 0) {
52
+ issues.push("No source files found");
53
+ return { score: 0, weight: WEIGHTS.code, issues, details };
54
+ }
55
+ details.push(`${sourceFiles.length} source files`);
56
+ let totalLines = 0;
57
+ let consoleLogs = 0;
58
+ let todos = 0;
59
+ let emptyCatches = 0;
60
+ let anyCasts = 0;
61
+ let debuggers = 0;
62
+ for (const file of sourceFiles) {
63
+ try {
64
+ const content = fs.readFileSync(file, "utf-8");
65
+ const lines = content.split("\n");
66
+ totalLines += lines.length;
67
+ for (const line of lines) {
68
+ // Skip comments for some checks
69
+ const codePart = line.replace(/\/\/.*$/g, "");
70
+ if (codePart.match(/console\.(log|debug|info|warn)\(/))
71
+ consoleLogs++;
72
+ if (codePart.match(/\/\/\s*(TODO|FIXME|HACK|XXX|BUG)/i))
73
+ todos++;
74
+ if (codePart.match(/catch\s*\([^)]*\)\s*\{\s*\}/))
75
+ emptyCatches++;
76
+ if (codePart.match(/as\s+any\b/) && !line.includes("// biome-ignore"))
77
+ anyCasts++;
78
+ if (codePart.match(/\bdebugger\b/))
79
+ debuggers++;
80
+ }
81
+ }
82
+ catch { }
83
+ }
84
+ details.push(`${totalLines} total lines`);
85
+ // Build issues list
86
+ if (consoleLogs > 0)
87
+ issues.push(`${consoleLogs} console.log/debug statements`);
88
+ if (todos > 0)
89
+ issues.push(`${todos} TODO/FIXME comments`);
90
+ if (emptyCatches > 0)
91
+ issues.push(`${emptyCatches} empty catch blocks`);
92
+ if (anyCasts > 3)
93
+ issues.push(`${anyCasts} 'as any' casts (${anyCasts > 10 ? "many" : "some"})`);
94
+ if (debuggers > 0)
95
+ issues.push(`${debuggers} debugger statements!`);
96
+ // Calculate score
97
+ let score = 100;
98
+ score -= Math.min(20, consoleLogs * 2);
99
+ score -= Math.min(10, todos);
100
+ score -= Math.min(15, emptyCatches * 5);
101
+ score -= Math.min(10, Math.max(0, anyCasts - 3));
102
+ score -= debuggers * 15;
103
+ return { score: Math.max(0, score), weight: WEIGHTS.code, issues, details };
104
+ }
105
+ /**
106
+ * TESTS: Check test coverage and configuration
107
+ */
108
+ function validateTests(root) {
109
+ const issues = [];
110
+ const details = [];
111
+ // Look for test files
112
+ const testFiles = findTestFiles(root);
113
+ const hasTestFiles = testFiles.length > 0;
114
+ // Look for test framework config
115
+ const testFramework = detectTestFramework(root);
116
+ const hasTestFramework = testFramework !== null;
117
+ // Count test cases (rough estimate)
118
+ let testCases = 0;
119
+ for (const file of testFiles) {
120
+ try {
121
+ const content = fs.readFileSync(file, "utf-8");
122
+ // Match common test patterns
123
+ const matches = content.match(/\b(it|test|describe|spec|Scenario)\s*\(/g);
124
+ if (matches)
125
+ testCases += matches.length;
126
+ }
127
+ catch { }
128
+ }
129
+ details.push(`${testFiles.length} test files`);
130
+ details.push(`${testCases} test cases (approximate)`);
131
+ if (testFramework)
132
+ details.push(`Framework: ${testFramework}`);
133
+ // Build issues
134
+ if (!hasTestFiles)
135
+ issues.push("No test files found");
136
+ else if (testFiles.length < 2)
137
+ issues.push("Only 1 test file (consider adding more)");
138
+ if (!hasTestFramework)
139
+ issues.push("No test framework configuration detected");
140
+ if (testCases === 0 && hasTestFiles)
141
+ issues.push("Test files found but no test cases detected");
142
+ // Calculate score
143
+ let score = 100;
144
+ if (!hasTestFiles)
145
+ score -= 50;
146
+ else if (testFiles.length < 2)
147
+ score -= 10;
148
+ if (!hasTestFramework)
149
+ score -= 25;
150
+ if (testCases === 0 && hasTestFiles)
151
+ score -= 15;
152
+ return { score: Math.max(0, score), weight: WEIGHTS.tests, issues, details };
153
+ }
154
+ /**
155
+ * DOCS: Check documentation completeness
156
+ */
157
+ function validateDocs(root) {
158
+ const issues = [];
159
+ const details = [];
160
+ const required = [
161
+ { file: "README.md", name: "README" },
162
+ { file: "LICENSE", name: "LICENSE (or LICENSE.md)" },
163
+ { file: "CHANGELOG.md", name: "CHANGELOG" },
164
+ ];
165
+ const found = [];
166
+ const missing = [];
167
+ for (const { file, name } of required) {
168
+ const exists = fs.existsSync(path.join(root, file)) ||
169
+ (file === "LICENSE" && fs.existsSync(path.join(root, "LICENSE.md")));
170
+ if (exists) {
171
+ found.push(name);
172
+ }
173
+ else {
174
+ missing.push(name);
175
+ }
176
+ }
177
+ // Check package.json for metadata (Node projects)
178
+ const packageJsonPath = path.join(root, "package.json");
179
+ let hasMetadata = false;
180
+ if (fs.existsSync(packageJsonPath)) {
181
+ try {
182
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
183
+ hasMetadata = !!(pkg.description && pkg.author);
184
+ if (hasMetadata)
185
+ details.push("package.json has description and author");
186
+ else
187
+ issues.push("package.json missing description or author");
188
+ }
189
+ catch { }
190
+ }
191
+ details.push(`Found: ${found.join(", ") || "none"}`);
192
+ if (missing.length > 0)
193
+ details.push(`Missing: ${missing.join(", ")}`);
194
+ // Build issues
195
+ if (missing.includes("README"))
196
+ issues.push("No README.md found");
197
+ if (missing.includes("LICENSE (or LICENSE.md)"))
198
+ issues.push("No LICENSE file found");
199
+ if (missing.includes("CHANGELOG"))
200
+ issues.push("No CHANGELOG.md found (recommended)");
201
+ // Calculate score
202
+ let score = 100;
203
+ if (missing.includes("README"))
204
+ score -= 40;
205
+ if (missing.includes("LICENSE (or LICENSE.md)"))
206
+ score -= 30;
207
+ if (missing.includes("CHANGELOG"))
208
+ score -= 10;
209
+ if (!hasMetadata)
210
+ score -= 10;
211
+ return { score: Math.max(0, score), weight: WEIGHTS.docs, issues, details };
212
+ }
213
+ /**
214
+ * CONFIG: Check configuration files
215
+ */
216
+ function validateConfig(root) {
217
+ const issues = [];
218
+ const details = [];
219
+ // Essential config files
220
+ const checks = [
221
+ { file: ".gitignore", critical: true },
222
+ { file: "tsconfig.json", critical: false },
223
+ { file: "package.json", critical: true },
224
+ { file: ".pi-lens", critical: false, dir: true },
225
+ ];
226
+ const found = [];
227
+ for (const { file, critical, dir } of checks) {
228
+ const filePath = path.join(root, file);
229
+ const exists = dir ? fs.existsSync(filePath) : fs.existsSync(filePath);
230
+ if (exists) {
231
+ found.push(file);
232
+ }
233
+ else if (critical) {
234
+ issues.push(`Missing ${file}`);
235
+ }
236
+ }
237
+ // Check for node_modules in git (common mistake)
238
+ const gitignorePath = path.join(root, ".gitignore");
239
+ if (fs.existsSync(gitignorePath)) {
240
+ try {
241
+ const content = fs.readFileSync(gitignorePath, "utf-8");
242
+ if (content.includes("node_modules")) {
243
+ details.push(".gitignore excludes node_modules");
244
+ }
245
+ else {
246
+ issues.push(".gitignore does not exclude node_modules");
247
+ }
248
+ }
249
+ catch { }
250
+ }
251
+ details.push(`Config files: ${found.join(", ")}`);
252
+ // Calculate score
253
+ let score = 100;
254
+ if (!found.includes(".gitignore"))
255
+ score -= 30;
256
+ if (!found.includes("package.json") && !fs.existsSync(path.join(root, "Cargo.toml")) &&
257
+ !fs.existsSync(path.join(root, "pyproject.toml"))) {
258
+ score -= 20; // No package manifest at all
259
+ }
260
+ return { score: Math.max(0, score), weight: WEIGHTS.config, issues, details };
261
+ }
262
+ /**
263
+ * DEPLOY: Check deploy readiness
264
+ */
265
+ function validateDeploy(root) {
266
+ const issues = [];
267
+ const details = [];
268
+ // Check git status
269
+ let hasUncommitted = false;
270
+ let hasCleanGit = false;
271
+ if (fs.existsSync(path.join(root, ".git"))) {
272
+ hasCleanGit = true;
273
+ details.push("Git repository initialized");
274
+ // Check for uncommitted changes (best effort)
275
+ try {
276
+ // This requires git to be available - skip if not
277
+ }
278
+ catch { }
279
+ }
280
+ else {
281
+ issues.push("No git repository found");
282
+ }
283
+ // Check version is set (Node projects)
284
+ const packageJsonPath = path.join(root, "package.json");
285
+ if (fs.existsSync(packageJsonPath)) {
286
+ try {
287
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
288
+ if (pkg.version && pkg.version !== "0.0.0" && pkg.version !== "1.0.0") {
289
+ details.push(`Version: ${pkg.version}`);
290
+ }
291
+ else {
292
+ issues.push("Version not set or is default (0.0.0)");
293
+ }
294
+ // Check for build script
295
+ const hasBuildScript = pkg.scripts &&
296
+ (pkg.scripts.build || pkg.scripts.compile || pkg.scripts["build:prod"]);
297
+ if (hasBuildScript) {
298
+ details.push("Build script defined");
299
+ }
300
+ else {
301
+ issues.push("No build script found in package.json");
302
+ }
303
+ // Check for entry point
304
+ if (pkg.main || pkg.module || pkg.exports) {
305
+ details.push(`Entry: ${pkg.main || pkg.module || "defined via exports"}`);
306
+ }
307
+ else {
308
+ issues.push("No entry point (main/module) defined");
309
+ }
310
+ }
311
+ catch { }
312
+ }
313
+ // Check for Dockerfile or deploy config
314
+ const hasDocker = fs.existsSync(path.join(root, "Dockerfile")) ||
315
+ fs.existsSync(path.join(root, "docker-compose.yml"));
316
+ const hasCI = fs.existsSync(path.join(root, ".github", "workflows")) ||
317
+ fs.existsSync(path.join(root, ".gitlab-ci.yml")) ||
318
+ fs.existsSync(path.join(root, "azure-pipelines.yml"));
319
+ if (hasDocker)
320
+ details.push("Dockerfile present");
321
+ if (hasCI)
322
+ details.push("CI/CD config present");
323
+ // Calculate score
324
+ let score = 100;
325
+ if (!hasCleanGit)
326
+ score -= 30;
327
+ if (hasUncommitted)
328
+ score -= 20;
329
+ if (issues.some(i => i.includes("version")))
330
+ score -= 15;
331
+ if (issues.some(i => i.includes("build script")))
332
+ score -= 10;
333
+ if (issues.some(i => i.includes("entry point")))
334
+ score -= 10;
335
+ return { score: Math.max(0, score), weight: WEIGHTS.deploy, issues, details };
336
+ }
337
+ // --- Utilities ---
338
+ function scoreToGrade(score) {
339
+ if (score >= 90)
340
+ return "A";
341
+ if (score >= 80)
342
+ return "B";
343
+ if (score >= 70)
344
+ return "C";
345
+ if (score >= 60)
346
+ return "D";
347
+ return "F";
348
+ }
349
+ function findSourceFiles(root) {
350
+ const files = [];
351
+ const exts = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".go", ".rs", ".java"];
352
+ const scan = (dir) => {
353
+ try {
354
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
355
+ for (const entry of entries) {
356
+ const full = path.join(dir, entry.name);
357
+ if (entry.isDirectory()) {
358
+ if (EXCLUDED_DIRS.includes(entry.name))
359
+ continue;
360
+ scan(full);
361
+ }
362
+ else if (exts.some(ext => entry.name.endsWith(ext)) && !entry.name.includes(".test.") && !entry.name.includes(".spec.")) {
363
+ files.push(full);
364
+ }
365
+ }
366
+ }
367
+ catch { }
368
+ };
369
+ scan(root);
370
+ return files;
371
+ }
372
+ function findTestFiles(root) {
373
+ const files = [];
374
+ const testPatterns = [".test.", ".spec.", "_test.", "_spec.", "Test.", "Spec."];
375
+ const testDirs = ["__tests__", "tests", "test", "specs", "spec"];
376
+ const scan = (dir) => {
377
+ try {
378
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
379
+ for (const entry of entries) {
380
+ const full = path.join(dir, entry.name);
381
+ if (entry.isDirectory()) {
382
+ if (EXCLUDED_DIRS.includes(entry.name))
383
+ continue;
384
+ // Check if it's a test directory
385
+ if (testDirs.includes(entry.name) || entry.name.endsWith("-tests")) {
386
+ // Collect all files in test directories
387
+ const testFiles = findAllFiles(full);
388
+ files.push(...testFiles);
389
+ }
390
+ else {
391
+ scan(full);
392
+ }
393
+ }
394
+ else if (testPatterns.some(p => entry.name.includes(p))) {
395
+ files.push(full);
396
+ }
397
+ }
398
+ }
399
+ catch { }
400
+ };
401
+ scan(root);
402
+ return [...new Set(files)]; // Deduplicate
403
+ }
404
+ function findAllFiles(dir) {
405
+ const files = [];
406
+ try {
407
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
408
+ for (const entry of entries) {
409
+ const full = path.join(dir, entry.name);
410
+ if (entry.isDirectory()) {
411
+ files.push(...findAllFiles(full));
412
+ }
413
+ else {
414
+ files.push(full);
415
+ }
416
+ }
417
+ }
418
+ catch { }
419
+ return files;
420
+ }
421
+ function detectTestFramework(root) {
422
+ const pkgPath = path.join(root, "package.json");
423
+ if (fs.existsSync(pkgPath)) {
424
+ try {
425
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
426
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
427
+ if (deps["vitest"])
428
+ return "vitest";
429
+ if (deps["jest"])
430
+ return "jest";
431
+ if (deps["mocha"])
432
+ return "mocha";
433
+ if (deps["ava"])
434
+ return "ava";
435
+ if (deps["tap"])
436
+ return "tap";
437
+ if (deps["@playwright/test"])
438
+ return "playwright";
439
+ if (deps["cypress"])
440
+ return "cypress";
441
+ if (deps["pytest"] || fs.existsSync(path.join(root, "pytest.ini")))
442
+ return "pytest";
443
+ }
444
+ catch { }
445
+ }
446
+ // Check for pytest
447
+ if (fs.existsSync(path.join(root, "pytest.ini")) ||
448
+ fs.existsSync(path.join(root, "pyproject.toml"))) {
449
+ return "pytest";
450
+ }
451
+ // Check for cargo test
452
+ if (fs.existsSync(path.join(root, "Cargo.toml"))) {
453
+ return "cargo test";
454
+ }
455
+ // Check for go test
456
+ if (fs.existsSync(path.join(root, "go.mod"))) {
457
+ return "go test";
458
+ }
459
+ return null;
460
+ }
461
+ // --- Formatting ---
462
+ /**
463
+ * Format production readiness result for display
464
+ */
465
+ export function formatReadinessResult(result) {
466
+ const lines = [];
467
+ // Header with score and grade
468
+ const gradeColor = result.grade === "A" ? "๐ŸŸข" :
469
+ result.grade === "B" ? "๐ŸŸข" :
470
+ result.grade === "C" ? "๐ŸŸก" :
471
+ result.grade === "D" ? "๐ŸŸ " : "๐Ÿ”ด";
472
+ lines.push(`${gradeColor} Production Readiness: ${result.overallScore}/100 (Grade ${result.grade})`);
473
+ lines.push("");
474
+ // Categories
475
+ const categories = [
476
+ { key: "code", name: "Code Quality", emoji: "๐Ÿ“" },
477
+ { key: "tests", name: "Tests", emoji: "๐Ÿงช" },
478
+ { key: "docs", name: "Documentation", emoji: "๐Ÿ“„" },
479
+ { key: "config", name: "Configuration", emoji: "โš™๏ธ" },
480
+ { key: "deploy", name: "Deploy Readiness", emoji: "๐Ÿš€" },
481
+ ];
482
+ for (const { key, name, emoji } of categories) {
483
+ const cat = result.categories[key];
484
+ const status = cat.score >= 80 ? "โœ…" : cat.score >= 60 ? "โš ๏ธ" : "โŒ";
485
+ lines.push(`${emoji} ${name}: ${cat.score}/100 ${status}`);
486
+ for (const detail of cat.details) {
487
+ lines.push(` ${detail}`);
488
+ }
489
+ for (const issue of cat.issues) {
490
+ lines.push(` โŒ ${issue}`);
491
+ }
492
+ if (cat.details.length === 0 && cat.issues.length === 0) {
493
+ lines.push(` โœ… No issues`);
494
+ }
495
+ lines.push("");
496
+ }
497
+ return lines.join("\n");
498
+ }
499
+ /**
500
+ * Get critical issues that must be fixed before production
501
+ */
502
+ export function getCriticalIssues(result) {
503
+ const critical = [];
504
+ for (const [key, cat] of Object.entries(result.categories)) {
505
+ for (const issue of cat.issues) {
506
+ // Score critical based on category and issue severity
507
+ if (key === "code" && issue.includes("debugger")) {
508
+ critical.push(`[CRITICAL] ${issue}`);
509
+ }
510
+ else if (key === "tests" && !result.categories.tests.score) {
511
+ critical.push(`[CRITICAL] No tests found`);
512
+ }
513
+ else if (key === "docs" && issue.includes("README")) {
514
+ critical.push(`[IMPORTANT] ${issue}`);
515
+ }
516
+ else if (cat.score < 50) {
517
+ critical.push(`[${key.toUpperCase()}] ${issue}`);
518
+ }
519
+ }
520
+ }
521
+ return [...new Set(critical)]; // Deduplicate
522
+ }