@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.
- package/dist/commands/bootstrap-policy.d.ts +29 -0
- package/dist/commands/bootstrap-policy.d.ts.map +1 -0
- package/dist/commands/bootstrap-policy.js +334 -0
- package/dist/commands/bootstrap-policy.js.map +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +82 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/governance.d.ts +3 -0
- package/dist/commands/governance.d.ts.map +1 -0
- package/dist/commands/governance.js +390 -0
- package/dist/commands/governance.js.map +1 -0
- package/dist/commands/quickstart.d.ts +21 -0
- package/dist/commands/quickstart.d.ts.map +1 -0
- package/dist/commands/quickstart.js +178 -0
- package/dist/commands/quickstart.js.map +1 -0
- package/dist/commands/remediate-export.d.ts +36 -0
- package/dist/commands/remediate-export.d.ts.map +1 -0
- package/dist/commands/remediate-export.js +1072 -0
- package/dist/commands/remediate-export.js.map +1 -0
- package/dist/commands/replay.d.ts.map +1 -1
- package/dist/commands/replay.js +14 -0
- package/dist/commands/replay.js.map +1 -1
- package/dist/commands/session.d.ts +7 -0
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +156 -0
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/start-intent.d.ts.map +1 -1
- package/dist/commands/start-intent.js +61 -11
- package/dist/commands/start-intent.js.map +1 -1
- package/dist/commands/verify-guidance.d.ts +5 -0
- package/dist/commands/verify-guidance.d.ts.map +1 -0
- package/dist/commands/verify-guidance.js +49 -0
- package/dist/commands/verify-guidance.js.map +1 -0
- package/dist/commands/verify-output.d.ts +37 -0
- package/dist/commands/verify-output.d.ts.map +1 -0
- package/dist/commands/verify-output.js +572 -0
- package/dist/commands/verify-output.js.map +1 -0
- package/dist/commands/verify-render.d.ts +41 -0
- package/dist/commands/verify-render.d.ts.map +1 -0
- package/dist/commands/verify-render.js +457 -0
- package/dist/commands/verify-render.js.map +1 -0
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +384 -1091
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/workspace.d.ts.map +1 -1
- package/dist/commands/workspace.js +3 -14
- package/dist/commands/workspace.js.map +1 -1
- package/dist/context-engine/graph.d.ts.map +1 -1
- package/dist/context-engine/graph.js +69 -7
- package/dist/context-engine/graph.js.map +1 -1
- package/dist/context-engine/scanner.d.ts.map +1 -1
- package/dist/context-engine/scanner.js +9 -2
- package/dist/context-engine/scanner.js.map +1 -1
- package/dist/daemon/compatibility/execution.d.ts +42 -0
- package/dist/daemon/compatibility/execution.d.ts.map +1 -0
- package/dist/daemon/compatibility/execution.js +183 -0
- package/dist/daemon/compatibility/execution.js.map +1 -0
- package/dist/daemon/compatibility/mutation.d.ts +24 -0
- package/dist/daemon/compatibility/mutation.d.ts.map +1 -0
- package/dist/daemon/compatibility/mutation.js +724 -0
- package/dist/daemon/compatibility/mutation.js.map +1 -0
- package/dist/daemon/routes.d.ts +19 -0
- package/dist/daemon/routes.d.ts.map +1 -0
- package/dist/daemon/routes.js +123 -0
- package/dist/daemon/routes.js.map +1 -0
- package/dist/daemon/runtime/execution-bus.d.ts +217 -0
- package/dist/daemon/runtime/execution-bus.d.ts.map +1 -0
- package/dist/daemon/runtime/execution-bus.js +1420 -0
- package/dist/daemon/runtime/execution-bus.js.map +1 -0
- package/dist/daemon/runtime/workspace-runtime.d.ts +280 -0
- package/dist/daemon/runtime/workspace-runtime.d.ts.map +1 -0
- package/dist/daemon/runtime/workspace-runtime.js +1473 -0
- package/dist/daemon/runtime/workspace-runtime.js.map +1 -0
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +171 -874
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/shaping.d.ts +11 -0
- package/dist/daemon/shaping.d.ts.map +1 -0
- package/dist/daemon/shaping.js +240 -0
- package/dist/daemon/shaping.js.map +1 -0
- package/dist/governance/canonical-invariants.d.ts +88 -0
- package/dist/governance/canonical-invariants.d.ts.map +1 -0
- package/dist/governance/canonical-invariants.js +197 -0
- package/dist/governance/canonical-invariants.js.map +1 -0
- package/dist/governance/canonical-ordering.d.ts +76 -0
- package/dist/governance/canonical-ordering.d.ts.map +1 -0
- package/dist/governance/canonical-ordering.js +189 -0
- package/dist/governance/canonical-ordering.js.map +1 -0
- package/dist/governance/canonical-pipeline.d.ts +9 -1
- package/dist/governance/canonical-pipeline.d.ts.map +1 -1
- package/dist/governance/canonical-pipeline.js +367 -24
- package/dist/governance/canonical-pipeline.js.map +1 -1
- package/dist/governance/diff-line-provenance.d.ts +59 -0
- package/dist/governance/diff-line-provenance.d.ts.map +1 -0
- package/dist/governance/diff-line-provenance.js +118 -0
- package/dist/governance/diff-line-provenance.js.map +1 -0
- package/dist/governance/pilot-readiness.d.ts +34 -0
- package/dist/governance/pilot-readiness.d.ts.map +1 -0
- package/dist/governance/pilot-readiness.js +226 -0
- package/dist/governance/pilot-readiness.js.map +1 -0
- package/dist/governance/policy-parity-validator.d.ts +62 -0
- package/dist/governance/policy-parity-validator.d.ts.map +1 -0
- package/dist/governance/policy-parity-validator.js +137 -0
- package/dist/governance/policy-parity-validator.js.map +1 -0
- package/dist/governance/remediation-boundary.d.ts +55 -0
- package/dist/governance/remediation-boundary.d.ts.map +1 -0
- package/dist/governance/remediation-boundary.js +120 -0
- package/dist/governance/remediation-boundary.js.map +1 -0
- package/dist/governance/structural-cache.d.ts +103 -0
- package/dist/governance/structural-cache.d.ts.map +1 -0
- package/dist/governance/structural-cache.js +235 -0
- package/dist/governance/structural-cache.js.map +1 -0
- package/dist/governance/structural-on-diff.d.ts +22 -2
- package/dist/governance/structural-on-diff.d.ts.map +1 -1
- package/dist/governance/structural-on-diff.js +36 -4
- package/dist/governance/structural-on-diff.js.map +1 -1
- package/dist/governance/structural-policy-merge.d.ts +8 -0
- package/dist/governance/structural-policy-merge.d.ts.map +1 -1
- package/dist/governance/structural-policy-merge.js +7 -0
- package/dist/governance/structural-policy-merge.js.map +1 -1
- package/dist/governance/verify-runtime-guard.d.ts +99 -0
- package/dist/governance/verify-runtime-guard.d.ts.map +1 -0
- package/dist/governance/verify-runtime-guard.js +129 -0
- package/dist/governance/verify-runtime-guard.js.map +1 -0
- package/dist/index.js +277 -77
- package/dist/index.js.map +1 -1
- package/dist/intent-engine/repo-classifier.d.ts +64 -0
- package/dist/intent-engine/repo-classifier.d.ts.map +1 -0
- package/dist/intent-engine/repo-classifier.js +178 -0
- package/dist/intent-engine/repo-classifier.js.map +1 -0
- package/dist/structural-rules/index.d.ts +4 -0
- package/dist/structural-rules/index.d.ts.map +1 -1
- package/dist/structural-rules/index.js +18 -1
- package/dist/structural-rules/index.js.map +1 -1
- package/dist/structural-rules/python/PY003-broad-except-clause.d.ts +21 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.d.ts.map +1 -1
- package/dist/structural-rules/python/PY003-broad-except-clause.js +212 -21
- package/dist/structural-rules/python/PY003-broad-except-clause.js.map +1 -1
- package/dist/structural-rules/python/PY011-thread-lifecycle.d.ts +11 -0
- package/dist/structural-rules/python/PY011-thread-lifecycle.d.ts.map +1 -0
- package/dist/structural-rules/python/PY011-thread-lifecycle.js +97 -0
- package/dist/structural-rules/python/PY011-thread-lifecycle.js.map +1 -0
- package/dist/structural-rules/python/PY012-asyncio-run-misuse.d.ts +11 -0
- package/dist/structural-rules/python/PY012-asyncio-run-misuse.d.ts.map +1 -0
- package/dist/structural-rules/python/PY012-asyncio-run-misuse.js +83 -0
- package/dist/structural-rules/python/PY012-asyncio-run-misuse.js.map +1 -0
- package/dist/structural-rules/python/PY013-mutable-default-arg.d.ts +11 -0
- package/dist/structural-rules/python/PY013-mutable-default-arg.d.ts.map +1 -0
- package/dist/structural-rules/python/PY013-mutable-default-arg.js +73 -0
- package/dist/structural-rules/python/PY013-mutable-default-arg.js.map +1 -0
- package/dist/structural-rules/python/PY014-fixed-sleep-retry.d.ts +11 -0
- package/dist/structural-rules/python/PY014-fixed-sleep-retry.d.ts.map +1 -0
- package/dist/structural-rules/python/PY014-fixed-sleep-retry.js +115 -0
- package/dist/structural-rules/python/PY014-fixed-sleep-retry.js.map +1 -0
- package/dist/structural-rules/types.d.ts +12 -0
- package/dist/structural-rules/types.d.ts.map +1 -1
- package/dist/utils/active-engineering-context.d.ts +12 -0
- package/dist/utils/active-engineering-context.d.ts.map +1 -0
- package/dist/utils/active-engineering-context.js +67 -0
- package/dist/utils/active-engineering-context.js.map +1 -0
- package/dist/utils/artifact-io.d.ts +33 -0
- package/dist/utils/artifact-io.d.ts.map +1 -0
- package/dist/utils/artifact-io.js +183 -0
- package/dist/utils/artifact-io.js.map +1 -0
- package/dist/utils/change-contract.d.ts +6 -2
- package/dist/utils/change-contract.d.ts.map +1 -1
- package/dist/utils/change-contract.js +175 -0
- package/dist/utils/change-contract.js.map +1 -1
- package/dist/utils/context-pack.d.ts +12 -0
- package/dist/utils/context-pack.d.ts.map +1 -0
- package/dist/utils/context-pack.js +147 -0
- package/dist/utils/context-pack.js.map +1 -0
- package/dist/utils/control-plane.d.ts +18 -0
- package/dist/utils/control-plane.d.ts.map +1 -1
- package/dist/utils/control-plane.js +31 -4
- package/dist/utils/control-plane.js.map +1 -1
- package/dist/utils/drift-intelligence.d.ts +47 -0
- package/dist/utils/drift-intelligence.d.ts.map +1 -0
- package/dist/utils/drift-intelligence.js +2099 -0
- package/dist/utils/drift-intelligence.js.map +1 -0
- package/dist/utils/execution-actions.d.ts +22 -0
- package/dist/utils/execution-actions.d.ts.map +1 -0
- package/dist/utils/execution-actions.js +103 -0
- package/dist/utils/execution-actions.js.map +1 -0
- package/dist/utils/execution-bus.d.ts +1 -214
- package/dist/utils/execution-bus.d.ts.map +1 -1
- package/dist/utils/execution-bus.js +15 -1359
- package/dist/utils/execution-bus.js.map +1 -1
- package/dist/utils/git.d.ts +1 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +13 -3
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/governance-decisions.d.ts +75 -0
- package/dist/utils/governance-decisions.d.ts.map +1 -0
- package/dist/utils/governance-decisions.js +412 -0
- package/dist/utils/governance-decisions.js.map +1 -0
- package/dist/utils/governance-provenance.d.ts +1 -1
- package/dist/utils/governance-provenance.d.ts.map +1 -1
- package/dist/utils/governance-provenance.js +5 -7
- package/dist/utils/governance-provenance.js.map +1 -1
- package/dist/utils/governance.d.ts +108 -0
- package/dist/utils/governance.d.ts.map +1 -1
- package/dist/utils/governance.js +209 -7
- package/dist/utils/governance.js.map +1 -1
- package/dist/utils/intelligence-runtime-common.d.ts +30 -0
- package/dist/utils/intelligence-runtime-common.d.ts.map +1 -0
- package/dist/utils/intelligence-runtime-common.js +156 -0
- package/dist/utils/intelligence-runtime-common.js.map +1 -0
- package/dist/utils/intent-contract-diagnostics.d.ts +9 -0
- package/dist/utils/intent-contract-diagnostics.d.ts.map +1 -0
- package/dist/utils/intent-contract-diagnostics.js +322 -0
- package/dist/utils/intent-contract-diagnostics.js.map +1 -0
- package/dist/utils/intent-pack.d.ts +15 -0
- package/dist/utils/intent-pack.d.ts.map +1 -0
- package/dist/utils/intent-pack.js +196 -0
- package/dist/utils/intent-pack.js.map +1 -0
- package/dist/utils/plan-sync.d.ts +1 -0
- package/dist/utils/plan-sync.d.ts.map +1 -1
- package/dist/utils/plan-sync.js +23 -0
- package/dist/utils/plan-sync.js.map +1 -1
- package/dist/utils/policy-decision.d.ts +5 -0
- package/dist/utils/policy-decision.d.ts.map +1 -0
- package/dist/utils/policy-decision.js +17 -0
- package/dist/utils/policy-decision.js.map +1 -0
- package/dist/utils/replay-custody.d.ts +43 -0
- package/dist/utils/replay-custody.d.ts.map +1 -0
- package/dist/utils/replay-custody.js +168 -0
- package/dist/utils/replay-custody.js.map +1 -0
- package/dist/utils/replay-runtime.d.ts +13 -0
- package/dist/utils/replay-runtime.d.ts.map +1 -1
- package/dist/utils/replay-runtime.js +96 -9
- package/dist/utils/replay-runtime.js.map +1 -1
- package/dist/utils/repository-intelligence.d.ts +9 -0
- package/dist/utils/repository-intelligence.d.ts.map +1 -0
- package/dist/utils/repository-intelligence.js +372 -0
- package/dist/utils/repository-intelligence.js.map +1 -0
- package/dist/utils/runtime-events.d.ts.map +1 -1
- package/dist/utils/runtime-events.js +25 -6
- package/dist/utils/runtime-events.js.map +1 -1
- package/dist/utils/semantic-contract-intelligence.d.ts +20 -0
- package/dist/utils/semantic-contract-intelligence.d.ts.map +1 -0
- package/dist/utils/semantic-contract-intelligence.js +825 -0
- package/dist/utils/semantic-contract-intelligence.js.map +1 -0
- package/dist/utils/session-continuity.d.ts +56 -0
- package/dist/utils/session-continuity.d.ts.map +1 -0
- package/dist/utils/session-continuity.js +318 -0
- package/dist/utils/session-continuity.js.map +1 -0
- package/dist/utils/verification-evidence.d.ts.map +1 -1
- package/dist/utils/verification-evidence.js +4 -1
- package/dist/utils/verification-evidence.js.map +1 -1
- package/dist/utils/verify-runtime-stability.d.ts +142 -0
- package/dist/utils/verify-runtime-stability.d.ts.map +1 -0
- package/dist/utils/verify-runtime-stability.js +230 -0
- package/dist/utils/verify-runtime-stability.js.map +1 -0
- package/dist/utils/workspace-runtime.d.ts +1 -266
- package/dist/utils/workspace-runtime.d.ts.map +1 -1
- package/dist/utils/workspace-runtime.js +15 -1412
- package/dist/utils/workspace-runtime.js.map +1 -1
- package/package.json +11 -10
- 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
|
|
26
|
-
const
|
|
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 -
|
|
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
|
-
|
|
225
|
+
rawBodyLines.push(bodyLine);
|
|
41
226
|
j++;
|
|
42
227
|
}
|
|
43
|
-
if (
|
|
228
|
+
if (rawBodyLines.length === 0)
|
|
44
229
|
continue;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
if (
|
|
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
|
-
|
|
54
|
-
const nonEmpty = bodyLines
|
|
243
|
+
const nonEmptyNonPass = strippedLines
|
|
55
244
|
.map(l => l.trim())
|
|
56
|
-
.filter(l => l.length > 0 && l !== 'pass'
|
|
57
|
-
|
|
58
|
-
|
|
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:
|
|
70
|
-
'
|
|
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
|
|
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":";;;
|
|
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":"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"}
|
|
@@ -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
|