truecourse 0.5.7 → 0.5.8-windows.2

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.
package/cli.mjs CHANGED
@@ -3976,7 +3976,7 @@ var init_paths = __esm({
3976
3976
  "packages/core/dist/config/paths.js"() {
3977
3977
  "use strict";
3978
3978
  TRUECOURSE_DIR = ".truecourse";
3979
- GITIGNORE_CONTENTS = "analyses/\nLATEST.json\nhistory.json\ndiff.json\nui-state.json\nlogs/\n.analyze.lock\n";
3979
+ GITIGNORE_CONTENTS = "analyses/\nhistory.json\ndiff.json\nui-state.json\nlogs/\n.analyze.lock\n";
3980
3980
  }
3981
3981
  });
3982
3982
 
@@ -4077,15 +4077,17 @@ __export(helpers_exports, {
4077
4077
  requireRegisteredRepo: () => requireRegisteredRepo,
4078
4078
  severityColor: () => severityColor,
4079
4079
  severityIcon: () => severityIcon,
4080
+ syncShippedSkills: () => syncShippedSkills,
4080
4081
  writeConfig: () => writeConfig
4081
4082
  });
4082
4083
  import { exec as exec2 } from "node:child_process";
4083
- import { cpSync, existsSync, mkdirSync, readdirSync } from "node:fs";
4084
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4084
4085
  import fs3 from "node:fs";
4085
4086
  import os2 from "node:os";
4086
4087
  import path3 from "node:path";
4087
4088
  import { dirname, resolve } from "node:path";
4088
4089
  import { fileURLToPath } from "node:url";
4090
+ import { createHash } from "node:crypto";
4089
4091
  function getConfigPath() {
4090
4092
  return path3.join(os2.homedir(), ".truecourse", "config.json");
4091
4093
  }
