@rigour-labs/core 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +9 -1
  2. package/dist/gates/agent-team.d.ts +0 -1
  3. package/dist/gates/agent-team.js +0 -1
  4. package/dist/gates/checkpoint.d.ts +0 -2
  5. package/dist/gates/checkpoint.js +0 -2
  6. package/dist/gates/context-window-artifacts.d.ts +6 -2
  7. package/dist/gates/context-window-artifacts.js +107 -31
  8. package/dist/gates/deep-analysis.d.ts +2 -0
  9. package/dist/gates/deep-analysis.js +41 -11
  10. package/dist/gates/dependency.d.ts +0 -2
  11. package/dist/gates/dependency.js +23 -5
  12. package/dist/gates/deprecated-apis.d.ts +0 -2
  13. package/dist/gates/deprecated-apis.js +33 -20
  14. package/dist/gates/duplication-drift/index.d.ts +61 -0
  15. package/dist/gates/duplication-drift/index.js +240 -0
  16. package/dist/gates/duplication-drift/similarity.d.ts +68 -0
  17. package/dist/gates/duplication-drift/similarity.js +177 -0
  18. package/dist/gates/duplication-drift/tokenizer.d.ts +55 -0
  19. package/dist/gates/duplication-drift/tokenizer.js +195 -0
  20. package/dist/gates/frontend-secret-exposure.d.ts +0 -3
  21. package/dist/gates/frontend-secret-exposure.js +1 -114
  22. package/dist/gates/frontend-secret-patterns.d.ts +33 -0
  23. package/dist/gates/frontend-secret-patterns.js +119 -0
  24. package/dist/gates/{hallucinated-imports.d.ts → hallucinated-imports/index.d.ts} +2 -29
  25. package/dist/gates/hallucinated-imports/index.js +174 -0
  26. package/dist/gates/hallucinated-imports/js-resolver.d.ts +45 -0
  27. package/dist/gates/hallucinated-imports/js-resolver.js +320 -0
  28. package/dist/gates/hallucinated-imports/manifest-discovery.d.ts +28 -0
  29. package/dist/gates/hallucinated-imports/manifest-discovery.js +114 -0
  30. package/dist/gates/hallucinated-imports/python-resolver.d.ts +24 -0
  31. package/dist/gates/hallucinated-imports/python-resolver.js +306 -0
  32. package/dist/gates/hallucinated-imports-lang.d.ts +2 -2
  33. package/dist/gates/hallucinated-imports-lang.js +269 -34
  34. package/dist/gates/hallucinated-imports.test.js +1 -2
  35. package/dist/gates/inconsistent-error-handling.d.ts +0 -5
  36. package/dist/gates/inconsistent-error-handling.js +15 -144
  37. package/dist/gates/language-adapters/csharp-adapter.d.ts +16 -0
  38. package/dist/gates/language-adapters/csharp-adapter.js +211 -0
  39. package/dist/gates/language-adapters/go-adapter.d.ts +26 -0
  40. package/dist/gates/language-adapters/go-adapter.js +195 -0
  41. package/dist/gates/language-adapters/index.d.ts +15 -0
  42. package/dist/gates/language-adapters/index.js +16 -0
  43. package/dist/gates/language-adapters/java-adapter.d.ts +16 -0
  44. package/dist/gates/language-adapters/java-adapter.js +237 -0
  45. package/dist/gates/language-adapters/js-adapter.d.ts +26 -0
  46. package/dist/gates/language-adapters/js-adapter.js +279 -0
  47. package/dist/gates/language-adapters/python-adapter.d.ts +25 -0
  48. package/dist/gates/language-adapters/python-adapter.js +183 -0
  49. package/dist/gates/language-adapters/registry.d.ts +26 -0
  50. package/dist/gates/language-adapters/registry.js +65 -0
  51. package/dist/gates/language-adapters/ruby-adapter.d.ts +25 -0
  52. package/dist/gates/language-adapters/ruby-adapter.js +217 -0
  53. package/dist/gates/language-adapters/rust-adapter.d.ts +27 -0
  54. package/dist/gates/language-adapters/rust-adapter.js +235 -0
  55. package/dist/gates/language-adapters/types.d.ts +60 -0
  56. package/dist/gates/language-adapters/types.js +22 -0
  57. package/dist/gates/logic-drift-extractors.d.ts +15 -0
  58. package/dist/gates/logic-drift-extractors.js +34 -0
  59. package/dist/gates/logic-drift.d.ts +0 -30
  60. package/dist/gates/logic-drift.js +39 -129
  61. package/dist/gates/phantom-apis.d.ts +0 -2
  62. package/dist/gates/phantom-apis.js +49 -20
  63. package/dist/gates/promise-safety.d.ts +0 -1
  64. package/dist/gates/promise-safety.js +14 -2
  65. package/dist/gates/runner.js +51 -22
  66. package/dist/gates/security-patterns-data.d.ts +14 -0
  67. package/dist/gates/security-patterns-data.js +235 -0
  68. package/dist/gates/security-patterns.d.ts +17 -3
  69. package/dist/gates/security-patterns.js +80 -211
  70. package/dist/gates/side-effect-analysis/categorizer.d.ts +32 -0
  71. package/dist/gates/side-effect-analysis/categorizer.js +83 -0
  72. package/dist/gates/{side-effect-analysis.d.ts → side-effect-analysis/index.d.ts} +3 -5
  73. package/dist/gates/{side-effect-analysis.js → side-effect-analysis/index.js} +33 -45
  74. package/dist/gates/side-effect-analysis/scope-tracker.d.ts +37 -0
  75. package/dist/gates/side-effect-analysis/scope-tracker.js +40 -0
  76. package/dist/gates/side-effect-helpers/index.d.ts +4 -0
  77. package/dist/gates/side-effect-helpers/index.js +4 -0
  78. package/dist/gates/side-effect-helpers/pattern-detection.d.ts +123 -0
  79. package/dist/gates/{side-effect-helpers.js → side-effect-helpers/pattern-detection.js} +22 -468
  80. package/dist/gates/side-effect-helpers/resource-tracking.d.ts +80 -0
  81. package/dist/gates/side-effect-helpers/resource-tracking.js +281 -0
  82. package/dist/gates/side-effect-helpers/scope-analysis.d.ts +21 -0
  83. package/dist/gates/side-effect-helpers/scope-analysis.js +146 -0
  84. package/dist/gates/side-effect-helpers/types.d.ts +38 -0
  85. package/dist/gates/side-effect-helpers/types.js +41 -0
  86. package/dist/gates/side-effect-rules.d.ts +0 -1
  87. package/dist/gates/side-effect-rules.js +0 -1
  88. package/dist/gates/style-drift-rules.d.ts +86 -0
  89. package/dist/gates/style-drift-rules.js +103 -0
  90. package/dist/gates/style-drift.d.ts +7 -16
  91. package/dist/gates/style-drift.js +101 -119
  92. package/dist/gates/test-quality-matchers.d.ts +53 -0
  93. package/dist/gates/test-quality-matchers.js +86 -0
  94. package/dist/gates/test-quality.d.ts +0 -3
  95. package/dist/gates/test-quality.js +47 -44
  96. package/dist/hooks/checker.d.ts +0 -1
  97. package/dist/hooks/checker.js +1 -3
  98. package/dist/hooks/dlp-templates.d.ts +0 -1
  99. package/dist/hooks/dlp-templates.js +0 -4
  100. package/dist/hooks/index.d.ts +0 -2
  101. package/dist/hooks/index.js +0 -2
  102. package/dist/hooks/input-validator.d.ts +0 -1
  103. package/dist/hooks/input-validator.js +0 -1
  104. package/dist/hooks/input-validator.test.js +0 -1
  105. package/dist/hooks/standalone-checker.d.ts +0 -1
  106. package/dist/hooks/standalone-checker.js +0 -1
  107. package/dist/hooks/standalone-dlp-checker.d.ts +0 -1
  108. package/dist/hooks/standalone-dlp-checker.js +0 -1
  109. package/dist/hooks/templates.d.ts +6 -1
  110. package/dist/hooks/templates.js +6 -1
  111. package/dist/hooks/types.d.ts +1 -2
  112. package/dist/hooks/types.js +1 -1
  113. package/dist/index.d.ts +1 -1
  114. package/dist/index.js +1 -1
  115. package/dist/services/adaptive-thresholds.d.ts +0 -2
  116. package/dist/services/adaptive-thresholds.js +0 -2
  117. package/dist/services/filesystem-cache.d.ts +0 -1
  118. package/dist/services/filesystem-cache.js +0 -1
  119. package/dist/services/score-history.d.ts +0 -1
  120. package/dist/services/score-history.js +0 -1
  121. package/dist/services/temporal-drift.d.ts +1 -2
  122. package/dist/services/temporal-drift.js +7 -8
  123. package/dist/storage/db.d.ts +23 -7
  124. package/dist/storage/db.js +116 -55
  125. package/dist/storage/findings.d.ts +4 -3
  126. package/dist/storage/findings.js +13 -20
  127. package/dist/storage/local-memory.d.ts +4 -4
  128. package/dist/storage/local-memory.js +20 -22
  129. package/dist/storage/patterns.d.ts +5 -5
  130. package/dist/storage/patterns.js +20 -26
  131. package/dist/storage/scans.d.ts +6 -6
  132. package/dist/storage/scans.js +12 -21
  133. package/dist/types/index.d.ts +1 -0
  134. package/dist/utils/scanner.js +1 -1
  135. package/package.json +7 -8
  136. package/dist/gates/duplication-drift.d.ts +0 -128
  137. package/dist/gates/duplication-drift.js +0 -585
  138. package/dist/gates/hallucinated-imports.js +0 -641
  139. package/dist/gates/side-effect-helpers.d.ts +0 -260
