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,202 @@
1
+ /**
2
+ * State Matrix Builder
3
+ *
4
+ * Implements Amain's 57×72 state transfer matrix construction.
5
+ * Counts parent→child transitions in the TypeScript AST.
6
+ */
7
+
8
+ import * as ts from "typescript";
9
+ import { getStateIndex, NUM_STATES, NUM_SYNTAX } from "./amain-types.js";
10
+
11
+ // ============================================================================
12
+ // Matrix Construction
13
+ // ============================================================================
14
+
15
+ /**
16
+ * Build a 57×72 state transfer matrix from TypeScript source code.
17
+ *
18
+ * matrix[i][j] = count of transitions from syntax state i to state j
19
+ *
20
+ * @param sourceCode TypeScript source code
21
+ * @returns 57×72 matrix of transition counts
22
+ */
23
+ export function buildStateMatrix(sourceCode: string): number[][] {
24
+ const sourceFile = ts.createSourceFile(
25
+ "temp.ts",
26
+ sourceCode,
27
+ ts.ScriptTarget.Latest,
28
+ true,
29
+ ts.ScriptKind.TS,
30
+ );
31
+
32
+ // Initialize 57×72 matrix with zeros
33
+ const matrix: number[][] = Array(NUM_SYNTAX)
34
+ .fill(0)
35
+ .map(() => Array(NUM_STATES).fill(0));
36
+
37
+ // Walk AST and count transitions
38
+ function visitNode(node: ts.Node, parentKind?: number) {
39
+ const nodeState = getStateIndex(node);
40
+
41
+ if (parentKind !== undefined) {
42
+ const parentState = getStateIndex({ kind: parentKind } as ts.Node);
43
+ // Only count transitions from syntax states (first 57)
44
+ if (parentState < NUM_SYNTAX) {
45
+ matrix[parentState][nodeState]++;
46
+ }
47
+ }
48
+
49
+ // Continue to children
50
+ ts.forEachChild(node, (child) => visitNode(child, node.kind));
51
+ }
52
+
53
+ visitNode(sourceFile);
54
+ return matrix;
55
+ }
56
+
57
+ /**
58
+ * Build matrix from a source file node (for incremental updates)
59
+ */
60
+ export function buildStateMatrixFromFile(
61
+ sourceFile: ts.SourceFile,
62
+ ): number[][] {
63
+ // Initialize 57×72 matrix with zeros
64
+ const matrix: number[][] = Array(NUM_SYNTAX)
65
+ .fill(0)
66
+ .map(() => Array(NUM_STATES).fill(0));
67
+
68
+ // Walk AST and count transitions
69
+ function visitNode(node: ts.Node, parentKind?: number) {
70
+ const nodeState = getStateIndex(node);
71
+
72
+ if (parentKind !== undefined) {
73
+ const parentState = getStateIndex({ kind: parentKind } as ts.Node);
74
+ // Only count transitions from syntax states (first 57)
75
+ if (parentState < NUM_SYNTAX) {
76
+ matrix[parentState][nodeState]++;
77
+ }
78
+ }
79
+
80
+ // Continue to children
81
+ ts.forEachChild(node, (child) => visitNode(child, node.kind));
82
+ }
83
+
84
+ visitNode(sourceFile);
85
+ return matrix;
86
+ }
87
+
88
+ // ============================================================================
89
+ // Probability Normalization
90
+ // ============================================================================
91
+
92
+ /**
93
+ * Convert count matrix to probability matrix.
94
+ * Each row sums to 1 (Markov chain property).
95
+ *
96
+ * @param matrix 57×72 count matrix
97
+ * @returns 57×72 probability matrix
98
+ */
99
+ export function toProbabilityMatrix(matrix: number[][]): number[][] {
100
+ return matrix.map((row) => {
101
+ const sum = row.reduce((a, b) => a + b, 0);
102
+ if (sum === 0) return row.map(() => 0);
103
+ return row.map((val) => val / sum);
104
+ });
105
+ }
106
+
107
+ // ============================================================================
108
+ // Similarity Calculation
109
+ // ============================================================================
110
+
111
+ /**
112
+ * Calculate cosine similarity between two state matrices.
113
+ * Returns 0-1 similarity score (1 = identical).
114
+ *
115
+ * @param matrix1 57×72 count matrix
116
+ * @param matrix2 57×72 count matrix
117
+ * @returns similarity score 0-1
118
+ */
119
+ export function calculateSimilarity(
120
+ matrix1: number[][],
121
+ matrix2: number[][],
122
+ ): number {
123
+ const prob1 = toProbabilityMatrix(matrix1);
124
+ const prob2 = toProbabilityMatrix(matrix2);
125
+
126
+ const similarities: number[] = [];
127
+
128
+ for (let i = 0; i < NUM_SYNTAX; i++) {
129
+ const row1 = prob1[i];
130
+ const row2 = prob2[i];
131
+
132
+ // Skip if both rows are empty
133
+ const hasData1 = row1.some((v) => v > 0);
134
+ const hasData2 = row2.some((v) => v > 0);
135
+
136
+ if (hasData1 || hasData2) {
137
+ // Calculate cosine similarity for this state
138
+ let dotProduct = 0;
139
+ let norm1 = 0;
140
+ let norm2 = 0;
141
+
142
+ for (let j = 0; j < NUM_STATES; j++) {
143
+ dotProduct += row1[j] * row2[j];
144
+ norm1 += row1[j] * row1[j];
145
+ norm2 += row2[j] * row2[j];
146
+ }
147
+
148
+ if (norm1 > 0 && norm2 > 0) {
149
+ similarities.push(dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)));
150
+ }
151
+ }
152
+ }
153
+
154
+ // Return average similarity across all states
155
+ if (similarities.length === 0) return 0;
156
+ return similarities.reduce((a, b) => a + b, 0) / similarities.length;
157
+ }
158
+
159
+ /**
160
+ * Calculate similarity from source code directly (convenience method)
161
+ */
162
+ export function calculateSimilarityFromCode(
163
+ code1: string,
164
+ code2: string,
165
+ ): number {
166
+ const matrix1 = buildStateMatrix(code1);
167
+ const matrix2 = buildStateMatrix(code2);
168
+ return calculateSimilarity(matrix1, matrix2);
169
+ }
170
+
171
+ // ============================================================================
172
+ // Matrix Statistics (for guardrails)
173
+ // ============================================================================
174
+
175
+ /**
176
+ * Count total non-zero transitions in matrix (proxy for function complexity)
177
+ */
178
+ export function countTransitions(matrix: number[][]): number {
179
+ return matrix.flat().filter((v) => v > 0).length;
180
+ }
181
+
182
+ /**
183
+ * Check if function meets complexity threshold (>20 transitions)
184
+ */
185
+ export function isComplexEnough(matrix: number[][]): boolean {
186
+ return countTransitions(matrix) >= 20;
187
+ }
188
+
189
+ /**
190
+ * Serialize matrix for storage (sparse format to save space)
191
+ */
192
+ export function serializeMatrix(matrix: number[][]): number[][] {
193
+ // Return full matrix for now - can optimize to sparse later if needed
194
+ return matrix;
195
+ }
196
+
197
+ /**
198
+ * Deserialize matrix from storage
199
+ */
200
+ export function deserializeMatrix(data: number[][]): number[][] {
201
+ return data;
202
+ }
@@ -1,5 +1,5 @@
1
- import { spawnSync } from "node:child_process";
2
1
  import * as path from "node:path";