@@ -4352,6 +4354,31 @@ function computeMissingSkills(repoPath) {
4352
4354
  function hasInstalledSkills(repoPath) {
4353
4355
  return computeMissingSkills(repoPath).length === 0;
4354
4356
  }
4357
+ function skillFilePath(parent, name) {
4358
+ return resolve(parent, name, "SKILL.md");
4359
+ }
4360
+ function sha256OfFile(filePath) {
4361
+ if (!existsSync(filePath)) return null;
4362
+ return createHash("sha256").update(readFileSync(filePath)).digest("hex");
4363
+ }
4364
+ function skillsLockPath(repoPath) {
4365
+ return resolve(skillsParentDir(repoPath), SKILLS_LOCKFILE_NAME);
4366
+ }
4367
+ function readSkillsLock(repoPath) {
4368
+ const lockPath2 = skillsLockPath(repoPath);
4369
+ if (!existsSync(lockPath2)) return {};
4370
+ try {
4371
+ const parsed = JSON.parse(readFileSync(lockPath2, "utf-8"));
4372
+ return parsed && typeof parsed === "object" ? parsed : {};
4373
+ } catch {
4374
+ return {};
4375
+ }
4376
+ }
4377
+ function writeSkillsLock(repoPath, lock) {
4378
+ const lockPath2 = skillsLockPath(repoPath);
4379
+ mkdirSync(dirname(lockPath2), { recursive: true });
4380
+ writeFileSync(lockPath2, JSON.stringify(lock, null, 2) + "\n");
4381
+ }
4355
4382
  function copySkills(repoPath, skillNames) {
4356
4383
  const src = resolveSkillsSrcDir();
4357
4384
  if (!src) {
@@ -4360,25 +4387,97 @@ function copySkills(repoPath, skillNames) {
4360
4387
  }
4361
4388
  const parent = skillsParentDir(repoPath);
4362
4389
  mkdirSync(parent, { recursive: true });
4390
+ const lock = readSkillsLock(repoPath);
4363
4391
  for (const name of skillNames) {
4364
4392
  const skillSrc = resolve(src, name);
4365
4393
  const skillDest = resolve(parent, name);
4366
4394
  if (existsSync(skillDest)) continue;
4367
4395
  cpSync(skillSrc, skillDest, { recursive: true });
4396
+ const sha = sha256OfFile(skillFilePath(parent, name));
4397
+ if (sha) lock[name] = sha;
4368
4398
  }
4399
+ writeSkillsLock(repoPath, lock);
4369
4400
  O2.success(
4370
4401
  `Installed ${skillNames.length} Claude Code skill${skillNames.length === 1 ? "" : "s"}:`
4371
4402
  );
4372
4403
  for (const name of skillNames) O2.message(` - ${name}`);
4373
4404
  }
4405
+ function syncShippedSkills(repoPath, srcDirOverride) {
4406
+ const src = srcDirOverride ?? resolveSkillsSrcDir();
4407
+ if (!src) return;
4408
+ const parent = skillsParentDir(repoPath);
4409
+ if (!existsSync(parent)) return;
4410
+ const shipped = listSkillDirs(src);
4411
+ const lock = readSkillsLock(repoPath);
4412
+ const updated = [];
4413
+ const migrated = [];
4414
+ const customized = [];
4415
+ let lockChanged = false;
4416
+ const overwriteWithShipped = (name, shippedSha) => {
4417
+ const skillSrc = resolve(src, name);
4418
+ const skillDest = resolve(parent, name);
4419
+ rmSync(skillDest, { recursive: true, force: true });
4420
+ cpSync(skillSrc, skillDest, { recursive: true });
4421
+ lock[name] = shippedSha;
4422
+ lockChanged = true;
4423
+ };
4424
+ for (const name of shipped) {
4425
+ const skillSrcFile = skillFilePath(src, name);
4426
+ const skillDestFile = skillFilePath(parent, name);
4427
+ if (!existsSync(skillDestFile)) continue;
4428
+ const shippedSha = sha256OfFile(skillSrcFile);
4429
+ const installedSha = sha256OfFile(skillDestFile);
4430
+ if (!shippedSha || !installedSha) continue;
4431
+ if (shippedSha === installedSha) {
4432
+ if (lock[name] !== shippedSha) {
4433
+ lock[name] = shippedSha;
4434
+ lockChanged = true;
4435
+ }
4436
+ continue;
4437
+ }
4438
+ const recordedSha = lock[name];
4439
+ if (!recordedSha) {
4440
+ overwriteWithShipped(name, shippedSha);
4441
+ migrated.push(name);
4442
+ continue;
4443
+ }
4444
+ if (installedSha === recordedSha) {
4445
+ overwriteWithShipped(name, shippedSha);
4446
+ updated.push(name);
4447
+ } else {
4448
+ customized.push(name);
4449
+ }
4450
+ }
4451
+ if (lockChanged) writeSkillsLock(repoPath, lock);
4452
+ if (updated.length > 0) {
4453
+ O2.info(
4454
+ `Updated ${updated.length} Claude Code skill${updated.length === 1 ? "" : "s"} to the current version: ${updated.join(", ")}`
4455
+ );
4456
+ }
4457
+ if (migrated.length > 0) {
4458
+ const word = migrated.length === 1 ? "skill" : "skills";
4459
+ O2.info(
4460
+ `Migrated ${migrated.length} pre-existing Claude Code ${word} to the current shipped version: ${migrated.join(", ")}.
4461
+ If you had local edits, they're recoverable from git history. Future upgrades will preserve customizations automatically.`
4462
+ );
4463
+ }
4464
+ if (customized.length > 0) {
4465
+ const word = customized.length === 1 ? "skill has" : "skills have";
4466
+ O2.warn(
4467
+ `Claude Code ${word} local edits and a newer version is available \u2014 keeping yours: ${customized.join(", ")}.
4468
+ To take the new version, delete the skill folder under .claude/skills/ and re-run truecourse.`
4469
+ );
4470
+ }
4471
+ }
4374
4472
  async function promptInstallSkills(repoPath, { install } = {}) {
4473
+ if (install === false) return;
4474
+ syncShippedSkills(repoPath);
4375
4475
  const missing = computeMissingSkills(repoPath);
4376
4476
  if (missing.length === 0) return;
4377
4477
  if (install === true) {
4378
4478
  copySkills(repoPath, missing);
4379
4479
  return;
4380
4480
  }
4381
- if (install === false) return;
4382
4481
  if (!isInteractive()) return;
4383
4482
  const src = resolveSkillsSrcDir();
4384
4483
  const shipped = src ? listSkillDirs(src) : [];
@@ -4389,7 +4488,7 @@ async function promptInstallSkills(repoPath, { install } = {}) {
4389
4488
  if (q(answer) || !answer) return;
4390
4489
  copySkills(repoPath, missing);
4391
4490
  }
4392
- var DEFAULT_PORT, DEFAULT_CONFIG;
4491
+ var DEFAULT_PORT, DEFAULT_CONFIG, SKILLS_LOCKFILE_NAME;
4393
4492
  var init_helpers = __esm({
4394
4493
  "tools/cli/src/commands/helpers.ts"() {
4395
4494
  "use strict";
@@ -4398,6 +4497,7 @@ var init_helpers = __esm({
4398
4497
  init_registry();
4399
4498
  DEFAULT_PORT = 3001;
4400
4499
  DEFAULT_CONFIG = { runMode: "console" };
4500
+ SKILLS_LOCKFILE_NAME = ".truecourse-skills.json";
4401
4501
  }
4402
4502
  });
4403
4503
 
@@ -10542,7 +10642,12 @@ var init_language_config = __esm({
10542
10642
  classQuery: `
10543
10643
  (class_declaration) @class
10544
10644
  (class) @class
10545
- `
10645
+ `,
10646
+ // Skip pre-built/minified bundles. They have no analytical value (build
10647
+ // artifacts of source already in the repo or vendored libs) and a single
10648
+ // ~1MB minified line can blow past V8's max string length when the LLM
10649
+ // context router extracts thousands of arrow_function bodies from it.
10650
+ ignorePatterns: ["**/*.min.js", "**/*.min.cjs", "**/*.min.mjs"]
10546
10651
  };
10547
10652
  PYTHON_CONFIG = {
10548
10653
  name: "python",
@@ -10585,9 +10690,55 @@ var init_language_config = __esm({
10585
10690
  });
10586
10691
 
10587
10692
  // packages/analyzer/dist/file-discovery.js
10588
- import { existsSync as existsSync2, readFileSync, readdirSync as readdirSync2, statSync } from "fs";
10693
+ import { existsSync as existsSync2, openSync, readFileSync as readFileSync2, readSync, readdirSync as readdirSync2, statSync, closeSync } from "fs";
10589
10694
  import { execFileSync } from "child_process";
10590
10695
  import { join, relative, resolve as resolve2 } from "path";
10696
+ function looksLikeBuildArtifact(absPath) {
10697
+ const slash = absPath.lastIndexOf("/");
10698
+ const basename2 = slash >= 0 ? absPath.slice(slash + 1) : absPath;
10699
+ return /\.(bundle|production|development)\.(js|cjs|mjs|jsx|ts|tsx|mts|cts)$/i.test(basename2);
10700
+ }
10701
+ function looksMinified(absPath) {
10702
+ const dotIdx = absPath.lastIndexOf(".");
10703
+ if (dotIdx < 0)
10704
+ return false;
10705
+ const ext2 = absPath.slice(dotIdx);
10706
+ if (!SNIFFED_EXTS.has(ext2))
10707
+ return false;
10708
+ if (looksLikeBuildArtifact(absPath))
10709
+ return true;
10710
+ let size;
10711
+ try {
10712
+ size = statSync(absPath).size;
10713
+ } catch {
10714
+ return false;
10715
+ }
10716
+ if (size < MIN_SIZE_FOR_SNIFF)
10717
+ return false;
10718
+ let fd = null;
10719
+ try {
10720
+ fd = openSync(absPath, "r");
10721
+ const buf = Buffer.alloc(SNIFF_BYTES);
10722
+ const read = readSync(fd, buf, 0, SNIFF_BYTES, 0);
10723
+ let newlines = 0;
10724
+ for (let i = 0; i < read; i++) {
10725
+ if (buf[i] === 10)
10726
+ newlines++;
10727
+ if (newlines >= MIN_NEWLINES)
10728
+ return false;
10729
+ }
10730
+ return true;
10731
+ } catch {
10732
+ return false;
10733
+ } finally {
10734
+ if (fd !== null) {
10735
+ try {
10736
+ closeSync(fd);
10737
+ } catch {
10738
+ }
10739
+ }
10740
+ }
10741
+ }
10591
10742
  function isInsideGitWorkTree(dir) {
10592
10743
  try {
10593
10744
  const out = execFileSync("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], {
@@ -10602,7 +10753,7 @@ function buildPostGitFilter(dir) {
10602
10753
  const ig = (0, import_ignore.default)();
10603
10754
  const tcPath = join(dir, ".truecourseignore");
10604
10755
  if (existsSync2(tcPath))
10605
- ig.add(readFileSync(tcPath, "utf8"));
10756
+ ig.add(readFileSync2(tcPath, "utf8"));
10606
10757
  ig.add(".git");
10607
10758
  for (const p2 of getAllTestPatterns())
10608
10759
  ig.add(p2);
@@ -10635,8 +10786,11 @@ function discoverFilesViaGit(dir) {
10635
10786
  }
10636
10787
  if (!isFile)
10637
10788
  continue;
10638
- if (detectLanguage(abs))
10639
- out.push(abs);
10789
+ if (!detectLanguage(abs))
10790
+ continue;
10791
+ if (looksMinified(abs))
10792
+ continue;
10793
+ out.push(abs);
10640
10794
  }
10641
10795
  return out;
10642
10796
  }
@@ -10694,11 +10848,11 @@ function loadIgnorePatterns(baseDir) {
10694
10848
  const gitignores = findAllGitignores(baseDir);
10695
10849
  const rootDir = gitignores.length > 0 && gitignores[0] ? gitignores[0].dir : baseDir;
10696
10850
  for (const { path: gitignorePath } of gitignores) {
10697
- ig.add(readFileSync(gitignorePath, "utf8"));
10851
+ ig.add(readFileSync2(gitignorePath, "utf8"));
10698
10852
  }
10699
10853
  const truecourseignorePath = join(baseDir, ".truecourseignore");
10700
10854
  if (existsSync2(truecourseignorePath)) {
10701
- const content = readFileSync(truecourseignorePath, "utf8");
10855
+ const content = readFileSync2(truecourseignorePath, "utf8");
10702
10856
  const prefix = relative(rootDir, baseDir).replace(/\\/g, "/");
10703
10857
  ig.add(reanchorTruecourseignore(content, prefix));
10704
10858
  }
@@ -10725,8 +10879,11 @@ function discoverFilesViaWalker(dir) {
10725
10879
  if (stat.isDirectory()) {
10726
10880
  traverse(fullPath);
10727
10881
  } else if (stat.isFile()) {
10728
- if (detectLanguage(fullPath))
10729
- files.push(fullPath);
10882
+ if (!detectLanguage(fullPath))
10883
+ continue;
10884
+ if (looksMinified(fullPath))
10885
+ continue;
10886
+ files.push(fullPath);
10730
10887
  }
10731
10888
  }
10732
10889
  } catch {
@@ -10741,12 +10898,16 @@ function discoverFiles(dir) {
10741
10898
  return viaGit;
10742
10899
  return discoverFilesViaWalker(dir);
10743
10900
  }
10744
- var import_ignore;
10901
+ var import_ignore, SNIFFED_EXTS, MIN_SIZE_FOR_SNIFF, SNIFF_BYTES, MIN_NEWLINES;
10745
10902
  var init_file_discovery = __esm({
10746
10903
  "packages/analyzer/dist/file-discovery.js"() {
10747
10904
  "use strict";
10748
10905
  import_ignore = __toESM(require_ignore(), 1);
10749
10906
  init_language_config();
10907
+ SNIFFED_EXTS = /* @__PURE__ */ new Set([".js", ".jsx", ".cjs", ".mjs", ".ts", ".tsx", ".mts", ".cts"]);
10908
+ MIN_SIZE_FOR_SNIFF = 15 * 1024;
10909
+ SNIFF_BYTES = 8 * 1024;
10910
+ MIN_NEWLINES = 4;
10750
10911
  }
10751
10912
  });
10752
10913
 
@@ -12516,6 +12677,15 @@ var init_typescript = __esm({
12516
12677
 
12517
12678
  // packages/analyzer/dist/extractors/languages/javascript.js
12518
12679
  import { Query as Query3 } from "web-tree-sitter";
12680
+ function isNestedInFunction2(node) {
12681
+ let current = node.parent;
12682
+ while (current) {
12683
+ if (FUNCTION_NODE_TYPES2.has(current.type))
12684
+ return true;
12685
+ current = current.parent;
12686
+ }
12687
+ return false;
12688
+ }
12519
12689
  function extractFunctionName2(node) {
12520
12690
  const nameNode = node.childForFieldName("name");
12521
12691
  if (nameNode?.text)
@@ -12523,6 +12693,8 @@ function extractFunctionName2(node) {
12523
12693
  const parent = node.parent;
12524
12694
  if (!parent)
12525
12695
  return "anonymous";
12696
+ if (isNestedInFunction2(node))
12697
+ return "anonymous";
12526
12698
  if (parent.type === "variable_declarator") {
12527
12699
  const varName = parent.childForFieldName("name");
12528
12700
  if (varName?.text)
@@ -12541,25 +12713,15 @@ function extractFunctionName2(node) {
12541
12713
  return prop.text;
12542
12714
  }
12543
12715
  }
12544
- if (parent.type === "arguments") {
12545
- const callNode = parent.parent;
12546
- if (callNode?.type === "call_expression") {
12547
- const callee = callNode.childForFieldName("function");
12548
- if (callee?.type === "member_expression") {
12549
- const method = callee.childForFieldName("property");
12550
- if (method?.text)
12551
- return `${method.text}_handler`;
12552
- }
12553
- }
12554
- }
12555
12716
  return "anonymous";
12556
12717
  }
12557
12718
  function isExported2(node) {
12558
12719
  let current = node.parent;
12559
12720
  while (current) {
12560
- if (current.type === "export_statement") {
12721
+ if (current.type === "export_statement")
12561
12722
  return true;
12562
- }
12723
+ if (FUNCTION_NODE_TYPES2.has(current.type))
12724
+ return false;
12563
12725
  current = current.parent;
12564
12726
  }
12565
12727
  return false;
@@ -12904,12 +13066,22 @@ function extractJavaScriptExports(tree, _filePath) {
12904
13066
  }
12905
13067
  return exports;
12906
13068
  }
13069
+ var FUNCTION_NODE_TYPES2;
12907
13070
  var init_javascript = __esm({
12908
13071
  "packages/analyzer/dist/extractors/languages/javascript.js"() {
12909
13072
  "use strict";
12910
13073
  init_language_config();
12911
13074
  init_parser();
12912
13075
  init_common();
13076
+ FUNCTION_NODE_TYPES2 = /* @__PURE__ */ new Set([
13077
+ "function_declaration",
13078
+ "function",
13079
+ "function_expression",
13080
+ "arrow_function",
13081
+ "generator_function_declaration",
13082
+ "generator_function",
13083
+ "method_definition"
13084
+ ]);
12913
13085
  }
12914
13086
  });
12915
13087
 
@@ -12942,7 +13114,7 @@ function computePythonFunctionMetrics(node) {
12942
13114
  walkNesting(bodyNode, 0);
12943
13115
  return { lineCount, statementCount, maxNestingDepth: maxNestingDepth2 };
12944
13116
  }
12945
- function isNestedInFunction2(node) {
13117
+ function isNestedInFunction3(node) {
12946
13118
  let current = node.parent;
12947
13119
  while (current) {
12948
13120
  if (current.type === "function_definition")
@@ -13009,7 +13181,7 @@ function extractPythonFunctions(tree, filePath) {
13009
13181
  const name = node.childForFieldName("name")?.text;
13010
13182
  if (!name)
13011
13183
  return;
13012
- if (isNestedInFunction2(node))
13184
+ if (isNestedInFunction3(node))
13013
13185
  return;
13014
13186
  const paramsNode = node.childForFieldName("parameters");
13015
13187
  const params = extractPythonParameters(paramsNode);
@@ -13645,7 +13817,7 @@ var init_registry2 = __esm({
13645
13817
 
13646
13818
  // packages/analyzer/dist/dependency-graph.js
13647
13819
  import { resolve as resolve4, dirname as dirname4, join as join3 } from "path";
13648
- import { existsSync as existsSync4, readFileSync as readFileSync2, readdirSync as readdirSync4, realpathSync, statSync as statSync3 } from "fs";
13820
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync4, realpathSync, statSync as statSync3 } from "fs";
13649
13821
  function resolveRelativeFallback(importSource, containingFile, analyzedFiles, extensions, indexFiles) {
13650
13822
  const fromDir = dirname4(containingFile);
13651
13823
  const basePath = resolve4(fromDir, importSource);
@@ -13679,7 +13851,7 @@ function buildWorkspacePackageMap(rootPath) {
13679
13851
  if (!existsSync4(pkgJsonPath))
13680
13852
  continue;
13681
13853
  try {
13682
- const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf-8"));
13854
+ const pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf-8"));
13683
13855
  if (pkg.name)
13684
13856
  packageMap.set(pkg.name, pkgDir);
13685
13857
  } catch {
@@ -13710,7 +13882,7 @@ function resolveWorkspaceFallback(importSource, workspacePackages, analyzedFiles
13710
13882
  const pkgJsonPath = join3(pkgDir, "package.json");
13711
13883
  if (existsSync4(pkgJsonPath)) {
13712
13884
  try {
13713
- const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf-8"));
13885
+ const pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf-8"));
13714
13886
  const mainField = pkg.main || pkg.module;
13715
13887
  if (mainField) {
13716
13888
  const mainPath = resolve4(pkgDir, mainField);
@@ -16247,7 +16419,7 @@ var init_layer_detector = __esm({
16247
16419
  });
16248
16420
 
16249
16421
  // packages/analyzer/dist/service-detectors/javascript.js
16250
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
16422
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
16251
16423
  import { join as join4 } from "path";
16252
16424
  var jsServiceDetector;
16253
16425
  var init_javascript2 = __esm({
@@ -16259,7 +16431,7 @@ var init_javascript2 = __esm({
16259
16431
  if (!existsSync5(packageJsonPath))
16260
16432
  return [];
16261
16433
  try {
16262
- const pkg = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
16434
+ const pkg = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
16263
16435
  return Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
16264
16436
  } catch {
16265
16437
  return [];
@@ -16270,7 +16442,7 @@ var init_javascript2 = __esm({
16270
16442
  if (!existsSync5(packageJsonPath))
16271
16443
  return false;
16272
16444
  try {
16273
- const pkg = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
16445
+ const pkg = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
16274
16446
  const libraryIndicators = ["main", "module", "exports", "types", "typings"];
16275
16447
  return libraryIndicators.some((indicator) => pkg[indicator]);
16276
16448
  } catch {
@@ -16282,7 +16454,7 @@ var init_javascript2 = __esm({
16282
16454
  });
16283
16455
 
16284
16456
  // packages/analyzer/dist/service-detectors/python.js
16285
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
16457
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
16286
16458
  import { join as join5 } from "path";
16287
16459
  var pythonServiceDetector;
16288
16460
  var init_python4 = __esm({
@@ -16294,7 +16466,7 @@ var init_python4 = __esm({
16294
16466
  const reqPath = join5(servicePath, "requirements.txt");
16295
16467
  if (existsSync6(reqPath)) {
16296
16468
  try {
16297
- const content = readFileSync4(reqPath, "utf-8");
16469
+ const content = readFileSync5(reqPath, "utf-8");
16298
16470
  for (const line of content.split("\n")) {
16299
16471
  const trimmed2 = line.trim();
16300
16472
  if (!trimmed2 || trimmed2.startsWith("#") || trimmed2.startsWith("-"))
@@ -16309,7 +16481,7 @@ var init_python4 = __esm({
16309
16481
  const pyprojectPath = join5(servicePath, "pyproject.toml");
16310
16482
  if (existsSync6(pyprojectPath)) {
16311
16483
  try {
16312
- const content = readFileSync4(pyprojectPath, "utf-8");
16484
+ const content = readFileSync5(pyprojectPath, "utf-8");
16313
16485
  const depsMatch = content.match(/dependencies\s*=\s*\[([\s\S]*?)\]/m);
16314
16486
  if (depsMatch) {
16315
16487
  const depMatches = depsMatch[1].matchAll(/"([^"]+)"|'([^']+)'/g);
@@ -16361,7 +16533,7 @@ var init_registry3 = __esm({
16361
16533
  });
16362
16534
 
16363
16535
  // packages/analyzer/dist/service-detector.js
16364
- import { existsSync as existsSync7, readFileSync as readFileSync5, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
16536
+ import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
16365
16537
  import { join as join6, basename, dirname as dirname5 } from "path";
16366
16538
  function detectServices(rootPath, allFiles) {
16367
16539
  const monorepoServices = detectMonorepoServices(rootPath, allFiles);
@@ -16495,7 +16667,7 @@ function detectDockerComposeServices(rootPath, allFiles) {
16495
16667
  return [];
16496
16668
  }
16497
16669
  try {
16498
- const content = readFileSync5(composePath2, "utf-8");
16670
+ const content = readFileSync6(composePath2, "utf-8");
16499
16671
  const services = [];
16500
16672
  const lines = content.split("\n");
16501
16673
  let inServices = false;
@@ -16533,7 +16705,7 @@ function detectServiceType(servicePath, files) {
16533
16705
  const hasPackageJson = existsSync7(join6(servicePath, "package.json"));
16534
16706
  if (hasPackageJson) {
16535
16707
  try {
16536
- const pkg = JSON.parse(readFileSync5(join6(servicePath, "package.json"), "utf-8"));
16708
+ const pkg = JSON.parse(readFileSync6(join6(servicePath, "package.json"), "utf-8"));
16537
16709
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
16538
16710
  const isFrontend = patterns.frontend.frameworks.some((fw) => deps[fw]);
16539
16711
  if (isFrontend) {
@@ -16562,7 +16734,7 @@ function detectFramework(servicePath) {
16562
16734
  const packageJsonPath = join6(servicePath, "package.json");
16563
16735
  if (existsSync7(packageJsonPath)) {
16564
16736
  try {
16565
- const pkg = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
16737
+ const pkg = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
16566
16738
  const deps2 = { ...pkg.dependencies, ...pkg.devDependencies };
16567
16739
  for (const fw of patterns.metaFrameworks) {
16568
16740
  if (deps2[fw]) {
@@ -16600,7 +16772,7 @@ function getServiceName(servicePath) {
16600
16772
  const packageJsonPath = join6(servicePath, "package.json");
16601
16773
  if (existsSync7(packageJsonPath)) {
16602
16774
  try {
16603
- const pkg = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
16775
+ const pkg = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
16604
16776
  if (pkg.name) {
16605
16777
  return pkg.name;
16606
16778
  }
@@ -17162,7 +17334,7 @@ var init_registry4 = __esm({
17162
17334
  });
17163
17335
 
17164
17336
  // packages/analyzer/dist/database-detector.js
17165
- import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync6 } from "fs";
17337
+ import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync6 } from "fs";
17166
17338
  import { join as join7, resolve as resolve5 } from "path";
17167
17339
  function detectDatabases(rootPath, analyses, services) {
17168
17340
  const detections = [];
@@ -17199,7 +17371,7 @@ function detectDatabases(rootPath, analyses, services) {
17199
17371
  const schemaResults = /* @__PURE__ */ new Map();
17200
17372
  const prismaFiles = findFiles(rootPath, "schema.prisma", ["node_modules", ".git", "dist"]);
17201
17373
  for (const prismaFile of prismaFiles) {
17202
- const content = readFileSync6(prismaFile, "utf-8");
17374
+ const content = readFileSync7(prismaFile, "utf-8");
17203
17375
  const result = parsePrismaSchema(content);
17204
17376
  let dbType = "postgres";
17205
17377
  const providerMatch = content.match(/provider\s*=\s*"(\w+)"/);
@@ -17237,7 +17409,7 @@ function detectDatabases(rootPath, analyses, services) {
17237
17409
  if (!parser4.matchesImport(analysis))
17238
17410
  continue;
17239
17411
  try {
17240
- const content = readFileSync6(resolve5(analysis.filePath), "utf-8");
17412
+ const content = readFileSync7(resolve5(analysis.filePath), "utf-8");
17241
17413
  if (parser4.validateContent && !parser4.validateContent(content))
17242
17414
  continue;
17243
17415
  const result = parser4.parse(content);
@@ -17287,7 +17459,7 @@ function parseDockerCompose(rootPath) {
17287
17459
  for (const file of composeFiles) {
17288
17460
  const filePath = join7(rootPath, file);
17289
17461
  if (existsSync8(filePath)) {
17290
- composeContent = readFileSync6(filePath, "utf-8");
17462
+ composeContent = readFileSync7(filePath, "utf-8");
17291
17463
  break;
17292
17464
  }
17293
17465
  }
@@ -18548,7 +18720,7 @@ var init_entities = __esm({
18548
18720
 
18549
18721
  // packages/analyzer/dist/lsp-client.js
18550
18722
  import { spawn as spawn2 } from "child_process";
18551
- import { readFileSync as readFileSync7 } from "fs";
18723
+ import { readFileSync as readFileSync8 } from "fs";
18552
18724
  import { resolve as resolve6 } from "path";
18553
18725
  import { pathToFileURL, fileURLToPath as fileURLToPath3 } from "url";
18554
18726
  function encodeMessage(msg) {
@@ -18663,7 +18835,7 @@ var init_lsp_client = __esm({
18663
18835
  // -------------------------------------------------------------------------
18664
18836
  openFile(filePath) {
18665
18837
  const absPath = resolve6(this.rootPath, filePath);
18666
- const content = readFileSync7(absPath, "utf-8");
18838
+ const content = readFileSync8(absPath, "utf-8");
18667
18839
  this.sendNotification("textDocument/didOpen", {
18668
18840
  textDocument: {
18669
18841
  uri: pathToFileURL(absPath).href,
@@ -18775,7 +18947,7 @@ var init_lsp_client = __esm({
18775
18947
  const moduleMap = /* @__PURE__ */ new Map();
18776
18948
  for (const fa of fileAnalyses) {
18777
18949
  const absPath = resolve6(this.rootPath, fa.filePath);
18778
- const fileContent = readFileSync7(absPath, "utf-8");
18950
+ const fileContent = readFileSync8(absPath, "utf-8");
18779
18951
  const lines = fileContent.split("\n");
18780
18952
  const fileResolutions = /* @__PURE__ */ new Map();
18781
18953
  for (const imp of fa.imports) {
@@ -32874,6 +33046,19 @@ function isPropertyAccess(node) {
32874
33046
  }
32875
33047
  if (parent.type === "keyword_argument" && parent.childForFieldName("name")?.id === node.id)
32876
33048
  return true;
33049
+ if (parent.type === "jsx_attribute" && parent.namedChild(0)?.id === node.id)
33050
+ return true;
33051
+ if (parent.type === "jsx_opening_element" || parent.type === "jsx_closing_element" || parent.type === "jsx_self_closing_element") {
33052
+ const tagName = parent.childForFieldName("name");
33053
+ if (tagName?.id === node.id) {
33054
+ const text = node.text;
33055
+ if (text.length > 0) {
33056
+ const first2 = text[0];
33057
+ if (first2 === first2.toLowerCase() || text.includes("-"))
33058
+ return true;
33059
+ }
33060
+ }
33061
+ }
32877
33062
  return false;
32878
33063
  }
32879
33064
  function isJsDeclarationPosition(node) {
@@ -32907,6 +33092,10 @@ function isJsDeclarationPosition(node) {
32907
33092
  }
32908
33093
  if (parent.type === "catch_clause")
32909
33094
  return true;
33095
+ if (parent.type === "for_in_statement" && parent.childForFieldName("left")?.id === node.id)
33096
+ return true;
33097
+ if (parent.type === "arrow_function" && parent.childForFieldName("parameter")?.id === node.id)
33098
+ return true;
32910
33099
  return false;
32911
33100
  }
32912
33101
  function isPyDeclarationPosition(node) {
@@ -33046,9 +33235,9 @@ function buildScopeTree(rootNode, language) {
33046
33235
  return;
33047
33236
  }
33048
33237
  if ((parent.type === "function_declaration" || parent.type === "generator_function_declaration") && parent.childForFieldName("name")?.id === node.id) {
33049
- const targetScope = findFunctionScope(scope);
33050
- if (!targetScope.variables.has(node.text)) {
33051
- createVariable(node.text, "function", node, targetScope, false);
33238
+ const enclosing = scope.parent ? findFunctionScope(scope.parent) : scope;
33239
+ if (!enclosing.variables.has(node.text)) {
33240
+ createVariable(node.text, "function", node, enclosing, false);
33052
33241
  }
33053
33242
  return;
33054
33243
  }
@@ -33068,6 +33257,13 @@ function buildScopeTree(rootNode, language) {
33068
33257
  }
33069
33258
  return;
33070
33259
  }
33260
+ if (parent.type === "arrow_function" && parent.childForFieldName("parameter")?.id === node.id) {
33261
+ const funcScope = nodeToScope.get(parent.id);
33262
+ if (funcScope && !funcScope.variables.has(node.text)) {
33263
+ createVariable(node.text, "parameter", node, funcScope, false);
33264
+ }
33265
+ return;
33266
+ }
33071
33267
  if (parent.type === "rest_pattern" || parent.type === "rest_element") {
33072
33268
  const funcNode = findFunctionAncestor(node);
33073
33269
  if (funcNode) {
@@ -33090,6 +33286,68 @@ function buildScopeTree(rootNode, language) {
33090
33286
  return;
33091
33287
  }
33092
33288
  }
33289
+ if (parent.type === "array_pattern" || parent.type === "object_pattern") {
33290
+ if (isInFormalParameters(node)) {
33291
+ const funcNode = findFunctionAncestor(node);
33292
+ if (funcNode) {
33293
+ const funcScope = nodeToScope.get(funcNode.id);
33294
+ if (funcScope && !funcScope.variables.has(node.text)) {
33295
+ createVariable(node.text, "parameter", node, funcScope, false);
33296
+ }
33297
+ }
33298
+ return;
33299
+ }
33300
+ let ancestor = parent.parent;
33301
+ let declarator = null;
33302
+ let forIn = null;
33303
+ while (ancestor) {
33304
+ if (ancestor.type === "variable_declarator") {
33305
+ declarator = ancestor;
33306
+ break;
33307
+ }
33308
+ if (ancestor.type === "for_in_statement" && ancestor.childForFieldName("left")?.id === parent.id) {
33309
+ forIn = ancestor;
33310
+ break;
33311
+ }
33312
+ if (ancestor.type === "function_declaration" || ancestor.type === "function_expression" || ancestor.type === "arrow_function" || ancestor.type === "method_definition" || ancestor.type === "program")
33313
+ break;
33314
+ ancestor = ancestor.parent;
33315
+ }
33316
+ if (declarator) {
33317
+ const grandparent = declarator.parent;
33318
+ if (grandparent?.type === "variable_declaration") {
33319
+ const targetScope = findFunctionScope(scope);
33320
+ if (!targetScope.variables.has(node.text)) {
33321
+ createVariable(node.text, "var", node, targetScope, false);
33322
+ }
33323
+ } else if (grandparent?.type === "lexical_declaration") {
33324
+ const keyword = grandparent.child(0)?.text;
33325
+ const kind = keyword === "const" ? "const" : "let";
33326
+ if (!scope.variables.has(node.text)) {
33327
+ createVariable(node.text, kind, node, scope, false);
33328
+ }
33329
+ }
33330
+ return;
33331
+ }
33332
+ if (forIn) {
33333
+ const kindText = forIn.childForFieldName("kind")?.text;
33334
+ const kind = kindText === "var" ? "var" : kindText === "const" ? "const" : kindText === "let" ? "let" : "for-variable";
33335
+ const targetScope = kind === "var" ? findFunctionScope(scope) : scope;
33336
+ if (!targetScope.variables.has(node.text)) {
33337
+ createVariable(node.text, kind, node, targetScope, false);
33338
+ }
33339
+ return;
33340
+ }
33341
+ }
33342
+ if (parent.type === "for_in_statement" && parent.childForFieldName("left")?.id === node.id) {
33343
+ const kindText = parent.childForFieldName("kind")?.text;
33344
+ const kind = kindText === "var" ? "var" : kindText === "const" ? "const" : kindText === "let" ? "let" : "for-variable";
33345
+ const targetScope = kind === "var" ? findFunctionScope(scope) : scope;
33346
+ if (!targetScope.variables.has(node.text)) {
33347
+ createVariable(node.text, kind, node, targetScope, false);
33348
+ }
33349
+ return;
33350
+ }
33093
33351
  if (parent.type === "shorthand_property_identifier_pattern") {
33094
33352
  if (isInFormalParameters(node)) {
33095
33353
  const funcNode = findFunctionAncestor(node);
@@ -33430,16 +33688,27 @@ function buildScopeTree(rootNode, language) {
33430
33688
  if (isJs && node.type === "shorthand_property_identifier_pattern") {
33431
33689
  let ancestor = node.parent;
33432
33690
  let declarator = null;
33691
+ let isParam = false;
33692
+ let funcNode = null;
33433
33693
  while (ancestor) {
33434
33694
  if (ancestor.type === "variable_declarator") {
33435
33695
  declarator = ancestor;
33436
33696
  break;
33437
33697
  }
33438
- if (ancestor.type === "formal_parameters" || ancestor.type === "function_declaration" || ancestor.type === "arrow_function" || ancestor.type === "method_definition") {
33698
+ if (ancestor.type === "formal_parameters" || ancestor.type === "function_declaration" || ancestor.type === "function_expression" || ancestor.type === "arrow_function" || ancestor.type === "method_definition" || ancestor.type === "generator_function_declaration" || ancestor.type === "generator_function") {
33699
+ isParam = true;
33700
+ funcNode = ancestor.type === "formal_parameters" ? ancestor.parent : ancestor;
33439
33701
  break;
33440
33702
  }
33441
33703
  ancestor = ancestor.parent;
33442
33704
  }
33705
+ if (isParam && funcNode) {
33706
+ const funcScope = nodeToScope.get(funcNode.id);
33707
+ const name = node.text;
33708
+ if (funcScope && !funcScope.variables.has(name)) {
33709
+ createVariable(name, "parameter", node, funcScope, false);
33710
+ }
33711
+ }
33443
33712
  if (declarator) {
33444
33713
  const grandparent = declarator.parent;
33445
33714
  const name = node.text;
@@ -33509,7 +33778,7 @@ var init_known_globals = __esm({
33509
33778
  "packages/analyzer/dist/data-flow/known-globals.js"() {
33510
33779
  "use strict";
33511
33780
  JS_GLOBALS = /* @__PURE__ */ new Set([
33512
- // Browser
33781
+ // Browser — global namespaces
33513
33782
  "window",
33514
33783
  "document",
33515
33784
  "navigator",
@@ -33517,19 +33786,109 @@ var init_known_globals = __esm({
33517
33786
  "history",
33518
33787
  "localStorage",
33519
33788
  "sessionStorage",
33789
+ "screen",
33790
+ "performance",
33791
+ "crypto",
33792
+ "caches",
33793
+ "indexedDB",
33794
+ "matchMedia",
33795
+ // Browser — networking / fetch
33520
33796
  "fetch",
33521
33797
  "XMLHttpRequest",
33522
33798
  "WebSocket",
33799
+ "EventSource",
33523
33800
  "URL",
33524
33801
  "URLSearchParams",
33802
+ "Headers",
33803
+ "Request",
33804
+ "Response",
33805
+ "FormData",
33806
+ "Blob",
33807
+ "File",
33808
+ "FileReader",
33809
+ "FileList",
33810
+ "AbortController",
33811
+ "AbortSignal",
33812
+ "ReadableStream",
33813
+ "WritableStream",
33814
+ "TransformStream",
33815
+ // Browser — events
33816
+ "Event",
33817
+ "CustomEvent",
33818
+ "EventTarget",
33819
+ "MessageEvent",
33820
+ "ErrorEvent",
33821
+ "CloseEvent",
33822
+ "ProgressEvent",
33823
+ "PointerEvent",
33824
+ "KeyboardEvent",
33825
+ "MouseEvent",
33826
+ "TouchEvent",
33827
+ "DragEvent",
33828
+ "WheelEvent",
33829
+ "FocusEvent",
33830
+ "InputEvent",
33831
+ "StorageEvent",
33832
+ "PopStateEvent",
33833
+ "HashChangeEvent",
33834
+ "BeforeUnloadEvent",
33835
+ // Browser — DOM
33836
+ "Node",
33837
+ "Element",
33838
+ "HTMLElement",
33839
+ "HTMLInputElement",
33840
+ "HTMLButtonElement",
33841
+ "HTMLFormElement",
33842
+ "HTMLSelectElement",
33843
+ "HTMLTextAreaElement",
33844
+ "HTMLAnchorElement",
33845
+ "HTMLImageElement",
33846
+ "HTMLCanvasElement",
33847
+ "HTMLVideoElement",
33848
+ "HTMLAudioElement",
33849
+ "HTMLDivElement",
33850
+ "HTMLSpanElement",
33851
+ "HTMLScriptElement",
33852
+ "HTMLStyleElement",
33853
+ "HTMLTemplateElement",
33854
+ "HTMLIFrameElement",
33855
+ "HTMLDialogElement",
33856
+ "DocumentFragment",
33857
+ "ShadowRoot",
33858
+ "Text",
33859
+ "Comment",
33860
+ "Range",
33861
+ "Selection",
33862
+ "MutationObserver",
33863
+ "IntersectionObserver",
33864
+ "ResizeObserver",
33865
+ "PerformanceObserver",
33866
+ "NodeList",
33867
+ "HTMLCollection",
33868
+ "DOMTokenList",
33869
+ "CanvasRenderingContext2D",
33870
+ "OffscreenCanvas",
33871
+ // Browser — timers / scheduling
33525
33872
  "setTimeout",
33526
33873
  "setInterval",
33527
33874
  "clearTimeout",
33528
33875
  "clearInterval",
33529
33876
  "requestAnimationFrame",
33877
+ "cancelAnimationFrame",
33878
+ "requestIdleCallback",
33879
+ "cancelIdleCallback",
33880
+ // Browser — dialogs
33530
33881
  "alert",
33531
33882
  "confirm",
33532
33883
  "prompt",
33884
+ // Browser — workers / messaging
33885
+ "Worker",
33886
+ "SharedWorker",
33887
+ "ServiceWorker",
33888
+ "BroadcastChannel",
33889
+ "MessageChannel",
33890
+ "MessagePort",
33891
+ "postMessage",
33533
33892
  // Node.js
33534
33893
  "process",
33535
33894
  "global",
@@ -33903,8 +34262,13 @@ function buildDataFlowContext(rootNode, language) {
33903
34262
  continue;
33904
34263
  if (v.useSites.length === 0)
33905
34264
  continue;
34265
+ if (v.declarationNode.parent?.type === "named_expression")
34266
+ continue;
34267
+ const directUseSites = v.useSites.filter((u2) => !isInNestedFunctionScope(u2.scope, v.scope));
34268
+ if (directUseSites.length === 0)
34269
+ continue;
33906
34270
  const declPos = v.declarationNode.startIndex;
33907
- const earliestUse = Math.min(...v.useSites.map((u2) => u2.node.startIndex));
34271
+ const earliestUse = Math.min(...directUseSites.map((u2) => u2.node.startIndex));
33908
34272
  if (earliestUse < declPos) {
33909
34273
  result.push(v);
33910
34274
  }
@@ -33912,6 +34276,17 @@ function buildDataFlowContext(rootNode, language) {
33912
34276
  cachedUsedBeforeDefined = result;
33913
34277
  return result;
33914
34278
  }
34279
+ function isInNestedFunctionScope(useScope, declScope) {
34280
+ if (useScope === declScope)
34281
+ return false;
34282
+ let cursor = useScope;
34283
+ while (cursor && cursor !== declScope) {
34284
+ if (cursor.kind === "function")
34285
+ return true;
34286
+ cursor = cursor.parent;
34287
+ }
34288
+ return false;
34289
+ }
33915
34290
  function unusedVariables() {
33916
34291
  if (cachedUnused)
33917
34292
  return cachedUnused;
@@ -34495,7 +34870,72 @@ var init_insecure_cookie = __esm({
34495
34870
  });
34496
34871
 
34497
34872
  // packages/analyzer/dist/rules/security/visitors/javascript/disabled-auto-escaping.js
34498
- var disabledAutoEscapingVisitor;
34873
+ function isStaticStringLiteral(node) {
34874
+ if (node.type === "string")
34875
+ return true;
34876
+ if (node.type === "template_string") {
34877
+ for (let i = 0; i < node.namedChildCount; i++) {
34878
+ const child = node.namedChild(i);
34879
+ if (child?.type === "template_substitution")
34880
+ return false;
34881
+ }
34882
+ return true;
34883
+ }
34884
+ return false;
34885
+ }
34886
+ function isEscapeCall(node) {
34887
+ if (node.type !== "call_expression")
34888
+ return false;
34889
+ const fn = node.childForFieldName("function");
34890
+ if (!fn)
34891
+ return false;
34892
+ if (fn.type === "identifier")
34893
+ return ESCAPE_HELPER_NAMES.has(fn.text);
34894
+ if (fn.type === "member_expression") {
34895
+ const prop = fn.childForFieldName("property");
34896
+ if (prop && ESCAPE_HELPER_NAMES.has(prop.text))
34897
+ return true;
34898
+ const obj = fn.childForFieldName("object");
34899
+ if (obj?.type === "identifier" && /purify/i.test(obj.text))
34900
+ return true;
34901
+ }
34902
+ return false;
34903
+ }
34904
+ function isFullyEscapedRhs(node) {
34905
+ if (node.type === "string")
34906
+ return true;
34907
+ if (node.type === "number")
34908
+ return true;
34909
+ if (node.type === "true" || node.type === "false" || node.type === "null" || node.type === "undefined")
34910
+ return true;
34911
+ if (isEscapeCall(node))
34912
+ return true;
34913
+ if (node.type === "template_string") {
34914
+ for (let i = 0; i < node.namedChildCount; i++) {
34915
+ const child = node.namedChild(i);
34916
+ if (!child)
34917
+ continue;
34918
+ if (child.type === "template_substitution") {
34919
+ const inner = child.namedChild(0);
34920
+ if (!inner || !isFullyEscapedRhs(inner))
34921
+ return false;
34922
+ }
34923
+ }
34924
+ return true;
34925
+ }
34926
+ if (node.type === "binary_expression") {
34927
+ const op = node.children.find((c2) => c2.text === "+");
34928
+ if (!op)
34929
+ return false;
34930
+ const lhs = node.childForFieldName("left");
34931
+ const rhs = node.childForFieldName("right");
34932
+ if (!lhs || !rhs)
34933
+ return false;
34934
+ return isFullyEscapedRhs(lhs) && isFullyEscapedRhs(rhs);
34935
+ }
34936
+ return false;
34937
+ }
34938
+ var disabledAutoEscapingVisitor, ESCAPE_HELPER_NAMES;
34499
34939
  var init_disabled_auto_escaping = __esm({
34500
34940
  "packages/analyzer/dist/rules/security/visitors/javascript/disabled-auto-escaping.js"() {
34501
34941
  "use strict";
@@ -34516,6 +34956,11 @@ var init_disabled_auto_escaping = __esm({
34516
34956
  if (left?.type === "member_expression") {
34517
34957
  const prop = left.childForFieldName("property");
34518
34958
  if (prop?.text === "innerHTML") {
34959
+ const right = node.childForFieldName("right");
34960
+ if (right && isStaticStringLiteral(right))
34961
+ return null;
34962
+ if (right && isFullyEscapedRhs(right))
34963
+ return null;
34519
34964
  return makeViolation(this.ruleKey, node, filePath, "high", "Disabled auto-escaping", "Direct innerHTML assignment can lead to XSS vulnerabilities.", sourceCode, "Use textContent instead of innerHTML, or sanitize input with DOMPurify.");
34520
34965
  }
34521
34966
  }
@@ -34523,6 +34968,19 @@ var init_disabled_auto_escaping = __esm({
34523
34968
  return null;
34524
34969
  }
34525
34970
  };
34971
+ ESCAPE_HELPER_NAMES = /* @__PURE__ */ new Set([
34972
+ "esc",
34973
+ "escape",
34974
+ "escapeHtml",
34975
+ "escapeHTML",
34976
+ "escapeHtmlEntities",
34977
+ "htmlEscape",
34978
+ "htmlEntities",
34979
+ "sanitize",
34980
+ "sanitizeHtml",
34981
+ "sanitizeHTML",
34982
+ "purify"
34983
+ ]);
34526
34984
  }
34527
34985
  });
34528
34986
 
@@ -38137,6 +38595,18 @@ var init_javascript3 = __esm({
38137
38595
  });
38138
38596
 
38139
38597
  // packages/analyzer/dist/rules/security/visitors/python/sql-injection.js
38598
+ function hasStringOperand(binNode) {
38599
+ for (let i = 0; i < binNode.namedChildCount; i++) {
38600
+ const child = binNode.namedChild(i);
38601
+ if (!child)
38602
+ continue;
38603
+ if (child.type === "string")
38604
+ return true;
38605
+ if (child.type === "binary_operator" && hasStringOperand(child))
38606
+ return true;
38607
+ }
38608
+ return false;
38609
+ }
38140
38610
  var PYTHON_QUERY_METHODS, pythonSqlInjectionVisitor;
38141
38611
  var init_sql_injection2 = __esm({
38142
38612
  "packages/analyzer/dist/rules/security/visitors/python/sql-injection.js"() {
@@ -38190,7 +38660,9 @@ var init_sql_injection2 = __esm({
38190
38660
  if (firstArg.type === "binary_operator") {
38191
38661
  const op = firstArg.children.find((c2) => c2.text === "+");
38192
38662
  if (op) {
38193
- return makeViolation(this.ruleKey, node, filePath, "high", "Potential SQL injection", `String concatenation passed to ${methodName}(). Use parameterized queries instead.`, sourceCode, "Use parameterized queries (e.g., %s or :param) instead of string concatenation in SQL.");
38663
+ if (hasStringOperand(firstArg)) {
38664
+ return makeViolation(this.ruleKey, node, filePath, "high", "Potential SQL injection", `String concatenation passed to ${methodName}(). Use parameterized queries instead.`, sourceCode, "Use parameterized queries (e.g., %s or :param) instead of string concatenation in SQL.");
38665
+ }
38194
38666
  }
38195
38667
  }
38196
38668
  return null;
@@ -40142,6 +40614,40 @@ var init_paramiko_call = __esm({
40142
40614
  });
40143
40615
 
40144
40616
  // packages/analyzer/dist/rules/security/visitors/python/suspicious-url-open.js
40617
+ function isConfigSourcedVar(node, varName) {
40618
+ let func = node.parent;
40619
+ while (func) {
40620
+ if (func.type === "function_definition")
40621
+ break;
40622
+ func = func.parent;
40623
+ }
40624
+ let scope = func ? func.childForFieldName("body") : node.tree.rootNode;
40625
+ if (!scope)
40626
+ return false;
40627
+ let found = false;
40628
+ function walk(n) {
40629
+ if (found)
40630
+ return;
40631
+ if (n.type === "assignment") {
40632
+ const lhs = n.childForFieldName("left");
40633
+ const rhs = n.childForFieldName("right");
40634
+ if (lhs?.type === "identifier" && lhs.text === varName && rhs) {
40635
+ const text = rhs.text;
40636
+ if (/\bos\.getenv\b|\bos\.environ\b|\bconfig\.|\bsettings\.|\bSettings\(\)|\bConfig\(\)/.test(text)) {
40637
+ found = true;
40638
+ return;
40639
+ }
40640
+ }
40641
+ }
40642
+ for (let i = 0; i < n.childCount; i++) {
40643
+ const ch = n.child(i);
40644
+ if (ch)
40645
+ walk(ch);
40646
+ }
40647
+ }
40648
+ walk(scope);
40649
+ return found;
40650
+ }
40145
40651
  var pythonSuspiciousUrlOpenVisitor;
40146
40652
  var init_suspicious_url_open = __esm({
40147
40653
  "packages/analyzer/dist/rules/security/visitors/python/suspicious-url-open.js"() {
@@ -40175,10 +40681,11 @@ var init_suspicious_url_open = __esm({
40175
40681
  const firstArg = args.namedChildren[0];
40176
40682
  if (!firstArg)
40177
40683
  return null;
40178
- if (firstArg.type !== "string" || firstArg.text.startsWith("f")) {
40179
- return makeViolation(this.ruleKey, node, filePath, "high", "urlopen with user-controlled URL", `${objectName ? objectName + "." : ""}urlopen() called with a non-literal URL. User-controlled URLs enable SSRF.`, sourceCode, "Validate and allowlist URLs before passing them to urlopen().");
40180
- }
40181
- return null;
40684
+ if (firstArg.type === "string" && !firstArg.text.startsWith("f"))
40685
+ return null;
40686
+ if (firstArg.type === "identifier" && isConfigSourcedVar(node, firstArg.text))
40687
+ return null;
40688
+ return makeViolation(this.ruleKey, node, filePath, "high", "urlopen with user-controlled URL", `${objectName ? objectName + "." : ""}urlopen() called with a non-literal URL. User-controlled URLs enable SSRF.`, sourceCode, "Validate and allowlist URLs before passing them to urlopen().");
40182
40689
  }
40183
40690
  };
40184
40691
  }
@@ -42636,8 +43143,15 @@ var init_exclusions = __esm({
42636
43143
  // All asterisks (masked values)
42637
43144
  /^x+$/i,
42638
43145
  // All x's (placeholder)
42639
- /^\.{3,}$/
43146
+ /^\.{3,}$/,
42640
43147
  // Ellipsis placeholders
43148
+ // Filenames with a recognized document / media / archive / data
43149
+ // extension. Composed object keys like `<hash>__<date>__<batch>.pdf`
43150
+ // look like high-entropy tokens to pattern detectors, but the
43151
+ // trailing extension makes them clearly filenames. The optional
43152
+ // trailing slash matches S3 path prefixes that act as directories
43153
+ // (`processed/.../<file>.pdf/source_pdfs/...`).
43154
+ /\.(pdf|csv|tsv|json|jsonl|ndjson|parquet|xml|html?|md|rst|txt|log|zip|tar|gz|tgz|bz2|7z|rar|png|jpe?g|gif|webp|svg|ico|bmp|tiff?|mp[34]|wav|flac|ogg|webm|avi|mov|mkv|docx?|xlsx?|pptx?|odt|ods|odp|rtf|epub|yaml|yml|toml|ini|cfg|conf|sql)\/?$/i
42641
43155
  ];
42642
43156
  }
42643
43157
  });
@@ -44442,7 +44956,7 @@ var init_secret_scanner = __esm({
44442
44956
  function stripCommentMarkers(text) {
44443
44957
  return text.replace(/^\/\/\s?/, "").replace(/^\/\*\s?/, "").replace(/\s?\*\/$/, "").replace(/^\s*\*\s?/gm, "").replace(/^#\s?/gm, "").trim();
44444
44958
  }
44445
- var hardcodedSecretVisitor, IPV4_REGEX, EXCLUDED_IPS, hardcodedIpVisitor, CLEARTEXT_PROTOCOLS, LOCALHOST_PREFIXES, clearTextProtocolVisitor, BIP39_COMMON_WORDS, hardcodedBlockchainMnemonicVisitor, DB_CONNECTION_PATTERNS, hardcodedDatabasePasswordVisitor, ldapUnauthenticatedVisitor, passwordStoredPlaintextVisitor, HASH_FUNCTIONS_NEEDING_SALT, unpredictableSaltMissingVisitor, sensitiveFileVisitor, hardcodedSecretInCommentVisitor, SECURITY_UNIVERSAL_VISITORS;
44959
+ var hardcodedSecretVisitor, IPV4_REGEX, EXCLUDED_IPS, hardcodedIpVisitor, CLEARTEXT_PROTOCOLS, LOCALHOST_PREFIXES, clearTextProtocolVisitor, BIP39_COMMON_WORDS, hardcodedBlockchainMnemonicVisitor, DB_URL_PATTERN, DB_PASSWORD_KV_PATTERNS, NON_LITERAL_PASSWORD_SEGMENT, hardcodedDatabasePasswordVisitor, ldapUnauthenticatedVisitor, passwordStoredPlaintextVisitor, HASH_FUNCTIONS_NEEDING_SALT, unpredictableSaltMissingVisitor, sensitiveFileVisitor, hardcodedSecretInCommentVisitor, SECURITY_UNIVERSAL_VISITORS;
44446
44960
  var init_universal = __esm({
44447
44961
  "packages/analyzer/dist/rules/security/visitors/universal.js"() {
44448
44962
  "use strict";
@@ -44460,6 +44974,15 @@ var init_universal = __esm({
44460
44974
  if (parent?.type === "pair" && parent.childForFieldName("key")?.id === node.id) {
44461
44975
  return null;
44462
44976
  }
44977
+ if (parent?.type === "expression_statement") {
44978
+ const grandParent = parent.parent;
44979
+ if (grandParent && (grandParent.type === "module" || grandParent.type === "block")) {
44980
+ const firstChild = grandParent.namedChild(0);
44981
+ if (firstChild && firstChild.id === parent.id) {
44982
+ return null;
44983
+ }
44984
+ }
44985
+ }
44463
44986
  const match2 = scanForSecrets(stripped);
44464
44987
  if (match2) {
44465
44988
  return makeViolation(this.ruleKey, node, filePath, "critical", `Hardcoded secret detected (${match2.patternId})`, `This string matches a known secret pattern: ${match2.description}. Use environment variables instead.`, sourceCode, "Move this secret to an environment variable.");
@@ -44474,7 +44997,11 @@ var init_universal = __esm({
44474
44997
  const secretNames = ["password", "passwd", "secret", "apikey", "api_key", "token", "auth_token", "access_token", "private_key"];
44475
44998
  const isNonSecretName = /(?:uri|url|endpoint|type|scope|name|header|grant|method)/.test(name);
44476
44999
  const isNonSecretValue = /https?:\/\//.test(stripped) || /^(true|false|null|undefined|localhost|None|True|False|Bearer)$/i.test(stripped) || /[[\]<>{}()#.=\s]/.test(stripped);
44477
- if (secretNames.some((s) => name.includes(s)) && stripped.length >= 8 && !isNonSecretName && !isNonSecretValue) {
45000
+ const nameClean = name.replace(/^['"`]+|['"`]+$/g, "");
45001
+ const valueClean = stripped.toLowerCase();
45002
+ const isPlainAlphaSnake = /^[a-z_]+$/.test(valueClean);
45003
+ const isVocabularyTag = isPlainAlphaSnake && nameClean.length >= 4 && (valueClean === nameClean || valueClean.includes(nameClean));
45004
+ if (secretNames.some((s) => name.includes(s)) && stripped.length >= 8 && !isNonSecretName && !isNonSecretValue && !isVocabularyTag) {
44478
45005
  return makeViolation(this.ruleKey, node, filePath, "critical", "Hardcoded secret detected", `Variable "${nameNode.text}" contains what appears to be a hardcoded secret. Use environment variables instead.`, sourceCode, "Move this secret to an environment variable.");
44479
45006
  }
44480
45007
  }
@@ -45488,21 +46015,28 @@ var init_universal = __esm({
45488
46015
  return null;
45489
46016
  }
45490
46017
  };
45491
- DB_CONNECTION_PATTERNS = [
45492
- /(?:mysql|postgresql|postgres|mongodb|sqlite|mssql|oracle):\/\/[^:]+:([^@]+)@/i,
46018
+ DB_URL_PATTERN = /(?:mysql|postgresql|postgres|mongodb|sqlite|mssql|oracle):\/\/[^:]+:([^@]+)@/i;
46019
+ DB_PASSWORD_KV_PATTERNS = [
45493
46020
  /password\s*=\s*['"][^'"]{4,}['"]/i,
45494
46021
  /pwd\s*=\s*['"][^'"]{4,}['"]/i
45495
46022
  ];
46023
+ NON_LITERAL_PASSWORD_SEGMENT = /^\{[^{}]*\}$|^\$\{[^}]*\}$|^%s$|^%\([^)]+\)s$|^<[^>]+>$|^\$\([^)]*\)$|^\$[A-Z_][A-Z0-9_]*$|process\.env/i;
45496
46024
  hardcodedDatabasePasswordVisitor = {
45497
46025
  ruleKey: "security/deterministic/hardcoded-database-password",
45498
46026
  nodeTypes: ["string", "template_string"],
45499
46027
  visit(node, filePath, sourceCode) {
45500
46028
  const text = node.text;
45501
46029
  const stripped = text.replace(/^[fFbBrRuU]*['"`]{1,3}|['"`]{1,3}$/g, "");
45502
- for (const pattern of DB_CONNECTION_PATTERNS) {
46030
+ if (/\$\{|%s|<password>|\$\(|process\.env/i.test(stripped))
46031
+ return null;
46032
+ const urlMatch = DB_URL_PATTERN.exec(stripped);
46033
+ if (urlMatch) {
46034
+ if (NON_LITERAL_PASSWORD_SEGMENT.test(urlMatch[1]))
46035
+ return null;
46036
+ return makeViolation(this.ruleKey, node, filePath, "critical", "Hardcoded database password", "Database connection string contains a hardcoded password.", sourceCode, "Load database credentials from environment variables.");
46037
+ }
46038
+ for (const pattern of DB_PASSWORD_KV_PATTERNS) {
45503
46039
  if (pattern.test(stripped)) {
45504
- if (/\$\{|%s|<password>|\$\(|process\.env/i.test(stripped))
45505
- return null;
45506
46040
  return makeViolation(this.ruleKey, node, filePath, "critical", "Hardcoded database password", "Database connection string contains a hardcoded password.", sourceCode, "Load database credentials from environment variables.");
45507
46041
  }
45508
46042
  }
@@ -47478,6 +48012,78 @@ function isKeyFromControlledIteration(assignmentNode, keyName) {
47478
48012
  }
47479
48013
  return false;
47480
48014
  }
48015
+ function isKeyFromForOfLoopVarMember(assignmentNode, keyName) {
48016
+ let scope = assignmentNode.parent;
48017
+ while (scope) {
48018
+ if (scope.type === "function_declaration" || scope.type === "function_expression" || scope.type === "arrow_function" || scope.type === "method_definition" || scope.type === "program")
48019
+ break;
48020
+ scope = scope.parent;
48021
+ }
48022
+ if (!scope)
48023
+ return false;
48024
+ const forVarNames = /* @__PURE__ */ new Set();
48025
+ function collectForVars(n) {
48026
+ if (n.type === "for_in_statement") {
48027
+ const left = n.childForFieldName("left");
48028
+ if (left?.type === "identifier")
48029
+ forVarNames.add(left.text);
48030
+ }
48031
+ if (n !== scope && (n.type === "function_declaration" || n.type === "function_expression" || n.type === "arrow_function" || n.type === "method_definition"))
48032
+ return;
48033
+ for (let i = 0; i < n.childCount; i++) {
48034
+ const ch = n.child(i);
48035
+ if (ch)
48036
+ collectForVars(ch);
48037
+ }
48038
+ }
48039
+ collectForVars(scope);
48040
+ if (forVarNames.size === 0)
48041
+ return false;
48042
+ let found = false;
48043
+ function findAssignment(n) {
48044
+ if (found)
48045
+ return;
48046
+ if (n.type === "variable_declarator") {
48047
+ const name = n.childForFieldName("name");
48048
+ const value = n.childForFieldName("value");
48049
+ if (name?.type === "identifier" && name.text === keyName && value?.type === "member_expression") {
48050
+ const obj = value.childForFieldName("object");
48051
+ if (obj?.type === "identifier" && forVarNames.has(obj.text)) {
48052
+ found = true;
48053
+ return;
48054
+ }
48055
+ }
48056
+ }
48057
+ if (n !== scope && (n.type === "function_declaration" || n.type === "function_expression" || n.type === "arrow_function" || n.type === "method_definition"))
48058
+ return;
48059
+ for (let i = 0; i < n.childCount; i++) {
48060
+ const ch = n.child(i);
48061
+ if (ch)
48062
+ findAssignment(ch);
48063
+ }
48064
+ }
48065
+ findAssignment(scope);
48066
+ return found;
48067
+ }
48068
+ function isAssignmentGuardedByKeyInObject(assignmentNode, keyName, objText) {
48069
+ let cursor = assignmentNode.parent;
48070
+ while (cursor) {
48071
+ if (cursor.type === "if_statement") {
48072
+ const cond = cursor.childForFieldName("condition");
48073
+ if (cond) {
48074
+ const condText = cond.text;
48075
+ const pattern = new RegExp(`\\b${escapeRegExp(keyName)}\\s+in\\s+${escapeRegExp(objText)}\\b`);
48076
+ if (pattern.test(condText))
48077
+ return true;
48078
+ }
48079
+ }
48080
+ cursor = cursor.parent;
48081
+ }
48082
+ return false;
48083
+ }
48084
+ function escapeRegExp(s) {
48085
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
48086
+ }
47481
48087
  function isObjectEntriesOrKeys(node) {
47482
48088
  if (node.type === "call_expression") {
47483
48089
  const fn = node.childForFieldName("function");
@@ -47491,6 +48097,77 @@ function isObjectEntriesOrKeys(node) {
47491
48097
  }
47492
48098
  return false;
47493
48099
  }
48100
+ function isKeyDestructuredParam(assignmentNode, keyName) {
48101
+ let scope = assignmentNode.parent;
48102
+ while (scope) {
48103
+ if (scope.type === "function_declaration" || scope.type === "function_expression" || scope.type === "arrow_function" || scope.type === "method_definition") {
48104
+ const params = scope.childForFieldName("parameters") ?? scope.childForFieldName("parameter");
48105
+ if (params && containsDestructuredName(params, keyName))
48106
+ return true;
48107
+ }
48108
+ if (scope.type === "program")
48109
+ break;
48110
+ scope = scope.parent;
48111
+ }
48112
+ return false;
48113
+ }
48114
+ function containsDestructuredName(params, keyName) {
48115
+ let found = false;
48116
+ function walk(n) {
48117
+ if (found)
48118
+ return;
48119
+ if (n.type === "identifier" && n.text === keyName) {
48120
+ const parent = n.parent;
48121
+ if (parent?.type === "array_pattern" || parent?.type === "object_pattern") {
48122
+ found = true;
48123
+ return;
48124
+ }
48125
+ if (parent?.type === "shorthand_property_identifier_pattern" && n.text === keyName) {
48126
+ found = true;
48127
+ return;
48128
+ }
48129
+ }
48130
+ for (let i = 0; i < n.childCount; i++) {
48131
+ const child = n.child(i);
48132
+ if (child)
48133
+ walk(child);
48134
+ }
48135
+ }
48136
+ walk(params);
48137
+ return found;
48138
+ }
48139
+ function isKeyAssignedFromLocalCall(assignmentNode, keyName) {
48140
+ let scope = assignmentNode.parent;
48141
+ while (scope) {
48142
+ if (scope.type === "function_declaration" || scope.type === "function_expression" || scope.type === "arrow_function" || scope.type === "method_definition" || scope.type === "program")
48143
+ break;
48144
+ scope = scope.parent;
48145
+ }
48146
+ if (!scope)
48147
+ return false;
48148
+ let found = false;
48149
+ function walk(n) {
48150
+ if (found)
48151
+ return;
48152
+ if (n.type === "variable_declarator") {
48153
+ const name = n.childForFieldName("name");
48154
+ const value = n.childForFieldName("value");
48155
+ if (name?.type === "identifier" && name.text === keyName && value?.type === "call_expression") {
48156
+ found = true;
48157
+ return;
48158
+ }
48159
+ }
48160
+ if (n !== scope && (n.type === "function_declaration" || n.type === "function_expression" || n.type === "arrow_function" || n.type === "method_definition"))
48161
+ return;
48162
+ for (let i = 0; i < n.childCount; i++) {
48163
+ const child = n.child(i);
48164
+ if (child)
48165
+ walk(child);
48166
+ }
48167
+ }
48168
+ walk(scope);
48169
+ return found;
48170
+ }
47494
48171
  var prototypePollutionVisitor;
47495
48172
  var init_prototype_pollution = __esm({
47496
48173
  "packages/analyzer/dist/rules/bugs/visitors/javascript/prototype-pollution.js"() {
@@ -47521,6 +48198,14 @@ var init_prototype_pollution = __esm({
47521
48198
  const keyName = index.text;
47522
48199
  if (isKeyFromControlledIteration(node, keyName))
47523
48200
  return null;
48201
+ if (isKeyAssignedFromLocalCall(node, keyName))
48202
+ return null;
48203
+ if (isKeyDestructuredParam(node, keyName))
48204
+ return null;
48205
+ if (isKeyFromForOfLoopVarMember(node, keyName))
48206
+ return null;
48207
+ if (isAssignmentGuardedByKeyInObject(node, keyName, obj.text))
48208
+ return null;
47524
48209
  return makeViolation(this.ruleKey, node, filePath, "high", "Prototype pollution", `\`${left.text}\` uses a dynamic key for property assignment \u2014 if \`${index.text}\` is \`"__proto__"\` or \`"constructor"\`, this enables prototype pollution.`, sourceCode, `Validate that \`${index.text}\` is not "__proto__", "constructor", or "prototype" before assignment, or use Map instead.`);
47525
48210
  }
47526
48211
  };
@@ -51444,8 +52129,10 @@ var init_useeffect_missing_deps = __esm({
51444
52129
  }
51445
52130
  const suspiciousIds = [...usedIds].filter((id) => /^[a-z]/.test(id) && !id.startsWith("set") && id.length > 1 && !EXCLUDED_IDENTIFIERS.has(id) && !refAccessedIds.has(id) && !stableFunctions.has(id) && !componentBodyFunctions.has(id));
51446
52131
  const nodeStartLine = node.startPosition.row;
51447
- const linesAbove = sourceCode.split("\n").slice(Math.max(0, nodeStartLine - 2), nodeStartLine + 1);
51448
- if (linesAbove.some((line) => line.includes("eslint-disable")))
52132
+ const nodeEndLine = node.endPosition.row;
52133
+ const sourceLines = sourceCode.split("\n");
52134
+ const windowLines = sourceLines.slice(Math.max(0, nodeStartLine - 2), nodeEndLine + 2);
52135
+ if (windowLines.some((line) => line.includes("eslint-disable")))
51449
52136
  return null;
51450
52137
  if (suspiciousIds.length > 0) {
51451
52138
  return makeViolation(this.ruleKey, depsArg, filePath, "high", "useEffect with empty deps may have stale closure", `\`useEffect\` has an empty dependency array but references \`${suspiciousIds.slice(0, 3).join("`, `")}\` \u2014 these will be stale closures if they change.`, sourceCode, `Add the referenced variables to the dependency array: \`[${suspiciousIds.slice(0, 3).join(", ")}]\`.`);
@@ -52909,7 +53596,7 @@ var init_empty_catch2 = __esm({
52909
53596
  if (!body)
52910
53597
  return null;
52911
53598
  const statements = body.namedChildren.filter((c2) => c2.type !== "comment");
52912
- if (statements.length === 0 || statements.length === 1 && statements[0].type === "pass_statement") {
53599
+ if (statements.length === 0) {
52913
53600
  return makeViolation(this.ruleKey, node, filePath, "medium", "Empty except block", "This except block swallows errors silently. Add error handling or at least log the error.", sourceCode, "Add error logging or re-raise the exception in this except block.");
52914
53601
  }
52915
53602
  return null;
@@ -52951,6 +53638,15 @@ var init_bare_except = __esm({
52951
53638
  });
52952
53639
 
52953
53640
  // packages/analyzer/dist/rules/bugs/visitors/python/_helpers.js
53641
+ function isFStringWithInterpolation(node) {
53642
+ if (node.type !== "string")
53643
+ return false;
53644
+ for (const child of node.namedChildren) {
53645
+ if (child && child.type === "interpolation")
53646
+ return true;
53647
+ }
53648
+ return false;
53649
+ }
52954
53650
  var MUTABLE_DEFAULTS, PY_TERMINAL_TYPES, SAFE_DEFAULT_CALLS, VALID_OPEN_MODES, DUNDER_PARAM_COUNTS, CANCELLATION_EXCEPTIONS, BROAD_EXCEPTIONS, MUTATING_METHODS, SPECIAL_METHOD_RETURN_CONSTRAINTS, PYTHON_BUILTIN_NON_EXCEPTIONS, BIDI_CHARS, VALID_FORMAT_CHARS, FORMAT_SPEC_RE;
52955
53651
  var init_helpers3 = __esm({
52956
53652
  "packages/analyzer/dist/rules/bugs/visitors/python/_helpers.js"() {
@@ -53753,17 +54449,25 @@ var init_loop_variable_overrides_iterator = __esm({
53753
54449
  if (loopVar.type !== "identifier")
53754
54450
  return null;
53755
54451
  const varName = loopVar.text;
53756
- function containsIdentifier(n, name) {
53757
- if (n.type === "identifier" && n.text === name)
54452
+ function hasFreeUse(n, name) {
54453
+ if (n.type === "identifier" && n.text === name) {
54454
+ const parent = n.parent;
54455
+ if (parent?.type === "attribute" && parent.childForFieldName("attribute")?.id === n.id) {
54456
+ return false;
54457
+ }
54458
+ if (parent?.type === "keyword_argument" && parent.childForFieldName("name")?.id === n.id) {
54459
+ return false;
54460
+ }
53758
54461
  return true;
54462
+ }
53759
54463
  for (let i = 0; i < n.childCount; i++) {
53760
54464
  const child = n.child(i);
53761
- if (child && containsIdentifier(child, name))
54465
+ if (child && hasFreeUse(child, name))
53762
54466
  return true;
53763
54467
  }
53764
54468
  return false;
53765
54469
  }
53766
- if (containsIdentifier(iterExpr, varName)) {
54470
+ if (hasFreeUse(iterExpr, varName)) {
53767
54471
  return makeViolation(this.ruleKey, loopVar, filePath, "high", "Loop variable overrides iterator", `Loop variable \`${varName}\` has the same name as the iterable \`${iterExpr.text}\` \u2014 after the first iteration \`${varName}\` no longer references the original iterable.`, sourceCode, `Rename the loop variable to something different from \`${iterExpr.text}\`.`);
53768
54472
  }
53769
54473
  return null;
@@ -55353,6 +56057,7 @@ var init_static_key_dict_comprehension = __esm({
55353
56057
  "packages/analyzer/dist/rules/bugs/visitors/python/static-key-dict-comprehension.js"() {
55354
56058
  "use strict";
55355
56059
  init_types();
56060
+ init_helpers3();
55356
56061
  pythonStaticKeyDictComprehensionVisitor = {
55357
56062
  ruleKey: "bugs/deterministic/static-key-dict-comprehension",
55358
56063
  languages: ["python"],
@@ -55365,7 +56070,7 @@ var init_static_key_dict_comprehension = __esm({
55365
56070
  if (!keyNode)
55366
56071
  return null;
55367
56072
  const LITERAL_TYPES5 = /* @__PURE__ */ new Set(["string", "integer", "float", "true", "false", "none"]);
55368
- if (LITERAL_TYPES5.has(keyNode.type)) {
56073
+ if (LITERAL_TYPES5.has(keyNode.type) && !isFStringWithInterpolation(keyNode)) {
55369
56074
  return makeViolation(this.ruleKey, keyNode, filePath, "high", "Static key in dict comprehension", `Dict comprehension with constant key \`${keyNode.text}\` \u2014 every iteration overwrites the same key, leaving only the last value.`, sourceCode, "Use a variable as the key, or use a list comprehension if you only need the values.");
55370
56075
  }
55371
56076
  return null;
@@ -56570,6 +57275,7 @@ var init_duplicate_dict_key = __esm({
56570
57275
  "packages/analyzer/dist/rules/bugs/visitors/python/duplicate-dict-key.js"() {
56571
57276
  "use strict";
56572
57277
  init_types();
57278
+ init_helpers3();
56573
57279
  pythonDuplicateDictKeyVisitor = {
56574
57280
  ruleKey: "bugs/deterministic/duplicate-dict-key",
56575
57281
  languages: ["python"],
@@ -56585,7 +57291,8 @@ var init_duplicate_dict_key = __esm({
56585
57291
  const leftNode = forInClause?.childForFieldName("left");
56586
57292
  const loopVarNames = leftNode ? collectLoopVarNames(leftNode) : /* @__PURE__ */ new Set();
56587
57293
  const LITERAL_TYPES5 = /* @__PURE__ */ new Set(["string", "integer", "float", "true", "false", "none"]);
56588
- const isConstantKey = LITERAL_TYPES5.has(keyNode.type) || keyNode.type === "identifier" && loopVarNames.size > 0 && !loopVarNames.has(keyNode.text);
57294
+ const isStaticLiteral = LITERAL_TYPES5.has(keyNode.type) && !isFStringWithInterpolation(keyNode);
57295
+ const isConstantKey = isStaticLiteral || keyNode.type === "identifier" && loopVarNames.size > 0 && !loopVarNames.has(keyNode.text);
56589
57296
  if (isConstantKey) {
56590
57297
  return makeViolation(this.ruleKey, keyNode, filePath, "high", "Constant key in dict comprehension", `Dict comprehension with constant key \`${keyNode.text}\` \u2014 each iteration overwrites the same key, leaving only the last value.`, sourceCode, "Use a key expression that depends on the loop variable, or use a list comprehension instead.");
56591
57298
  }
@@ -56992,9 +57699,8 @@ var init_warnings_no_stacklevel = __esm({
56992
57699
  const funcText = func.text;
56993
57700
  if (funcText !== "warnings.warn" && funcText !== "warn")
56994
57701
  return null;
56995
- if (funcText === "warn") {
57702
+ if (funcText === "warn")
56996
57703
  return null;
56997
- }
56998
57704
  const args = node.childForFieldName("arguments");
56999
57705
  if (!args)
57000
57706
  return null;
@@ -58213,6 +58919,27 @@ function isMutableInit2(node) {
58213
58919
  function isConstantName(name) {
58214
58920
  return name === name.toUpperCase();
58215
58921
  }
58922
+ function moduleHasLockGuard(moduleNode) {
58923
+ for (let i = 0; i < moduleNode.namedChildCount; i++) {
58924
+ const stmt = moduleNode.namedChild(i);
58925
+ if (!stmt)
58926
+ continue;
58927
+ const inner = stmt.type === "expression_statement" ? stmt.namedChild(0) : stmt;
58928
+ if (inner?.type !== "assignment")
58929
+ continue;
58930
+ const rhs = inner.childForFieldName("right");
58931
+ if (rhs?.type !== "call")
58932
+ continue;
58933
+ const fn = rhs.childForFieldName("function");
58934
+ if (!fn)
58935
+ continue;
58936
+ const text = fn.text;
58937
+ if (text === "threading.Lock" || text === "threading.RLock" || text === "threading.Semaphore" || text === "threading.BoundedSemaphore" || text === "asyncio.Lock" || text === "multiprocessing.Lock" || text === "multiprocessing.RLock" || text === "Lock" || text === "RLock") {
58938
+ return true;
58939
+ }
58940
+ }
58941
+ return false;
58942
+ }
58216
58943
  var pythonSharedMutableModuleStateVisitor;
58217
58944
  var init_shared_mutable_module_state2 = __esm({
58218
58945
  "packages/analyzer/dist/rules/bugs/visitors/python/shared-mutable-module-state.js"() {
@@ -58236,6 +58963,11 @@ var init_shared_mutable_module_state2 = __esm({
58236
58963
  return null;
58237
58964
  if (varName === "__all__")
58238
58965
  return null;
58966
+ let moduleNode = node.parent;
58967
+ while (moduleNode && moduleNode.type !== "module")
58968
+ moduleNode = moduleNode.parent;
58969
+ if (moduleNode && moduleHasLockGuard(moduleNode))
58970
+ return null;
58239
58971
  return makeViolation(this.ruleKey, node, filePath, "high", "Shared mutable state in module scope", `\`${varName}\` is a mutable ${right.type} at module level \u2014 in server environments this state is shared across all requests, causing race conditions and data leaks.`, sourceCode, "Move mutable state inside request handlers or use immutable data structures.");
58240
58972
  }
58241
58973
  };
@@ -59653,9 +60385,31 @@ function extractStringContent(node) {
59653
60385
  }
59654
60386
  return null;
59655
60387
  }
60388
+ function pythonRegexToJs(pattern) {
60389
+ let flags = "";
60390
+ const collectFlag = (f) => {
60391
+ if ("imsu".includes(f) && !flags.includes(f))
60392
+ flags += f;
60393
+ };
60394
+ const prefix = pattern.match(/^\(\?([aiLmsux]+)\)/);
60395
+ if (prefix) {
60396
+ for (const f of prefix[1])
60397
+ collectFlag(f);
60398
+ pattern = pattern.slice(prefix[0].length);
60399
+ }
60400
+ pattern = pattern.replace(/\(\?([aiLmsux]+):/g, (_, flagSet) => {
60401
+ for (const f of flagSet)
60402
+ collectFlag(f);
60403
+ return "(?:";
60404
+ });
60405
+ pattern = pattern.replace(/\(\?P</g, "(?<");
60406
+ pattern = pattern.replace(/\(\?P=([^)]+)\)/g, "\\k<$1>");
60407
+ return { pattern, flags };
60408
+ }
59656
60409
  function isValidRegex(pattern) {
59657
60410
  try {
59658
- new RegExp(pattern);
60411
+ const normalized = pythonRegexToJs(pattern);
60412
+ new RegExp(normalized.pattern, normalized.flags);
59659
60413
  return true;
59660
60414
  } catch {
59661
60415
  return false;
@@ -61398,6 +62152,25 @@ var init_confusing_implicit_concat = __esm({
61398
62152
  }
61399
62153
  if (hasFormatString)
61400
62154
  continue;
62155
+ const isMultiLine = child.startPosition.row !== child.endPosition.row;
62156
+ if (isMultiLine) {
62157
+ const parts = child.namedChildren;
62158
+ let allButLastEndWithSpace = parts.length > 1;
62159
+ for (let i = 0; i < parts.length - 1; i++) {
62160
+ const t2 = parts[i].text;
62161
+ const stripped = t2.replace(/['"]+$/, "");
62162
+ const lastChar = stripped.charAt(stripped.length - 1);
62163
+ if (lastChar !== " " && lastChar !== "\\" && lastChar !== " ") {
62164
+ allButLastEndWithSpace = false;
62165
+ break;
62166
+ }
62167
+ }
62168
+ if (allButLastEndWithSpace)
62169
+ continue;
62170
+ const totalLen = parts.reduce((sum, p2) => sum + p2.text.length, 0);
62171
+ if (totalLen > 60)
62172
+ continue;
62173
+ }
61401
62174
  return makeViolation(this.ruleKey, child, filePath, "medium", "Confusing implicit string concatenation", `Implicit string concatenation \`${child.text.slice(0, 50)}${child.text.length > 50 ? "..." : ""}\` inside a ${node.type.replace(/_/g, " ")} \u2014 adjacent string literals are joined at compile time. If two separate strings were intended, add a comma between them.`, sourceCode, "If you meant two separate strings, add a comma. If intentional concatenation, use explicit `+` or join into a single string.");
61402
62175
  }
61403
62176
  }
@@ -61881,6 +62654,7 @@ var init_static_key_dict_comprehension_ruff = __esm({
61881
62654
  "packages/analyzer/dist/rules/bugs/visitors/python/static-key-dict-comprehension-ruff.js"() {
61882
62655
  "use strict";
61883
62656
  init_types();
62657
+ init_helpers3();
61884
62658
  LITERAL_TYPES2 = /* @__PURE__ */ new Set(["string", "integer", "float", "true", "false", "none"]);
61885
62659
  pythonStaticKeyDictComprehensionRuffVisitor = {
61886
62660
  ruleKey: "bugs/deterministic/static-key-dict-comprehension-ruff",
@@ -61893,7 +62667,7 @@ var init_static_key_dict_comprehension_ruff = __esm({
61893
62667
  const keyNode = pairNode.childForFieldName("key");
61894
62668
  if (!keyNode)
61895
62669
  return null;
61896
- if (LITERAL_TYPES2.has(keyNode.type)) {
62670
+ if (LITERAL_TYPES2.has(keyNode.type) && !isFStringWithInterpolation(keyNode)) {
61897
62671
  return makeViolation(this.ruleKey, keyNode, filePath, "high", "Static key in dict comprehension", `Dict comprehension with constant key \`${keyNode.text}\` \u2014 every iteration overwrites the same key, resulting in a single-entry dict with only the last value.`, sourceCode, "Use a variable expression as the key. If you only need values, use a list comprehension instead.");
61898
62672
  }
61899
62673
  return null;
@@ -63496,7 +64270,16 @@ function findFunctionDefs(root) {
63496
64270
  hasVarArgs = true;
63497
64271
  continue;
63498
64272
  }
63499
- if (p2.type === "identifier" || p2.type === "typed_parameter") {
64273
+ if (p2.type === "typed_parameter") {
64274
+ const inner = p2.namedChild(0);
64275
+ if (inner && (inner.type === "list_splat_pattern" || inner.type === "dictionary_splat_pattern")) {
64276
+ hasVarArgs = true;
64277
+ continue;
64278
+ }
64279
+ required++;
64280
+ continue;
64281
+ }
64282
+ if (p2.type === "identifier") {
63500
64283
  required++;
63501
64284
  } else if (p2.type === "default_parameter" || p2.type === "typed_default_parameter") {
63502
64285
  optional++;
@@ -65482,14 +66265,25 @@ var init_expression_complexity = __esm({
65482
66265
  return null;
65483
66266
  let operatorCount = 0;
65484
66267
  const BINARY_TYPES = /* @__PURE__ */ new Set(["binary_expression", "logical_expression"]);
66268
+ const FUNCTION_BOUNDARY_TYPES = /* @__PURE__ */ new Set([
66269
+ "function_declaration",
66270
+ "function_expression",
66271
+ "arrow_function",
66272
+ "method_definition",
66273
+ "generator_function_declaration",
66274
+ "generator_function"
66275
+ ]);
65485
66276
  function countOps(n) {
65486
66277
  if (BINARY_TYPES.has(n.type)) {
65487
66278
  operatorCount++;
65488
66279
  }
65489
66280
  for (let i = 0; i < n.childCount; i++) {
65490
66281
  const child = n.child(i);
65491
- if (child)
65492
- countOps(child);
66282
+ if (!child)
66283
+ continue;
66284
+ if (FUNCTION_BOUNDARY_TYPES.has(child.type))
66285
+ continue;
66286
+ countOps(child);
65493
66287
  }
65494
66288
  }
65495
66289
  countOps(expr);
@@ -75716,26 +76510,6 @@ var init_and_or_ternary = __esm({
75716
76510
  }
75717
76511
  });
75718
76512
 
75719
- // packages/analyzer/dist/rules/code-quality/visitors/python/any-type-hint.js
75720
- var pythonAnyTypeHintVisitor;
75721
- var init_any_type_hint = __esm({
75722
- "packages/analyzer/dist/rules/code-quality/visitors/python/any-type-hint.js"() {
75723
- "use strict";
75724
- init_types();
75725
- pythonAnyTypeHintVisitor = {
75726
- ruleKey: "code-quality/deterministic/any-type-hint",
75727
- languages: ["python"],
75728
- nodeTypes: ["type"],
75729
- visit(node, filePath, sourceCode) {
75730
- const text = node.text.trim();
75731
- if (text !== "Any")
75732
- return null;
75733
- return makeViolation(this.ruleKey, node, filePath, "medium", "Any used as type hint", "Using `Any` as a type hint defeats type checking \u2014 use a more specific type.", sourceCode, "Replace `Any` with a specific type annotation, or use `object` if truly any type is acceptable.");
75734
- }
75735
- };
75736
- }
75737
- });
75738
-
75739
76513
  // packages/analyzer/dist/rules/code-quality/visitors/python/assert-in-production.js
75740
76514
  var pythonAssertInProductionVisitor;
75741
76515
  var init_assert_in_production = __esm({
@@ -78272,6 +79046,24 @@ var init_implicit_string_concatenation = __esm({
78272
79046
  const grandparent = parent.parent;
78273
79047
  if (!isCollection(parent) && !isCollection(grandparent ?? { type: "" }))
78274
79048
  return null;
79049
+ if (node.startPosition.row !== node.endPosition.row) {
79050
+ const parts = node.namedChildren;
79051
+ let allButLastEndWithSpace = parts.length > 1;
79052
+ for (let i = 0; i < parts.length - 1; i++) {
79053
+ const t2 = parts[i].text;
79054
+ const inner = t2.replace(/['"]+$/, "");
79055
+ const lastChar = inner.charAt(inner.length - 1);
79056
+ if (lastChar !== " " && lastChar !== "\\" && lastChar !== " ") {
79057
+ allButLastEndWithSpace = false;
79058
+ break;
79059
+ }
79060
+ }
79061
+ if (allButLastEndWithSpace)
79062
+ return null;
79063
+ const totalLen = parts.reduce((sum, p2) => sum + p2.text.length, 0);
79064
+ if (totalLen > 60)
79065
+ return null;
79066
+ }
78275
79067
  return makeViolation(this.ruleKey, node, filePath, "high", "Implicit string concatenation in collection", "Adjacent string literals in a collection are implicitly concatenated \u2014 this may be a missing comma.", sourceCode, "Add a comma between the strings, or use explicit `+` concatenation if intentional.");
78276
79068
  }
78277
79069
  };
@@ -82015,6 +82807,14 @@ var init_try_except_continue = __esm({
82015
82807
  return null;
82016
82808
  if (stmts[0].type !== "continue_statement")
82017
82809
  return null;
82810
+ const exprChildren = node.namedChildren.filter((c2) => c2.type !== "block" && c2.type !== "comment");
82811
+ if (exprChildren.length > 0) {
82812
+ const exceptionType = exprChildren[0];
82813
+ const typeText = exceptionType.type === "as_pattern" ? exceptionType.namedChildren[0]?.text : exceptionType.text;
82814
+ if (typeText && typeText !== "Exception" && typeText !== "BaseException") {
82815
+ return null;
82816
+ }
82817
+ }
82018
82818
  return makeViolation(this.ruleKey, node, filePath, "medium", "Silent exception with continue", "`except` block with only `continue` silently ignores errors in loops.", sourceCode, "Add logging or error handling before `continue`, or use `contextlib.suppress()` for intentional suppression.");
82019
82819
  }
82020
82820
  };
@@ -84386,34 +85186,56 @@ var init_ambiguous_unicode_character = __esm({
84386
85186
  "use strict";
84387
85187
  init_types();
84388
85188
  CONFUSABLE_MAP = {
84389
- "\xB4": "'",
84390
- // acute accent → apostrophe
84391
- "\u2018": "'",
84392
- // left single quote → apostrophe
84393
- "\u2019": "'",
84394
- // right single quote → apostrophe
84395
- "\u201C": '"',
84396
- // left double quote → double quote
84397
- "\u201D": '"',
84398
- // right double quote → double quote
84399
- "\u2013": "-",
84400
- // en dash → hyphen
84401
- "\u2014": "--",
84402
- // em dash → double hyphen
84403
- "\xAD": "-",
84404
- // soft hyphen
84405
- "\u2212": "-",
84406
- // minus sign
84407
- "\xD7": "x",
84408
- // multiplication sign
84409
85189
  "\u03BF": "o",
84410
85190
  // Greek small letter omicron
84411
85191
  "\u0430": "a",
84412
85192
  // Cyrillic small a
84413
85193
  "\u0435": "e",
84414
85194
  // Cyrillic small e
84415
- "\u043E": "o"
85195
+ "\u043E": "o",
84416
85196
  // Cyrillic small o
85197
+ "\u0440": "p",
85198
+ // Cyrillic small er
85199
+ "\u0441": "c",
85200
+ // Cyrillic small es
85201
+ "\u0445": "x",
85202
+ // Cyrillic small ha
85203
+ "\u0455": "s",
85204
+ // Cyrillic small dze
85205
+ "\u0456": "i",
85206
+ // Cyrillic small Byelorussian-Ukrainian I
85207
+ "\u0501": "d",
85208
+ // Cyrillic small komi de
85209
+ "\u051B": "q",
85210
+ // Cyrillic small qa
85211
+ "\u051D": "w",
85212
+ // Cyrillic small we
85213
+ "\u0399": "I",
85214
+ // Greek capital iota
85215
+ "\u0396": "Z",
85216
+ // Greek capital zeta
85217
+ "\u039F": "O",
85218
+ // Greek capital omicron
85219
+ "\u0410": "A",
85220
+ // Cyrillic capital A
85221
+ "\u0415": "E",
85222
+ // Cyrillic capital E
85223
+ "\u041A": "K",
85224
+ // Cyrillic capital Ka
85225
+ "\u041C": "M",
85226
+ // Cyrillic capital Em
85227
+ "\u041D": "H",
85228
+ // Cyrillic capital En (renders as H)
85229
+ "\u041E": "O",
85230
+ // Cyrillic capital O
85231
+ "\u0420": "P",
85232
+ // Cyrillic capital er
85233
+ "\u0421": "C",
85234
+ // Cyrillic capital es
85235
+ "\u0422": "T",
85236
+ // Cyrillic capital te
85237
+ "\u0425": "X"
85238
+ // Cyrillic capital ha
84417
85239
  };
84418
85240
  pythonAmbiguousUnicodeCharacterVisitor = {
84419
85241
  ruleKey: "code-quality/deterministic/ambiguous-unicode-character",
@@ -84661,6 +85483,8 @@ var init_regex_char_class_preferred = __esm({
84661
85483
  if (!isReCall4)
84662
85484
  return null;
84663
85485
  const pattern = node.text.slice(1, -1);
85486
+ if (/\(\?[aiLmux]*s[aiLmux]*[):]/.test(pattern))
85487
+ return null;
84664
85488
  if (/\..+?\?/.test(pattern) || /\.\*\?/.test(pattern)) {
84665
85489
  return makeViolation(this.ruleKey, node, filePath, "low", "Reluctant quantifier where character class preferred", "Using `.+?` or `.*?` \u2014 a character class like `[^x]*` is more explicit and efficient than a reluctant quantifier.", sourceCode, "Replace the reluctant quantifier with an explicit character class.");
84666
85490
  }
@@ -85388,6 +86212,62 @@ var init_lambda_reserved_env_var = __esm({
85388
86212
  });
85389
86213
 
85390
86214
  // packages/analyzer/dist/rules/code-quality/visitors/python/boto3-client-error.js
86215
+ function findAwsClientVarNames(root) {
86216
+ const names = /* @__PURE__ */ new Set();
86217
+ function walk(n) {
86218
+ if (n.type === "assignment") {
86219
+ const lhs = n.childForFieldName("left");
86220
+ const rhs = n.childForFieldName("right");
86221
+ if (lhs?.type === "identifier" && rhs?.type === "call") {
86222
+ const fn = rhs.childForFieldName("function");
86223
+ if (fn?.type === "attribute") {
86224
+ const obj = fn.childForFieldName("object");
86225
+ const attr = fn.childForFieldName("attribute");
86226
+ if ((obj?.text === "boto3" || obj?.text === "aiobotocore") && (attr?.text === "client" || attr?.text === "resource")) {
86227
+ names.add(lhs.text);
86228
+ }
86229
+ }
86230
+ }
86231
+ }
86232
+ for (let i = 0; i < n.childCount; i++) {
86233
+ const child = n.child(i);
86234
+ if (child)
86235
+ walk(child);
86236
+ }
86237
+ }
86238
+ walk(root);
86239
+ return names;
86240
+ }
86241
+ function bodyHasAwsCall(body, awsVarNames) {
86242
+ let found = false;
86243
+ function walk(n) {
86244
+ if (found)
86245
+ return;
86246
+ if (n.type === "call") {
86247
+ const fn = n.childForFieldName("function");
86248
+ if (fn?.type === "attribute") {
86249
+ let receiver = fn.childForFieldName("object");
86250
+ while (receiver?.type === "attribute") {
86251
+ receiver = receiver.childForFieldName("object");
86252
+ }
86253
+ if (receiver?.type === "identifier") {
86254
+ const id = receiver.text;
86255
+ if (id === "boto3" || id === "botocore" || id === "aiobotocore" || awsVarNames.has(id)) {
86256
+ found = true;
86257
+ return;
86258
+ }
86259
+ }
86260
+ }
86261
+ }
86262
+ for (let i = 0; i < n.childCount; i++) {
86263
+ const child = n.child(i);
86264
+ if (child)
86265
+ walk(child);
86266
+ }
86267
+ }
86268
+ walk(body);
86269
+ return found;
86270
+ }
85391
86271
  var pythonBoto3ClientErrorVisitor;
85392
86272
  var init_boto3_client_error = __esm({
85393
86273
  "packages/analyzer/dist/rules/code-quality/visitors/python/boto3-client-error.js"() {
@@ -85425,6 +86305,9 @@ var init_boto3_client_error = __esm({
85425
86305
  });
85426
86306
  if (!hasBareOrGeneric)
85427
86307
  return null;
86308
+ const awsVarNames = findAwsClientVarNames(node.tree.rootNode);
86309
+ if (!bodyHasAwsCall(body, awsVarNames))
86310
+ return null;
85428
86311
  return makeViolation(this.ruleKey, node, filePath, "medium", "Uncaught botocore ClientError", "boto3/botocore calls inside try block but `ClientError` is not explicitly caught \u2014 generic `except` hides AWS API errors.", sourceCode, "Add explicit `except botocore.exceptions.ClientError as e:` to handle AWS API errors.");
85429
86312
  }
85430
86313
  };
@@ -86289,7 +87172,6 @@ var init_python7 = __esm({
86289
87172
  init_commented_out_code2();
86290
87173
  init_abstract_class_without_abstract_method();
86291
87174
  init_and_or_ternary();
86292
- init_any_type_hint();
86293
87175
  init_assert_in_production();
86294
87176
  init_async_long_sleep();
86295
87177
  init_async_unused_async();
@@ -86544,7 +87426,6 @@ var init_python7 = __esm({
86544
87426
  pythonCommentedOutCodeVisitor,
86545
87427
  pythonAbstractClassWithoutAbstractMethodVisitor,
86546
87428
  pythonAndOrTernaryVisitor,
86547
- pythonAnyTypeHintVisitor,
86548
87429
  pythonAssertInProductionVisitor,
86549
87430
  pythonAsyncLongSleepVisitor,
86550
87431
  pythonAsyncUnusedAsyncVisitor,
@@ -88925,6 +89806,22 @@ var init_incorrect_dict_iterator = __esm({
88925
89806
  });
88926
89807
 
88927
89808
  // packages/analyzer/dist/rules/performance/visitors/python/try-except-in-loop.js
89809
+ function isTypedSkipOnlyHandler(exceptClause) {
89810
+ const block = exceptClause.namedChildren.find((c2) => c2.type === "block");
89811
+ if (!block)
89812
+ return false;
89813
+ const stmts = block.namedChildren.filter((c2) => c2.type !== "comment");
89814
+ if (stmts.length !== 1)
89815
+ return false;
89816
+ if (stmts[0].type !== "continue_statement" && stmts[0].type !== "pass_statement")
89817
+ return false;
89818
+ const exprChildren = exceptClause.namedChildren.filter((c2) => c2.type !== "block" && c2.type !== "comment");
89819
+ if (exprChildren.length === 0)
89820
+ return false;
89821
+ const exceptionType = exprChildren[0];
89822
+ const typeText = exceptionType.type === "as_pattern" ? exceptionType.namedChildren[0]?.text : exceptionType.text;
89823
+ return typeText !== void 0 && typeText !== "Exception" && typeText !== "BaseException";
89824
+ }
88928
89825
  var tryExceptInLoopVisitor;
88929
89826
  var init_try_except_in_loop = __esm({
88930
89827
  "packages/analyzer/dist/rules/performance/visitors/python/try-except-in-loop.js"() {
@@ -88938,6 +89835,10 @@ var init_try_except_in_loop = __esm({
88938
89835
  visit(node, filePath, sourceCode) {
88939
89836
  if (!isInsidePythonLoop(node))
88940
89837
  return null;
89838
+ const exceptClauses = node.namedChildren.filter((c2) => c2.type === "except_clause");
89839
+ if (exceptClauses.length > 0 && exceptClauses.every(isTypedSkipOnlyHandler)) {
89840
+ return null;
89841
+ }
88941
89842
  return makeViolation(this.ruleKey, node, filePath, "low", "try/except inside loop", "try/except inside a loop adds overhead per iteration. Move the try/except outside the loop if possible.", sourceCode, "Wrap the entire loop in a try/except, or use a conditional check instead of exception handling.");
88942
89843
  }
88943
89844
  };
@@ -89311,6 +90212,9 @@ var init_catch_without_error_type = __esm({
89311
90212
  const hasTypeAnnotation = node.childForFieldName("type") !== null;
89312
90213
  if (hasTypeAnnotation)
89313
90214
  return null;
90215
+ const stmts = body.namedChildren.filter((c2) => c2.type !== "comment");
90216
+ if (stmts.length <= 1)
90217
+ return null;
89314
90218
  return makeViolation(this.ruleKey, node, filePath, "medium", "Catch without error type discrimination", "Catch block does not check or narrow the error type. Different error types may need different handling.", sourceCode, "Use instanceof checks or type guards in the catch block to handle specific error types.");
89315
90219
  }
89316
90220
  };
@@ -89378,6 +90282,28 @@ function isInsidePromiseConstructor(node) {
89378
90282
  }
89379
90283
  return false;
89380
90284
  }
90285
+ function looksLikeAwsLambda(sourceCode) {
90286
+ if (/\baws-lambda\b/.test(sourceCode))
90287
+ return true;
90288
+ if (/\bAPIGatewayProxy(Event|Result|Handler)/.test(sourceCode))
90289
+ return true;
90290
+ if (/\bLambda(Event|Result|Context|Handler)\b/.test(sourceCode))
90291
+ return true;
90292
+ if (/export\s+const\s+handler\s*[:=]\s*async\s*\(/.test(sourceCode))
90293
+ return true;
90294
+ return false;
90295
+ }
90296
+ function looksLikeAwsCdkScript(sourceCode) {
90297
+ if (/\baws-cdk-lib\b/.test(sourceCode))
90298
+ return true;
90299
+ if (/@aws-cdk\//.test(sourceCode))
90300
+ return true;
90301
+ if (/\bnew\s+cdk\.(App|Stack)\b/.test(sourceCode))
90302
+ return true;
90303
+ if (/\bapp\.synth\(\)/.test(sourceCode))
90304
+ return true;
90305
+ return false;
90306
+ }
89381
90307
  var init_helpers7 = __esm({
89382
90308
  "packages/analyzer/dist/rules/reliability/visitors/javascript/_helpers.js"() {
89383
90309
  "use strict";
@@ -89664,7 +90590,11 @@ var init_unchecked_array_access = __esm({
89664
90590
  init_helpers7();
89665
90591
  uncheckedArrayAccessVisitor = {
89666
90592
  ruleKey: "reliability/deterministic/unchecked-array-access",
89667
- languages: ["typescript", "tsx", "javascript"],
90593
+ // TS/TSX only. The rule is meaningful when the project opts into
90594
+ // `noUncheckedIndexedAccess`, which only exists in TypeScript. Plain
90595
+ // JS / JSX has no static type system to opt into - every index access
90596
+ // is implicitly `T | undefined` and flagging it is pure noise.
90597
+ languages: ["typescript", "tsx"],
89668
90598
  nodeTypes: ["subscript_expression"],
89669
90599
  visit(node, filePath, sourceCode) {
89670
90600
  const object = node.childForFieldName("object");
@@ -89960,6 +90890,7 @@ var init_uncaught_exception_no_handler = __esm({
89960
90890
  "packages/analyzer/dist/rules/reliability/visitors/javascript/uncaught-exception-no-handler.js"() {
89961
90891
  "use strict";
89962
90892
  init_types();
90893
+ init_helpers7();
89963
90894
  uncaughtExceptionNoHandlerVisitor = {
89964
90895
  ruleKey: "reliability/deterministic/uncaught-exception-no-handler",
89965
90896
  languages: ["typescript", "tsx", "javascript"],
@@ -89972,6 +90903,10 @@ var init_uncaught_exception_no_handler = __esm({
89972
90903
  if (!lowerPath.includes("index.") && !lowerPath.includes("main.") && !lowerPath.includes("server.") && !lowerPath.includes("app.") && !lowerPath.endsWith("/worker.ts") && !lowerPath.endsWith("/worker.js") && !lowerPath.includes("bin/")) {
89973
90904
  return null;
89974
90905
  }
90906
+ if (looksLikeAwsLambda(sourceCode))
90907
+ return null;
90908
+ if (looksLikeAwsCdkScript(sourceCode))
90909
+ return null;
89975
90910
  const text = sourceCode.replace(/\/\/.*$/gm, "");
89976
90911
  if (text.includes("'uncaughtException'") || text.includes('"uncaughtException"') || text.includes("`uncaughtException`")) {
89977
90912
  return null;
@@ -90024,6 +90959,7 @@ var init_unhandled_rejection_no_handler = __esm({
90024
90959
  "packages/analyzer/dist/rules/reliability/visitors/javascript/unhandled-rejection-no-handler.js"() {
90025
90960
  "use strict";
90026
90961
  init_types();
90962
+ init_helpers7();
90027
90963
  unhandledRejectionNoHandlerVisitor = {
90028
90964
  ruleKey: "reliability/deterministic/unhandled-rejection-no-handler",
90029
90965
  languages: ["typescript", "tsx", "javascript"],
@@ -90036,6 +90972,10 @@ var init_unhandled_rejection_no_handler = __esm({
90036
90972
  if (!lowerPath.includes("index.") && !lowerPath.includes("main.") && !lowerPath.includes("server.") && !lowerPath.includes("app.") && !lowerPath.endsWith("/worker.ts") && !lowerPath.endsWith("/worker.js") && !lowerPath.includes("bin/")) {
90037
90973
  return null;
90038
90974
  }
90975
+ if (looksLikeAwsLambda(sourceCode))
90976
+ return null;
90977
+ if (looksLikeAwsCdkScript(sourceCode))
90978
+ return null;
90039
90979
  const text = sourceCode.replace(/\/\/.*$/gm, "");
90040
90980
  if (text.includes("'unhandledRejection'") || text.includes('"unhandledRejection"') || text.includes("`unhandledRejection`")) {
90041
90981
  return null;
@@ -91352,6 +92292,21 @@ var init_duplicate_import4 = __esm({
91352
92292
  });
91353
92293
 
91354
92294
  // packages/analyzer/dist/rules/architecture/visitors/python/declarations-in-global-scope.js
92295
+ function isTypingConstruct(node) {
92296
+ if (node.type !== "subscript")
92297
+ return false;
92298
+ const value = node.childForFieldName("value");
92299
+ if (!value)
92300
+ return false;
92301
+ if (value.type === "identifier")
92302
+ return TYPING_NAMES.has(value.text);
92303
+ if (value.type === "attribute") {
92304
+ const attr = value.childForFieldName("attribute");
92305
+ if (attr && TYPING_NAMES.has(attr.text))
92306
+ return true;
92307
+ }
92308
+ return false;
92309
+ }
91355
92310
  function rootIsCall(node) {
91356
92311
  if (node.type === "call")
91357
92312
  return true;
@@ -91367,11 +92322,46 @@ function rootIsCall(node) {
91367
92322
  }
91368
92323
  return false;
91369
92324
  }
91370
- var pythonDeclarationsInGlobalScopeVisitor;
92325
+ var TYPING_NAMES, pythonDeclarationsInGlobalScopeVisitor;
91371
92326
  var init_declarations_in_global_scope2 = __esm({
91372
92327
  "packages/analyzer/dist/rules/architecture/visitors/python/declarations-in-global-scope.js"() {
91373
92328
  "use strict";
91374
92329
  init_types();
92330
+ TYPING_NAMES = /* @__PURE__ */ new Set([
92331
+ "Callable",
92332
+ "Optional",
92333
+ "Union",
92334
+ "Literal",
92335
+ "Annotated",
92336
+ "Final",
92337
+ "List",
92338
+ "Tuple",
92339
+ "Dict",
92340
+ "Set",
92341
+ "FrozenSet",
92342
+ "Type",
92343
+ "ClassVar",
92344
+ "Sequence",
92345
+ "Mapping",
92346
+ "MutableMapping",
92347
+ "Iterable",
92348
+ "Iterator",
92349
+ "Generator",
92350
+ "Coroutine",
92351
+ "Awaitable",
92352
+ "AsyncIterable",
92353
+ "AsyncIterator",
92354
+ "Protocol",
92355
+ "TypedDict",
92356
+ "NamedTuple",
92357
+ // Built-in generics (PEP 585)
92358
+ "list",
92359
+ "tuple",
92360
+ "dict",
92361
+ "set",
92362
+ "frozenset",
92363
+ "type"
92364
+ ]);
91375
92365
  pythonDeclarationsInGlobalScopeVisitor = {
91376
92366
  ruleKey: "architecture/deterministic/declarations-in-global-scope",
91377
92367
  languages: ["python"],
@@ -91433,6 +92423,8 @@ var init_declarations_in_global_scope2 = __esm({
91433
92423
  return null;
91434
92424
  if (right?.type === "attribute" && rootIsCall(right))
91435
92425
  return null;
92426
+ if (right?.type === "subscript" && isTypingConstruct(right))
92427
+ return null;
91436
92428
  return makeViolation(this.ruleKey, node, filePath, "medium", "Mutable variable in global scope", `Module-level mutable variable '${name}' creates shared state that is hard to test.`, sourceCode, "Move into a function, class, or use UPPER_CASE for intended constants.");
91437
92429
  }
91438
92430
  };
@@ -91897,13 +92889,47 @@ var init_missing_transaction = __esm({
91897
92889
  });
91898
92890
 
91899
92891
  // packages/analyzer/dist/rules/database/visitors/javascript/unvalidated-external-data.js
91900
- var unvalidatedExternalDataVisitor;
92892
+ function hasOrmLikeReceiver(node) {
92893
+ const fn = node.childForFieldName("function");
92894
+ if (fn?.type !== "member_expression")
92895
+ return false;
92896
+ let receiver = fn.childForFieldName("object");
92897
+ while (receiver?.type === "member_expression") {
92898
+ receiver = receiver.childForFieldName("object");
92899
+ }
92900
+ if (receiver?.type !== "identifier")
92901
+ return false;
92902
+ const name = receiver.text.toLowerCase();
92903
+ return ORM_RECEIVER_NAMES2.has(name);
92904
+ }
92905
+ var AMBIGUOUS_ORM_METHODS, ORM_RECEIVER_NAMES2, unvalidatedExternalDataVisitor;
91901
92906
  var init_unvalidated_external_data = __esm({
91902
92907
  "packages/analyzer/dist/rules/database/visitors/javascript/unvalidated-external-data.js"() {
91903
92908
  "use strict";
91904
92909
  init_types();
91905
92910
  init_helpers9();
91906
92911
  init_javascript_helpers();
92912
+ AMBIGUOUS_ORM_METHODS = /* @__PURE__ */ new Set(["add", "delete"]);
92913
+ ORM_RECEIVER_NAMES2 = /* @__PURE__ */ new Set([
92914
+ "session",
92915
+ "db",
92916
+ "conn",
92917
+ "connection",
92918
+ "cursor",
92919
+ "engine",
92920
+ "database",
92921
+ "manager",
92922
+ "repo",
92923
+ "repository",
92924
+ "orm",
92925
+ "em",
92926
+ "tx",
92927
+ "trx",
92928
+ "knex",
92929
+ "prisma",
92930
+ "sequelize",
92931
+ "mongoose"
92932
+ ]);
91907
92933
  unvalidatedExternalDataVisitor = {
91908
92934
  ruleKey: "database/deterministic/unvalidated-external-data",
91909
92935
  languages: ["typescript", "tsx", "javascript"],
@@ -91913,6 +92939,8 @@ var init_unvalidated_external_data = __esm({
91913
92939
  const methodName = getMethodName(node);
91914
92940
  if (!ORM_WRITE_METHODS2.has(methodName) && !SQL_WRITE_METHODS.has(methodName))
91915
92941
  return null;
92942
+ if (AMBIGUOUS_ORM_METHODS.has(methodName) && !hasOrmLikeReceiver(node))
92943
+ return null;
91916
92944
  const args = node.childForFieldName("arguments");
91917
92945
  if (!args)
91918
92946
  return null;
@@ -92298,6 +93326,9 @@ var init_missing_migration2 = __esm({
92298
93326
  if (!/alter\s+table|create\s+table|drop\s+table|create\s+index|drop\s+index/.test(sqlText)) {
92299
93327
  return null;
92300
93328
  }
93329
+ if (/if\s+(not\s+)?exists/.test(sqlText)) {
93330
+ return null;
93331
+ }
92301
93332
  return makeViolation(this.ruleKey, node, filePath, "high", "Schema change outside migration file", `DDL statement found outside a migration file. Schema changes should be tracked in migrations.`, sourceCode, "Move this schema change into a versioned migration file.");
92302
93333
  }
92303
93334
  };
@@ -92305,6 +93336,122 @@ var init_missing_migration2 = __esm({
92305
93336
  });
92306
93337
 
92307
93338
  // packages/analyzer/dist/rules/database/visitors/python/connection-not-released.js
93339
+ function escapeRegExp2(s) {
93340
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
93341
+ }
93342
+ function isFactoryReturn(node) {
93343
+ let current = node.parent;
93344
+ while (current) {
93345
+ if (current.type === "return_statement")
93346
+ return true;
93347
+ if (current.type === "expression_statement")
93348
+ return false;
93349
+ if (current.type === "function_definition")
93350
+ return false;
93351
+ current = current.parent;
93352
+ }
93353
+ return false;
93354
+ }
93355
+ function isModuleLevelCache(node) {
93356
+ let assign = node.parent;
93357
+ while (assign) {
93358
+ if (assign.type === "assignment")
93359
+ break;
93360
+ if (assign.type === "expression_statement")
93361
+ return false;
93362
+ if (assign.type === "function_definition")
93363
+ return false;
93364
+ if (assign.type === "class_definition")
93365
+ return false;
93366
+ assign = assign.parent;
93367
+ }
93368
+ if (!assign)
93369
+ return false;
93370
+ const stmt = assign.parent;
93371
+ if (!(stmt?.type === "expression_statement" && stmt.parent?.type === "module"))
93372
+ return false;
93373
+ const lhs = assign.childForFieldName("left");
93374
+ const nameNode = lhs?.type === "identifier" ? lhs : null;
93375
+ if (!nameNode)
93376
+ return false;
93377
+ const name = nameNode.text;
93378
+ return /^[A-Z_][A-Z0-9_]*$/.test(name) || name.startsWith("_");
93379
+ }
93380
+ function isInstanceAttributeAssignmentInPrivateClass(node) {
93381
+ let assign = node.parent;
93382
+ while (assign) {
93383
+ if (assign.type === "assignment")
93384
+ break;
93385
+ if (assign.type === "expression_statement")
93386
+ return false;
93387
+ if (assign.type === "function_definition")
93388
+ return false;
93389
+ assign = assign.parent;
93390
+ }
93391
+ if (!assign)
93392
+ return false;
93393
+ const lhs = assign.childForFieldName("left");
93394
+ if (lhs?.type !== "attribute")
93395
+ return false;
93396
+ const obj = lhs.childForFieldName("object");
93397
+ if (!(obj?.type === "identifier" && (obj.text === "self" || obj.text === "cls")))
93398
+ return false;
93399
+ let cls = assign.parent;
93400
+ while (cls) {
93401
+ if (cls.type === "class_definition") {
93402
+ const name = cls.childForFieldName("name");
93403
+ return name?.type === "identifier" && name.text.startsWith("_");
93404
+ }
93405
+ cls = cls.parent;
93406
+ }
93407
+ return false;
93408
+ }
93409
+ function isClosedInFinally(node) {
93410
+ let assignmentParent = node.parent;
93411
+ while (assignmentParent) {
93412
+ if (assignmentParent.type === "assignment")
93413
+ break;
93414
+ if (assignmentParent.type === "expression_statement")
93415
+ return false;
93416
+ if (assignmentParent.type === "function_definition")
93417
+ return false;
93418
+ assignmentParent = assignmentParent.parent;
93419
+ }
93420
+ if (!assignmentParent)
93421
+ return false;
93422
+ const lhs = assignmentParent.childForFieldName("left");
93423
+ if (!lhs || lhs.type !== "identifier")
93424
+ return false;
93425
+ const varName = lhs.text;
93426
+ let func = assignmentParent.parent;
93427
+ while (func) {
93428
+ if (func.type === "function_definition")
93429
+ break;
93430
+ func = func.parent;
93431
+ }
93432
+ if (!func)
93433
+ return false;
93434
+ const body = func.childForFieldName("body");
93435
+ if (!body)
93436
+ return false;
93437
+ const closePattern2 = new RegExp(`\\b${escapeRegExp2(varName)}\\.(?:close|dispose|release)\\s*\\(`);
93438
+ let found = false;
93439
+ function walk(n) {
93440
+ if (found)
93441
+ return;
93442
+ if (n.type === "finally_clause" && closePattern2.test(n.text)) {
93443
+ found = true;
93444
+ return;
93445
+ }
93446
+ for (let i = 0; i < n.childCount; i++) {
93447
+ const ch = n.child(i);
93448
+ if (ch)
93449
+ walk(ch);
93450
+ }
93451
+ }
93452
+ walk(body);
93453
+ return found;
93454
+ }
92308
93455
  var pythonConnectionNotReleasedVisitor;
92309
93456
  var init_connection_not_released2 = __esm({
92310
93457
  "packages/analyzer/dist/rules/database/visitors/python/connection-not-released.js"() {
@@ -92352,6 +93499,14 @@ var init_connection_not_released2 = __esm({
92352
93499
  break;
92353
93500
  current = current.parent;
92354
93501
  }
93502
+ if (isFactoryReturn(node))
93503
+ return null;
93504
+ if (isClosedInFinally(node))
93505
+ return null;
93506
+ if (isModuleLevelCache(node))
93507
+ return null;
93508
+ if (isInstanceAttributeAssignmentInPrivateClass(node))
93509
+ return null;
92355
93510
  return makeViolation(this.ruleKey, node, filePath, "high", "Database connection not released", `${methodName}() acquires a connection but it may not be released on error. Use a context manager (with statement) or try/finally to guarantee the connection is released.`, sourceCode, "Use `with connection:` or a try/finally block to ensure the connection is always released.");
92356
93511
  }
92357
93512
  };
@@ -92435,6 +93590,23 @@ var init_orm_lazy_load_in_loop2 = __esm({
92435
93590
  });
92436
93591
 
92437
93592
  // packages/analyzer/dist/rules/database/visitors/python/missing-transaction.js
93593
+ function hasCursorLikeParam(params) {
93594
+ for (let i = 0; i < params.namedChildCount; i++) {
93595
+ const p2 = params.namedChild(i);
93596
+ if (!p2)
93597
+ continue;
93598
+ let nameNode = null;
93599
+ if (p2.type === "identifier") {
93600
+ nameNode = p2;
93601
+ } else if (p2.type === "typed_parameter" || p2.type === "default_parameter" || p2.type === "typed_default_parameter") {
93602
+ nameNode = p2.childForFieldName("name") ?? p2.namedChild(0);
93603
+ }
93604
+ if (nameNode && nameNode.type === "identifier" && CURSOR_PARAM_NAMES.has(nameNode.text)) {
93605
+ return true;
93606
+ }
93607
+ }
93608
+ return false;
93609
+ }
92438
93610
  function isOrmWriteCall(n) {
92439
93611
  const name = getPythonMethodName(n);
92440
93612
  if (name === "execute" || name === "executemany") {
@@ -92442,7 +93614,7 @@ function isOrmWriteCall(n) {
92442
93614
  const firstArg = args?.namedChildren[0];
92443
93615
  if (firstArg?.type === "string") {
92444
93616
  const sql = firstArg.text.toLowerCase();
92445
- if (/insert|update|delete|alter|create|drop/.test(sql))
93617
+ if (/\b(insert|update|delete|alter|create|drop)\b/.test(sql))
92446
93618
  return true;
92447
93619
  }
92448
93620
  return false;
@@ -92455,26 +93627,26 @@ function isOrmWriteCall(n) {
92455
93627
  if (fn?.type === "attribute") {
92456
93628
  const receiver = fn.childForFieldName("object");
92457
93629
  if (receiver?.type === "identifier") {
92458
- return ORM_RECEIVER_NAMES2.has(receiver.text);
93630
+ return ORM_RECEIVER_NAMES3.has(receiver.text);
92459
93631
  }
92460
93632
  if (receiver?.type === "attribute") {
92461
93633
  const attrText = receiver.childForFieldName("attribute")?.text;
92462
- if (attrText && ORM_RECEIVER_NAMES2.has(attrText))
93634
+ if (attrText && ORM_RECEIVER_NAMES3.has(attrText))
92463
93635
  return true;
92464
93636
  const rootObj = receiver.childForFieldName("object");
92465
- if (rootObj?.type === "identifier" && ORM_RECEIVER_NAMES2.has(rootObj.text))
93637
+ if (rootObj?.type === "identifier" && ORM_RECEIVER_NAMES3.has(rootObj.text))
92466
93638
  return true;
92467
93639
  }
92468
93640
  }
92469
93641
  return false;
92470
93642
  }
92471
- var ORM_RECEIVER_NAMES2, UNAMBIGUOUS_DB_WRITES, pythonMissingTransactionVisitor;
93643
+ var ORM_RECEIVER_NAMES3, UNAMBIGUOUS_DB_WRITES, CURSOR_PARAM_NAMES, pythonMissingTransactionVisitor;
92472
93644
  var init_missing_transaction2 = __esm({
92473
93645
  "packages/analyzer/dist/rules/database/visitors/python/missing-transaction.js"() {
92474
93646
  "use strict";
92475
93647
  init_types();
92476
93648
  init_helpers10();
92477
- ORM_RECEIVER_NAMES2 = /* @__PURE__ */ new Set([
93649
+ ORM_RECEIVER_NAMES3 = /* @__PURE__ */ new Set([
92478
93650
  "session",
92479
93651
  "db",
92480
93652
  "conn",
@@ -92494,6 +93666,17 @@ var init_missing_transaction2 = __esm({
92494
93666
  "bulk_update",
92495
93667
  "executemany"
92496
93668
  ]);
93669
+ CURSOR_PARAM_NAMES = /* @__PURE__ */ new Set([
93670
+ "cur",
93671
+ "cursor",
93672
+ "conn",
93673
+ "connection",
93674
+ "session",
93675
+ "db",
93676
+ "tx",
93677
+ "trans",
93678
+ "transaction"
93679
+ ]);
92497
93680
  pythonMissingTransactionVisitor = {
92498
93681
  ruleKey: "database/deterministic/missing-transaction",
92499
93682
  languages: ["python"],
@@ -92505,8 +93688,18 @@ var init_missing_transaction2 = __esm({
92505
93688
  if (!body)
92506
93689
  return null;
92507
93690
  const bodyText = body.text.toLowerCase();
92508
- if (/transaction|atomic|begin\b/.test(bodyText))
93691
+ if (/transaction|atomic|begin\b|autocommit\s*=\s*false/.test(bodyText))
92509
93692
  return null;
93693
+ if (/\bwith\s+[^:]*\bas\s+(conn|connection|cursor|cur|session|engine|tx|trans)\b[^:]*:/.test(bodyText))
93694
+ return null;
93695
+ const funcDef = body.parent;
93696
+ if (funcDef?.type === "function_definition") {
93697
+ const nameNode = funcDef.childForFieldName("name");
93698
+ const params = funcDef.childForFieldName("parameters");
93699
+ if (nameNode?.text.startsWith("_") && params && hasCursorLikeParam(params)) {
93700
+ return null;
93701
+ }
93702
+ }
92510
93703
  let writeCount = 0;
92511
93704
  let seenSelf = false;
92512
93705
  let isSecondOccurrence = false;
@@ -99283,6 +100476,503 @@ var init_rules_service = __esm({
99283
100476
  }
99284
100477
  });
99285
100478
 
100479
+ // node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js
100480
+ var require_windows = __commonJS({
100481
+ "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js"(exports, module) {
100482
+ module.exports = isexe;
100483
+ isexe.sync = sync;
100484
+ var fs17 = __require("fs");
100485
+ function checkPathExt(path22, options) {
100486
+ var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
100487
+ if (!pathext) {
100488
+ return true;
100489
+ }
100490
+ pathext = pathext.split(";");
100491
+ if (pathext.indexOf("") !== -1) {
100492
+ return true;
100493
+ }
100494
+ for (var i = 0; i < pathext.length; i++) {
100495
+ var p2 = pathext[i].toLowerCase();
100496
+ if (p2 && path22.substr(-p2.length).toLowerCase() === p2) {
100497
+ return true;
100498
+ }
100499
+ }
100500
+ return false;
100501
+ }
100502
+ function checkStat(stat, path22, options) {
100503
+ if (!stat.isSymbolicLink() && !stat.isFile()) {
100504
+ return false;
100505
+ }
100506
+ return checkPathExt(path22, options);
100507
+ }
100508
+ function isexe(path22, options, cb) {
100509
+ fs17.stat(path22, function(er, stat) {
100510
+ cb(er, er ? false : checkStat(stat, path22, options));
100511
+ });
100512
+ }
100513
+ function sync(path22, options) {
100514
+ return checkStat(fs17.statSync(path22), path22, options);
100515
+ }
100516
+ }
100517
+ });
100518
+
100519
+ // node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js
100520
+ var require_mode = __commonJS({
100521
+ "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js"(exports, module) {
100522
+ module.exports = isexe;
100523
+ isexe.sync = sync;
100524
+ var fs17 = __require("fs");
100525
+ function isexe(path22, options, cb) {
100526
+ fs17.stat(path22, function(er, stat) {
100527
+ cb(er, er ? false : checkStat(stat, options));
100528
+ });
100529
+ }
100530
+ function sync(path22, options) {
100531
+ return checkStat(fs17.statSync(path22), options);
100532
+ }
100533
+ function checkStat(stat, options) {
100534
+ return stat.isFile() && checkMode(stat, options);
100535
+ }
100536
+ function checkMode(stat, options) {
100537
+ var mod = stat.mode;
100538
+ var uid = stat.uid;
100539
+ var gid = stat.gid;
100540
+ var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
100541
+ var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
100542
+ var u2 = parseInt("100", 8);
100543
+ var g = parseInt("010", 8);
100544
+ var o = parseInt("001", 8);
100545
+ var ug = u2 | g;
100546
+ var ret = mod & o || mod & g && gid === myGid || mod & u2 && uid === myUid || mod & ug && myUid === 0;
100547
+ return ret;
100548
+ }
100549
+ }
100550
+ });
100551
+
100552
+ // node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js
100553
+ var require_isexe = __commonJS({
100554
+ "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js"(exports, module) {
100555
+ var fs17 = __require("fs");
100556
+ var core2;
100557
+ if (process.platform === "win32" || global.TESTING_WINDOWS) {
100558
+ core2 = require_windows();
100559
+ } else {
100560
+ core2 = require_mode();
100561
+ }
100562
+ module.exports = isexe;
100563
+ isexe.sync = sync;
100564
+ function isexe(path22, options, cb) {
100565
+ if (typeof options === "function") {
100566
+ cb = options;
100567
+ options = {};
100568
+ }
100569
+ if (!cb) {
100570
+ if (typeof Promise !== "function") {
100571
+ throw new TypeError("callback not provided");
100572
+ }
100573
+ return new Promise(function(resolve8, reject) {
100574
+ isexe(path22, options || {}, function(er, is) {
100575
+ if (er) {
100576
+ reject(er);
100577
+ } else {
100578
+ resolve8(is);
100579
+ }
100580
+ });
100581
+ });
100582
+ }
100583
+ core2(path22, options || {}, function(er, is) {
100584
+ if (er) {
100585
+ if (er.code === "EACCES" || options && options.ignoreErrors) {
100586
+ er = null;
100587
+ is = false;
100588
+ }
100589
+ }
100590
+ cb(er, is);
100591
+ });
100592
+ }
100593
+ function sync(path22, options) {
100594
+ try {
100595
+ return core2.sync(path22, options || {});
100596
+ } catch (er) {
100597
+ if (options && options.ignoreErrors || er.code === "EACCES") {
100598
+ return false;
100599
+ } else {
100600
+ throw er;
100601
+ }
100602
+ }
100603
+ }
100604
+ }
100605
+ });
100606
+
100607
+ // node_modules/.pnpm/which@2.0.2/node_modules/which/which.js
100608
+ var require_which = __commonJS({
100609
+ "node_modules/.pnpm/which@2.0.2/node_modules/which/which.js"(exports, module) {
100610
+ var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
100611
+ var path22 = __require("path");
100612
+ var COLON = isWindows ? ";" : ":";
100613
+ var isexe = require_isexe();
100614
+ var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
100615
+ var getPathInfo = (cmd, opt) => {
100616
+ const colon = opt.colon || COLON;
100617
+ const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [""] : [
100618
+ // windows always checks the cwd first
100619
+ ...isWindows ? [process.cwd()] : [],
100620
+ ...(opt.path || process.env.PATH || /* istanbul ignore next: very unusual */
100621
+ "").split(colon)
100622
+ ];
100623
+ const pathExtExe = isWindows ? opt.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "";
100624
+ const pathExt = isWindows ? pathExtExe.split(colon) : [""];
100625
+ if (isWindows) {
100626
+ if (cmd.indexOf(".") !== -1 && pathExt[0] !== "")
100627
+ pathExt.unshift("");
100628
+ }
100629
+ return {
100630
+ pathEnv,
100631
+ pathExt,
100632
+ pathExtExe
100633
+ };
100634
+ };
100635
+ var which = (cmd, opt, cb) => {
100636
+ if (typeof opt === "function") {
100637
+ cb = opt;
100638
+ opt = {};
100639
+ }
100640
+ if (!opt)
100641
+ opt = {};
100642
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
100643
+ const found = [];
100644
+ const step = (i) => new Promise((resolve8, reject) => {
100645
+ if (i === pathEnv.length)
100646
+ return opt.all && found.length ? resolve8(found) : reject(getNotFoundError(cmd));
100647
+ const ppRaw = pathEnv[i];
100648
+ const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
100649
+ const pCmd = path22.join(pathPart, cmd);
100650
+ const p2 = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
100651
+ resolve8(subStep(p2, i, 0));
100652
+ });
100653
+ const subStep = (p2, i, ii) => new Promise((resolve8, reject) => {
100654
+ if (ii === pathExt.length)
100655
+ return resolve8(step(i + 1));
100656
+ const ext2 = pathExt[ii];
100657
+ isexe(p2 + ext2, { pathExt: pathExtExe }, (er, is) => {
100658
+ if (!er && is) {
100659
+ if (opt.all)
100660
+ found.push(p2 + ext2);
100661
+ else
100662
+ return resolve8(p2 + ext2);
100663
+ }
100664
+ return resolve8(subStep(p2, i, ii + 1));
100665
+ });
100666
+ });
100667
+ return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
100668
+ };
100669
+ var whichSync = (cmd, opt) => {
100670
+ opt = opt || {};
100671
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
100672
+ const found = [];
100673
+ for (let i = 0; i < pathEnv.length; i++) {
100674
+ const ppRaw = pathEnv[i];
100675
+ const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
100676
+ const pCmd = path22.join(pathPart, cmd);
100677
+ const p2 = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
100678
+ for (let j2 = 0; j2 < pathExt.length; j2++) {
100679
+ const cur = p2 + pathExt[j2];
100680
+ try {
100681
+ const is = isexe.sync(cur, { pathExt: pathExtExe });
100682
+ if (is) {
100683
+ if (opt.all)
100684
+ found.push(cur);
100685
+ else
100686
+ return cur;
100687
+ }
100688
+ } catch (ex) {
100689
+ }
100690
+ }
100691
+ }
100692
+ if (opt.all && found.length)
100693
+ return found;
100694
+ if (opt.nothrow)
100695
+ return null;
100696
+ throw getNotFoundError(cmd);
100697
+ };
100698
+ module.exports = which;
100699
+ which.sync = whichSync;
100700
+ }
100701
+ });
100702
+
100703
+ // node_modules/.pnpm/path-key@3.1.1/node_modules/path-key/index.js
100704
+ var require_path_key = __commonJS({
100705
+ "node_modules/.pnpm/path-key@3.1.1/node_modules/path-key/index.js"(exports, module) {
100706
+ "use strict";
100707
+ var pathKey = (options = {}) => {
100708
+ const environment = options.env || process.env;
100709
+ const platform = options.platform || process.platform;
100710
+ if (platform !== "win32") {
100711
+ return "PATH";
100712
+ }
100713
+ return Object.keys(environment).reverse().find((key) => key.toUpperCase() === "PATH") || "Path";
100714
+ };
100715
+ module.exports = pathKey;
100716
+ module.exports.default = pathKey;
100717
+ }
100718
+ });
100719
+
100720
+ // node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js
100721
+ var require_resolveCommand = __commonJS({
100722
+ "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
100723
+ "use strict";
100724
+ var path22 = __require("path");
100725
+ var which = require_which();
100726
+ var getPathKey = require_path_key();
100727
+ function resolveCommandAttempt(parsed, withoutPathExt) {
100728
+ const env = parsed.options.env || process.env;
100729
+ const cwd = process.cwd();
100730
+ const hasCustomCwd = parsed.options.cwd != null;
100731
+ const shouldSwitchCwd = hasCustomCwd && process.chdir !== void 0 && !process.chdir.disabled;
100732
+ if (shouldSwitchCwd) {
100733
+ try {
100734
+ process.chdir(parsed.options.cwd);
100735
+ } catch (err) {
100736
+ }
100737
+ }
100738
+ let resolved;
100739
+ try {
100740
+ resolved = which.sync(parsed.command, {
100741
+ path: env[getPathKey({ env })],
100742
+ pathExt: withoutPathExt ? path22.delimiter : void 0
100743
+ });
100744
+ } catch (e) {
100745
+ } finally {
100746
+ if (shouldSwitchCwd) {
100747
+ process.chdir(cwd);
100748
+ }
100749
+ }
100750
+ if (resolved) {
100751
+ resolved = path22.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
100752
+ }
100753
+ return resolved;
100754
+ }
100755
+ function resolveCommand(parsed) {
100756
+ return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true);
100757
+ }
100758
+ module.exports = resolveCommand;
100759
+ }
100760
+ });
100761
+
100762
+ // node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.js
100763
+ var require_escape = __commonJS({
100764
+ "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.js"(exports, module) {
100765
+ "use strict";
100766
+ var metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
100767
+ function escapeCommand(arg) {
100768
+ arg = arg.replace(metaCharsRegExp, "^$1");
100769
+ return arg;
100770
+ }
100771
+ function escapeArgument(arg, doubleEscapeMetaChars) {
100772
+ arg = `${arg}`;
100773
+ arg = arg.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
100774
+ arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
100775
+ arg = `"${arg}"`;
100776
+ arg = arg.replace(metaCharsRegExp, "^$1");
100777
+ if (doubleEscapeMetaChars) {
100778
+ arg = arg.replace(metaCharsRegExp, "^$1");
100779
+ }
100780
+ return arg;
100781
+ }
100782
+ module.exports.command = escapeCommand;
100783
+ module.exports.argument = escapeArgument;
100784
+ }
100785
+ });
100786
+
100787
+ // node_modules/.pnpm/shebang-regex@3.0.0/node_modules/shebang-regex/index.js
100788
+ var require_shebang_regex = __commonJS({
100789
+ "node_modules/.pnpm/shebang-regex@3.0.0/node_modules/shebang-regex/index.js"(exports, module) {
100790
+ "use strict";
100791
+ module.exports = /^#!(.*)/;
100792
+ }
100793
+ });
100794
+
100795
+ // node_modules/.pnpm/shebang-command@2.0.0/node_modules/shebang-command/index.js
100796
+ var require_shebang_command = __commonJS({
100797
+ "node_modules/.pnpm/shebang-command@2.0.0/node_modules/shebang-command/index.js"(exports, module) {
100798
+ "use strict";
100799
+ var shebangRegex = require_shebang_regex();
100800
+ module.exports = (string = "") => {
100801
+ const match2 = string.match(shebangRegex);
100802
+ if (!match2) {
100803
+ return null;
100804
+ }
100805
+ const [path22, argument] = match2[0].replace(/#! ?/, "").split(" ");
100806
+ const binary2 = path22.split("/").pop();
100807
+ if (binary2 === "env") {
100808
+ return argument;
100809
+ }
100810
+ return argument ? `${binary2} ${argument}` : binary2;
100811
+ };
100812
+ }
100813
+ });
100814
+
100815
+ // node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js
100816
+ var require_readShebang = __commonJS({
100817
+ "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
100818
+ "use strict";
100819
+ var fs17 = __require("fs");
100820
+ var shebangCommand = require_shebang_command();
100821
+ function readShebang(command) {
100822
+ const size = 150;
100823
+ const buffer = Buffer.alloc(size);
100824
+ let fd;
100825
+ try {
100826
+ fd = fs17.openSync(command, "r");
100827
+ fs17.readSync(fd, buffer, 0, size, 0);
100828
+ fs17.closeSync(fd);
100829
+ } catch (e) {
100830
+ }
100831
+ return shebangCommand(buffer.toString());
100832
+ }
100833
+ module.exports = readShebang;
100834
+ }
100835
+ });
100836
+
100837
+ // node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js
100838
+ var require_parse = __commonJS({
100839
+ "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js"(exports, module) {
100840
+ "use strict";
100841
+ var path22 = __require("path");
100842
+ var resolveCommand = require_resolveCommand();
100843
+ var escape2 = require_escape();
100844
+ var readShebang = require_readShebang();
100845
+ var isWin = process.platform === "win32";
100846
+ var isExecutableRegExp = /\.(?:com|exe)$/i;
100847
+ var isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
100848
+ function detectShebang(parsed) {
100849
+ parsed.file = resolveCommand(parsed);
100850
+ const shebang = parsed.file && readShebang(parsed.file);
100851
+ if (shebang) {
100852
+ parsed.args.unshift(parsed.file);
100853
+ parsed.command = shebang;
100854
+ return resolveCommand(parsed);
100855
+ }
100856
+ return parsed.file;
100857
+ }
100858
+ function parseNonShell(parsed) {
100859
+ if (!isWin) {
100860
+ return parsed;
100861
+ }
100862
+ const commandFile = detectShebang(parsed);
100863
+ const needsShell = !isExecutableRegExp.test(commandFile);
100864
+ if (parsed.options.forceShell || needsShell) {
100865
+ const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
100866
+ parsed.command = path22.normalize(parsed.command);
100867
+ parsed.command = escape2.command(parsed.command);
100868
+ parsed.args = parsed.args.map((arg) => escape2.argument(arg, needsDoubleEscapeMetaChars));
100869
+ const shellCommand = [parsed.command].concat(parsed.args).join(" ");
100870
+ parsed.args = ["/d", "/s", "/c", `"${shellCommand}"`];
100871
+ parsed.command = process.env.comspec || "cmd.exe";
100872
+ parsed.options.windowsVerbatimArguments = true;
100873
+ }
100874
+ return parsed;
100875
+ }
100876
+ function parse2(command, args, options) {
100877
+ if (args && !Array.isArray(args)) {
100878
+ options = args;
100879
+ args = null;
100880
+ }
100881
+ args = args ? args.slice(0) : [];
100882
+ options = Object.assign({}, options);
100883
+ const parsed = {
100884
+ command,
100885
+ args,
100886
+ options,
100887
+ file: void 0,
100888
+ original: {
100889
+ command,
100890
+ args
100891
+ }
100892
+ };
100893
+ return options.shell ? parsed : parseNonShell(parsed);
100894
+ }
100895
+ module.exports = parse2;
100896
+ }
100897
+ });
100898
+
100899
+ // node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.js
100900
+ var require_enoent = __commonJS({
100901
+ "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.js"(exports, module) {
100902
+ "use strict";
100903
+ var isWin = process.platform === "win32";
100904
+ function notFoundError(original, syscall) {
100905
+ return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), {
100906
+ code: "ENOENT",
100907
+ errno: "ENOENT",
100908
+ syscall: `${syscall} ${original.command}`,
100909
+ path: original.command,
100910
+ spawnargs: original.args
100911
+ });
100912
+ }
100913
+ function hookChildProcess(cp, parsed) {
100914
+ if (!isWin) {
100915
+ return;
100916
+ }
100917
+ const originalEmit = cp.emit;
100918
+ cp.emit = function(name, arg1) {
100919
+ if (name === "exit") {
100920
+ const err = verifyENOENT(arg1, parsed);
100921
+ if (err) {
100922
+ return originalEmit.call(cp, "error", err);
100923
+ }
100924
+ }
100925
+ return originalEmit.apply(cp, arguments);
100926
+ };
100927
+ }
100928
+ function verifyENOENT(status, parsed) {
100929
+ if (isWin && status === 1 && !parsed.file) {
100930
+ return notFoundError(parsed.original, "spawn");
100931
+ }
100932
+ return null;
100933
+ }
100934
+ function verifyENOENTSync(status, parsed) {
100935
+ if (isWin && status === 1 && !parsed.file) {
100936
+ return notFoundError(parsed.original, "spawnSync");
100937
+ }
100938
+ return null;
100939
+ }
100940
+ module.exports = {
100941
+ hookChildProcess,
100942
+ verifyENOENT,
100943
+ verifyENOENTSync,
100944
+ notFoundError
100945
+ };
100946
+ }
100947
+ });
100948
+
100949
+ // node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/index.js
100950
+ var require_cross_spawn = __commonJS({
100951
+ "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/index.js"(exports, module) {
100952
+ "use strict";
100953
+ var cp = __require("child_process");
100954
+ var parse2 = require_parse();
100955
+ var enoent = require_enoent();
100956
+ function spawn6(command, args, options) {
100957
+ const parsed = parse2(command, args, options);
100958
+ const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
100959
+ enoent.hookChildProcess(spawned, parsed);
100960
+ return spawned;
100961
+ }
100962
+ function spawnSync2(command, args, options) {
100963
+ const parsed = parse2(command, args, options);
100964
+ const result = cp.spawnSync(parsed.command, parsed.args, parsed.options);
100965
+ result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
100966
+ return result;
100967
+ }
100968
+ module.exports = spawn6;
100969
+ module.exports.spawn = spawn6;
100970
+ module.exports.sync = spawnSync2;
100971
+ module.exports._parse = parse2;
100972
+ module.exports._enoent = enoent;
100973
+ }
100974
+ });
100975
+
99286
100976
  // node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
99287
100977
  var Node, Queue;
99288
100978
  var init_yocto_queue = __esm({
@@ -102102,15 +103792,15 @@ var init_schemas2 = __esm({
102102
103792
  });
102103
103793
 
102104
103794
  // packages/core/dist/services/llm/cli-provider.js
102105
- import { spawn as spawn3 } from "node:child_process";
102106
- import { mkdirSync as mkdirSync2, writeFileSync } from "node:fs";
103795
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
102107
103796
  import { join as join8 } from "node:path";
102108
103797
  import { tmpdir } from "node:os";
102109
103798
  import { randomUUID as randomUUID3 } from "node:crypto";
102110
- var BaseCLIProvider, ClaudeCodeProvider;
103799
+ var import_cross_spawn, BaseCLIProvider, ClaudeCodeProvider;
102111
103800
  var init_cli_provider = __esm({
102112
103801
  "packages/core/dist/services/llm/cli-provider.js"() {
102113
103802
  "use strict";
103803
+ import_cross_spawn = __toESM(require_cross_spawn(), 1);
102114
103804
  init_logger();
102115
103805
  init_p_limit();
102116
103806
  init_analysis_registry();
@@ -102178,10 +103868,10 @@ var init_cli_provider = __esm({
102178
103868
  return;
102179
103869
  const n = String(++this.callCounter).padStart(2, "0");
102180
103870
  const prefix = join8(this.debugDir, `${n}-${label}`);
102181
- writeFileSync(`${prefix}-input.txt`, prompt, "utf-8");
102182
- writeFileSync(`${prefix}-output.json`, rawOutput, "utf-8");
103871
+ writeFileSync2(`${prefix}-input.txt`, prompt, "utf-8");
103872
+ writeFileSync2(`${prefix}-output.json`, rawOutput, "utf-8");
102183
103873
  if (jsonSchema)
102184
- writeFileSync(`${prefix}-schema.json`, jsonSchema, "utf-8");
103874
+ writeFileSync2(`${prefix}-schema.json`, jsonSchema, "utf-8");
102185
103875
  }
102186
103876
  /** Strip nesting guard env vars so subprocess doesn't detect parent Claude Code. */
102187
103877
  getCleanEnv() {
@@ -102213,7 +103903,7 @@ var init_cli_provider = __esm({
102213
103903
  ...opts?.extraArgs ?? []
102214
103904
  ];
102215
103905
  return new Promise((resolve8, reject) => {
102216
- const child = spawn3(this.binaryName, args, {
103906
+ const child = (0, import_cross_spawn.default)(this.binaryName, args, {
102217
103907
  env: this.getCleanEnv(),
102218
103908
  stdio: ["pipe", "pipe", "pipe"],
102219
103909
  ...this._repoPath ? { cwd: this._repoPath } : {}
@@ -103120,7 +104810,38 @@ function splitIntoBatches(tier, rules, content, fileCount, functionCount, filePa
103120
104810
  }
103121
104811
  return batches;
103122
104812
  }
104813
+ function translateContextRangeError(err, fileContents) {
104814
+ if (!(err instanceof RangeError) || !err.message.includes("Invalid string length")) {
104815
+ throw err;
104816
+ }
104817
+ const sized = [...fileContents.entries()].map(([filePath, fc]) => ({
104818
+ filePath,
104819
+ sizeKb: Math.round(fc.content.length / 1024),
104820
+ lineCount: fc.lineCount,
104821
+ // long single lines are a strong minified-bundle signal
104822
+ maxLineLength: fc.lineCount > 0 ? Math.round(fc.content.length / fc.lineCount) : 0
104823
+ })).sort((a, b) => b.sizeKb - a.sizeKb).slice(0, 5);
104824
+ const list = sized.map((f) => {
104825
+ const minHint = f.maxLineLength > 5e3 ? " [likely minified]" : "";
104826
+ return ` - ${f.filePath} (${f.sizeKb} KB, ${f.lineCount} lines)${minHint}`;
104827
+ }).join("\n");
104828
+ const suggestions = sized.slice(0, 3).map((f) => ` ${f.filePath}`).join("\n");
104829
+ throw new Error(`LLM context exceeded V8's max string length (~512 MB) while preparing rule batches. This is almost always caused by minified bundles, vendored libraries, or generated files.
104830
+
104831
+ Largest files in scope:
104832
+ ${list}
104833
+
104834
+ Add the offending paths to \`.truecourseignore\` at the repo root, e.g.:
104835
+ ${suggestions}`);
104836
+ }
103123
104837
  function estimateContext(rules, fileAnalyses, fileContents, options) {
104838
+ try {
104839
+ return estimateContextInner(rules, fileAnalyses, fileContents, options);
104840
+ } catch (err) {
104841
+ translateContextRangeError(err, fileContents);
104842
+ }
104843
+ }
104844
+ function estimateContextInner(rules, fileAnalyses, fileContents, options) {
103124
104845
  const grouped = groupRulesByContext(rules);
103125
104846
  const tiers = [];
103126
104847
  const useFilePaths = options?.useFilePaths ?? false;
@@ -103179,6 +104900,13 @@ function estimateContext(rules, fileAnalyses, fileContents, options) {
103179
104900
  };
103180
104901
  }
103181
104902
  function routeContext(rules, fileAnalyses, fileContents) {
104903
+ try {
104904
+ return routeContextInner(rules, fileAnalyses, fileContents);
104905
+ } catch (err) {
104906
+ translateContextRangeError(err, fileContents);
104907
+ }
104908
+ }
104909
+ function routeContextInner(rules, fileAnalyses, fileContents) {
103182
104910
  const grouped = groupRulesByContext(rules);
103183
104911
  const batches = [];
103184
104912
  const faByPath = /* @__PURE__ */ new Map();
@@ -103794,24 +105522,39 @@ async function runViolationPipeline(input) {
103794
105522
  tracker?.start(stepKey);
103795
105523
  if (domain === "architecture") {
103796
105524
  tracker?.detail(stepKey, "Service checks...");
105525
+ await new Promise((r) => setImmediate(r));
105526
+ throwIfAborted(signal);
103797
105527
  serviceViolationResults.push(...runDeterministicServiceChecks(result, domainRules));
103798
105528
  tracker?.detail(stepKey, "Module checks...");
105529
+ await new Promise((r) => setImmediate(r));
105530
+ throwIfAborted(signal);
103799
105531
  moduleViolationResults.push(...runDeterministicModuleChecks(result, domainRules));
103800
105532
  tracker?.detail(stepKey, "Method checks...");
105533
+ await new Promise((r) => setImmediate(r));
105534
+ throwIfAborted(signal);
103801
105535
  methodViolationResults.push(...runDeterministicMethodChecks(result, domainRules));
103802
105536
  tracker?.detail(stepKey, "Deterministic checks done");
103803
105537
  }
103804
105538
  }
103805
105539
  const allCodeViolations = [];
103806
105540
  if (enabledCodeRules.length > 0 && filesToScan.length > 0) {
105541
+ const activeCodeDomains = [];
103807
105542
  for (const domain of DOMAIN_ORDER) {
103808
105543
  if (domain === "architecture")
103809
105544
  continue;
103810
105545
  const domainRules = enabledDeterministic.filter((r) => (r.domain ?? "").startsWith(domain));
103811
- if (domainRules.length > 0)
105546
+ if (domainRules.length > 0) {
103812
105547
  tracker?.start(`${domain}`);
105548
+ activeCodeDomains.push(domain);
105549
+ }
103813
105550
  }
103814
105551
  await new Promise((r) => setImmediate(r));
105552
+ const totalFiles = filesToScan.length;
105553
+ let processed = 0;
105554
+ const SPINNER_YIELD_MS = 25;
105555
+ const DETAIL_UPDATE_MS = 100;
105556
+ let lastYieldMs = Date.now();
105557
+ let lastDetailMs = lastYieldMs;
103815
105558
  for (const { filePath, resolve: resolve8 } of filesToScan) {
103816
105559
  try {
103817
105560
  const lang = detectLanguage(filePath);
@@ -103827,6 +105570,23 @@ async function runViolationPipeline(input) {
103827
105570
  allCodeViolations.push(...codeRuleViolations);
103828
105571
  } catch {
103829
105572
  }
105573
+ processed++;
105574
+ if (signal?.aborted)
105575
+ throw new DOMException("Analysis cancelled", "AbortError");
105576
+ const now2 = Date.now();
105577
+ const isLast = processed === totalFiles;
105578
+ if (isLast || now2 - lastDetailMs >= DETAIL_UPDATE_MS) {
105579
+ const detail = `${processed}/${totalFiles} files`;
105580
+ for (const domain of activeCodeDomains)
105581
+ tracker?.detail(domain, detail);
105582
+ lastDetailMs = now2;
105583
+ }
105584
+ if (isLast || now2 - lastYieldMs >= SPINNER_YIELD_MS) {
105585
+ await new Promise((r) => setImmediate(r));
105586
+ lastYieldMs = Date.now();
105587
+ if (signal?.aborted)
105588
+ throw new DOMException("Analysis cancelled", "AbortError");
105589
+ }
103830
105590
  }
103831
105591
  }
103832
105592
  log.info(`[Pipeline] Code scan: ${allCodeViolations.length} violations from ${filesToScan.length} files (${enabledCodeRules.length} det rules, ${enabledLlmCodeRules.length} LLM rules)`);
@@ -105212,7 +106972,6 @@ async function runAdd(options = {}) {
105212
106972
 
105213
106973
  // tools/cli/src/commands/analyze.ts
105214
106974
  init_dist4();
105215
- import { execSync } from "node:child_process";
105216
106975
  import path16 from "node:path";
105217
106976
 
105218
106977
  // packages/core/dist/commands/analyze-in-process.js
@@ -105235,6 +106994,19 @@ init_registry();
105235
106994
  init_project_config();
105236
106995
  init_git();
105237
106996
  init_logger();
106997
+
106998
+ // packages/core/dist/lib/cli-binary.js
106999
+ var import_cross_spawn2 = __toESM(require_cross_spawn(), 1);
107000
+ function isCliBinaryAvailable(binary2) {
107001
+ const result = (0, import_cross_spawn2.sync)(binary2, ["--version"], {
107002
+ stdio: "ignore",
107003
+ timeout: 5e3
107004
+ });
107005
+ return result.status === 0;
107006
+ }
107007
+
107008
+ // tools/cli/src/commands/analyze.ts
107009
+ init_config2();
105238
107010
  init_helpers();
105239
107011
 
105240
107012
  // tools/cli/src/commands/llm-prompt.ts
@@ -105322,14 +107094,13 @@ function showFirstRunNotice() {
105322
107094
 
105323
107095
  // tools/cli/src/commands/analyze.ts
105324
107096
  function ensureClaudeCli() {
105325
- try {
105326
- execSync("which claude", { stdio: "ignore" });
105327
- } catch {
105328
- O2.error(
105329
- "Claude Code CLI not found on PATH. TrueCourse requires the `claude` binary to run analysis.\nInstall it from https://docs.anthropic.com/en/docs/claude-code and try again."
105330
- );
105331
- process.exit(1);
105332
- }
107097
+ const binary2 = config.claudeCodeBinary;
107098
+ if (isCliBinaryAvailable(binary2)) return;
107099
+ O2.error(
107100
+ `Claude Code CLI not found (tried \`${binary2}\`). TrueCourse requires the Claude Code binary to run analysis.
107101
+ Install it from https://docs.anthropic.com/en/docs/claude-code, or set CLAUDE_CODE_BINARY to its name or absolute path if it's installed elsewhere.`
107102
+ );
107103
+ process.exit(1);
105333
107104
  }
105334
107105
  function resolveOrInitProject() {
105335
107106
  const repoDir = resolveRepoDir(process.cwd()) ?? process.cwd();
@@ -105479,7 +107250,16 @@ async function runAnalyze(options = {}) {
105479
107250
  if (payload.steps) renderSteps(payload.steps);
105480
107251
  }, stepDefs);
105481
107252
  const abortController = new AbortController();
105482
- const onSigint = () => abortController.abort();
107253
+ let sigintRequested = false;
107254
+ const onSigint = () => {
107255
+ if (sigintRequested) {
107256
+ process.stderr.write("\nForce quit.\n");
107257
+ process.exit(130);
107258
+ }
107259
+ sigintRequested = true;
107260
+ abortController.abort();
107261
+ process.stderr.write("\nCancelling\u2026 (press Ctrl+C again to force quit)\n");
107262
+ };
105483
107263
  process.on("SIGINT", onSigint);
105484
107264
  try {
105485
107265
  const result = await analyzeInProcess(project, {
@@ -105536,7 +107316,16 @@ async function runAnalyzeDiff(options = {}) {
105536
107316
  if (payload.steps) renderSteps(payload.steps);
105537
107317
  }, stepDefs);
105538
107318
  const abortController = new AbortController();
105539
- const onSigint = () => abortController.abort();
107319
+ let sigintRequested = false;
107320
+ const onSigint = () => {
107321
+ if (sigintRequested) {
107322
+ process.stderr.write("\nForce quit.\n");
107323
+ process.exit(130);
107324
+ }
107325
+ sigintRequested = true;
107326
+ abortController.abort();
107327
+ process.stderr.write("\nCancelling\u2026 (press Ctrl+C again to force quit)\n");
107328
+ };
105540
107329
  process.on("SIGINT", onSigint);
105541
107330
  try {
105542
107331
  const { diff } = await diffInProcess2(project, {
@@ -105591,7 +107380,7 @@ import { fileURLToPath as fileURLToPath5 } from "node:url";
105591
107380
  import fs12 from "node:fs";
105592
107381
  import path17 from "node:path";
105593
107382
  import os5 from "node:os";
105594
- import { execSync as execSync2 } from "node:child_process";
107383
+ import { execSync } from "node:child_process";
105595
107384
 
105596
107385
  // tools/cli/src/commands/service/env.ts
105597
107386
  import fs11 from "node:fs";
@@ -105672,11 +107461,11 @@ var MacOSService = class {
105672
107461
  fs12.mkdirSync(path17.dirname(logPath), { recursive: true });
105673
107462
  const plist = buildPlist(serverPath, logPath, envVars);
105674
107463
  fs12.writeFileSync(PLIST_PATH, plist, "utf-8");
105675
- execSync2(`launchctl load -w "${PLIST_PATH}"`, { stdio: "pipe" });
107464
+ execSync(`launchctl load -w "${PLIST_PATH}"`, { stdio: "pipe" });
105676
107465
  }
105677
107466
  async uninstall() {
105678
107467
  try {
105679
- execSync2(`launchctl unload "${PLIST_PATH}"`, { stdio: "pipe" });
107468
+ execSync(`launchctl unload "${PLIST_PATH}"`, { stdio: "pipe" });
105680
107469
  } catch {
105681
107470
  }
105682
107471
  if (fs12.existsSync(PLIST_PATH)) {
@@ -105687,17 +107476,17 @@ var MacOSService = class {
105687
107476
  if (!fs12.existsSync(PLIST_PATH)) {
105688
107477
  throw new Error("Service is not installed. Run 'truecourse service install' first.");
105689
107478
  }
105690
- execSync2(`launchctl load -w "${PLIST_PATH}"`, { stdio: "pipe" });
107479
+ execSync(`launchctl load -w "${PLIST_PATH}"`, { stdio: "pipe" });
105691
107480
  }
105692
107481
  async stop() {
105693
107482
  try {
105694
- execSync2(`launchctl unload "${PLIST_PATH}"`, { stdio: "pipe" });
107483
+ execSync(`launchctl unload "${PLIST_PATH}"`, { stdio: "pipe" });
105695
107484
  } catch {
105696
107485
  }
105697
107486
  }
105698
107487
  async status() {
105699
107488
  try {
105700
- const output = execSync2(`launchctl list ${SERVICE_LABEL}`, {
107489
+ const output = execSync(`launchctl list ${SERVICE_LABEL}`, {
105701
107490
  stdio: ["pipe", "pipe", "pipe"],
105702
107491
  encoding: "utf-8"
105703
107492
  });
@@ -105722,7 +107511,7 @@ var MacOSService = class {
105722
107511
  import fs13 from "node:fs";
105723
107512
  import path18 from "node:path";
105724
107513
  import os6 from "node:os";
105725
- import { execSync as execSync3 } from "node:child_process";
107514
+ import { execSync as execSync2 } from "node:child_process";
105726
107515
  var SERVICE_NAME = "truecourse";
105727
107516
  var UNIT_DIR = path18.join(os6.homedir(), ".config", "systemd", "user");
105728
107517
  var UNIT_PATH = path18.join(UNIT_DIR, `${SERVICE_NAME}.service`);
@@ -105752,35 +107541,35 @@ var LinuxService = class {
105752
107541
  fs13.mkdirSync(path18.dirname(logPath), { recursive: true });
105753
107542
  const unit = buildUnitFile(serverPath, logPath);
105754
107543
  fs13.writeFileSync(UNIT_PATH, unit, "utf-8");
105755
- execSync3("systemctl --user daemon-reload", { stdio: "pipe" });
105756
- execSync3(`systemctl --user enable ${SERVICE_NAME}`, { stdio: "pipe" });
107544
+ execSync2("systemctl --user daemon-reload", { stdio: "pipe" });
107545
+ execSync2(`systemctl --user enable ${SERVICE_NAME}`, { stdio: "pipe" });
105757
107546
  }
105758
107547
  async uninstall() {
105759
107548
  try {
105760
- execSync3(`systemctl --user stop ${SERVICE_NAME}`, { stdio: "pipe" });
107549
+ execSync2(`systemctl --user stop ${SERVICE_NAME}`, { stdio: "pipe" });
105761
107550
  } catch {
105762
107551
  }
105763
107552
  try {
105764
- execSync3(`systemctl --user disable ${SERVICE_NAME}`, { stdio: "pipe" });
107553
+ execSync2(`systemctl --user disable ${SERVICE_NAME}`, { stdio: "pipe" });
105765
107554
  } catch {
105766
107555
  }
105767
107556
  if (fs13.existsSync(UNIT_PATH)) {
105768
107557
  fs13.unlinkSync(UNIT_PATH);
105769
107558
  }
105770
107559
  try {
105771
- execSync3("systemctl --user daemon-reload", { stdio: "pipe" });
107560
+ execSync2("systemctl --user daemon-reload", { stdio: "pipe" });
105772
107561
  } catch {
105773
107562
  }
105774
107563
  }
105775
107564
  async start() {
105776
- execSync3(`systemctl --user start ${SERVICE_NAME}`, { stdio: "pipe" });
107565
+ execSync2(`systemctl --user start ${SERVICE_NAME}`, { stdio: "pipe" });
105777
107566
  }
105778
107567
  async stop() {
105779
- execSync3(`systemctl --user stop ${SERVICE_NAME}`, { stdio: "pipe" });
107568
+ execSync2(`systemctl --user stop ${SERVICE_NAME}`, { stdio: "pipe" });
105780
107569
  }
105781
107570
  async status() {
105782
107571
  try {
105783
- const output = execSync3(
107572
+ const output = execSync2(
105784
107573
  `systemctl --user show ${SERVICE_NAME} --property=ActiveState,MainPID`,
105785
107574
  { stdio: ["pipe", "pipe", "pipe"], encoding: "utf-8" }
105786
107575
  );
@@ -105799,7 +107588,7 @@ var LinuxService = class {
105799
107588
  };
105800
107589
 
105801
107590
  // tools/cli/src/commands/service/windows.ts
105802
- import { execSync as execSync4 } from "node:child_process";
107591
+ import { execSync as execSync3 } from "node:child_process";
105803
107592
  var SERVICE_NAME2 = "TrueCourse";
105804
107593
  var WindowsService = class {
105805
107594
  svc;
@@ -105849,14 +107638,14 @@ var WindowsService = class {
105849
107638
  });
105850
107639
  }
105851
107640
  async start() {
105852
- execSync4(`sc.exe start ${SERVICE_NAME2}`, { stdio: "pipe" });
107641
+ execSync3(`sc.exe start ${SERVICE_NAME2}`, { stdio: "pipe" });
105853
107642
  }
105854
107643
  async stop() {
105855
- execSync4(`sc.exe stop ${SERVICE_NAME2}`, { stdio: "pipe" });
107644
+ execSync3(`sc.exe stop ${SERVICE_NAME2}`, { stdio: "pipe" });
105856
107645
  }
105857
107646
  async status() {
105858
107647
  try {
105859
- const output = execSync4(`sc.exe query ${SERVICE_NAME2}`, {
107648
+ const output = execSync3(`sc.exe query ${SERVICE_NAME2}`, {
105860
107649
  stdio: ["pipe", "pipe", "pipe"],
105861
107650
  encoding: "utf-8"
105862
107651
  });
@@ -105872,7 +107661,7 @@ var WindowsService = class {
105872
107661
  }
105873
107662
  async isInstalled() {
105874
107663
  try {
105875
- execSync4(`sc.exe query ${SERVICE_NAME2}`, { stdio: "pipe" });
107664
+ execSync3(`sc.exe query ${SERVICE_NAME2}`, { stdio: "pipe" });
105876
107665
  return true;
105877
107666
  } catch {
105878
107667
  return false;
@@ -106462,7 +108251,7 @@ async function runRulesLlm(options) {
106462
108251
  }
106463
108252
 
106464
108253
  // tools/cli/src/commands/hooks.ts
106465
- import { execSync as execSync5 } from "node:child_process";
108254
+ import { execSync as execSync4 } from "node:child_process";
106466
108255
  import fs16 from "node:fs";
106467
108256
  import path21 from "node:path";
106468
108257
 
@@ -109290,7 +111079,7 @@ async function runHooksRun() {
109290
111079
  }
109291
111080
  let hasStaged = false;
109292
111081
  try {
109293
- const output = execSync5("git diff --cached --name-only --diff-filter=ACM", {
111082
+ const output = execSync4("git diff --cached --name-only --diff-filter=ACM", {
109294
111083
  encoding: "utf-8",
109295
111084
  cwd: projectRoot
109296
111085
  }).trim();
@@ -109367,7 +111156,7 @@ async function runHooksRun() {
109367
111156
 
109368
111157
  // tools/cli/src/index.ts
109369
111158
  var program2 = new Command();
109370
- program2.name("truecourse").version("0.5.7").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
111159
+ program2.name("truecourse").version("0.5.8-windows.2").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
109371
111160
  var dashboardCmd = program2.command("dashboard").description("Start the TrueCourse dashboard and open it in your browser").option("--reconfigure", "Re-prompt for console vs background service mode").option("--service", "Run as a background service (skips mode prompt)").option("--console", "Run in this terminal (skips mode prompt)").action(async (options) => {
109372
111161
  if (options.service && options.console) {
109373
111162
  console.error("error: --service and --console are mutually exclusive");