pi-lens 3.8.39 → 3.8.41

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 (65) hide show
  1. package/CHANGELOG.md +84 -5
  2. package/README.md +37 -1
  3. package/clients/biome-client.ts +5 -4
  4. package/clients/cache/rule-cache.ts +1 -1
  5. package/clients/complexity-client.ts +1 -1
  6. package/clients/dependency-checker.ts +1 -1
  7. package/clients/dispatch/diagnostic-taxonomy.ts +13 -1
  8. package/clients/dispatch/dispatcher.ts +9 -0
  9. package/clients/dispatch/fact-scheduler.ts +1 -1
  10. package/clients/dispatch/integration.ts +58 -3
  11. package/clients/dispatch/runners/index.ts +2 -0
  12. package/clients/dispatch/runners/semgrep.ts +269 -0
  13. package/clients/dispatch/runners/shellcheck.ts +2 -8
  14. package/clients/dispatch/runners/tree-sitter.ts +32 -11
  15. package/clients/dispatch/tool-profile.ts +1 -0
  16. package/clients/format-service.ts +10 -0
  17. package/clients/formatters.ts +22 -8
  18. package/clients/installer/index.ts +3 -3
  19. package/clients/knip-client.ts +360 -362
  20. package/clients/lsp/aggregation.ts +91 -0
  21. package/clients/lsp/client.ts +91 -38
  22. package/clients/lsp/index.ts +88 -72
  23. package/clients/lsp/launch.ts +107 -34
  24. package/clients/lsp/server-strategies.ts +71 -0
  25. package/clients/lsp/server.ts +76 -57
  26. package/clients/path-utils.ts +17 -0
  27. package/clients/pipeline.ts +23 -5
  28. package/clients/production-readiness.ts +2 -2
  29. package/clients/read-guard-logger.ts +41 -1
  30. package/clients/read-guard-tool-lines.ts +17 -4
  31. package/clients/read-guard.ts +95 -46
  32. package/clients/runtime-agent-end.ts +3 -0
  33. package/clients/runtime-session.ts +5 -0
  34. package/clients/runtime-tool-result.ts +48 -1
  35. package/clients/runtime-turn.ts +48 -4
  36. package/clients/sanitize.ts +1 -1
  37. package/clients/semgrep-config.ts +213 -0
  38. package/clients/tool-policy.ts +1982 -1936
  39. package/clients/tree-sitter-client.ts +1 -1
  40. package/clients/widget-state.ts +283 -0
  41. package/commands/booboo.ts +34 -2
  42. package/index.ts +231 -17
  43. package/package.json +3 -2
  44. package/rules/rule-catalog.json +25 -1
  45. package/rules/tree-sitter-queries/cobol/lock-table-cobol.yml +35 -0
  46. package/rules/tree-sitter-queries/cpp/unnecessary-bit-ops.yml +58 -0
  47. package/rules/tree-sitter-queries/java/infinite-loop.yml +58 -0
  48. package/rules/tree-sitter-queries/java/infinite-recursion.yml +58 -0
  49. package/rules/tree-sitter-queries/java/mockito-initialized.yml +66 -0
  50. package/rules/tree-sitter-queries/java/name-capitalization-conflict.yml +54 -0
  51. package/rules/tree-sitter-queries/java/no-octal-values.yml +48 -0
  52. package/rules/tree-sitter-queries/java/resources-closed.yml +57 -0
  53. package/rules/tree-sitter-queries/java/short-circuit-logic.yml +57 -0
  54. package/rules/tree-sitter-queries/java/tests-include-assertions.yml +60 -0
  55. package/rules/tree-sitter-queries/java/unnecessary-bit-ops-java.yml +57 -0
  56. package/rules/tree-sitter-queries/javascript/switch-case-termination-js.yml +64 -0
  57. package/rules/tree-sitter-queries/plsql/lock-table.yml +42 -0
  58. package/rules/tree-sitter-queries/plsql/nchar-nvarchar2-bytes.yml +54 -0
  59. package/rules/tree-sitter-queries/python/no-super-torchscript.yml +52 -0
  60. package/rules/tree-sitter-queries/typescript/default-not-last.yml +54 -0
  61. package/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +51 -0
  62. package/rules/tree-sitter-queries/typescript/empty-switch-case.yml +54 -0
  63. package/rules/tree-sitter-queries/typescript/infinite-loop.yml +55 -0
  64. package/rules/tree-sitter-queries/typescript/self-assignment.yml +46 -0
  65. package/rules/tree-sitter-queries/typescript/switch-case-termination.yml +64 -0
