@neurcode-ai/cli 0.9.64 → 0.9.66
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/LICENSE +201 -0
- 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/brain.d.ts.map +1 -1
- package/dist/commands/brain.js +273 -0
- package/dist/commands/brain.js.map +1 -1
- 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/pilot-report.d.ts +9 -0
- package/dist/commands/pilot-report.d.ts.map +1 -0
- package/dist/commands/pilot-report.js +176 -0
- package/dist/commands/pilot-report.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 +31 -0
- package/dist/commands/remediate-export.d.ts.map +1 -0
- package/dist/commands/remediate-export.js +283 -0
- package/dist/commands/remediate-export.js.map +1 -0
- package/dist/commands/remediate-governance.d.ts +54 -0
- package/dist/commands/remediate-governance.d.ts.map +1 -0
- package/dist/commands/remediate-governance.js +375 -0
- package/dist/commands/remediate-governance.js.map +1 -0
- package/dist/commands/remediate.d.ts.map +1 -1
- package/dist/commands/remediate.js.map +1 -1
- package/dist/commands/replay.d.ts.map +1 -1
- package/dist/commands/replay.js +30 -0
- package/dist/commands/replay.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +409 -30
- package/dist/commands/verify.js.map +1 -1
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +1078 -0
- package/dist/daemon/server.js.map +1 -1
- package/dist/explainability/DeterminismClassifier.d.ts +34 -0
- package/dist/explainability/DeterminismClassifier.d.ts.map +1 -0
- package/dist/explainability/DeterminismClassifier.js +104 -0
- package/dist/explainability/DeterminismClassifier.js.map +1 -0
- package/dist/explainability/ViolationFormatter.d.ts +32 -0
- package/dist/explainability/ViolationFormatter.d.ts.map +1 -0
- package/dist/explainability/ViolationFormatter.js +252 -0
- package/dist/explainability/ViolationFormatter.js.map +1 -0
- package/dist/explainability/index.d.ts +15 -0
- package/dist/explainability/index.d.ts.map +1 -0
- package/dist/explainability/index.js +94 -0
- package/dist/explainability/index.js.map +1 -0
- package/dist/explainability/types.d.ts +37 -0
- package/dist/explainability/types.d.ts.map +1 -0
- package/dist/explainability/types.js +3 -0
- package/dist/explainability/types.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 +45 -0
- package/dist/governance/canonical-pipeline.d.ts.map +1 -0
- package/dist/governance/canonical-pipeline.js +616 -0
- package/dist/governance/canonical-pipeline.js.map +1 -0
- 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 +240 -0
- package/dist/governance/structural-cache.js.map +1 -0
- package/dist/governance/structural-on-diff.d.ts +33 -0
- package/dist/governance/structural-on-diff.d.ts.map +1 -0
- package/dist/governance/structural-on-diff.js +67 -0
- package/dist/governance/structural-on-diff.js.map +1 -0
- package/dist/governance/structural-policy-merge.d.ts +22 -0
- package/dist/governance/structural-policy-merge.d.ts.map +1 -0
- package/dist/governance/structural-policy-merge.js +32 -0
- package/dist/governance/structural-policy-merge.js.map +1 -0
- 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 +107 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/review-compression/index.d.ts +50 -0
- package/dist/integrations/review-compression/index.d.ts.map +1 -0
- package/dist/integrations/review-compression/index.js +158 -0
- package/dist/integrations/review-compression/index.js.map +1 -0
- package/dist/intent-engine/domain-taxonomy.d.ts +42 -0
- package/dist/intent-engine/domain-taxonomy.d.ts.map +1 -0
- package/dist/intent-engine/domain-taxonomy.js +534 -0
- package/dist/intent-engine/domain-taxonomy.js.map +1 -0
- package/dist/intent-engine/index.d.ts +1 -0
- package/dist/intent-engine/index.d.ts.map +1 -1
- package/dist/intent-engine/index.js +6 -1
- package/dist/intent-engine/index.js.map +1 -1
- package/dist/intent-engine/parser.d.ts.map +1 -1
- package/dist/intent-engine/parser.js +47 -0
- package/dist/intent-engine/parser.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/intent-engine/semantic-expander.d.ts +104 -0
- package/dist/intent-engine/semantic-expander.d.ts.map +1 -0
- package/dist/intent-engine/semantic-expander.js +480 -0
- package/dist/intent-engine/semantic-expander.js.map +1 -0
- package/dist/patch-engine/patterns.d.ts.map +1 -1
- package/dist/patch-engine/patterns.js +8 -4
- package/dist/patch-engine/patterns.js.map +1 -1
- package/dist/semantic/index.d.ts +14 -0
- package/dist/semantic/index.d.ts.map +1 -0
- package/dist/semantic/index.js +30 -0
- package/dist/semantic/index.js.map +1 -0
- package/dist/semantic/tfidf-engine.d.ts +81 -0
- package/dist/semantic/tfidf-engine.d.ts.map +1 -0
- package/dist/semantic/tfidf-engine.js +278 -0
- package/dist/semantic/tfidf-engine.js.map +1 -0
- package/dist/semantic/vector-store.d.ts +108 -0
- package/dist/semantic/vector-store.d.ts.map +1 -0
- package/dist/semantic/vector-store.js +321 -0
- package/dist/semantic/vector-store.js.map +1 -0
- package/dist/structural-rules/context-severity.d.ts +46 -0
- package/dist/structural-rules/context-severity.d.ts.map +1 -0
- package/dist/structural-rules/context-severity.js +115 -0
- package/dist/structural-rules/context-severity.js.map +1 -0
- package/dist/structural-rules/distributed/DS001-saga-rollback-absence.d.ts +11 -0
- package/dist/structural-rules/distributed/DS001-saga-rollback-absence.d.ts.map +1 -0
- package/dist/structural-rules/distributed/DS001-saga-rollback-absence.js +212 -0
- package/dist/structural-rules/distributed/DS001-saga-rollback-absence.js.map +1 -0
- package/dist/structural-rules/distributed/DS002-missing-correlation-id.d.ts +11 -0
- package/dist/structural-rules/distributed/DS002-missing-correlation-id.d.ts.map +1 -0
- package/dist/structural-rules/distributed/DS002-missing-correlation-id.js +213 -0
- package/dist/structural-rules/distributed/DS002-missing-correlation-id.js.map +1 -0
- package/dist/structural-rules/distributed/index.d.ts +3 -0
- package/dist/structural-rules/distributed/index.d.ts.map +1 -0
- package/dist/structural-rules/distributed/index.js +8 -0
- package/dist/structural-rules/distributed/index.js.map +1 -0
- package/dist/structural-rules/engine.d.ts +25 -0
- package/dist/structural-rules/engine.d.ts.map +1 -0
- package/dist/structural-rules/engine.js +90 -0
- package/dist/structural-rules/engine.js.map +1 -0
- package/dist/structural-rules/index.d.ts +45 -0
- package/dist/structural-rules/index.d.ts.map +1 -0
- package/dist/structural-rules/index.js +158 -0
- package/dist/structural-rules/index.js.map +1 -0
- package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.d.ts +11 -0
- package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.d.ts.map +1 -0
- package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.js +66 -0
- package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.js.map +1 -0
- package/dist/structural-rules/python/PY002-unbounded-dict-singleton.d.ts +11 -0
- package/dist/structural-rules/python/PY002-unbounded-dict-singleton.d.ts.map +1 -0
- package/dist/structural-rules/python/PY002-unbounded-dict-singleton.js +135 -0
- package/dist/structural-rules/python/PY002-unbounded-dict-singleton.js.map +1 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.d.ts +32 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.d.ts.map +1 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.js +277 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.js.map +1 -0
- package/dist/structural-rules/python/PY004-swallowed-async-exception.d.ts +11 -0
- package/dist/structural-rules/python/PY004-swallowed-async-exception.d.ts.map +1 -0
- package/dist/structural-rules/python/PY004-swallowed-async-exception.js +167 -0
- package/dist/structural-rules/python/PY004-swallowed-async-exception.js.map +1 -0
- package/dist/structural-rules/python/PY005-fastapi-without-pydantic.d.ts +11 -0
- package/dist/structural-rules/python/PY005-fastapi-without-pydantic.d.ts.map +1 -0
- package/dist/structural-rules/python/PY005-fastapi-without-pydantic.js +154 -0
- package/dist/structural-rules/python/PY005-fastapi-without-pydantic.js.map +1 -0
- package/dist/structural-rules/python/PY006-blocking-io-in-async.d.ts +11 -0
- package/dist/structural-rules/python/PY006-blocking-io-in-async.d.ts.map +1 -0
- package/dist/structural-rules/python/PY006-blocking-io-in-async.js +130 -0
- package/dist/structural-rules/python/PY006-blocking-io-in-async.js.map +1 -0
- package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.d.ts +11 -0
- package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.d.ts.map +1 -0
- package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.js +93 -0
- package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.js.map +1 -0
- package/dist/structural-rules/python/PY008-celery-task-without-retry.d.ts +11 -0
- package/dist/structural-rules/python/PY008-celery-task-without-retry.d.ts.map +1 -0
- package/dist/structural-rules/python/PY008-celery-task-without-retry.js +154 -0
- package/dist/structural-rules/python/PY008-celery-task-without-retry.js.map +1 -0
- package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.d.ts +11 -0
- package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.d.ts.map +1 -0
- package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.js +133 -0
- package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.js.map +1 -0
- package/dist/structural-rules/python/PY010-leaked-aiohttp-session.d.ts +11 -0
- package/dist/structural-rules/python/PY010-leaked-aiohttp-session.d.ts.map +1 -0
- package/dist/structural-rules/python/PY010-leaked-aiohttp-session.js +80 -0
- package/dist/structural-rules/python/PY010-leaked-aiohttp-session.js.map +1 -0
- 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/rules/SR001-swallowed-async-rejection.d.ts +11 -0
- package/dist/structural-rules/rules/SR001-swallowed-async-rejection.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR001-swallowed-async-rejection.js +145 -0
- package/dist/structural-rules/rules/SR001-swallowed-async-rejection.js.map +1 -0
- package/dist/structural-rules/rules/SR002-unbounded-collection.d.ts +11 -0
- package/dist/structural-rules/rules/SR002-unbounded-collection.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR002-unbounded-collection.js +196 -0
- package/dist/structural-rules/rules/SR002-unbounded-collection.js.map +1 -0
- package/dist/structural-rules/rules/SR003-timer-without-cleanup.d.ts +11 -0
- package/dist/structural-rules/rules/SR003-timer-without-cleanup.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR003-timer-without-cleanup.js +148 -0
- package/dist/structural-rules/rules/SR003-timer-without-cleanup.js.map +1 -0
- package/dist/structural-rules/rules/SR004-request-boundary-no-validation.d.ts +11 -0
- package/dist/structural-rules/rules/SR004-request-boundary-no-validation.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR004-request-boundary-no-validation.js +162 -0
- package/dist/structural-rules/rules/SR004-request-boundary-no-validation.js.map +1 -0
- package/dist/structural-rules/rules/SR005-halfopen-probe-gate.d.ts +11 -0
- package/dist/structural-rules/rules/SR005-halfopen-probe-gate.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR005-halfopen-probe-gate.js +150 -0
- package/dist/structural-rules/rules/SR005-halfopen-probe-gate.js.map +1 -0
- package/dist/structural-rules/rules/SR006-fanout-error-sanitization.d.ts +11 -0
- package/dist/structural-rules/rules/SR006-fanout-error-sanitization.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR006-fanout-error-sanitization.js +161 -0
- package/dist/structural-rules/rules/SR006-fanout-error-sanitization.js.map +1 -0
- package/dist/structural-rules/rules/SR007-cross-request-error.d.ts +11 -0
- package/dist/structural-rules/rules/SR007-cross-request-error.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR007-cross-request-error.js +175 -0
- package/dist/structural-rules/rules/SR007-cross-request-error.js.map +1 -0
- package/dist/structural-rules/rules/SR008-background-task-orphan.d.ts +11 -0
- package/dist/structural-rules/rules/SR008-background-task-orphan.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR008-background-task-orphan.js +176 -0
- package/dist/structural-rules/rules/SR008-background-task-orphan.js.map +1 -0
- package/dist/structural-rules/rules/SR009-missing-retry-backoff.d.ts +11 -0
- package/dist/structural-rules/rules/SR009-missing-retry-backoff.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR009-missing-retry-backoff.js +168 -0
- package/dist/structural-rules/rules/SR009-missing-retry-backoff.js.map +1 -0
- package/dist/structural-rules/rules/SR010-retry-storm.d.ts +11 -0
- package/dist/structural-rules/rules/SR010-retry-storm.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR010-retry-storm.js +181 -0
- package/dist/structural-rules/rules/SR010-retry-storm.js.map +1 -0
- package/dist/structural-rules/rules/SR011-event-listener-leak.d.ts +11 -0
- package/dist/structural-rules/rules/SR011-event-listener-leak.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR011-event-listener-leak.js +208 -0
- package/dist/structural-rules/rules/SR011-event-listener-leak.js.map +1 -0
- package/dist/structural-rules/rules/SR012-promise-race-leak.d.ts +11 -0
- package/dist/structural-rules/rules/SR012-promise-race-leak.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR012-promise-race-leak.js +191 -0
- package/dist/structural-rules/rules/SR012-promise-race-leak.js.map +1 -0
- package/dist/structural-rules/rules/SR013-missing-idempotency-key.d.ts +11 -0
- package/dist/structural-rules/rules/SR013-missing-idempotency-key.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR013-missing-idempotency-key.js +219 -0
- package/dist/structural-rules/rules/SR013-missing-idempotency-key.js.map +1 -0
- package/dist/structural-rules/rules/SR014-mutable-closure-async.d.ts +11 -0
- package/dist/structural-rules/rules/SR014-mutable-closure-async.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR014-mutable-closure-async.js +208 -0
- package/dist/structural-rules/rules/SR014-mutable-closure-async.js.map +1 -0
- package/dist/structural-rules/rules/SR015-dangling-abort-controller.d.ts +11 -0
- package/dist/structural-rules/rules/SR015-dangling-abort-controller.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR015-dangling-abort-controller.js +190 -0
- package/dist/structural-rules/rules/SR015-dangling-abort-controller.js.map +1 -0
- package/dist/structural-rules/rules/SR016-unsafe-json-parse.d.ts +11 -0
- package/dist/structural-rules/rules/SR016-unsafe-json-parse.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR016-unsafe-json-parse.js +187 -0
- package/dist/structural-rules/rules/SR016-unsafe-json-parse.js.map +1 -0
- package/dist/structural-rules/suppressions.d.ts +43 -0
- package/dist/structural-rules/suppressions.d.ts.map +1 -0
- package/dist/structural-rules/suppressions.js +115 -0
- package/dist/structural-rules/suppressions.js.map +1 -0
- package/dist/structural-rules/types.d.ts +55 -0
- package/dist/structural-rules/types.d.ts.map +1 -0
- package/dist/structural-rules/types.js +3 -0
- package/dist/structural-rules/types.js.map +1 -0
- package/dist/utils/brain-cache.d.ts +100 -0
- package/dist/utils/brain-cache.d.ts.map +1 -0
- package/dist/utils/brain-cache.js +346 -0
- package/dist/utils/brain-cache.js.map +1 -0
- package/dist/utils/governance-provenance.d.ts +95 -0
- package/dist/utils/governance-provenance.d.ts.map +1 -0
- package/dist/utils/governance-provenance.js +187 -0
- package/dist/utils/governance-provenance.js.map +1 -0
- package/dist/utils/pilot-metrics.d.ts +46 -0
- package/dist/utils/pilot-metrics.d.ts.map +1 -0
- package/dist/utils/pilot-metrics.js +240 -0
- package/dist/utils/pilot-metrics.js.map +1 -0
- package/dist/utils/replay-runtime.d.ts +34 -0
- package/dist/utils/replay-runtime.d.ts.map +1 -1
- package/dist/utils/replay-runtime.js +207 -0
- package/dist/utils/replay-runtime.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/workspace/cross-repo-graph.d.ts +111 -0
- package/dist/workspace/cross-repo-graph.d.ts.map +1 -0
- package/dist/workspace/cross-repo-graph.js +450 -0
- package/dist/workspace/cross-repo-graph.js.map +1 -0
- package/dist/workspace/federated-context.d.ts +144 -0
- package/dist/workspace/federated-context.d.ts.map +1 -0
- package/dist/workspace/federated-context.js +347 -0
- package/dist/workspace/federated-context.js.map +1 -0
- package/dist/workspace/index.d.ts +38 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +48 -0
- package/dist/workspace/index.js.map +1 -0
- package/package.json +9 -9
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PY010LeakedAiohttpSession = void 0;
|
|
4
|
+
// Matches bare assignment: session = aiohttp.ClientSession() or session = ClientSession()
|
|
5
|
+
// Captures variable name and whether it's aiohttp-qualified
|
|
6
|
+
const SESSION_ASSIGN_RE = /^(\s*)(\w+)\s*=\s*(?:aiohttp\.)?ClientSession\s*\(/;
|
|
7
|
+
// Matches correct async with usage
|
|
8
|
+
const ASYNC_WITH_SESSION_RE = /^\s*async\s+with\s+(?:aiohttp\.)?ClientSession\s*\(/;
|
|
9
|
+
// Matches await <varname>.close() — shutdown hook pattern
|
|
10
|
+
const AWAIT_CLOSE_RE = /\bawait\s+\w+\s*\.\s*close\s*\(\)/;
|
|
11
|
+
class PY010LeakedAiohttpSession {
|
|
12
|
+
id = 'PY010';
|
|
13
|
+
name = 'aiohttp.ClientSession created without context manager';
|
|
14
|
+
policyRef = 'PY010';
|
|
15
|
+
severity = 'BLOCKING';
|
|
16
|
+
languages = ['python'];
|
|
17
|
+
description = 'aiohttp.ClientSession() assigned to a variable without `async with` leaks TCP connection pools and file descriptors.';
|
|
18
|
+
check(filePath, sourceText) {
|
|
19
|
+
try {
|
|
20
|
+
const violations = [];
|
|
21
|
+
// Normalize line endings
|
|
22
|
+
const normalizedText = sourceText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
23
|
+
const lines = normalizedText.split('\n');
|
|
24
|
+
// Pre-scan: does the file contain await <something>.close() anywhere?
|
|
25
|
+
// This handles the module-level singleton pattern with a shutdown hook.
|
|
26
|
+
const fileHasAwaitClose = AWAIT_CLOSE_RE.test(normalizedText);
|
|
27
|
+
for (let i = 0; i < lines.length; i++) {
|
|
28
|
+
const line = lines[i];
|
|
29
|
+
const trimmed = line.trimStart();
|
|
30
|
+
// Skip comment lines
|
|
31
|
+
if (trimmed.startsWith('#'))
|
|
32
|
+
continue;
|
|
33
|
+
// Skip noqa
|
|
34
|
+
if (/\bnoqa\b/.test(line))
|
|
35
|
+
continue;
|
|
36
|
+
// Skip correct async with usage
|
|
37
|
+
if (ASYNC_WITH_SESSION_RE.test(line))
|
|
38
|
+
continue;
|
|
39
|
+
const match = SESSION_ASSIGN_RE.exec(line);
|
|
40
|
+
if (!match)
|
|
41
|
+
continue;
|
|
42
|
+
const varName = match[2];
|
|
43
|
+
// Check if the specific variable has a close() call anywhere in the file
|
|
44
|
+
const varCloseRe = new RegExp(`\\bawait\\s+${varName}\\s*\\.\\s*close\\s*\\(\\)`);
|
|
45
|
+
const varHasClose = varCloseRe.test(normalizedText);
|
|
46
|
+
// If this variable is closed somewhere (shutdown hook) — it's a managed singleton, skip
|
|
47
|
+
if (varHasClose)
|
|
48
|
+
continue;
|
|
49
|
+
// If the file has generic await <x>.close() and this is likely a module-level singleton
|
|
50
|
+
// Heuristic: if the assignment is at module level (indent == 0) and file has await close → skip
|
|
51
|
+
const assignIndent = match[1].length;
|
|
52
|
+
if (assignIndent === 0 && fileHasAwaitClose)
|
|
53
|
+
continue;
|
|
54
|
+
violations.push({
|
|
55
|
+
ruleId: this.id,
|
|
56
|
+
ruleName: this.name,
|
|
57
|
+
policyRef: this.policyRef,
|
|
58
|
+
severity: this.severity,
|
|
59
|
+
filePath,
|
|
60
|
+
line: i + 1,
|
|
61
|
+
column: 1,
|
|
62
|
+
evidence: line.slice(0, 120),
|
|
63
|
+
operationalRisk: 'Each unclosed aiohttp session leaks a TCP connection pool and an underlying connector. ' +
|
|
64
|
+
'In services that create sessions per-request, this exhausts file descriptors within minutes under load.',
|
|
65
|
+
remediation: 'Use `async with aiohttp.ClientSession() as session:` for request-scoped sessions. ' +
|
|
66
|
+
'For long-lived sessions, create once at startup and call `await session.close()` in the app shutdown handler.',
|
|
67
|
+
determinism: 'heuristic-advisory',
|
|
68
|
+
confidence: 0.85,
|
|
69
|
+
language: 'python',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return violations;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.PY010LeakedAiohttpSession = PY010LeakedAiohttpSession;
|
|
80
|
+
//# sourceMappingURL=PY010-leaked-aiohttp-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY010-leaked-aiohttp-session.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY010-leaked-aiohttp-session.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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY014-fixed-sleep-retry.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY014-fixed-sleep-retry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AA8C7E,qBAAa,oBAAqB,YAAW,cAAc;IACzD,EAAE,SAAW;IACb,IAAI,SAAmD;IACvD,SAAS,SAAW;IACpB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SAE8B;IAEzC,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAuEnE"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PY014FixedSleepRetry = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* PY014 — Fixed-Sleep Retry Without Exponential Backoff
|
|
6
|
+
*
|
|
7
|
+
* Retry loops that use a constant time.sleep() delay cause thundering herd
|
|
8
|
+
* against downstream services when many workers fail simultaneously.
|
|
9
|
+
*
|
|
10
|
+
* Detection: for/while loop + except clause + time.sleep(constant literal)
|
|
11
|
+
* with no exponential calculation visible nearby.
|
|
12
|
+
*
|
|
13
|
+
* BLOCKING: fixed retry sleep = synchronized retry storms on partial outage.
|
|
14
|
+
*/
|
|
15
|
+
const LOOP_START_RE = /^(\s*)(?:for\s+\w+|while\s+)/;
|
|
16
|
+
const EXCEPT_RE = /^\s*except\b/;
|
|
17
|
+
const FIXED_SLEEP_RE = /\btime\.sleep\s*\(\s*(\d+(?:\.\d+)?)\s*\)/;
|
|
18
|
+
const EXPONENTIAL_MARKERS = [
|
|
19
|
+
/\*\*\s*\d/,
|
|
20
|
+
/\*=\s*2/,
|
|
21
|
+
/\bmin\s*\(/,
|
|
22
|
+
/\brandom\.uniform/,
|
|
23
|
+
/\bjitter\b/,
|
|
24
|
+
/\bbackoff\b/,
|
|
25
|
+
/\bexponential\b/,
|
|
26
|
+
/sleep\s*\*\s*\d/,
|
|
27
|
+
/\battempt\s*\*/,
|
|
28
|
+
/\* attempt/,
|
|
29
|
+
];
|
|
30
|
+
function hasExponentialNearby(lines, center, radius = 12) {
|
|
31
|
+
const start = Math.max(0, center - radius);
|
|
32
|
+
const end = Math.min(lines.length - 1, center + radius);
|
|
33
|
+
for (let i = start; i <= end; i++) {
|
|
34
|
+
for (const re of EXPONENTIAL_MARKERS) {
|
|
35
|
+
if (re.test(lines[i]))
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
function getIndent(line) {
|
|
42
|
+
return line.length - line.trimStart().length;
|
|
43
|
+
}
|
|
44
|
+
class PY014FixedSleepRetry {
|
|
45
|
+
id = 'PY014';
|
|
46
|
+
name = 'Fixed-sleep retry without exponential backoff';
|
|
47
|
+
policyRef = 'PY014';
|
|
48
|
+
severity = 'BLOCKING';
|
|
49
|
+
languages = ['python'];
|
|
50
|
+
description = 'time.sleep() with a constant value inside a retry loop creates thundering herd. ' +
|
|
51
|
+
'Use exponential backoff with jitter.';
|
|
52
|
+
check(filePath, sourceText) {
|
|
53
|
+
try {
|
|
54
|
+
const violations = [];
|
|
55
|
+
const lines = sourceText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
|
|
56
|
+
const loopScopes = [];
|
|
57
|
+
for (let i = 0; i < lines.length; i++) {
|
|
58
|
+
const line = lines[i];
|
|
59
|
+
const trimmed = line.trimStart();
|
|
60
|
+
// Track loop starts
|
|
61
|
+
const loopMatch = LOOP_START_RE.exec(line);
|
|
62
|
+
if (loopMatch) {
|
|
63
|
+
loopScopes.push({ startLine: i, indent: loopMatch[1].length, hasExcept: false });
|
|
64
|
+
}
|
|
65
|
+
// Pop exited scopes
|
|
66
|
+
if (loopScopes.length > 0 && trimmed.length > 0 && !trimmed.startsWith('#')) {
|
|
67
|
+
const currentIndent = getIndent(line);
|
|
68
|
+
while (loopScopes.length > 0 &&
|
|
69
|
+
currentIndent < loopScopes[loopScopes.length - 1].indent &&
|
|
70
|
+
i > loopScopes[loopScopes.length - 1].startLine) {
|
|
71
|
+
loopScopes.pop();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Mark loop scope as containing an except
|
|
75
|
+
if (EXCEPT_RE.test(line) && loopScopes.length > 0) {
|
|
76
|
+
loopScopes[loopScopes.length - 1].hasExcept = true;
|
|
77
|
+
}
|
|
78
|
+
// Detect fixed sleep inside retry loop
|
|
79
|
+
const sleepMatch = FIXED_SLEEP_RE.exec(line);
|
|
80
|
+
if (sleepMatch && loopScopes.length > 0) {
|
|
81
|
+
const scope = loopScopes[loopScopes.length - 1];
|
|
82
|
+
if (scope.hasExcept && !hasExponentialNearby(lines, i)) {
|
|
83
|
+
const sleepVal = sleepMatch[1];
|
|
84
|
+
violations.push({
|
|
85
|
+
ruleId: this.id,
|
|
86
|
+
ruleName: this.name,
|
|
87
|
+
policyRef: this.policyRef,
|
|
88
|
+
severity: this.severity,
|
|
89
|
+
filePath,
|
|
90
|
+
line: i + 1,
|
|
91
|
+
column: line.indexOf('time.sleep') + 1,
|
|
92
|
+
evidence: line.trim(),
|
|
93
|
+
operationalRisk: `Fixed retry sleep time.sleep(${sleepVal}) inside retry loop (loop at line ${scope.startLine + 1}). ` +
|
|
94
|
+
'Under partial outage, all workers retry simultaneously after the same delay, ' +
|
|
95
|
+
'creating a thundering herd that overwhelms the recovering service.',
|
|
96
|
+
remediation: `Replace with exponential backoff:\n` +
|
|
97
|
+
` import random\n` +
|
|
98
|
+
` sleep_time = min(${sleepVal} * (2 ** attempt) + random.uniform(0, 1), max_sleep)\n` +
|
|
99
|
+
` time.sleep(sleep_time)`,
|
|
100
|
+
determinism: 'deterministic-structural',
|
|
101
|
+
confidence: 0.82,
|
|
102
|
+
language: 'python',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return violations;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.PY014FixedSleepRetry = PY014FixedSleepRetry;
|
|
115
|
+
//# sourceMappingURL=PY014-fixed-sleep-retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY014-fixed-sleep-retry.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY014-fixed-sleep-retry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
|
|
2
|
+
export declare class SR001SwallowedAsyncRejection 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=SR001-swallowed-async-rejection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SR001-swallowed-async-rejection.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/rules/SR001-swallowed-async-rejection.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAiC7E,qBAAa,4BAA6B,YAAW,cAAc;IACjE,EAAE,SAAW;IACb,IAAI,SAA+B;IACnC,SAAS,SAAU;IACnB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAgC;IACzD,WAAW,SACgG;IAE3G,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAmFnE"}
|