@neurcode-ai/cli 0.9.65 → 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.
Files changed (99) hide show
  1. package/dist/commands/bootstrap-policy.d.ts +29 -0
  2. package/dist/commands/bootstrap-policy.d.ts.map +1 -0
  3. package/dist/commands/bootstrap-policy.js +334 -0
  4. package/dist/commands/bootstrap-policy.js.map +1 -0
  5. package/dist/commands/doctor.d.ts.map +1 -1
  6. package/dist/commands/doctor.js +82 -0
  7. package/dist/commands/doctor.js.map +1 -1
  8. package/dist/commands/quickstart.d.ts +21 -0
  9. package/dist/commands/quickstart.d.ts.map +1 -0
  10. package/dist/commands/quickstart.js +178 -0
  11. package/dist/commands/quickstart.js.map +1 -0
  12. package/dist/commands/remediate-export.d.ts +31 -0
  13. package/dist/commands/remediate-export.d.ts.map +1 -0
  14. package/dist/commands/remediate-export.js +283 -0
  15. package/dist/commands/remediate-export.js.map +1 -0
  16. package/dist/commands/verify.d.ts.map +1 -1
  17. package/dist/commands/verify.js +106 -10
  18. package/dist/commands/verify.js.map +1 -1
  19. package/dist/governance/canonical-invariants.d.ts +88 -0
  20. package/dist/governance/canonical-invariants.d.ts.map +1 -0
  21. package/dist/governance/canonical-invariants.js +197 -0
  22. package/dist/governance/canonical-invariants.js.map +1 -0
  23. package/dist/governance/canonical-ordering.d.ts +76 -0
  24. package/dist/governance/canonical-ordering.d.ts.map +1 -0
  25. package/dist/governance/canonical-ordering.js +189 -0
  26. package/dist/governance/canonical-ordering.js.map +1 -0
  27. package/dist/governance/canonical-pipeline.d.ts +7 -0
  28. package/dist/governance/canonical-pipeline.d.ts.map +1 -1
  29. package/dist/governance/canonical-pipeline.js +184 -16
  30. package/dist/governance/canonical-pipeline.js.map +1 -1
  31. package/dist/governance/diff-line-provenance.d.ts +59 -0
  32. package/dist/governance/diff-line-provenance.d.ts.map +1 -0
  33. package/dist/governance/diff-line-provenance.js +118 -0
  34. package/dist/governance/diff-line-provenance.js.map +1 -0
  35. package/dist/governance/pilot-readiness.d.ts +34 -0
  36. package/dist/governance/pilot-readiness.d.ts.map +1 -0
  37. package/dist/governance/pilot-readiness.js +226 -0
  38. package/dist/governance/pilot-readiness.js.map +1 -0
  39. package/dist/governance/policy-parity-validator.d.ts +62 -0
  40. package/dist/governance/policy-parity-validator.d.ts.map +1 -0
  41. package/dist/governance/policy-parity-validator.js +137 -0
  42. package/dist/governance/policy-parity-validator.js.map +1 -0
  43. package/dist/governance/remediation-boundary.d.ts +55 -0
  44. package/dist/governance/remediation-boundary.d.ts.map +1 -0
  45. package/dist/governance/remediation-boundary.js +120 -0
  46. package/dist/governance/remediation-boundary.js.map +1 -0
  47. package/dist/governance/structural-cache.d.ts +103 -0
  48. package/dist/governance/structural-cache.d.ts.map +1 -0
  49. package/dist/governance/structural-cache.js +240 -0
  50. package/dist/governance/structural-cache.js.map +1 -0
  51. package/dist/governance/structural-on-diff.d.ts +22 -2
  52. package/dist/governance/structural-on-diff.d.ts.map +1 -1
  53. package/dist/governance/structural-on-diff.js +36 -4
  54. package/dist/governance/structural-on-diff.js.map +1 -1
  55. package/dist/governance/structural-policy-merge.d.ts +8 -0
  56. package/dist/governance/structural-policy-merge.d.ts.map +1 -1
  57. package/dist/governance/structural-policy-merge.js +7 -0
  58. package/dist/governance/structural-policy-merge.js.map +1 -1
  59. package/dist/governance/verify-runtime-guard.d.ts +99 -0
  60. package/dist/governance/verify-runtime-guard.d.ts.map +1 -0
  61. package/dist/governance/verify-runtime-guard.js +129 -0
  62. package/dist/governance/verify-runtime-guard.js.map +1 -0
  63. package/dist/index.js +50 -14
  64. package/dist/index.js.map +1 -1
  65. package/dist/intent-engine/repo-classifier.d.ts +64 -0
  66. package/dist/intent-engine/repo-classifier.d.ts.map +1 -0
  67. package/dist/intent-engine/repo-classifier.js +178 -0
  68. package/dist/intent-engine/repo-classifier.js.map +1 -0
  69. package/dist/structural-rules/index.d.ts +4 -0
  70. package/dist/structural-rules/index.d.ts.map +1 -1
  71. package/dist/structural-rules/index.js +18 -1
  72. package/dist/structural-rules/index.js.map +1 -1
  73. package/dist/structural-rules/python/PY003-broad-except-clause.d.ts +21 -0
  74. package/dist/structural-rules/python/PY003-broad-except-clause.d.ts.map +1 -1
  75. package/dist/structural-rules/python/PY003-broad-except-clause.js +212 -21
  76. package/dist/structural-rules/python/PY003-broad-except-clause.js.map +1 -1
  77. package/dist/structural-rules/python/PY011-thread-lifecycle.d.ts +11 -0
  78. package/dist/structural-rules/python/PY011-thread-lifecycle.d.ts.map +1 -0
  79. package/dist/structural-rules/python/PY011-thread-lifecycle.js +97 -0
  80. package/dist/structural-rules/python/PY011-thread-lifecycle.js.map +1 -0
  81. package/dist/structural-rules/python/PY012-asyncio-run-misuse.d.ts +11 -0
  82. package/dist/structural-rules/python/PY012-asyncio-run-misuse.d.ts.map +1 -0
  83. package/dist/structural-rules/python/PY012-asyncio-run-misuse.js +83 -0
  84. package/dist/structural-rules/python/PY012-asyncio-run-misuse.js.map +1 -0
  85. package/dist/structural-rules/python/PY013-mutable-default-arg.d.ts +11 -0
  86. package/dist/structural-rules/python/PY013-mutable-default-arg.d.ts.map +1 -0
  87. package/dist/structural-rules/python/PY013-mutable-default-arg.js +73 -0
  88. package/dist/structural-rules/python/PY013-mutable-default-arg.js.map +1 -0
  89. package/dist/structural-rules/python/PY014-fixed-sleep-retry.d.ts +11 -0
  90. package/dist/structural-rules/python/PY014-fixed-sleep-retry.d.ts.map +1 -0
  91. package/dist/structural-rules/python/PY014-fixed-sleep-retry.js +115 -0
  92. package/dist/structural-rules/python/PY014-fixed-sleep-retry.js.map +1 -0
  93. package/dist/structural-rules/types.d.ts +12 -0
  94. package/dist/structural-rules/types.d.ts.map +1 -1
  95. package/dist/utils/verify-runtime-stability.d.ts +142 -0
  96. package/dist/utils/verify-runtime-stability.d.ts.map +1 -0
  97. package/dist/utils/verify-runtime-stability.js +230 -0
  98. package/dist/utils/verify-runtime-stability.js.map +1 -0
  99. package/package.json +1 -1
