pumuki-ast-hooks 5.5.36 → 5.5.37

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.5.36",
3
+ "version": "5.5.37",
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": {
@@ -23,7 +23,15 @@ describe('AI_EVIDENCE.json structure validation', () => {
23
23
  question_3_clean_architecture: 'Verify that the code follows Clean Architecture and SOLID principles',
24
24
  last_answered: new Date().toISOString()
25
25
  },
26
- rules_read: {
26
+ rules_read: [
27
+ {
28
+ file: 'rulesgold.mdc',
29
+ verified: true,
30
+ summary: 'loaded (example)',
31
+ path: '/tmp/rulesgold.mdc'
32
+ }
33
+ ],
34
+ rules_read_flags: {
27
35
  backend: true,
28
36
  frontend: false,
29
37
  ios: false,
@@ -79,15 +87,25 @@ describe('AI_EVIDENCE.json structure validation', () => {
79
87
  expect(evidence.protocol_3_questions.last_answered).toBeDefined();
80
88
  });
81
89
 
82
- it('should have rules_read field tracking all platforms', () => {
90
+ it('should have rules_read field with rule load evidence entries', () => {
83
91
  const evidence = createMockEvidence();
84
92
  expect(evidence.rules_read).toBeDefined();
85
- expect(evidence.rules_read.backend).toBe(true);
86
- expect(evidence.rules_read.frontend).toBe(false);
87
- expect(evidence.rules_read.ios).toBe(false);
88
- expect(evidence.rules_read.android).toBe(false);
89
- expect(evidence.rules_read.gold).toBe(true);
90
- expect(evidence.rules_read.last_checked).toBeDefined();
93
+ expect(Array.isArray(evidence.rules_read)).toBe(true);
94
+ expect(evidence.rules_read.length).toBeGreaterThan(0);
95
+ expect(evidence.rules_read[0]).toHaveProperty('file');
96
+ expect(evidence.rules_read[0]).toHaveProperty('verified');
97
+ expect(evidence.rules_read[0]).toHaveProperty('summary');
98
+ });
99
+
100
+ it('should have rules_read_flags legacy field tracking platform flags', () => {
101
+ const evidence = createMockEvidence();
102
+ expect(evidence.rules_read_flags).toBeDefined();
103
+ expect(evidence.rules_read_flags.backend).toBe(true);
104
+ expect(evidence.rules_read_flags.frontend).toBe(false);
105
+ expect(evidence.rules_read_flags.ios).toBe(false);
106
+ expect(evidence.rules_read_flags.android).toBe(false);
107
+ expect(evidence.rules_read_flags.gold).toBe(true);
108
+ expect(evidence.rules_read_flags.last_checked).toBeDefined();
91
109
  });
92
110
 
93
111
  it('should have current_context field with branch and file info', () => {
@@ -9,6 +9,7 @@ const { TokenManager } = require('../utils/token-manager');
9
9
  const { toErrorMessage } = require('../utils/error-utils');
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
+ const DynamicRulesLoader = require('../../application/services/DynamicRulesLoader');
12
13
 
13
14
  function deriveCategoryFromRuleId(ruleId) {
14
15
  if (!ruleId || typeof ruleId !== 'string') return 'unknown';
@@ -24,6 +25,121 @@ function deriveCategoryFromRuleId(ruleId) {
24
25
  return parts[0] || 'unknown';
25
26
  }
26
27
 
28
+ function detectPlatformsFromStagedFiles(stagedFiles) {
29
+ const platforms = new Set();
30
+ const files = Array.isArray(stagedFiles) ? stagedFiles : [];
31
+
32
+ files.forEach(filePath => {
33
+ const lowerPath = String(filePath || '').toLowerCase();
34
+
35
+ if (lowerPath.includes('apps/backend/') || lowerPath.includes('/services/') || lowerPath.includes('services/') || lowerPath.includes('/functions/') || lowerPath.includes('functions/')) {
36
+ platforms.add('backend');
37
+ }
38
+ if (lowerPath.includes('apps/web-app/') || lowerPath.includes('apps/admin') || lowerPath.includes('apps/frontend/') || lowerPath.includes('frontend/')) {
39
+ platforms.add('frontend');
40
+ }
41
+ if (lowerPath.includes('apps/ios/') || lowerPath.endsWith('.swift')) {
42
+ platforms.add('ios');
43
+ }
44
+ if (lowerPath.includes('apps/android/') || lowerPath.endsWith('.kt') || lowerPath.endsWith('.kts') || lowerPath.endsWith('.java')) {
45
+ platforms.add('android');
46
+ }
47
+ });
48
+
49
+ return platforms;
50
+ }
51
+
52
+ function countViolationsByPlatform(violations) {
53
+ const counts = { backend: 0, frontend: 0, ios: 0, android: 0 };
54
+ const list = Array.isArray(violations) ? violations : [];
55
+
56
+ list.forEach(v => {
57
+ const ruleId = v.ruleId || v.rule || 'unknown';
58
+ const category = String(v.category || deriveCategoryFromRuleId(ruleId) || '').toLowerCase();
59
+ const platform = category.split('.')[0];
60
+ if (counts[platform] !== undefined) {
61
+ counts[platform] += 1;
62
+ }
63
+ });
64
+
65
+ return counts;
66
+ }
67
+
68
+ function buildPlatformsEvidence(stagedFiles, violations) {
69
+ const stagedDetected = detectPlatformsFromStagedFiles(stagedFiles);
70
+ const violationCounts = countViolationsByPlatform(violations);
71
+
72
+ const platforms = ['backend', 'frontend', 'ios', 'android'];
73
+ const result = {};
74
+
75
+ platforms.forEach(p => {
76
+ const violationsCount = violationCounts[p] || 0;
77
+ result[p] = {
78
+ detected: stagedDetected.has(p) || violationsCount > 0,
79
+ violations: violationsCount
80
+ };
81
+ });
82
+
83
+ return result;
84
+ }
85
+
86
+ function summarizeRulesContent(content) {
87
+ if (!content || typeof content !== 'string') {
88
+ return 'not found';
89
+ }
90
+ const firstNonEmpty = content
91
+ .split('\n')
92
+ .map(l => l.trim())
93
+ .find(l => l.length > 0);
94
+ const firstLine = firstNonEmpty ? firstNonEmpty.slice(0, 140) : '';
95
+ return firstLine.length > 0 ? firstLine : `loaded (${content.length} chars)`;
96
+ }
97
+
98
+ async function buildRulesReadEvidence(platformsEvidence) {
99
+ const loader = new DynamicRulesLoader();
100
+ const entries = [];
101
+
102
+ const detectedPlatforms = ['backend', 'frontend', 'ios', 'android']
103
+ .filter(p => platformsEvidence && platformsEvidence[p] && platformsEvidence[p].detected);
104
+
105
+ const uniqueFiles = ['rulesgold.mdc', ...detectedPlatforms.map(p => loader.rulesMap[p]).filter(Boolean)];
106
+
107
+ for (const file of uniqueFiles) {
108
+ let verified = false;
109
+ let summary = 'not found';
110
+ let resolvedPath = null;
111
+
112
+ try {
113
+ const content = await loader.loadRule(file);
114
+ verified = Boolean(content);
115
+ summary = summarizeRulesContent(content);
116
+ const cached = loader.cache && loader.cache.rules ? loader.cache.rules.get(file) : null;
117
+ resolvedPath = cached && cached.fullPath ? cached.fullPath : null;
118
+ } catch (error) {
119
+ summary = `error: ${toErrorMessage(error)}`;
120
+ }
121
+
122
+ entries.push({
123
+ file,
124
+ verified,
125
+ summary,
126
+ path: resolvedPath
127
+ });
128
+ }
129
+
130
+ return {
131
+ entries,
132
+ legacyFlags: {
133
+ backend: detectedPlatforms.includes('backend'),
134
+ frontend: detectedPlatforms.includes('frontend'),
135
+ ios: detectedPlatforms.includes('ios'),
136
+ android: detectedPlatforms.includes('android'),
137
+ gold: true,
138
+ last_checked: formatLocalTimestamp()
139
+ }
140
+ };
141
+ }
142
+
27
143
  function formatLocalTimestamp(date = new Date()) {
28
144
  const year = date.getFullYear();
29
145
  const month = String(date.getMonth() + 1).padStart(2, '0');
@@ -90,7 +206,7 @@ async function runIntelligentAudit() {
90
206
  const gateResult = { passed: true, exitCode: 0, blockedBy: null };
91
207
  const tokenManager = new TokenManager();
92
208
  const tokenUsage = tokenManager.estimate(enhancedAll, {});
93
- updateAIEvidence(enhancedAll, gateResult, tokenUsage);
209
+ await updateAIEvidence(enhancedAll, gateResult, tokenUsage);
94
210
  process.exit(0);
95
211
  }
96
212
 
@@ -136,7 +252,7 @@ async function runIntelligentAudit() {
136
252
 
137
253
  tokenManager.record(tokenUsage);
138
254
 
139
- updateAIEvidence(enhancedViolations, gateResult, tokenUsage);
255
+ await updateAIEvidence(enhancedViolations, gateResult, tokenUsage);
140
256
 
141
257
  saveEnhancedViolations(enhancedViolations);
142
258
 
@@ -201,7 +317,7 @@ function saveEnhancedViolations(violations) {
201
317
  fs.writeFileSync(outputPath, JSON.stringify(enhanced, null, 2));
202
318
  }
203
319
 
204
- function updateAIEvidence(violations, gateResult, tokenUsage) {
320
+ async function updateAIEvidence(violations, gateResult, tokenUsage) {
205
321
  const evidencePath = '.AI_EVIDENCE.json';
206
322
 
207
323
  if (!fs.existsSync(evidencePath)) {
@@ -320,14 +436,12 @@ function updateAIEvidence(violations, gateResult, tokenUsage) {
320
436
  last_answered: formatLocalTimestamp()
321
437
  };
322
438
 
323
- evidence.rules_read = {
324
- backend: true,
325
- frontend: false,
326
- ios: false,
327
- android: false,
328
- gold: true,
329
- last_checked: formatLocalTimestamp()
330
- };
439
+ const stagedFiles = getStagedFiles();
440
+ const platformsEvidence = buildPlatformsEvidence(stagedFiles, violations);
441
+ const rulesRead = await buildRulesReadEvidence(platformsEvidence);
442
+
443
+ evidence.rules_read = rulesRead.entries;
444
+ evidence.rules_read_flags = rulesRead.legacyFlags;
331
445
 
332
446
  evidence.current_context = {
333
447
  working_on: env.get('AUTO_EVIDENCE_SUMMARY', 'AST Intelligence Analysis'),
@@ -337,12 +451,7 @@ function updateAIEvidence(violations, gateResult, tokenUsage) {
337
451
  timestamp: formatLocalTimestamp()
338
452
  };
339
453
 
340
- evidence.platforms = {
341
- backend: { detected: true, violations: violations.filter(v => v.category && v.category.includes('backend')).length },
342
- frontend: { detected: false, violations: 0 },
343
- ios: { detected: false, violations: 0 },
344
- android: { detected: false, violations: 0 }
345
- };
454
+ evidence.platforms = platformsEvidence;
346
455
 
347
456
  evidence.session_id = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
348
457