@rigour-labs/core 5.0.1 → 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 +0 -2
  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 +0 -1
  110. package/dist/hooks/templates.js +0 -1
  111. package/dist/hooks/types.d.ts +0 -1
  112. package/dist/hooks/types.js +0 -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,183 @@
1
+ /**
2
+ * Python Language Adapter
3
+ *
4
+ * Handles: .py, .pyw
5
+ *
6
+ * Regex-based static analysis for Python code patterns.
7
+ */
8
+ import { classifyCasing } from './types.js';
9
+ class PythonAdapter {
10
+ id = 'python';
11
+ name = 'Python';
12
+ extensions = ['.py', '.pyw'];
13
+ extractFunctions(source, filePath) {
14
+ const functions = [];
15
+ const lines = source.split('\n');
16
+ for (let i = 0; i < lines.length; i++) {
17
+ const line = lines[i];
18
+ // def foo(...): or async def foo(...):
19
+ const match = line.match(/^\s*(async\s+)?def\s+(\w+)\s*\(/);
20
+ if (match) {
21
+ const isAsync = !!match[1];
22
+ const name = match[2];
23
+ const body = this.extractIndentBody(lines, i);
24
+ const endLine = i + body.length;
25
+ const isExported = false; // Python doesn't have explicit exports in the traditional sense
26
+ functions.push({
27
+ name, startLine: i + 1, endLine: endLine + 1,
28
+ body: body.join('\n'), isAsync, isExported,
29
+ });
30
+ }
31
+ }
32
+ return functions;
33
+ }
34
+ extractImports(source) {
35
+ const imports = [];
36
+ const lines = source.split('\n');
37
+ for (let i = 0; i < lines.length; i++) {
38
+ const line = lines[i];
39
+ // import x or import x as y
40
+ let match = line.match(/^\s*import\s+([\w.]+)(?:\s+as\s+(\w+))?/);
41
+ if (match) {
42
+ const names = match[2] ? [match[2]] : [match[1]];
43
+ imports.push({
44
+ module: match[1], names, line: i + 1, isDynamic: false,
45
+ });
46
+ }
47
+ // from x import y, z
48
+ match = line.match(/^\s*from\s+([\w.]+)\s+import\s+(.+)/);
49
+ if (match) {
50
+ const names = match[2]
51
+ .split(',')
52
+ .map(s => s.trim())
53
+ .filter(s => s && s !== '*');
54
+ imports.push({
55
+ module: match[1], names: names.length > 0 ? names : [], line: i + 1, isDynamic: false,
56
+ });
57
+ }
58
+ }
59
+ return imports;
60
+ }
61
+ extractErrorHandlers(source) {
62
+ const handlers = [];
63
+ const lines = source.split('\n');
64
+ for (let i = 0; i < lines.length; i++) {
65
+ const line = lines[i];
66
+ // except ExceptionType as e: OR bare except:
67
+ const match = line.match(/^\s*except\s*(?:(\w[\w.]*)(?:\s+as\s+(\w+))?)?\s*:/);
68
+ if (match) {
69
+ const body = this.extractIndentBody(lines, i);
70
+ const exceptionType = match[1] || null; // null = bare except
71
+ const strategy = this.classifyPythonErrorStrategy(body.join('\n'), exceptionType);
72
+ handlers.push({
73
+ type: exceptionType ? 'except' : 'bare-except',
74
+ strategy,
75
+ startLine: i + 1, body: body.join('\n'),
76
+ });
77
+ }
78
+ }
79
+ return handlers;
80
+ }
81
+ extractNamingPatterns(source) {
82
+ const patterns = [];
83
+ const lines = source.split('\n');
84
+ for (let i = 0; i < lines.length; i++) {
85
+ const line = lines[i];
86
+ // def foo(...):
87
+ let match = line.match(/^\s*def\s+(\w+)\s*\(/);
88
+ if (match) {
89
+ patterns.push({
90
+ name: match[1], kind: 'function',
91
+ convention: classifyCasing(match[1]),
92
+ });
93
+ }
94
+ // class Foo:
95
+ match = line.match(/^\s*class\s+(\w+)/);
96
+ if (match) {
97
+ patterns.push({
98
+ name: match[1], kind: 'class',
99
+ convention: classifyCasing(match[1]),
100
+ });
101
+ }
102
+ // foo = ... (variable assignment at module level)
103
+ match = line.match(/^(\w+)\s*=/);
104
+ if (match && !line.includes('def') && !line.includes('class')) {
105
+ const name = match[1];
106
+ // Distinguish constants from regular variables
107
+ const kind = /^[A-Z][A-Z0-9_]*$/.test(name) ? 'constant' : 'variable';
108
+ patterns.push({
109
+ name, kind,
110
+ convention: classifyCasing(name),
111
+ });
112
+ }
113
+ }
114
+ return patterns;
115
+ }
116
+ stripComments(source) {
117
+ // Remove line comments (#)
118
+ let result = source.replace(/#.*/g, '');
119
+ // Remove docstrings (""" or ''')
120
+ result = result.replace(/"""[\s\S]*?"""/g, '');
121
+ result = result.replace(/'''[\s\S]*?'''/g, '');
122
+ return result;
123
+ }
124
+ extractComparisonOps(source) {
125
+ const ops = [];
126
+ const cleaned = this.stripComments(source);
127
+ const matches = cleaned.matchAll(/(==|!=|>=|<=|>(?!=)|<(?!=)|\bis\s+not\b|\bis\b|\bnot\s+in\b|\bin\b)/g);
128
+ for (const m of matches) {
129
+ ops.push(m[1].trim());
130
+ }
131
+ return ops;
132
+ }
133
+ countBranches(source) {
134
+ let count = 0;
135
+ const cleaned = this.stripComments(source);
136
+ count += (cleaned.match(/\bif\s+/g) || []).length;
137
+ count += (cleaned.match(/\belif\s+/g) || []).length;
138
+ count += (cleaned.match(/\belse\s*:/g) || []).length;
139
+ count += (cleaned.match(/\bmatch\s+/g) || []).length;
140
+ count += (cleaned.match(/\bcase\s+/g) || []).length;
141
+ return count;
142
+ }
143
+ countReturns(source) {
144
+ return (source.match(/\breturn\b/g) || []).length;
145
+ }
146
+ extractIndentBody(lines, startIndex) {
147
+ const body = [];
148
+ const defLine = lines[startIndex];
149
+ const defIndent = defLine.match(/^(\s*)/)?.[1]?.length || 0;
150
+ for (let i = startIndex + 1; i < lines.length; i++) {
151
+ const line = lines[i];
152
+ if (line.trim() === '') {
153
+ body.push(line);
154
+ continue;
155
+ }
156
+ const currentIndent = line.match(/^(\s*)/)?.[1]?.length || 0;
157
+ if (currentIndent <= defIndent)
158
+ break;
159
+ body.push(line);
160
+ }
161
+ return body;
162
+ }
163
+ classifyPythonErrorStrategy(body, exceptionType) {
164
+ const trimmed = body.trim();
165
+ // Bare except with just pass/... is always a swallow anti-pattern
166
+ if (!trimmed || trimmed === 'pass' || trimmed === '...')
167
+ return 'swallow';
168
+ // `except Exception: pass` is also a swallow (catches everything, does nothing useful)
169
+ if (exceptionType === 'Exception' && (trimmed === 'pass' || trimmed === '...' || !trimmed))
170
+ return 'swallow';
171
+ if (/\braise\b/.test(trimmed)) {
172
+ if (/\braise\s+\w+Error\b/.test(trimmed))
173
+ return 'wrap';
174
+ return 'rethrow';
175
+ }
176
+ if (/\breturn\s+None\b|\breturn\s*$/.test(trimmed))
177
+ return 'return-error';
178
+ if (/\blogging\.\w+|\blogger\.\w+|\bprint\s*\(/.test(trimmed))
179
+ return 'log';
180
+ return 'other';
181
+ }
182
+ }
183
+ export const pythonAdapter = new PythonAdapter();
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Language Adapter Registry
3
+ *
4
+ * Single source of truth for language detection and adapter resolution.
5
+ * Gates call `registry.getAdapter(filePath)` instead of branching on extensions.
6
+ */
7
+ import type { LanguageAdapter } from './types.js';
8
+ export declare class LanguageAdapterRegistry {
9
+ private extensionMap;
10
+ private adapters;
11
+ constructor(adapters?: LanguageAdapter[]);
12
+ /** Auto-detect language from file extension and return the appropriate adapter */
13
+ getAdapter(filePath: string): LanguageAdapter | null;
14
+ /** Check if a file extension is supported */
15
+ isSupported(filePath: string): boolean;
16
+ /** Get all registered language IDs */
17
+ getSupportedLanguages(): string[];
18
+ /** Get all registered adapters */
19
+ getAllAdapters(): LanguageAdapter[];
20
+ /** Get scan glob patterns that cover all registered languages */
21
+ getScanPatterns(): string[];
22
+ /** Get all supported file extensions */
23
+ getSupportedExtensions(): string[];
24
+ }
25
+ /** Singleton registry instance — import this in gates */
26
+ export declare const languageAdapters: LanguageAdapterRegistry;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Language Adapter Registry
3
+ *
4
+ * Single source of truth for language detection and adapter resolution.
5
+ * Gates call `registry.getAdapter(filePath)` instead of branching on extensions.
6
+ */
7
+ import path from 'path';
8
+ import { jsAdapter } from './js-adapter.js';
9
+ import { pythonAdapter } from './python-adapter.js';
10
+ import { goAdapter } from './go-adapter.js';
11
+ import { rubyAdapter } from './ruby-adapter.js';
12
+ import { rustAdapter } from './rust-adapter.js';
13
+ import { csharpAdapter } from './csharp-adapter.js';
14
+ import { javaAdapter } from './java-adapter.js';
15
+ const ALL_ADAPTERS = [
16
+ jsAdapter,
17
+ pythonAdapter,
18
+ goAdapter,
19
+ rubyAdapter,
20
+ rustAdapter,
21
+ csharpAdapter,
22
+ javaAdapter,
23
+ ];
24
+ export class LanguageAdapterRegistry {
25
+ extensionMap = new Map();
26
+ adapters;
27
+ constructor(adapters = ALL_ADAPTERS) {
28
+ this.adapters = adapters;
29
+ for (const adapter of adapters) {
30
+ for (const ext of adapter.extensions) {
31
+ this.extensionMap.set(ext.toLowerCase(), adapter);
32
+ }
33
+ }
34
+ }
35
+ /** Auto-detect language from file extension and return the appropriate adapter */
36
+ getAdapter(filePath) {
37
+ const ext = path.extname(filePath).toLowerCase();
38
+ return this.extensionMap.get(ext) ?? null;
39
+ }
40
+ /** Check if a file extension is supported */
41
+ isSupported(filePath) {
42
+ return this.getAdapter(filePath) !== null;
43
+ }
44
+ /** Get all registered language IDs */
45
+ getSupportedLanguages() {
46
+ return this.adapters.map(a => a.id);
47
+ }
48
+ /** Get all registered adapters */
49
+ getAllAdapters() {
50
+ return [...this.adapters];
51
+ }
52
+ /** Get scan glob patterns that cover all registered languages */
53
+ getScanPatterns() {
54
+ const allExts = this.adapters
55
+ .flatMap(a => a.extensions)
56
+ .map(e => e.replace('.', ''));
57
+ return [`**/*.{${allExts.join(',')}}`];
58
+ }
59
+ /** Get all supported file extensions */
60
+ getSupportedExtensions() {
61
+ return [...this.extensionMap.keys()];
62
+ }
63
+ }
64
+ /** Singleton registry instance — import this in gates */
65
+ export const languageAdapters = new LanguageAdapterRegistry();
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Ruby Language Adapter
3
+ *
4
+ * Handles Ruby-specific code analysis:
5
+ * - Function extraction (def method_name with indent-based body)
6
+ * - Import parsing (require, require_relative, gem)
7
+ * - Error handling (rescue blocks)
8
+ * - Naming conventions (snake_case methods, PascalCase classes)
9
+ */
10
+ import { LanguageAdapter, FunctionFact, ImportFact, ErrorHandlerFact, NamingPattern } from './types.js';
11
+ export declare class RubyAdapter implements LanguageAdapter {
12
+ readonly id = "ruby";
13
+ readonly name = "Ruby";
14
+ readonly extensions: string[];
15
+ extractFunctions(source: string, filePath?: string): FunctionFact[];
16
+ extractImports(source: string): ImportFact[];
17
+ extractErrorHandlers(source: string): ErrorHandlerFact[];
18
+ extractNamingPatterns(source: string): NamingPattern[];
19
+ stripComments(source: string): string;
20
+ extractComparisonOps(source: string): string[];
21
+ countBranches(source: string): number;
22
+ countReturns(source: string): number;
23
+ private classifyRubyStrategy;
24
+ }
25
+ export declare const rubyAdapter: RubyAdapter;
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Ruby Language Adapter
3
+ *
4
+ * Handles Ruby-specific code analysis:
5
+ * - Function extraction (def method_name with indent-based body)
6
+ * - Import parsing (require, require_relative, gem)
7
+ * - Error handling (rescue blocks)
8
+ * - Naming conventions (snake_case methods, PascalCase classes)
9
+ */
10
+ import { classifyCasing } from './types.js';
11
+ export class RubyAdapter {
12
+ id = 'ruby';
13
+ name = 'Ruby';
14
+ extensions = ['.rb', '.rake'];
15
+ extractFunctions(source, filePath) {
16
+ const functions = [];
17
+ const lines = source.split('\n');
18
+ // Match: def method_name(...) or def self.method_name(...)
19
+ const defRegex = /^\s*def\s+(?:self\.)?(\w+)\s*(?:\(|$)/;
20
+ for (let i = 0; i < lines.length; i++) {
21
+ const match = lines[i].match(defRegex);
22
+ if (!match)
23
+ continue;
24
+ const name = match[1];
25
+ const startLine = i + 1;
26
+ const baseIndent = lines[i].match(/^(\s*)/)?.[1]?.length || 0;
27
+ const body = [];
28
+ // Extract indent-based body until 'end' at same indent level
29
+ for (let j = i + 1; j < lines.length; j++) {
30
+ const currentLine = lines[j];
31
+ if (currentLine.trim() === '') {
32
+ body.push(currentLine);
33
+ continue;
34
+ }
35
+ const currentIndent = currentLine.match(/^(\s*)/)?.[1]?.length || 0;
36
+ if (currentIndent <= baseIndent && currentLine.trim() === 'end') {
37
+ break;
38
+ }
39
+ if (currentIndent <= baseIndent && currentLine.trim() !== '') {
40
+ break;
41
+ }
42
+ body.push(currentLine);
43
+ }
44
+ functions.push({
45
+ name,
46
+ startLine,
47
+ endLine: startLine + body.length,
48
+ body: body.join('\n'),
49
+ isAsync: false,
50
+ isExported: true, // Ruby doesn't have true exports; all methods accessible
51
+ });
52
+ }
53
+ return functions;
54
+ }
55
+ extractImports(source) {
56
+ const imports = [];
57
+ const lines = source.split('\n');
58
+ for (let i = 0; i < lines.length; i++) {
59
+ const line = lines[i].trim();
60
+ // require 'foo'
61
+ let match = line.match(/^\s*require\s+['"]([^'"]+)['"]/);
62
+ if (match) {
63
+ imports.push({
64
+ module: match[1],
65
+ names: [],
66
+ line: i + 1,
67
+ isDynamic: false,
68
+ });
69
+ continue;
70
+ }
71
+ // require_relative 'foo'
72
+ match = line.match(/^\s*require_relative\s+['"]([^'"]+)['"]/);
73
+ if (match) {
74
+ imports.push({
75
+ module: match[1],
76
+ names: [],
77
+ line: i + 1,
78
+ isDynamic: false,
79
+ });
80
+ continue;
81
+ }
82
+ // gem 'baz'
83
+ match = line.match(/^\s*gem\s+['"]([^'"]+)['"]/);
84
+ if (match) {
85
+ imports.push({
86
+ module: match[1],
87
+ names: [],
88
+ line: i + 1,
89
+ isDynamic: false,
90
+ });
91
+ }
92
+ }
93
+ return imports;
94
+ }
95
+ extractErrorHandlers(source) {
96
+ const handlers = [];
97
+ const lines = source.split('\n');
98
+ for (let i = 0; i < lines.length; i++) {
99
+ const line = lines[i];
100
+ // Match: rescue ExceptionClass => var
101
+ if (/^\s*rescue\s+/.test(line)) {
102
+ const baseIndent = line.match(/^(\s*)/)?.[1]?.length || 0;
103
+ const body = [];
104
+ // Collect body until next rescue/else/ensure/end at same level
105
+ for (let j = i + 1; j < lines.length; j++) {
106
+ const currentLine = lines[j];
107
+ if (currentLine.trim() === '') {
108
+ body.push(currentLine);
109
+ continue;
110
+ }
111
+ const currentIndent = currentLine.match(/^(\s*)/)?.[1]?.length || 0;
112
+ if (currentIndent <= baseIndent && /^\s*(?:rescue|else|ensure|end)\b/.test(currentLine)) {
113
+ break;
114
+ }
115
+ body.push(currentLine);
116
+ }
117
+ const bodyStr = body.join('\n');
118
+ const strategy = this.classifyRubyStrategy(bodyStr);
119
+ handlers.push({
120
+ type: 'rescue',
121
+ strategy,
122
+ startLine: i + 1,
123
+ body: bodyStr,
124
+ });
125
+ }
126
+ }
127
+ return handlers;
128
+ }
129
+ extractNamingPatterns(source) {
130
+ const patterns = [];
131
+ const lines = source.split('\n');
132
+ for (let i = 0; i < lines.length; i++) {
133
+ const line = lines[i];
134
+ // Method definitions
135
+ let match = line.match(/^\s*def\s+(?:self\.)?(\w+)/);
136
+ if (match) {
137
+ const name = match[1];
138
+ patterns.push({
139
+ name,
140
+ kind: 'method',
141
+ convention: classifyCasing(name),
142
+ });
143
+ continue;
144
+ }
145
+ // Class definitions
146
+ match = line.match(/^\s*class\s+(\w+)/);
147
+ if (match) {
148
+ const name = match[1];
149
+ patterns.push({
150
+ name,
151
+ kind: 'class',
152
+ convention: classifyCasing(name),
153
+ });
154
+ continue;
155
+ }
156
+ // Constant assignments
157
+ match = line.match(/^\s*([A-Z_]\w*)\s*=/);
158
+ if (match) {
159
+ const name = match[1];
160
+ patterns.push({
161
+ name,
162
+ kind: 'constant',
163
+ convention: classifyCasing(name),
164
+ });
165
+ }
166
+ }
167
+ return patterns;
168
+ }
169
+ stripComments(source) {
170
+ // Remove line comments: #
171
+ let result = source.replace(/#.*/g, '');
172
+ // Remove block comments: =begin...=end
173
+ result = result.replace(/^\s*=begin\b[\s\S]*?^\s*=end\b/m, '');
174
+ return result;
175
+ }
176
+ extractComparisonOps(source) {
177
+ const ops = [];
178
+ const cleaned = this.stripComments(source);
179
+ const matches = cleaned.matchAll(/(===|==|!=|>=|<=|<=>|>(?!=)|<(?!=))/g);
180
+ for (const m of matches) {
181
+ ops.push(m[1]);
182
+ }
183
+ return ops;
184
+ }
185
+ countBranches(source) {
186
+ let count = 0;
187
+ const cleaned = this.stripComments(source);
188
+ count += (cleaned.match(/\bif\s+/g) || []).length;
189
+ count += (cleaned.match(/\belsif\s+/g) || []).length;
190
+ count += (cleaned.match(/\belse\b/g) || []).length;
191
+ count += (cleaned.match(/\bunless\s+/g) || []).length;
192
+ count += (cleaned.match(/\bcase\s+/g) || []).length;
193
+ count += (cleaned.match(/\bwhen\s+/g) || []).length;
194
+ return count;
195
+ }
196
+ countReturns(source) {
197
+ // Count explicit return statements
198
+ return (source.match(/\breturn\b/g) || []).length;
199
+ }
200
+ classifyRubyStrategy(body) {
201
+ const trimmed = body.trim();
202
+ if (!trimmed)
203
+ return 'swallow';
204
+ if (/\braise\b/.test(trimmed))
205
+ return 'rethrow';
206
+ if (/\breturn\s+nil\b/.test(trimmed))
207
+ return 'return-null';
208
+ if (/\breturn\s+false\b/.test(trimmed))
209
+ return 'return-false';
210
+ if (/\breturn\s+/.test(trimmed))
211
+ return 'return-value';
212
+ if (/\bputs\b|\blogger\b|\bRails\.logger\b/i.test(trimmed))
213
+ return 'log';
214
+ return 'other';
215
+ }
216
+ }
217
+ export const rubyAdapter = new RubyAdapter();
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Rust Language Adapter
3
+ *
4
+ * Handles Rust-specific code analysis:
5
+ * - Function extraction (fn, pub fn, pub async fn)
6
+ * - Import parsing (use statements)
7
+ * - Error handling (match, unwrap_or_else)
8
+ * - Naming conventions (snake_case functions, PascalCase types)
9
+ */
10
+ import { LanguageAdapter, FunctionFact, ImportFact, ErrorHandlerFact, NamingPattern } from './types.js';
11
+ export declare class RustAdapter implements LanguageAdapter {
12
+ readonly id = "rust";
13
+ readonly name = "Rust";
14
+ readonly extensions: string[];
15
+ extractFunctions(source: string, filePath?: string): FunctionFact[];
16
+ extractImports(source: string): ImportFact[];
17
+ extractErrorHandlers(source: string): ErrorHandlerFact[];
18
+ extractNamingPatterns(source: string): NamingPattern[];
19
+ stripComments(source: string): string;
20
+ extractComparisonOps(source: string): string[];
21
+ countBranches(source: string): number;
22
+ countReturns(source: string): number;
23
+ private extractBraceBody;
24
+ private extractCallbackBody;
25
+ private classifyRustErrorStrategy;
26
+ }
27
+ export declare const rustAdapter: RustAdapter;