@neurcode-ai/cli 0.9.65 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/dist/commands/bootstrap-policy.d.ts +29 -0
  2. package/dist/commands/bootstrap-policy.d.ts.map +1 -0
  3. package/dist/commands/bootstrap-policy.js +334 -0
  4. package/dist/commands/bootstrap-policy.js.map +1 -0
  5. package/dist/commands/doctor.d.ts.map +1 -1
  6. package/dist/commands/doctor.js +82 -0
  7. package/dist/commands/doctor.js.map +1 -1
  8. package/dist/commands/governance.d.ts +3 -0
  9. package/dist/commands/governance.d.ts.map +1 -0
  10. package/dist/commands/governance.js +390 -0
  11. package/dist/commands/governance.js.map +1 -0
  12. package/dist/commands/quickstart.d.ts +21 -0
  13. package/dist/commands/quickstart.d.ts.map +1 -0
  14. package/dist/commands/quickstart.js +178 -0
  15. package/dist/commands/quickstart.js.map +1 -0
  16. package/dist/commands/remediate-export.d.ts +36 -0
  17. package/dist/commands/remediate-export.d.ts.map +1 -0
  18. package/dist/commands/remediate-export.js +1072 -0
  19. package/dist/commands/remediate-export.js.map +1 -0
  20. package/dist/commands/replay.d.ts.map +1 -1
  21. package/dist/commands/replay.js +14 -0
  22. package/dist/commands/replay.js.map +1 -1
  23. package/dist/commands/session.d.ts +7 -0
  24. package/dist/commands/session.d.ts.map +1 -1
  25. package/dist/commands/session.js +156 -0
  26. package/dist/commands/session.js.map +1 -1
  27. package/dist/commands/start-intent.d.ts.map +1 -1
  28. package/dist/commands/start-intent.js +61 -11
  29. package/dist/commands/start-intent.js.map +1 -1
  30. package/dist/commands/verify-guidance.d.ts +5 -0
  31. package/dist/commands/verify-guidance.d.ts.map +1 -0
  32. package/dist/commands/verify-guidance.js +49 -0
  33. package/dist/commands/verify-guidance.js.map +1 -0
  34. package/dist/commands/verify-output.d.ts +37 -0
  35. package/dist/commands/verify-output.d.ts.map +1 -0
  36. package/dist/commands/verify-output.js +572 -0
  37. package/dist/commands/verify-output.js.map +1 -0
  38. package/dist/commands/verify-render.d.ts +41 -0
  39. package/dist/commands/verify-render.d.ts.map +1 -0
  40. package/dist/commands/verify-render.js +457 -0
  41. package/dist/commands/verify-render.js.map +1 -0
  42. package/dist/commands/verify.d.ts.map +1 -1
  43. package/dist/commands/verify.js +384 -1091
  44. package/dist/commands/verify.js.map +1 -1
  45. package/dist/commands/workspace.d.ts.map +1 -1
  46. package/dist/commands/workspace.js +3 -14
  47. package/dist/commands/workspace.js.map +1 -1
  48. package/dist/context-engine/graph.d.ts.map +1 -1
  49. package/dist/context-engine/graph.js +69 -7
  50. package/dist/context-engine/graph.js.map +1 -1
  51. package/dist/context-engine/scanner.d.ts.map +1 -1
  52. package/dist/context-engine/scanner.js +9 -2
  53. package/dist/context-engine/scanner.js.map +1 -1
  54. package/dist/daemon/compatibility/execution.d.ts +42 -0
  55. package/dist/daemon/compatibility/execution.d.ts.map +1 -0
  56. package/dist/daemon/compatibility/execution.js +183 -0
  57. package/dist/daemon/compatibility/execution.js.map +1 -0
  58. package/dist/daemon/compatibility/mutation.d.ts +24 -0
  59. package/dist/daemon/compatibility/mutation.d.ts.map +1 -0
  60. package/dist/daemon/compatibility/mutation.js +724 -0
  61. package/dist/daemon/compatibility/mutation.js.map +1 -0
  62. package/dist/daemon/routes.d.ts +19 -0
  63. package/dist/daemon/routes.d.ts.map +1 -0
  64. package/dist/daemon/routes.js +123 -0
  65. package/dist/daemon/routes.js.map +1 -0
  66. package/dist/daemon/runtime/execution-bus.d.ts +217 -0
  67. package/dist/daemon/runtime/execution-bus.d.ts.map +1 -0
  68. package/dist/daemon/runtime/execution-bus.js +1420 -0
  69. package/dist/daemon/runtime/execution-bus.js.map +1 -0
  70. package/dist/daemon/runtime/workspace-runtime.d.ts +280 -0
  71. package/dist/daemon/runtime/workspace-runtime.d.ts.map +1 -0
  72. package/dist/daemon/runtime/workspace-runtime.js +1473 -0
  73. package/dist/daemon/runtime/workspace-runtime.js.map +1 -0
  74. package/dist/daemon/server.d.ts.map +1 -1
  75. package/dist/daemon/server.js +171 -874
  76. package/dist/daemon/server.js.map +1 -1
  77. package/dist/daemon/shaping.d.ts +11 -0
  78. package/dist/daemon/shaping.d.ts.map +1 -0
  79. package/dist/daemon/shaping.js +240 -0
  80. package/dist/daemon/shaping.js.map +1 -0
  81. package/dist/governance/canonical-invariants.d.ts +88 -0
  82. package/dist/governance/canonical-invariants.d.ts.map +1 -0
  83. package/dist/governance/canonical-invariants.js +197 -0
  84. package/dist/governance/canonical-invariants.js.map +1 -0
  85. package/dist/governance/canonical-ordering.d.ts +76 -0
  86. package/dist/governance/canonical-ordering.d.ts.map +1 -0
  87. package/dist/governance/canonical-ordering.js +189 -0
  88. package/dist/governance/canonical-ordering.js.map +1 -0
  89. package/dist/governance/canonical-pipeline.d.ts +9 -1
  90. package/dist/governance/canonical-pipeline.d.ts.map +1 -1
  91. package/dist/governance/canonical-pipeline.js +367 -24
  92. package/dist/governance/canonical-pipeline.js.map +1 -1
  93. package/dist/governance/diff-line-provenance.d.ts +59 -0
  94. package/dist/governance/diff-line-provenance.d.ts.map +1 -0
  95. package/dist/governance/diff-line-provenance.js +118 -0
  96. package/dist/governance/diff-line-provenance.js.map +1 -0
  97. package/dist/governance/pilot-readiness.d.ts +34 -0
  98. package/dist/governance/pilot-readiness.d.ts.map +1 -0
  99. package/dist/governance/pilot-readiness.js +226 -0
  100. package/dist/governance/pilot-readiness.js.map +1 -0
  101. package/dist/governance/policy-parity-validator.d.ts +62 -0
  102. package/dist/governance/policy-parity-validator.d.ts.map +1 -0
  103. package/dist/governance/policy-parity-validator.js +137 -0
  104. package/dist/governance/policy-parity-validator.js.map +1 -0
  105. package/dist/governance/remediation-boundary.d.ts +55 -0
  106. package/dist/governance/remediation-boundary.d.ts.map +1 -0
  107. package/dist/governance/remediation-boundary.js +120 -0
  108. package/dist/governance/remediation-boundary.js.map +1 -0
  109. package/dist/governance/structural-cache.d.ts +103 -0
  110. package/dist/governance/structural-cache.d.ts.map +1 -0
  111. package/dist/governance/structural-cache.js +235 -0
  112. package/dist/governance/structural-cache.js.map +1 -0
  113. package/dist/governance/structural-on-diff.d.ts +22 -2
  114. package/dist/governance/structural-on-diff.d.ts.map +1 -1
  115. package/dist/governance/structural-on-diff.js +36 -4
  116. package/dist/governance/structural-on-diff.js.map +1 -1
  117. package/dist/governance/structural-policy-merge.d.ts +8 -0
  118. package/dist/governance/structural-policy-merge.d.ts.map +1 -1
  119. package/dist/governance/structural-policy-merge.js +7 -0
  120. package/dist/governance/structural-policy-merge.js.map +1 -1
  121. package/dist/governance/verify-runtime-guard.d.ts +99 -0
  122. package/dist/governance/verify-runtime-guard.d.ts.map +1 -0
  123. package/dist/governance/verify-runtime-guard.js +129 -0
  124. package/dist/governance/verify-runtime-guard.js.map +1 -0
  125. package/dist/index.js +277 -77
  126. package/dist/index.js.map +1 -1
  127. package/dist/intent-engine/repo-classifier.d.ts +64 -0
  128. package/dist/intent-engine/repo-classifier.d.ts.map +1 -0
  129. package/dist/intent-engine/repo-classifier.js +178 -0
  130. package/dist/intent-engine/repo-classifier.js.map +1 -0
  131. package/dist/structural-rules/index.d.ts +4 -0
  132. package/dist/structural-rules/index.d.ts.map +1 -1
  133. package/dist/structural-rules/index.js +18 -1
  134. package/dist/structural-rules/index.js.map +1 -1
  135. package/dist/structural-rules/python/PY003-broad-except-clause.d.ts +21 -0
  136. package/dist/structural-rules/python/PY003-broad-except-clause.d.ts.map +1 -1
  137. package/dist/structural-rules/python/PY003-broad-except-clause.js +212 -21
  138. package/dist/structural-rules/python/PY003-broad-except-clause.js.map +1 -1
  139. package/dist/structural-rules/python/PY011-thread-lifecycle.d.ts +11 -0
  140. package/dist/structural-rules/python/PY011-thread-lifecycle.d.ts.map +1 -0
  141. package/dist/structural-rules/python/PY011-thread-lifecycle.js +97 -0
  142. package/dist/structural-rules/python/PY011-thread-lifecycle.js.map +1 -0
  143. package/dist/structural-rules/python/PY012-asyncio-run-misuse.d.ts +11 -0
  144. package/dist/structural-rules/python/PY012-asyncio-run-misuse.d.ts.map +1 -0
  145. package/dist/structural-rules/python/PY012-asyncio-run-misuse.js +83 -0
  146. package/dist/structural-rules/python/PY012-asyncio-run-misuse.js.map +1 -0
  147. package/dist/structural-rules/python/PY013-mutable-default-arg.d.ts +11 -0
  148. package/dist/structural-rules/python/PY013-mutable-default-arg.d.ts.map +1 -0
  149. package/dist/structural-rules/python/PY013-mutable-default-arg.js +73 -0
  150. package/dist/structural-rules/python/PY013-mutable-default-arg.js.map +1 -0
  151. package/dist/structural-rules/python/PY014-fixed-sleep-retry.d.ts +11 -0
  152. package/dist/structural-rules/python/PY014-fixed-sleep-retry.d.ts.map +1 -0
  153. package/dist/structural-rules/python/PY014-fixed-sleep-retry.js +115 -0
  154. package/dist/structural-rules/python/PY014-fixed-sleep-retry.js.map +1 -0
  155. package/dist/structural-rules/types.d.ts +12 -0
  156. package/dist/structural-rules/types.d.ts.map +1 -1
  157. package/dist/utils/active-engineering-context.d.ts +12 -0
  158. package/dist/utils/active-engineering-context.d.ts.map +1 -0
  159. package/dist/utils/active-engineering-context.js +67 -0
  160. package/dist/utils/active-engineering-context.js.map +1 -0
  161. package/dist/utils/artifact-io.d.ts +33 -0
  162. package/dist/utils/artifact-io.d.ts.map +1 -0
  163. package/dist/utils/artifact-io.js +183 -0
  164. package/dist/utils/artifact-io.js.map +1 -0
  165. package/dist/utils/change-contract.d.ts +6 -2
  166. package/dist/utils/change-contract.d.ts.map +1 -1
  167. package/dist/utils/change-contract.js +175 -0
  168. package/dist/utils/change-contract.js.map +1 -1
  169. package/dist/utils/context-pack.d.ts +12 -0
  170. package/dist/utils/context-pack.d.ts.map +1 -0
  171. package/dist/utils/context-pack.js +147 -0
  172. package/dist/utils/context-pack.js.map +1 -0
  173. package/dist/utils/control-plane.d.ts +18 -0
  174. package/dist/utils/control-plane.d.ts.map +1 -1
  175. package/dist/utils/control-plane.js +31 -4
  176. package/dist/utils/control-plane.js.map +1 -1
  177. package/dist/utils/drift-intelligence.d.ts +47 -0
  178. package/dist/utils/drift-intelligence.d.ts.map +1 -0
  179. package/dist/utils/drift-intelligence.js +2099 -0
  180. package/dist/utils/drift-intelligence.js.map +1 -0
  181. package/dist/utils/execution-actions.d.ts +22 -0
  182. package/dist/utils/execution-actions.d.ts.map +1 -0
  183. package/dist/utils/execution-actions.js +103 -0
  184. package/dist/utils/execution-actions.js.map +1 -0
  185. package/dist/utils/execution-bus.d.ts +1 -214
  186. package/dist/utils/execution-bus.d.ts.map +1 -1
  187. package/dist/utils/execution-bus.js +15 -1359
  188. package/dist/utils/execution-bus.js.map +1 -1
  189. package/dist/utils/git.d.ts +1 -0
  190. package/dist/utils/git.d.ts.map +1 -1
  191. package/dist/utils/git.js +13 -3
  192. package/dist/utils/git.js.map +1 -1
  193. package/dist/utils/governance-decisions.d.ts +75 -0
  194. package/dist/utils/governance-decisions.d.ts.map +1 -0
  195. package/dist/utils/governance-decisions.js +412 -0
  196. package/dist/utils/governance-decisions.js.map +1 -0
  197. package/dist/utils/governance-provenance.d.ts +1 -1
  198. package/dist/utils/governance-provenance.d.ts.map +1 -1
  199. package/dist/utils/governance-provenance.js +5 -7
  200. package/dist/utils/governance-provenance.js.map +1 -1
  201. package/dist/utils/governance.d.ts +108 -0
  202. package/dist/utils/governance.d.ts.map +1 -1
  203. package/dist/utils/governance.js +209 -7
  204. package/dist/utils/governance.js.map +1 -1
  205. package/dist/utils/intelligence-runtime-common.d.ts +30 -0
  206. package/dist/utils/intelligence-runtime-common.d.ts.map +1 -0
  207. package/dist/utils/intelligence-runtime-common.js +156 -0
  208. package/dist/utils/intelligence-runtime-common.js.map +1 -0
  209. package/dist/utils/intent-contract-diagnostics.d.ts +9 -0
  210. package/dist/utils/intent-contract-diagnostics.d.ts.map +1 -0
  211. package/dist/utils/intent-contract-diagnostics.js +322 -0
  212. package/dist/utils/intent-contract-diagnostics.js.map +1 -0
  213. package/dist/utils/intent-pack.d.ts +15 -0
  214. package/dist/utils/intent-pack.d.ts.map +1 -0
  215. package/dist/utils/intent-pack.js +196 -0
  216. package/dist/utils/intent-pack.js.map +1 -0
  217. package/dist/utils/plan-sync.d.ts +1 -0
  218. package/dist/utils/plan-sync.d.ts.map +1 -1
  219. package/dist/utils/plan-sync.js +23 -0
  220. package/dist/utils/plan-sync.js.map +1 -1
  221. package/dist/utils/policy-decision.d.ts +5 -0
  222. package/dist/utils/policy-decision.d.ts.map +1 -0
  223. package/dist/utils/policy-decision.js +17 -0
  224. package/dist/utils/policy-decision.js.map +1 -0
  225. package/dist/utils/replay-custody.d.ts +43 -0
  226. package/dist/utils/replay-custody.d.ts.map +1 -0
  227. package/dist/utils/replay-custody.js +168 -0
  228. package/dist/utils/replay-custody.js.map +1 -0
  229. package/dist/utils/replay-runtime.d.ts +13 -0
  230. package/dist/utils/replay-runtime.d.ts.map +1 -1
  231. package/dist/utils/replay-runtime.js +96 -9
  232. package/dist/utils/replay-runtime.js.map +1 -1
  233. package/dist/utils/repository-intelligence.d.ts +9 -0
  234. package/dist/utils/repository-intelligence.d.ts.map +1 -0
  235. package/dist/utils/repository-intelligence.js +372 -0
  236. package/dist/utils/repository-intelligence.js.map +1 -0
  237. package/dist/utils/runtime-events.d.ts.map +1 -1
  238. package/dist/utils/runtime-events.js +25 -6
  239. package/dist/utils/runtime-events.js.map +1 -1
  240. package/dist/utils/semantic-contract-intelligence.d.ts +20 -0
  241. package/dist/utils/semantic-contract-intelligence.d.ts.map +1 -0
  242. package/dist/utils/semantic-contract-intelligence.js +825 -0
  243. package/dist/utils/semantic-contract-intelligence.js.map +1 -0
  244. package/dist/utils/session-continuity.d.ts +56 -0
  245. package/dist/utils/session-continuity.d.ts.map +1 -0
  246. package/dist/utils/session-continuity.js +318 -0
  247. package/dist/utils/session-continuity.js.map +1 -0
  248. package/dist/utils/verification-evidence.d.ts.map +1 -1
  249. package/dist/utils/verification-evidence.js +4 -1
  250. package/dist/utils/verification-evidence.js.map +1 -1
  251. package/dist/utils/verify-runtime-stability.d.ts +142 -0
  252. package/dist/utils/verify-runtime-stability.d.ts.map +1 -0
  253. package/dist/utils/verify-runtime-stability.js +230 -0
  254. package/dist/utils/verify-runtime-stability.js.map +1 -0
  255. package/dist/utils/workspace-runtime.d.ts +1 -266
  256. package/dist/utils/workspace-runtime.d.ts.map +1 -1
  257. package/dist/utils/workspace-runtime.js +15 -1412
  258. package/dist/utils/workspace-runtime.js.map +1 -1
  259. package/package.json +11 -10
  260. package/LICENSE +0 -201
