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/server.mjs CHANGED
@@ -40750,7 +40750,12 @@ var init_language_config = __esm({
40750
40750
  classQuery: `
40751
40751
  (class_declaration) @class
40752
40752
  (class) @class
40753
- `
40753
+ `,
40754
+ // Skip pre-built/minified bundles. They have no analytical value (build
40755
+ // artifacts of source already in the repo or vendored libs) and a single
40756
+ // ~1MB minified line can blow past V8's max string length when the LLM
40757
+ // context router extracts thousands of arrow_function bodies from it.
40758
+ ignorePatterns: ["**/*.min.js", "**/*.min.cjs", "**/*.min.mjs"]
40754
40759
  };
40755
40760
  PYTHON_CONFIG = {
40756
40761
  name: "python",
@@ -40793,9 +40798,55 @@ var init_language_config = __esm({
40793
40798
  });
40794
40799
 
40795
40800
  // packages/analyzer/dist/file-discovery.js
40796
- import { existsSync as existsSync2, readFileSync, readdirSync, statSync as statSync2 } from "fs";
40801
+ import { existsSync as existsSync2, openSync, readFileSync, readSync, readdirSync, statSync as statSync2, closeSync } from "fs";
40797
40802
  import { execFileSync } from "child_process";
40798
40803
  import { join, relative, resolve } from "path";
40804
+ function looksLikeBuildArtifact(absPath) {
40805
+ const slash = absPath.lastIndexOf("/");
40806
+ const basename2 = slash >= 0 ? absPath.slice(slash + 1) : absPath;
40807
+ return /\.(bundle|production|development)\.(js|cjs|mjs|jsx|ts|tsx|mts|cts)$/i.test(basename2);
40808
+ }
40809
+ function looksMinified(absPath) {
40810
+ const dotIdx = absPath.lastIndexOf(".");
40811
+ if (dotIdx < 0)
40812
+ return false;
40813
+ const ext2 = absPath.slice(dotIdx);
40814
+ if (!SNIFFED_EXTS.has(ext2))
40815
+ return false;
40816
+ if (looksLikeBuildArtifact(absPath))
40817
+ return true;
40818
+ let size;
40819
+ try {
40820
+ size = statSync2(absPath).size;
40821
+ } catch {
40822
+ return false;
40823
+ }
40824
+ if (size < MIN_SIZE_FOR_SNIFF)
40825
+ return false;
40826
+ let fd = null;
40827
+ try {
40828
+ fd = openSync(absPath, "r");
40829
+ const buf = Buffer.alloc(SNIFF_BYTES);
40830
+ const read = readSync(fd, buf, 0, SNIFF_BYTES, 0);
40831
+ let newlines = 0;
40832
+ for (let i = 0; i < read; i++) {
40833
+ if (buf[i] === 10)
40834
+ newlines++;
40835
+ if (newlines >= MIN_NEWLINES)
40836
+ return false;
40837
+ }
40838
+ return true;
40839
+ } catch {
40840
+ return false;
40841
+ } finally {
40842
+ if (fd !== null) {
40843
+ try {
40844
+ closeSync(fd);
40845
+ } catch {
40846
+ }
40847
+ }
40848
+ }
40849
+ }
40799
40850
  function isInsideGitWorkTree(dir) {
40800
40851
  try {
40801
40852
  const out = execFileSync("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], {
@@ -40843,8 +40894,11 @@ function discoverFilesViaGit(dir) {
40843
40894
  }
40844
40895
  if (!isFile)
40845
40896
  continue;
40846
- if (detectLanguage(abs))
40847
- out.push(abs);
40897
+ if (!detectLanguage(abs))
40898
+ continue;
40899
+ if (looksMinified(abs))
40900
+ continue;
40901
+ out.push(abs);
40848
40902
  }
40849
40903
  return out;
40850
40904
  }
@@ -40933,8 +40987,11 @@ function discoverFilesViaWalker(dir) {
40933
40987
  if (stat.isDirectory()) {
40934
40988
  traverse(fullPath);
40935
40989
  } else if (stat.isFile()) {
40936
- if (detectLanguage(fullPath))
40937
- files.push(fullPath);
40990
+ if (!detectLanguage(fullPath))
40991
+ continue;
40992
+ if (looksMinified(fullPath))
40993
+ continue;
40994
+ files.push(fullPath);
40938
40995
  }
40939
40996
  }
40940
40997
  } catch {
@@ -40949,12 +41006,16 @@ function discoverFiles(dir) {
40949
41006
  return viaGit;
40950
41007
  return discoverFilesViaWalker(dir);
40951
41008
  }
40952
- var import_ignore;
41009
+ var import_ignore, SNIFFED_EXTS, MIN_SIZE_FOR_SNIFF, SNIFF_BYTES, MIN_NEWLINES;
40953
41010
  var init_file_discovery = __esm({
40954
41011
  "packages/analyzer/dist/file-discovery.js"() {
40955
41012
  "use strict";
40956
41013
  import_ignore = __toESM(require_ignore(), 1);
40957
41014
  init_language_config();
41015
+ SNIFFED_EXTS = /* @__PURE__ */ new Set([".js", ".jsx", ".cjs", ".mjs", ".ts", ".tsx", ".mts", ".cts"]);
41016
+ MIN_SIZE_FOR_SNIFF = 15 * 1024;
41017
+ SNIFF_BYTES = 8 * 1024;
41018
+ MIN_NEWLINES = 4;
40958
41019
  }
40959
41020
  });
40960
41021
 
@@ -42724,6 +42785,15 @@ var init_typescript = __esm({
42724
42785
 
42725
42786
  // packages/analyzer/dist/extractors/languages/javascript.js
42726
42787
  import { Query as Query3 } from "web-tree-sitter";
42788
+ function isNestedInFunction2(node2) {
42789
+ let current = node2.parent;
42790
+ while (current) {
42791
+ if (FUNCTION_NODE_TYPES2.has(current.type))
42792
+ return true;
42793
+ current = current.parent;
42794
+ }
42795
+ return false;
42796
+ }
42727
42797
  function extractFunctionName2(node2) {
42728
42798
  const nameNode = node2.childForFieldName("name");
42729
42799
  if (nameNode?.text)
@@ -42731,6 +42801,8 @@ function extractFunctionName2(node2) {
42731
42801
  const parent = node2.parent;
42732
42802
  if (!parent)
42733
42803
  return "anonymous";
42804
+ if (isNestedInFunction2(node2))
42805
+ return "anonymous";
42734
42806
  if (parent.type === "variable_declarator") {
42735
42807
  const varName = parent.childForFieldName("name");
42736
42808
  if (varName?.text)
@@ -42749,25 +42821,15 @@ function extractFunctionName2(node2) {
42749
42821
  return prop.text;
42750
42822
  }
42751
42823
  }
42752
- if (parent.type === "arguments") {
42753
- const callNode = parent.parent;
42754
- if (callNode?.type === "call_expression") {
42755
- const callee = callNode.childForFieldName("function");
42756
- if (callee?.type === "member_expression") {
42757
- const method = callee.childForFieldName("property");
42758
- if (method?.text)
42759
- return `${method.text}_handler`;
42760
- }
42761
- }
42762
- }
42763
42824
  return "anonymous";
42764
42825
  }
42765
42826
  function isExported2(node2) {
42766
42827
  let current = node2.parent;
42767
42828
  while (current) {
42768
- if (current.type === "export_statement") {
42829
+ if (current.type === "export_statement")
42769
42830
  return true;
42770
- }
42831
+ if (FUNCTION_NODE_TYPES2.has(current.type))
42832
+ return false;
42771
42833
  current = current.parent;
42772
42834
  }
42773
42835
  return false;
@@ -43112,12 +43174,22 @@ function extractJavaScriptExports(tree, _filePath) {
43112
43174
  }
43113
43175
  return exports;
43114
43176
  }
43177
+ var FUNCTION_NODE_TYPES2;
43115
43178
  var init_javascript = __esm({
43116
43179
  "packages/analyzer/dist/extractors/languages/javascript.js"() {
43117
43180
  "use strict";
43118
43181
  init_language_config();
43119
43182
  init_parser();
43120
43183
  init_common();
43184
+ FUNCTION_NODE_TYPES2 = /* @__PURE__ */ new Set([
43185
+ "function_declaration",
43186
+ "function",
43187
+ "function_expression",
43188
+ "arrow_function",
43189
+ "generator_function_declaration",
43190
+ "generator_function",
43191
+ "method_definition"
43192
+ ]);
43121
43193
  }
43122
43194
  });
43123
43195
 
@@ -43150,7 +43222,7 @@ function computePythonFunctionMetrics(node2) {
43150
43222
  walkNesting(bodyNode, 0);
43151
43223
  return { lineCount, statementCount, maxNestingDepth: maxNestingDepth2 };
43152
43224
  }
43153
- function isNestedInFunction2(node2) {
43225
+ function isNestedInFunction3(node2) {
43154
43226
  let current = node2.parent;
43155
43227
  while (current) {
43156
43228
  if (current.type === "function_definition")
@@ -43217,7 +43289,7 @@ function extractPythonFunctions(tree, filePath) {
43217
43289
  const name = node2.childForFieldName("name")?.text;
43218
43290
  if (!name)
43219
43291
  return;
43220
- if (isNestedInFunction2(node2))
43292
+ if (isNestedInFunction3(node2))
43221
43293
  return;
43222
43294
  const paramsNode = node2.childForFieldName("parameters");
43223
43295
  const params = extractPythonParameters(paramsNode);
@@ -63082,6 +63154,19 @@ function isPropertyAccess(node2) {
63082
63154
  }
63083
63155
  if (parent.type === "keyword_argument" && parent.childForFieldName("name")?.id === node2.id)
63084
63156
  return true;
63157
+ if (parent.type === "jsx_attribute" && parent.namedChild(0)?.id === node2.id)
63158
+ return true;
63159
+ if (parent.type === "jsx_opening_element" || parent.type === "jsx_closing_element" || parent.type === "jsx_self_closing_element") {
63160
+ const tagName = parent.childForFieldName("name");
63161
+ if (tagName?.id === node2.id) {
63162
+ const text = node2.text;
63163
+ if (text.length > 0) {
63164
+ const first2 = text[0];
63165
+ if (first2 === first2.toLowerCase() || text.includes("-"))
63166
+ return true;
63167
+ }
63168
+ }
63169
+ }
63085
63170
  return false;
63086
63171
  }
63087
63172
  function isJsDeclarationPosition(node2) {
@@ -63115,6 +63200,10 @@ function isJsDeclarationPosition(node2) {
63115
63200
  }
63116
63201
  if (parent.type === "catch_clause")
63117
63202
  return true;
63203
+ if (parent.type === "for_in_statement" && parent.childForFieldName("left")?.id === node2.id)
63204
+ return true;
63205
+ if (parent.type === "arrow_function" && parent.childForFieldName("parameter")?.id === node2.id)
63206
+ return true;
63118
63207
  return false;
63119
63208
  }
63120
63209
  function isPyDeclarationPosition(node2) {
@@ -63254,9 +63343,9 @@ function buildScopeTree(rootNode, language) {
63254
63343
  return;
63255
63344
  }
63256
63345
  if ((parent.type === "function_declaration" || parent.type === "generator_function_declaration") && parent.childForFieldName("name")?.id === node2.id) {
63257
- const targetScope = findFunctionScope(scope);
63258
- if (!targetScope.variables.has(node2.text)) {
63259
- createVariable(node2.text, "function", node2, targetScope, false);
63346
+ const enclosing = scope.parent ? findFunctionScope(scope.parent) : scope;
63347
+ if (!enclosing.variables.has(node2.text)) {
63348
+ createVariable(node2.text, "function", node2, enclosing, false);
63260
63349
  }
63261
63350
  return;
63262
63351
  }
@@ -63276,6 +63365,13 @@ function buildScopeTree(rootNode, language) {
63276
63365
  }
63277
63366
  return;
63278
63367
  }
63368
+ if (parent.type === "arrow_function" && parent.childForFieldName("parameter")?.id === node2.id) {
63369
+ const funcScope = nodeToScope.get(parent.id);
63370
+ if (funcScope && !funcScope.variables.has(node2.text)) {
63371
+ createVariable(node2.text, "parameter", node2, funcScope, false);
63372
+ }
63373
+ return;
63374
+ }
63279
63375
  if (parent.type === "rest_pattern" || parent.type === "rest_element") {
63280
63376
  const funcNode = findFunctionAncestor(node2);
63281
63377
  if (funcNode) {
@@ -63298,6 +63394,68 @@ function buildScopeTree(rootNode, language) {
63298
63394
  return;
63299
63395
  }
63300
63396
  }
63397
+ if (parent.type === "array_pattern" || parent.type === "object_pattern") {
63398
+ if (isInFormalParameters(node2)) {
63399
+ const funcNode = findFunctionAncestor(node2);
63400
+ if (funcNode) {
63401
+ const funcScope = nodeToScope.get(funcNode.id);
63402
+ if (funcScope && !funcScope.variables.has(node2.text)) {
63403
+ createVariable(node2.text, "parameter", node2, funcScope, false);
63404
+ }
63405
+ }
63406
+ return;
63407
+ }
63408
+ let ancestor = parent.parent;
63409
+ let declarator = null;
63410
+ let forIn = null;
63411
+ while (ancestor) {
63412
+ if (ancestor.type === "variable_declarator") {
63413
+ declarator = ancestor;
63414
+ break;
63415
+ }
63416
+ if (ancestor.type === "for_in_statement" && ancestor.childForFieldName("left")?.id === parent.id) {
63417
+ forIn = ancestor;
63418
+ break;
63419
+ }
63420
+ if (ancestor.type === "function_declaration" || ancestor.type === "function_expression" || ancestor.type === "arrow_function" || ancestor.type === "method_definition" || ancestor.type === "program")
63421
+ break;
63422
+ ancestor = ancestor.parent;
63423
+ }
63424
+ if (declarator) {
63425
+ const grandparent = declarator.parent;
63426
+ if (grandparent?.type === "variable_declaration") {
63427
+ const targetScope = findFunctionScope(scope);
63428
+ if (!targetScope.variables.has(node2.text)) {
63429
+ createVariable(node2.text, "var", node2, targetScope, false);
63430
+ }
63431
+ } else if (grandparent?.type === "lexical_declaration") {
63432
+ const keyword = grandparent.child(0)?.text;
63433
+ const kind = keyword === "const" ? "const" : "let";
63434
+ if (!scope.variables.has(node2.text)) {
63435
+ createVariable(node2.text, kind, node2, scope, false);
63436
+ }
63437
+ }
63438
+ return;
63439
+ }
63440
+ if (forIn) {
63441
+ const kindText = forIn.childForFieldName("kind")?.text;
63442
+ const kind = kindText === "var" ? "var" : kindText === "const" ? "const" : kindText === "let" ? "let" : "for-variable";
63443
+ const targetScope = kind === "var" ? findFunctionScope(scope) : scope;
63444
+ if (!targetScope.variables.has(node2.text)) {
63445
+ createVariable(node2.text, kind, node2, targetScope, false);
63446
+ }
63447
+ return;
63448
+ }
63449
+ }
63450
+ if (parent.type === "for_in_statement" && parent.childForFieldName("left")?.id === node2.id) {
63451
+ const kindText = parent.childForFieldName("kind")?.text;
63452
+ const kind = kindText === "var" ? "var" : kindText === "const" ? "const" : kindText === "let" ? "let" : "for-variable";
63453
+ const targetScope = kind === "var" ? findFunctionScope(scope) : scope;
63454
+ if (!targetScope.variables.has(node2.text)) {
63455
+ createVariable(node2.text, kind, node2, targetScope, false);
63456
+ }
63457
+ return;
63458
+ }
63301
63459
  if (parent.type === "shorthand_property_identifier_pattern") {
63302
63460
  if (isInFormalParameters(node2)) {
63303
63461
  const funcNode = findFunctionAncestor(node2);
@@ -63638,16 +63796,27 @@ function buildScopeTree(rootNode, language) {
63638
63796
  if (isJs && node2.type === "shorthand_property_identifier_pattern") {
63639
63797
  let ancestor = node2.parent;
63640
63798
  let declarator = null;
63799
+ let isParam = false;
63800
+ let funcNode = null;
63641
63801
  while (ancestor) {
63642
63802
  if (ancestor.type === "variable_declarator") {
63643
63803
  declarator = ancestor;
63644
63804
  break;
63645
63805
  }
63646
- if (ancestor.type === "formal_parameters" || ancestor.type === "function_declaration" || ancestor.type === "arrow_function" || ancestor.type === "method_definition") {
63806
+ 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") {
63807
+ isParam = true;
63808
+ funcNode = ancestor.type === "formal_parameters" ? ancestor.parent : ancestor;
63647
63809
  break;
63648
63810
  }
63649
63811
  ancestor = ancestor.parent;
63650
63812
  }
63813
+ if (isParam && funcNode) {
63814
+ const funcScope = nodeToScope.get(funcNode.id);
63815
+ const name = node2.text;
63816
+ if (funcScope && !funcScope.variables.has(name)) {
63817
+ createVariable(name, "parameter", node2, funcScope, false);
63818
+ }
63819
+ }
63651
63820
  if (declarator) {
63652
63821
  const grandparent = declarator.parent;
63653
63822
  const name = node2.text;
@@ -63717,7 +63886,7 @@ var init_known_globals = __esm({
63717
63886
  "packages/analyzer/dist/data-flow/known-globals.js"() {
63718
63887
  "use strict";
63719
63888
  JS_GLOBALS = /* @__PURE__ */ new Set([
63720
- // Browser
63889
+ // Browser — global namespaces
63721
63890
  "window",
63722
63891
  "document",
63723
63892
  "navigator",
@@ -63725,19 +63894,109 @@ var init_known_globals = __esm({
63725
63894
  "history",
63726
63895
  "localStorage",
63727
63896
  "sessionStorage",
63897
+ "screen",
63898
+ "performance",
63899
+ "crypto",
63900
+ "caches",
63901
+ "indexedDB",
63902
+ "matchMedia",
63903
+ // Browser — networking / fetch
63728
63904
  "fetch",
63729
63905
  "XMLHttpRequest",
63730
63906
  "WebSocket",
63907
+ "EventSource",
63731
63908
  "URL",
63732
63909
  "URLSearchParams",
63910
+ "Headers",
63911
+ "Request",
63912
+ "Response",
63913
+ "FormData",
63914
+ "Blob",
63915
+ "File",
63916
+ "FileReader",
63917
+ "FileList",
63918
+ "AbortController",
63919
+ "AbortSignal",
63920
+ "ReadableStream",
63921
+ "WritableStream",
63922
+ "TransformStream",
63923
+ // Browser — events
63924
+ "Event",
63925
+ "CustomEvent",
63926
+ "EventTarget",
63927
+ "MessageEvent",
63928
+ "ErrorEvent",
63929
+ "CloseEvent",
63930
+ "ProgressEvent",
63931
+ "PointerEvent",
63932
+ "KeyboardEvent",
63933
+ "MouseEvent",
63934
+ "TouchEvent",
63935
+ "DragEvent",
63936
+ "WheelEvent",
63937
+ "FocusEvent",
63938
+ "InputEvent",
63939
+ "StorageEvent",
63940
+ "PopStateEvent",
63941
+ "HashChangeEvent",
63942
+ "BeforeUnloadEvent",
63943
+ // Browser — DOM
63944
+ "Node",
63945
+ "Element",
63946
+ "HTMLElement",
63947
+ "HTMLInputElement",
63948
+ "HTMLButtonElement",
63949
+ "HTMLFormElement",
63950
+ "HTMLSelectElement",
63951
+ "HTMLTextAreaElement",
63952
+ "HTMLAnchorElement",
63953
+ "HTMLImageElement",
63954
+ "HTMLCanvasElement",
63955
+ "HTMLVideoElement",
63956
+ "HTMLAudioElement",
63957
+ "HTMLDivElement",
63958
+ "HTMLSpanElement",
63959
+ "HTMLScriptElement",
63960
+ "HTMLStyleElement",
63961
+ "HTMLTemplateElement",
63962
+ "HTMLIFrameElement",
63963
+ "HTMLDialogElement",
63964
+ "DocumentFragment",
63965
+ "ShadowRoot",
63966
+ "Text",
63967
+ "Comment",
63968
+ "Range",
63969
+ "Selection",
63970
+ "MutationObserver",
63971
+ "IntersectionObserver",
63972
+ "ResizeObserver",
63973
+ "PerformanceObserver",
63974
+ "NodeList",
63975
+ "HTMLCollection",
63976
+ "DOMTokenList",
63977
+ "CanvasRenderingContext2D",
63978
+ "OffscreenCanvas",
63979
+ // Browser — timers / scheduling
63733
63980
  "setTimeout",
63734
63981
  "setInterval",
63735
63982
  "clearTimeout",
63736
63983
  "clearInterval",
63737
63984
  "requestAnimationFrame",
63985
+ "cancelAnimationFrame",
63986
+ "requestIdleCallback",
63987
+ "cancelIdleCallback",
63988
+ // Browser — dialogs
63738
63989
  "alert",
63739
63990
  "confirm",
63740
63991
  "prompt",
63992
+ // Browser — workers / messaging
63993
+ "Worker",
63994
+ "SharedWorker",
63995
+ "ServiceWorker",
63996
+ "BroadcastChannel",
63997
+ "MessageChannel",
63998
+ "MessagePort",
63999
+ "postMessage",
63741
64000
  // Node.js
63742
64001
  "process",
63743
64002
  "global",
@@ -64111,8 +64370,13 @@ function buildDataFlowContext(rootNode, language) {
64111
64370
  continue;
64112
64371
  if (v.useSites.length === 0)
64113
64372
  continue;
64373
+ if (v.declarationNode.parent?.type === "named_expression")
64374
+ continue;
64375
+ const directUseSites = v.useSites.filter((u) => !isInNestedFunctionScope(u.scope, v.scope));
64376
+ if (directUseSites.length === 0)
64377
+ continue;
64114
64378
  const declPos = v.declarationNode.startIndex;
64115
- const earliestUse = Math.min(...v.useSites.map((u) => u.node.startIndex));
64379
+ const earliestUse = Math.min(...directUseSites.map((u) => u.node.startIndex));
64116
64380
  if (earliestUse < declPos) {
64117
64381
  result.push(v);
64118
64382
  }
@@ -64120,6 +64384,17 @@ function buildDataFlowContext(rootNode, language) {
64120
64384
  cachedUsedBeforeDefined = result;
64121
64385
  return result;
64122
64386
  }
64387
+ function isInNestedFunctionScope(useScope, declScope) {
64388
+ if (useScope === declScope)
64389
+ return false;
64390
+ let cursor = useScope;
64391
+ while (cursor && cursor !== declScope) {
64392
+ if (cursor.kind === "function")
64393
+ return true;
64394
+ cursor = cursor.parent;
64395
+ }
64396
+ return false;
64397
+ }
64123
64398
  function unusedVariables() {
64124
64399
  if (cachedUnused)
64125
64400
  return cachedUnused;
@@ -64703,7 +64978,72 @@ var init_insecure_cookie = __esm({
64703
64978
  });
64704
64979
 
64705
64980
  // packages/analyzer/dist/rules/security/visitors/javascript/disabled-auto-escaping.js
64706
- var disabledAutoEscapingVisitor;
64981
+ function isStaticStringLiteral(node2) {
64982
+ if (node2.type === "string")
64983
+ return true;
64984
+ if (node2.type === "template_string") {
64985
+ for (let i = 0; i < node2.namedChildCount; i++) {
64986
+ const child = node2.namedChild(i);
64987
+ if (child?.type === "template_substitution")
64988
+ return false;
64989
+ }
64990
+ return true;
64991
+ }
64992
+ return false;
64993
+ }
64994
+ function isEscapeCall(node2) {
64995
+ if (node2.type !== "call_expression")
64996
+ return false;
64997
+ const fn = node2.childForFieldName("function");
64998
+ if (!fn)
64999
+ return false;
65000
+ if (fn.type === "identifier")
65001
+ return ESCAPE_HELPER_NAMES.has(fn.text);
65002
+ if (fn.type === "member_expression") {
65003
+ const prop = fn.childForFieldName("property");
65004
+ if (prop && ESCAPE_HELPER_NAMES.has(prop.text))
65005
+ return true;
65006
+ const obj = fn.childForFieldName("object");
65007
+ if (obj?.type === "identifier" && /purify/i.test(obj.text))
65008
+ return true;
65009
+ }
65010
+ return false;
65011
+ }
65012
+ function isFullyEscapedRhs(node2) {
65013
+ if (node2.type === "string")
65014
+ return true;
65015
+ if (node2.type === "number")
65016
+ return true;
65017
+ if (node2.type === "true" || node2.type === "false" || node2.type === "null" || node2.type === "undefined")
65018
+ return true;
65019
+ if (isEscapeCall(node2))
65020
+ return true;
65021
+ if (node2.type === "template_string") {
65022
+ for (let i = 0; i < node2.namedChildCount; i++) {
65023
+ const child = node2.namedChild(i);
65024
+ if (!child)
65025
+ continue;
65026
+ if (child.type === "template_substitution") {
65027
+ const inner = child.namedChild(0);
65028
+ if (!inner || !isFullyEscapedRhs(inner))
65029
+ return false;
65030
+ }
65031
+ }
65032
+ return true;
65033
+ }
65034
+ if (node2.type === "binary_expression") {
65035
+ const op = node2.children.find((c) => c.text === "+");
65036
+ if (!op)
65037
+ return false;
65038
+ const lhs = node2.childForFieldName("left");
65039
+ const rhs = node2.childForFieldName("right");
65040
+ if (!lhs || !rhs)
65041
+ return false;
65042
+ return isFullyEscapedRhs(lhs) && isFullyEscapedRhs(rhs);
65043
+ }
65044
+ return false;
65045
+ }
65046
+ var disabledAutoEscapingVisitor, ESCAPE_HELPER_NAMES;
64707
65047
  var init_disabled_auto_escaping = __esm({
64708
65048
  "packages/analyzer/dist/rules/security/visitors/javascript/disabled-auto-escaping.js"() {
64709
65049
  "use strict";
@@ -64724,6 +65064,11 @@ var init_disabled_auto_escaping = __esm({
64724
65064
  if (left?.type === "member_expression") {
64725
65065
  const prop = left.childForFieldName("property");
64726
65066
  if (prop?.text === "innerHTML") {
65067
+ const right = node2.childForFieldName("right");
65068
+ if (right && isStaticStringLiteral(right))
65069
+ return null;
65070
+ if (right && isFullyEscapedRhs(right))
65071
+ return null;
64727
65072
  return makeViolation(this.ruleKey, node2, filePath, "high", "Disabled auto-escaping", "Direct innerHTML assignment can lead to XSS vulnerabilities.", sourceCode, "Use textContent instead of innerHTML, or sanitize input with DOMPurify.");
64728
65073
  }
64729
65074
  }
@@ -64731,6 +65076,19 @@ var init_disabled_auto_escaping = __esm({
64731
65076
  return null;
64732
65077
  }
64733
65078
  };
65079
+ ESCAPE_HELPER_NAMES = /* @__PURE__ */ new Set([
65080
+ "esc",
65081
+ "escape",
65082
+ "escapeHtml",
65083
+ "escapeHTML",
65084
+ "escapeHtmlEntities",
65085
+ "htmlEscape",
65086
+ "htmlEntities",
65087
+ "sanitize",
65088
+ "sanitizeHtml",
65089
+ "sanitizeHTML",
65090
+ "purify"
65091
+ ]);
64734
65092
  }
64735
65093
  });
64736
65094
 
@@ -68345,6 +68703,18 @@ var init_javascript3 = __esm({
68345
68703
  });
68346
68704
 
68347
68705
  // packages/analyzer/dist/rules/security/visitors/python/sql-injection.js
68706
+ function hasStringOperand(binNode) {
68707
+ for (let i = 0; i < binNode.namedChildCount; i++) {
68708
+ const child = binNode.namedChild(i);
68709
+ if (!child)
68710
+ continue;
68711
+ if (child.type === "string")
68712
+ return true;
68713
+ if (child.type === "binary_operator" && hasStringOperand(child))
68714
+ return true;
68715
+ }
68716
+ return false;
68717
+ }
68348
68718
  var PYTHON_QUERY_METHODS, pythonSqlInjectionVisitor;
68349
68719
  var init_sql_injection2 = __esm({
68350
68720
  "packages/analyzer/dist/rules/security/visitors/python/sql-injection.js"() {
@@ -68398,7 +68768,9 @@ var init_sql_injection2 = __esm({
68398
68768
  if (firstArg.type === "binary_operator") {
68399
68769
  const op = firstArg.children.find((c) => c.text === "+");
68400
68770
  if (op) {
68401
- return makeViolation(this.ruleKey, node2, 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.");
68771
+ if (hasStringOperand(firstArg)) {
68772
+ return makeViolation(this.ruleKey, node2, 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.");
68773
+ }
68402
68774
  }
68403
68775
  }
68404
68776
  return null;
@@ -70350,6 +70722,40 @@ var init_paramiko_call = __esm({
70350
70722
  });
70351
70723
 
70352
70724
  // packages/analyzer/dist/rules/security/visitors/python/suspicious-url-open.js
70725
+ function isConfigSourcedVar(node2, varName) {
70726
+ let func = node2.parent;
70727
+ while (func) {
70728
+ if (func.type === "function_definition")
70729
+ break;
70730
+ func = func.parent;
70731
+ }
70732
+ let scope = func ? func.childForFieldName("body") : node2.tree.rootNode;
70733
+ if (!scope)
70734
+ return false;
70735
+ let found = false;
70736
+ function walk(n) {
70737
+ if (found)
70738
+ return;
70739
+ if (n.type === "assignment") {
70740
+ const lhs = n.childForFieldName("left");
70741
+ const rhs = n.childForFieldName("right");
70742
+ if (lhs?.type === "identifier" && lhs.text === varName && rhs) {
70743
+ const text = rhs.text;
70744
+ if (/\bos\.getenv\b|\bos\.environ\b|\bconfig\.|\bsettings\.|\bSettings\(\)|\bConfig\(\)/.test(text)) {
70745
+ found = true;
70746
+ return;
70747
+ }
70748
+ }
70749
+ }
70750
+ for (let i = 0; i < n.childCount; i++) {
70751
+ const ch = n.child(i);
70752
+ if (ch)
70753
+ walk(ch);
70754
+ }
70755
+ }
70756
+ walk(scope);
70757
+ return found;
70758
+ }
70353
70759
  var pythonSuspiciousUrlOpenVisitor;
70354
70760
  var init_suspicious_url_open = __esm({
70355
70761
  "packages/analyzer/dist/rules/security/visitors/python/suspicious-url-open.js"() {
@@ -70383,10 +70789,11 @@ var init_suspicious_url_open = __esm({
70383
70789
  const firstArg = args.namedChildren[0];
70384
70790
  if (!firstArg)
70385
70791
  return null;
70386
- if (firstArg.type !== "string" || firstArg.text.startsWith("f")) {
70387
- return makeViolation(this.ruleKey, node2, 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().");
70388
- }
70389
- return null;
70792
+ if (firstArg.type === "string" && !firstArg.text.startsWith("f"))
70793
+ return null;
70794
+ if (firstArg.type === "identifier" && isConfigSourcedVar(node2, firstArg.text))
70795
+ return null;
70796
+ return makeViolation(this.ruleKey, node2, 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().");
70390
70797
  }
70391
70798
  };
70392
70799
  }
@@ -72844,8 +73251,15 @@ var init_exclusions = __esm({
72844
73251
  // All asterisks (masked values)
72845
73252
  /^x+$/i,
72846
73253
  // All x's (placeholder)
72847
- /^\.{3,}$/
73254
+ /^\.{3,}$/,
72848
73255
  // Ellipsis placeholders
73256
+ // Filenames with a recognized document / media / archive / data
73257
+ // extension. Composed object keys like `<hash>__<date>__<batch>.pdf`
73258
+ // look like high-entropy tokens to pattern detectors, but the
73259
+ // trailing extension makes them clearly filenames. The optional
73260
+ // trailing slash matches S3 path prefixes that act as directories
73261
+ // (`processed/.../<file>.pdf/source_pdfs/...`).
73262
+ /\.(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
72849
73263
  ];
72850
73264
  }
72851
73265
  });
@@ -74650,7 +75064,7 @@ var init_secret_scanner = __esm({
74650
75064
  function stripCommentMarkers(text) {
74651
75065
  return text.replace(/^\/\/\s?/, "").replace(/^\/\*\s?/, "").replace(/\s?\*\/$/, "").replace(/^\s*\*\s?/gm, "").replace(/^#\s?/gm, "").trim();
74652
75066
  }
74653
- 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;
75067
+ 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;
74654
75068
  var init_universal = __esm({
74655
75069
  "packages/analyzer/dist/rules/security/visitors/universal.js"() {
74656
75070
  "use strict";
@@ -74668,6 +75082,15 @@ var init_universal = __esm({
74668
75082
  if (parent?.type === "pair" && parent.childForFieldName("key")?.id === node2.id) {
74669
75083
  return null;
74670
75084
  }
75085
+ if (parent?.type === "expression_statement") {
75086
+ const grandParent = parent.parent;
75087
+ if (grandParent && (grandParent.type === "module" || grandParent.type === "block")) {
75088
+ const firstChild = grandParent.namedChild(0);
75089
+ if (firstChild && firstChild.id === parent.id) {
75090
+ return null;
75091
+ }
75092
+ }
75093
+ }
74671
75094
  const match2 = scanForSecrets(stripped);
74672
75095
  if (match2) {
74673
75096
  return makeViolation(this.ruleKey, node2, 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.");
@@ -74682,7 +75105,11 @@ var init_universal = __esm({
74682
75105
  const secretNames = ["password", "passwd", "secret", "apikey", "api_key", "token", "auth_token", "access_token", "private_key"];
74683
75106
  const isNonSecretName = /(?:uri|url|endpoint|type|scope|name|header|grant|method)/.test(name);
74684
75107
  const isNonSecretValue = /https?:\/\//.test(stripped) || /^(true|false|null|undefined|localhost|None|True|False|Bearer)$/i.test(stripped) || /[[\]<>{}()#.=\s]/.test(stripped);
74685
- if (secretNames.some((s) => name.includes(s)) && stripped.length >= 8 && !isNonSecretName && !isNonSecretValue) {
75108
+ const nameClean = name.replace(/^['"`]+|['"`]+$/g, "");
75109
+ const valueClean = stripped.toLowerCase();
75110
+ const isPlainAlphaSnake = /^[a-z_]+$/.test(valueClean);
75111
+ const isVocabularyTag = isPlainAlphaSnake && nameClean.length >= 4 && (valueClean === nameClean || valueClean.includes(nameClean));
75112
+ if (secretNames.some((s) => name.includes(s)) && stripped.length >= 8 && !isNonSecretName && !isNonSecretValue && !isVocabularyTag) {
74686
75113
  return makeViolation(this.ruleKey, node2, 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.");
74687
75114
  }
74688
75115
  }
@@ -75696,21 +76123,28 @@ var init_universal = __esm({
75696
76123
  return null;
75697
76124
  }
75698
76125
  };
75699
- DB_CONNECTION_PATTERNS = [
75700
- /(?:mysql|postgresql|postgres|mongodb|sqlite|mssql|oracle):\/\/[^:]+:([^@]+)@/i,
76126
+ DB_URL_PATTERN = /(?:mysql|postgresql|postgres|mongodb|sqlite|mssql|oracle):\/\/[^:]+:([^@]+)@/i;
76127
+ DB_PASSWORD_KV_PATTERNS = [
75701
76128
  /password\s*=\s*['"][^'"]{4,}['"]/i,
75702
76129
  /pwd\s*=\s*['"][^'"]{4,}['"]/i
75703
76130
  ];
76131
+ NON_LITERAL_PASSWORD_SEGMENT = /^\{[^{}]*\}$|^\$\{[^}]*\}$|^%s$|^%\([^)]+\)s$|^<[^>]+>$|^\$\([^)]*\)$|^\$[A-Z_][A-Z0-9_]*$|process\.env/i;
75704
76132
  hardcodedDatabasePasswordVisitor = {
75705
76133
  ruleKey: "security/deterministic/hardcoded-database-password",
75706
76134
  nodeTypes: ["string", "template_string"],
75707
76135
  visit(node2, filePath, sourceCode) {
75708
76136
  const text = node2.text;
75709
76137
  const stripped = text.replace(/^[fFbBrRuU]*['"`]{1,3}|['"`]{1,3}$/g, "");
75710
- for (const pattern of DB_CONNECTION_PATTERNS) {
76138
+ if (/\$\{|%s|<password>|\$\(|process\.env/i.test(stripped))
76139
+ return null;
76140
+ const urlMatch = DB_URL_PATTERN.exec(stripped);
76141
+ if (urlMatch) {
76142
+ if (NON_LITERAL_PASSWORD_SEGMENT.test(urlMatch[1]))
76143
+ return null;
76144
+ return makeViolation(this.ruleKey, node2, filePath, "critical", "Hardcoded database password", "Database connection string contains a hardcoded password.", sourceCode, "Load database credentials from environment variables.");
76145
+ }
76146
+ for (const pattern of DB_PASSWORD_KV_PATTERNS) {
75711
76147
  if (pattern.test(stripped)) {
75712
- if (/\$\{|%s|<password>|\$\(|process\.env/i.test(stripped))
75713
- return null;
75714
76148
  return makeViolation(this.ruleKey, node2, filePath, "critical", "Hardcoded database password", "Database connection string contains a hardcoded password.", sourceCode, "Load database credentials from environment variables.");
75715
76149
  }
75716
76150
  }
@@ -77686,6 +78120,78 @@ function isKeyFromControlledIteration(assignmentNode, keyName) {
77686
78120
  }
77687
78121
  return false;
77688
78122
  }
78123
+ function isKeyFromForOfLoopVarMember(assignmentNode, keyName) {
78124
+ let scope = assignmentNode.parent;
78125
+ while (scope) {
78126
+ if (scope.type === "function_declaration" || scope.type === "function_expression" || scope.type === "arrow_function" || scope.type === "method_definition" || scope.type === "program")
78127
+ break;
78128
+ scope = scope.parent;
78129
+ }
78130
+ if (!scope)
78131
+ return false;
78132
+ const forVarNames = /* @__PURE__ */ new Set();
78133
+ function collectForVars(n) {
78134
+ if (n.type === "for_in_statement") {
78135
+ const left = n.childForFieldName("left");
78136
+ if (left?.type === "identifier")
78137
+ forVarNames.add(left.text);
78138
+ }
78139
+ if (n !== scope && (n.type === "function_declaration" || n.type === "function_expression" || n.type === "arrow_function" || n.type === "method_definition"))
78140
+ return;
78141
+ for (let i = 0; i < n.childCount; i++) {
78142
+ const ch = n.child(i);
78143
+ if (ch)
78144
+ collectForVars(ch);
78145
+ }
78146
+ }
78147
+ collectForVars(scope);
78148
+ if (forVarNames.size === 0)
78149
+ return false;
78150
+ let found = false;
78151
+ function findAssignment(n) {
78152
+ if (found)
78153
+ return;
78154
+ if (n.type === "variable_declarator") {
78155
+ const name = n.childForFieldName("name");
78156
+ const value = n.childForFieldName("value");
78157
+ if (name?.type === "identifier" && name.text === keyName && value?.type === "member_expression") {
78158
+ const obj = value.childForFieldName("object");
78159
+ if (obj?.type === "identifier" && forVarNames.has(obj.text)) {
78160
+ found = true;
78161
+ return;
78162
+ }
78163
+ }
78164
+ }
78165
+ if (n !== scope && (n.type === "function_declaration" || n.type === "function_expression" || n.type === "arrow_function" || n.type === "method_definition"))
78166
+ return;
78167
+ for (let i = 0; i < n.childCount; i++) {
78168
+ const ch = n.child(i);
78169
+ if (ch)
78170
+ findAssignment(ch);
78171
+ }
78172
+ }
78173
+ findAssignment(scope);
78174
+ return found;
78175
+ }
78176
+ function isAssignmentGuardedByKeyInObject(assignmentNode, keyName, objText) {
78177
+ let cursor = assignmentNode.parent;
78178
+ while (cursor) {
78179
+ if (cursor.type === "if_statement") {
78180
+ const cond = cursor.childForFieldName("condition");
78181
+ if (cond) {
78182
+ const condText = cond.text;
78183
+ const pattern = new RegExp(`\\b${escapeRegExp(keyName)}\\s+in\\s+${escapeRegExp(objText)}\\b`);
78184
+ if (pattern.test(condText))
78185
+ return true;
78186
+ }
78187
+ }
78188
+ cursor = cursor.parent;
78189
+ }
78190
+ return false;
78191
+ }
78192
+ function escapeRegExp(s) {
78193
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
78194
+ }
77689
78195
  function isObjectEntriesOrKeys(node2) {
77690
78196
  if (node2.type === "call_expression") {
77691
78197
  const fn = node2.childForFieldName("function");
@@ -77699,6 +78205,77 @@ function isObjectEntriesOrKeys(node2) {
77699
78205
  }
77700
78206
  return false;
77701
78207
  }
78208
+ function isKeyDestructuredParam(assignmentNode, keyName) {
78209
+ let scope = assignmentNode.parent;
78210
+ while (scope) {
78211
+ if (scope.type === "function_declaration" || scope.type === "function_expression" || scope.type === "arrow_function" || scope.type === "method_definition") {
78212
+ const params = scope.childForFieldName("parameters") ?? scope.childForFieldName("parameter");
78213
+ if (params && containsDestructuredName(params, keyName))
78214
+ return true;
78215
+ }
78216
+ if (scope.type === "program")
78217
+ break;
78218
+ scope = scope.parent;
78219
+ }
78220
+ return false;
78221
+ }
78222
+ function containsDestructuredName(params, keyName) {
78223
+ let found = false;
78224
+ function walk(n) {
78225
+ if (found)
78226
+ return;
78227
+ if (n.type === "identifier" && n.text === keyName) {
78228
+ const parent = n.parent;
78229
+ if (parent?.type === "array_pattern" || parent?.type === "object_pattern") {
78230
+ found = true;
78231
+ return;
78232
+ }
78233
+ if (parent?.type === "shorthand_property_identifier_pattern" && n.text === keyName) {
78234
+ found = true;
78235
+ return;
78236
+ }
78237
+ }
78238
+ for (let i = 0; i < n.childCount; i++) {
78239
+ const child = n.child(i);
78240
+ if (child)
78241
+ walk(child);
78242
+ }
78243
+ }
78244
+ walk(params);
78245
+ return found;
78246
+ }
78247
+ function isKeyAssignedFromLocalCall(assignmentNode, keyName) {
78248
+ let scope = assignmentNode.parent;
78249
+ while (scope) {
78250
+ if (scope.type === "function_declaration" || scope.type === "function_expression" || scope.type === "arrow_function" || scope.type === "method_definition" || scope.type === "program")
78251
+ break;
78252
+ scope = scope.parent;
78253
+ }
78254
+ if (!scope)
78255
+ return false;
78256
+ let found = false;
78257
+ function walk(n) {
78258
+ if (found)
78259
+ return;
78260
+ if (n.type === "variable_declarator") {
78261
+ const name = n.childForFieldName("name");
78262
+ const value = n.childForFieldName("value");
78263
+ if (name?.type === "identifier" && name.text === keyName && value?.type === "call_expression") {
78264
+ found = true;
78265
+ return;
78266
+ }
78267
+ }
78268
+ if (n !== scope && (n.type === "function_declaration" || n.type === "function_expression" || n.type === "arrow_function" || n.type === "method_definition"))
78269
+ return;
78270
+ for (let i = 0; i < n.childCount; i++) {
78271
+ const child = n.child(i);
78272
+ if (child)
78273
+ walk(child);
78274
+ }
78275
+ }
78276
+ walk(scope);
78277
+ return found;
78278
+ }
77702
78279
  var prototypePollutionVisitor;
77703
78280
  var init_prototype_pollution = __esm({
77704
78281
  "packages/analyzer/dist/rules/bugs/visitors/javascript/prototype-pollution.js"() {
@@ -77729,6 +78306,14 @@ var init_prototype_pollution = __esm({
77729
78306
  const keyName = index.text;
77730
78307
  if (isKeyFromControlledIteration(node2, keyName))
77731
78308
  return null;
78309
+ if (isKeyAssignedFromLocalCall(node2, keyName))
78310
+ return null;
78311
+ if (isKeyDestructuredParam(node2, keyName))
78312
+ return null;
78313
+ if (isKeyFromForOfLoopVarMember(node2, keyName))
78314
+ return null;
78315
+ if (isAssignmentGuardedByKeyInObject(node2, keyName, obj.text))
78316
+ return null;
77732
78317
  return makeViolation(this.ruleKey, node2, 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.`);
77733
78318
  }
77734
78319
  };
@@ -81652,8 +82237,10 @@ var init_useeffect_missing_deps = __esm({
81652
82237
  }
81653
82238
  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));
81654
82239
  const nodeStartLine = node2.startPosition.row;
81655
- const linesAbove = sourceCode.split("\n").slice(Math.max(0, nodeStartLine - 2), nodeStartLine + 1);
81656
- if (linesAbove.some((line) => line.includes("eslint-disable")))
82240
+ const nodeEndLine = node2.endPosition.row;
82241
+ const sourceLines = sourceCode.split("\n");
82242
+ const windowLines = sourceLines.slice(Math.max(0, nodeStartLine - 2), nodeEndLine + 2);
82243
+ if (windowLines.some((line) => line.includes("eslint-disable")))
81657
82244
  return null;
81658
82245
  if (suspiciousIds.length > 0) {
81659
82246
  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(", ")}]\`.`);
@@ -83117,7 +83704,7 @@ var init_empty_catch2 = __esm({
83117
83704
  if (!body)
83118
83705
  return null;
83119
83706
  const statements = body.namedChildren.filter((c) => c.type !== "comment");
83120
- if (statements.length === 0 || statements.length === 1 && statements[0].type === "pass_statement") {
83707
+ if (statements.length === 0) {
83121
83708
  return makeViolation(this.ruleKey, node2, 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.");
83122
83709
  }
83123
83710
  return null;
@@ -83159,6 +83746,15 @@ var init_bare_except = __esm({
83159
83746
  });
83160
83747
 
83161
83748
  // packages/analyzer/dist/rules/bugs/visitors/python/_helpers.js
83749
+ function isFStringWithInterpolation(node2) {
83750
+ if (node2.type !== "string")
83751
+ return false;
83752
+ for (const child of node2.namedChildren) {
83753
+ if (child && child.type === "interpolation")
83754
+ return true;
83755
+ }
83756
+ return false;
83757
+ }
83162
83758
  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;
83163
83759
  var init_helpers2 = __esm({
83164
83760
  "packages/analyzer/dist/rules/bugs/visitors/python/_helpers.js"() {
@@ -83961,17 +84557,25 @@ var init_loop_variable_overrides_iterator = __esm({
83961
84557
  if (loopVar.type !== "identifier")
83962
84558
  return null;
83963
84559
  const varName = loopVar.text;
83964
- function containsIdentifier(n, name) {
83965
- if (n.type === "identifier" && n.text === name)
84560
+ function hasFreeUse(n, name) {
84561
+ if (n.type === "identifier" && n.text === name) {
84562
+ const parent = n.parent;
84563
+ if (parent?.type === "attribute" && parent.childForFieldName("attribute")?.id === n.id) {
84564
+ return false;
84565
+ }
84566
+ if (parent?.type === "keyword_argument" && parent.childForFieldName("name")?.id === n.id) {
84567
+ return false;
84568
+ }
83966
84569
  return true;
84570
+ }
83967
84571
  for (let i = 0; i < n.childCount; i++) {
83968
84572
  const child = n.child(i);
83969
- if (child && containsIdentifier(child, name))
84573
+ if (child && hasFreeUse(child, name))
83970
84574
  return true;
83971
84575
  }
83972
84576
  return false;
83973
84577
  }
83974
- if (containsIdentifier(iterExpr, varName)) {
84578
+ if (hasFreeUse(iterExpr, varName)) {
83975
84579
  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}\`.`);
83976
84580
  }
83977
84581
  return null;
@@ -85561,6 +86165,7 @@ var init_static_key_dict_comprehension = __esm({
85561
86165
  "packages/analyzer/dist/rules/bugs/visitors/python/static-key-dict-comprehension.js"() {
85562
86166
  "use strict";
85563
86167
  init_types2();
86168
+ init_helpers2();
85564
86169
  pythonStaticKeyDictComprehensionVisitor = {
85565
86170
  ruleKey: "bugs/deterministic/static-key-dict-comprehension",
85566
86171
  languages: ["python"],
@@ -85573,7 +86178,7 @@ var init_static_key_dict_comprehension = __esm({
85573
86178
  if (!keyNode)
85574
86179
  return null;
85575
86180
  const LITERAL_TYPES5 = /* @__PURE__ */ new Set(["string", "integer", "float", "true", "false", "none"]);
85576
- if (LITERAL_TYPES5.has(keyNode.type)) {
86181
+ if (LITERAL_TYPES5.has(keyNode.type) && !isFStringWithInterpolation(keyNode)) {
85577
86182
  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.");
85578
86183
  }
85579
86184
  return null;
@@ -86778,6 +87383,7 @@ var init_duplicate_dict_key = __esm({
86778
87383
  "packages/analyzer/dist/rules/bugs/visitors/python/duplicate-dict-key.js"() {
86779
87384
  "use strict";
86780
87385
  init_types2();
87386
+ init_helpers2();
86781
87387
  pythonDuplicateDictKeyVisitor = {
86782
87388
  ruleKey: "bugs/deterministic/duplicate-dict-key",
86783
87389
  languages: ["python"],
@@ -86793,7 +87399,8 @@ var init_duplicate_dict_key = __esm({
86793
87399
  const leftNode = forInClause?.childForFieldName("left");
86794
87400
  const loopVarNames = leftNode ? collectLoopVarNames(leftNode) : /* @__PURE__ */ new Set();
86795
87401
  const LITERAL_TYPES5 = /* @__PURE__ */ new Set(["string", "integer", "float", "true", "false", "none"]);
86796
- const isConstantKey = LITERAL_TYPES5.has(keyNode.type) || keyNode.type === "identifier" && loopVarNames.size > 0 && !loopVarNames.has(keyNode.text);
87402
+ const isStaticLiteral = LITERAL_TYPES5.has(keyNode.type) && !isFStringWithInterpolation(keyNode);
87403
+ const isConstantKey = isStaticLiteral || keyNode.type === "identifier" && loopVarNames.size > 0 && !loopVarNames.has(keyNode.text);
86797
87404
  if (isConstantKey) {
86798
87405
  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.");
86799
87406
  }
@@ -87200,9 +87807,8 @@ var init_warnings_no_stacklevel = __esm({
87200
87807
  const funcText = func.text;
87201
87808
  if (funcText !== "warnings.warn" && funcText !== "warn")
87202
87809
  return null;
87203
- if (funcText === "warn") {
87810
+ if (funcText === "warn")
87204
87811
  return null;
87205
- }
87206
87812
  const args = node2.childForFieldName("arguments");
87207
87813
  if (!args)
87208
87814
  return null;
@@ -88421,6 +89027,27 @@ function isMutableInit2(node2) {
88421
89027
  function isConstantName(name) {
88422
89028
  return name === name.toUpperCase();
88423
89029
  }
89030
+ function moduleHasLockGuard(moduleNode) {
89031
+ for (let i = 0; i < moduleNode.namedChildCount; i++) {
89032
+ const stmt = moduleNode.namedChild(i);
89033
+ if (!stmt)
89034
+ continue;
89035
+ const inner = stmt.type === "expression_statement" ? stmt.namedChild(0) : stmt;
89036
+ if (inner?.type !== "assignment")
89037
+ continue;
89038
+ const rhs = inner.childForFieldName("right");
89039
+ if (rhs?.type !== "call")
89040
+ continue;
89041
+ const fn = rhs.childForFieldName("function");
89042
+ if (!fn)
89043
+ continue;
89044
+ const text = fn.text;
89045
+ 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") {
89046
+ return true;
89047
+ }
89048
+ }
89049
+ return false;
89050
+ }
88424
89051
  var pythonSharedMutableModuleStateVisitor;
88425
89052
  var init_shared_mutable_module_state2 = __esm({
88426
89053
  "packages/analyzer/dist/rules/bugs/visitors/python/shared-mutable-module-state.js"() {
@@ -88444,6 +89071,11 @@ var init_shared_mutable_module_state2 = __esm({
88444
89071
  return null;
88445
89072
  if (varName === "__all__")
88446
89073
  return null;
89074
+ let moduleNode = node2.parent;
89075
+ while (moduleNode && moduleNode.type !== "module")
89076
+ moduleNode = moduleNode.parent;
89077
+ if (moduleNode && moduleHasLockGuard(moduleNode))
89078
+ return null;
88447
89079
  return makeViolation(this.ruleKey, node2, 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.");
88448
89080
  }
88449
89081
  };
@@ -89861,9 +90493,31 @@ function extractStringContent(node2) {
89861
90493
  }
89862
90494
  return null;
89863
90495
  }
90496
+ function pythonRegexToJs(pattern) {
90497
+ let flags = "";
90498
+ const collectFlag = (f2) => {
90499
+ if ("imsu".includes(f2) && !flags.includes(f2))
90500
+ flags += f2;
90501
+ };
90502
+ const prefix = pattern.match(/^\(\?([aiLmsux]+)\)/);
90503
+ if (prefix) {
90504
+ for (const f2 of prefix[1])
90505
+ collectFlag(f2);
90506
+ pattern = pattern.slice(prefix[0].length);
90507
+ }
90508
+ pattern = pattern.replace(/\(\?([aiLmsux]+):/g, (_, flagSet) => {
90509
+ for (const f2 of flagSet)
90510
+ collectFlag(f2);
90511
+ return "(?:";
90512
+ });
90513
+ pattern = pattern.replace(/\(\?P</g, "(?<");
90514
+ pattern = pattern.replace(/\(\?P=([^)]+)\)/g, "\\k<$1>");
90515
+ return { pattern, flags };
90516
+ }
89864
90517
  function isValidRegex(pattern) {
89865
90518
  try {
89866
- new RegExp(pattern);
90519
+ const normalized = pythonRegexToJs(pattern);
90520
+ new RegExp(normalized.pattern, normalized.flags);
89867
90521
  return true;
89868
90522
  } catch {
89869
90523
  return false;
@@ -91606,6 +92260,25 @@ var init_confusing_implicit_concat = __esm({
91606
92260
  }
91607
92261
  if (hasFormatString)
91608
92262
  continue;
92263
+ const isMultiLine = child.startPosition.row !== child.endPosition.row;
92264
+ if (isMultiLine) {
92265
+ const parts = child.namedChildren;
92266
+ let allButLastEndWithSpace = parts.length > 1;
92267
+ for (let i = 0; i < parts.length - 1; i++) {
92268
+ const t = parts[i].text;
92269
+ const stripped = t.replace(/['"]+$/, "");
92270
+ const lastChar = stripped.charAt(stripped.length - 1);
92271
+ if (lastChar !== " " && lastChar !== "\\" && lastChar !== " ") {
92272
+ allButLastEndWithSpace = false;
92273
+ break;
92274
+ }
92275
+ }
92276
+ if (allButLastEndWithSpace)
92277
+ continue;
92278
+ const totalLen = parts.reduce((sum, p) => sum + p.text.length, 0);
92279
+ if (totalLen > 60)
92280
+ continue;
92281
+ }
91609
92282
  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 ${node2.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.");
91610
92283
  }
91611
92284
  }
@@ -92089,6 +92762,7 @@ var init_static_key_dict_comprehension_ruff = __esm({
92089
92762
  "packages/analyzer/dist/rules/bugs/visitors/python/static-key-dict-comprehension-ruff.js"() {
92090
92763
  "use strict";
92091
92764
  init_types2();
92765
+ init_helpers2();
92092
92766
  LITERAL_TYPES2 = /* @__PURE__ */ new Set(["string", "integer", "float", "true", "false", "none"]);
92093
92767
  pythonStaticKeyDictComprehensionRuffVisitor = {
92094
92768
  ruleKey: "bugs/deterministic/static-key-dict-comprehension-ruff",
@@ -92101,7 +92775,7 @@ var init_static_key_dict_comprehension_ruff = __esm({
92101
92775
  const keyNode = pairNode.childForFieldName("key");
92102
92776
  if (!keyNode)
92103
92777
  return null;
92104
- if (LITERAL_TYPES2.has(keyNode.type)) {
92778
+ if (LITERAL_TYPES2.has(keyNode.type) && !isFStringWithInterpolation(keyNode)) {
92105
92779
  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.");
92106
92780
  }
92107
92781
  return null;
@@ -93704,7 +94378,16 @@ function findFunctionDefs(root) {
93704
94378
  hasVarArgs = true;
93705
94379
  continue;
93706
94380
  }
93707
- if (p.type === "identifier" || p.type === "typed_parameter") {
94381
+ if (p.type === "typed_parameter") {
94382
+ const inner = p.namedChild(0);
94383
+ if (inner && (inner.type === "list_splat_pattern" || inner.type === "dictionary_splat_pattern")) {
94384
+ hasVarArgs = true;
94385
+ continue;
94386
+ }
94387
+ required++;
94388
+ continue;
94389
+ }
94390
+ if (p.type === "identifier") {
93708
94391
  required++;
93709
94392
  } else if (p.type === "default_parameter" || p.type === "typed_default_parameter") {
93710
94393
  optional++;
@@ -95690,14 +96373,25 @@ var init_expression_complexity = __esm({
95690
96373
  return null;
95691
96374
  let operatorCount = 0;
95692
96375
  const BINARY_TYPES = /* @__PURE__ */ new Set(["binary_expression", "logical_expression"]);
96376
+ const FUNCTION_BOUNDARY_TYPES = /* @__PURE__ */ new Set([
96377
+ "function_declaration",
96378
+ "function_expression",
96379
+ "arrow_function",
96380
+ "method_definition",
96381
+ "generator_function_declaration",
96382
+ "generator_function"
96383
+ ]);
95693
96384
  function countOps(n) {
95694
96385
  if (BINARY_TYPES.has(n.type)) {
95695
96386
  operatorCount++;
95696
96387
  }
95697
96388
  for (let i = 0; i < n.childCount; i++) {
95698
96389
  const child = n.child(i);
95699
- if (child)
95700
- countOps(child);
96390
+ if (!child)
96391
+ continue;
96392
+ if (FUNCTION_BOUNDARY_TYPES.has(child.type))
96393
+ continue;
96394
+ countOps(child);
95701
96395
  }
95702
96396
  }
95703
96397
  countOps(expr);
@@ -105924,26 +106618,6 @@ var init_and_or_ternary = __esm({
105924
106618
  }
105925
106619
  });
105926
106620
 
105927
- // packages/analyzer/dist/rules/code-quality/visitors/python/any-type-hint.js
105928
- var pythonAnyTypeHintVisitor;
105929
- var init_any_type_hint = __esm({
105930
- "packages/analyzer/dist/rules/code-quality/visitors/python/any-type-hint.js"() {
105931
- "use strict";
105932
- init_types2();
105933
- pythonAnyTypeHintVisitor = {
105934
- ruleKey: "code-quality/deterministic/any-type-hint",
105935
- languages: ["python"],
105936
- nodeTypes: ["type"],
105937
- visit(node2, filePath, sourceCode) {
105938
- const text = node2.text.trim();
105939
- if (text !== "Any")
105940
- return null;
105941
- return makeViolation(this.ruleKey, node2, 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.");
105942
- }
105943
- };
105944
- }
105945
- });
105946
-
105947
106621
  // packages/analyzer/dist/rules/code-quality/visitors/python/assert-in-production.js
105948
106622
  var pythonAssertInProductionVisitor;
105949
106623
  var init_assert_in_production = __esm({
@@ -108480,6 +109154,24 @@ var init_implicit_string_concatenation = __esm({
108480
109154
  const grandparent = parent.parent;
108481
109155
  if (!isCollection(parent) && !isCollection(grandparent ?? { type: "" }))
108482
109156
  return null;
109157
+ if (node2.startPosition.row !== node2.endPosition.row) {
109158
+ const parts = node2.namedChildren;
109159
+ let allButLastEndWithSpace = parts.length > 1;
109160
+ for (let i = 0; i < parts.length - 1; i++) {
109161
+ const t = parts[i].text;
109162
+ const inner = t.replace(/['"]+$/, "");
109163
+ const lastChar = inner.charAt(inner.length - 1);
109164
+ if (lastChar !== " " && lastChar !== "\\" && lastChar !== " ") {
109165
+ allButLastEndWithSpace = false;
109166
+ break;
109167
+ }
109168
+ }
109169
+ if (allButLastEndWithSpace)
109170
+ return null;
109171
+ const totalLen = parts.reduce((sum, p) => sum + p.text.length, 0);
109172
+ if (totalLen > 60)
109173
+ return null;
109174
+ }
108483
109175
  return makeViolation(this.ruleKey, node2, 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.");
108484
109176
  }
108485
109177
  };
@@ -112223,6 +112915,14 @@ var init_try_except_continue = __esm({
112223
112915
  return null;
112224
112916
  if (stmts[0].type !== "continue_statement")
112225
112917
  return null;
112918
+ const exprChildren = node2.namedChildren.filter((c) => c.type !== "block" && c.type !== "comment");
112919
+ if (exprChildren.length > 0) {
112920
+ const exceptionType = exprChildren[0];
112921
+ const typeText = exceptionType.type === "as_pattern" ? exceptionType.namedChildren[0]?.text : exceptionType.text;
112922
+ if (typeText && typeText !== "Exception" && typeText !== "BaseException") {
112923
+ return null;
112924
+ }
112925
+ }
112226
112926
  return makeViolation(this.ruleKey, node2, 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.");
112227
112927
  }
112228
112928
  };
@@ -114594,34 +115294,56 @@ var init_ambiguous_unicode_character = __esm({
114594
115294
  "use strict";
114595
115295
  init_types2();
114596
115296
  CONFUSABLE_MAP = {
114597
- "\xB4": "'",
114598
- // acute accent → apostrophe
114599
- "\u2018": "'",
114600
- // left single quote → apostrophe
114601
- "\u2019": "'",
114602
- // right single quote → apostrophe
114603
- "\u201C": '"',
114604
- // left double quote → double quote
114605
- "\u201D": '"',
114606
- // right double quote → double quote
114607
- "\u2013": "-",
114608
- // en dash → hyphen
114609
- "\u2014": "--",
114610
- // em dash → double hyphen
114611
- "\xAD": "-",
114612
- // soft hyphen
114613
- "\u2212": "-",
114614
- // minus sign
114615
- "\xD7": "x",
114616
- // multiplication sign
114617
115297
  "\u03BF": "o",
114618
115298
  // Greek small letter omicron
114619
115299
  "\u0430": "a",
114620
115300
  // Cyrillic small a
114621
115301
  "\u0435": "e",
114622
115302
  // Cyrillic small e
114623
- "\u043E": "o"
115303
+ "\u043E": "o",
114624
115304
  // Cyrillic small o
115305
+ "\u0440": "p",
115306
+ // Cyrillic small er
115307
+ "\u0441": "c",
115308
+ // Cyrillic small es
115309
+ "\u0445": "x",
115310
+ // Cyrillic small ha
115311
+ "\u0455": "s",
115312
+ // Cyrillic small dze
115313
+ "\u0456": "i",
115314
+ // Cyrillic small Byelorussian-Ukrainian I
115315
+ "\u0501": "d",
115316
+ // Cyrillic small komi de
115317
+ "\u051B": "q",
115318
+ // Cyrillic small qa
115319
+ "\u051D": "w",
115320
+ // Cyrillic small we
115321
+ "\u0399": "I",
115322
+ // Greek capital iota
115323
+ "\u0396": "Z",
115324
+ // Greek capital zeta
115325
+ "\u039F": "O",
115326
+ // Greek capital omicron
115327
+ "\u0410": "A",
115328
+ // Cyrillic capital A
115329
+ "\u0415": "E",
115330
+ // Cyrillic capital E
115331
+ "\u041A": "K",
115332
+ // Cyrillic capital Ka
115333
+ "\u041C": "M",
115334
+ // Cyrillic capital Em
115335
+ "\u041D": "H",
115336
+ // Cyrillic capital En (renders as H)
115337
+ "\u041E": "O",
115338
+ // Cyrillic capital O
115339
+ "\u0420": "P",
115340
+ // Cyrillic capital er
115341
+ "\u0421": "C",
115342
+ // Cyrillic capital es
115343
+ "\u0422": "T",
115344
+ // Cyrillic capital te
115345
+ "\u0425": "X"
115346
+ // Cyrillic capital ha
114625
115347
  };
114626
115348
  pythonAmbiguousUnicodeCharacterVisitor = {
114627
115349
  ruleKey: "code-quality/deterministic/ambiguous-unicode-character",
@@ -114869,6 +115591,8 @@ var init_regex_char_class_preferred = __esm({
114869
115591
  if (!isReCall4)
114870
115592
  return null;
114871
115593
  const pattern = node2.text.slice(1, -1);
115594
+ if (/\(\?[aiLmux]*s[aiLmux]*[):]/.test(pattern))
115595
+ return null;
114872
115596
  if (/\..+?\?/.test(pattern) || /\.\*\?/.test(pattern)) {
114873
115597
  return makeViolation(this.ruleKey, node2, 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.");
114874
115598
  }
@@ -115596,6 +116320,62 @@ var init_lambda_reserved_env_var = __esm({
115596
116320
  });
115597
116321
 
115598
116322
  // packages/analyzer/dist/rules/code-quality/visitors/python/boto3-client-error.js
116323
+ function findAwsClientVarNames(root) {
116324
+ const names = /* @__PURE__ */ new Set();
116325
+ function walk(n) {
116326
+ if (n.type === "assignment") {
116327
+ const lhs = n.childForFieldName("left");
116328
+ const rhs = n.childForFieldName("right");
116329
+ if (lhs?.type === "identifier" && rhs?.type === "call") {
116330
+ const fn = rhs.childForFieldName("function");
116331
+ if (fn?.type === "attribute") {
116332
+ const obj = fn.childForFieldName("object");
116333
+ const attr = fn.childForFieldName("attribute");
116334
+ if ((obj?.text === "boto3" || obj?.text === "aiobotocore") && (attr?.text === "client" || attr?.text === "resource")) {
116335
+ names.add(lhs.text);
116336
+ }
116337
+ }
116338
+ }
116339
+ }
116340
+ for (let i = 0; i < n.childCount; i++) {
116341
+ const child = n.child(i);
116342
+ if (child)
116343
+ walk(child);
116344
+ }
116345
+ }
116346
+ walk(root);
116347
+ return names;
116348
+ }
116349
+ function bodyHasAwsCall(body, awsVarNames) {
116350
+ let found = false;
116351
+ function walk(n) {
116352
+ if (found)
116353
+ return;
116354
+ if (n.type === "call") {
116355
+ const fn = n.childForFieldName("function");
116356
+ if (fn?.type === "attribute") {
116357
+ let receiver = fn.childForFieldName("object");
116358
+ while (receiver?.type === "attribute") {
116359
+ receiver = receiver.childForFieldName("object");
116360
+ }
116361
+ if (receiver?.type === "identifier") {
116362
+ const id = receiver.text;
116363
+ if (id === "boto3" || id === "botocore" || id === "aiobotocore" || awsVarNames.has(id)) {
116364
+ found = true;
116365
+ return;
116366
+ }
116367
+ }
116368
+ }
116369
+ }
116370
+ for (let i = 0; i < n.childCount; i++) {
116371
+ const child = n.child(i);
116372
+ if (child)
116373
+ walk(child);
116374
+ }
116375
+ }
116376
+ walk(body);
116377
+ return found;
116378
+ }
115599
116379
  var pythonBoto3ClientErrorVisitor;
115600
116380
  var init_boto3_client_error = __esm({
115601
116381
  "packages/analyzer/dist/rules/code-quality/visitors/python/boto3-client-error.js"() {
@@ -115633,6 +116413,9 @@ var init_boto3_client_error = __esm({
115633
116413
  });
115634
116414
  if (!hasBareOrGeneric)
115635
116415
  return null;
116416
+ const awsVarNames = findAwsClientVarNames(node2.tree.rootNode);
116417
+ if (!bodyHasAwsCall(body, awsVarNames))
116418
+ return null;
115636
116419
  return makeViolation(this.ruleKey, node2, 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.");
115637
116420
  }
115638
116421
  };
@@ -116497,7 +117280,6 @@ var init_python7 = __esm({
116497
117280
  init_commented_out_code2();
116498
117281
  init_abstract_class_without_abstract_method();
116499
117282
  init_and_or_ternary();
116500
- init_any_type_hint();
116501
117283
  init_assert_in_production();
116502
117284
  init_async_long_sleep();
116503
117285
  init_async_unused_async();
@@ -116752,7 +117534,6 @@ var init_python7 = __esm({
116752
117534
  pythonCommentedOutCodeVisitor,
116753
117535
  pythonAbstractClassWithoutAbstractMethodVisitor,
116754
117536
  pythonAndOrTernaryVisitor,
116755
- pythonAnyTypeHintVisitor,
116756
117537
  pythonAssertInProductionVisitor,
116757
117538
  pythonAsyncLongSleepVisitor,
116758
117539
  pythonAsyncUnusedAsyncVisitor,
@@ -119133,6 +119914,22 @@ var init_incorrect_dict_iterator = __esm({
119133
119914
  });
119134
119915
 
119135
119916
  // packages/analyzer/dist/rules/performance/visitors/python/try-except-in-loop.js
119917
+ function isTypedSkipOnlyHandler(exceptClause) {
119918
+ const block = exceptClause.namedChildren.find((c) => c.type === "block");
119919
+ if (!block)
119920
+ return false;
119921
+ const stmts = block.namedChildren.filter((c) => c.type !== "comment");
119922
+ if (stmts.length !== 1)
119923
+ return false;
119924
+ if (stmts[0].type !== "continue_statement" && stmts[0].type !== "pass_statement")
119925
+ return false;
119926
+ const exprChildren = exceptClause.namedChildren.filter((c) => c.type !== "block" && c.type !== "comment");
119927
+ if (exprChildren.length === 0)
119928
+ return false;
119929
+ const exceptionType = exprChildren[0];
119930
+ const typeText = exceptionType.type === "as_pattern" ? exceptionType.namedChildren[0]?.text : exceptionType.text;
119931
+ return typeText !== void 0 && typeText !== "Exception" && typeText !== "BaseException";
119932
+ }
119136
119933
  var tryExceptInLoopVisitor;
119137
119934
  var init_try_except_in_loop = __esm({
119138
119935
  "packages/analyzer/dist/rules/performance/visitors/python/try-except-in-loop.js"() {
@@ -119146,6 +119943,10 @@ var init_try_except_in_loop = __esm({
119146
119943
  visit(node2, filePath, sourceCode) {
119147
119944
  if (!isInsidePythonLoop(node2))
119148
119945
  return null;
119946
+ const exceptClauses = node2.namedChildren.filter((c) => c.type === "except_clause");
119947
+ if (exceptClauses.length > 0 && exceptClauses.every(isTypedSkipOnlyHandler)) {
119948
+ return null;
119949
+ }
119149
119950
  return makeViolation(this.ruleKey, node2, 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.");
119150
119951
  }
119151
119952
  };
@@ -119519,6 +120320,9 @@ var init_catch_without_error_type = __esm({
119519
120320
  const hasTypeAnnotation = node2.childForFieldName("type") !== null;
119520
120321
  if (hasTypeAnnotation)
119521
120322
  return null;
120323
+ const stmts = body.namedChildren.filter((c) => c.type !== "comment");
120324
+ if (stmts.length <= 1)
120325
+ return null;
119522
120326
  return makeViolation(this.ruleKey, node2, 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.");
119523
120327
  }
119524
120328
  };
@@ -119586,6 +120390,28 @@ function isInsidePromiseConstructor(node2) {
119586
120390
  }
119587
120391
  return false;
119588
120392
  }
120393
+ function looksLikeAwsLambda(sourceCode) {
120394
+ if (/\baws-lambda\b/.test(sourceCode))
120395
+ return true;
120396
+ if (/\bAPIGatewayProxy(Event|Result|Handler)/.test(sourceCode))
120397
+ return true;
120398
+ if (/\bLambda(Event|Result|Context|Handler)\b/.test(sourceCode))
120399
+ return true;
120400
+ if (/export\s+const\s+handler\s*[:=]\s*async\s*\(/.test(sourceCode))
120401
+ return true;
120402
+ return false;
120403
+ }
120404
+ function looksLikeAwsCdkScript(sourceCode) {
120405
+ if (/\baws-cdk-lib\b/.test(sourceCode))
120406
+ return true;
120407
+ if (/@aws-cdk\//.test(sourceCode))
120408
+ return true;
120409
+ if (/\bnew\s+cdk\.(App|Stack)\b/.test(sourceCode))
120410
+ return true;
120411
+ if (/\bapp\.synth\(\)/.test(sourceCode))
120412
+ return true;
120413
+ return false;
120414
+ }
119589
120415
  var init_helpers6 = __esm({
119590
120416
  "packages/analyzer/dist/rules/reliability/visitors/javascript/_helpers.js"() {
119591
120417
  "use strict";
@@ -119872,7 +120698,11 @@ var init_unchecked_array_access = __esm({
119872
120698
  init_helpers6();
119873
120699
  uncheckedArrayAccessVisitor = {
119874
120700
  ruleKey: "reliability/deterministic/unchecked-array-access",
119875
- languages: ["typescript", "tsx", "javascript"],
120701
+ // TS/TSX only. The rule is meaningful when the project opts into
120702
+ // `noUncheckedIndexedAccess`, which only exists in TypeScript. Plain
120703
+ // JS / JSX has no static type system to opt into - every index access
120704
+ // is implicitly `T | undefined` and flagging it is pure noise.
120705
+ languages: ["typescript", "tsx"],
119876
120706
  nodeTypes: ["subscript_expression"],
119877
120707
  visit(node2, filePath, sourceCode) {
119878
120708
  const object = node2.childForFieldName("object");
@@ -120168,6 +120998,7 @@ var init_uncaught_exception_no_handler = __esm({
120168
120998
  "packages/analyzer/dist/rules/reliability/visitors/javascript/uncaught-exception-no-handler.js"() {
120169
120999
  "use strict";
120170
121000
  init_types2();
121001
+ init_helpers6();
120171
121002
  uncaughtExceptionNoHandlerVisitor = {
120172
121003
  ruleKey: "reliability/deterministic/uncaught-exception-no-handler",
120173
121004
  languages: ["typescript", "tsx", "javascript"],
@@ -120180,6 +121011,10 @@ var init_uncaught_exception_no_handler = __esm({
120180
121011
  if (!lowerPath.includes("index.") && !lowerPath.includes("main.") && !lowerPath.includes("server.") && !lowerPath.includes("app.") && !lowerPath.endsWith("/worker.ts") && !lowerPath.endsWith("/worker.js") && !lowerPath.includes("bin/")) {
120181
121012
  return null;
120182
121013
  }
121014
+ if (looksLikeAwsLambda(sourceCode))
121015
+ return null;
121016
+ if (looksLikeAwsCdkScript(sourceCode))
121017
+ return null;
120183
121018
  const text = sourceCode.replace(/\/\/.*$/gm, "");
120184
121019
  if (text.includes("'uncaughtException'") || text.includes('"uncaughtException"') || text.includes("`uncaughtException`")) {
120185
121020
  return null;
@@ -120232,6 +121067,7 @@ var init_unhandled_rejection_no_handler = __esm({
120232
121067
  "packages/analyzer/dist/rules/reliability/visitors/javascript/unhandled-rejection-no-handler.js"() {
120233
121068
  "use strict";
120234
121069
  init_types2();
121070
+ init_helpers6();
120235
121071
  unhandledRejectionNoHandlerVisitor = {
120236
121072
  ruleKey: "reliability/deterministic/unhandled-rejection-no-handler",
120237
121073
  languages: ["typescript", "tsx", "javascript"],
@@ -120244,6 +121080,10 @@ var init_unhandled_rejection_no_handler = __esm({
120244
121080
  if (!lowerPath.includes("index.") && !lowerPath.includes("main.") && !lowerPath.includes("server.") && !lowerPath.includes("app.") && !lowerPath.endsWith("/worker.ts") && !lowerPath.endsWith("/worker.js") && !lowerPath.includes("bin/")) {
120245
121081
  return null;
120246
121082
  }
121083
+ if (looksLikeAwsLambda(sourceCode))
121084
+ return null;
121085
+ if (looksLikeAwsCdkScript(sourceCode))
121086
+ return null;
120247
121087
  const text = sourceCode.replace(/\/\/.*$/gm, "");
120248
121088
  if (text.includes("'unhandledRejection'") || text.includes('"unhandledRejection"') || text.includes("`unhandledRejection`")) {
120249
121089
  return null;
@@ -121560,6 +122400,21 @@ var init_duplicate_import4 = __esm({
121560
122400
  });
121561
122401
 
121562
122402
  // packages/analyzer/dist/rules/architecture/visitors/python/declarations-in-global-scope.js
122403
+ function isTypingConstruct(node2) {
122404
+ if (node2.type !== "subscript")
122405
+ return false;
122406
+ const value = node2.childForFieldName("value");
122407
+ if (!value)
122408
+ return false;
122409
+ if (value.type === "identifier")
122410
+ return TYPING_NAMES.has(value.text);
122411
+ if (value.type === "attribute") {
122412
+ const attr = value.childForFieldName("attribute");
122413
+ if (attr && TYPING_NAMES.has(attr.text))
122414
+ return true;
122415
+ }
122416
+ return false;
122417
+ }
121563
122418
  function rootIsCall(node2) {
121564
122419
  if (node2.type === "call")
121565
122420
  return true;
@@ -121575,11 +122430,46 @@ function rootIsCall(node2) {
121575
122430
  }
121576
122431
  return false;
121577
122432
  }
121578
- var pythonDeclarationsInGlobalScopeVisitor;
122433
+ var TYPING_NAMES, pythonDeclarationsInGlobalScopeVisitor;
121579
122434
  var init_declarations_in_global_scope2 = __esm({
121580
122435
  "packages/analyzer/dist/rules/architecture/visitors/python/declarations-in-global-scope.js"() {
121581
122436
  "use strict";
121582
122437
  init_types2();
122438
+ TYPING_NAMES = /* @__PURE__ */ new Set([
122439
+ "Callable",
122440
+ "Optional",
122441
+ "Union",
122442
+ "Literal",
122443
+ "Annotated",
122444
+ "Final",
122445
+ "List",
122446
+ "Tuple",
122447
+ "Dict",
122448
+ "Set",
122449
+ "FrozenSet",
122450
+ "Type",
122451
+ "ClassVar",
122452
+ "Sequence",
122453
+ "Mapping",
122454
+ "MutableMapping",
122455
+ "Iterable",
122456
+ "Iterator",
122457
+ "Generator",
122458
+ "Coroutine",
122459
+ "Awaitable",
122460
+ "AsyncIterable",
122461
+ "AsyncIterator",
122462
+ "Protocol",
122463
+ "TypedDict",
122464
+ "NamedTuple",
122465
+ // Built-in generics (PEP 585)
122466
+ "list",
122467
+ "tuple",
122468
+ "dict",
122469
+ "set",
122470
+ "frozenset",
122471
+ "type"
122472
+ ]);
121583
122473
  pythonDeclarationsInGlobalScopeVisitor = {
121584
122474
  ruleKey: "architecture/deterministic/declarations-in-global-scope",
121585
122475
  languages: ["python"],
@@ -121641,6 +122531,8 @@ var init_declarations_in_global_scope2 = __esm({
121641
122531
  return null;
121642
122532
  if (right?.type === "attribute" && rootIsCall(right))
121643
122533
  return null;
122534
+ if (right?.type === "subscript" && isTypingConstruct(right))
122535
+ return null;
121644
122536
  return makeViolation(this.ruleKey, node2, 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.");
121645
122537
  }
121646
122538
  };
@@ -122105,13 +122997,47 @@ var init_missing_transaction = __esm({
122105
122997
  });
122106
122998
 
122107
122999
  // packages/analyzer/dist/rules/database/visitors/javascript/unvalidated-external-data.js
122108
- var unvalidatedExternalDataVisitor;
123000
+ function hasOrmLikeReceiver(node2) {
123001
+ const fn = node2.childForFieldName("function");
123002
+ if (fn?.type !== "member_expression")
123003
+ return false;
123004
+ let receiver = fn.childForFieldName("object");
123005
+ while (receiver?.type === "member_expression") {
123006
+ receiver = receiver.childForFieldName("object");
123007
+ }
123008
+ if (receiver?.type !== "identifier")
123009
+ return false;
123010
+ const name = receiver.text.toLowerCase();
123011
+ return ORM_RECEIVER_NAMES2.has(name);
123012
+ }
123013
+ var AMBIGUOUS_ORM_METHODS, ORM_RECEIVER_NAMES2, unvalidatedExternalDataVisitor;
122109
123014
  var init_unvalidated_external_data = __esm({
122110
123015
  "packages/analyzer/dist/rules/database/visitors/javascript/unvalidated-external-data.js"() {
122111
123016
  "use strict";
122112
123017
  init_types2();
122113
123018
  init_helpers8();
122114
123019
  init_javascript_helpers();
123020
+ AMBIGUOUS_ORM_METHODS = /* @__PURE__ */ new Set(["add", "delete"]);
123021
+ ORM_RECEIVER_NAMES2 = /* @__PURE__ */ new Set([
123022
+ "session",
123023
+ "db",
123024
+ "conn",
123025
+ "connection",
123026
+ "cursor",
123027
+ "engine",
123028
+ "database",
123029
+ "manager",
123030
+ "repo",
123031
+ "repository",
123032
+ "orm",
123033
+ "em",
123034
+ "tx",
123035
+ "trx",
123036
+ "knex",
123037
+ "prisma",
123038
+ "sequelize",
123039
+ "mongoose"
123040
+ ]);
122115
123041
  unvalidatedExternalDataVisitor = {
122116
123042
  ruleKey: "database/deterministic/unvalidated-external-data",
122117
123043
  languages: ["typescript", "tsx", "javascript"],
@@ -122121,6 +123047,8 @@ var init_unvalidated_external_data = __esm({
122121
123047
  const methodName = getMethodName(node2);
122122
123048
  if (!ORM_WRITE_METHODS2.has(methodName) && !SQL_WRITE_METHODS.has(methodName))
122123
123049
  return null;
123050
+ if (AMBIGUOUS_ORM_METHODS.has(methodName) && !hasOrmLikeReceiver(node2))
123051
+ return null;
122124
123052
  const args = node2.childForFieldName("arguments");
122125
123053
  if (!args)
122126
123054
  return null;
@@ -122506,6 +123434,9 @@ var init_missing_migration2 = __esm({
122506
123434
  if (!/alter\s+table|create\s+table|drop\s+table|create\s+index|drop\s+index/.test(sqlText)) {
122507
123435
  return null;
122508
123436
  }
123437
+ if (/if\s+(not\s+)?exists/.test(sqlText)) {
123438
+ return null;
123439
+ }
122509
123440
  return makeViolation(this.ruleKey, node2, 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.");
122510
123441
  }
122511
123442
  };
@@ -122513,6 +123444,122 @@ var init_missing_migration2 = __esm({
122513
123444
  });
122514
123445
 
122515
123446
  // packages/analyzer/dist/rules/database/visitors/python/connection-not-released.js
123447
+ function escapeRegExp2(s) {
123448
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
123449
+ }
123450
+ function isFactoryReturn(node2) {
123451
+ let current = node2.parent;
123452
+ while (current) {
123453
+ if (current.type === "return_statement")
123454
+ return true;
123455
+ if (current.type === "expression_statement")
123456
+ return false;
123457
+ if (current.type === "function_definition")
123458
+ return false;
123459
+ current = current.parent;
123460
+ }
123461
+ return false;
123462
+ }
123463
+ function isModuleLevelCache(node2) {
123464
+ let assign = node2.parent;
123465
+ while (assign) {
123466
+ if (assign.type === "assignment")
123467
+ break;
123468
+ if (assign.type === "expression_statement")
123469
+ return false;
123470
+ if (assign.type === "function_definition")
123471
+ return false;
123472
+ if (assign.type === "class_definition")
123473
+ return false;
123474
+ assign = assign.parent;
123475
+ }
123476
+ if (!assign)
123477
+ return false;
123478
+ const stmt = assign.parent;
123479
+ if (!(stmt?.type === "expression_statement" && stmt.parent?.type === "module"))
123480
+ return false;
123481
+ const lhs = assign.childForFieldName("left");
123482
+ const nameNode = lhs?.type === "identifier" ? lhs : null;
123483
+ if (!nameNode)
123484
+ return false;
123485
+ const name = nameNode.text;
123486
+ return /^[A-Z_][A-Z0-9_]*$/.test(name) || name.startsWith("_");
123487
+ }
123488
+ function isInstanceAttributeAssignmentInPrivateClass(node2) {
123489
+ let assign = node2.parent;
123490
+ while (assign) {
123491
+ if (assign.type === "assignment")
123492
+ break;
123493
+ if (assign.type === "expression_statement")
123494
+ return false;
123495
+ if (assign.type === "function_definition")
123496
+ return false;
123497
+ assign = assign.parent;
123498
+ }
123499
+ if (!assign)
123500
+ return false;
123501
+ const lhs = assign.childForFieldName("left");
123502
+ if (lhs?.type !== "attribute")
123503
+ return false;
123504
+ const obj = lhs.childForFieldName("object");
123505
+ if (!(obj?.type === "identifier" && (obj.text === "self" || obj.text === "cls")))
123506
+ return false;
123507
+ let cls = assign.parent;
123508
+ while (cls) {
123509
+ if (cls.type === "class_definition") {
123510
+ const name = cls.childForFieldName("name");
123511
+ return name?.type === "identifier" && name.text.startsWith("_");
123512
+ }
123513
+ cls = cls.parent;
123514
+ }
123515
+ return false;
123516
+ }
123517
+ function isClosedInFinally(node2) {
123518
+ let assignmentParent = node2.parent;
123519
+ while (assignmentParent) {
123520
+ if (assignmentParent.type === "assignment")
123521
+ break;
123522
+ if (assignmentParent.type === "expression_statement")
123523
+ return false;
123524
+ if (assignmentParent.type === "function_definition")
123525
+ return false;
123526
+ assignmentParent = assignmentParent.parent;
123527
+ }
123528
+ if (!assignmentParent)
123529
+ return false;
123530
+ const lhs = assignmentParent.childForFieldName("left");
123531
+ if (!lhs || lhs.type !== "identifier")
123532
+ return false;
123533
+ const varName = lhs.text;
123534
+ let func = assignmentParent.parent;
123535
+ while (func) {
123536
+ if (func.type === "function_definition")
123537
+ break;
123538
+ func = func.parent;
123539
+ }
123540
+ if (!func)
123541
+ return false;
123542
+ const body = func.childForFieldName("body");
123543
+ if (!body)
123544
+ return false;
123545
+ const closePattern2 = new RegExp(`\\b${escapeRegExp2(varName)}\\.(?:close|dispose|release)\\s*\\(`);
123546
+ let found = false;
123547
+ function walk(n) {
123548
+ if (found)
123549
+ return;
123550
+ if (n.type === "finally_clause" && closePattern2.test(n.text)) {
123551
+ found = true;
123552
+ return;
123553
+ }
123554
+ for (let i = 0; i < n.childCount; i++) {
123555
+ const ch = n.child(i);
123556
+ if (ch)
123557
+ walk(ch);
123558
+ }
123559
+ }
123560
+ walk(body);
123561
+ return found;
123562
+ }
122516
123563
  var pythonConnectionNotReleasedVisitor;
122517
123564
  var init_connection_not_released2 = __esm({
122518
123565
  "packages/analyzer/dist/rules/database/visitors/python/connection-not-released.js"() {
@@ -122560,6 +123607,14 @@ var init_connection_not_released2 = __esm({
122560
123607
  break;
122561
123608
  current = current.parent;
122562
123609
  }
123610
+ if (isFactoryReturn(node2))
123611
+ return null;
123612
+ if (isClosedInFinally(node2))
123613
+ return null;
123614
+ if (isModuleLevelCache(node2))
123615
+ return null;
123616
+ if (isInstanceAttributeAssignmentInPrivateClass(node2))
123617
+ return null;
122563
123618
  return makeViolation(this.ruleKey, node2, 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.");
122564
123619
  }
122565
123620
  };
@@ -122643,6 +123698,23 @@ var init_orm_lazy_load_in_loop2 = __esm({
122643
123698
  });
122644
123699
 
122645
123700
  // packages/analyzer/dist/rules/database/visitors/python/missing-transaction.js
123701
+ function hasCursorLikeParam(params) {
123702
+ for (let i = 0; i < params.namedChildCount; i++) {
123703
+ const p = params.namedChild(i);
123704
+ if (!p)
123705
+ continue;
123706
+ let nameNode = null;
123707
+ if (p.type === "identifier") {
123708
+ nameNode = p;
123709
+ } else if (p.type === "typed_parameter" || p.type === "default_parameter" || p.type === "typed_default_parameter") {
123710
+ nameNode = p.childForFieldName("name") ?? p.namedChild(0);
123711
+ }
123712
+ if (nameNode && nameNode.type === "identifier" && CURSOR_PARAM_NAMES.has(nameNode.text)) {
123713
+ return true;
123714
+ }
123715
+ }
123716
+ return false;
123717
+ }
122646
123718
  function isOrmWriteCall(n) {
122647
123719
  const name = getPythonMethodName(n);
122648
123720
  if (name === "execute" || name === "executemany") {
@@ -122650,7 +123722,7 @@ function isOrmWriteCall(n) {
122650
123722
  const firstArg = args?.namedChildren[0];
122651
123723
  if (firstArg?.type === "string") {
122652
123724
  const sql = firstArg.text.toLowerCase();
122653
- if (/insert|update|delete|alter|create|drop/.test(sql))
123725
+ if (/\b(insert|update|delete|alter|create|drop)\b/.test(sql))
122654
123726
  return true;
122655
123727
  }
122656
123728
  return false;
@@ -122663,26 +123735,26 @@ function isOrmWriteCall(n) {
122663
123735
  if (fn?.type === "attribute") {
122664
123736
  const receiver = fn.childForFieldName("object");
122665
123737
  if (receiver?.type === "identifier") {
122666
- return ORM_RECEIVER_NAMES2.has(receiver.text);
123738
+ return ORM_RECEIVER_NAMES3.has(receiver.text);
122667
123739
  }
122668
123740
  if (receiver?.type === "attribute") {
122669
123741
  const attrText = receiver.childForFieldName("attribute")?.text;
122670
- if (attrText && ORM_RECEIVER_NAMES2.has(attrText))
123742
+ if (attrText && ORM_RECEIVER_NAMES3.has(attrText))
122671
123743
  return true;
122672
123744
  const rootObj = receiver.childForFieldName("object");
122673
- if (rootObj?.type === "identifier" && ORM_RECEIVER_NAMES2.has(rootObj.text))
123745
+ if (rootObj?.type === "identifier" && ORM_RECEIVER_NAMES3.has(rootObj.text))
122674
123746
  return true;
122675
123747
  }
122676
123748
  }
122677
123749
  return false;
122678
123750
  }
122679
- var ORM_RECEIVER_NAMES2, UNAMBIGUOUS_DB_WRITES, pythonMissingTransactionVisitor;
123751
+ var ORM_RECEIVER_NAMES3, UNAMBIGUOUS_DB_WRITES, CURSOR_PARAM_NAMES, pythonMissingTransactionVisitor;
122680
123752
  var init_missing_transaction2 = __esm({
122681
123753
  "packages/analyzer/dist/rules/database/visitors/python/missing-transaction.js"() {
122682
123754
  "use strict";
122683
123755
  init_types2();
122684
123756
  init_helpers9();
122685
- ORM_RECEIVER_NAMES2 = /* @__PURE__ */ new Set([
123757
+ ORM_RECEIVER_NAMES3 = /* @__PURE__ */ new Set([
122686
123758
  "session",
122687
123759
  "db",
122688
123760
  "conn",
@@ -122702,6 +123774,17 @@ var init_missing_transaction2 = __esm({
122702
123774
  "bulk_update",
122703
123775
  "executemany"
122704
123776
  ]);
123777
+ CURSOR_PARAM_NAMES = /* @__PURE__ */ new Set([
123778
+ "cur",
123779
+ "cursor",
123780
+ "conn",
123781
+ "connection",
123782
+ "session",
123783
+ "db",
123784
+ "tx",
123785
+ "trans",
123786
+ "transaction"
123787
+ ]);
122705
123788
  pythonMissingTransactionVisitor = {
122706
123789
  ruleKey: "database/deterministic/missing-transaction",
122707
123790
  languages: ["python"],
@@ -122713,8 +123796,18 @@ var init_missing_transaction2 = __esm({
122713
123796
  if (!body)
122714
123797
  return null;
122715
123798
  const bodyText = body.text.toLowerCase();
122716
- if (/transaction|atomic|begin\b/.test(bodyText))
123799
+ if (/transaction|atomic|begin\b|autocommit\s*=\s*false/.test(bodyText))
123800
+ return null;
123801
+ if (/\bwith\s+[^:]*\bas\s+(conn|connection|cursor|cur|session|engine|tx|trans)\b[^:]*:/.test(bodyText))
122717
123802
  return null;
123803
+ const funcDef = body.parent;
123804
+ if (funcDef?.type === "function_definition") {
123805
+ const nameNode = funcDef.childForFieldName("name");
123806
+ const params = funcDef.childForFieldName("parameters");
123807
+ if (nameNode?.text.startsWith("_") && params && hasCursorLikeParam(params)) {
123808
+ return null;
123809
+ }
123810
+ }
122718
123811
  let writeCount = 0;
122719
123812
  let seenSelf = false;
122720
123813
  let isSecondOccurrence = false;
@@ -124128,6 +125221,503 @@ var init_dist2 = __esm({
124128
125221
  }
124129
125222
  });
124130
125223
 
125224
+ // node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js
125225
+ var require_windows = __commonJS({
125226
+ "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js"(exports, module) {
125227
+ module.exports = isexe;
125228
+ isexe.sync = sync;
125229
+ var fs14 = __require("fs");
125230
+ function checkPathExt(path19, options) {
125231
+ var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
125232
+ if (!pathext) {
125233
+ return true;
125234
+ }
125235
+ pathext = pathext.split(";");
125236
+ if (pathext.indexOf("") !== -1) {
125237
+ return true;
125238
+ }
125239
+ for (var i = 0; i < pathext.length; i++) {
125240
+ var p = pathext[i].toLowerCase();
125241
+ if (p && path19.substr(-p.length).toLowerCase() === p) {
125242
+ return true;
125243
+ }
125244
+ }
125245
+ return false;
125246
+ }
125247
+ function checkStat(stat, path19, options) {
125248
+ if (!stat.isSymbolicLink() && !stat.isFile()) {
125249
+ return false;
125250
+ }
125251
+ return checkPathExt(path19, options);
125252
+ }
125253
+ function isexe(path19, options, cb) {
125254
+ fs14.stat(path19, function(er, stat) {
125255
+ cb(er, er ? false : checkStat(stat, path19, options));
125256
+ });
125257
+ }
125258
+ function sync(path19, options) {
125259
+ return checkStat(fs14.statSync(path19), path19, options);
125260
+ }
125261
+ }
125262
+ });
125263
+
125264
+ // node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js
125265
+ var require_mode = __commonJS({
125266
+ "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js"(exports, module) {
125267
+ module.exports = isexe;
125268
+ isexe.sync = sync;
125269
+ var fs14 = __require("fs");
125270
+ function isexe(path19, options, cb) {
125271
+ fs14.stat(path19, function(er, stat) {
125272
+ cb(er, er ? false : checkStat(stat, options));
125273
+ });
125274
+ }
125275
+ function sync(path19, options) {
125276
+ return checkStat(fs14.statSync(path19), options);
125277
+ }
125278
+ function checkStat(stat, options) {
125279
+ return stat.isFile() && checkMode(stat, options);
125280
+ }
125281
+ function checkMode(stat, options) {
125282
+ var mod = stat.mode;
125283
+ var uid = stat.uid;
125284
+ var gid = stat.gid;
125285
+ var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
125286
+ var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
125287
+ var u = parseInt("100", 8);
125288
+ var g = parseInt("010", 8);
125289
+ var o = parseInt("001", 8);
125290
+ var ug = u | g;
125291
+ var ret = mod & o || mod & g && gid === myGid || mod & u && uid === myUid || mod & ug && myUid === 0;
125292
+ return ret;
125293
+ }
125294
+ }
125295
+ });
125296
+
125297
+ // node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js
125298
+ var require_isexe = __commonJS({
125299
+ "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js"(exports, module) {
125300
+ var fs14 = __require("fs");
125301
+ var core;
125302
+ if (process.platform === "win32" || global.TESTING_WINDOWS) {
125303
+ core = require_windows();
125304
+ } else {
125305
+ core = require_mode();
125306
+ }
125307
+ module.exports = isexe;
125308
+ isexe.sync = sync;
125309
+ function isexe(path19, options, cb) {
125310
+ if (typeof options === "function") {
125311
+ cb = options;
125312
+ options = {};
125313
+ }
125314
+ if (!cb) {
125315
+ if (typeof Promise !== "function") {
125316
+ throw new TypeError("callback not provided");
125317
+ }
125318
+ return new Promise(function(resolve7, reject) {
125319
+ isexe(path19, options || {}, function(er, is) {
125320
+ if (er) {
125321
+ reject(er);
125322
+ } else {
125323
+ resolve7(is);
125324
+ }
125325
+ });
125326
+ });
125327
+ }
125328
+ core(path19, options || {}, function(er, is) {
125329
+ if (er) {
125330
+ if (er.code === "EACCES" || options && options.ignoreErrors) {
125331
+ er = null;
125332
+ is = false;
125333
+ }
125334
+ }
125335
+ cb(er, is);
125336
+ });
125337
+ }
125338
+ function sync(path19, options) {
125339
+ try {
125340
+ return core.sync(path19, options || {});
125341
+ } catch (er) {
125342
+ if (options && options.ignoreErrors || er.code === "EACCES") {
125343
+ return false;
125344
+ } else {
125345
+ throw er;
125346
+ }
125347
+ }
125348
+ }
125349
+ }
125350
+ });
125351
+
125352
+ // node_modules/.pnpm/which@2.0.2/node_modules/which/which.js
125353
+ var require_which = __commonJS({
125354
+ "node_modules/.pnpm/which@2.0.2/node_modules/which/which.js"(exports, module) {
125355
+ var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
125356
+ var path19 = __require("path");
125357
+ var COLON = isWindows ? ";" : ":";
125358
+ var isexe = require_isexe();
125359
+ var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
125360
+ var getPathInfo = (cmd, opt) => {
125361
+ const colon = opt.colon || COLON;
125362
+ const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [""] : [
125363
+ // windows always checks the cwd first
125364
+ ...isWindows ? [process.cwd()] : [],
125365
+ ...(opt.path || process.env.PATH || /* istanbul ignore next: very unusual */
125366
+ "").split(colon)
125367
+ ];
125368
+ const pathExtExe = isWindows ? opt.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "";
125369
+ const pathExt = isWindows ? pathExtExe.split(colon) : [""];
125370
+ if (isWindows) {
125371
+ if (cmd.indexOf(".") !== -1 && pathExt[0] !== "")
125372
+ pathExt.unshift("");
125373
+ }
125374
+ return {
125375
+ pathEnv,
125376
+ pathExt,
125377
+ pathExtExe
125378
+ };
125379
+ };
125380
+ var which = (cmd, opt, cb) => {
125381
+ if (typeof opt === "function") {
125382
+ cb = opt;
125383
+ opt = {};
125384
+ }
125385
+ if (!opt)
125386
+ opt = {};
125387
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
125388
+ const found = [];
125389
+ const step = (i) => new Promise((resolve7, reject) => {
125390
+ if (i === pathEnv.length)
125391
+ return opt.all && found.length ? resolve7(found) : reject(getNotFoundError(cmd));
125392
+ const ppRaw = pathEnv[i];
125393
+ const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
125394
+ const pCmd = path19.join(pathPart, cmd);
125395
+ const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
125396
+ resolve7(subStep(p, i, 0));
125397
+ });
125398
+ const subStep = (p, i, ii) => new Promise((resolve7, reject) => {
125399
+ if (ii === pathExt.length)
125400
+ return resolve7(step(i + 1));
125401
+ const ext2 = pathExt[ii];
125402
+ isexe(p + ext2, { pathExt: pathExtExe }, (er, is) => {
125403
+ if (!er && is) {
125404
+ if (opt.all)
125405
+ found.push(p + ext2);
125406
+ else
125407
+ return resolve7(p + ext2);
125408
+ }
125409
+ return resolve7(subStep(p, i, ii + 1));
125410
+ });
125411
+ });
125412
+ return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
125413
+ };
125414
+ var whichSync = (cmd, opt) => {
125415
+ opt = opt || {};
125416
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
125417
+ const found = [];
125418
+ for (let i = 0; i < pathEnv.length; i++) {
125419
+ const ppRaw = pathEnv[i];
125420
+ const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
125421
+ const pCmd = path19.join(pathPart, cmd);
125422
+ const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
125423
+ for (let j = 0; j < pathExt.length; j++) {
125424
+ const cur = p + pathExt[j];
125425
+ try {
125426
+ const is = isexe.sync(cur, { pathExt: pathExtExe });
125427
+ if (is) {
125428
+ if (opt.all)
125429
+ found.push(cur);
125430
+ else
125431
+ return cur;
125432
+ }
125433
+ } catch (ex) {
125434
+ }
125435
+ }
125436
+ }
125437
+ if (opt.all && found.length)
125438
+ return found;
125439
+ if (opt.nothrow)
125440
+ return null;
125441
+ throw getNotFoundError(cmd);
125442
+ };
125443
+ module.exports = which;
125444
+ which.sync = whichSync;
125445
+ }
125446
+ });
125447
+
125448
+ // node_modules/.pnpm/path-key@3.1.1/node_modules/path-key/index.js
125449
+ var require_path_key = __commonJS({
125450
+ "node_modules/.pnpm/path-key@3.1.1/node_modules/path-key/index.js"(exports, module) {
125451
+ "use strict";
125452
+ var pathKey = (options = {}) => {
125453
+ const environment = options.env || process.env;
125454
+ const platform = options.platform || process.platform;
125455
+ if (platform !== "win32") {
125456
+ return "PATH";
125457
+ }
125458
+ return Object.keys(environment).reverse().find((key) => key.toUpperCase() === "PATH") || "Path";
125459
+ };
125460
+ module.exports = pathKey;
125461
+ module.exports.default = pathKey;
125462
+ }
125463
+ });
125464
+
125465
+ // node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js
125466
+ var require_resolveCommand = __commonJS({
125467
+ "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
125468
+ "use strict";
125469
+ var path19 = __require("path");
125470
+ var which = require_which();
125471
+ var getPathKey = require_path_key();
125472
+ function resolveCommandAttempt(parsed, withoutPathExt) {
125473
+ const env = parsed.options.env || process.env;
125474
+ const cwd = process.cwd();
125475
+ const hasCustomCwd = parsed.options.cwd != null;
125476
+ const shouldSwitchCwd = hasCustomCwd && process.chdir !== void 0 && !process.chdir.disabled;
125477
+ if (shouldSwitchCwd) {
125478
+ try {
125479
+ process.chdir(parsed.options.cwd);
125480
+ } catch (err) {
125481
+ }
125482
+ }
125483
+ let resolved;
125484
+ try {
125485
+ resolved = which.sync(parsed.command, {
125486
+ path: env[getPathKey({ env })],
125487
+ pathExt: withoutPathExt ? path19.delimiter : void 0
125488
+ });
125489
+ } catch (e) {
125490
+ } finally {
125491
+ if (shouldSwitchCwd) {
125492
+ process.chdir(cwd);
125493
+ }
125494
+ }
125495
+ if (resolved) {
125496
+ resolved = path19.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
125497
+ }
125498
+ return resolved;
125499
+ }
125500
+ function resolveCommand(parsed) {
125501
+ return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true);
125502
+ }
125503
+ module.exports = resolveCommand;
125504
+ }
125505
+ });
125506
+
125507
+ // node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.js
125508
+ var require_escape = __commonJS({
125509
+ "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.js"(exports, module) {
125510
+ "use strict";
125511
+ var metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
125512
+ function escapeCommand(arg) {
125513
+ arg = arg.replace(metaCharsRegExp, "^$1");
125514
+ return arg;
125515
+ }
125516
+ function escapeArgument(arg, doubleEscapeMetaChars) {
125517
+ arg = `${arg}`;
125518
+ arg = arg.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
125519
+ arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
125520
+ arg = `"${arg}"`;
125521
+ arg = arg.replace(metaCharsRegExp, "^$1");
125522
+ if (doubleEscapeMetaChars) {
125523
+ arg = arg.replace(metaCharsRegExp, "^$1");
125524
+ }
125525
+ return arg;
125526
+ }
125527
+ module.exports.command = escapeCommand;
125528
+ module.exports.argument = escapeArgument;
125529
+ }
125530
+ });
125531
+
125532
+ // node_modules/.pnpm/shebang-regex@3.0.0/node_modules/shebang-regex/index.js
125533
+ var require_shebang_regex = __commonJS({
125534
+ "node_modules/.pnpm/shebang-regex@3.0.0/node_modules/shebang-regex/index.js"(exports, module) {
125535
+ "use strict";
125536
+ module.exports = /^#!(.*)/;
125537
+ }
125538
+ });
125539
+
125540
+ // node_modules/.pnpm/shebang-command@2.0.0/node_modules/shebang-command/index.js
125541
+ var require_shebang_command = __commonJS({
125542
+ "node_modules/.pnpm/shebang-command@2.0.0/node_modules/shebang-command/index.js"(exports, module) {
125543
+ "use strict";
125544
+ var shebangRegex = require_shebang_regex();
125545
+ module.exports = (string = "") => {
125546
+ const match2 = string.match(shebangRegex);
125547
+ if (!match2) {
125548
+ return null;
125549
+ }
125550
+ const [path19, argument] = match2[0].replace(/#! ?/, "").split(" ");
125551
+ const binary = path19.split("/").pop();
125552
+ if (binary === "env") {
125553
+ return argument;
125554
+ }
125555
+ return argument ? `${binary} ${argument}` : binary;
125556
+ };
125557
+ }
125558
+ });
125559
+
125560
+ // node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js
125561
+ var require_readShebang = __commonJS({
125562
+ "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
125563
+ "use strict";
125564
+ var fs14 = __require("fs");
125565
+ var shebangCommand = require_shebang_command();
125566
+ function readShebang(command) {
125567
+ const size = 150;
125568
+ const buffer = Buffer.alloc(size);
125569
+ let fd;
125570
+ try {
125571
+ fd = fs14.openSync(command, "r");
125572
+ fs14.readSync(fd, buffer, 0, size, 0);
125573
+ fs14.closeSync(fd);
125574
+ } catch (e) {
125575
+ }
125576
+ return shebangCommand(buffer.toString());
125577
+ }
125578
+ module.exports = readShebang;
125579
+ }
125580
+ });
125581
+
125582
+ // node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js
125583
+ var require_parse2 = __commonJS({
125584
+ "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js"(exports, module) {
125585
+ "use strict";
125586
+ var path19 = __require("path");
125587
+ var resolveCommand = require_resolveCommand();
125588
+ var escape3 = require_escape();
125589
+ var readShebang = require_readShebang();
125590
+ var isWin = process.platform === "win32";
125591
+ var isExecutableRegExp = /\.(?:com|exe)$/i;
125592
+ var isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
125593
+ function detectShebang(parsed) {
125594
+ parsed.file = resolveCommand(parsed);
125595
+ const shebang = parsed.file && readShebang(parsed.file);
125596
+ if (shebang) {
125597
+ parsed.args.unshift(parsed.file);
125598
+ parsed.command = shebang;
125599
+ return resolveCommand(parsed);
125600
+ }
125601
+ return parsed.file;
125602
+ }
125603
+ function parseNonShell(parsed) {
125604
+ if (!isWin) {
125605
+ return parsed;
125606
+ }
125607
+ const commandFile = detectShebang(parsed);
125608
+ const needsShell = !isExecutableRegExp.test(commandFile);
125609
+ if (parsed.options.forceShell || needsShell) {
125610
+ const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
125611
+ parsed.command = path19.normalize(parsed.command);
125612
+ parsed.command = escape3.command(parsed.command);
125613
+ parsed.args = parsed.args.map((arg) => escape3.argument(arg, needsDoubleEscapeMetaChars));
125614
+ const shellCommand = [parsed.command].concat(parsed.args).join(" ");
125615
+ parsed.args = ["/d", "/s", "/c", `"${shellCommand}"`];
125616
+ parsed.command = process.env.comspec || "cmd.exe";
125617
+ parsed.options.windowsVerbatimArguments = true;
125618
+ }
125619
+ return parsed;
125620
+ }
125621
+ function parse2(command, args, options) {
125622
+ if (args && !Array.isArray(args)) {
125623
+ options = args;
125624
+ args = null;
125625
+ }
125626
+ args = args ? args.slice(0) : [];
125627
+ options = Object.assign({}, options);
125628
+ const parsed = {
125629
+ command,
125630
+ args,
125631
+ options,
125632
+ file: void 0,
125633
+ original: {
125634
+ command,
125635
+ args
125636
+ }
125637
+ };
125638
+ return options.shell ? parsed : parseNonShell(parsed);
125639
+ }
125640
+ module.exports = parse2;
125641
+ }
125642
+ });
125643
+
125644
+ // node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.js
125645
+ var require_enoent = __commonJS({
125646
+ "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.js"(exports, module) {
125647
+ "use strict";
125648
+ var isWin = process.platform === "win32";
125649
+ function notFoundError(original, syscall) {
125650
+ return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), {
125651
+ code: "ENOENT",
125652
+ errno: "ENOENT",
125653
+ syscall: `${syscall} ${original.command}`,
125654
+ path: original.command,
125655
+ spawnargs: original.args
125656
+ });
125657
+ }
125658
+ function hookChildProcess(cp, parsed) {
125659
+ if (!isWin) {
125660
+ return;
125661
+ }
125662
+ const originalEmit = cp.emit;
125663
+ cp.emit = function(name, arg1) {
125664
+ if (name === "exit") {
125665
+ const err = verifyENOENT(arg1, parsed);
125666
+ if (err) {
125667
+ return originalEmit.call(cp, "error", err);
125668
+ }
125669
+ }
125670
+ return originalEmit.apply(cp, arguments);
125671
+ };
125672
+ }
125673
+ function verifyENOENT(status, parsed) {
125674
+ if (isWin && status === 1 && !parsed.file) {
125675
+ return notFoundError(parsed.original, "spawn");
125676
+ }
125677
+ return null;
125678
+ }
125679
+ function verifyENOENTSync(status, parsed) {
125680
+ if (isWin && status === 1 && !parsed.file) {
125681
+ return notFoundError(parsed.original, "spawnSync");
125682
+ }
125683
+ return null;
125684
+ }
125685
+ module.exports = {
125686
+ hookChildProcess,
125687
+ verifyENOENT,
125688
+ verifyENOENTSync,
125689
+ notFoundError
125690
+ };
125691
+ }
125692
+ });
125693
+
125694
+ // node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/index.js
125695
+ var require_cross_spawn = __commonJS({
125696
+ "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/index.js"(exports, module) {
125697
+ "use strict";
125698
+ var cp = __require("child_process");
125699
+ var parse2 = require_parse2();
125700
+ var enoent = require_enoent();
125701
+ function spawn4(command, args, options) {
125702
+ const parsed = parse2(command, args, options);
125703
+ const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
125704
+ enoent.hookChildProcess(spawned, parsed);
125705
+ return spawned;
125706
+ }
125707
+ function spawnSync(command, args, options) {
125708
+ const parsed = parse2(command, args, options);
125709
+ const result = cp.spawnSync(parsed.command, parsed.args, parsed.options);
125710
+ result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
125711
+ return result;
125712
+ }
125713
+ module.exports = spawn4;
125714
+ module.exports.spawn = spawn4;
125715
+ module.exports.sync = spawnSync;
125716
+ module.exports._parse = parse2;
125717
+ module.exports._enoent = enoent;
125718
+ }
125719
+ });
125720
+
124131
125721
  // node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
124132
125722
  var Node, Queue;
124133
125723
  var init_yocto_queue = __esm({
@@ -126608,15 +128198,15 @@ var init_schemas = __esm({
126608
128198
  });
126609
128199
 
126610
128200
  // packages/core/dist/services/llm/cli-provider.js
126611
- import { spawn as spawn3 } from "node:child_process";
126612
128201
  import { mkdirSync, writeFileSync } from "node:fs";
126613
128202
  import { join as join8 } from "node:path";
126614
128203
  import { tmpdir } from "node:os";
126615
128204
  import { randomUUID as randomUUID2 } from "node:crypto";
126616
- var BaseCLIProvider, ClaudeCodeProvider;
128205
+ var import_cross_spawn, BaseCLIProvider, ClaudeCodeProvider;
126617
128206
  var init_cli_provider = __esm({
126618
128207
  "packages/core/dist/services/llm/cli-provider.js"() {
126619
128208
  "use strict";
128209
+ import_cross_spawn = __toESM(require_cross_spawn(), 1);
126620
128210
  init_logger();
126621
128211
  init_p_limit();
126622
128212
  init_analysis_registry();
@@ -126719,7 +128309,7 @@ var init_cli_provider = __esm({
126719
128309
  ...opts?.extraArgs ?? []
126720
128310
  ];
126721
128311
  return new Promise((resolve7, reject) => {
126722
- const child = spawn3(this.binaryName, args, {
128312
+ const child = (0, import_cross_spawn.default)(this.binaryName, args, {
126723
128313
  env: this.getCleanEnv(),
126724
128314
  stdio: ["pipe", "pipe", "pipe"],
126725
128315
  ...this._repoPath ? { cwd: this._repoPath } : {}
@@ -140671,7 +142261,7 @@ import fs2 from "node:fs";
140671
142261
  import os2 from "node:os";
140672
142262
  import path3 from "node:path";
140673
142263
  var TRUECOURSE_DIR = ".truecourse";
140674
- var GITIGNORE_CONTENTS = "analyses/\nLATEST.json\nhistory.json\ndiff.json\nui-state.json\nlogs/\n.analyze.lock\n";
142264
+ var GITIGNORE_CONTENTS = "analyses/\nhistory.json\ndiff.json\nui-state.json\nlogs/\n.analyze.lock\n";
140675
142265
  function getGlobalDir() {
140676
142266
  return process.env.TRUECOURSE_HOME || path3.join(os2.homedir(), TRUECOURSE_DIR);
140677
142267
  }
@@ -146706,7 +148296,38 @@ function splitIntoBatches(tier, rules, content, fileCount, functionCount, filePa
146706
148296
  }
146707
148297
  return batches;
146708
148298
  }
148299
+ function translateContextRangeError(err, fileContents) {
148300
+ if (!(err instanceof RangeError) || !err.message.includes("Invalid string length")) {
148301
+ throw err;
148302
+ }
148303
+ const sized = [...fileContents.entries()].map(([filePath, fc]) => ({
148304
+ filePath,
148305
+ sizeKb: Math.round(fc.content.length / 1024),
148306
+ lineCount: fc.lineCount,
148307
+ // long single lines are a strong minified-bundle signal
148308
+ maxLineLength: fc.lineCount > 0 ? Math.round(fc.content.length / fc.lineCount) : 0
148309
+ })).sort((a, b) => b.sizeKb - a.sizeKb).slice(0, 5);
148310
+ const list = sized.map((f2) => {
148311
+ const minHint = f2.maxLineLength > 5e3 ? " [likely minified]" : "";
148312
+ return ` - ${f2.filePath} (${f2.sizeKb} KB, ${f2.lineCount} lines)${minHint}`;
148313
+ }).join("\n");
148314
+ const suggestions = sized.slice(0, 3).map((f2) => ` ${f2.filePath}`).join("\n");
148315
+ 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.
148316
+
148317
+ Largest files in scope:
148318
+ ${list}
148319
+
148320
+ Add the offending paths to \`.truecourseignore\` at the repo root, e.g.:
148321
+ ${suggestions}`);
148322
+ }
146709
148323
  function estimateContext(rules, fileAnalyses, fileContents, options) {
148324
+ try {
148325
+ return estimateContextInner(rules, fileAnalyses, fileContents, options);
148326
+ } catch (err) {
148327
+ translateContextRangeError(err, fileContents);
148328
+ }
148329
+ }
148330
+ function estimateContextInner(rules, fileAnalyses, fileContents, options) {
146710
148331
  const grouped = groupRulesByContext(rules);
146711
148332
  const tiers = [];
146712
148333
  const useFilePaths = options?.useFilePaths ?? false;
@@ -146765,6 +148386,13 @@ function estimateContext(rules, fileAnalyses, fileContents, options) {
146765
148386
  };
146766
148387
  }
146767
148388
  function routeContext(rules, fileAnalyses, fileContents) {
148389
+ try {
148390
+ return routeContextInner(rules, fileAnalyses, fileContents);
148391
+ } catch (err) {
148392
+ translateContextRangeError(err, fileContents);
148393
+ }
148394
+ }
148395
+ function routeContextInner(rules, fileAnalyses, fileContents) {
146768
148396
  const grouped = groupRulesByContext(rules);
146769
148397
  const batches = [];
146770
148398
  const faByPath = /* @__PURE__ */ new Map();
@@ -147354,24 +148982,39 @@ async function runViolationPipeline(input) {
147354
148982
  tracker?.start(stepKey);
147355
148983
  if (domain === "architecture") {
147356
148984
  tracker?.detail(stepKey, "Service checks...");
148985
+ await new Promise((r) => setImmediate(r));
148986
+ throwIfAborted(signal);
147357
148987
  serviceViolationResults.push(...runDeterministicServiceChecks(result, domainRules));
147358
148988
  tracker?.detail(stepKey, "Module checks...");
148989
+ await new Promise((r) => setImmediate(r));
148990
+ throwIfAborted(signal);
147359
148991
  moduleViolationResults.push(...runDeterministicModuleChecks(result, domainRules));
147360
148992
  tracker?.detail(stepKey, "Method checks...");
148993
+ await new Promise((r) => setImmediate(r));
148994
+ throwIfAborted(signal);
147361
148995
  methodViolationResults.push(...runDeterministicMethodChecks(result, domainRules));
147362
148996
  tracker?.detail(stepKey, "Deterministic checks done");
147363
148997
  }
147364
148998
  }
147365
148999
  const allCodeViolations = [];
147366
149000
  if (enabledCodeRules.length > 0 && filesToScan.length > 0) {
149001
+ const activeCodeDomains = [];
147367
149002
  for (const domain of DOMAIN_ORDER) {
147368
149003
  if (domain === "architecture")
147369
149004
  continue;
147370
149005
  const domainRules = enabledDeterministic.filter((r) => (r.domain ?? "").startsWith(domain));
147371
- if (domainRules.length > 0)
149006
+ if (domainRules.length > 0) {
147372
149007
  tracker?.start(`${domain}`);
149008
+ activeCodeDomains.push(domain);
149009
+ }
147373
149010
  }
147374
149011
  await new Promise((r) => setImmediate(r));
149012
+ const totalFiles = filesToScan.length;
149013
+ let processed = 0;
149014
+ const SPINNER_YIELD_MS = 25;
149015
+ const DETAIL_UPDATE_MS = 100;
149016
+ let lastYieldMs = Date.now();
149017
+ let lastDetailMs = lastYieldMs;
147375
149018
  for (const { filePath, resolve: resolve7 } of filesToScan) {
147376
149019
  try {
147377
149020
  const lang = detectLanguage(filePath);
@@ -147387,6 +149030,23 @@ async function runViolationPipeline(input) {
147387
149030
  allCodeViolations.push(...codeRuleViolations);
147388
149031
  } catch {
147389
149032
  }
149033
+ processed++;
149034
+ if (signal?.aborted)
149035
+ throw new DOMException("Analysis cancelled", "AbortError");
149036
+ const now2 = Date.now();
149037
+ const isLast = processed === totalFiles;
149038
+ if (isLast || now2 - lastDetailMs >= DETAIL_UPDATE_MS) {
149039
+ const detail = `${processed}/${totalFiles} files`;
149040
+ for (const domain of activeCodeDomains)
149041
+ tracker?.detail(domain, detail);
149042
+ lastDetailMs = now2;
149043
+ }
149044
+ if (isLast || now2 - lastYieldMs >= SPINNER_YIELD_MS) {
149045
+ await new Promise((r) => setImmediate(r));
149046
+ lastYieldMs = Date.now();
149047
+ if (signal?.aborted)
149048
+ throw new DOMException("Analysis cancelled", "AbortError");
149049
+ }
147390
149050
  }
147391
149051
  }
147392
149052
  log.info(`[Pipeline] Code scan: ${allCodeViolations.length} violations from ${filesToScan.length} files (${enabledCodeRules.length} det rules, ${enabledLlmCodeRules.length} LLM rules)`);