pumuki-ast-hooks 5.6.13 → 5.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki-ast-hooks",
3
- "version": "5.6.13",
3
+ "version": "5.6.15",
4
4
  "description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,3 +9,5 @@
9
9
  {"timestamp":"2026-01-11T18:48:20.329Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
10
10
  {"timestamp":"2026-01-11T18:54:50.282Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
11
11
  {"timestamp":"2026-01-11T19:08:34.232Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
12
+ {"timestamp":"2026-01-11T20:43:24.310Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
13
+ {"timestamp":"2026-01-11T21:27:16.722Z","level":"info","component":"AutoRecovery","event":"NotificationCenterService shutdown","data":{"totalEnqueued":0,"totalSent":0,"totalDeduplicated":0,"totalCooldownSkipped":0,"totalFailed":0,"totalRetries":0,"queueSize":0,"deduplication":{"size":0},"cooldowns":{"activeCooldowns":0}},"context":{}}
@@ -42,3 +42,11 @@
42
42
  {"timestamp":"2026-01-11T19:08:34.083Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_CONFIG_EXISTS","data":{"configPath":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.hook-system/config.json"},"context":{}}
43
43
  {"timestamp":"2026-01-11T19:08:34.083Z","level":"error","component":"InstallWizard","event":"INSTALL_WIZARD_SYMLINK_FAILED","data":{"error":"EEXIST: file already exists, symlink '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/scripts/hooks-system/bin/guard-supervisor.js' -> '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.git/hooks/guard-supervisor'"},"context":{}}
44
44
  {"timestamp":"2026-01-11T19:08:34.083Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_COMPLETED","data":{},"context":{}}
45
+ {"timestamp":"2026-01-11T20:43:24.451Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_START","data":{"repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"},"context":{}}
46
+ {"timestamp":"2026-01-11T20:43:24.460Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_CONFIG_EXISTS","data":{"configPath":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.hook-system/config.json"},"context":{}}
47
+ {"timestamp":"2026-01-11T20:43:24.461Z","level":"error","component":"InstallWizard","event":"INSTALL_WIZARD_SYMLINK_FAILED","data":{"error":"EEXIST: file already exists, symlink '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/scripts/hooks-system/bin/guard-supervisor.js' -> '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.git/hooks/guard-supervisor'"},"context":{}}
48
+ {"timestamp":"2026-01-11T20:43:24.461Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_COMPLETED","data":{},"context":{}}
49
+ {"timestamp":"2026-01-11T21:27:16.554Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_START","data":{"repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"},"context":{}}
50
+ {"timestamp":"2026-01-11T21:27:16.569Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_CONFIG_EXISTS","data":{"configPath":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.hook-system/config.json"},"context":{}}
51
+ {"timestamp":"2026-01-11T21:27:16.571Z","level":"error","component":"InstallWizard","event":"INSTALL_WIZARD_SYMLINK_FAILED","data":{"error":"EEXIST: file already exists, symlink '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/scripts/hooks-system/bin/guard-supervisor.js' -> '/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system/.git/hooks/guard-supervisor'"},"context":{}}
52
+ {"timestamp":"2026-01-11T21:27:16.572Z","level":"info","component":"InstallWizard","event":"INSTALL_WIZARD_COMPLETED","data":{},"context":{}}
@@ -506,3 +506,51 @@
506
506
  {"timestamp":1768162663136,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
507
507
  {"timestamp":1768162663136,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
508
508
  {"timestamp":1768162663136,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
509
+ {"timestamp":1768164204308,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
510
+ {"timestamp":1768164204309,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
511
+ {"timestamp":1768164204309,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
512
+ {"timestamp":1768164204309,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
513
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
514
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
515
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
516
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
517
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
518
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
519
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
520
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
521
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
522
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
523
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
524
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
525
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
526
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
527
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
528
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
529
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
530
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
531
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
532
+ {"timestamp":1768164204310,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
533
+ {"timestamp":1768166836716,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
534
+ {"timestamp":1768166836720,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
535
+ {"timestamp":1768166836720,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
536
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
537
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
538
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
539
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
540
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
541
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
542
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
543
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
544
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
545
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
546
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
547
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
548
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
549
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
550
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
551
+ {"timestamp":1768166836721,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
552
+ {"timestamp":1768166836722,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
553
+ {"timestamp":1768166836722,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
554
+ {"timestamp":1768166836722,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
555
+ {"timestamp":1768166836722,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
556
+ {"timestamp":1768166836722,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
@@ -87,6 +87,139 @@ describe('AST Common Module', () => {
87
87
  expect(findings.some(f => f.ruleId === 'common.testing.missing_makesut')).toBe(false);
88
88
  expect(findings.some(f => f.ruleId === 'common.testing.missing_track_for_memory_leaks')).toBe(false);
89
89
  });
90
+
91
+ it('does report missing_makesut/missing_track_for_memory_leaks for value type tests unless explicitly ignored (Ruralgo case)', () => {
92
+ const project = new Project({
93
+ useInMemoryFileSystem: true,
94
+ skipAddingFilesFromTsConfig: true,
95
+ });
96
+
97
+ const swiftPath = '/tmp/DomainAPIEndpointTests.spec.swift';
98
+ const simpleValueTypeTest = `import Domain
99
+ import XCTest
100
+
101
+ final class DomainAPIEndpointTests: XCTestCase {
102
+ func test_givenCustomInit_whenAccessingProperties_thenReturnsExpectedValues() {
103
+ let body = Data([0x01, 0x02])
104
+ let queryItems = [URLQueryItem(name: "page", value: "1")]
105
+
106
+ let endpointPatch = APIEndpoint(
107
+ path: "/any", method: .patch, body: body, queryItems: queryItems)
108
+
109
+ XCTAssertEqual(endpointPatch.path, "/any")
110
+ XCTAssertEqual(endpointPatch.method, .patch)
111
+ XCTAssertEqual(endpointPatch.body, body)
112
+ XCTAssertEqual(endpointPatch.queryItems, queryItems)
113
+ }
114
+ }`;
115
+
116
+ const sf = project.createSourceFile(swiftPath, simpleValueTypeTest);
117
+ jest.spyOn(fs, 'readFileSync').mockReturnValue(simpleValueTypeTest);
118
+
119
+ const findings = [];
120
+ runCommonIntelligence(project, findings);
121
+
122
+ expect(findings.some(f => f.ruleId === 'common.testing.missing_makesut')).toBe(true);
123
+ expect(findings.some(f => f.ruleId === 'common.testing.missing_track_for_memory_leaks')).toBe(true);
124
+ });
125
+
126
+ it('does apply rules to complex tests with async/classes', () => {
127
+ const project = new Project({
128
+ useInMemoryFileSystem: true,
129
+ skipAddingFilesFromTsConfig: true,
130
+ });
131
+
132
+ const swiftPath = '/tmp/NetworkClientTests.spec.swift';
133
+ const complexTest = `import XCTest
134
+
135
+ final class NetworkClientTests: XCTestCase {
136
+ override func setUp() {
137
+ super.setUp()
138
+ }
139
+
140
+ func test_load_deliversErrorOnClientError() async {
141
+ let client = HTTPClient()
142
+ let result = await client.load()
143
+ XCTAssertNotNil(result)
144
+ }
145
+ }`;
146
+
147
+ const sf = project.createSourceFile(swiftPath, complexTest);
148
+ jest.spyOn(fs, 'readFileSync').mockReturnValue(complexTest);
149
+
150
+ const findings = [];
151
+ runCommonIntelligence(project, findings);
152
+
153
+ expect(findings.some(f => f.ruleId === 'common.testing.missing_makesut')).toBe(true);
154
+ expect(findings.some(f => f.ruleId === 'common.testing.missing_track_for_memory_leaks')).toBe(true);
155
+ });
156
+
157
+ it('respects ast-ignore comments', () => {
158
+ const project = new Project({
159
+ useInMemoryFileSystem: true,
160
+ skipAddingFilesFromTsConfig: true,
161
+ });
162
+
163
+ const swiftPath = '/tmp/SomeTests.spec.swift';
164
+ const testWithIgnore = `import XCTest
165
+ // ast-ignore: missing_makesut, missing_track_for_memory_leaks
166
+
167
+ final class SomeTests: XCTestCase {
168
+ override func setUp() {
169
+ super.setUp()
170
+ }
171
+
172
+ func test_something() async {
173
+ let result = await someAsyncCall()
174
+ XCTAssertNotNil(result)
175
+ }
176
+ }`;
177
+
178
+ const sf = project.createSourceFile(swiftPath, testWithIgnore);
179
+ jest.spyOn(fs, 'readFileSync').mockReturnValue(testWithIgnore);
180
+
181
+ const findings = [];
182
+ runCommonIntelligence(project, findings);
183
+
184
+ expect(findings.some(f => f.ruleId === 'common.testing.missing_makesut')).toBe(false);
185
+ expect(findings.some(f => f.ruleId === 'common.testing.missing_track_for_memory_leaks')).toBe(false);
186
+ });
187
+
188
+ it('allows explicit ignore for Ruralgo value type tests via ast-ignore comment', () => {
189
+ const project = new Project({
190
+ useInMemoryFileSystem: true,
191
+ skipAddingFilesFromTsConfig: true,
192
+ });
193
+
194
+ const swiftPath = '/tmp/DomainAPIEndpointTests.spec.swift';
195
+ const ignoredValueTypeTest = `import Domain
196
+ import XCTest
197
+ // ast-ignore: missing_makesut, missing_track_for_memory_leaks
198
+
199
+ final class DomainAPIEndpointTests: XCTestCase {
200
+ func test_givenCustomInit_whenAccessingProperties_thenReturnsExpectedValues() {
201
+ let body = Data([0x01, 0x02])
202
+ let queryItems = [URLQueryItem(name: "page", value: "1")]
203
+
204
+ let endpointPatch = APIEndpoint(
205
+ path: "/any", method: .patch, body: body, queryItems: queryItems)
206
+
207
+ XCTAssertEqual(endpointPatch.path, "/any")
208
+ XCTAssertEqual(endpointPatch.method, .patch)
209
+ XCTAssertEqual(endpointPatch.body, body)
210
+ XCTAssertEqual(endpointPatch.queryItems, queryItems)
211
+ }
212
+ }`;
213
+
214
+ const sf = project.createSourceFile(swiftPath, ignoredValueTypeTest);
215
+ jest.spyOn(fs, 'readFileSync').mockReturnValue(ignoredValueTypeTest);
216
+
217
+ const findings = [];
218
+ runCommonIntelligence(project, findings);
219
+
220
+ expect(findings.some(f => f.ruleId === 'common.testing.missing_makesut')).toBe(false);
221
+ expect(findings.some(f => f.ruleId === 'common.testing.missing_track_for_memory_leaks')).toBe(false);
222
+ });
90
223
  });
91
224
 
92
225
  describe('exports', () => {
@@ -71,6 +71,66 @@ function hasTrackForMemoryLeaksEvidence(content) {
71
71
  return hasTrackForMemoryLeaks(content) || hasMakeSUT(content);
72
72
  }
73
73
 
74
+ /**
75
+ * Detect if a test file is a simple value type test that doesn't need makeSUT/trackForMemoryLeaks.
76
+ * Simple tests typically:
77
+ * - Have no async/await or closures capturing self
78
+ * - Have no class instantiation patterns (reference types)
79
+ * - Test only property access or simple assertions
80
+ * - Have no setUp/tearDown methods
81
+ */
82
+ function isSimpleValueTypeTest(content) {
83
+ // If it has setUp/tearDown, it's not simple - needs proper patterns
84
+ if (/\boverride\s+func\s+(setUp|tearDown)/.test(content)) return false;
85
+
86
+ // If it has async/await, needs memory tracking
87
+ if (/\basync\b|\bawait\b|\bexpectation\(|\bwaitForExpectations\b/.test(content)) return false;
88
+
89
+ // If it has closures that might capture self, needs tracking
90
+ if (/\[\s*(weak|unowned)?\s*self\s*\]/.test(content)) return false;
91
+
92
+ // If it has completion handlers or callbacks, needs tracking
93
+ if (/completion\s*:|\@escaping|\bresult\s*in\b/i.test(content)) return false;
94
+
95
+ // If it creates classes (not structs), needs makeSUT pattern
96
+ // Look for "= ClassName(" but not "= StructName(" - heuristic: classes often have "Client", "Service", "Manager", "Repository"
97
+ if (/=\s*\w*(Client|Service|Manager|Repository|UseCase|Interactor|Presenter|ViewModel|Controller)\s*\(/.test(content)) return false;
98
+
99
+ // If it's a very short test (likely simple property test), skip rules
100
+ const lines = content.split('\n').filter(l => l.trim().length > 0);
101
+ if (lines.length <= 40) return true;
102
+
103
+ // If it only has XCTAssertEqual for properties, it's likely a simple value type test
104
+ const testMethods = content.match(/func\s+test_[^{]+\{[^}]+\}/gs) || [];
105
+ if (testMethods.length > 0) {
106
+ const allSimple = testMethods.every(method => {
107
+ // Simple test: mostly XCTAssert* calls, no complex setup
108
+ const hasOnlyAsserts = /XCTAssert(Equal|True|False|Nil|NotNil)/.test(method);
109
+ const hasNoComplexSetup = !/\bsut\b|makeSUT|\bawait\b|completion/.test(method);
110
+ return hasOnlyAsserts && hasNoComplexSetup;
111
+ });
112
+ if (allSimple && testMethods.length <= 3) return true;
113
+ }
114
+
115
+ return false;
116
+ }
117
+
118
+ /**
119
+ * Check for ast-ignore comments in content
120
+ * Supports: // ast-ignore: rule_name or // ast-ignore: rule1, rule2
121
+ */
122
+ function hasAstIgnoreComment(content, ruleId) {
123
+ const ignorePattern = /\/\/\s*ast-ignore:\s*([^\n]+)/gi;
124
+ let match;
125
+ while ((match = ignorePattern.exec(content)) !== null) {
126
+ const rules = match[1].split(',').map(r => r.trim().toLowerCase());
127
+ if (rules.includes(ruleId.toLowerCase()) || rules.includes('all')) {
128
+ return true;
129
+ }
130
+ }
131
+ return false;
132
+ }
133
+
74
134
  function detectMockSignals(content) {
75
135
  const signals = [
76
136
  /\bjest\.mock\s*\(/,
@@ -228,7 +288,9 @@ function runCommonIntelligence(project, findings) {
228
288
  }
229
289
 
230
290
  if (isSwiftOrKotlinTest) {
231
- if (!shouldSkipMakeSUTRule(filePath) && !hasMakeSUT(content)) {
291
+ if (!shouldSkipMakeSUTRule(filePath) &&
292
+ !hasMakeSUT(content) &&
293
+ !hasAstIgnoreComment(content, 'missing_makesut')) {
232
294
  pushFinding(
233
295
  'common.testing.missing_makesut',
234
296
  'high',
@@ -239,7 +301,9 @@ function runCommonIntelligence(project, findings) {
239
301
  );
240
302
  }
241
303
 
242
- if (!shouldSkipTrackForMemoryLeaksRule(filePath) && !hasTrackForMemoryLeaksEvidence(content)) {
304
+ if (!shouldSkipTrackForMemoryLeaksRule(filePath) &&
305
+ !hasTrackForMemoryLeaksEvidence(content) &&
306
+ !hasAstIgnoreComment(content, 'missing_track_for_memory_leaks')) {
243
307
  pushFinding(
244
308
  'common.testing.missing_track_for_memory_leaks',
245
309
  'critical',
@@ -25,3 +25,9 @@
25
25
  {"timestamp":"2026-01-11T19:08:37.452Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"ok","percentUsed":10,"tokensUsed":100000,"maxTokens":1000000,"source":"realtime","stale":false},"context":{"message":"Result level=ok percent=10% used=100000/1000000 source=realtime"}}
26
26
  {"timestamp":"2026-01-11T19:08:37.467Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"warning","percentUsed":91,"tokensUsed":910000,"maxTokens":1000000,"source":"fallback","stale":false},"context":{"message":"Result level=warning percent=91% used=910000/1000000 source=fallback"}}
27
27
  {"timestamp":"2026-01-11T19:08:37.468Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"critical","percentUsed":98,"tokensUsed":980000,"maxTokens":1000000,"source":"realtime","stale":true},"context":{"message":"Result level=critical percent=98% used=980000/1000000 source=realtime (stale)"}}
28
+ {"timestamp":"2026-01-11T20:43:25.680Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"ok","percentUsed":10,"tokensUsed":100000,"maxTokens":1000000,"source":"realtime","stale":false},"context":{"message":"Result level=ok percent=10% used=100000/1000000 source=realtime"}}
29
+ {"timestamp":"2026-01-11T20:43:25.683Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"warning","percentUsed":91,"tokensUsed":910000,"maxTokens":1000000,"source":"fallback","stale":false},"context":{"message":"Result level=warning percent=91% used=910000/1000000 source=fallback"}}
30
+ {"timestamp":"2026-01-11T20:43:25.683Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"critical","percentUsed":98,"tokensUsed":980000,"maxTokens":1000000,"source":"realtime","stale":true},"context":{"message":"Result level=critical percent=98% used=980000/1000000 source=realtime (stale)"}}
31
+ {"timestamp":"2026-01-11T21:27:20.297Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"ok","percentUsed":10,"tokensUsed":100000,"maxTokens":1000000,"source":"realtime","stale":false},"context":{"message":"Result level=ok percent=10% used=100000/1000000 source=realtime"}}
32
+ {"timestamp":"2026-01-11T21:27:20.301Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"warning","percentUsed":91,"tokensUsed":910000,"maxTokens":1000000,"source":"fallback","stale":false},"context":{"message":"Result level=warning percent=91% used=910000/1000000 source=fallback"}}
33
+ {"timestamp":"2026-01-11T21:27:20.301Z","level":"info","component":"TokenMonitor","event":"TOKEN_MONITOR_RESULT","data":{"level":"critical","percentUsed":98,"tokensUsed":980000,"maxTokens":1000000,"source":"realtime","stale":true},"context":{"message":"Result level=critical percent=98% used=980000/1000000 source=realtime (stale)"}}