deep-slop 1.4.1 → 1.6.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.
@@ -3899,636 +3899,192 @@ var init_file_cache = __esm({
3899
3899
  }
3900
3900
  });
3901
3901
 
3902
- // src/plugins/loader.ts
3903
- async function loadPlugin(pluginPath) {
3904
- try {
3905
- const mod = await import(pluginPath);
3906
- const engine = mod.default;
3907
- if (!engine || typeof engine !== "object") {
3908
- return null;
3902
+ // src/utils/tree-sitter.ts
3903
+ function clearParseCache() {
3904
+ parseCache.clear();
3905
+ }
3906
+ async function initParser() {
3907
+ if (initDone) return initOk;
3908
+ if (initPromise) return initPromise;
3909
+ initPromise = (async () => {
3910
+ try {
3911
+ const wt = await import("web-tree-sitter");
3912
+ const { dirname: dirname9 } = await import("node:path");
3913
+ const wasmDir = dirname9(
3914
+ __require.resolve("web-tree-sitter/tree-sitter.wasm")
3915
+ );
3916
+ await wt.Parser.init({
3917
+ locateFile: (name) => `${wasmDir}/${name}`
3918
+ });
3919
+ const parser = new wt.Parser();
3920
+ parserInstance = parser;
3921
+ const tsWasm = __require.resolve(
3922
+ "tree-sitter-typescript/tree-sitter-typescript.wasm"
3923
+ );
3924
+ tsLang = await wt.Language.load(tsWasm);
3925
+ const tsxWasm = __require.resolve(
3926
+ "tree-sitter-typescript/tree-sitter-tsx.wasm"
3927
+ );
3928
+ tsxLang = await wt.Language.load(tsxWasm);
3929
+ initDone = true;
3930
+ initOk = true;
3931
+ return true;
3932
+ } catch (err) {
3933
+ initDone = true;
3934
+ initOk = false;
3935
+ return false;
3909
3936
  }
3910
- if (typeof engine.name === "string" && typeof engine.description === "string" && Array.isArray(engine.supportedLanguages) && typeof engine.run === "function") {
3911
- return engine;
3937
+ })();
3938
+ return initPromise;
3939
+ }
3940
+ async function initPythonParser() {
3941
+ if (pyInitDone) return pyInitOk;
3942
+ if (!initOk) {
3943
+ const baseOk = await initParser();
3944
+ if (!baseOk) {
3945
+ pyInitDone = true;
3946
+ pyInitOk = false;
3947
+ return false;
3912
3948
  }
3913
- return null;
3949
+ }
3950
+ try {
3951
+ const pyWasm = __require.resolve(
3952
+ "tree-sitter-python/python.wasm"
3953
+ );
3954
+ const wt = await import("web-tree-sitter");
3955
+ pyLang = await wt.Language.load(pyWasm);
3956
+ pyInitDone = true;
3957
+ pyInitOk = true;
3958
+ return true;
3914
3959
  } catch {
3915
- return null;
3960
+ pyInitDone = true;
3961
+ pyInitOk = false;
3962
+ return false;
3916
3963
  }
3917
3964
  }
3918
- async function loadPlugins(paths) {
3919
- const results = await Promise.allSettled(
3920
- paths.map((p) => loadPlugin(p))
3921
- );
3922
- const engines = [];
3923
- for (const r of results) {
3924
- if (r.status === "fulfilled" && r.value !== null) {
3925
- engines.push(r.value);
3965
+ async function initGoParser() {
3966
+ if (goInitDone) return goInitOk;
3967
+ if (!initOk) {
3968
+ const baseOk = await initParser();
3969
+ if (!baseOk) {
3970
+ goInitDone = true;
3971
+ goInitOk = false;
3972
+ return false;
3926
3973
  }
3927
3974
  }
3928
- return engines;
3929
- }
3930
- var init_loader = __esm({
3931
- "src/plugins/loader.ts"() {
3932
- "use strict";
3975
+ try {
3976
+ const goWasm = __require.resolve(
3977
+ "tree-sitter-go/go.wasm"
3978
+ );
3979
+ const wt = await import("web-tree-sitter");
3980
+ goLang = await wt.Language.load(goWasm);
3981
+ goInitDone = true;
3982
+ goInitOk = true;
3983
+ return true;
3984
+ } catch {
3985
+ goInitDone = true;
3986
+ goInitOk = false;
3987
+ return false;
3933
3988
  }
3934
- });
3935
-
3936
- // src/plugins/registry.ts
3937
- var registry_exports = {};
3938
- __export(registry_exports, {
3939
- PLUGIN_DIR: () => PLUGIN_DIR,
3940
- discoverAndLoadPlugins: () => discoverAndLoadPlugins,
3941
- getPluginDir: () => getPluginDir,
3942
- pluginRegistry: () => pluginRegistry
3943
- });
3944
- import { join as join2 } from "node:path";
3945
- import { readdir } from "node:fs/promises";
3946
- import { existsSync as existsSync2 } from "node:fs";
3947
- function getPluginDir(rootDir) {
3948
- return join2(rootDir, PLUGIN_DIR);
3949
3989
  }
3950
- async function discoverAndLoadPlugins(rootDir) {
3951
- if (pluginRegistry.isLoaded) {
3952
- return pluginRegistry.getEngines();
3990
+ async function initRustParser() {
3991
+ if (rustInitDone) return rustInitOk;
3992
+ if (!initOk) {
3993
+ const baseOk = await initParser();
3994
+ if (!baseOk) {
3995
+ rustInitDone = true;
3996
+ rustInitOk = false;
3997
+ return false;
3998
+ }
3953
3999
  }
3954
- const pluginDir = getPluginDir(rootDir);
3955
- if (!existsSync2(pluginDir)) {
3956
- pluginRegistry.setLoaded(true);
3957
- return [];
4000
+ try {
4001
+ const rustWasm = __require.resolve(
4002
+ "tree-sitter-rust/rust.wasm"
4003
+ );
4004
+ const wt = await import("web-tree-sitter");
4005
+ rustLang = await wt.Language.load(rustWasm);
4006
+ rustInitDone = true;
4007
+ rustInitOk = true;
4008
+ return true;
4009
+ } catch {
4010
+ rustInitDone = true;
4011
+ rustInitOk = false;
4012
+ return false;
4013
+ }
4014
+ }
4015
+ async function initPhpParser() {
4016
+ if (phpInitDone) return phpInitOk;
4017
+ if (!initOk) {
4018
+ const baseOk = await initParser();
4019
+ if (!baseOk) {
4020
+ phpInitDone = true;
4021
+ phpInitOk = false;
4022
+ return false;
4023
+ }
3958
4024
  }
3959
4025
  try {
3960
- const files = await readdir(pluginDir);
3961
- const pluginPaths = files.filter((f) => f.endsWith(".js") || f.endsWith(".mjs")).map((f) => join2(pluginDir, f));
3962
- const engines = await loadPlugins(pluginPaths);
3963
- for (let i = 0; i < engines.length; i++) {
3964
- const engine = engines[i];
3965
- pluginRegistry.register({
3966
- id: engine.name,
3967
- path: pluginPaths[i],
3968
- engine,
3969
- loaded: true
3970
- });
4026
+ const phpWasm = __require.resolve(
4027
+ "tree-sitter-php/php.wasm"
4028
+ );
4029
+ const wt = await import("web-tree-sitter");
4030
+ phpLang = await wt.Language.load(phpWasm);
4031
+ phpInitDone = true;
4032
+ phpInitOk = true;
4033
+ return true;
4034
+ } catch {
4035
+ phpInitDone = true;
4036
+ phpInitOk = false;
4037
+ return false;
4038
+ }
4039
+ }
4040
+ async function initCsharpParser() {
4041
+ if (csharpInitDone) return csharpInitOk;
4042
+ if (!initOk) {
4043
+ const baseOk = await initParser();
4044
+ if (!baseOk) {
4045
+ csharpInitDone = true;
4046
+ csharpInitOk = false;
4047
+ return false;
3971
4048
  }
3972
- for (let i = 0; i < pluginPaths.length; i++) {
3973
- const path2 = pluginPaths[i];
3974
- const engine = engines.find((e) => {
3975
- const entry = pluginRegistry.getAll().find((ent) => ent.path === path2);
3976
- return entry?.engine;
3977
- });
3978
- if (!engine && !pluginRegistry.has(path2)) {
3979
- pluginRegistry.register({
3980
- id: `plugin-${i}`,
3981
- path: path2,
3982
- engine: null,
3983
- loaded: false,
3984
- error: "Failed to load plugin module"
3985
- });
3986
- }
4049
+ }
4050
+ try {
4051
+ const csharpWasm = __require.resolve(
4052
+ "tree-sitter-c-sharp/c_sharp.wasm"
4053
+ );
4054
+ const wt = await import("web-tree-sitter");
4055
+ csharpLang = await wt.Language.load(csharpWasm);
4056
+ csharpInitDone = true;
4057
+ csharpInitOk = true;
4058
+ return true;
4059
+ } catch {
4060
+ csharpInitDone = true;
4061
+ csharpInitOk = false;
4062
+ return false;
4063
+ }
4064
+ }
4065
+ async function initSwiftParser() {
4066
+ if (swiftInitDone) return swiftInitOk;
4067
+ if (!initOk) {
4068
+ const baseOk = await initParser();
4069
+ if (!baseOk) {
4070
+ swiftInitDone = true;
4071
+ swiftInitOk = false;
4072
+ return false;
3987
4073
  }
3988
- pluginRegistry.setLoaded(true);
3989
- return engines;
4074
+ }
4075
+ try {
4076
+ const swiftWasm = __require.resolve(
4077
+ "tree-sitter-swift/swift.wasm"
4078
+ );
4079
+ const wt = await import("web-tree-sitter");
4080
+ swiftLang = await wt.Language.load(swiftWasm);
4081
+ swiftInitDone = true;
4082
+ swiftInitOk = true;
4083
+ return true;
3990
4084
  } catch {
3991
- pluginRegistry.setLoaded(true);
3992
- return [];
3993
- }
3994
- }
3995
- var PluginRegistry, pluginRegistry, PLUGIN_DIR;
3996
- var init_registry = __esm({
3997
- "src/plugins/registry.ts"() {
3998
- "use strict";
3999
- init_loader();
4000
- PluginRegistry = class {
4001
- entries = /* @__PURE__ */ new Map();
4002
- loaded = false;
4003
- /** Get all registered plugins */
4004
- getAll() {
4005
- return [...this.entries.values()];
4006
- }
4007
- /** Get all successfully loaded engines */
4008
- getEngines() {
4009
- return [...this.entries.values()].filter((e) => e.loaded).map((e) => e.engine);
4010
- }
4011
- /** Get a specific plugin by id */
4012
- get(id) {
4013
- return this.entries.get(id);
4014
- }
4015
- /** Get a loaded engine by name */
4016
- getEngine(name) {
4017
- const entry = this.entries.get(name);
4018
- return entry?.loaded ? entry.engine : void 0;
4019
- }
4020
- /** Register a plugin entry */
4021
- register(entry) {
4022
- this.entries.set(entry.id, entry);
4023
- }
4024
- /** Remove a plugin by id */
4025
- remove(id) {
4026
- return this.entries.delete(id);
4027
- }
4028
- /** Check if a plugin is registered */
4029
- has(id) {
4030
- return this.entries.has(id);
4031
- }
4032
- /** Number of registered plugins */
4033
- get size() {
4034
- return this.entries.size;
4035
- }
4036
- /** Whether the registry has been loaded from disk */
4037
- get isLoaded() {
4038
- return this.loaded;
4039
- }
4040
- /** Mark the registry as loaded */
4041
- setLoaded(loaded) {
4042
- this.loaded = loaded;
4043
- }
4044
- /** Clear all entries */
4045
- clear() {
4046
- this.entries.clear();
4047
- this.loaded = false;
4048
- }
4049
- };
4050
- pluginRegistry = new PluginRegistry();
4051
- PLUGIN_DIR = ".deep-slop/plugins";
4052
- }
4053
- });
4054
-
4055
- // src/utils/suppress.ts
4056
- import { join as join3 } from "node:path";
4057
- import { readFileSync as readFileSync2 } from "node:fs";
4058
- function parseSuppressDirectives(content) {
4059
- const entries = [];
4060
- const lines = content.split("\n");
4061
- for (let i = 0; i < lines.length; i++) {
4062
- const line = lines[i].trim();
4063
- const lineNum = i + 1;
4064
- const ignoreNextMatch = line.match(
4065
- /\/\/\s*deep-slop-ignore-next(?:\s+(.+))?$/
4066
- );
4067
- if (ignoreNextMatch) {
4068
- entries.push({
4069
- directiveLine: lineNum,
4070
- targetLine: lineNum + 1,
4071
- rules: parseRuleList(ignoreNextMatch[1]),
4072
- type: "next-line"
4073
- });
4074
- continue;
4075
- }
4076
- const ignoreLineMatch = line.match(
4077
- /\/\/\s*deep-slop-ignore-line(?:\s+(.+))?$/
4078
- );
4079
- if (ignoreLineMatch) {
4080
- entries.push({
4081
- directiveLine: lineNum,
4082
- targetLine: lineNum,
4083
- rules: parseRuleList(ignoreLineMatch[1]),
4084
- type: "line"
4085
- });
4086
- continue;
4087
- }
4088
- const ignoreRuleMatch = line.match(
4089
- /\/\/\s*deep-slop-ignore(?:\s+(.+))?$/
4090
- );
4091
- if (ignoreRuleMatch) {
4092
- const rulePart = ignoreRuleMatch[1];
4093
- if (rulePart !== "start" && rulePart !== "end") {
4094
- entries.push({
4095
- directiveLine: lineNum,
4096
- targetLine: lineNum + 1,
4097
- rules: parseRuleList(rulePart),
4098
- type: "next-line"
4099
- });
4100
- continue;
4101
- }
4102
- }
4103
- const ignoreStartMatch = line.match(
4104
- /\/\/\s*deep-slop-ignore-start(?:\s+(.+))?$/
4105
- );
4106
- if (ignoreStartMatch) {
4107
- entries.push({
4108
- directiveLine: lineNum,
4109
- targetLine: lineNum,
4110
- rules: parseRuleList(ignoreStartMatch[1]),
4111
- type: "block-start"
4112
- });
4113
- continue;
4114
- }
4115
- const ignoreEndMatch = line.match(
4116
- /\/\/\s*deep-slop-ignore-end/
4117
- );
4118
- if (ignoreEndMatch) {
4119
- entries.push({
4120
- directiveLine: lineNum,
4121
- targetLine: lineNum,
4122
- rules: /* @__PURE__ */ new Set(),
4123
- type: "block-end"
4124
- });
4125
- continue;
4126
- }
4127
- const nextLineMatch = line.match(
4128
- /\/\/\s*deep-slop-disable-next-line(?:\s+(.+))?$/
4129
- );
4130
- if (nextLineMatch) {
4131
- entries.push({
4132
- directiveLine: lineNum,
4133
- targetLine: lineNum + 1,
4134
- rules: parseRuleList(nextLineMatch[1]),
4135
- type: "next-line"
4136
- });
4137
- continue;
4138
- }
4139
- const lineMatch = line.match(
4140
- /\/\/\s*deep-slop-disable-line(?:\s+(.+))?$/
4141
- );
4142
- if (lineMatch) {
4143
- entries.push({
4144
- directiveLine: lineNum,
4145
- targetLine: lineNum,
4146
- rules: parseRuleList(lineMatch[1]),
4147
- type: "line"
4148
- });
4149
- continue;
4150
- }
4151
- const blockStart = line.match(
4152
- /\/\*\s*deep-slop-disable(?:\s+(.+))?\s*\//
4153
- );
4154
- if (blockStart) {
4155
- entries.push({
4156
- directiveLine: lineNum,
4157
- targetLine: lineNum,
4158
- rules: parseRuleList(blockStart[1]),
4159
- type: "block-start"
4160
- });
4161
- continue;
4162
- }
4163
- const blockEnd = line.match(/\/\*\s*deep-slop-enable\s*\//);
4164
- if (blockEnd) {
4165
- entries.push({
4166
- directiveLine: lineNum,
4167
- targetLine: lineNum,
4168
- rules: /* @__PURE__ */ new Set(),
4169
- type: "block-end"
4170
- });
4171
- }
4172
- }
4173
- return entries;
4174
- }
4175
- function parseRuleList(rulesStr) {
4176
- if (!rulesStr) return /* @__PURE__ */ new Set();
4177
- const rules = rulesStr.split(/[,\\s]+/).map((r) => r.trim()).filter(Boolean);
4178
- return new Set(rules);
4179
- }
4180
- function buildSuppressChecker(entries) {
4181
- const blockRanges = [];
4182
- let currentStart = null;
4183
- for (const entry of entries) {
4184
- if (entry.type === "block-start") {
4185
- currentStart = { line: entry.directiveLine, rules: entry.rules };
4186
- } else if (entry.type === "block-end" && currentStart) {
4187
- blockRanges.push({
4188
- startLine: currentStart.line,
4189
- endLine: entry.directiveLine,
4190
- rules: currentStart.rules
4191
- });
4192
- currentStart = null;
4193
- }
4194
- }
4195
- if (currentStart) {
4196
- blockRanges.push({
4197
- startLine: currentStart.line,
4198
- endLine: 999999,
4199
- rules: currentStart.rules
4200
- });
4201
- }
4202
- return (line, rule) => {
4203
- for (const entry of entries) {
4204
- if (entry.type === "next-line" || entry.type === "line") {
4205
- if (entry.targetLine === line) {
4206
- if (entry.rules.size === 0 || entry.rules.has(rule)) {
4207
- return true;
4208
- }
4209
- }
4210
- }
4211
- }
4212
- for (const range2 of blockRanges) {
4213
- if (line >= range2.startLine && line <= range2.endLine) {
4214
- if (range2.rules.size === 0 || range2.rules.has(rule)) {
4215
- return true;
4216
- }
4217
- }
4218
- }
4219
- return false;
4220
- };
4221
- }
4222
- function buildSuppressMap(content) {
4223
- const entries = parseSuppressDirectives(content);
4224
- const isSuppressed = buildSuppressChecker(entries);
4225
- return { entries, isSuppressed };
4226
- }
4227
- function loadIgnoreFile(rootDir) {
4228
- const ignorePath = join3(rootDir, ".deep-slop", ".deep-slop-ignore");
4229
- try {
4230
- const content = readFileSync2(ignorePath, "utf-8");
4231
- return content.split("\n").map((line) => line.split("#")[0].trim()).filter((line) => line && !line.startsWith("file:"));
4232
- } catch {
4233
- return [];
4234
- }
4235
- }
4236
- function applySuppressDirectives(diagnostics, fileContents, globallySuppressed = /* @__PURE__ */ new Set()) {
4237
- const suppressMaps = /* @__PURE__ */ new Map();
4238
- for (const [filePath, content] of fileContents) {
4239
- suppressMaps.set(filePath, buildSuppressMap(content));
4240
- }
4241
- const filtered = [];
4242
- let suppressedCount = 0;
4243
- for (const diag8 of diagnostics) {
4244
- if (globallySuppressed.has(diag8.rule)) {
4245
- suppressedCount++;
4246
- continue;
4247
- }
4248
- const map = suppressMaps.get(diag8.filePath);
4249
- if (map && map.isSuppressed(diag8.line, diag8.rule)) {
4250
- suppressedCount++;
4251
- } else {
4252
- filtered.push(diag8);
4253
- }
4254
- }
4255
- return { filtered, suppressedCount };
4256
- }
4257
- var init_suppress = __esm({
4258
- "src/utils/suppress.ts"() {
4259
- "use strict";
4260
- }
4261
- });
4262
-
4263
- // src/utils/file-utils.ts
4264
- import { readFile as readFile2 } from "node:fs/promises";
4265
- async function readFileContent(filePath) {
4266
- const buffer = await readFile2(filePath);
4267
- let content = buffer.toString("utf-8");
4268
- if (content.charCodeAt(0) === 65279) {
4269
- content = content.slice(1);
4270
- }
4271
- return content;
4272
- }
4273
- function detectEncodingAnomalies(content) {
4274
- const hasBom = content.charCodeAt(0) === 65279;
4275
- const hasCrlf = content.includes("\r\n");
4276
- const hasZwnbsp = content.includes("\uFEFF");
4277
- const lfOnly = (content.match(/(?<!\r)\n/g) ?? []).length;
4278
- const crlfCount = (content.match(/\r\n/g) ?? []).length;
4279
- const lineEnding = crlfCount > 0 && lfOnly > crlfCount ? "mixed" : crlfCount > 0 ? "crlf" : "lf";
4280
- return { hasBom, hasCrlf, hasZwnbsp, lineEnding };
4281
- }
4282
- function toLines(content) {
4283
- return content.split("\n").map((text, i) => ({ num: i + 1, text }));
4284
- }
4285
- function extractImports(content, language) {
4286
- const imports = [];
4287
- const lines = toLines(content);
4288
- for (const { num, text } of lines) {
4289
- const trimmed = text.trim();
4290
- if (language === "typescript" || language === "javascript") {
4291
- const jsMatch = trimmed.match(
4292
- /^import\s+(?:type\s+)?(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+['"]([^'"]+)['"]/
4293
- );
4294
- if (jsMatch) {
4295
- imports.push({
4296
- line: num,
4297
- source: jsMatch[1],
4298
- raw: trimmed,
4299
- isTypeOnly: trimmed.includes("import type"),
4300
- isDefault: !trimmed.includes("{")
4301
- });
4302
- }
4303
- const dynMatch = trimmed.match(/import\s*\(\s*['"]([^'"]+)['"]\s*\)/);
4304
- if (dynMatch) {
4305
- imports.push({
4306
- line: num,
4307
- source: dynMatch[1],
4308
- raw: trimmed,
4309
- isTypeOnly: false,
4310
- isDynamic: true
4311
- });
4312
- }
4313
- const reqMatch = trimmed.match(/(?:const|let|var)\s+[^=]*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
4314
- if (reqMatch) {
4315
- imports.push({
4316
- line: num,
4317
- source: reqMatch[1],
4318
- raw: trimmed,
4319
- isTypeOnly: false,
4320
- isRequire: true
4321
- });
4322
- }
4323
- }
4324
- if (language === "python") {
4325
- const pyMatch = trimmed.match(/^from\s+([^\s]+)\s+import/);
4326
- if (pyMatch) {
4327
- imports.push({ line: num, source: pyMatch[1], raw: trimmed, isTypeOnly: false });
4328
- }
4329
- const pyImport = trimmed.match(/^import\s+([^\s]+)/);
4330
- if (pyImport) {
4331
- imports.push({ line: num, source: pyImport[1], raw: trimmed, isTypeOnly: false });
4332
- }
4333
- }
4334
- if (language === "go") {
4335
- const goMatch = trimmed.match(/"([^"]+)"/);
4336
- if (trimmed.startsWith("import") && goMatch) {
4337
- imports.push({ line: num, source: goMatch[1], raw: trimmed, isTypeOnly: false });
4338
- }
4339
- }
4340
- }
4341
- return imports;
4342
- }
4343
- var init_file_utils = __esm({
4344
- "src/utils/file-utils.ts"() {
4345
- "use strict";
4346
- }
4347
- });
4348
-
4349
- // src/utils/tree-sitter.ts
4350
- async function initParser() {
4351
- if (initDone) return initOk;
4352
- if (initPromise) return initPromise;
4353
- initPromise = (async () => {
4354
- try {
4355
- const wt = await import("web-tree-sitter");
4356
- const { dirname: dirname9 } = await import("node:path");
4357
- const wasmDir = dirname9(
4358
- __require.resolve("web-tree-sitter/tree-sitter.wasm")
4359
- );
4360
- await wt.Parser.init({
4361
- locateFile: (name) => `${wasmDir}/${name}`
4362
- });
4363
- const parser = new wt.Parser();
4364
- parserInstance = parser;
4365
- const tsWasm = __require.resolve(
4366
- "tree-sitter-typescript/tree-sitter-typescript.wasm"
4367
- );
4368
- tsLang = await wt.Language.load(tsWasm);
4369
- const tsxWasm = __require.resolve(
4370
- "tree-sitter-typescript/tree-sitter-tsx.wasm"
4371
- );
4372
- tsxLang = await wt.Language.load(tsxWasm);
4373
- initDone = true;
4374
- initOk = true;
4375
- return true;
4376
- } catch (err) {
4377
- initDone = true;
4378
- initOk = false;
4379
- return false;
4380
- }
4381
- })();
4382
- return initPromise;
4383
- }
4384
- async function initPythonParser() {
4385
- if (pyInitDone) return pyInitOk;
4386
- if (!initOk) {
4387
- const baseOk = await initParser();
4388
- if (!baseOk) {
4389
- pyInitDone = true;
4390
- pyInitOk = false;
4391
- return false;
4392
- }
4393
- }
4394
- try {
4395
- const pyWasm = __require.resolve(
4396
- "tree-sitter-python/python.wasm"
4397
- );
4398
- const wt = await import("web-tree-sitter");
4399
- pyLang = await wt.Language.load(pyWasm);
4400
- pyInitDone = true;
4401
- pyInitOk = true;
4402
- return true;
4403
- } catch {
4404
- pyInitDone = true;
4405
- pyInitOk = false;
4406
- return false;
4407
- }
4408
- }
4409
- async function initGoParser() {
4410
- if (goInitDone) return goInitOk;
4411
- if (!initOk) {
4412
- const baseOk = await initParser();
4413
- if (!baseOk) {
4414
- goInitDone = true;
4415
- goInitOk = false;
4416
- return false;
4417
- }
4418
- }
4419
- try {
4420
- const goWasm = __require.resolve(
4421
- "tree-sitter-go/go.wasm"
4422
- );
4423
- const wt = await import("web-tree-sitter");
4424
- goLang = await wt.Language.load(goWasm);
4425
- goInitDone = true;
4426
- goInitOk = true;
4427
- return true;
4428
- } catch {
4429
- goInitDone = true;
4430
- goInitOk = false;
4431
- return false;
4432
- }
4433
- }
4434
- async function initRustParser() {
4435
- if (rustInitDone) return rustInitOk;
4436
- if (!initOk) {
4437
- const baseOk = await initParser();
4438
- if (!baseOk) {
4439
- rustInitDone = true;
4440
- rustInitOk = false;
4441
- return false;
4442
- }
4443
- }
4444
- try {
4445
- const rustWasm = __require.resolve(
4446
- "tree-sitter-rust/rust.wasm"
4447
- );
4448
- const wt = await import("web-tree-sitter");
4449
- rustLang = await wt.Language.load(rustWasm);
4450
- rustInitDone = true;
4451
- rustInitOk = true;
4452
- return true;
4453
- } catch {
4454
- rustInitDone = true;
4455
- rustInitOk = false;
4456
- return false;
4457
- }
4458
- }
4459
- async function initPhpParser() {
4460
- if (phpInitDone) return phpInitOk;
4461
- if (!initOk) {
4462
- const baseOk = await initParser();
4463
- if (!baseOk) {
4464
- phpInitDone = true;
4465
- phpInitOk = false;
4466
- return false;
4467
- }
4468
- }
4469
- try {
4470
- const phpWasm = __require.resolve(
4471
- "tree-sitter-php/php.wasm"
4472
- );
4473
- const wt = await import("web-tree-sitter");
4474
- phpLang = await wt.Language.load(phpWasm);
4475
- phpInitDone = true;
4476
- phpInitOk = true;
4477
- return true;
4478
- } catch {
4479
- phpInitDone = true;
4480
- phpInitOk = false;
4481
- return false;
4482
- }
4483
- }
4484
- async function initCsharpParser() {
4485
- if (csharpInitDone) return csharpInitOk;
4486
- if (!initOk) {
4487
- const baseOk = await initParser();
4488
- if (!baseOk) {
4489
- csharpInitDone = true;
4490
- csharpInitOk = false;
4491
- return false;
4492
- }
4493
- }
4494
- try {
4495
- const csharpWasm = __require.resolve(
4496
- "tree-sitter-c-sharp/c_sharp.wasm"
4497
- );
4498
- const wt = await import("web-tree-sitter");
4499
- csharpLang = await wt.Language.load(csharpWasm);
4500
- csharpInitDone = true;
4501
- csharpInitOk = true;
4502
- return true;
4503
- } catch {
4504
- csharpInitDone = true;
4505
- csharpInitOk = false;
4506
- return false;
4507
- }
4508
- }
4509
- async function initSwiftParser() {
4510
- if (swiftInitDone) return swiftInitOk;
4511
- if (!initOk) {
4512
- const baseOk = await initParser();
4513
- if (!baseOk) {
4514
- swiftInitDone = true;
4515
- swiftInitOk = false;
4516
- return false;
4517
- }
4518
- }
4519
- try {
4520
- const swiftWasm = __require.resolve(
4521
- "tree-sitter-swift/swift.wasm"
4522
- );
4523
- const wt = await import("web-tree-sitter");
4524
- swiftLang = await wt.Language.load(swiftWasm);
4525
- swiftInitDone = true;
4526
- swiftInitOk = true;
4527
- return true;
4528
- } catch {
4529
- swiftInitDone = true;
4530
- swiftInitOk = false;
4531
- return false;
4085
+ swiftInitDone = true;
4086
+ swiftInitOk = false;
4087
+ return false;
4532
4088
  }
4533
4089
  }
4534
4090
  function isAvailable() {
@@ -4538,14 +4094,19 @@ function isPythonAvailable() {
4538
4094
  return pyInitOk && pyLang !== null;
4539
4095
  }
4540
4096
  async function parseFile(content, isTsx = false, filePath) {
4097
+ if (filePath) {
4098
+ const cacheKey = `${filePath}:${isTsx ? "tsx" : "ts"}`;
4099
+ const cached = parseCache.get(cacheKey);
4100
+ if (cached !== void 0) return cached;
4101
+ }
4541
4102
  if (filePath) {
4542
4103
  const ext2 = filePath.toLowerCase();
4543
- if (ext2.endsWith(".go")) return parseGoFile(content);
4544
- if (ext2.endsWith(".rs")) return parseRustFile(content);
4545
- if (ext2.endsWith(".php")) return parsePhpFile(content);
4546
- if (ext2.endsWith(".cs")) return parseCsharpFile(content);
4547
- if (ext2.endsWith(".swift")) return parseSwiftFile(content);
4548
- if (ext2.endsWith(".py")) return parsePython(content);
4104
+ if (ext2.endsWith(".go")) return parseGoFile(content, filePath);
4105
+ if (ext2.endsWith(".rs")) return parseRustFile(content, filePath);
4106
+ if (ext2.endsWith(".php")) return parsePhpFile(content, filePath);
4107
+ if (ext2.endsWith(".cs")) return parseCsharpFile(content, filePath);
4108
+ if (ext2.endsWith(".swift")) return parseSwiftFile(content, filePath);
4109
+ if (ext2.endsWith(".py")) return parsePython(content, filePath);
4549
4110
  }
4550
4111
  if (!parserInstance || !tsLang && !tsxLang) {
4551
4112
  const ok = await initParser();
@@ -4556,12 +4117,21 @@ async function parseFile(content, isTsx = false, filePath) {
4556
4117
  parserInstance.setLanguage(lang);
4557
4118
  const tree = parserInstance.parse(content);
4558
4119
  if (!tree) return null;
4559
- return convertNode(tree.rootNode, null);
4120
+ const result = convertNode(tree.rootNode, null);
4121
+ if (filePath) {
4122
+ parseCache.set(`${filePath}:${isTsx ? "tsx" : "ts"}`, result);
4123
+ }
4124
+ return result;
4560
4125
  } catch {
4126
+ if (filePath) parseCache.set(`${filePath}:${isTsx ? "tsx" : "ts"}`, null);
4561
4127
  return null;
4562
4128
  }
4563
4129
  }
4564
- async function parsePython(content) {
4130
+ async function parsePython(content, filePath) {
4131
+ if (filePath) {
4132
+ const cached = parseCache.get(`py:${filePath}`);
4133
+ if (cached !== void 0) return cached;
4134
+ }
4565
4135
  if (!pyLang) {
4566
4136
  const ok = await initPythonParser();
4567
4137
  if (!ok) return null;
@@ -4573,13 +4143,23 @@ async function parsePython(content) {
4573
4143
  try {
4574
4144
  parserInstance.setLanguage(pyLang);
4575
4145
  const tree = parserInstance.parse(content);
4576
- if (!tree) return null;
4577
- return convertNode(tree.rootNode, null);
4146
+ if (!tree) {
4147
+ if (filePath) parseCache.set(`py:${filePath}`, null);
4148
+ return null;
4149
+ }
4150
+ const result = convertNode(tree.rootNode, null);
4151
+ if (filePath) parseCache.set(`py:${filePath}`, result);
4152
+ return result;
4578
4153
  } catch {
4154
+ if (filePath) parseCache.set(`py:${filePath}`, null);
4579
4155
  return null;
4580
4156
  }
4581
4157
  }
4582
- async function parseGoFile(content) {
4158
+ async function parseGoFile(content, filePath) {
4159
+ if (filePath) {
4160
+ const cached = parseCache.get(`go:${filePath}`);
4161
+ if (cached !== void 0) return cached;
4162
+ }
4583
4163
  if (!goLang) {
4584
4164
  const ok = await initGoParser();
4585
4165
  if (!ok) return null;
@@ -4591,13 +4171,23 @@ async function parseGoFile(content) {
4591
4171
  try {
4592
4172
  parserInstance.setLanguage(goLang);
4593
4173
  const tree = parserInstance.parse(content);
4594
- if (!tree) return null;
4595
- return convertNode(tree.rootNode, null);
4174
+ if (!tree) {
4175
+ if (filePath) parseCache.set(`go:${filePath}`, null);
4176
+ return null;
4177
+ }
4178
+ const result = convertNode(tree.rootNode, null);
4179
+ if (filePath) parseCache.set(`go:${filePath}`, result);
4180
+ return result;
4596
4181
  } catch {
4182
+ if (filePath) parseCache.set(`go:${filePath}`, null);
4597
4183
  return null;
4598
4184
  }
4599
4185
  }
4600
- async function parseRustFile(content) {
4186
+ async function parseRustFile(content, filePath) {
4187
+ if (filePath) {
4188
+ const cached = parseCache.get(`rs:${filePath}`);
4189
+ if (cached !== void 0) return cached;
4190
+ }
4601
4191
  if (!rustLang) {
4602
4192
  const ok = await initRustParser();
4603
4193
  if (!ok) return null;
@@ -4609,444 +4199,928 @@ async function parseRustFile(content) {
4609
4199
  try {
4610
4200
  parserInstance.setLanguage(rustLang);
4611
4201
  const tree = parserInstance.parse(content);
4612
- if (!tree) return null;
4613
- return convertNode(tree.rootNode, null);
4202
+ if (!tree) {
4203
+ if (filePath) parseCache.set(`rs:${filePath}`, null);
4204
+ return null;
4205
+ }
4206
+ const result = convertNode(tree.rootNode, null);
4207
+ if (filePath) parseCache.set(`rs:${filePath}`, result);
4208
+ return result;
4614
4209
  } catch {
4210
+ if (filePath) parseCache.set(`rs:${filePath}`, null);
4615
4211
  return null;
4616
4212
  }
4617
4213
  }
4618
- async function parsePhpFile(content) {
4214
+ async function parsePhpFile(content, filePath) {
4215
+ if (filePath) {
4216
+ const cached = parseCache.get(`php:${filePath}`);
4217
+ if (cached !== void 0) return cached;
4218
+ }
4619
4219
  if (!phpLang) {
4620
4220
  const ok = await initPhpParser();
4621
4221
  if (!ok) return null;
4622
4222
  }
4623
- if (!parserInstance) {
4624
- const ok = await initParser();
4625
- if (!ok) return null;
4223
+ if (!parserInstance) {
4224
+ const ok = await initParser();
4225
+ if (!ok) return null;
4226
+ }
4227
+ try {
4228
+ parserInstance.setLanguage(phpLang);
4229
+ const tree = parserInstance.parse(content);
4230
+ if (!tree) {
4231
+ if (filePath) parseCache.set(`php:${filePath}`, null);
4232
+ return null;
4233
+ }
4234
+ const result = convertNode(tree.rootNode, null);
4235
+ if (filePath) parseCache.set(`php:${filePath}`, result);
4236
+ return result;
4237
+ } catch {
4238
+ if (filePath) parseCache.set(`php:${filePath}`, null);
4239
+ return null;
4240
+ }
4241
+ }
4242
+ async function parseCsharpFile(content, filePath) {
4243
+ if (filePath) {
4244
+ const cached = parseCache.get(`cs:${filePath}`);
4245
+ if (cached !== void 0) return cached;
4246
+ }
4247
+ if (!csharpLang) {
4248
+ const ok = await initCsharpParser();
4249
+ if (!ok) return null;
4250
+ }
4251
+ if (!parserInstance) {
4252
+ const ok = await initParser();
4253
+ if (!ok) return null;
4254
+ }
4255
+ try {
4256
+ parserInstance.setLanguage(csharpLang);
4257
+ const tree = parserInstance.parse(content);
4258
+ if (!tree) {
4259
+ if (filePath) parseCache.set(`cs:${filePath}`, null);
4260
+ return null;
4261
+ }
4262
+ const result = convertNode(tree.rootNode, null);
4263
+ if (filePath) parseCache.set(`cs:${filePath}`, result);
4264
+ return result;
4265
+ } catch {
4266
+ if (filePath) parseCache.set(`cs:${filePath}`, null);
4267
+ return null;
4268
+ }
4269
+ }
4270
+ async function parseSwiftFile(content, filePath) {
4271
+ if (filePath) {
4272
+ const cached = parseCache.get(`swift:${filePath}`);
4273
+ if (cached !== void 0) return cached;
4274
+ }
4275
+ if (!swiftLang) {
4276
+ const ok = await initSwiftParser();
4277
+ if (!ok) return null;
4278
+ }
4279
+ if (!parserInstance) {
4280
+ const ok = await initParser();
4281
+ if (!ok) return null;
4282
+ }
4283
+ try {
4284
+ parserInstance.setLanguage(swiftLang);
4285
+ const tree = parserInstance.parse(content);
4286
+ if (!tree) {
4287
+ if (filePath) parseCache.set(`swift:${filePath}`, null);
4288
+ return null;
4289
+ }
4290
+ const result = convertNode(tree.rootNode, null);
4291
+ if (filePath) parseCache.set(`swift:${filePath}`, result);
4292
+ return result;
4293
+ } catch {
4294
+ if (filePath) parseCache.set(`swift:${filePath}`, null);
4295
+ return null;
4296
+ }
4297
+ }
4298
+ function findNodesOfType(root, type) {
4299
+ const results = [];
4300
+ function walk(node) {
4301
+ if (node.type === type) results.push(node);
4302
+ for (const child of node.children) walk(child);
4303
+ }
4304
+ walk(root);
4305
+ return results;
4306
+ }
4307
+ function findNodesOfTypes(root, types3) {
4308
+ const typeSet = new Set(types3);
4309
+ const results = [];
4310
+ function walk(node) {
4311
+ if (typeSet.has(node.type)) results.push(node);
4312
+ for (const child of node.children) walk(child);
4313
+ }
4314
+ walk(root);
4315
+ return results;
4316
+ }
4317
+ function walkAST(root, visitor) {
4318
+ function walk(node) {
4319
+ const result = visitor(node);
4320
+ if (result !== false) {
4321
+ for (const child of node.children) walk(child);
4322
+ }
4323
+ }
4324
+ walk(root);
4325
+ }
4326
+ function findAncestor(node, predicate) {
4327
+ let current = node.parent;
4328
+ while (current) {
4329
+ if (predicate(current)) return current;
4330
+ current = current.parent;
4331
+ }
4332
+ return null;
4333
+ }
4334
+ function isInsideCatch(node) {
4335
+ return findAncestor(node, (n) => n.type === "catch_clause") !== null;
4336
+ }
4337
+ function findAncestorOfType(node, type) {
4338
+ return findAncestor(node, (n) => n.type === type);
4339
+ }
4340
+ function isCatchBodyEmpty(catchNode) {
4341
+ if (catchNode.type !== "catch_clause") return false;
4342
+ const body = catchNode.children.find(
4343
+ (c) => c.type === "statement_block" || c.type === "block"
4344
+ );
4345
+ if (!body) return true;
4346
+ const nonTrivial = body.children.filter(
4347
+ (c) => c.type !== "comment" && c.type !== "//" && c.type !== "/*" && c.type !== "{" && c.type !== "}" && c.text.trim() !== ""
4348
+ );
4349
+ return nonTrivial.length === 0;
4350
+ }
4351
+ function getAsExpressionType(node) {
4352
+ if (node.type !== "as_expression") return null;
4353
+ const typeChild = node.children.find((c) => c.fieldName === "type");
4354
+ return typeChild?.text ?? null;
4355
+ }
4356
+ function getAsExpressionContext(node) {
4357
+ if (isInsideCatch(node)) return "catch";
4358
+ const funcAncestor = findAncestor(
4359
+ node,
4360
+ (n) => ["function_declaration", "arrow_function", "method_definition"].includes(n.type)
4361
+ );
4362
+ if (funcAncestor) {
4363
+ const text = funcAncestor.text.toLowerCase();
4364
+ if (/prisma|drizzle|sequelize|mongoose|typeorm|knex|supabase/.test(text))
4365
+ return "orm";
4366
+ if (/json\.parse|parse\(/.test(text)) return "json";
4367
+ }
4368
+ return "unknown";
4369
+ }
4370
+ function extractImportFromNode(node) {
4371
+ if (node.type !== "import_statement" && node.type !== "import_declaration")
4372
+ return null;
4373
+ const sourceNode = node.children.find(
4374
+ (c) => c.type === "string" || c.fieldName === "source"
4375
+ );
4376
+ if (!sourceNode) return null;
4377
+ const source = sourceNode.text.replace(/^['"]|['"]$/g, "");
4378
+ const isTypeOnly = node.text.includes("import type ");
4379
+ const symbols = [];
4380
+ const namedImport = node.children.find(
4381
+ (c) => c.type === "named_imports" || c.type === "import_clause"
4382
+ );
4383
+ if (namedImport) {
4384
+ for (const child of namedImport.children) {
4385
+ if (child.type === "identifier" || child.type === "type_identifier" || child.type === "import_specifier") {
4386
+ symbols.push(child.text);
4387
+ }
4388
+ }
4389
+ }
4390
+ return { source, symbols, line: node.startRow + 1, isTypeOnly };
4391
+ }
4392
+ function findPythonFunctions(root) {
4393
+ const funcNodes = findNodesOfTypes(root, [
4394
+ "function_definition",
4395
+ "decorated_definition"
4396
+ ]);
4397
+ const results = [];
4398
+ for (const node of funcNodes) {
4399
+ if (node.type === "decorated_definition") {
4400
+ const inner = node.children.find(
4401
+ (c) => c.type === "function_definition"
4402
+ );
4403
+ if (inner) {
4404
+ results.push(extractPythonFunctionInfo(inner, node));
4405
+ }
4406
+ } else {
4407
+ results.push(extractPythonFunctionInfo(node));
4408
+ }
4409
+ }
4410
+ return results;
4411
+ }
4412
+ function findPythonClasses(root) {
4413
+ const classNodes = findNodesOfTypes(root, [
4414
+ "class_definition",
4415
+ "decorated_definition"
4416
+ ]);
4417
+ const results = [];
4418
+ for (const node of classNodes) {
4419
+ if (node.type === "decorated_definition") {
4420
+ const inner = node.children.find(
4421
+ (c) => c.type === "class_definition"
4422
+ );
4423
+ if (inner) {
4424
+ results.push(extractPythonClassInfo(inner, node));
4425
+ }
4426
+ } else {
4427
+ results.push(extractPythonClassInfo(node));
4428
+ }
4429
+ }
4430
+ return results;
4431
+ }
4432
+ function findPythonImports(root) {
4433
+ const importNodes = findNodesOfTypes(root, [
4434
+ "import_statement",
4435
+ "import_from_statement"
4436
+ ]);
4437
+ return importNodes.map(extractPythonImportInfo);
4438
+ }
4439
+ function isPythonFunctionStub(funcInfo) {
4440
+ const text = funcInfo.text;
4441
+ const bodyLines = text.split("\n").slice(1).join("\n").trim();
4442
+ if (/^(\s*(pass|\.\.\.)\s*)$/.test(bodyLines)) return true;
4443
+ const docstringOnly = bodyLines.replace(/"""[\s\S]*?"""/g, "").replace(/'''[\s\S]*?'''/g, "").trim();
4444
+ if (docstringOnly === "" || docstringOnly === "pass" || docstringOnly === "...") {
4445
+ return true;
4446
+ }
4447
+ return false;
4448
+ }
4449
+ function detectPythonAIPatterns(root) {
4450
+ const findings = [];
4451
+ const functions = findPythonFunctions(root);
4452
+ for (const fn of functions) {
4453
+ if (isPythonFunctionStub(fn)) {
4454
+ findings.push({
4455
+ type: "python-stub-function",
4456
+ message: `Function '${fn.name}' is a stub (only pass/ellipsis)`,
4457
+ line: fn.line
4458
+ });
4459
+ }
4460
+ }
4461
+ const tryNodes = findNodesOfType(root, "try_statement");
4462
+ for (const tryNode of tryNodes) {
4463
+ const exceptNodes = tryNode.children.filter(
4464
+ (c) => c.type === "except_clause"
4465
+ );
4466
+ for (const exceptNode of exceptNodes) {
4467
+ const hasSpecificType = exceptNode.children.some(
4468
+ (c) => c.type === "identifier" || c.type === "tuple"
4469
+ );
4470
+ if (!hasSpecificType) {
4471
+ const bareExcept = exceptNode.children.find(
4472
+ (c) => c.text && c.text.includes("except")
4473
+ );
4474
+ findings.push({
4475
+ type: "python-bare-except",
4476
+ message: "Bare except clause catches all exceptions",
4477
+ line: exceptNode.startRow + 1
4478
+ });
4479
+ }
4480
+ }
4481
+ }
4482
+ const commentNodes = findNodesOfType(root, "comment");
4483
+ for (const comment of commentNodes) {
4484
+ const text = comment.text.toLowerCase();
4485
+ if (/todo|fixme|hack|xxx/.test(text)) {
4486
+ findings.push({
4487
+ type: "python-todo-stub",
4488
+ message: `TODO/FIXME comment: ${comment.text.trim()}`,
4489
+ line: comment.startRow + 1
4490
+ });
4491
+ }
4626
4492
  }
4627
- try {
4628
- parserInstance.setLanguage(phpLang);
4629
- const tree = parserInstance.parse(content);
4630
- if (!tree) return null;
4631
- return convertNode(tree.rootNode, null);
4632
- } catch {
4633
- return null;
4493
+ const callNodes = findNodesOfType(root, "call");
4494
+ for (const call of callNodes) {
4495
+ const func = call.children[0];
4496
+ if (func && func.text === "print") {
4497
+ findings.push({
4498
+ type: "python-print-leftover",
4499
+ message: "print() statement \u2014 likely debug leftover",
4500
+ line: call.startRow + 1
4501
+ });
4502
+ }
4634
4503
  }
4504
+ return findings;
4635
4505
  }
4636
- async function parseCsharpFile(content) {
4637
- if (!csharpLang) {
4638
- const ok = await initCsharpParser();
4639
- if (!ok) return null;
4640
- }
4641
- if (!parserInstance) {
4642
- const ok = await initParser();
4643
- if (!ok) return null;
4644
- }
4645
- try {
4646
- parserInstance.setLanguage(csharpLang);
4647
- const tree = parserInstance.parse(content);
4648
- if (!tree) return null;
4649
- return convertNode(tree.rootNode, null);
4650
- } catch {
4651
- return null;
4506
+ function extractPythonFunctionInfo(funcNode, decoratedParent) {
4507
+ const nameNode = funcNode.children.find((c) => c.fieldName === "name") ?? funcNode.children.find((c) => c.type === "identifier");
4508
+ const name = nameNode?.text ?? "(anonymous)";
4509
+ const params = funcNode.children.find(
4510
+ (c) => c.type === "parameters"
4511
+ );
4512
+ const parameters = params ? params.children.filter((c) => c.type === "identifier" || c.type === "typed_parameter" || c.type === "default_parameter").map((c) => c.children[0]?.text ?? c.text) : [];
4513
+ const isAsync = funcNode.children.some(
4514
+ (c) => c.type === "async"
4515
+ );
4516
+ const decorators = [];
4517
+ if (decoratedParent) {
4518
+ for (const child of decoratedParent.children) {
4519
+ if (child.type === "decorator") {
4520
+ decorators.push(child.text.replace("@", ""));
4521
+ }
4522
+ }
4652
4523
  }
4524
+ return {
4525
+ name,
4526
+ decorators,
4527
+ parameters,
4528
+ isAsync,
4529
+ line: funcNode.startRow + 1,
4530
+ endLine: funcNode.endRow + 1,
4531
+ text: funcNode.text
4532
+ };
4653
4533
  }
4654
- async function parseSwiftFile(content) {
4655
- if (!swiftLang) {
4656
- const ok = await initSwiftParser();
4657
- if (!ok) return null;
4534
+ function extractPythonClassInfo(classNode, decoratedParent) {
4535
+ const nameNode = classNode.children.find((c) => c.fieldName === "name") ?? classNode.children.find((c) => c.type === "identifier");
4536
+ const name = nameNode?.text ?? "(anonymous)";
4537
+ const argList = classNode.children.find(
4538
+ (c) => c.type === "argument_list"
4539
+ );
4540
+ const bases = argList ? argList.children.filter((c) => c.type === "identifier" || c.type === "attribute").map((c) => c.text) : [];
4541
+ const decorators = [];
4542
+ if (decoratedParent) {
4543
+ for (const child of decoratedParent.children) {
4544
+ if (child.type === "decorator") {
4545
+ decorators.push(child.text.replace("@", ""));
4546
+ }
4547
+ }
4658
4548
  }
4659
- if (!parserInstance) {
4660
- const ok = await initParser();
4661
- if (!ok) return null;
4549
+ const body = classNode.children.find(
4550
+ (c) => c.type === "block"
4551
+ );
4552
+ const methods = [];
4553
+ if (body) {
4554
+ for (const child of body.children) {
4555
+ if (child.type === "function_definition") {
4556
+ methods.push(extractPythonFunctionInfo(child));
4557
+ } else if (child.type === "decorated_definition") {
4558
+ const inner = child.children.find(
4559
+ (c) => c.type === "function_definition"
4560
+ );
4561
+ if (inner) {
4562
+ methods.push(extractPythonFunctionInfo(inner, child));
4563
+ }
4564
+ }
4565
+ }
4662
4566
  }
4663
- try {
4664
- parserInstance.setLanguage(swiftLang);
4665
- const tree = parserInstance.parse(content);
4666
- if (!tree) return null;
4667
- return convertNode(tree.rootNode, null);
4668
- } catch {
4669
- return null;
4567
+ return {
4568
+ name,
4569
+ bases,
4570
+ decorators,
4571
+ methods,
4572
+ line: classNode.startRow + 1,
4573
+ endLine: classNode.endRow + 1,
4574
+ text: classNode.text
4575
+ };
4576
+ }
4577
+ function extractPythonImportInfo(importNode) {
4578
+ const isFromImport = importNode.type === "import_from_statement";
4579
+ let module = "";
4580
+ const symbols = [];
4581
+ if (isFromImport) {
4582
+ const moduleNode = importNode.children.find(
4583
+ (c) => c.fieldName === "module_name" || c.type === "dotted_name" && c.fieldName !== "name" || c.type === "identifier" && c.fieldName !== "name"
4584
+ );
4585
+ module = moduleNode?.text ?? "";
4586
+ const nameList = importNode.children.find(
4587
+ (c) => c.type === "dotted_name" && c !== moduleNode
4588
+ );
4589
+ const identifierChildren = importNode.children.filter(
4590
+ (c) => c.type === "identifier" && c !== moduleNode
4591
+ );
4592
+ if (nameList) {
4593
+ symbols.push(nameList.text);
4594
+ }
4595
+ for (const id of identifierChildren) {
4596
+ if (id.text !== "from" && id.text !== "import" && id.text !== module) {
4597
+ symbols.push(id.text);
4598
+ }
4599
+ }
4600
+ } else {
4601
+ const dottedNames = importNode.children.filter(
4602
+ (c) => c.type === "dotted_name"
4603
+ );
4604
+ const identifiers = importNode.children.filter(
4605
+ (c) => c.type === "identifier"
4606
+ );
4607
+ for (const dn of dottedNames) {
4608
+ symbols.push(dn.text);
4609
+ }
4610
+ for (const id of identifiers) {
4611
+ if (id.text !== "import") {
4612
+ symbols.push(id.text);
4613
+ }
4614
+ }
4615
+ module = symbols[0] ?? "";
4670
4616
  }
4617
+ return {
4618
+ module,
4619
+ symbols,
4620
+ isFromImport,
4621
+ line: importNode.startRow + 1,
4622
+ text: importNode.text
4623
+ };
4671
4624
  }
4672
- function findNodesOfType(root, type) {
4673
- const results = [];
4674
- function walk(node) {
4675
- if (node.type === type) results.push(node);
4676
- for (const child of node.children) walk(child);
4625
+ function convertNode(node, parent) {
4626
+ const children = [];
4627
+ const astNode = {
4628
+ type: node.type,
4629
+ text: node.text,
4630
+ startRow: node.startPosition.row,
4631
+ startCol: node.startPosition.column,
4632
+ endRow: node.endPosition.row,
4633
+ endCol: node.endPosition.column,
4634
+ children,
4635
+ parent,
4636
+ fieldName: null
4637
+ };
4638
+ for (let i = 0; i < node.childCount; i++) {
4639
+ const child = node.child(i);
4640
+ if (child) {
4641
+ const converted = convertNode(child, astNode);
4642
+ converted.fieldName = node.fieldNameForChild(i) ?? null;
4643
+ children.push(converted);
4644
+ }
4677
4645
  }
4678
- walk(root);
4679
- return results;
4646
+ return astNode;
4680
4647
  }
4681
- function findNodesOfTypes(root, types3) {
4682
- const typeSet = new Set(types3);
4683
- const results = [];
4684
- function walk(node) {
4685
- if (typeSet.has(node.type)) results.push(node);
4686
- for (const child of node.children) walk(child);
4648
+ var parseCache, parserInstance, tsLang, tsxLang, initPromise, initDone, initOk, pyLang, pyInitDone, pyInitOk, goLang, goInitDone, goInitOk, rustLang, rustInitDone, rustInitOk, phpLang, phpInitDone, phpInitOk, csharpLang, csharpInitDone, csharpInitOk, swiftLang, swiftInitDone, swiftInitOk;
4649
+ var init_tree_sitter = __esm({
4650
+ "src/utils/tree-sitter.ts"() {
4651
+ "use strict";
4652
+ parseCache = /* @__PURE__ */ new Map();
4653
+ parserInstance = null;
4654
+ tsLang = null;
4655
+ tsxLang = null;
4656
+ initPromise = null;
4657
+ initDone = false;
4658
+ initOk = false;
4659
+ pyLang = null;
4660
+ pyInitDone = false;
4661
+ pyInitOk = false;
4662
+ goLang = null;
4663
+ goInitDone = false;
4664
+ goInitOk = false;
4665
+ rustLang = null;
4666
+ rustInitDone = false;
4667
+ rustInitOk = false;
4668
+ phpLang = null;
4669
+ phpInitDone = false;
4670
+ phpInitOk = false;
4671
+ csharpLang = null;
4672
+ csharpInitDone = false;
4673
+ csharpInitOk = false;
4674
+ swiftLang = null;
4675
+ swiftInitDone = false;
4676
+ swiftInitOk = false;
4677
+ }
4678
+ });
4679
+
4680
+ // src/plugins/loader.ts
4681
+ async function loadPlugin(pluginPath) {
4682
+ try {
4683
+ const mod = await import(pluginPath);
4684
+ const engine = mod.default;
4685
+ if (!engine || typeof engine !== "object") {
4686
+ return null;
4687
+ }
4688
+ if (typeof engine.name === "string" && typeof engine.description === "string" && Array.isArray(engine.supportedLanguages) && typeof engine.run === "function") {
4689
+ return engine;
4690
+ }
4691
+ return null;
4692
+ } catch {
4693
+ return null;
4687
4694
  }
4688
- walk(root);
4689
- return results;
4690
4695
  }
4691
- function walkAST(root, visitor) {
4692
- function walk(node) {
4693
- const result = visitor(node);
4694
- if (result !== false) {
4695
- for (const child of node.children) walk(child);
4696
+ async function loadPlugins(paths) {
4697
+ const results = await Promise.allSettled(
4698
+ paths.map((p) => loadPlugin(p))
4699
+ );
4700
+ const engines = [];
4701
+ for (const r of results) {
4702
+ if (r.status === "fulfilled" && r.value !== null) {
4703
+ engines.push(r.value);
4696
4704
  }
4697
4705
  }
4698
- walk(root);
4706
+ return engines;
4699
4707
  }
4700
- function findAncestor(node, predicate) {
4701
- let current = node.parent;
4702
- while (current) {
4703
- if (predicate(current)) return current;
4704
- current = current.parent;
4708
+ var init_loader = __esm({
4709
+ "src/plugins/loader.ts"() {
4710
+ "use strict";
4705
4711
  }
4706
- return null;
4707
- }
4708
- function isInsideCatch(node) {
4709
- return findAncestor(node, (n) => n.type === "catch_clause") !== null;
4710
- }
4711
- function findAncestorOfType(node, type) {
4712
- return findAncestor(node, (n) => n.type === type);
4713
- }
4714
- function isCatchBodyEmpty(catchNode) {
4715
- if (catchNode.type !== "catch_clause") return false;
4716
- const body = catchNode.children.find(
4717
- (c) => c.type === "statement_block" || c.type === "block"
4718
- );
4719
- if (!body) return true;
4720
- const nonTrivial = body.children.filter(
4721
- (c) => c.type !== "comment" && c.type !== "//" && c.type !== "/*" && c.type !== "{" && c.type !== "}" && c.text.trim() !== ""
4722
- );
4723
- return nonTrivial.length === 0;
4724
- }
4725
- function getAsExpressionType(node) {
4726
- if (node.type !== "as_expression") return null;
4727
- const typeChild = node.children.find((c) => c.fieldName === "type");
4728
- return typeChild?.text ?? null;
4712
+ });
4713
+
4714
+ // src/plugins/registry.ts
4715
+ var registry_exports = {};
4716
+ __export(registry_exports, {
4717
+ PLUGIN_DIR: () => PLUGIN_DIR,
4718
+ discoverAndLoadPlugins: () => discoverAndLoadPlugins,
4719
+ getPluginDir: () => getPluginDir,
4720
+ pluginRegistry: () => pluginRegistry
4721
+ });
4722
+ import { join as join2 } from "node:path";
4723
+ import { readdir } from "node:fs/promises";
4724
+ import { existsSync as existsSync2 } from "node:fs";
4725
+ function getPluginDir(rootDir) {
4726
+ return join2(rootDir, PLUGIN_DIR);
4729
4727
  }
4730
- function getAsExpressionContext(node) {
4731
- if (isInsideCatch(node)) return "catch";
4732
- const funcAncestor = findAncestor(
4733
- node,
4734
- (n) => ["function_declaration", "arrow_function", "method_definition"].includes(n.type)
4735
- );
4736
- if (funcAncestor) {
4737
- const text = funcAncestor.text.toLowerCase();
4738
- if (/prisma|drizzle|sequelize|mongoose|typeorm|knex|supabase/.test(text))
4739
- return "orm";
4740
- if (/json\.parse|parse\(/.test(text)) return "json";
4728
+ async function discoverAndLoadPlugins(rootDir) {
4729
+ if (pluginRegistry.isLoaded) {
4730
+ return pluginRegistry.getEngines();
4741
4731
  }
4742
- return "unknown";
4743
- }
4744
- function extractImportFromNode(node) {
4745
- if (node.type !== "import_statement" && node.type !== "import_declaration")
4746
- return null;
4747
- const sourceNode = node.children.find(
4748
- (c) => c.type === "string" || c.fieldName === "source"
4749
- );
4750
- if (!sourceNode) return null;
4751
- const source = sourceNode.text.replace(/^['"]|['"]$/g, "");
4752
- const isTypeOnly = node.text.includes("import type ");
4753
- const symbols = [];
4754
- const namedImport = node.children.find(
4755
- (c) => c.type === "named_imports" || c.type === "import_clause"
4756
- );
4757
- if (namedImport) {
4758
- for (const child of namedImport.children) {
4759
- if (child.type === "identifier" || child.type === "type_identifier" || child.type === "import_specifier") {
4760
- symbols.push(child.text);
4761
- }
4762
- }
4732
+ const pluginDir = getPluginDir(rootDir);
4733
+ if (!existsSync2(pluginDir)) {
4734
+ pluginRegistry.setLoaded(true);
4735
+ return [];
4763
4736
  }
4764
- return { source, symbols, line: node.startRow + 1, isTypeOnly };
4765
- }
4766
- function findPythonFunctions(root) {
4767
- const funcNodes = findNodesOfTypes(root, [
4768
- "function_definition",
4769
- "decorated_definition"
4770
- ]);
4771
- const results = [];
4772
- for (const node of funcNodes) {
4773
- if (node.type === "decorated_definition") {
4774
- const inner = node.children.find(
4775
- (c) => c.type === "function_definition"
4776
- );
4777
- if (inner) {
4778
- results.push(extractPythonFunctionInfo(inner, node));
4779
- }
4780
- } else {
4781
- results.push(extractPythonFunctionInfo(node));
4737
+ try {
4738
+ const files = await readdir(pluginDir);
4739
+ const pluginPaths = files.filter((f) => f.endsWith(".js") || f.endsWith(".mjs")).map((f) => join2(pluginDir, f));
4740
+ const engines = await loadPlugins(pluginPaths);
4741
+ for (let i = 0; i < engines.length; i++) {
4742
+ const engine = engines[i];
4743
+ pluginRegistry.register({
4744
+ id: engine.name,
4745
+ path: pluginPaths[i],
4746
+ engine,
4747
+ loaded: true
4748
+ });
4782
4749
  }
4783
- }
4784
- return results;
4785
- }
4786
- function findPythonClasses(root) {
4787
- const classNodes = findNodesOfTypes(root, [
4788
- "class_definition",
4789
- "decorated_definition"
4790
- ]);
4791
- const results = [];
4792
- for (const node of classNodes) {
4793
- if (node.type === "decorated_definition") {
4794
- const inner = node.children.find(
4795
- (c) => c.type === "class_definition"
4796
- );
4797
- if (inner) {
4798
- results.push(extractPythonClassInfo(inner, node));
4750
+ for (let i = 0; i < pluginPaths.length; i++) {
4751
+ const path2 = pluginPaths[i];
4752
+ const engine = engines.find((e) => {
4753
+ const entry = pluginRegistry.getAll().find((ent) => ent.path === path2);
4754
+ return entry?.engine;
4755
+ });
4756
+ if (!engine && !pluginRegistry.has(path2)) {
4757
+ pluginRegistry.register({
4758
+ id: `plugin-${i}`,
4759
+ path: path2,
4760
+ engine: null,
4761
+ loaded: false,
4762
+ error: "Failed to load plugin module"
4763
+ });
4799
4764
  }
4800
- } else {
4801
- results.push(extractPythonClassInfo(node));
4802
4765
  }
4766
+ pluginRegistry.setLoaded(true);
4767
+ return engines;
4768
+ } catch {
4769
+ pluginRegistry.setLoaded(true);
4770
+ return [];
4803
4771
  }
4804
- return results;
4805
- }
4806
- function findPythonImports(root) {
4807
- const importNodes = findNodesOfTypes(root, [
4808
- "import_statement",
4809
- "import_from_statement"
4810
- ]);
4811
- return importNodes.map(extractPythonImportInfo);
4812
4772
  }
4813
- function isPythonFunctionStub(funcInfo) {
4814
- const text = funcInfo.text;
4815
- const bodyLines = text.split("\n").slice(1).join("\n").trim();
4816
- if (/^(\s*(pass|\.\.\.)\s*)$/.test(bodyLines)) return true;
4817
- const docstringOnly = bodyLines.replace(/"""[\s\S]*?"""/g, "").replace(/'''[\s\S]*?'''/g, "").trim();
4818
- if (docstringOnly === "" || docstringOnly === "pass" || docstringOnly === "...") {
4819
- return true;
4773
+ var PluginRegistry, pluginRegistry, PLUGIN_DIR;
4774
+ var init_registry = __esm({
4775
+ "src/plugins/registry.ts"() {
4776
+ "use strict";
4777
+ init_loader();
4778
+ PluginRegistry = class {
4779
+ entries = /* @__PURE__ */ new Map();
4780
+ loaded = false;
4781
+ /** Get all registered plugins */
4782
+ getAll() {
4783
+ return [...this.entries.values()];
4784
+ }
4785
+ /** Get all successfully loaded engines */
4786
+ getEngines() {
4787
+ return [...this.entries.values()].filter((e) => e.loaded).map((e) => e.engine);
4788
+ }
4789
+ /** Get a specific plugin by id */
4790
+ get(id) {
4791
+ return this.entries.get(id);
4792
+ }
4793
+ /** Get a loaded engine by name */
4794
+ getEngine(name) {
4795
+ const entry = this.entries.get(name);
4796
+ return entry?.loaded ? entry.engine : void 0;
4797
+ }
4798
+ /** Register a plugin entry */
4799
+ register(entry) {
4800
+ this.entries.set(entry.id, entry);
4801
+ }
4802
+ /** Remove a plugin by id */
4803
+ remove(id) {
4804
+ return this.entries.delete(id);
4805
+ }
4806
+ /** Check if a plugin is registered */
4807
+ has(id) {
4808
+ return this.entries.has(id);
4809
+ }
4810
+ /** Number of registered plugins */
4811
+ get size() {
4812
+ return this.entries.size;
4813
+ }
4814
+ /** Whether the registry has been loaded from disk */
4815
+ get isLoaded() {
4816
+ return this.loaded;
4817
+ }
4818
+ /** Mark the registry as loaded */
4819
+ setLoaded(loaded) {
4820
+ this.loaded = loaded;
4821
+ }
4822
+ /** Clear all entries */
4823
+ clear() {
4824
+ this.entries.clear();
4825
+ this.loaded = false;
4826
+ }
4827
+ };
4828
+ pluginRegistry = new PluginRegistry();
4829
+ PLUGIN_DIR = ".deep-slop/plugins";
4820
4830
  }
4821
- return false;
4822
- }
4823
- function detectPythonAIPatterns(root) {
4824
- const findings = [];
4825
- const functions = findPythonFunctions(root);
4826
- for (const fn of functions) {
4827
- if (isPythonFunctionStub(fn)) {
4828
- findings.push({
4829
- type: "python-stub-function",
4830
- message: `Function '${fn.name}' is a stub (only pass/ellipsis)`,
4831
- line: fn.line
4831
+ });
4832
+
4833
+ // src/utils/suppress.ts
4834
+ import { join as join3 } from "node:path";
4835
+ import { readFileSync as readFileSync2 } from "node:fs";
4836
+ function parseSuppressDirectives(content) {
4837
+ const entries = [];
4838
+ const lines = content.split("\n");
4839
+ for (let i = 0; i < lines.length; i++) {
4840
+ const line = lines[i].trim();
4841
+ const lineNum = i + 1;
4842
+ const ignoreNextMatch = line.match(
4843
+ /\/\/\s*deep-slop-ignore-next(?:\s+(.+))?$/
4844
+ );
4845
+ if (ignoreNextMatch) {
4846
+ entries.push({
4847
+ directiveLine: lineNum,
4848
+ targetLine: lineNum + 1,
4849
+ rules: parseRuleList(ignoreNextMatch[1]),
4850
+ type: "next-line"
4832
4851
  });
4852
+ continue;
4833
4853
  }
4834
- }
4835
- const tryNodes = findNodesOfType(root, "try_statement");
4836
- for (const tryNode of tryNodes) {
4837
- const exceptNodes = tryNode.children.filter(
4838
- (c) => c.type === "except_clause"
4854
+ const ignoreLineMatch = line.match(
4855
+ /\/\/\s*deep-slop-ignore-line(?:\s+(.+))?$/
4839
4856
  );
4840
- for (const exceptNode of exceptNodes) {
4841
- const hasSpecificType = exceptNode.children.some(
4842
- (c) => c.type === "identifier" || c.type === "tuple"
4843
- );
4844
- if (!hasSpecificType) {
4845
- const bareExcept = exceptNode.children.find(
4846
- (c) => c.text && c.text.includes("except")
4847
- );
4848
- findings.push({
4849
- type: "python-bare-except",
4850
- message: "Bare except clause catches all exceptions",
4851
- line: exceptNode.startRow + 1
4857
+ if (ignoreLineMatch) {
4858
+ entries.push({
4859
+ directiveLine: lineNum,
4860
+ targetLine: lineNum,
4861
+ rules: parseRuleList(ignoreLineMatch[1]),
4862
+ type: "line"
4863
+ });
4864
+ continue;
4865
+ }
4866
+ const ignoreRuleMatch = line.match(
4867
+ /\/\/\s*deep-slop-ignore(?:\s+(.+))?$/
4868
+ );
4869
+ if (ignoreRuleMatch) {
4870
+ const rulePart = ignoreRuleMatch[1];
4871
+ if (rulePart !== "start" && rulePart !== "end") {
4872
+ entries.push({
4873
+ directiveLine: lineNum,
4874
+ targetLine: lineNum + 1,
4875
+ rules: parseRuleList(rulePart),
4876
+ type: "next-line"
4852
4877
  });
4878
+ continue;
4853
4879
  }
4854
4880
  }
4855
- }
4856
- const commentNodes = findNodesOfType(root, "comment");
4857
- for (const comment of commentNodes) {
4858
- const text = comment.text.toLowerCase();
4859
- if (/todo|fixme|hack|xxx/.test(text)) {
4860
- findings.push({
4861
- type: "python-todo-stub",
4862
- message: `TODO/FIXME comment: ${comment.text.trim()}`,
4863
- line: comment.startRow + 1
4881
+ const ignoreStartMatch = line.match(
4882
+ /\/\/\s*deep-slop-ignore-start(?:\s+(.+))?$/
4883
+ );
4884
+ if (ignoreStartMatch) {
4885
+ entries.push({
4886
+ directiveLine: lineNum,
4887
+ targetLine: lineNum,
4888
+ rules: parseRuleList(ignoreStartMatch[1]),
4889
+ type: "block-start"
4864
4890
  });
4891
+ continue;
4865
4892
  }
4866
- }
4867
- const callNodes = findNodesOfType(root, "call");
4868
- for (const call of callNodes) {
4869
- const func = call.children[0];
4870
- if (func && func.text === "print") {
4871
- findings.push({
4872
- type: "python-print-leftover",
4873
- message: "print() statement \u2014 likely debug leftover",
4874
- line: call.startRow + 1
4893
+ const ignoreEndMatch = line.match(
4894
+ /\/\/\s*deep-slop-ignore-end/
4895
+ );
4896
+ if (ignoreEndMatch) {
4897
+ entries.push({
4898
+ directiveLine: lineNum,
4899
+ targetLine: lineNum,
4900
+ rules: /* @__PURE__ */ new Set(),
4901
+ type: "block-end"
4902
+ });
4903
+ continue;
4904
+ }
4905
+ const nextLineMatch = line.match(
4906
+ /\/\/\s*deep-slop-disable-next-line(?:\s+(.+))?$/
4907
+ );
4908
+ if (nextLineMatch) {
4909
+ entries.push({
4910
+ directiveLine: lineNum,
4911
+ targetLine: lineNum + 1,
4912
+ rules: parseRuleList(nextLineMatch[1]),
4913
+ type: "next-line"
4914
+ });
4915
+ continue;
4916
+ }
4917
+ const lineMatch = line.match(
4918
+ /\/\/\s*deep-slop-disable-line(?:\s+(.+))?$/
4919
+ );
4920
+ if (lineMatch) {
4921
+ entries.push({
4922
+ directiveLine: lineNum,
4923
+ targetLine: lineNum,
4924
+ rules: parseRuleList(lineMatch[1]),
4925
+ type: "line"
4926
+ });
4927
+ continue;
4928
+ }
4929
+ const blockStart = line.match(
4930
+ /\/\*\s*deep-slop-disable(?:\s+(.+))?\s*\//
4931
+ );
4932
+ if (blockStart) {
4933
+ entries.push({
4934
+ directiveLine: lineNum,
4935
+ targetLine: lineNum,
4936
+ rules: parseRuleList(blockStart[1]),
4937
+ type: "block-start"
4938
+ });
4939
+ continue;
4940
+ }
4941
+ const blockEnd = line.match(/\/\*\s*deep-slop-enable\s*\//);
4942
+ if (blockEnd) {
4943
+ entries.push({
4944
+ directiveLine: lineNum,
4945
+ targetLine: lineNum,
4946
+ rules: /* @__PURE__ */ new Set(),
4947
+ type: "block-end"
4875
4948
  });
4876
4949
  }
4877
4950
  }
4878
- return findings;
4951
+ return entries;
4879
4952
  }
4880
- function extractPythonFunctionInfo(funcNode, decoratedParent) {
4881
- const nameNode = funcNode.children.find((c) => c.fieldName === "name") ?? funcNode.children.find((c) => c.type === "identifier");
4882
- const name = nameNode?.text ?? "(anonymous)";
4883
- const params = funcNode.children.find(
4884
- (c) => c.type === "parameters"
4885
- );
4886
- const parameters = params ? params.children.filter((c) => c.type === "identifier" || c.type === "typed_parameter" || c.type === "default_parameter").map((c) => c.children[0]?.text ?? c.text) : [];
4887
- const isAsync = funcNode.children.some(
4888
- (c) => c.type === "async"
4889
- );
4890
- const decorators = [];
4891
- if (decoratedParent) {
4892
- for (const child of decoratedParent.children) {
4893
- if (child.type === "decorator") {
4894
- decorators.push(child.text.replace("@", ""));
4895
- }
4953
+ function parseRuleList(rulesStr) {
4954
+ if (!rulesStr) return /* @__PURE__ */ new Set();
4955
+ const rules = rulesStr.split(/[,\\s]+/).map((r) => r.trim()).filter(Boolean);
4956
+ return new Set(rules);
4957
+ }
4958
+ function buildSuppressChecker(entries) {
4959
+ const blockRanges = [];
4960
+ let currentStart = null;
4961
+ for (const entry of entries) {
4962
+ if (entry.type === "block-start") {
4963
+ currentStart = { line: entry.directiveLine, rules: entry.rules };
4964
+ } else if (entry.type === "block-end" && currentStart) {
4965
+ blockRanges.push({
4966
+ startLine: currentStart.line,
4967
+ endLine: entry.directiveLine,
4968
+ rules: currentStart.rules
4969
+ });
4970
+ currentStart = null;
4896
4971
  }
4897
4972
  }
4898
- return {
4899
- name,
4900
- decorators,
4901
- parameters,
4902
- isAsync,
4903
- line: funcNode.startRow + 1,
4904
- endLine: funcNode.endRow + 1,
4905
- text: funcNode.text
4973
+ if (currentStart) {
4974
+ blockRanges.push({
4975
+ startLine: currentStart.line,
4976
+ endLine: 999999,
4977
+ rules: currentStart.rules
4978
+ });
4979
+ }
4980
+ return (line, rule) => {
4981
+ for (const entry of entries) {
4982
+ if (entry.type === "next-line" || entry.type === "line") {
4983
+ if (entry.targetLine === line) {
4984
+ if (entry.rules.size === 0 || entry.rules.has(rule)) {
4985
+ return true;
4986
+ }
4987
+ }
4988
+ }
4989
+ }
4990
+ for (const range2 of blockRanges) {
4991
+ if (line >= range2.startLine && line <= range2.endLine) {
4992
+ if (range2.rules.size === 0 || range2.rules.has(rule)) {
4993
+ return true;
4994
+ }
4995
+ }
4996
+ }
4997
+ return false;
4906
4998
  };
4907
4999
  }
4908
- function extractPythonClassInfo(classNode, decoratedParent) {
4909
- const nameNode = classNode.children.find((c) => c.fieldName === "name") ?? classNode.children.find((c) => c.type === "identifier");
4910
- const name = nameNode?.text ?? "(anonymous)";
4911
- const argList = classNode.children.find(
4912
- (c) => c.type === "argument_list"
4913
- );
4914
- const bases = argList ? argList.children.filter((c) => c.type === "identifier" || c.type === "attribute").map((c) => c.text) : [];
4915
- const decorators = [];
4916
- if (decoratedParent) {
4917
- for (const child of decoratedParent.children) {
4918
- if (child.type === "decorator") {
4919
- decorators.push(child.text.replace("@", ""));
4920
- }
5000
+ function buildSuppressMap(content) {
5001
+ const entries = parseSuppressDirectives(content);
5002
+ const isSuppressed = buildSuppressChecker(entries);
5003
+ return { entries, isSuppressed };
5004
+ }
5005
+ function loadIgnoreFile(rootDir) {
5006
+ const ignorePath = join3(rootDir, ".deep-slop", ".deep-slop-ignore");
5007
+ try {
5008
+ const content = readFileSync2(ignorePath, "utf-8");
5009
+ return content.split("\n").map((line) => line.split("#")[0].trim()).filter((line) => line && !line.startsWith("file:"));
5010
+ } catch {
5011
+ return [];
5012
+ }
5013
+ }
5014
+ function applySuppressDirectives(diagnostics, fileContents, globallySuppressed = /* @__PURE__ */ new Set()) {
5015
+ const suppressMaps = /* @__PURE__ */ new Map();
5016
+ for (const [filePath, content] of fileContents) {
5017
+ suppressMaps.set(filePath, buildSuppressMap(content));
5018
+ }
5019
+ const filtered = [];
5020
+ let suppressedCount = 0;
5021
+ for (const diag8 of diagnostics) {
5022
+ if (globallySuppressed.has(diag8.rule)) {
5023
+ suppressedCount++;
5024
+ continue;
5025
+ }
5026
+ const map = suppressMaps.get(diag8.filePath);
5027
+ if (map && map.isSuppressed(diag8.line, diag8.rule)) {
5028
+ suppressedCount++;
5029
+ } else {
5030
+ filtered.push(diag8);
4921
5031
  }
4922
5032
  }
4923
- const body = classNode.children.find(
4924
- (c) => c.type === "block"
4925
- );
4926
- const methods = [];
4927
- if (body) {
4928
- for (const child of body.children) {
4929
- if (child.type === "function_definition") {
4930
- methods.push(extractPythonFunctionInfo(child));
4931
- } else if (child.type === "decorated_definition") {
4932
- const inner = child.children.find(
4933
- (c) => c.type === "function_definition"
4934
- );
4935
- if (inner) {
4936
- methods.push(extractPythonFunctionInfo(inner, child));
4937
- }
4938
- }
4939
- }
5033
+ return { filtered, suppressedCount };
5034
+ }
5035
+ var init_suppress = __esm({
5036
+ "src/utils/suppress.ts"() {
5037
+ "use strict";
4940
5038
  }
4941
- return {
4942
- name,
4943
- bases,
4944
- decorators,
4945
- methods,
4946
- line: classNode.startRow + 1,
4947
- endLine: classNode.endRow + 1,
4948
- text: classNode.text
4949
- };
5039
+ });
5040
+
5041
+ // src/utils/file-utils.ts
5042
+ import { readFile as readFile2 } from "node:fs/promises";
5043
+ async function readFileContent(filePath) {
5044
+ const buffer = await readFile2(filePath);
5045
+ let content = buffer.toString("utf-8");
5046
+ if (content.charCodeAt(0) === 65279) {
5047
+ content = content.slice(1);
5048
+ }
5049
+ return content;
4950
5050
  }
4951
- function extractPythonImportInfo(importNode) {
4952
- const isFromImport = importNode.type === "import_from_statement";
4953
- let module = "";
4954
- const symbols = [];
4955
- if (isFromImport) {
4956
- const moduleNode = importNode.children.find(
4957
- (c) => c.fieldName === "module_name" || c.type === "dotted_name" && c.fieldName !== "name" || c.type === "identifier" && c.fieldName !== "name"
4958
- );
4959
- module = moduleNode?.text ?? "";
4960
- const nameList = importNode.children.find(
4961
- (c) => c.type === "dotted_name" && c !== moduleNode
4962
- );
4963
- const identifierChildren = importNode.children.filter(
4964
- (c) => c.type === "identifier" && c !== moduleNode
4965
- );
4966
- if (nameList) {
4967
- symbols.push(nameList.text);
4968
- }
4969
- for (const id of identifierChildren) {
4970
- if (id.text !== "from" && id.text !== "import" && id.text !== module) {
4971
- symbols.push(id.text);
5051
+ function detectEncodingAnomalies(content) {
5052
+ const hasBom = content.charCodeAt(0) === 65279;
5053
+ const hasCrlf = content.includes("\r\n");
5054
+ const hasZwnbsp = content.includes("\uFEFF");
5055
+ const lfOnly = (content.match(/(?<!\r)\n/g) ?? []).length;
5056
+ const crlfCount = (content.match(/\r\n/g) ?? []).length;
5057
+ const lineEnding = crlfCount > 0 && lfOnly > crlfCount ? "mixed" : crlfCount > 0 ? "crlf" : "lf";
5058
+ return { hasBom, hasCrlf, hasZwnbsp, lineEnding };
5059
+ }
5060
+ function toLines(content) {
5061
+ return content.split("\n").map((text, i) => ({ num: i + 1, text }));
5062
+ }
5063
+ function extractImports(content, language) {
5064
+ const imports = [];
5065
+ const lines = toLines(content);
5066
+ for (const { num, text } of lines) {
5067
+ const trimmed = text.trim();
5068
+ if (language === "typescript" || language === "javascript") {
5069
+ const jsMatch = trimmed.match(
5070
+ /^import\s+(?:type\s+)?(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+['"]([^'"]+)['"]/
5071
+ );
5072
+ if (jsMatch) {
5073
+ imports.push({
5074
+ line: num,
5075
+ source: jsMatch[1],
5076
+ raw: trimmed,
5077
+ isTypeOnly: trimmed.includes("import type"),
5078
+ isDefault: !trimmed.includes("{")
5079
+ });
5080
+ }
5081
+ const dynMatch = trimmed.match(/import\s*\(\s*['"]([^'"]+)['"]\s*\)/);
5082
+ if (dynMatch) {
5083
+ imports.push({
5084
+ line: num,
5085
+ source: dynMatch[1],
5086
+ raw: trimmed,
5087
+ isTypeOnly: false,
5088
+ isDynamic: true
5089
+ });
5090
+ }
5091
+ const reqMatch = trimmed.match(/(?:const|let|var)\s+[^=]*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
5092
+ if (reqMatch) {
5093
+ imports.push({
5094
+ line: num,
5095
+ source: reqMatch[1],
5096
+ raw: trimmed,
5097
+ isTypeOnly: false,
5098
+ isRequire: true
5099
+ });
4972
5100
  }
4973
5101
  }
4974
- } else {
4975
- const dottedNames = importNode.children.filter(
4976
- (c) => c.type === "dotted_name"
4977
- );
4978
- const identifiers = importNode.children.filter(
4979
- (c) => c.type === "identifier"
4980
- );
4981
- for (const dn of dottedNames) {
4982
- symbols.push(dn.text);
4983
- }
4984
- for (const id of identifiers) {
4985
- if (id.text !== "import") {
4986
- symbols.push(id.text);
5102
+ if (language === "python") {
5103
+ const pyMatch = trimmed.match(/^from\s+([^\s]+)\s+import/);
5104
+ if (pyMatch) {
5105
+ imports.push({ line: num, source: pyMatch[1], raw: trimmed, isTypeOnly: false });
5106
+ }
5107
+ const pyImport = trimmed.match(/^import\s+([^\s]+)/);
5108
+ if (pyImport) {
5109
+ imports.push({ line: num, source: pyImport[1], raw: trimmed, isTypeOnly: false });
4987
5110
  }
4988
5111
  }
4989
- module = symbols[0] ?? "";
4990
- }
4991
- return {
4992
- module,
4993
- symbols,
4994
- isFromImport,
4995
- line: importNode.startRow + 1,
4996
- text: importNode.text
4997
- };
4998
- }
4999
- function convertNode(node, parent) {
5000
- const children = [];
5001
- const astNode = {
5002
- type: node.type,
5003
- text: node.text,
5004
- startRow: node.startPosition.row,
5005
- startCol: node.startPosition.column,
5006
- endRow: node.endPosition.row,
5007
- endCol: node.endPosition.column,
5008
- children,
5009
- parent,
5010
- fieldName: null
5011
- };
5012
- for (let i = 0; i < node.childCount; i++) {
5013
- const child = node.child(i);
5014
- if (child) {
5015
- const converted = convertNode(child, astNode);
5016
- converted.fieldName = node.fieldNameForChild(i) ?? null;
5017
- children.push(converted);
5112
+ if (language === "go") {
5113
+ const goMatch = trimmed.match(/"([^"]+)"/);
5114
+ if (trimmed.startsWith("import") && goMatch) {
5115
+ imports.push({ line: num, source: goMatch[1], raw: trimmed, isTypeOnly: false });
5116
+ }
5018
5117
  }
5019
5118
  }
5020
- return astNode;
5119
+ return imports;
5021
5120
  }
5022
- var parserInstance, tsLang, tsxLang, initPromise, initDone, initOk, pyLang, pyInitDone, pyInitOk, goLang, goInitDone, goInitOk, rustLang, rustInitDone, rustInitOk, phpLang, phpInitDone, phpInitOk, csharpLang, csharpInitDone, csharpInitOk, swiftLang, swiftInitDone, swiftInitOk;
5023
- var init_tree_sitter = __esm({
5024
- "src/utils/tree-sitter.ts"() {
5121
+ var init_file_utils = __esm({
5122
+ "src/utils/file-utils.ts"() {
5025
5123
  "use strict";
5026
- parserInstance = null;
5027
- tsLang = null;
5028
- tsxLang = null;
5029
- initPromise = null;
5030
- initDone = false;
5031
- initOk = false;
5032
- pyLang = null;
5033
- pyInitDone = false;
5034
- pyInitOk = false;
5035
- goLang = null;
5036
- goInitDone = false;
5037
- goInitOk = false;
5038
- rustLang = null;
5039
- rustInitDone = false;
5040
- rustInitOk = false;
5041
- phpLang = null;
5042
- phpInitDone = false;
5043
- phpInitOk = false;
5044
- csharpLang = null;
5045
- csharpInitDone = false;
5046
- csharpInitOk = false;
5047
- swiftLang = null;
5048
- swiftInitDone = false;
5049
- swiftInitOk = false;
5050
5124
  }
5051
5125
  });
5052
5126
 
@@ -25862,6 +25936,7 @@ import { performance as performance3 } from "node:perf_hooks";
25862
25936
  async function runScan(context, callbacks) {
25863
25937
  const startTotal = performance3.now();
25864
25938
  clearFileCache();
25939
+ clearParseCache();
25865
25940
  if (context.files?.length) {
25866
25941
  await preloadFiles(context.files);
25867
25942
  }
@@ -26039,6 +26114,7 @@ var init_orchestrator = __esm({
26039
26114
  init_rule_overrides();
26040
26115
  init_store();
26041
26116
  init_file_cache();
26117
+ init_tree_sitter();
26042
26118
  init_registry();
26043
26119
  init_suppress();
26044
26120
  EXT_TO_LANG = {
@@ -29579,6 +29655,161 @@ var init_list = __esm({
29579
29655
  }
29580
29656
  });
29581
29657
 
29658
+ // src/config/json-schema.ts
29659
+ var json_schema_exports = {};
29660
+ __export(json_schema_exports, {
29661
+ generateJsonSchema: () => generateJsonSchema
29662
+ });
29663
+ function generateJsonSchema() {
29664
+ return {
29665
+ $schema: "http://json-schema.org/draft-07/schema#",
29666
+ title: "DeepSlopConfig",
29667
+ description: "Configuration for deep-slop \u2014 deep AI slop detection with 18 AST-powered engines",
29668
+ type: "object",
29669
+ properties: {
29670
+ engines: {
29671
+ type: "object",
29672
+ description: "Enable/disable individual analysis engines",
29673
+ additionalProperties: { type: "boolean" },
29674
+ properties: {
29675
+ "ast-slop": { type: "boolean", description: "AI slop pattern detection" },
29676
+ "import-intelligence": { type: "boolean", description: "Import optimization and barrel analysis" },
29677
+ "dead-flow": { type: "boolean", description: "Dead code and unreachable branch detection" },
29678
+ "type-safety": { type: "boolean", description: "TypeScript type safety analysis" },
29679
+ "syntax-deep": { type: "boolean", description: "Syntax anomaly detection" },
29680
+ "security-deep": { type: "boolean", description: "Security vulnerability scanning" },
29681
+ "arch-constraints": { type: "boolean", description: "Architecture constraint analysis" },
29682
+ "dup-detect": { type: "boolean", description: "Duplicate code detection" },
29683
+ "perf-hints": { type: "boolean", description: "Performance hints" },
29684
+ "i18n-lint": { type: "boolean", description: "Internationalization linting" },
29685
+ "config-lint": { type: "boolean", description: "Configuration validation" },
29686
+ "meta-quality": { type: "boolean", description: "Meta quality scoring and trend analysis" },
29687
+ "lint-external": { type: "boolean", description: "External linter integration (ruff, golangci-lint, clippy)" },
29688
+ "arch-rules": { type: "boolean", description: "User-defined architecture rules" },
29689
+ knip: { type: "boolean", description: "Unused dependency/export detection" },
29690
+ "format-lint": { type: "boolean", description: "Formatting consistency" },
29691
+ "framework-lint": { type: "boolean", description: "Framework-specific rules (Next.js, Tailwind)" },
29692
+ "markup-lint": { type: "boolean", description: "Markup & config quality (JSON, YAML, CSS, HTML, Markdown)" }
29693
+ }
29694
+ },
29695
+ exclude: {
29696
+ type: "array",
29697
+ items: { type: "string" },
29698
+ description: "Glob patterns to exclude from scanning",
29699
+ default: ["node_modules", "dist", "coverage", ".git"]
29700
+ },
29701
+ quality: {
29702
+ type: "object",
29703
+ description: "Quality thresholds",
29704
+ properties: {
29705
+ maxFunctionLoc: { type: "number", default: 50, description: "Max lines per function" },
29706
+ maxFileLoc: { type: "number", default: 300, description: "Max lines per file" },
29707
+ maxNesting: { type: "number", default: 4, description: "Max nesting depth" },
29708
+ maxParams: { type: "number", default: 5, description: "Max function parameters" },
29709
+ maxCyclomatic: { type: "number", default: 10, description: "Max cyclomatic complexity" },
29710
+ maxCoupling: { type: "number", default: 7, description: "Max coupling between modules" }
29711
+ },
29712
+ additionalProperties: true
29713
+ },
29714
+ security: {
29715
+ type: "object",
29716
+ description: "Security engine settings",
29717
+ properties: {
29718
+ audit: { type: "boolean", default: true, description: "Run npm audit" },
29719
+ auditTimeout: { type: "number", default: 3e4, description: "Audit timeout in ms" },
29720
+ owasp: { type: "boolean", default: true, description: "Enable OWASP checks" }
29721
+ },
29722
+ additionalProperties: true
29723
+ },
29724
+ imports: {
29725
+ type: "object",
29726
+ description: "Import intelligence settings",
29727
+ properties: {
29728
+ suggestAlternatives: { type: "boolean", default: true },
29729
+ optimizeBarrels: { type: "boolean", default: true },
29730
+ validateAliases: { type: "boolean", default: true },
29731
+ buildGraph: { type: "boolean", default: true },
29732
+ maxCircularDepth: { type: "number", default: 5 }
29733
+ },
29734
+ additionalProperties: true
29735
+ },
29736
+ types: {
29737
+ type: "object",
29738
+ description: "Type safety settings",
29739
+ properties: {
29740
+ flagAsAny: { type: "boolean", default: true },
29741
+ suggestTypes: { type: "boolean", default: true },
29742
+ flagDoubleAssertion: { type: "boolean", default: true }
29743
+ },
29744
+ additionalProperties: true
29745
+ },
29746
+ deadCode: {
29747
+ type: "object",
29748
+ description: "Dead code detection settings",
29749
+ properties: {
29750
+ unreachableBranches: { type: "boolean", default: true },
29751
+ unusedExports: { type: "boolean", default: true },
29752
+ unusedVariables: { type: "boolean", default: true }
29753
+ },
29754
+ additionalProperties: true
29755
+ },
29756
+ i18n: {
29757
+ type: "object",
29758
+ description: "Internationalization settings",
29759
+ properties: {
29760
+ hardcodedStrings: { type: "boolean", default: true },
29761
+ validateKeys: { type: "boolean", default: true }
29762
+ },
29763
+ additionalProperties: true
29764
+ },
29765
+ scoring: {
29766
+ type: "object",
29767
+ description: "Scoring configuration",
29768
+ properties: {
29769
+ mode: { type: "string", enum: ["logarithmic", "linear"], default: "logarithmic" },
29770
+ smoothing: { type: "number", default: 20 },
29771
+ maxPerRule: { type: "number", default: 40 }
29772
+ }
29773
+ },
29774
+ telemetry: {
29775
+ type: "object",
29776
+ description: "Telemetry settings",
29777
+ properties: {
29778
+ enabled: { type: "boolean", default: false }
29779
+ }
29780
+ },
29781
+ ci: {
29782
+ type: "object",
29783
+ description: "CI quality gate settings",
29784
+ properties: {
29785
+ failBelow: { type: "number", default: 70, description: "Minimum score to pass CI" },
29786
+ format: { type: "string", enum: ["json", "human", "sarif"], default: "json" },
29787
+ failOnErrors: { type: "boolean", default: true }
29788
+ }
29789
+ },
29790
+ rules: {
29791
+ type: "object",
29792
+ description: "Per-rule severity overrides (e.g. ast-slop/narrative-comment: off)",
29793
+ additionalProperties: {
29794
+ type: "string",
29795
+ enum: ["off", "info", "suggestion", "warning", "error"]
29796
+ }
29797
+ },
29798
+ extends: {
29799
+ type: "string",
29800
+ description: "Preset to extend (recommended, strict, minimal)",
29801
+ enum: ["recommended", "strict", "minimal"]
29802
+ }
29803
+ },
29804
+ additionalProperties: false
29805
+ };
29806
+ }
29807
+ var init_json_schema = __esm({
29808
+ "src/config/json-schema.ts"() {
29809
+ "use strict";
29810
+ }
29811
+ });
29812
+
29582
29813
  // node_modules/.pnpm/commander@14.0.3/node_modules/commander/esm.mjs
29583
29814
  var import_index = __toESM(require_commander(), 1);
29584
29815
  var {
@@ -29676,7 +29907,7 @@ function computeExitCode(options) {
29676
29907
  init_formatter();
29677
29908
 
29678
29909
  // src/version.ts
29679
- var APP_VERSION = "1.4.1";
29910
+ var APP_VERSION = "1.6.0";
29680
29911
 
29681
29912
  // src/output/sarif.ts
29682
29913
  function mapSeverity4(sev) {
@@ -32989,6 +33220,20 @@ program2.command("badge").description("Generate a shields.io badge for your deep
32989
33220
  console.log("");
32990
33221
  }
32991
33222
  });
33223
+ program2.command("schema").description("Output JSON Schema for .deep-slop/config.yml (for IDE autocomplete)").option("--output <path>", "Write schema to file instead of stdout").action(async (opts) => {
33224
+ const { generateJsonSchema: generateJsonSchema2 } = await Promise.resolve().then(() => (init_json_schema(), json_schema_exports));
33225
+ const schema = generateJsonSchema2();
33226
+ if (opts.output) {
33227
+ const { writeFileSync: writeFileSync11 } = await import("node:fs");
33228
+ const { resolve: resolvePath } = await import("node:path");
33229
+ const outPath = resolvePath(opts.output);
33230
+ writeFileSync11(outPath, JSON.stringify(schema, null, 2) + "\n");
33231
+ process.stderr.write(` \u2714 Schema written to ${outPath}
33232
+ `);
33233
+ } else {
33234
+ console.log(JSON.stringify(schema, null, 2));
33235
+ }
33236
+ });
32992
33237
  program2.command("discover").description("Analyze project: languages, frameworks, package manager, linters, tests, CI").argument("[path]", "project directory", ".").option("--json", "Output as JSON").action(async (path2, opts) => {
32993
33238
  const rootDir = resolve13(path2);
32994
33239
  process.stderr.write(`