@@ -0,0 +1,281 @@
1
+ import { stripStrings } from './types.js';
2
+ import { findBlockEndBrace } from './scope-analysis.js';
3
+ // ═══════════════════════════════════════════════════════════════════
4
+ // VARIABLE BINDING — Track resource creation → cleanup pairs
5
+ // ═══════════════════════════════════════════════════════════════════
6
+ /**
7
+ * Extract the variable name from an assignment.
8
+ * "const timer = setInterval(...)" → "timer"
9
+ * "let fd = fs.open(...)" → "fd"
10
+ * "self.watcher = chokidar.watch()" → "self.watcher"
11
+ * "timer := time.NewTicker(...)" → "timer" (Go)
12
+ *
13
+ * Returns null if the call result is NOT stored in a variable.
14
+ */
15
+ export function extractVariableBinding(line, lang) {
16
+ const stripped = stripStrings(line).trim();
17
+ if (lang === 'go') {
18
+ // Go: `ticker := time.NewTicker(...)` or `ticker, _ := ...`
19
+ const goMatch = stripped.match(/^(\w+)(?:\s*,\s*\w+)*\s*:?=\s*/);
20
+ if (goMatch)
21
+ return goMatch[1];
22
+ return null;
23
+ }
24
+ if (lang === 'py') {
25
+ // Python: `timer = threading.Timer(...)` or `self.timer = ...`
26
+ const pyMatch = stripped.match(/^((?:self\.)?[\w.]+)\s*=\s*(?!==)/);
27
+ if (pyMatch)
28
+ return pyMatch[1];
29
+ return null;
30
+ }
31
+ if (lang === 'rb') {
32
+ // Ruby: `@watcher = Listen.to(...)` or `watcher = ...`
33
+ const rbMatch = stripped.match(/^(@?\w+)\s*=\s*/);
34
+ if (rbMatch)
35
+ return rbMatch[1];
36
+ return null;
37
+ }
38
+ // JS/TS/Java/C#/Rust: `const x = ...`, `let x = ...`, `var x = ...`, `auto x = ...`
39
+ const jsMatch = stripped.match(/^(?:const|let|var|final|auto|val)\s+(\w+)\s*=\s*/);
40
+ if (jsMatch)
41
+ return jsMatch[1];
42
+ // Member assignment: `this.timer = ...`, `self.timer = ...`
43
+ const memberMatch = stripped.match(/^(?:this|self)\.([\w]+)\s*=\s*/);
44
+ if (memberMatch)
45
+ return memberMatch[1];
46
+ // Simple assignment: `timer = ...`
47
+ const simpleMatch = stripped.match(/^(\w+)\s*=\s*(?!==)/);
48
+ if (simpleMatch) {
49
+ // Exclude control flow keywords
50
+ const name = simpleMatch[1];
51
+ if (['if', 'for', 'while', 'switch', 'return', 'throw'].includes(name))
52
+ return null;
53
+ return name;
54
+ }
55
+ return null;
56
+ }
57
+ /**
58
+ * Check if a specific variable is used in a cleanup call within a scope.
59
+ *
60
+ * Unlike the naive "does clearInterval exist in the file?", this checks:
61
+ * 1. The cleanup function references the specific variable
62
+ * 2. The cleanup is within the correct scope (same function or cleanup callback)
63
+ *
64
+ * Example: for variable "timer" and cleanup patterns [/clearInterval/],
65
+ * matches: `clearInterval(timer)`, `clearInterval(this.timer)`, `timer.close()`
66
+ */
67
+ export function hasCleanupForVariable(lines, varName, scopeStart, scopeEnd, cleanupPatterns, lang) {
68
+ const scope = lines.slice(scopeStart, scopeEnd);
69
+ for (let i = 0; i < scope.length; i++) {
70
+ const stripped = stripStrings(scope[i]);
71
+ // Check cleanup patterns that reference the specific variable
72
+ for (const pat of cleanupPatterns) {
73
+ if (!pat.test(stripped))
74
+ continue;
75
+ // The cleanup call should reference our variable
76
+ const escapedVar = varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
77
+ const varRef = new RegExp(`\\b${escapedVar}\\b`);
78
+ if (varRef.test(stripped))
79
+ return true;
80
+ // Also check method calls on the variable: timer.close(), timer.stop()
81
+ // The pattern might match a generic .close() — check if it's on our var
82
+ }
83
+ // Direct method cleanup on the variable: varName.close(), varName.Stop()
84
+ const escapedVar = varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
85
+ const methodCleanup = new RegExp(`\\b${escapedVar}\\.(?:close|stop|destroy|kill|terminate|dispose|cancel|shutdown|unsubscribe|disconnect|end|release|Clear|Stop|Dispose|Close|Cancel)\\s*\\(`);
86
+ if (methodCleanup.test(stripped))
87
+ return true;
88
+ }
89
+ return false;
90
+ }
91
+ /**
92
+ * Check if a line is inside a cleanup/teardown context.
93
+ *
94
+ * Cleanup contexts where resource cleanup is expected:
95
+ * - JS/TS: useEffect return function, componentWillUnmount, beforeDestroy, ngOnDestroy, dispose()
96
+ * - Python: __del__, __exit__, close(), cleanup(), teardown
97
+ * - Go: defer statement
98
+ * - Java: finally block, close() method, @PreDestroy
99
+ * - C#: Dispose(), using block, finalizer
100
+ * - Ruby: ensure block, at_exit
101
+ */
102
+ export function isInsideCleanupContext(lines, lineIdx, lang) {
103
+ // Scan backwards up to 30 lines for cleanup context markers
104
+ for (let j = lineIdx; j >= Math.max(0, lineIdx - 30); j--) {
105
+ const trimmed = lines[j].trim();
106
+ switch (lang) {
107
+ case 'js':
108
+ case 'ts':
109
+ // React useEffect cleanup: `return () => { cleanup }`
110
+ if (/\breturn\s+(?:\(\)\s*=>|function\s*\()/.test(trimmed))
111
+ return true;
112
+ // Lifecycle: componentWillUnmount, ngOnDestroy, beforeDestroy
113
+ if (/\b(?:componentWillUnmount|ngOnDestroy|beforeDestroy|dispose)\s*\(/.test(trimmed))
114
+ return true;
115
+ // Event: 'beforeunload', 'unload'
116
+ if (/['"](?:beforeunload|unload)['"]\s*,/.test(trimmed))
117
+ return true;
118
+ break;
119
+ case 'py':
120
+ if (/\bdef\s+(?:__del__|__exit__|close|cleanup|teardown|dispose)\s*\(/.test(trimmed))
121
+ return true;
122
+ if (/\bfinally\s*:/.test(trimmed))
123
+ return true;
124
+ break;
125
+ case 'go':
126
+ if (/\bdefer\b/.test(trimmed))
127
+ return true;
128
+ break;
129
+ case 'java':
130
+ if (/\bfinally\s*\{/.test(trimmed))
131
+ return true;
132
+ if (/\b(?:close|destroy|cleanup|dispose)\s*\(/.test(trimmed))
133
+ return true;
134
+ if (/@PreDestroy/.test(trimmed))
135
+ return true;
136
+ break;
137
+ case 'cs':
138
+ if (/\bDispose\s*\(/.test(trimmed))
139
+ return true;
140
+ if (/\busing\s*\(/.test(trimmed))
141
+ return true;
142
+ if (/~\w+\s*\(/.test(trimmed))
143
+ return true; // finalizer
144
+ break;
145
+ case 'rb':
146
+ if (/\bensure\b/.test(trimmed))
147
+ return true;
148
+ break;
149
+ case 'rs':
150
+ if (/\bimpl\s+Drop\b/.test(trimmed))
151
+ return true;
152
+ break;
153
+ }
154
+ // Stop at function boundaries
155
+ if (isFunctionBoundary(trimmed, lang))
156
+ break;
157
+ }
158
+ return false;
159
+ }
160
+ // ═══════════════════════════════════════════════════════════════════
161
+ // FRAMEWORK-AWARE PATTERNS — Detect safe idioms per ecosystem
162
+ // ═══════════════════════════════════════════════════════════════════
163
+ /**
164
+ * Check if a timer/resource creation is inside a React useEffect
165
+ * that returns a cleanup function.
166
+ *
167
+ * Pattern:
168
+ * useEffect(() => {
169
+ * const timer = setInterval(...) ← creation
170
+ * return () => clearInterval(timer) ← cleanup
171
+ * }, [deps])
172
+ */
173
+ export function isInUseEffectWithCleanup(lines, lineIdx) {
174
+ // Walk backwards to find useEffect
175
+ let braceDepth = 0;
176
+ for (let j = lineIdx; j >= Math.max(0, lineIdx - 30); j--) {
177
+ const stripped = stripStrings(lines[j]);
178
+ for (const ch of stripped) {
179
+ if (ch === '}')
180
+ braceDepth++;
181
+ if (ch === '{')
182
+ braceDepth--;
183
+ }
184
+ if (/\buseEffect\s*\(/.test(stripped) && braceDepth <= 0) {
185
+ // Found enclosing useEffect — now check if it has a return () => ...
186
+ const effectEnd = findBlockEndBrace(lines, j);
187
+ const effectBody = lines.slice(j, effectEnd).join('\n');
188
+ // Look for cleanup return: `return () =>` or `return function`
189
+ if (/\breturn\s+(?:\(\)\s*=>|function\s*\()/.test(effectBody)) {
190
+ return true;
191
+ }
192
+ return false;
193
+ }
194
+ }
195
+ return false;
196
+ }
197
+ /**
198
+ * Check if a Go resource open is immediately followed by defer close.
199
+ *
200
+ * Idiomatic Go:
201
+ * f, err := os.Open(path)
202
+ * if err != nil { return err }
203
+ * defer f.Close()
204
+ */
205
+ export function hasGoDefer(lines, openLine, varName) {
206
+ // Check lines between open and open+5 for defer using the variable
207
+ const escaped = varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
208
+ const deferPat = new RegExp(`\\bdefer\\s+${escaped}\\.`);
209
+ for (let j = openLine + 1; j < Math.min(lines.length, openLine + 6); j++) {
210
+ if (deferPat.test(lines[j]))
211
+ return true;
212
+ // Also match: defer func() { varName.Close() }()
213
+ if (/\bdefer\s+func\s*\(\)/.test(lines[j])) {
214
+ const endDefer = findBlockEndBrace(lines, j);
215
+ const body = lines.slice(j, endDefer).join('\n');
216
+ if (new RegExp(`\\b${escaped}\\.`).test(body))
217
+ return true;
218
+ }
219
+ }
220
+ return false;
221
+ }
222
+ /**
223
+ * Check if a Python open() is inside a `with` statement (context manager).
224
+ */
225
+ export function isPythonWithStatement(line) {
226
+ return /\bwith\s+/.test(stripStrings(line));
227
+ }
228
+ /**
229
+ * Check if a Java resource open is inside try-with-resources.
230
+ * Pattern: try (var x = new FileStream(...)) { ... }
231
+ */
232
+ export function isJavaTryWithResources(lines, lineIdx) {
233
+ for (let j = lineIdx; j >= Math.max(0, lineIdx - 3); j--) {
234
+ if (/\btry\s*\(/.test(stripStrings(lines[j])))
235
+ return true;
236
+ }
237
+ return false;
238
+ }
239
+ /**
240
+ * Check if a C# resource is inside a using statement/declaration.
241
+ * Patterns: `using (var x = ...)` or `using var x = ...` (C# 8+)
242
+ */
243
+ export function isCSharpUsing(line) {
244
+ const stripped = stripStrings(line);
245
+ return /\busing\s*\(/.test(stripped) || /\busing\s+(?:var|await)\b/.test(stripped);
246
+ }
247
+ /**
248
+ * Check if a Ruby File.open uses block form (auto-closes).
249
+ * Pattern: File.open(path) do |f| ... end
250
+ * File.open(path) { |f| ... }
251
+ */
252
+ export function isRubyBlockForm(line) {
253
+ return /\bdo\s*\|/.test(line) || /\{\s*\|/.test(line);
254
+ }
255
+ /**
256
+ * Check if a Rust resource is automatically dropped (RAII).
257
+ * In Rust, all resources are dropped when they go out of scope,
258
+ * so we only flag resources in unsafe blocks or static/global context.
259
+ */
260
+ export function isRustAutoDropped(lines, lineIdx) {
261
+ // Check if inside unsafe block (manual memory management)
262
+ for (let j = lineIdx; j >= Math.max(0, lineIdx - 20); j--) {
263
+ if (/\bunsafe\s*\{/.test(lines[j]))
264
+ return false; // Not auto-dropped in unsafe
265
+ }
266
+ return true; // Normal Rust = RAII applies
267
+ }
268
+ function isFunctionBoundary(trimmed, lang) {
269
+ switch (lang) {
270
+ case 'py':
271
+ return /^(?:def|class|async\s+def)\s/.test(trimmed);
272
+ case 'go':
273
+ return /^func\s/.test(trimmed);
274
+ case 'rb':
275
+ return /^(?:def|class|module)\s/.test(trimmed);
276
+ case 'rs':
277
+ return /^(?:fn|impl|pub\s+fn|pub\s+async\s+fn)\s/.test(trimmed);
278
+ default:
279
+ return /^(?:export\s+)?(?:async\s+)?(?:function|class)\s/.test(trimmed);
280
+ }
281
+ }
@@ -0,0 +1,21 @@
1
+ import { SideEffectLang } from './types.js';
2
+ /**
3
+ * Find the enclosing function scope for a given line.
4
+ * Returns { start, end } of the function body.
5
+ * For module-level code, returns { start: 0, end: lines.length }.
6
+ *
7
+ * Follows promise-safety's approach of backward scanning with brace tracking.
8
+ */
9
+ export declare function findEnclosingFunction(lines: string[], lineIdx: number, lang: SideEffectLang): {
10
+ start: number;
11
+ end: number;
12
+ };
13
+ /**
14
+ * Find the end of a brace-delimited block starting at `start`.
15
+ */
16
+ export declare function findBlockEndBrace(lines: string[], start: number): number;
17
+ /**
18
+ * Find the end of an indentation-delimited block (Python).
19
+ */
20
+ export declare function findBlockEndIndent(lines: string[], start: number): number;
21
+ export declare function getFunctionPatterns(lang: SideEffectLang): RegExp[];
@@ -0,0 +1,146 @@
1
+ import { stripStrings } from './types.js';
2
+ // ═══════════════════════════════════════════════════════════════════
3
+ // SCOPE ANALYSIS — Find function/block boundaries
4
+ // ═══════════════════════════════════════════════════════════════════
5
+ /**
6
+ * Find the enclosing function scope for a given line.
7
+ * Returns { start, end } of the function body.
8
+ * For module-level code, returns { start: 0, end: lines.length }.
9
+ *
10
+ * Follows promise-safety's approach of backward scanning with brace tracking.
11
+ */
12
+ export function findEnclosingFunction(lines, lineIdx, lang) {
13
+ if (lang === 'py')
14
+ return findEnclosingFunctionPython(lines, lineIdx);
15
+ if (lang === 'rb')
16
+ return findEnclosingFunctionRuby(lines, lineIdx);
17
+ return findEnclosingFunctionBrace(lines, lineIdx, lang);
18
+ }
19
+ function findEnclosingFunctionBrace(lines, lineIdx, lang) {
20
+ // Walk backwards tracking brace depth to find function definition
21
+ let braceDepth = 0;
22
+ const funcPatterns = getFunctionPatterns(lang);
23
+ for (let j = lineIdx; j >= Math.max(0, lineIdx - 200); j--) {
24
+ const stripped = stripStrings(lines[j]);
25
+ // Count braces (reverse direction: } increases, { decreases)
26
+ for (const ch of stripped) {
27
+ if (ch === '}')
28
+ braceDepth++;
29
+ if (ch === '{')
30
+ braceDepth--;
31
+ }
32
+ // If braceDepth < 0, we've exited the enclosing block going backwards
33
+ if (braceDepth < 0) {
34
+ // Check if this line is a function definition
35
+ for (const pat of funcPatterns) {
36
+ if (pat.test(stripped)) {
37
+ const end = findBlockEndBrace(lines, j);
38
+ return { start: j, end };
39
+ }
40
+ }
41
+ // It's some other block (if/for/etc), keep looking
42
+ braceDepth = 0;
43
+ }
44
+ }
45
+ // Module level
46
+ return { start: 0, end: lines.length };
47
+ }
48
+ function findEnclosingFunctionPython(lines, lineIdx) {
49
+ const lineIndent = lines[lineIdx].length - lines[lineIdx].trimStart().length;
50
+ for (let j = lineIdx - 1; j >= 0; j--) {
51
+ const trimmed = lines[j].trim();
52
+ if (trimmed === '' || trimmed.startsWith('#'))
53
+ continue;
54
+ const indent = lines[j].length - lines[j].trimStart().length;
55
+ if (indent < lineIndent && /^\s*(?:async\s+)?def\s+\w+/.test(lines[j])) {
56
+ const end = findBlockEndIndent(lines, j);
57
+ return { start: j, end };
58
+ }
59
+ if (indent === 0 && /^\s*(?:class|def|async\s+def)\s/.test(lines[j])) {
60
+ const end = findBlockEndIndent(lines, j);
61
+ return { start: j, end };
62
+ }
63
+ }
64
+ return { start: 0, end: lines.length };
65
+ }
66
+ function findEnclosingFunctionRuby(lines, lineIdx) {
67
+ for (let j = lineIdx - 1; j >= Math.max(0, lineIdx - 100); j--) {
68
+ const trimmed = lines[j].trim();
69
+ if (/^def\s+\w+/.test(trimmed)) {
70
+ const end = findBlockEndRuby(lines, j);
71
+ return { start: j, end };
72
+ }
73
+ }
74
+ return { start: 0, end: lines.length };
75
+ }
76
+ /**
77
+ * Find the end of a brace-delimited block starting at `start`.
78
+ */
79
+ export function findBlockEndBrace(lines, start) {
80
+ let braces = 0;
81
+ let started = false;
82
+ const maxScan = Math.min(lines.length, start + 300);
83
+ for (let j = start; j < maxScan; j++) {
84
+ const stripped = stripStrings(lines[j]);
85
+ for (const ch of stripped) {
86
+ if (ch === '{') {
87
+ braces++;
88
+ started = true;
89
+ }
90
+ if (ch === '}')
91
+ braces--;
92
+ }
93
+ if (started && braces <= 0)
94
+ return j + 1;
95
+ }
96
+ return maxScan;
97
+ }
98
+ /**
99
+ * Find the end of an indentation-delimited block (Python).
100
+ */
101
+ export function findBlockEndIndent(lines, start) {
102
+ const baseIndent = lines[start].length - lines[start].trimStart().length;
103
+ const maxScan = Math.min(lines.length, start + 300);
104
+ for (let j = start + 1; j < maxScan; j++) {
105
+ const trimmed = lines[j].trim();
106
+ if (trimmed === '' || trimmed.startsWith('#'))
107
+ continue;
108
+ const indent = lines[j].length - lines[j].trimStart().length;
109
+ if (indent <= baseIndent)
110
+ return j;
111
+ }
112
+ return maxScan;
113
+ }
114
+ function findBlockEndRuby(lines, start) {
115
+ let depth = 0;
116
+ const maxScan = Math.min(lines.length, start + 300);
117
+ const openers = /\b(?:def|do|class|module|if|unless|while|until|for|begin|case)\b/;
118
+ for (let j = start; j < maxScan; j++) {
119
+ const trimmed = lines[j].trim();
120
+ if (openers.test(trimmed))
121
+ depth++;
122
+ if (/^\s*end\b/.test(trimmed)) {
123
+ depth--;
124
+ if (depth <= 0)
125
+ return j + 1;
126
+ }
127
+ }
128
+ return maxScan;
129
+ }
130
+ export function getFunctionPatterns(lang) {
131
+ switch (lang) {
132
+ case 'go':
133
+ return [/\bfunc\s+/];
134
+ case 'rs':
135
+ return [/\bfn\s+\w+/];
136
+ case 'java':
137
+ case 'cs':
138
+ return [/(?:public|private|protected|static|async|void|int|string|Task|var)\s+\w+\s*\(/];
139
+ default: // js, ts
140
+ return [
141
+ /(?:export\s+)?(?:async\s+)?function\s+\w+/,
142
+ /(?:const|let|var)\s+\w+\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/,
143
+ /\w+\s*\([^)]*\)\s*\{/, // method shorthand
144
+ ];
145
+ }
146
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Side-Effect Analysis Helpers
3
+ *
4
+ * Context-aware utilities for smart side-effect detection.
5
+ * Follows the same architectural patterns as promise-safety-helpers.ts:
6
+ * - Scope-aware analysis (brace/indent tracking)
7
+ * - Variable binding tracking (pair resource creation with cleanup)
8
+ * - Framework detection (React useEffect, Go defer, Python with, etc.)
9
+ * - Path overlap analysis (circular file watcher detection)
10
+ *
11
+ * These helpers make side-effect detection SMART — instead of asking
12
+ * "does clearInterval exist anywhere in the file?", we ask
13
+ * "is the specific timer variable cleaned up in the right scope?"
14
+ */
15
+ export type SideEffectLang = 'js' | 'ts' | 'py' | 'go' | 'rs' | 'cs' | 'java' | 'rb';
16
+ export interface SideEffectViolation {
17
+ rule: string;
18
+ severity: 'critical' | 'high' | 'medium' | 'low';
19
+ file: string;
20
+ line: number;
21
+ match: string;
22
+ description: string;
23
+ hint: string;
24
+ }
25
+ /**
26
+ * Tracks a resource creation and its expected cleanup.
27
+ * E.g.: { varName: 'timer', createLine: 5, createCall: 'setInterval' }
28
+ */
29
+ export interface ResourceBinding {
30
+ varName: string | null;
31
+ createLine: number;
32
+ createCall: string;
33
+ scopeStart: number;
34
+ scopeEnd: number;
35
+ }
36
+ export declare const LANG_MAP: Record<string, SideEffectLang>;
37
+ export declare const FILE_GLOBS: string[];
38
+ export declare function stripStrings(line: string): string;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Side-Effect Analysis Helpers
3
+ *
4
+ * Context-aware utilities for smart side-effect detection.
5
+ * Follows the same architectural patterns as promise-safety-helpers.ts:
6
+ * - Scope-aware analysis (brace/indent tracking)
7
+ * - Variable binding tracking (pair resource creation with cleanup)
8
+ * - Framework detection (React useEffect, Go defer, Python with, etc.)
9
+ * - Path overlap analysis (circular file watcher detection)
10
+ *
11
+ * These helpers make side-effect detection SMART — instead of asking
12
+ * "does clearInterval exist anywhere in the file?", we ask
13
+ * "is the specific timer variable cleaned up in the right scope?"
14
+ */
15
+ // ── Language detection ──
16
+ export const LANG_MAP = {
17
+ '.ts': 'ts', '.tsx': 'ts', '.mts': 'ts',
18
+ '.js': 'js', '.jsx': 'js', '.mjs': 'js', '.cjs': 'js',
19
+ '.py': 'py',
20
+ '.go': 'go',
21
+ '.rs': 'rs',
22
+ '.cs': 'cs',
23
+ '.java': 'java',
24
+ '.rb': 'rb',
25
+ };
26
+ export const FILE_GLOBS = [
27
+ '**/*.{ts,tsx,mts,js,jsx,mjs,cjs}',
28
+ '**/*.py',
29
+ '**/*.go',
30
+ '**/*.rs',
31
+ '**/*.cs',
32
+ '**/*.java',
33
+ '**/*.rb',
34
+ ];
35
+ // ── Strip string contents to avoid false positives in regex matching ──
36
+ export function stripStrings(line) {
37
+ return line
38
+ .replace(/`[^`]*`/g, '""')
39
+ .replace(/"(?:[^"\\]|\\.)*"/g, '""')
40
+ .replace(/'(?:[^'\\]|\\.)*'/g, '""');
41
+ }
@@ -10,7 +10,6 @@
10
10
  * - a check function that verifies context (surrounding lines)
11
11
  * - severity and description
12
12
  *
13
- * @since v4.3.0
14
13
  */
15
14
  export type SideEffectLang = 'js' | 'ts' | 'py' | 'go' | 'rs' | 'cs' | 'java' | 'rb';
16
15
  export interface SideEffectViolation {
@@ -10,7 +10,6 @@
10
10
  * - a check function that verifies context (surrounding lines)
11
11
  * - severity and description
12
12
  *
13
- * @since v4.3.0
14
13
  */
15
14
  // ── Language detection ──
16
15
  export const LANG_MAP = {
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Style Drift Detection Gate — Naming Convention Rules and Regexes
3
+ *
4
+ * Contains per-language naming convention rules and naming pattern regexes.
5
+ */
6
+ /**
7
+ * Casing classification rules
8
+ */
9
+ export declare function classifyCasing(name: string): 'camelCase' | 'snake_case' | 'PascalCase' | 'SCREAMING_SNAKE' | null;
10
+ /**
11
+ * Function name pattern for JavaScript
12
+ */
13
+ export declare const JS_FUNCTION_PATTERN: RegExp;
14
+ /**
15
+ * Method definition pattern for all languages
16
+ */
17
+ export declare const METHOD_PATTERN: RegExp;
18
+ /**
19
+ * Arrow function assignment pattern
20
+ */
21
+ export declare const ARROW_FUNCTION_PATTERN: RegExp;
22
+ /**
23
+ * Variable declaration pattern (non-function)
24
+ */
25
+ export declare const VAR_DECLARATION_PATTERN: RegExp;
26
+ /**
27
+ * Python function pattern
28
+ */
29
+ export declare const PYTHON_FUNCTION_PATTERN: RegExp;
30
+ /**
31
+ * Python variable pattern
32
+ */
33
+ export declare const PYTHON_VAR_PATTERN: RegExp;
34
+ /**
35
+ * Go function pattern
36
+ */
37
+ export declare const GO_FUNCTION_PATTERN: RegExp;
38
+ /**
39
+ * Go variable pattern
40
+ */
41
+ export declare const GO_VAR_PATTERN: RegExp;
42
+ /**
43
+ * Rust function pattern
44
+ */
45
+ export declare const RUST_FUNCTION_PATTERN: RegExp;
46
+ /**
47
+ * Rust variable pattern
48
+ */
49
+ export declare const RUST_VAR_PATTERN: RegExp;
50
+ /**
51
+ * Ruby method pattern
52
+ */
53
+ export declare const RUBY_METHOD_PATTERN: RegExp;
54
+ /**
55
+ * Ruby variable pattern
56
+ */
57
+ export declare const RUBY_VAR_PATTERN: RegExp;
58
+ /**
59
+ * Java/Kotlin/C# method pattern
60
+ */
61
+ export declare const JAVA_METHOD_PATTERN: RegExp;
62
+ /**
63
+ * Java/Kotlin/C# variable pattern
64
+ */
65
+ export declare const JAVA_VAR_PATTERN: RegExp;
66
+ /**
67
+ * Error handling patterns
68
+ */
69
+ export declare const TRY_CATCH_PATTERN: RegExp;
70
+ export declare const CATCH_PATTERN: RegExp;
71
+ export declare const RESULT_TYPE_PATTERN: RegExp;
72
+ /**
73
+ * Import style patterns
74
+ */
75
+ export declare const NAMED_IMPORT_PATTERN: RegExp;
76
+ export declare const WILDCARD_IMPORT_PATTERN: RegExp;
77
+ export declare const SIDE_EFFECT_IMPORT_PATTERN: RegExp;
78
+ export declare const DEFAULT_IMPORT_PATTERN: RegExp;
79
+ /**
80
+ * Quote style detection
81
+ */
82
+ export declare function countQuotes(line: string): {
83
+ single: number;
84
+ double: number;
85
+ backtick: number;
86
+ };