2
+ import { safeSpawn } from "./safe-spawn.js";
3
3
  export class SubprocessClient {
4
4
  constructor(verbose = false) {
5
5
  this.available = null;
@@ -13,10 +13,8 @@ export class SubprocessClient {
13
13
  return this.available;
14
14
  const cmd = this.getCheckCommand();
15
15
  try {
16
- const result = spawnSync(cmd[0], cmd.slice(1), {
17
- encoding: "utf-8",
16
+ const result = safeSpawn(cmd[0], cmd.slice(1), {
18
17
  timeout: 10000,
19
- shell: true,
20
18
  });
21
19
  this.available = !result.error && result.status === 0;
22
20
  if (this.available) {
@@ -39,17 +37,20 @@ export class SubprocessClient {
39
37
  runCommand(cmd, options = {}) {
40
38
  const { cwd, timeout = 15000, input } = options;
41
39
  try {
42
- const result = spawnSync(cmd[0], cmd.slice(1), {
43
- encoding: "utf-8",
40
+ const result = safeSpawn(cmd[0], cmd.slice(1), {
44
41
  timeout,
45
42
  cwd,
46
- shell: true,
47
- input,
48
43
  });
49
44
  if (result.error) {
50
45
  this.log(`Command error: ${result.error.message}`);
51
46
  }
52
- return result;
47
+ // Return in a shape compatible with spawnSync return type
48
+ return {
49
+ status: result.status,
50
+ stdout: result.stdout,
51
+ stderr: result.stderr,
52
+ error: result.error,
53
+ };
53
54
  }
54
55
  catch (err) {
55
56
  this.log(`Command failed: ${err.message}`);
@@ -1,5 +1,6 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import * as path from "node:path";
3
+ import { safeSpawn } from "./safe-spawn.js";
3
4
 
4
5
  export interface Diagnostic {
5
6
  line: number;
@@ -35,10 +36,8 @@ export abstract class SubprocessClient<T extends Diagnostic> {
35
36
 
36
37
  const cmd = this.getCheckCommand();
37
38
  try {
38
- const result = spawnSync(cmd[0], cmd.slice(1), {
39
- encoding: "utf-8",
39
+ const result = safeSpawn(cmd[0], cmd.slice(1), {
40
40
  timeout: 10000,
41
- shell: true,
42
41
  });
43
42
 
44
43
  this.available = !result.error && result.status === 0;
@@ -73,19 +72,22 @@ export abstract class SubprocessClient<T extends Diagnostic> {
73
72
  const { cwd, timeout = 15000, input } = options;
74
73
 
75
74
  try {
76
- const result = spawnSync(cmd[0], cmd.slice(1), {
77
- encoding: "utf-8",
75
+ const result = safeSpawn(cmd[0], cmd.slice(1), {
78
76
  timeout,
79
77
  cwd,
80
- shell: true,
81
- input,
82
78
  });
83
79
 
84
80
  if (result.error) {
85
81
  this.log(`Command error: ${result.error.message}`);
86
82
  }
87
83
 
88
- return result;
84
+ // Return in a shape compatible with spawnSync return type
85
+ return {
86
+ status: result.status,
87
+ stdout: result.stdout,
88
+ stderr: result.stderr,
89
+ error: result.error,
90
+ } as unknown as ReturnType<typeof spawnSync>;
89
91
  } catch (err: any) {
90
92
  this.log(`Command failed: ${err.message}`);
91
93
  return {
@@ -9,9 +9,9 @@
9
9
  * Design: File-level targeted testing — only runs tests for the
10
10
  * specific file being edited, not the entire suite.
11
11
  */
12
- import { spawnSync } from "node:child_process";
13
12
  import * as fs from "node:fs";
14
13
  import * as path from "node:path";
14
+ import { safeSpawn } from "./safe-spawn.js";
15
15
  // --- Test File Patterns ---
16
16
  const _TEST_FILE_PATTERNS = [
17
17
  {
@@ -250,10 +250,8 @@ export class TestRunnerClient {
250
250
  // Priority 5: Check if pytest is available globally (for Python)
251
251
  try {
252
252
  const whichCmd = process.platform === "win32" ? "where" : "which";
253
- const result = spawnSync(whichCmd, ["pytest"], {
254
- encoding: "utf-8",
253
+ const result = safeSpawn(whichCmd, ["pytest"], {
255
254
  timeout: 2000,
256
- shell: true,
257
255
  });
258
256
  if (result.status === 0) {
259
257
  this.log("Detected pytest globally");
@@ -336,11 +334,9 @@ export class TestRunnerClient {
336
334
  try {
337
335
  const args = config.args(absoluteTestFile, cwd);
338
336
  this.log(`Running: ${config.command} ${args.join(" ")}`);
339
- const result = spawnSync(config.command, args, {
340
- encoding: "utf-8",
337
+ const result = safeSpawn(config.command, args, {
341
338
  cwd,
342
339
  timeout: 60000, // 60s timeout
343
- shell: true,
344
340
  });
345
341
  const stdout = result.stdout || "";
346
342
  const stderr = result.stderr || "";
@@ -13,6 +13,7 @@
13
13
  import { spawnSync } from "node:child_process";
14
14
  import * as fs from "node:fs";
15
15
  import * as path from "node:path";
16
+ import { safeSpawn } from "./safe-spawn.js";
16
17
 
17
18
  // --- Types ---
18
19
 
@@ -303,10 +304,8 @@ export class TestRunnerClient {
303
304
  // Priority 5: Check if pytest is available globally (for Python)
304
305
  try {
305
306
  const whichCmd = process.platform === "win32" ? "where" : "which";
306
- const result = spawnSync(whichCmd, ["pytest"], {
307
- encoding: "utf-8",
307
+ const result = safeSpawn(whichCmd, ["pytest"], {
308
308
  timeout: 2000,
309
- shell: true,
310
309
  });
311
310
  if (result.status === 0) {
312
311
  this.log("Detected pytest globally");
@@ -413,11 +412,9 @@ export class TestRunnerClient {
413
412
  const args = config.args(absoluteTestFile, cwd);
414
413
  this.log(`Running: ${config.command} ${args.join(" ")}`);
415
414
 
416
- const result = spawnSync(config.command, args, {
417
- encoding: "utf-8",
415
+ const result = safeSpawn(config.command, args, {
418
416
  cwd,
419
417
  timeout: 60000, // 60s timeout
420
- shell: true,
421
418
  });
422
419
 
423
420
  const stdout = result.stdout || "";
@@ -4,7 +4,7 @@
4
4
  * Provides cached tool availability checks to avoid repeated spawnSync calls.
5
5
  * Tools like biome, ruff, ast-grep are checked once per session.
6
6
  */
7
- import { spawnSync } from "node:child_process";
7
+ import { safeSpawn } from "./safe-spawn.js";
8
8
  // --- Tool Registry ---
9
9
  export const TOOL_REGISTRY = [
10
10
  {
@@ -91,10 +91,8 @@ export function isToolAvailable(toolName) {
91
91
  const tool = TOOL_REGISTRY.find((t) => t.name === toolName);
92
92
  if (!tool) {
93
93
  // Unknown tool - try direct command check
94
- const result = spawnSync(toolName, ["--version"], {
95
- encoding: "utf-8",
94
+ const result = safeSpawn(toolName, ["--version"], {
96
95
  timeout: 5000,
97
- shell: true,
98
96
  });
99
97
  const available = !result.error && result.status === 0;
100
98
  TOOL_CACHE.set(toolName, {
@@ -108,10 +106,8 @@ export function isToolAvailable(toolName) {
108
106
  }
109
107
  // Check using tool's version command
110
108
  if (tool.versionCommand) {
111
- const result = spawnSync(tool.command, tool.versionCommand, {
112
- encoding: "utf-8",
109
+ const result = safeSpawn(tool.command, tool.versionCommand, {
113
110
  timeout: 10000,
114
- shell: true,
115
111
  });
116
112
  const available = !result.error && result.status === 0;
117
113
  const output = result.stdout + result.stderr;
@@ -138,10 +134,8 @@ export function getToolVersion(toolName) {
138
134
  // Try to get version even if not cached
139
135
  const tool = TOOL_REGISTRY.find((t) => t.name === toolName);
140
136
  if (tool?.versionCommand) {
141
- const result = spawnSync(tool.command, tool.versionCommand, {
142
- encoding: "utf-8",
137
+ const result = safeSpawn(tool.command, tool.versionCommand, {
143
138
  timeout: 10000,
144
- shell: true,
145
139
  });
146
140
  if (!result.error && result.status === 0) {
147
141
  const output = result.stdout + result.stderr;
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import { spawnSync } from "node:child_process";
9
+ import { safeSpawn } from "./safe-spawn.js";
9
10
 
10
11
  // --- Types ---
11
12
 
@@ -116,10 +117,8 @@ export function isToolAvailable(toolName: string): boolean {
116
117
  const tool = TOOL_REGISTRY.find((t) => t.name === toolName);
117
118
  if (!tool) {
118
119
  // Unknown tool - try direct command check
119
- const result = spawnSync(toolName, ["--version"], {
120
- encoding: "utf-8",
120
+ const result = safeSpawn(toolName, ["--version"], {
121
121
  timeout: 5000,
122
- shell: true,
123
122
  });
124
123
  const available = !result.error && result.status === 0;
125
124
  TOOL_CACHE.set(toolName, {
@@ -134,10 +133,8 @@ export function isToolAvailable(toolName: string): boolean {
134
133
 
135
134
  // Check using tool's version command
136
135
  if (tool.versionCommand) {
137
- const result = spawnSync(tool.command, tool.versionCommand, {
138
- encoding: "utf-8",
136
+ const result = safeSpawn(tool.command, tool.versionCommand, {
139
137
  timeout: 10000,
140
- shell: true,
141
138
  });
142
139
  const available = !result.error && result.status === 0;
143
140
  const output = result.stdout + result.stderr;
@@ -169,10 +166,8 @@ export function getToolVersion(toolName: string): string | undefined {
169
166
  // Try to get version even if not cached
170
167
  const tool = TOOL_REGISTRY.find((t) => t.name === toolName);
171
168
  if (tool?.versionCommand) {
172
- const result = spawnSync(tool.command, tool.versionCommand, {
173
- encoding: "utf-8",
169
+ const result = safeSpawn(tool.command, tool.versionCommand, {
174
170
  timeout: 10000,
175
- shell: true,
176
171
  });
177
172
  if (!result.error && result.status === 0) {
178
173
  const output = result.stdout + result.stderr;