pumuki-ast-hooks 5.5.36 → 5.5.38

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.38",
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,183 @@ 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
+ function buildAutoContextFrontmatter(detectedPlatforms) {
99
+ const platforms = Array.isArray(detectedPlatforms) ? detectedPlatforms.filter(Boolean) : [];
100
+ const generated = formatLocalTimestamp();
101
+ const platformStr = platforms.length > 0 ? platforms.join(', ') : 'none';
102
+ return `---\nalwaysApply: true\ndescription: Auto-generated context for detected platforms\nplatforms: ${platformStr}\ngenerated: ${generated}\n---\n\n`;
103
+ }
104
+
105
+ async function buildAutoContextContent(platformsEvidence) {
106
+ const loader = new DynamicRulesLoader();
107
+ const detectedPlatforms = ['backend', 'frontend', 'ios', 'android']
108
+ .filter(p => platformsEvidence && platformsEvidence[p] && platformsEvidence[p].detected);
109
+
110
+ const files = ['rulesgold.mdc', ...detectedPlatforms.map(p => loader.rulesMap[p]).filter(Boolean)];
111
+ const uniqueFiles = Array.from(new Set(files));
112
+
113
+ let content = buildAutoContextFrontmatter(detectedPlatforms);
114
+
115
+ for (const file of uniqueFiles) {
116
+ let ruleContent = null;
117
+ try {
118
+ ruleContent = await loader.loadRule(file);
119
+ } catch {
120
+ ruleContent = null;
121
+ }
122
+
123
+ content += `## Source: ${file}\n\n`;
124
+ content += ruleContent ? `${ruleContent}\n\n` : `not found\n\n`;
125
+ content += `---\n\n`;
126
+ }
127
+
128
+ return content;
129
+ }
130
+
131
+ async function writeAutoContextFiles(platformsEvidence) {
132
+ try {
133
+ const pkgPath = path.join(process.cwd(), 'package.json');
134
+ if (fs.existsSync(pkgPath)) {
135
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
136
+ if (pkg && pkg.name === 'pumuki-ast-hooks') {
137
+ return;
138
+ }
139
+ }
140
+ } catch (error) {
141
+ process.stderr.write(`[Intelligent Audit] ⚠️ Failed to inspect package.json for auto-context skip logic (${toErrorMessage(error)})\n`);
142
+ }
143
+
144
+ const content = await buildAutoContextContent(platformsEvidence);
145
+ const targets = [
146
+ path.join(process.cwd(), '.cursor', 'rules', 'auto-context.mdc'),
147
+ path.join(process.cwd(), '.windsurf', 'rules', 'auto-context.mdc')
148
+ ];
149
+
150
+ for (const target of targets) {
151
+ try {
152
+ await fs.promises.mkdir(path.dirname(target), { recursive: true });
153
+ await fs.promises.writeFile(target, content, 'utf-8');
154
+ } catch (error) {
155
+ process.stderr.write(`[Intelligent Audit] ⚠️ Failed to write auto-context: ${target} (${toErrorMessage(error)})\n`);
156
+ }
157
+ }
158
+ }
159
+
160
+ async function buildRulesReadEvidence(platformsEvidence) {
161
+ const loader = new DynamicRulesLoader();
162
+ const entries = [];
163
+
164
+ const detectedPlatforms = ['backend', 'frontend', 'ios', 'android']
165
+ .filter(p => platformsEvidence && platformsEvidence[p] && platformsEvidence[p].detected);
166
+
167
+ const uniqueFiles = ['rulesgold.mdc', ...detectedPlatforms.map(p => loader.rulesMap[p]).filter(Boolean)];
168
+
169
+ for (const file of uniqueFiles) {
170
+ let verified = false;
171
+ let summary = 'not found';
172
+ let resolvedPath = null;
173
+
174
+ try {
175
+ const content = await loader.loadRule(file);
176
+ verified = Boolean(content);
177
+ summary = summarizeRulesContent(content);
178
+ const cached = loader.cache && loader.cache.rules ? loader.cache.rules.get(file) : null;
179
+ resolvedPath = cached && cached.fullPath ? cached.fullPath : null;
180
+ } catch (error) {
181
+ summary = `error: ${toErrorMessage(error)}`;
182
+ }
183
+
184
+ entries.push({
185
+ file,
186
+ verified,
187
+ summary,
188
+ path: resolvedPath
189
+ });
190
+ }
191
+
192
+ return {
193
+ entries,
194
+ legacyFlags: {
195
+ backend: detectedPlatforms.includes('backend'),
196
+ frontend: detectedPlatforms.includes('frontend'),
197
+ ios: detectedPlatforms.includes('ios'),
198
+ android: detectedPlatforms.includes('android'),
199
+ gold: true,
200
+ last_checked: formatLocalTimestamp()
201
+ }
202
+ };
203
+ }
204
+
27
205
  function formatLocalTimestamp(date = new Date()) {
28
206
  const year = date.getFullYear();
29
207
  const month = String(date.getMonth() + 1).padStart(2, '0');
@@ -90,7 +268,7 @@ async function runIntelligentAudit() {
90
268
  const gateResult = { passed: true, exitCode: 0, blockedBy: null };
91
269
  const tokenManager = new TokenManager();
92
270
  const tokenUsage = tokenManager.estimate(enhancedAll, {});
93
- updateAIEvidence(enhancedAll, gateResult, tokenUsage);
271
+ await updateAIEvidence(enhancedAll, gateResult, tokenUsage);
94
272
  process.exit(0);
95
273
  }
96
274
 
@@ -136,7 +314,7 @@ async function runIntelligentAudit() {
136
314
 
137
315
  tokenManager.record(tokenUsage);
138
316
 
139
- updateAIEvidence(enhancedViolations, gateResult, tokenUsage);
317
+ await updateAIEvidence(enhancedViolations, gateResult, tokenUsage);
140
318
 
141
319
  saveEnhancedViolations(enhancedViolations);
142
320
 
@@ -201,7 +379,7 @@ function saveEnhancedViolations(violations) {
201
379
  fs.writeFileSync(outputPath, JSON.stringify(enhanced, null, 2));
202
380
  }
203
381
 
204
- function updateAIEvidence(violations, gateResult, tokenUsage) {
382
+ async function updateAIEvidence(violations, gateResult, tokenUsage) {
205
383
  const evidencePath = '.AI_EVIDENCE.json';
206
384
 
207
385
  if (!fs.existsSync(evidencePath)) {
@@ -320,14 +498,14 @@ function updateAIEvidence(violations, gateResult, tokenUsage) {
320
498
  last_answered: formatLocalTimestamp()
321
499
  };
322
500
 
323
- evidence.rules_read = {
324
- backend: true,
325
- frontend: false,
326
- ios: false,
327
- android: false,
328
- gold: true,
329
- last_checked: formatLocalTimestamp()
330
- };
501
+ const stagedFiles = getStagedFiles();
502
+ const platformsEvidence = buildPlatformsEvidence(stagedFiles, violations);
503
+ const rulesRead = await buildRulesReadEvidence(platformsEvidence);
504
+
505
+ await writeAutoContextFiles(platformsEvidence);
506
+
507
+ evidence.rules_read = rulesRead.entries;
508
+ evidence.rules_read_flags = rulesRead.legacyFlags;
331
509
 
332
510
  evidence.current_context = {
333
511
  working_on: env.get('AUTO_EVIDENCE_SUMMARY', 'AST Intelligence Analysis'),
@@ -337,12 +515,7 @@ function updateAIEvidence(violations, gateResult, tokenUsage) {
337
515
  timestamp: formatLocalTimestamp()
338
516
  };
339
517
 
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
- };
518
+ evidence.platforms = platformsEvidence;
346
519
 
347
520
  evidence.session_id = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
348
521