@@ -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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"}
@@ -19,6 +19,18 @@ export interface StructuralViolation {
19
19
  determinism: DeterminismLevel;
20
20
  confidence: number;
21
21
  language: RuleLanguage;
22
+ /**
23
+ * Phase 2 — Diff-scoped enforcement.
24
+ * true → violation is on a line modified in the current diff (BLOCKING eligible)
25
+ * false → violation is on an untouched historical line (demoted to ADVISORY + legacyDebt)
26
+ * undefined → diff-scope not applied (e.g. --strict-full-file mode)
27
+ */
28
+ introducedOnModifiedLine?: boolean;
29
+ /**
30
+ * true when the violation is demoted from BLOCKING to ADVISORY because it
31
+ * exists on an unmodified line. Tagged for reviewer visibility and telemetry.
32
+ */
33
+ legacyDebt?: boolean;
22
34
  }
23
35
  export interface StructuralRuleResult {
24
36
  violations: StructuralViolation[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/structural-rules/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GACxB,0BAA0B,GAC1B,wBAAwB,GACxB,oBAAoB,GACpB,uBAAuB,CAAC;AAE5B,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,UAAU,CAAC;AACnD,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,QAAQ,GAAG,YAAY,CAAC;AAElE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAY,MAAM,CAAC;IACzB,QAAQ,EAAU,MAAM,CAAC;IACzB,SAAS,EAAS,MAAM,CAAC;IACzB,QAAQ,EAAU,YAAY,CAAC;IAC/B,QAAQ,EAAU,MAAM,CAAC;IACzB,IAAI,EAAc,MAAM,CAAC;IACzB,MAAM,EAAY,MAAM,CAAC;IACzB,QAAQ,EAAU,MAAM,CAAC;IACzB,eAAe,EAAG,MAAM,CAAC;IACzB,WAAW,EAAO,MAAM,CAAC;IACzB,WAAW,EAAO,gBAAgB,CAAC;IACnC,UAAU,EAAQ,MAAM,CAAC;IACzB,QAAQ,EAAU,YAAY,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAY,mBAAmB,EAAE,CAAC;IAC5C,aAAa,EAAS,MAAM,CAAC;IAC7B,UAAU,EAAY,MAAM,CAAC;IAC7B,YAAY,EAAU,MAAM,EAAE,CAAC;IAC/B,YAAY,EAAU,MAAM,EAAE,CAAC;IAC/B,eAAe,EAAO,MAAM,CAAC;IAC7B,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;IAC5C,mBAAmB,EAAG,kBAAkB,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAS,MAAM,CAAC;IAClB,IAAI,EAAO,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAG,YAAY,CAAC;IACxB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAAC;CACpE"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/structural-rules/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GACxB,0BAA0B,GAC1B,wBAAwB,GACxB,oBAAoB,GACpB,uBAAuB,CAAC;AAE5B,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,UAAU,CAAC;AACnD,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,QAAQ,GAAG,YAAY,CAAC;AAElE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAY,MAAM,CAAC;IACzB,QAAQ,EAAU,MAAM,CAAC;IACzB,SAAS,EAAS,MAAM,CAAC;IACzB,QAAQ,EAAU,YAAY,CAAC;IAC/B,QAAQ,EAAU,MAAM,CAAC;IACzB,IAAI,EAAc,MAAM,CAAC;IACzB,MAAM,EAAY,MAAM,CAAC;IACzB,QAAQ,EAAU,MAAM,CAAC;IACzB,eAAe,EAAG,MAAM,CAAC;IACzB,WAAW,EAAO,MAAM,CAAC;IACzB,WAAW,EAAO,gBAAgB,CAAC;IACnC,UAAU,EAAQ,MAAM,CAAC;IACzB,QAAQ,EAAU,YAAY,CAAC;IAC/B;;;;;OAKG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAY,mBAAmB,EAAE,CAAC;IAC5C,aAAa,EAAS,MAAM,CAAC;IAC7B,UAAU,EAAY,MAAM,CAAC;IAC7B,YAAY,EAAU,MAAM,EAAE,CAAC;IAC/B,YAAY,EAAU,MAAM,EAAE,CAAC;IAC/B,eAAe,EAAO,MAAM,CAAC;IAC7B,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;IAC5C,mBAAmB,EAAG,kBAAkB,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAS,MAAM,CAAC;IAClB,IAAI,EAAO,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAG,YAAY,CAAC;IACxB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAAC;CACpE"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Verify Runtime Stability (Phase 4 — Verify Runtime Stability)
3
+ *
4
+ * Bounded execution guarantees for enterprise CI environments.
5
+ *
6
+ * Provides:
7
+ * 1. Hard execution timeouts with graceful partial-result preservation
8
+ * 2. Large repo detection and cache-build recommendations
9
+ * 3. Memory pressure tracking with layered degradation
10
+ * 4. Runtime transparency — every degraded mode is explicitly reported
11
+ *
12
+ * Layered degradation order (when memory or time pressure is detected):
13
+ * 1. Semantic layer first (LLM-assisted planning, intent expansion)
14
+ * 2. Advisory systems second (heuristic signals, advisory-only rules)
15
+ * 3. NEVER structural governance — AST-backed structural verification
16
+ * MUST remain operational even under full degradation
17
+ *
18
+ * Design constraints:
19
+ * - All timeouts are configurable via environment variables
20
+ * - Degradation is explicit and reported in verify output
21
+ * - Partial findings are always preserved (never silently dropped)
22
+ * - Structural governance is protected by construction
23
+ */
24
+ /** Local verify max wall-clock time (90s default) */
25
+ export declare const LOCAL_VERIFY_MAX_MS: number;
26
+ /** CI verify max wall-clock time (180s default) */
27
+ export declare const CI_VERIFY_MAX_MS: number;
28
+ /** Repo size threshold for large-repo warning (10,000 files) */
29
+ export declare const LARGE_REPO_FILE_THRESHOLD: number;
30
+ /** Heap usage fraction above which semantic layer is degraded (0.75 = 75%) */
31
+ export declare const MEMORY_DEGRADE_SEMANTIC_THRESHOLD = 0.75;
32
+ /** Heap usage fraction above which advisory layer is degraded (0.90 = 90%) */
33
+ export declare const MEMORY_DEGRADE_ADVISORY_THRESHOLD = 0.9;
34
+ export type DegradedLayer = 'semantic' | 'advisory';
35
+ export interface VerifyRuntimeContext {
36
+ /** Whether running in CI mode */
37
+ isCI: boolean;
38
+ /** Effective timeout in milliseconds */
39
+ timeoutMs: number;
40
+ /** Timestamp when verify started */
41
+ startedAt: number;
42
+ /** Set of layers that have been degraded */
43
+ degradedLayers: Set<DegradedLayer>;
44
+ /** Degradation reasons */
45
+ degradationReasons: string[];
46
+ /** Skipped subsystems due to degradation */
47
+ skippedSubsystems: string[];
48
+ /** Whether large-repo mode was triggered */
49
+ largeRepoMode: boolean;
50
+ /** Estimated file count (if detectable) */
51
+ estimatedFileCount: number | null;
52
+ }
53
+ export interface VerifyRuntimeReport {
54
+ degraded: boolean;
55
+ degradedLayers: DegradedLayer[];
56
+ degradationReasons: string[];
57
+ skippedSubsystems: string[];
58
+ largeRepoMode: boolean;
59
+ estimatedFileCount: number | null;
60
+ elapsedMs: number;
61
+ timeoutMs: number;
62
+ remainingMs: number;
63
+ memoryUsageMb: number;
64
+ heapUsedFraction: number;
65
+ structuralGovernanceOperational: true;
66
+ }
67
+ /**
68
+ * Create a new verify runtime context.
69
+ * Call once at the start of verify execution.
70
+ */
71
+ export declare function createVerifyRuntimeContext(isCI: boolean): VerifyRuntimeContext;
72
+ /**
73
+ * Check if the verify execution has exceeded its time budget.
74
+ *
75
+ * @returns true if timeout has been exceeded
76
+ */
77
+ export declare function isTimedOut(ctx: VerifyRuntimeContext): boolean;
78
+ /**
79
+ * Get elapsed time in milliseconds since verify started.
80
+ */
81
+ export declare function elapsedMs(ctx: VerifyRuntimeContext): number;
82
+ /**
83
+ * Get remaining time in milliseconds before timeout.
84
+ */
85
+ export declare function remainingMs(ctx: VerifyRuntimeContext): number;
86
+ /**
87
+ * Check if remaining time is below a given threshold.
88
+ * Use to preemptively skip expensive operations.
89
+ *
90
+ * @param ctx Runtime context
91
+ * @param thresholdMs Time threshold in ms (default: 5000ms)
92
+ */
93
+ export declare function isTimePressure(ctx: VerifyRuntimeContext, thresholdMs?: number): boolean;
94
+ /**
95
+ * Get current heap usage as a fraction of heap limit.
96
+ * Returns 0 if measurement is unavailable.
97
+ */
98
+ export declare function getHeapUsedFraction(): number;
99
+ /**
100
+ * Check memory pressure and apply layered degradation if needed.
101
+ *
102
+ * Degradation order:
103
+ * 1. 75% heap: degrade semantic layer (LLM intent expansion etc.)
104
+ * 2. 90% heap: degrade advisory layer (heuristic signals etc.)
105
+ * Structural governance is NEVER degraded by memory pressure.
106
+ *
107
+ * @param ctx Runtime context (mutated to record degradation)
108
+ */
109
+ export declare function applyMemoryPressureDegradation(ctx: VerifyRuntimeContext): void;
110
+ /**
111
+ * Estimate the number of tracked files in the git repository.
112
+ * Returns null if git is unavailable or the command fails.
113
+ */
114
+ export declare function estimateRepoFileCount(projectRoot: string): number | null;
115
+ /**
116
+ * Check if the repo qualifies as a large repo and apply appropriate guidance.
117
+ *
118
+ * If file count exceeds LARGE_REPO_FILE_THRESHOLD:
119
+ * - Sets ctx.largeRepoMode = true
120
+ * - Adds a cache-build recommendation to degradationReasons
121
+ * - Does NOT degrade structural governance
122
+ *
123
+ * @param ctx Runtime context (mutated)
124
+ * @param projectRoot Project root path
125
+ */
126
+ export declare function applyLargeRepoProtection(ctx: VerifyRuntimeContext, projectRoot: string): void;
127
+ /**
128
+ * Return true if the semantic layer should be skipped.
129
+ * Call before any LLM-assisted or semantic expansion operations.
130
+ */
131
+ export declare function shouldSkipSemanticLayer(ctx: VerifyRuntimeContext): boolean;
132
+ /**
133
+ * Return true if the advisory layer should be skipped.
134
+ * Call before any heuristic or advisory-only operations.
135
+ */
136
+ export declare function shouldSkipAdvisoryLayer(ctx: VerifyRuntimeContext): boolean;
137
+ /**
138
+ * Build a runtime transparency report from the context.
139
+ * Include this in verify JSON output when --ci flag is set.
140
+ */
141
+ export declare function buildVerifyRuntimeReport(ctx: VerifyRuntimeContext): VerifyRuntimeReport;
142
+ //# sourceMappingURL=verify-runtime-stability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-runtime-stability.d.ts","sourceRoot":"","sources":["../../src/utils/verify-runtime-stability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH,qDAAqD;AACrD,eAAO,MAAM,mBAAmB,QAC4C,CAAC;AAE7E,mDAAmD;AACnD,eAAO,MAAM,gBAAgB,QAC6C,CAAC;AAE3E,gEAAgE;AAChE,eAAO,MAAM,yBAAyB,QACsC,CAAC;AAE7E,8EAA8E;AAC9E,eAAO,MAAM,iCAAiC,OAAO,CAAC;AAEtD,8EAA8E;AAC9E,eAAO,MAAM,iCAAiC,MAAO,CAAC;AAItD,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,CAAC;AAEpD,MAAM,WAAW,oBAAoB;IACnC,iCAAiC;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,cAAc,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;IACnC,0BAA0B;IAC1B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,4CAA4C;IAC5C,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,4CAA4C;IAC5C,aAAa,EAAE,OAAO,CAAC;IACvB,2CAA2C;IAC3C,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,+BAA+B,EAAE,IAAI,CAAC;CACvC;AAID;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,OAAO,GAAG,oBAAoB,CAW9E;AAID;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,oBAAoB,GAAG,OAAO,CAE7D;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,oBAAoB,GAAG,MAAM,CAE3D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,oBAAoB,GAAG,MAAM,CAE7D;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,oBAAoB,EAAE,WAAW,SAAQ,GAAG,OAAO,CAEtF;AAID;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAU5C;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,oBAAoB,GAAG,IAAI,CAoB9E;AAID;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAaxE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,oBAAoB,EACzB,WAAW,EAAE,MAAM,GAClB,IAAI,CAiBN;AAID;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,oBAAoB,GAAG,OAAO,CAE1E;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,oBAAoB,GAAG,OAAO,CAE1E;AAID;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,oBAAoB,GAAG,mBAAmB,CAuBvF"}