@neurcode-ai/cli 0.9.63 → 0.9.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/LICENSE +201 -0
  2. package/dist/commands/brain.d.ts.map +1 -1
  3. package/dist/commands/brain.js +273 -0
  4. package/dist/commands/brain.js.map +1 -1
  5. package/dist/commands/control-plane.js +7 -7
  6. package/dist/commands/control-plane.js.map +1 -1
  7. package/dist/commands/fix.d.ts.map +1 -1
  8. package/dist/commands/fix.js +108 -1
  9. package/dist/commands/fix.js.map +1 -1
  10. package/dist/commands/patch-apply.d.ts +2 -0
  11. package/dist/commands/patch-apply.d.ts.map +1 -1
  12. package/dist/commands/patch-apply.js +331 -19
  13. package/dist/commands/patch-apply.js.map +1 -1
  14. package/dist/commands/pilot-report.d.ts +9 -0
  15. package/dist/commands/pilot-report.d.ts.map +1 -0
  16. package/dist/commands/pilot-report.js +176 -0
  17. package/dist/commands/pilot-report.js.map +1 -0
  18. package/dist/commands/remediate-governance.d.ts +54 -0
  19. package/dist/commands/remediate-governance.d.ts.map +1 -0
  20. package/dist/commands/remediate-governance.js +375 -0
  21. package/dist/commands/remediate-governance.js.map +1 -0
  22. package/dist/commands/remediate.d.ts.map +1 -1
  23. package/dist/commands/remediate.js.map +1 -1
  24. package/dist/commands/replay.d.ts.map +1 -1
  25. package/dist/commands/replay.js +35 -5
  26. package/dist/commands/replay.js.map +1 -1
  27. package/dist/commands/verify.d.ts.map +1 -1
  28. package/dist/commands/verify.js +336 -25
  29. package/dist/commands/verify.js.map +1 -1
  30. package/dist/commands/workspace.js +7 -7
  31. package/dist/commands/workspace.js.map +1 -1
  32. package/dist/daemon/server.d.ts +2 -2
  33. package/dist/daemon/server.d.ts.map +1 -1
  34. package/dist/daemon/server.js +2113 -32
  35. package/dist/daemon/server.js.map +1 -1
  36. package/dist/explainability/DeterminismClassifier.d.ts +34 -0
  37. package/dist/explainability/DeterminismClassifier.d.ts.map +1 -0
  38. package/dist/explainability/DeterminismClassifier.js +104 -0
  39. package/dist/explainability/DeterminismClassifier.js.map +1 -0
  40. package/dist/explainability/ViolationFormatter.d.ts +32 -0
  41. package/dist/explainability/ViolationFormatter.d.ts.map +1 -0
  42. package/dist/explainability/ViolationFormatter.js +252 -0
  43. package/dist/explainability/ViolationFormatter.js.map +1 -0
  44. package/dist/explainability/index.d.ts +15 -0
  45. package/dist/explainability/index.d.ts.map +1 -0
  46. package/dist/explainability/index.js +94 -0
  47. package/dist/explainability/index.js.map +1 -0
  48. package/dist/explainability/types.d.ts +37 -0
  49. package/dist/explainability/types.d.ts.map +1 -0
  50. package/dist/explainability/types.js +3 -0
  51. package/dist/explainability/types.js.map +1 -0
  52. package/dist/governance/canonical-pipeline.d.ts +38 -0
  53. package/dist/governance/canonical-pipeline.d.ts.map +1 -0
  54. package/dist/governance/canonical-pipeline.js +448 -0
  55. package/dist/governance/canonical-pipeline.js.map +1 -0
  56. package/dist/governance/structural-on-diff.d.ts +13 -0
  57. package/dist/governance/structural-on-diff.d.ts.map +1 -0
  58. package/dist/governance/structural-on-diff.js +35 -0
  59. package/dist/governance/structural-on-diff.js.map +1 -0
  60. package/dist/governance/structural-policy-merge.d.ts +14 -0
  61. package/dist/governance/structural-policy-merge.d.ts.map +1 -0
  62. package/dist/governance/structural-policy-merge.js +25 -0
  63. package/dist/governance/structural-policy-merge.js.map +1 -0
  64. package/dist/index.js +86 -4
  65. package/dist/index.js.map +1 -1
  66. package/dist/integrations/review-compression/index.d.ts +50 -0
  67. package/dist/integrations/review-compression/index.d.ts.map +1 -0
  68. package/dist/integrations/review-compression/index.js +158 -0
  69. package/dist/integrations/review-compression/index.js.map +1 -0
  70. package/dist/intent-engine/domain-taxonomy.d.ts +42 -0
  71. package/dist/intent-engine/domain-taxonomy.d.ts.map +1 -0
  72. package/dist/intent-engine/domain-taxonomy.js +534 -0
  73. package/dist/intent-engine/domain-taxonomy.js.map +1 -0
  74. package/dist/intent-engine/index.d.ts +1 -0
  75. package/dist/intent-engine/index.d.ts.map +1 -1
  76. package/dist/intent-engine/index.js +6 -1
  77. package/dist/intent-engine/index.js.map +1 -1
  78. package/dist/intent-engine/matcher.d.ts.map +1 -1
  79. package/dist/intent-engine/matcher.js +2 -0
  80. package/dist/intent-engine/matcher.js.map +1 -1
  81. package/dist/intent-engine/parser.d.ts.map +1 -1
  82. package/dist/intent-engine/parser.js +47 -0
  83. package/dist/intent-engine/parser.js.map +1 -1
  84. package/dist/intent-engine/semantic-expander.d.ts +104 -0
  85. package/dist/intent-engine/semantic-expander.d.ts.map +1 -0
  86. package/dist/intent-engine/semantic-expander.js +480 -0
  87. package/dist/intent-engine/semantic-expander.js.map +1 -0
  88. package/dist/patch-engine/diff.d.ts +1 -1
  89. package/dist/patch-engine/diff.js +1 -1
  90. package/dist/patch-engine/generator.d.ts +9 -0
  91. package/dist/patch-engine/generator.d.ts.map +1 -1
  92. package/dist/patch-engine/generator.js +375 -17
  93. package/dist/patch-engine/generator.js.map +1 -1
  94. package/dist/patch-engine/index.d.ts +25 -25
  95. package/dist/patch-engine/index.d.ts.map +1 -1
  96. package/dist/patch-engine/index.js +134 -87
  97. package/dist/patch-engine/index.js.map +1 -1
  98. package/dist/patch-engine/patterns.d.ts +1 -1
  99. package/dist/patch-engine/patterns.d.ts.map +1 -1
  100. package/dist/patch-engine/patterns.js +282 -41
  101. package/dist/patch-engine/patterns.js.map +1 -1
  102. package/dist/patch-engine/rollback.d.ts +31 -0
  103. package/dist/patch-engine/rollback.d.ts.map +1 -0
  104. package/dist/patch-engine/rollback.js +275 -0
  105. package/dist/patch-engine/rollback.js.map +1 -0
  106. package/dist/patch-engine/safety.d.ts +28 -0
  107. package/dist/patch-engine/safety.d.ts.map +1 -0
  108. package/dist/patch-engine/safety.js +122 -0
  109. package/dist/patch-engine/safety.js.map +1 -0
  110. package/dist/patch-engine/transaction.d.ts +52 -0
  111. package/dist/patch-engine/transaction.d.ts.map +1 -0
  112. package/dist/patch-engine/transaction.js +93 -0
  113. package/dist/patch-engine/transaction.js.map +1 -0
  114. package/dist/semantic/index.d.ts +14 -0
  115. package/dist/semantic/index.d.ts.map +1 -0
  116. package/dist/semantic/index.js +30 -0
  117. package/dist/semantic/index.js.map +1 -0
  118. package/dist/semantic/tfidf-engine.d.ts +81 -0
  119. package/dist/semantic/tfidf-engine.d.ts.map +1 -0
  120. package/dist/semantic/tfidf-engine.js +278 -0
  121. package/dist/semantic/tfidf-engine.js.map +1 -0
  122. package/dist/semantic/vector-store.d.ts +108 -0
  123. package/dist/semantic/vector-store.d.ts.map +1 -0
  124. package/dist/semantic/vector-store.js +321 -0
  125. package/dist/semantic/vector-store.js.map +1 -0
  126. package/dist/structural-rules/context-severity.d.ts +46 -0
  127. package/dist/structural-rules/context-severity.d.ts.map +1 -0
  128. package/dist/structural-rules/context-severity.js +115 -0
  129. package/dist/structural-rules/context-severity.js.map +1 -0
  130. package/dist/structural-rules/distributed/DS001-saga-rollback-absence.d.ts +11 -0
  131. package/dist/structural-rules/distributed/DS001-saga-rollback-absence.d.ts.map +1 -0
  132. package/dist/structural-rules/distributed/DS001-saga-rollback-absence.js +212 -0
  133. package/dist/structural-rules/distributed/DS001-saga-rollback-absence.js.map +1 -0
  134. package/dist/structural-rules/distributed/DS002-missing-correlation-id.d.ts +11 -0
  135. package/dist/structural-rules/distributed/DS002-missing-correlation-id.d.ts.map +1 -0
  136. package/dist/structural-rules/distributed/DS002-missing-correlation-id.js +213 -0
  137. package/dist/structural-rules/distributed/DS002-missing-correlation-id.js.map +1 -0
  138. package/dist/structural-rules/distributed/index.d.ts +3 -0
  139. package/dist/structural-rules/distributed/index.d.ts.map +1 -0
  140. package/dist/structural-rules/distributed/index.js +8 -0
  141. package/dist/structural-rules/distributed/index.js.map +1 -0
  142. package/dist/structural-rules/engine.d.ts +25 -0
  143. package/dist/structural-rules/engine.d.ts.map +1 -0
  144. package/dist/structural-rules/engine.js +90 -0
  145. package/dist/structural-rules/engine.js.map +1 -0
  146. package/dist/structural-rules/index.d.ts +41 -0
  147. package/dist/structural-rules/index.d.ts.map +1 -0
  148. package/dist/structural-rules/index.js +141 -0
  149. package/dist/structural-rules/index.js.map +1 -0
  150. package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.d.ts +11 -0
  151. package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.d.ts.map +1 -0
  152. package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.js +66 -0
  153. package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.js.map +1 -0
  154. package/dist/structural-rules/python/PY002-unbounded-dict-singleton.d.ts +11 -0
  155. package/dist/structural-rules/python/PY002-unbounded-dict-singleton.d.ts.map +1 -0
  156. package/dist/structural-rules/python/PY002-unbounded-dict-singleton.js +135 -0
  157. package/dist/structural-rules/python/PY002-unbounded-dict-singleton.js.map +1 -0
  158. package/dist/structural-rules/python/PY003-broad-except-clause.d.ts +11 -0
  159. package/dist/structural-rules/python/PY003-broad-except-clause.d.ts.map +1 -0
  160. package/dist/structural-rules/python/PY003-broad-except-clause.js +86 -0
  161. package/dist/structural-rules/python/PY003-broad-except-clause.js.map +1 -0
  162. package/dist/structural-rules/python/PY004-swallowed-async-exception.d.ts +11 -0
  163. package/dist/structural-rules/python/PY004-swallowed-async-exception.d.ts.map +1 -0
  164. package/dist/structural-rules/python/PY004-swallowed-async-exception.js +167 -0
  165. package/dist/structural-rules/python/PY004-swallowed-async-exception.js.map +1 -0
  166. package/dist/structural-rules/python/PY005-fastapi-without-pydantic.d.ts +11 -0
  167. package/dist/structural-rules/python/PY005-fastapi-without-pydantic.d.ts.map +1 -0
  168. package/dist/structural-rules/python/PY005-fastapi-without-pydantic.js +154 -0
  169. package/dist/structural-rules/python/PY005-fastapi-without-pydantic.js.map +1 -0
  170. package/dist/structural-rules/python/PY006-blocking-io-in-async.d.ts +11 -0
  171. package/dist/structural-rules/python/PY006-blocking-io-in-async.d.ts.map +1 -0
  172. package/dist/structural-rules/python/PY006-blocking-io-in-async.js +130 -0
  173. package/dist/structural-rules/python/PY006-blocking-io-in-async.js.map +1 -0
  174. package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.d.ts +11 -0
  175. package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.d.ts.map +1 -0
  176. package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.js +93 -0
  177. package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.js.map +1 -0
  178. package/dist/structural-rules/python/PY008-celery-task-without-retry.d.ts +11 -0
  179. package/dist/structural-rules/python/PY008-celery-task-without-retry.d.ts.map +1 -0
  180. package/dist/structural-rules/python/PY008-celery-task-without-retry.js +154 -0
  181. package/dist/structural-rules/python/PY008-celery-task-without-retry.js.map +1 -0
  182. package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.d.ts +11 -0
  183. package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.d.ts.map +1 -0
  184. package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.js +133 -0
  185. package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.js.map +1 -0
  186. package/dist/structural-rules/python/PY010-leaked-aiohttp-session.d.ts +11 -0
  187. package/dist/structural-rules/python/PY010-leaked-aiohttp-session.d.ts.map +1 -0
  188. package/dist/structural-rules/python/PY010-leaked-aiohttp-session.js +80 -0
  189. package/dist/structural-rules/python/PY010-leaked-aiohttp-session.js.map +1 -0
  190. package/dist/structural-rules/rules/SR001-swallowed-async-rejection.d.ts +11 -0
  191. package/dist/structural-rules/rules/SR001-swallowed-async-rejection.d.ts.map +1 -0
  192. package/dist/structural-rules/rules/SR001-swallowed-async-rejection.js +145 -0
  193. package/dist/structural-rules/rules/SR001-swallowed-async-rejection.js.map +1 -0
  194. package/dist/structural-rules/rules/SR002-unbounded-collection.d.ts +11 -0
  195. package/dist/structural-rules/rules/SR002-unbounded-collection.d.ts.map +1 -0
  196. package/dist/structural-rules/rules/SR002-unbounded-collection.js +196 -0
  197. package/dist/structural-rules/rules/SR002-unbounded-collection.js.map +1 -0
  198. package/dist/structural-rules/rules/SR003-timer-without-cleanup.d.ts +11 -0
  199. package/dist/structural-rules/rules/SR003-timer-without-cleanup.d.ts.map +1 -0
  200. package/dist/structural-rules/rules/SR003-timer-without-cleanup.js +148 -0
  201. package/dist/structural-rules/rules/SR003-timer-without-cleanup.js.map +1 -0
  202. package/dist/structural-rules/rules/SR004-request-boundary-no-validation.d.ts +11 -0
  203. package/dist/structural-rules/rules/SR004-request-boundary-no-validation.d.ts.map +1 -0
  204. package/dist/structural-rules/rules/SR004-request-boundary-no-validation.js +162 -0
  205. package/dist/structural-rules/rules/SR004-request-boundary-no-validation.js.map +1 -0
  206. package/dist/structural-rules/rules/SR005-halfopen-probe-gate.d.ts +11 -0
  207. package/dist/structural-rules/rules/SR005-halfopen-probe-gate.d.ts.map +1 -0
  208. package/dist/structural-rules/rules/SR005-halfopen-probe-gate.js +150 -0
  209. package/dist/structural-rules/rules/SR005-halfopen-probe-gate.js.map +1 -0
  210. package/dist/structural-rules/rules/SR006-fanout-error-sanitization.d.ts +11 -0
  211. package/dist/structural-rules/rules/SR006-fanout-error-sanitization.d.ts.map +1 -0
  212. package/dist/structural-rules/rules/SR006-fanout-error-sanitization.js +161 -0
  213. package/dist/structural-rules/rules/SR006-fanout-error-sanitization.js.map +1 -0
  214. package/dist/structural-rules/rules/SR007-cross-request-error.d.ts +11 -0
  215. package/dist/structural-rules/rules/SR007-cross-request-error.d.ts.map +1 -0
  216. package/dist/structural-rules/rules/SR007-cross-request-error.js +175 -0
  217. package/dist/structural-rules/rules/SR007-cross-request-error.js.map +1 -0
  218. package/dist/structural-rules/rules/SR008-background-task-orphan.d.ts +11 -0
  219. package/dist/structural-rules/rules/SR008-background-task-orphan.d.ts.map +1 -0
  220. package/dist/structural-rules/rules/SR008-background-task-orphan.js +176 -0
  221. package/dist/structural-rules/rules/SR008-background-task-orphan.js.map +1 -0
  222. package/dist/structural-rules/rules/SR009-missing-retry-backoff.d.ts +11 -0
  223. package/dist/structural-rules/rules/SR009-missing-retry-backoff.d.ts.map +1 -0
  224. package/dist/structural-rules/rules/SR009-missing-retry-backoff.js +168 -0
  225. package/dist/structural-rules/rules/SR009-missing-retry-backoff.js.map +1 -0
  226. package/dist/structural-rules/rules/SR010-retry-storm.d.ts +11 -0
  227. package/dist/structural-rules/rules/SR010-retry-storm.d.ts.map +1 -0
  228. package/dist/structural-rules/rules/SR010-retry-storm.js +181 -0
  229. package/dist/structural-rules/rules/SR010-retry-storm.js.map +1 -0
  230. package/dist/structural-rules/rules/SR011-event-listener-leak.d.ts +11 -0
  231. package/dist/structural-rules/rules/SR011-event-listener-leak.d.ts.map +1 -0
  232. package/dist/structural-rules/rules/SR011-event-listener-leak.js +208 -0
  233. package/dist/structural-rules/rules/SR011-event-listener-leak.js.map +1 -0
  234. package/dist/structural-rules/rules/SR012-promise-race-leak.d.ts +11 -0
  235. package/dist/structural-rules/rules/SR012-promise-race-leak.d.ts.map +1 -0
  236. package/dist/structural-rules/rules/SR012-promise-race-leak.js +191 -0
  237. package/dist/structural-rules/rules/SR012-promise-race-leak.js.map +1 -0
  238. package/dist/structural-rules/rules/SR013-missing-idempotency-key.d.ts +11 -0
  239. package/dist/structural-rules/rules/SR013-missing-idempotency-key.d.ts.map +1 -0
  240. package/dist/structural-rules/rules/SR013-missing-idempotency-key.js +219 -0
  241. package/dist/structural-rules/rules/SR013-missing-idempotency-key.js.map +1 -0
  242. package/dist/structural-rules/rules/SR014-mutable-closure-async.d.ts +11 -0
  243. package/dist/structural-rules/rules/SR014-mutable-closure-async.d.ts.map +1 -0
  244. package/dist/structural-rules/rules/SR014-mutable-closure-async.js +208 -0
  245. package/dist/structural-rules/rules/SR014-mutable-closure-async.js.map +1 -0
  246. package/dist/structural-rules/rules/SR015-dangling-abort-controller.d.ts +11 -0
  247. package/dist/structural-rules/rules/SR015-dangling-abort-controller.d.ts.map +1 -0
  248. package/dist/structural-rules/rules/SR015-dangling-abort-controller.js +190 -0
  249. package/dist/structural-rules/rules/SR015-dangling-abort-controller.js.map +1 -0
  250. package/dist/structural-rules/rules/SR016-unsafe-json-parse.d.ts +11 -0
  251. package/dist/structural-rules/rules/SR016-unsafe-json-parse.d.ts.map +1 -0
  252. package/dist/structural-rules/rules/SR016-unsafe-json-parse.js +187 -0
  253. package/dist/structural-rules/rules/SR016-unsafe-json-parse.js.map +1 -0
  254. package/dist/structural-rules/suppressions.d.ts +43 -0
  255. package/dist/structural-rules/suppressions.d.ts.map +1 -0
  256. package/dist/structural-rules/suppressions.js +115 -0
  257. package/dist/structural-rules/suppressions.js.map +1 -0
  258. package/dist/structural-rules/types.d.ts +43 -0
  259. package/dist/structural-rules/types.d.ts.map +1 -0
  260. package/dist/structural-rules/types.js +3 -0
  261. package/dist/structural-rules/types.js.map +1 -0
  262. package/dist/utils/advisory-signals.d.ts +5 -0
  263. package/dist/utils/advisory-signals.d.ts.map +1 -1
  264. package/dist/utils/advisory-signals.js +50 -12
  265. package/dist/utils/advisory-signals.js.map +1 -1
  266. package/dist/utils/ai-debt-budget.d.ts.map +1 -1
  267. package/dist/utils/ai-debt-budget.js +5 -2
  268. package/dist/utils/ai-debt-budget.js.map +1 -1
  269. package/dist/utils/brain-cache.d.ts +100 -0
  270. package/dist/utils/brain-cache.d.ts.map +1 -0
  271. package/dist/utils/brain-cache.js +346 -0
  272. package/dist/utils/brain-cache.js.map +1 -0
  273. package/dist/utils/cli-json.d.ts.map +1 -1
  274. package/dist/utils/cli-json.js +80 -12
  275. package/dist/utils/cli-json.js.map +1 -1
  276. package/dist/utils/execution-bus.d.ts +10 -0
  277. package/dist/utils/execution-bus.d.ts.map +1 -1
  278. package/dist/utils/execution-bus.js +16 -0
  279. package/dist/utils/execution-bus.js.map +1 -1
  280. package/dist/utils/governance-provenance.d.ts +95 -0
  281. package/dist/utils/governance-provenance.d.ts.map +1 -0
  282. package/dist/utils/governance-provenance.js +187 -0
  283. package/dist/utils/governance-provenance.js.map +1 -0
  284. package/dist/utils/pilot-metrics.d.ts +46 -0
  285. package/dist/utils/pilot-metrics.d.ts.map +1 -0
  286. package/dist/utils/pilot-metrics.js +240 -0
  287. package/dist/utils/pilot-metrics.js.map +1 -0
  288. package/dist/utils/policy-compiler.d.ts +6 -0
  289. package/dist/utils/policy-compiler.d.ts.map +1 -1
  290. package/dist/utils/policy-compiler.js +20 -0
  291. package/dist/utils/policy-compiler.js.map +1 -1
  292. package/dist/utils/replay-runtime.d.ts +34 -0
  293. package/dist/utils/replay-runtime.d.ts.map +1 -1
  294. package/dist/utils/replay-runtime.js +207 -0
  295. package/dist/utils/replay-runtime.js.map +1 -1
  296. package/dist/workspace/cross-repo-graph.d.ts +111 -0
  297. package/dist/workspace/cross-repo-graph.d.ts.map +1 -0
  298. package/dist/workspace/cross-repo-graph.js +450 -0
  299. package/dist/workspace/cross-repo-graph.js.map +1 -0
  300. package/dist/workspace/federated-context.d.ts +144 -0
  301. package/dist/workspace/federated-context.d.ts.map +1 -0
  302. package/dist/workspace/federated-context.js +347 -0
  303. package/dist/workspace/federated-context.js.map +1 -0
  304. package/dist/workspace/index.d.ts +38 -0
  305. package/dist/workspace/index.d.ts.map +1 -0
  306. package/dist/workspace/index.js +48 -0
  307. package/dist/workspace/index.js.map +1 -0
  308. package/package.json +10 -10
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PY004SwallowedAsyncException = void 0;
4
+ // Detect asyncio.gather( calls
5
+ const GATHER_RE = /asyncio\.gather\s*\(/g;
6
+ // return_exceptions=True present
7
+ const RETURN_EXCEPTIONS_RE = /return_exceptions\s*=\s*True/;
8
+ // Detect: await task inside try blocks where except only logs (no re-raise)
9
+ const AWAIT_IN_TRY_RE = /\bawait\s+\w+/;
10
+ const EXCEPT_RE = /^\s*except\b/;
11
+ const RERAISE_RE = /\braise\b/;
12
+ const LOGGING_RE = /\b(?:log|logger|logging|error|warn|report|track|capture|print)\s*[\.(]/i;
13
+ class PY004SwallowedAsyncException {
14
+ id = 'PY004';
15
+ name = 'Swallowed asyncio exception';
16
+ policyRef = 'P018';
17
+ severity = 'ADVISORY';
18
+ languages = ['python'];
19
+ description = 'asyncio.gather() without return_exceptions=True raises on first failure, silently cancelling other tasks. ' +
20
+ 'Also detects try/await blocks where the except only logs without re-raising.';
21
+ check(filePath, sourceText) {
22
+ try {
23
+ const violations = [];
24
+ const lines = sourceText.split('\n');
25
+ // --- Pattern 1: asyncio.gather( without return_exceptions=True ---
26
+ for (let i = 0; i < lines.length; i++) {
27
+ const line = lines[i];
28
+ if (!line.includes('asyncio.gather('))
29
+ continue;
30
+ // Collect the full gather call (may span multiple lines)
31
+ let gatherCall = line;
32
+ let j = i + 1;
33
+ // Scan up to 10 lines ahead to find closing )
34
+ while (j < Math.min(i + 10, lines.length) && !gatherCall.includes(')')) {
35
+ gatherCall += '\n' + lines[j];
36
+ j++;
37
+ }
38
+ if (RETURN_EXCEPTIONS_RE.test(gatherCall))
39
+ continue;
40
+ // Check if it's wrapped in a try block (look backwards up to 5 lines)
41
+ let insideTry = false;
42
+ for (let k = Math.max(0, i - 5); k < i; k++) {
43
+ if (/^\s*try\s*:/.test(lines[k])) {
44
+ insideTry = true;
45
+ break;
46
+ }
47
+ }
48
+ if (insideTry)
49
+ continue;
50
+ const evidence = line.slice(0, 120);
51
+ violations.push({
52
+ ruleId: this.id,
53
+ ruleName: this.name,
54
+ policyRef: this.policyRef,
55
+ severity: this.severity,
56
+ filePath,
57
+ line: i + 1,
58
+ column: line.indexOf('asyncio.gather') + 1,
59
+ evidence,
60
+ operationalRisk: 'asyncio.gather() without return_exceptions=True raises on the first task failure, ' +
61
+ 'but remaining tasks continue running as orphans. Their results and exceptions are lost, ' +
62
+ 'and resources they hold remain locked.',
63
+ remediation: 'Use `results = await asyncio.gather(*tasks, return_exceptions=True)` then inspect ' +
64
+ 'results: `errors = [r for r in results if isinstance(r, BaseException)]`.',
65
+ determinism: 'deterministic-structural',
66
+ confidence: 0.75,
67
+ language: 'python',
68
+ });
69
+ }
70
+ // --- Pattern 2: await inside try/except where except only logs, no re-raise ---
71
+ let i = 0;
72
+ while (i < lines.length) {
73
+ const line = lines[i];
74
+ const trimmed = line.trimStart();
75
+ if (!/^\s*try\s*:/.test(line)) {
76
+ i++;
77
+ continue;
78
+ }
79
+ const tryIndent = line.length - trimmed.length;
80
+ // Collect try body
81
+ const tryBody = [];
82
+ let j = i + 1;
83
+ while (j < lines.length) {
84
+ const bl = lines[j];
85
+ const bt = bl.trimStart();
86
+ if (bt.length === 0) {
87
+ j++;
88
+ continue;
89
+ }
90
+ const bi = bl.length - bt.length;
91
+ if (bi <= tryIndent && bt.length > 0)
92
+ break;
93
+ tryBody.push(bl);
94
+ j++;
95
+ }
96
+ // Does the try body contain an await?
97
+ if (!AWAIT_IN_TRY_RE.test(tryBody.join('\n'))) {
98
+ i = j;
99
+ continue;
100
+ }
101
+ // Now look for except block
102
+ while (j < lines.length) {
103
+ const el = lines[j];
104
+ const et = el.trimStart();
105
+ if (et.length === 0) {
106
+ j++;
107
+ continue;
108
+ }
109
+ const ei = el.length - et.length;
110
+ if (ei < tryIndent)
111
+ break;
112
+ if (!EXCEPT_RE.test(el)) {
113
+ j++;
114
+ continue;
115
+ }
116
+ // Found an except — collect its body
117
+ const exceptBody = [];
118
+ let k = j + 1;
119
+ while (k < lines.length) {
120
+ const kb = lines[k];
121
+ const kt = kb.trimStart();
122
+ if (kt.length === 0) {
123
+ k++;
124
+ continue;
125
+ }
126
+ const ki = kb.length - kt.length;
127
+ if (ki <= ei && kt.length > 0)
128
+ break;
129
+ exceptBody.push(kb);
130
+ k++;
131
+ }
132
+ const exceptText = exceptBody.join('\n');
133
+ if (!RERAISE_RE.test(exceptText) && LOGGING_RE.test(exceptText)) {
134
+ // Only logs, no re-raise — flag it
135
+ const evidence = el.slice(0, 120);
136
+ violations.push({
137
+ ruleId: this.id,
138
+ ruleName: this.name,
139
+ policyRef: this.policyRef,
140
+ severity: this.severity,
141
+ filePath,
142
+ line: j + 1,
143
+ column: ei + 1,
144
+ evidence,
145
+ operationalRisk: 'Exception from an awaited task is caught, logged, but not re-raised. ' +
146
+ 'The caller receives a successful return instead of an error, ' +
147
+ 'causing silent data inconsistency.',
148
+ remediation: 'Re-raise after logging: `except Exception as e: logger.error(e); raise` ' +
149
+ 'or let the exception propagate to a top-level handler.',
150
+ determinism: 'heuristic-advisory',
151
+ confidence: 0.75,
152
+ language: 'python',
153
+ });
154
+ }
155
+ j = k;
156
+ }
157
+ i = j;
158
+ }
159
+ return violations;
160
+ }
161
+ catch {
162
+ return [];
163
+ }
164
+ }
165
+ }
166
+ exports.PY004SwallowedAsyncException = PY004SwallowedAsyncException;
167
+ //# sourceMappingURL=PY004-swallowed-async-exception.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY004-swallowed-async-exception.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY004-swallowed-async-exception.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
2
+ export declare class PY005FastAPIWithoutPydantic implements StructuralRule {
3
+ id: string;
4
+ name: string;
5
+ policyRef: string;
6
+ severity: "BLOCKING";
7
+ languages: RuleLanguage[];
8
+ description: string;
9
+ check(filePath: string, sourceText: string): StructuralViolation[];
10
+ }
11
+ //# sourceMappingURL=PY005-fastapi-without-pydantic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY005-fastapi-without-pydantic.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY005-fastapi-without-pydantic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AA0D7E,qBAAa,2BAA4B,YAAW,cAAc;IAChE,EAAE,SAAW;IACb,IAAI,SAAuE;IAC3E,SAAS,SAAU;IACnB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SACmH;IAE9H,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAiGnE"}
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PY005FastAPIWithoutPydantic = void 0;
4
+ // FastAPI route decorator patterns
5
+ const ROUTE_DECORATOR_RE = /^\s*@(?:app|router|api_router)\.(get|post|put|patch|delete|head|options|trace)\s*\(/;
6
+ // Detect request.body() or request.json() or await request.body() / await request.json()
7
+ const RAW_REQUEST_RE = /\b(?:request|req)\s*\.\s*(?:body|json)\s*\(\)/;
8
+ // Detect Pydantic model usage in function signature: param: ModelName
9
+ // where ModelName starts with uppercase and isn't Request/Response/BackgroundTasks
10
+ const PYDANTIC_PARAM_RE = /\w+\s*:\s*([A-Z][A-Za-z0-9]+)(?!\s*=\s*(?:None|True|False|\d))/;
11
+ // Common non-Pydantic FastAPI types to exclude
12
+ const NON_PYDANTIC_TYPES = new Set([
13
+ 'Request',
14
+ 'Response',
15
+ 'BackgroundTasks',
16
+ 'HTTPException',
17
+ 'Depends',
18
+ 'Optional',
19
+ 'List',
20
+ 'Dict',
21
+ 'Any',
22
+ 'str',
23
+ 'int',
24
+ 'float',
25
+ 'bool',
26
+ 'bytes',
27
+ ]);
28
+ function extractFunctionSignature(lines, startIdx) {
29
+ // Collect function definition until the colon is found
30
+ let sig = '';
31
+ let depth = 0;
32
+ for (let i = startIdx; i < Math.min(startIdx + 10, lines.length); i++) {
33
+ sig += lines[i] + '\n';
34
+ for (const ch of lines[i]) {
35
+ if (ch === '(')
36
+ depth++;
37
+ else if (ch === ')')
38
+ depth--;
39
+ }
40
+ if (depth <= 0 && sig.includes('('))
41
+ break;
42
+ }
43
+ return sig;
44
+ }
45
+ function hasPydanticModelParam(sig) {
46
+ // Find all type annotations in the signature
47
+ const paramRegex = /\w+\s*:\s*([A-Z][A-Za-z0-9_]*)/g;
48
+ let match;
49
+ while ((match = paramRegex.exec(sig)) !== null) {
50
+ const typeName = match[1];
51
+ if (!NON_PYDANTIC_TYPES.has(typeName)) {
52
+ return true;
53
+ }
54
+ }
55
+ return false;
56
+ }
57
+ class PY005FastAPIWithoutPydantic {
58
+ id = 'PY005';
59
+ name = 'FastAPI route handler accessing raw request body without Pydantic';
60
+ policyRef = 'P019';
61
+ severity = 'BLOCKING';
62
+ languages = ['python'];
63
+ description = 'FastAPI route handlers that access request.body() or request.json() without a Pydantic model parameter bypass validation.';
64
+ check(filePath, sourceText) {
65
+ try {
66
+ const violations = [];
67
+ const lines = sourceText.split('\n');
68
+ let i = 0;
69
+ while (i < lines.length) {
70
+ const line = lines[i];
71
+ // Look for route decorator
72
+ if (!ROUTE_DECORATOR_RE.test(line)) {
73
+ i++;
74
+ continue;
75
+ }
76
+ const decoratorLine = i;
77
+ // Find the function definition (next def after decorator, skip other decorators)
78
+ let funcDefLine = -1;
79
+ let j = i + 1;
80
+ while (j < Math.min(i + 10, lines.length)) {
81
+ const l = lines[j].trimStart();
82
+ if (/^(?:async\s+)?def\s+\w+\s*\(/.test(l)) {
83
+ funcDefLine = j;
84
+ break;
85
+ }
86
+ if (l.length > 0 && !l.startsWith('@') && !l.startsWith('#'))
87
+ break;
88
+ j++;
89
+ }
90
+ if (funcDefLine === -1) {
91
+ i = j + 1;
92
+ continue;
93
+ }
94
+ // Extract function signature
95
+ const sig = extractFunctionSignature(lines, funcDefLine);
96
+ // If signature has a Pydantic model param, no violation
97
+ if (hasPydanticModelParam(sig)) {
98
+ i = funcDefLine + 1;
99
+ continue;
100
+ }
101
+ // Find function body (collect until next function/class at same or lower indent)
102
+ const funcIndent = lines[funcDefLine].length - lines[funcDefLine].trimStart().length;
103
+ const bodyLines = [];
104
+ let k = funcDefLine + 1;
105
+ while (k < lines.length) {
106
+ const bl = lines[k];
107
+ const bt = bl.trimStart();
108
+ if (bt.length === 0) {
109
+ k++;
110
+ continue;
111
+ }
112
+ const bi = bl.length - bt.length;
113
+ if (bi <= funcIndent && bt.length > 0)
114
+ break;
115
+ bodyLines.push(bl);
116
+ k++;
117
+ }
118
+ const bodyText = bodyLines.join('\n');
119
+ // Check if body accesses request.body() or request.json()
120
+ if (!RAW_REQUEST_RE.test(bodyText)) {
121
+ i = k;
122
+ continue;
123
+ }
124
+ const evidence = lines[funcDefLine].slice(0, 120);
125
+ violations.push({
126
+ ruleId: this.id,
127
+ ruleName: this.name,
128
+ policyRef: this.policyRef,
129
+ severity: this.severity,
130
+ filePath,
131
+ line: funcDefLine + 1,
132
+ column: (lines[funcDefLine].match(/^\s*/)?.[0].length ?? 0) + 1,
133
+ evidence,
134
+ operationalRisk: 'Raw request body is accessed without Pydantic validation. ' +
135
+ 'Malformed, oversized, or malicious payloads reach business logic directly, ' +
136
+ 'causing runtime errors, type confusion, or injection vulnerabilities.',
137
+ remediation: 'Add a Pydantic model parameter to the route handler: ' +
138
+ '`async def handler(data: MyRequestModel)` and let FastAPI validate and parse the body automatically. ' +
139
+ 'Remove the manual `request.body()` / `request.json()` call.',
140
+ determinism: 'deterministic-structural',
141
+ confidence: 0.80,
142
+ language: 'python',
143
+ });
144
+ i = k;
145
+ }
146
+ return violations;
147
+ }
148
+ catch {
149
+ return [];
150
+ }
151
+ }
152
+ }
153
+ exports.PY005FastAPIWithoutPydantic = PY005FastAPIWithoutPydantic;
154
+ //# sourceMappingURL=PY005-fastapi-without-pydantic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY005-fastapi-without-pydantic.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY005-fastapi-without-pydantic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
2
+ export declare class PY006BlockingIOInAsync implements StructuralRule {
3
+ id: string;
4
+ name: string;
5
+ policyRef: string;
6
+ severity: "BLOCKING";
7
+ languages: RuleLanguage[];
8
+ description: string;
9
+ check(filePath: string, sourceText: string): StructuralViolation[];
10
+ }
11
+ //# sourceMappingURL=PY006-blocking-io-in-async.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY006-blocking-io-in-async.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY006-blocking-io-in-async.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAwB7E,qBAAa,sBAAuB,YAAW,cAAc;IAC3D,EAAE,SAAW;IACb,IAAI,SAAwC;IAC5C,SAAS,SAAW;IACpB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SACoG;IAE/G,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CA8GnE"}
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PY006BlockingIOInAsync = void 0;
4
+ // Matches `async def funcname(`
5
+ const ASYNC_DEF_RE = /^(\s*)async\s+def\s+\w+\s*\(/;
6
+ // Matches nested sync `def` (not async def)
7
+ const SYNC_DEF_RE = /^\s+def\s+\w+\s*\(/;
8
+ // Blocking patterns to detect inside async function bodies
9
+ const BLOCKING_PATTERNS = [
10
+ { re: /\btime\.sleep\s*\(/, label: 'time.sleep()' },
11
+ { re: /\brequests\.get\s*\(/, label: 'requests.get()' },
12
+ { re: /\brequests\.post\s*\(/, label: 'requests.post()' },
13
+ { re: /\brequests\.request\s*\(/, label: 'requests.request()' },
14
+ { re: /\bsubprocess\.run\s*\(/, label: 'subprocess.run()' },
15
+ { re: /\bsubprocess\.call\s*\(/, label: 'subprocess.call()' },
16
+ // open( not preceded by aiofiles.open or async with
17
+ { re: /(?<!aiofiles\.)(?<!\bwith\s)\bopen\s*\(/, label: 'open()' },
18
+ ];
19
+ function getIndent(line) {
20
+ return line.length - line.trimStart().length;
21
+ }
22
+ class PY006BlockingIOInAsync {
23
+ id = 'PY006';
24
+ name = 'Blocking I/O call inside async def';
25
+ policyRef = 'PY006';
26
+ severity = 'BLOCKING';
27
+ languages = ['python'];
28
+ description = 'Blocking I/O (time.sleep, requests, open, subprocess) inside an async def function freezes the event loop.';
29
+ check(filePath, sourceText) {
30
+ try {
31
+ const violations = [];
32
+ // Normalize line endings
33
+ const lines = sourceText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
34
+ // Does the file import aiofiles?
35
+ const importsAiofiles = /\baiofiles\b/.test(sourceText);
36
+ let i = 0;
37
+ while (i < lines.length) {
38
+ const line = lines[i];
39
+ const asyncMatch = ASYNC_DEF_RE.exec(line);
40
+ if (!asyncMatch) {
41
+ i++;
42
+ continue;
43
+ }
44
+ const funcIndent = asyncMatch[1].length;
45
+ const bodyStart = i + 1;
46
+ i++;
47
+ // Collect the function body: lines with indent > funcIndent
48
+ while (i < lines.length) {
49
+ const bl = lines[i];
50
+ const trimmed = bl.trimStart();
51
+ // Blank lines are part of the body
52
+ if (trimmed.length === 0) {
53
+ i++;
54
+ continue;
55
+ }
56
+ const lineIndent = getIndent(bl);
57
+ // If we're back at or before the function's indent, we've left the body
58
+ if (lineIndent <= funcIndent)
59
+ break;
60
+ // Skip comment lines
61
+ if (trimmed.startsWith('#')) {
62
+ i++;
63
+ continue;
64
+ }
65
+ // Skip lines with noqa
66
+ if (/\bnoqa\b/.test(bl)) {
67
+ i++;
68
+ continue;
69
+ }
70
+ // Skip lines that are inside a nested sync def
71
+ // (we only care about the top-level async body, not nested sync helpers)
72
+ if (SYNC_DEF_RE.test(bl)) {
73
+ // Skip the entire nested sync function body
74
+ const nestedIndent = lineIndent;
75
+ i++;
76
+ while (i < lines.length) {
77
+ const nb = lines[i];
78
+ const nt = nb.trimStart();
79
+ if (nt.length === 0) {
80
+ i++;
81
+ continue;
82
+ }
83
+ if (getIndent(nb) <= nestedIndent)
84
+ break;
85
+ i++;
86
+ }
87
+ continue;
88
+ }
89
+ // Check blocking patterns
90
+ for (const { re, label } of BLOCKING_PATTERNS) {
91
+ // If it's an open() hit and aiofiles is imported, skip
92
+ if (label === 'open()' && importsAiofiles)
93
+ continue;
94
+ if (re.test(bl)) {
95
+ violations.push({
96
+ ruleId: this.id,
97
+ ruleName: this.name,
98
+ policyRef: this.policyRef,
99
+ severity: this.severity,
100
+ filePath,
101
+ line: i + 1,
102
+ column: 1,
103
+ evidence: bl.slice(0, 120),
104
+ operationalRisk: `\`${label}\` inside an async function blocks the entire event loop thread. ` +
105
+ 'All other coroutines are frozen for the duration of the call. ' +
106
+ 'Under load, a single blocking call can cause 100ms+ latency spikes across all concurrent requests.',
107
+ remediation: 'Replace time.sleep(n) with `await asyncio.sleep(n)`, ' +
108
+ 'requests.get() with `await aiohttp.ClientSession().get()`, ' +
109
+ 'open() with `async with aiofiles.open()`, ' +
110
+ 'subprocess.run() with `await asyncio.create_subprocess_exec()`.',
111
+ determinism: 'heuristic-advisory',
112
+ confidence: 0.82,
113
+ language: 'python',
114
+ });
115
+ break; // one violation per line
116
+ }
117
+ }
118
+ i++;
119
+ }
120
+ void bodyStart; // suppress unused warning
121
+ }
122
+ return violations;
123
+ }
124
+ catch {
125
+ return [];
126
+ }
127
+ }
128
+ }
129
+ exports.PY006BlockingIOInAsync = PY006BlockingIOInAsync;
130
+ //# sourceMappingURL=PY006-blocking-io-in-async.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY006-blocking-io-in-async.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY006-blocking-io-in-async.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
2
+ export declare class PY007SQLAlchemySessionLeak implements StructuralRule {
3
+ id: string;
4
+ name: string;
5
+ policyRef: string;
6
+ severity: "BLOCKING";
7
+ languages: RuleLanguage[];
8
+ description: string;
9
+ check(filePath: string, sourceText: string): StructuralViolation[];
10
+ }
11
+ //# sourceMappingURL=PY007-sqlalchemy-session-leak.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY007-sqlalchemy-session-leak.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY007-sqlalchemy-session-leak.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAmB7E,qBAAa,0BAA2B,YAAW,cAAc;IAC/D,EAAE,SAAW;IACb,IAAI,SAAwD;IAC5D,SAAS,SAAW;IACpB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SACwG;IAEnH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CA6EnE"}
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PY007SQLAlchemySessionLeak = void 0;
4
+ // Matches bare session assignment: session = Session() / session = SessionLocal() etc.
5
+ // Captures the variable name and the constructor call
6
+ const SESSION_ASSIGN_RE = /^(\s*)(\w+)\s*=\s*(Session|SessionLocal|AsyncSession|get_session|ScopedSession|sessionmaker\(\))\s*\(/;
7
+ // Matches a `with` or `async with` block opening with Session
8
+ const WITH_SESSION_RE = /^\s*(?:async\s+)?with\s+.*Session/;
9
+ // Matches session.close() in a finally block vicinity
10
+ const SESSION_CLOSE_RE = /\bsession\s*\.\s*close\s*\(\)/;
11
+ // Matches a `finally:` block
12
+ const FINALLY_RE = /^\s*finally\s*:/;
13
+ function getIndent(line) {
14
+ return line.length - line.trimStart().length;
15
+ }
16
+ class PY007SQLAlchemySessionLeak {
17
+ id = 'PY007';
18
+ name = 'SQLAlchemy session created outside context manager';
19
+ policyRef = 'PY007';
20
+ severity = 'BLOCKING';
21
+ languages = ['python'];
22
+ description = 'SQLAlchemy session assigned without a context manager or try/finally close() risks connection pool exhaustion.';
23
+ check(filePath, sourceText) {
24
+ try {
25
+ const violations = [];
26
+ // Normalize line endings
27
+ const lines = sourceText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
28
+ for (let i = 0; i < lines.length; i++) {
29
+ const line = lines[i];
30
+ // Skip if this line is a `with Session()` — safe usage
31
+ if (WITH_SESSION_RE.test(line))
32
+ continue;
33
+ const match = SESSION_ASSIGN_RE.exec(line);
34
+ if (!match)
35
+ continue;
36
+ const varName = match[2];
37
+ const assignIndent = match[1].length;
38
+ // Look ahead: find if there is a try/finally with session.close()
39
+ // Search up to 60 lines ahead within the same or deeper indentation scope
40
+ let hasFinallyClose = false;
41
+ let inFinally = false;
42
+ for (let j = i + 1; j < Math.min(i + 60, lines.length); j++) {
43
+ const jl = lines[j];
44
+ const jt = jl.trimStart();
45
+ if (jt.length === 0)
46
+ continue;
47
+ const jIndent = getIndent(jl);
48
+ // If we've gone back to a shallower indent than the assignment, stop
49
+ if (jIndent < assignIndent)
50
+ break;
51
+ if (FINALLY_RE.test(jl)) {
52
+ inFinally = true;
53
+ continue;
54
+ }
55
+ if (inFinally) {
56
+ // Check for varName.close() or generic session.close()
57
+ const closeRe = new RegExp(`\\b${varName}\\s*\\.\\s*close\\s*\\(\\)`);
58
+ if (closeRe.test(jl) || SESSION_CLOSE_RE.test(jl)) {
59
+ hasFinallyClose = true;
60
+ break;
61
+ }
62
+ }
63
+ }
64
+ if (!hasFinallyClose) {
65
+ violations.push({
66
+ ruleId: this.id,
67
+ ruleName: this.name,
68
+ policyRef: this.policyRef,
69
+ severity: this.severity,
70
+ filePath,
71
+ line: i + 1,
72
+ column: 1,
73
+ evidence: line.slice(0, 120),
74
+ operationalRisk: 'Unclosed SQLAlchemy sessions hold database connections open indefinitely. ' +
75
+ 'Connection pools exhaust under load, causing `TimeoutError: QueuePool limit of size X overflow Y reached` ' +
76
+ 'in production within hours of deployment.',
77
+ remediation: 'Use `with Session() as session:` or `async with AsyncSession() as session:` for automatic cleanup. ' +
78
+ 'Never use bare `session = Session()` without a corresponding `finally: session.close()`.',
79
+ determinism: 'heuristic-advisory',
80
+ confidence: 0.78,
81
+ language: 'python',
82
+ });
83
+ }
84
+ }
85
+ return violations;
86
+ }
87
+ catch {
88
+ return [];
89
+ }
90
+ }
91
+ }
92
+ exports.PY007SQLAlchemySessionLeak = PY007SQLAlchemySessionLeak;
93
+ //# sourceMappingURL=PY007-sqlalchemy-session-leak.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY007-sqlalchemy-session-leak.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY007-sqlalchemy-session-leak.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
2
+ export declare class PY008CeleryTaskWithoutRetry implements StructuralRule {
3
+ id: string;
4
+ name: string;
5
+ policyRef: string;
6
+ severity: "ADVISORY";
7
+ languages: RuleLanguage[];
8
+ description: string;
9
+ check(filePath: string, sourceText: string): StructuralViolation[];
10
+ }
11
+ //# sourceMappingURL=PY008-celery-task-without-retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY008-celery-task-without-retry.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY008-celery-task-without-retry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AA8C7E,qBAAa,2BAA4B,YAAW,cAAc;IAChE,EAAE,SAAW;IACb,IAAI,SAA6C;IACjD,SAAS,SAAW;IACpB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SACmH;IAE9H,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAiHnE"}