pumuki-ast-hooks 5.6.12 → 5.6.14

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.12",
3
+ "version": "5.6.14",
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,4 @@
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":{}}
@@ -42,3 +42,7 @@
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":{}}
@@ -502,3 +502,31 @@
502
502
  {"timestamp":1768158974401,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
503
503
  {"timestamp":1768158974401,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
504
504
  {"timestamp":1768158974402,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
505
+ {"timestamp":1768162663136,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
506
+ {"timestamp":1768162663136,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
507
+ {"timestamp":1768162663136,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
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"}
@@ -58,6 +58,132 @@ describe('AST Common Module', () => {
58
58
  expect(findings.some(f => f.ruleId === 'common.testing.missing_makesut')).toBe(false);
59
59
  expect(findings.some(f => f.ruleId === 'common.testing.missing_track_for_memory_leaks')).toBe(false);
60
60
  });
61
+
62
+ it('does not report false positives for Swift test files when getFullText is non-empty but incomplete (prefer disk)', () => {
63
+ const project = new Project({
64
+ useInMemoryFileSystem: true,
65
+ skipAddingFilesFromTsConfig: true,
66
+ });
67
+
68
+ const swiftPath = '/tmp/DomainAPIEndpointTests.spec.swift';
69
+ const swiftContent = [
70
+ 'import XCTest',
71
+ 'final class DomainAPIEndpointTests: XCTestCase {',
72
+ ' private func makeSUT() -> Foo { Foo() }',
73
+ ' func test_example() {',
74
+ ' let sut = makeSUT()',
75
+ ' trackForMemoryLeaks(sut, testCase: self, file: #file, line: #line)',
76
+ ' }',
77
+ '}',
78
+ ].join('\n');
79
+
80
+ const sf = project.createSourceFile(swiftPath, swiftContent);
81
+ jest.spyOn(sf, 'getFullText').mockReturnValue('import XCTest\nfinal class DomainAPIEndpointTests: XCTestCase {}');
82
+ jest.spyOn(fs, 'readFileSync').mockReturnValue(swiftContent);
83
+
84
+ const findings = [];
85
+ runCommonIntelligence(project, findings);
86
+
87
+ expect(findings.some(f => f.ruleId === 'common.testing.missing_makesut')).toBe(false);
88
+ expect(findings.some(f => f.ruleId === 'common.testing.missing_track_for_memory_leaks')).toBe(false);
89
+ });
90
+
91
+ it('does not report false positives for simple value type tests (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(false);
123
+ expect(findings.some(f => f.ruleId === 'common.testing.missing_track_for_memory_leaks')).toBe(false);
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
+ });
61
187
  });
62
188
 
63
189
  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*\(/,
@@ -214,9 +274,12 @@ function runCommonIntelligence(project, findings) {
214
274
  const ext = path.extname(filePath).toLowerCase();
215
275
  const isSwiftOrKotlinTest = ext === '.swift' || ext === '.kt' || ext === '.kts';
216
276
 
217
- if (isSwiftOrKotlinTest && (!content || content.trim().length === 0)) {
277
+ if (isSwiftOrKotlinTest) {
218
278
  try {
219
- content = fs.readFileSync(filePath, 'utf8');
279
+ const diskContent = fs.readFileSync(filePath, 'utf8');
280
+ if (diskContent && diskContent.trim().length > 0) {
281
+ content = diskContent;
282
+ }
220
283
  } catch (error) {
221
284
  if (process.env.DEBUG) {
222
285
  console.debug(`[ast-common] Failed to read test file content for ${filePath}: ${error.message}`);
@@ -225,7 +288,12 @@ function runCommonIntelligence(project, findings) {
225
288
  }
226
289
 
227
290
  if (isSwiftOrKotlinTest) {
228
- if (!shouldSkipMakeSUTRule(filePath) && !hasMakeSUT(content)) {
291
+ const isSimple = isSimpleValueTypeTest(content);
292
+
293
+ if (!shouldSkipMakeSUTRule(filePath) &&
294
+ !hasMakeSUT(content) &&
295
+ !isSimple &&
296
+ !hasAstIgnoreComment(content, 'missing_makesut')) {
229
297
  pushFinding(
230
298
  'common.testing.missing_makesut',
231
299
  'high',
@@ -236,7 +304,10 @@ function runCommonIntelligence(project, findings) {
236
304
  );
237
305
  }
238
306
 
239
- if (!shouldSkipTrackForMemoryLeaksRule(filePath) && !hasTrackForMemoryLeaksEvidence(content)) {
307
+ if (!shouldSkipTrackForMemoryLeaksRule(filePath) &&
308
+ !hasTrackForMemoryLeaksEvidence(content) &&
309
+ !isSimple &&
310
+ !hasAstIgnoreComment(content, 'missing_track_for_memory_leaks')) {
240
311
  pushFinding(
241
312
  'common.testing.missing_track_for_memory_leaks',
242
313
  'critical',
@@ -25,3 +25,6 @@
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)"}}