@@ -0,0 +1,213 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ export interface PiLensSemgrepConfig {
5
+ enabled?: boolean;
6
+ /** Semgrep config: local path, auto, p/<pack>, r/<rule>, or omitted for local auto-discovery. */
7
+ config?: string;
8
+ }
9
+
10
+ export interface ResolvedSemgrepConfig {
11
+ enabled: boolean;
12
+ /** Value to pass after --config. */
13
+ configArg?: string;
14
+ source: "local" | "pi-lens" | "flag" | "disabled";
15
+ reason?: string;
16
+ }
17
+
18
+ export const LOCAL_SEMGREP_CONFIG_NAMES = [
19
+ ".semgrep.yml",
20
+ ".semgrep.yaml",
21
+ "semgrep.yml",
22
+ "semgrep.yaml",
23
+ ] as const;
24
+
25
+ function walkUp(startDir: string): string[] {
26
+ const dirs: string[] = [];
27
+ let current = path.resolve(startDir || process.cwd());
28
+ while (true) {
29
+ dirs.push(current);
30
+ const parent = path.dirname(current);
31
+ if (parent === current) break;
32
+ current = parent;
33
+ }
34
+ return dirs;
35
+ }
36
+
37
+ export function findLocalSemgrepConfig(startDir: string): string | undefined {
38
+ for (const dir of walkUp(startDir)) {
39
+ for (const name of LOCAL_SEMGREP_CONFIG_NAMES) {
40
+ const candidate = path.join(dir, name);
41
+ if (fs.existsSync(candidate)) return candidate;
42
+ }
43
+ }
44
+ return undefined;
45
+ }
46
+
47
+ export function findPiLensSemgrepConfigPath(
48
+ startDir: string,
49
+ ): string | undefined {
50
+ for (const dir of walkUp(startDir)) {
51
+ const candidate = path.join(dir, ".pi-lens", "semgrep.json");
52
+ if (fs.existsSync(candidate)) return candidate;
53
+ }
54
+ return undefined;
55
+ }
56
+
57
+ export function getPiLensSemgrepConfigPath(cwd: string): string {
58
+ return path.join(
59
+ path.resolve(cwd || process.cwd()),
60
+ ".pi-lens",
61
+ "semgrep.json",
62
+ );
63
+ }
64
+
65
+ export function loadPiLensSemgrepConfig(
66
+ startDir: string,
67
+ ): PiLensSemgrepConfig | undefined {
68
+ const configPath = findPiLensSemgrepConfigPath(startDir);
69
+ if (!configPath) return undefined;
70
+ try {
71
+ const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8")) as unknown;
72
+ if (!parsed || typeof parsed !== "object") return undefined;
73
+ const raw = parsed as Record<string, unknown>;
74
+ return {
75
+ enabled: typeof raw.enabled === "boolean" ? raw.enabled : undefined,
76
+ config: typeof raw.config === "string" ? raw.config : undefined,
77
+ };
78
+ } catch {
79
+ return undefined;
80
+ }
81
+ }
82
+
83
+ export function savePiLensSemgrepConfig(
84
+ cwd: string,
85
+ config: PiLensSemgrepConfig,
86
+ ): string {
87
+ const configPath = getPiLensSemgrepConfigPath(cwd);
88
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
89
+ fs.writeFileSync(
90
+ `${configPath}.tmp`,
91
+ `${JSON.stringify(config, null, "\t")}\n`,
92
+ );
93
+ fs.renameSync(`${configPath}.tmp`, configPath);
94
+ return configPath;
95
+ }
96
+
97
+ export function removePiLensSemgrepConfig(cwd: string): boolean {
98
+ const configPath = getPiLensSemgrepConfigPath(cwd);
99
+ if (!fs.existsSync(configPath)) return false;
100
+ fs.unlinkSync(configPath);
101
+ return true;
102
+ }
103
+
104
+ function isRegistryOrAutoConfig(config: string): boolean {
105
+ return (
106
+ config === "auto" || config.startsWith("p/") || config.startsWith("r/")
107
+ );
108
+ }
109
+
110
+ export function normalizeSemgrepConfigArg(
111
+ config: string | undefined,
112
+ cwd: string,
113
+ ): string | undefined {
114
+ if (!config) return undefined;
115
+ const trimmed = config.trim();
116
+ if (!trimmed) return undefined;
117
+ if (isRegistryOrAutoConfig(trimmed)) return trimmed;
118
+ return path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed);
119
+ }
120
+
121
+ export function resolveSemgrepConfig(
122
+ cwd: string,
123
+ flags?: { enabled?: boolean; config?: string | boolean | undefined },
124
+ ): ResolvedSemgrepConfig {
125
+ const localConfig = findLocalSemgrepConfig(cwd);
126
+ const persisted = loadPiLensSemgrepConfig(cwd);
127
+ const flagConfig =
128
+ typeof flags?.config === "string" && flags.config.trim()
129
+ ? flags.config.trim()
130
+ : undefined;
131
+
132
+ if (persisted?.enabled === false && !flags?.enabled) {
133
+ return {
134
+ enabled: false,
135
+ source: "disabled",
136
+ reason: "disabled in .pi-lens/semgrep.json",
137
+ };
138
+ }
139
+
140
+ if (flags?.enabled) {
141
+ const configArg = normalizeSemgrepConfigArg(flagConfig ?? localConfig, cwd);
142
+ if (!configArg) {
143
+ return {
144
+ enabled: false,
145
+ source: "disabled",
146
+ reason:
147
+ "--lens-semgrep was set but no Semgrep config was found; pass --lens-semgrep-config auto|p/<pack>|<path> or create .semgrep.yml",
148
+ };
149
+ }
150
+ return {
151
+ enabled: true,
152
+ configArg,
153
+ source: "flag",
154
+ };
155
+ }
156
+
157
+ if (persisted?.enabled) {
158
+ const configArg = normalizeSemgrepConfigArg(
159
+ persisted.config ?? localConfig,
160
+ cwd,
161
+ );
162
+ if (!configArg) {
163
+ return {
164
+ enabled: false,
165
+ source: "disabled",
166
+ reason:
167
+ "Semgrep is enabled in .pi-lens/semgrep.json but no config is set or discovered",
168
+ };
169
+ }
170
+ return {
171
+ enabled: true,
172
+ configArg,
173
+ source: "pi-lens",
174
+ };
175
+ }
176
+
177
+ if (localConfig) {
178
+ return {
179
+ enabled: true,
180
+ configArg: localConfig,
181
+ source: "local",
182
+ };
183
+ }
184
+
185
+ return {
186
+ enabled: false,
187
+ source: "disabled",
188
+ reason: "no local semgrep config and semgrep not explicitly enabled",
189
+ };
190
+ }
191
+
192
+ export function createStarterSemgrepConfig(cwd: string): string {
193
+ const configPath = path.join(
194
+ path.resolve(cwd || process.cwd()),
195
+ ".semgrep.yml",
196
+ );
197
+ if (fs.existsSync(configPath)) return configPath;
198
+ const contents = `rules:
199
+ - id: pi-lens.no-eval
200
+ pattern: eval(...)
201
+ message: Avoid eval; use a safer, explicit parser or allowlisted operation.
202
+ languages: [javascript, typescript]
203
+ severity: ERROR
204
+ metadata:
205
+ pi-lens:
206
+ semantic: blocking
207
+ defect_class: injection
208
+ confidence: high
209
+ fix: Replace eval with a constrained parser or an explicit allowlist.
210
+ `;
211
+ fs.writeFileSync(configPath, contents);
212
+ return configPath;
213
+ }