@@ -1,10 +1,194 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PY003BroadExceptClause = void 0;
4
+ exports.stripCommentsAndStrings = stripCommentsAndStrings;
5
+ exports.classifyExceptionFlow = classifyExceptionFlow;
4
6
  // Matches: except Exception: or except Exception as e:
5
7
  const BROAD_EXCEPT_RE = /^(\s*)except\s+Exception(\s+as\s+\w+)?\s*:/;
6
- // Logging/reporting call patterns
8
+ // Logging/reporting call patterns — only checked AFTER stripping comments
7
9
  const LOGGING_RE = /\b(?:log|logger|logging|error|warn|warning|report|track|capture|sentry|bugsnag|rollbar|print)\s*[\.(]/i;
10
+ /**
11
+ * Strip Python comment lines and string literal regions from source lines.
12
+ *
13
+ * Algorithm (deterministic state machine):
14
+ * - Track whether we are inside a triple-quoted string (""" or ''')
15
+ * - Track whether we are inside a single-quoted string (" or ')
16
+ * - If a line starts with # (after stripping indent) → replace with empty string
17
+ * - Content inside string regions is neutralized (replaced with spaces of same length)
18
+ *
19
+ * This is NOT a full Python tokenizer — it handles the common cases that enable
20
+ * bypass of governance checks while remaining O(n) and dependency-free.
21
+ */
22
+ function stripCommentsAndStrings(lines) {
23
+ const result = [];
24
+ let inTripleDouble = false; // inside """..."""
25
+ let inTripleSingle = false; // inside '''...'''
26
+ for (const line of lines) {
27
+ const trimmed = line.trimStart();
28
+ // If we are inside a triple-quoted block, look for the closing delimiter
29
+ if (inTripleDouble) {
30
+ const closeIdx = line.indexOf('"""');
31
+ if (closeIdx !== -1) {
32
+ inTripleDouble = false;
33
+ // Neutralize up to and including the closing delimiter
34
+ result.push(' '.repeat(line.length));
35
+ }
36
+ else {
37
+ result.push(' '.repeat(line.length));
38
+ }
39
+ continue;
40
+ }
41
+ if (inTripleSingle) {
42
+ const closeIdx = line.indexOf("'''");
43
+ if (closeIdx !== -1) {
44
+ inTripleSingle = false;
45
+ result.push(' '.repeat(line.length));
46
+ }
47
+ else {
48
+ result.push(' '.repeat(line.length));
49
+ }
50
+ continue;
51
+ }
52
+ // Full-line comment — blank it entirely
53
+ if (trimmed.startsWith('#')) {
54
+ result.push('');
55
+ continue;
56
+ }
57
+ // Scan for string/comment delimiters character by character
58
+ let out = '';
59
+ let i = 0;
60
+ let inSingleQ = false;
61
+ let inDoubleQ = false;
62
+ while (i < line.length) {
63
+ const ch = line[i];
64
+ const remaining = line.slice(i);
65
+ if (!inSingleQ && !inDoubleQ) {
66
+ // Check for triple-quote opening
67
+ if (remaining.startsWith('"""')) {
68
+ const rest = line.slice(i + 3);
69
+ const closeInSameLine = rest.indexOf('"""');
70
+ if (closeInSameLine !== -1) {
71
+ // Triple-quote opens and closes on same line — neutralize it
72
+ out += ' '.repeat(3 + closeInSameLine + 3);
73
+ i += 3 + closeInSameLine + 3;
74
+ continue;
75
+ }
76
+ else {
77
+ inTripleDouble = true;
78
+ // Neutralize rest of line
79
+ out += ' '.repeat(line.length - i);
80
+ break;
81
+ }
82
+ }
83
+ if (remaining.startsWith("'''")) {
84
+ const rest = line.slice(i + 3);
85
+ const closeInSameLine = rest.indexOf("'''");
86
+ if (closeInSameLine !== -1) {
87
+ out += ' '.repeat(3 + closeInSameLine + 3);
88
+ i += 3 + closeInSameLine + 3;
89
+ continue;
90
+ }
91
+ else {
92
+ inTripleSingle = true;
93
+ out += ' '.repeat(line.length - i);
94
+ break;
95
+ }
96
+ }
97
+ // Start of single-line string
98
+ if (ch === '"') {
99
+ inDoubleQ = true;
100
+ out += ' ';
101
+ i++;
102
+ continue;
103
+ }
104
+ if (ch === "'") {
105
+ inSingleQ = true;
106
+ out += ' ';
107
+ i++;
108
+ continue;
109
+ }
110
+ // Inline comment
111
+ if (ch === '#') {
112
+ // Rest of line is comment — stop
113
+ break;
114
+ }
115
+ out += ch;
116
+ }
117
+ else if (inDoubleQ) {
118
+ if (ch === '\\') {
119
+ out += ' ';
120
+ i += 2;
121
+ continue;
122
+ } // escape
123
+ if (ch === '"') {
124
+ inDoubleQ = false;
125
+ }
126
+ out += ' ';
127
+ }
128
+ else if (inSingleQ) {
129
+ if (ch === '\\') {
130
+ out += ' ';
131
+ i += 2;
132
+ continue;
133
+ }
134
+ if (ch === "'") {
135
+ inSingleQ = false;
136
+ }
137
+ out += ' ';
138
+ }
139
+ i++;
140
+ }
141
+ result.push(out);
142
+ }
143
+ return result;
144
+ }
145
+ /**
146
+ * Classify the exception-handling flow of an except block body.
147
+ *
148
+ * Input: stripped lines (comments and strings already neutralized).
149
+ * Returns the strictest applicable classification.
150
+ */
151
+ function classifyExceptionFlow(strippedBodyLines, exceptIndent) {
152
+ // Only consider lines that are within the except block's indentation scope
153
+ const blockLines = strippedBodyLines.filter(l => {
154
+ const t = l.trimStart();
155
+ if (t.length === 0)
156
+ return false;
157
+ const indent = l.length - t.length;
158
+ return indent > exceptIndent;
159
+ });
160
+ if (blockLines.length === 0)
161
+ return 'swallow';
162
+ const bodyText = blockLines.join('\n');
163
+ // Detect raise statements — only real Python raise keywords at statement level
164
+ // (not inside strings or comments, already stripped above)
165
+ const RAISE_STMT_RE = /^\s*raise\b/m;
166
+ const hasRaise = RAISE_STMT_RE.test(bodyText);
167
+ // Detect "raise X from e" or "raise NewException(" — transformed rethrow
168
+ const TRANSFORM_RAISE_RE = /^\s*raise\s+\w+\s*(?:\(|from)/m;
169
+ const hasTransformRaise = TRANSFORM_RAISE_RE.test(bodyText);
170
+ // Detect bare "raise" (re-raises current exception)
171
+ const BARE_RAISE_RE = /^\s*raise\s*$/m;
172
+ const hasBareRaise = BARE_RAISE_RE.test(bodyText);
173
+ // Detect conditional raise (raise inside if block at deeper indent)
174
+ const CONDITIONAL_RAISE_RE = /^\s+raise\b/m;
175
+ const hasConditionalRaise = !hasBareRaise && !hasTransformRaise && CONDITIONAL_RAISE_RE.test(bodyText);
176
+ const hasLogging = LOGGING_RE.test(bodyText);
177
+ if (hasBareRaise) {
178
+ // Clean re-raise — not a violation
179
+ return 'partial-rethrow'; // partial because could be log+reraise
180
+ }
181
+ if (hasTransformRaise)
182
+ return 'transformed-rethrow';
183
+ if (hasConditionalRaise)
184
+ return 'partial-rethrow';
185
+ if (!hasRaise && hasLogging)
186
+ return 'log-only';
187
+ if (!hasRaise && !hasLogging)
188
+ return 'swallow';
189
+ // raise present but not bare/transform/conditional — treat as partial
190
+ return 'partial-rethrow';
191
+ }
8
192
  class PY003BroadExceptClause {
9
193
  id = 'PY003';
10
194
  name = 'Broad except clause swallowing errors';
@@ -22,40 +206,46 @@ class PY003BroadExceptClause {
22
206
  if (!match)
23
207
  continue;
24
208
  const exceptIndent = match[1].length;
25
- // Collect the except block body (lines more indented than the except statement)
26
- const bodyLines = [];
209
+ // Collect the raw except block body lines (indented deeper than the except)
210
+ const rawBodyLines = [];
27
211
  let j = i + 1;
28
212
  while (j < lines.length) {
29
213
  const bodyLine = lines[j];
30
214
  const bodyTrimmed = bodyLine.trimStart();
31
215
  // Empty line — continue collecting
32
216
  if (bodyTrimmed.length === 0) {
217
+ rawBodyLines.push(bodyLine);
33
218
  j++;
34
219
  continue;
35
220
  }
36
- const bodyIndent = bodyLine.length - bodyLine.trimStart().length;
221
+ const bodyIndent = bodyLine.length - bodyTrimmed.length;
37
222
  // If indent is less than or equal to the except indent, block ended
38
223
  if (bodyIndent <= exceptIndent)
39
224
  break;
40
- bodyLines.push(bodyLine);
225
+ rawBodyLines.push(bodyLine);
41
226
  j++;
42
227
  }
43
- if (bodyLines.length === 0)
228
+ if (rawBodyLines.length === 0)
44
229
  continue;
45
- const bodyText = bodyLines.join('\n');
46
- // Check for re-raise
47
- const hasReraise = /^\s*raise\b/.test(bodyText) || /\braise\b/.test(bodyText);
48
- if (hasReraise)
230
+ // ── AST-level analysis: strip comments and strings before checking ──
231
+ const strippedLines = stripCommentsAndStrings(rawBodyLines);
232
+ const flowClass = classifyExceptionFlow(strippedLines, exceptIndent);
233
+ // Only violations: swallow and log-only (log without re-raise)
234
+ // transformed-rethrow and partial-rethrow are handled by the engineer
235
+ if (flowClass === 'partial-rethrow' || flowClass === 'transformed-rethrow') {
49
236
  continue;
50
- // Check for logging
51
- if (LOGGING_RE.test(bodyText))
237
+ }
238
+ // Bare re-raise check: if bare raise exists in RAW lines (before stripping)
239
+ // this is not a swallow — the stripCommentsAndStrings already handles
240
+ // comment stripping, so we just trust classifyExceptionFlow here.
241
+ if (flowClass !== 'swallow' && flowClass !== 'log-only')
52
242
  continue;
53
- // Skip if body only contains pass
54
- const nonEmpty = bodyLines
243
+ const nonEmptyNonPass = strippedLines
55
244
  .map(l => l.trim())
56
- .filter(l => l.length > 0 && l !== 'pass' && !l.startsWith('#'));
57
- // If the body is just `pass` or empty meaningful statements, it's swallowing
58
- // If it has actual work (return something, set variable), still flag it
245
+ .filter(l => l.length > 0 && l !== 'pass');
246
+ const confidence = flowClass === 'swallow'
247
+ ? (nonEmptyNonPass.length === 0 ? 0.97 : 0.88)
248
+ : 0.82; // log-only
59
249
  const evidence = line.slice(0, 120);
60
250
  violations.push({
61
251
  ruleId: this.id,
@@ -65,13 +255,14 @@ class PY003BroadExceptClause {
65
255
  filePath,
66
256
  line: i + 1,
67
257
  column: exceptIndent + 1,
68
- evidence,
69
- operationalRisk: 'Catches ALL exceptions (including SystemExit, KeyboardInterrupt, MemoryError) without ' +
70
- 'logging or re-raising. Silent failures make debugging impossible and hide operational issues.',
258
+ evidence: `${evidence} [flow:${flowClass}]`,
259
+ operationalRisk: `except Exception: block classified as '${flowClass}'. ` +
260
+ 'Catches ALL exceptions (including SystemExit, KeyboardInterrupt, MemoryError) without ' +
261
+ 're-raising. Silent failures make debugging impossible and hide operational issues.',
71
262
  remediation: 'Either re-raise after handling: `except Exception as e: logger.error(e); raise` ' +
72
263
  'or narrow the exception type. Avoid bare `except Exception` without at minimum logging.',
73
264
  determinism: 'deterministic-structural',
74
- confidence: nonEmpty.length === 0 ? 0.95 : 0.85,
265
+ confidence,
75
266
  language: 'python',
76
267
  });
77
268
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PY003-broad-except-clause.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY003-broad-except-clause.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PY003-broad-except-clause.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY003-broad-except-clause.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
2
+ export declare class PY011ThreadLifecycle 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=PY011-thread-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY011-thread-lifecycle.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY011-thread-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAmB7E,qBAAa,oBAAqB,YAAW,cAAc;IACzD,EAAE,SAAW;IACb,IAAI,SAAoE;IACxE,SAAS,SAAW;IACpB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SAEoE;IAE/E,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAuEnE"}
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PY011ThreadLifecycle = void 0;
4
+ /**
5
+ * PY011 — Thread Lifecycle Governance
6
+ *
7
+ * Detects threading.Thread() usage without daemon=True and/or without
8
+ * a stored reference for later join/stop. In long-running services (e.g.,
9
+ * Airflow scheduler, API workers), non-daemon threads prevent graceful
10
+ * shutdown under SIGTERM. Detached thread references cannot be joined,
11
+ * making stop() semantically unreliable.
12
+ *
13
+ * BLOCKING: non-daemon threads in service code cause zombie accumulation
14
+ * across pod restarts in Kubernetes environments.
15
+ */
16
+ const THREAD_CREATE_RE = /\bthreading\.Thread\s*\(/;
17
+ const DAEMON_TRUE_RE = /\bdaemon\s*=\s*True\b/;
18
+ const THREAD_INLINE_START_RE = /\bthreading\.Thread\s*\(.*\)\s*\.start\s*\(\)/;
19
+ class PY011ThreadLifecycle {
20
+ id = 'PY011';
21
+ name = 'Thread created without daemon=True or without stored reference';
22
+ policyRef = 'PY011';
23
+ severity = 'BLOCKING';
24
+ languages = ['python'];
25
+ description = 'threading.Thread() without daemon=True prevents graceful shutdown under SIGTERM. ' +
26
+ 'Threads created without storing the reference cannot be joined or stopped.';
27
+ check(filePath, sourceText) {
28
+ try {
29
+ const violations = [];
30
+ const lines = sourceText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
31
+ for (let i = 0; i < lines.length; i++) {
32
+ const line = lines[i];
33
+ if (!THREAD_CREATE_RE.test(line))
34
+ continue;
35
+ // Inline .start() — reference immediately lost
36
+ if (THREAD_INLINE_START_RE.test(line)) {
37
+ violations.push({
38
+ ruleId: this.id,
39
+ ruleName: this.name,
40
+ policyRef: this.policyRef,
41
+ severity: this.severity,
42
+ filePath,
43
+ line: i + 1,
44
+ column: line.indexOf('threading.Thread') + 1,
45
+ evidence: line.trim(),
46
+ operationalRisk: 'Thread started inline without storing reference: cannot be joined or stopped. ' +
47
+ 'Under K8s SIGTERM, this thread becomes a zombie blocking clean shutdown.',
48
+ remediation: 'Store the thread reference: self._thread = threading.Thread(..., daemon=True)\n' +
49
+ 'Then call self._thread.join(timeout=5) in your stop/cleanup method.',
50
+ determinism: 'deterministic-structural',
51
+ confidence: 0.88,
52
+ language: 'python',
53
+ });
54
+ continue;
55
+ }
56
+ // Check if daemon=True appears within the next 8 lines
57
+ const searchAhead = Math.min(i + 8, lines.length);
58
+ let hasDaemon = DAEMON_TRUE_RE.test(line);
59
+ if (!hasDaemon) {
60
+ for (let j = i + 1; j < searchAhead; j++) {
61
+ if (DAEMON_TRUE_RE.test(lines[j])) {
62
+ hasDaemon = true;
63
+ break;
64
+ }
65
+ if (/\)\s*$/.test(lines[j].trimEnd()))
66
+ break;
67
+ }
68
+ }
69
+ if (!hasDaemon) {
70
+ violations.push({
71
+ ruleId: this.id,
72
+ ruleName: this.name,
73
+ policyRef: this.policyRef,
74
+ severity: this.severity,
75
+ filePath,
76
+ line: i + 1,
77
+ column: line.indexOf('threading.Thread') + 1,
78
+ evidence: line.trim(),
79
+ operationalRisk: 'Non-daemon thread blocks process exit under SIGTERM. ' +
80
+ 'In Kubernetes, pods will hang at termination until the thread finishes naturally.',
81
+ remediation: 'Add daemon=True: threading.Thread(target=..., daemon=True)\n' +
82
+ 'Store the reference and call .join(timeout=5) in stop/cleanup.',
83
+ determinism: 'deterministic-structural',
84
+ confidence: 0.88,
85
+ language: 'python',
86
+ });
87
+ }
88
+ }
89
+ return violations;
90
+ }
91
+ catch {
92
+ return [];
93
+ }
94
+ }
95
+ }
96
+ exports.PY011ThreadLifecycle = PY011ThreadLifecycle;
97
+ //# sourceMappingURL=PY011-thread-lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY011-thread-lifecycle.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY011-thread-lifecycle.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;GAWG;AAEH,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AACpD,MAAM,cAAc,GAAG,uBAAuB,CAAC;AAC/C,MAAM,sBAAsB,GAAG,+CAA+C,CAAC;AAE/E,MAAa,oBAAoB;IAC/B,EAAE,GAAG,OAAO,CAAC;IACb,IAAI,GAAG,gEAAgE,CAAC;IACxE,SAAS,GAAG,OAAO,CAAC;IACpB,QAAQ,GAAG,UAAmB,CAAC;IAC/B,SAAS,GAAmB,CAAC,QAAQ,CAAC,CAAC;IACvC,WAAW,GACT,mFAAmF;QACnF,4EAA4E,CAAC;IAE/E,KAAK,CAAC,QAAgB,EAAE,UAAkB;QACxC,IAAI,CAAC;YACH,MAAM,UAAU,GAA0B,EAAE,CAAC;YAC7C,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAEjF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACtB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAE3C,+CAA+C;gBAC/C,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACtC,UAAU,CAAC,IAAI,CAAC;wBACd,MAAM,EAAE,IAAI,CAAC,EAAE;wBACf,QAAQ,EAAE,IAAI,CAAC,IAAI;wBACnB,SAAS,EAAE,IAAI,CAAC,SAAS;wBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,QAAQ;wBACR,IAAI,EAAE,CAAC,GAAG,CAAC;wBACX,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,GAAG,CAAC;wBAC5C,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE;wBACrB,eAAe,EACb,gFAAgF;4BAChF,0EAA0E;wBAC5E,WAAW,EACT,iFAAiF;4BACjF,qEAAqE;wBACvE,WAAW,EAAE,0BAAmC;wBAChD,UAAU,EAAE,IAAI;wBAChB,QAAQ,EAAE,QAAQ;qBACnB,CAAC,CAAC;oBACH,SAAS;gBACX,CAAC;gBAED,uDAAuD;gBACvD,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;gBAClD,IAAI,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC1C,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;wBACzC,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;4BAAC,SAAS,GAAG,IAAI,CAAC;4BAAC,MAAM;wBAAC,CAAC;wBAC/D,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;4BAAE,MAAM;oBAC/C,CAAC;gBACH,CAAC;gBAED,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,UAAU,CAAC,IAAI,CAAC;wBACd,MAAM,EAAE,IAAI,CAAC,EAAE;wBACf,QAAQ,EAAE,IAAI,CAAC,IAAI;wBACnB,SAAS,EAAE,IAAI,CAAC,SAAS;wBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,QAAQ;wBACR,IAAI,EAAE,CAAC,GAAG,CAAC;wBACX,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,GAAG,CAAC;wBAC5C,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE;wBACrB,eAAe,EACb,uDAAuD;4BACvD,mFAAmF;wBACrF,WAAW,EACT,8DAA8D;4BAC9D,gEAAgE;wBAClE,WAAW,EAAE,0BAAmC;wBAChD,UAAU,EAAE,IAAI;wBAChB,QAAQ,EAAE,QAAQ;qBACnB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAED,OAAO,UAAU,CAAC;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF;AAjFD,oDAiFC"}
@@ -0,0 +1,11 @@
1
+ import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
2
+ export declare class PY012AsyncioRunMisuse 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=PY012-asyncio-run-misuse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY012-asyncio-run-misuse.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY012-asyncio-run-misuse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAmB7E,qBAAa,qBAAsB,YAAW,cAAc;IAC1D,EAAE,SAAW;IACb,IAAI,SAA2C;IAC/C,SAAS,SAAW;IACpB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SAEiD;IAE5D,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CA+DnE"}
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PY012AsyncioRunMisuse = void 0;
4
+ /**
5
+ * PY012 — asyncio.run() Misuse Inside Async Context
6
+ *
7
+ * asyncio.run() creates a new event loop and runs until the coroutine completes.
8
+ * Calling it inside an already-running event loop raises RuntimeError.
9
+ *
10
+ * BLOCKING: causes RuntimeError at startup in FastAPI, Airflow, Jupyter, and
11
+ * any other async runtime environment.
12
+ */
13
+ const ASYNC_DEF_RE = /^(\s*)async\s+def\s+\w+\s*\(/;
14
+ const ASYNCIO_RUN_RE = /\basyncio\.run\s*\(/;
15
+ function getIndent(line) {
16
+ return line.length - line.trimStart().length;
17
+ }
18
+ class PY012AsyncioRunMisuse {
19
+ id = 'PY012';
20
+ name = 'asyncio.run() called inside async def';
21
+ policyRef = 'PY012';
22
+ severity = 'BLOCKING';
23
+ languages = ['python'];
24
+ description = 'asyncio.run() inside an async def raises RuntimeError: "This event loop is already running." ' +
25
+ 'Use await coroutine() or asyncio.create_task() instead.';
26
+ check(filePath, sourceText) {
27
+ try {
28
+ const violations = [];
29
+ const lines = sourceText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
30
+ const asyncScopes = [];
31
+ for (let i = 0; i < lines.length; i++) {
32
+ const line = lines[i];
33
+ const trimmed = line.trimStart();
34
+ // Track async def entries
35
+ const asyncMatch = ASYNC_DEF_RE.exec(line);
36
+ if (asyncMatch) {
37
+ asyncScopes.push({ startLine: i, indent: asyncMatch[1].length });
38
+ }
39
+ // Pop scopes that have ended
40
+ if (asyncScopes.length > 0 && trimmed.length > 0 && !trimmed.startsWith('#')) {
41
+ const currentIndent = getIndent(line);
42
+ while (asyncScopes.length > 0 &&
43
+ currentIndent <= asyncScopes[asyncScopes.length - 1].indent &&
44
+ i > asyncScopes[asyncScopes.length - 1].startLine) {
45
+ if (/^(def |async def |class |@|if |for |while |with |try:|except|finally:|else:|elif )/.test(trimmed)) {
46
+ asyncScopes.pop();
47
+ }
48
+ else {
49
+ break;
50
+ }
51
+ }
52
+ }
53
+ // Detect asyncio.run() inside an async scope
54
+ if (asyncScopes.length > 0 && ASYNCIO_RUN_RE.test(line)) {
55
+ const scope = asyncScopes[asyncScopes.length - 1];
56
+ violations.push({
57
+ ruleId: this.id,
58
+ ruleName: this.name,
59
+ policyRef: this.policyRef,
60
+ severity: this.severity,
61
+ filePath,
62
+ line: i + 1,
63
+ column: line.indexOf('asyncio.run') + 1,
64
+ evidence: line.trim(),
65
+ operationalRisk: `asyncio.run() called inside async def (started at line ${scope.startLine + 1}). ` +
66
+ 'Raises RuntimeError: "This event loop is already running" in FastAPI, Airflow, and Jupyter.',
67
+ remediation: 'Replace asyncio.run(coro()) with: await coro()\n' +
68
+ 'Or if called from module-level code, restructure to avoid calling from inside an async function.',
69
+ determinism: 'deterministic-structural',
70
+ confidence: 0.85,
71
+ language: 'python',
72
+ });
73
+ }
74
+ }
75
+ return violations;
76
+ }
77
+ catch {
78
+ return [];
79
+ }
80
+ }
81
+ }
82
+ exports.PY012AsyncioRunMisuse = PY012AsyncioRunMisuse;
83
+ //# sourceMappingURL=PY012-asyncio-run-misuse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY012-asyncio-run-misuse.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY012-asyncio-run-misuse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
2
+ export declare class PY013MutableDefaultArg 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=PY013-mutable-default-arg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY013-mutable-default-arg.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY013-mutable-default-arg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAe7E,qBAAa,sBAAuB,YAAW,cAAc;IAC3D,EAAE,SAAW;IACb,IAAI,SAAqD;IACzD,SAAS,SAAW;IACpB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SAEsD;IAEjE,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAqDnE"}
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PY013MutableDefaultArg = void 0;
4
+ /**
5
+ * PY013 — Mutable Default Argument
6
+ *
7
+ * Python function default arguments are evaluated ONCE at function definition
8
+ * time. Using mutable objects (dict, list, set) as defaults creates a shared
9
+ * object across all calls. Classic Python gotcha reliably reproduced by LLMs.
10
+ *
11
+ * ADVISORY: correctness bug but not immediately fatal.
12
+ */
13
+ const PARAM_MUTABLE_RE = /(?::\s*\w+(?:\[.*?\])?\s*)?=\s*(\{\}|\[\]|set\s*\(\s*\))/;
14
+ const DEF_RE = /^(\s*)(?:async\s+)?def\s+\w+\s*\(/;
15
+ class PY013MutableDefaultArg {
16
+ id = 'PY013';
17
+ name = 'Mutable default argument in function definition';
18
+ policyRef = 'PY013';
19
+ severity = 'ADVISORY';
20
+ languages = ['python'];
21
+ description = 'Mutable default arguments ({}, [], set()) are shared across all calls. ' +
22
+ 'Use None as default and initialize inside the function body.';
23
+ check(filePath, sourceText) {
24
+ try {
25
+ const violations = [];
26
+ const lines = sourceText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
27
+ for (let i = 0; i < lines.length; i++) {
28
+ const line = lines[i];
29
+ if (!DEF_RE.test(line))
30
+ continue;
31
+ // Collect full signature (may span multiple lines)
32
+ let sig = line;
33
+ if (!sig.includes(')')) {
34
+ for (let j = i + 1; j < Math.min(i + 10, lines.length); j++) {
35
+ sig += ' ' + lines[j];
36
+ if (lines[j].includes(')'))
37
+ break;
38
+ }
39
+ }
40
+ const mutableMatch = PARAM_MUTABLE_RE.exec(sig);
41
+ if (mutableMatch) {
42
+ const defaultVal = mutableMatch[1];
43
+ const label = defaultVal === '{}' ? 'dict {}' :
44
+ defaultVal === '[]' ? 'list []' :
45
+ 'set()';
46
+ violations.push({
47
+ ruleId: this.id,
48
+ ruleName: this.name,
49
+ policyRef: this.policyRef,
50
+ severity: this.severity,
51
+ filePath,
52
+ line: i + 1,
53
+ column: line.indexOf('def') + 1,
54
+ evidence: line.trim(),
55
+ operationalRisk: `Mutable default argument (${label}) is shared across all calls to this function. ` +
56
+ 'Mutations in one call persist to subsequent calls, causing unpredictable behavior.',
57
+ remediation: `Use None as default: def f(x=None):\\n x = x or ${defaultVal === '[]' ? '[]' : '{}'}\n` +
58
+ 'This ensures each call gets a fresh mutable object.',
59
+ determinism: 'deterministic-structural',
60
+ confidence: 0.92,
61
+ language: 'python',
62
+ });
63
+ }
64
+ }
65
+ return violations;
66
+ }
67
+ catch {
68
+ return [];
69
+ }
70
+ }
71
+ }
72
+ exports.PY013MutableDefaultArg = PY013MutableDefaultArg;
73
+ //# sourceMappingURL=PY013-mutable-default-arg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PY013-mutable-default-arg.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY013-mutable-default-arg.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;GAQG;AAEH,MAAM,gBAAgB,GAAG,0DAA0D,CAAC;AACpF,MAAM,MAAM,GAAG,mCAAmC,CAAC;AAEnD,MAAa,sBAAsB;IACjC,EAAE,GAAG,OAAO,CAAC;IACb,IAAI,GAAG,iDAAiD,CAAC;IACzD,SAAS,GAAG,OAAO,CAAC;IACpB,QAAQ,GAAG,UAAmB,CAAC;IAC/B,SAAS,GAAmB,CAAC,QAAQ,CAAC,CAAC;IACvC,WAAW,GACT,yEAAyE;QACzE,8DAA8D,CAAC;IAEjE,KAAK,CAAC,QAAgB,EAAE,UAAkB;QACxC,IAAI,CAAC;YACH,MAAM,UAAU,GAA0B,EAAE,CAAC;YAC7C,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAEjF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACtB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAEjC,mDAAmD;gBACnD,IAAI,GAAG,GAAG,IAAI,CAAC;gBACf,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;oBACvB,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;wBAC5D,GAAG,IAAI,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;wBACtB,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;4BAAE,MAAM;oBACpC,CAAC;gBACH,CAAC;gBAED,MAAM,YAAY,GAAG,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAChD,IAAI,YAAY,EAAE,CAAC;oBACjB,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;oBACnC,MAAM,KAAK,GACT,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;wBACjC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;4BACjC,OAAO,CAAC;oBAEV,UAAU,CAAC,IAAI,CAAC;wBACd,MAAM,EAAE,IAAI,CAAC,EAAE;wBACf,QAAQ,EAAE,IAAI,CAAC,IAAI;wBACnB,SAAS,EAAE,IAAI,CAAC,SAAS;wBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,QAAQ;wBACR,IAAI,EAAE,CAAC,GAAG,CAAC;wBACX,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;wBAC/B,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE;wBACrB,eAAe,EACb,6BAA6B,KAAK,iDAAiD;4BACnF,oFAAoF;wBACtF,WAAW,EACT,sDAAsD,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI;4BAC3F,qDAAqD;wBACvD,WAAW,EAAE,0BAAmC;wBAChD,UAAU,EAAE,IAAI;wBAChB,QAAQ,EAAE,QAAQ;qBACnB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAED,OAAO,UAAU,CAAC;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF;AA/DD,wDA+DC"}
@@ -0,0 +1,11 @@
1
+ import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
2
+ export declare class PY014FixedSleepRetry 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=PY014-fixed-sleep-retry.d.ts.map