pumuki-ast-hooks 5.3.28 → 5.3.30

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.3.28",
3
+ "version": "5.3.30",
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": {
@@ -123,4 +123,4 @@
123
123
  "./skills": "./skills/skill-rules.json",
124
124
  "./hooks": "./hooks/index.js"
125
125
  }
126
- }
126
+ }
@@ -48,6 +48,45 @@ class McpConfigurator {
48
48
  this.logger = logger;
49
49
  }
50
50
 
51
+ resolveAutomationEntrypoint() {
52
+ // Prefer dependency-installed hook system (node_modules) to avoid requiring scripts/hooks-system in consumer repos
53
+ // Fallback to repo-local hook system if it exists.
54
+ const candidates = [
55
+ this.hookSystemRoot
56
+ ? path.join(this.hookSystemRoot, 'infrastructure', 'mcp', 'ast-intelligence-automation.js')
57
+ : null,
58
+ path.join(this.targetRoot, 'scripts', 'hooks-system', 'infrastructure', 'mcp', 'ast-intelligence-automation.js')
59
+ ].filter(Boolean);
60
+
61
+ for (const c of candidates) {
62
+ try {
63
+ if (fs.existsSync(c)) return c;
64
+ } catch {
65
+ // ignore
66
+ }
67
+ }
68
+
69
+ // Last resort: keep previous behaviour
70
+ return path.join(this.targetRoot, 'scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js');
71
+ }
72
+
73
+ disableDuplicateServersForRepo(existing, currentServerId) {
74
+ if (!existing || !existing.mcpServers) return;
75
+
76
+ Object.entries(existing.mcpServers).forEach(([id, server]) => {
77
+ if (!server || id === currentServerId) return;
78
+ const envObj = server.env || {};
79
+ const repoRoot = envObj.REPO_ROOT;
80
+ if (repoRoot !== this.targetRoot) return;
81
+
82
+ const args0 = Array.isArray(server.args) ? String(server.args[0] || '') : '';
83
+ // Disable legacy server pointing to repo-local scripts/hooks-system when we already configure the canonical one
84
+ if (args0.includes('/scripts/hooks-system/') || args0.includes('\\scripts\\hooks-system\\')) {
85
+ server.disabled = true;
86
+ }
87
+ });
88
+ }
89
+
51
90
  getGlobalWindsurfConfigPath() {
52
91
  return path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
53
92
  }
@@ -65,12 +104,13 @@ class McpConfigurator {
65
104
  }
66
105
 
67
106
  const serverId = computeServerIdForRepo(this.targetRoot);
107
+ const entrypoint = this.resolveAutomationEntrypoint();
68
108
  const mcpConfig = {
69
109
  mcpServers: {
70
110
  [serverId]: {
71
111
  command: nodePath,
72
112
  args: [
73
- path.join(this.targetRoot, 'scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js')
113
+ entrypoint
74
114
  ],
75
115
  env: {
76
116
  REPO_ROOT: this.targetRoot,
@@ -97,6 +137,9 @@ class McpConfigurator {
97
137
  const existing = JSON.parse(fs.readFileSync(globalConfigPath, 'utf8'));
98
138
  if (!existing.mcpServers) existing.mcpServers = {};
99
139
 
140
+ // Prevent duplicate MCP servers for the same repoRoot by disabling legacy entries.
141
+ this.disableDuplicateServersForRepo(existing, serverId);
142
+
100
143
  existing.mcpServers[serverId] = mcpConfig.mcpServers[serverId];
101
144
 
102
145
  fs.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2));
@@ -368,18 +368,36 @@ function generateOutput(findings, context, project, root) {
368
368
 
369
369
  // Top violations
370
370
  const grouped = {};
371
+ const levelRank = { LOW: 1, MEDIUM: 2, HIGH: 3, CRITICAL: 4 };
372
+ const emojiForLevel = (level) => (level === 'CRITICAL' || level === 'HIGH' ? '🔴' : '🔵');
373
+
371
374
  findings.forEach(f => {
372
- grouped[f.ruleId] = (grouped[f.ruleId] || 0) + 1;
375
+ if (!f || !f.ruleId) return;
376
+ const level = mapToLevel(f.severity);
377
+ if (!grouped[f.ruleId]) {
378
+ grouped[f.ruleId] = { count: 0, worstLevel: level };
379
+ }
380
+ grouped[f.ruleId].count += 1;
381
+ if ((levelRank[level] || 0) > (levelRank[grouped[f.ruleId].worstLevel] || 0)) {
382
+ grouped[f.ruleId].worstLevel = level;
383
+ }
384
+ });
385
+
386
+ const entries = Object.entries(grouped)
387
+ .map(([ruleId, data]) => ({ ruleId, count: data.count, worstLevel: data.worstLevel }))
388
+ .sort((a, b) => b.count - a.count);
389
+
390
+ const blockers = entries.filter(e => e.worstLevel === 'CRITICAL' || e.worstLevel === 'HIGH');
391
+ const nonBlockers = entries.filter(e => e.worstLevel !== 'CRITICAL' && e.worstLevel !== 'HIGH');
392
+
393
+ blockers.forEach(({ ruleId, count, worstLevel }) => {
394
+ console.error(`${emojiForLevel(worstLevel)} ${ruleId} - ${count} violations`);
373
395
  });
374
396
 
375
- Object.entries(grouped)
376
- .sort((a, b) => b[1] - a[1])
377
- .slice(0, 20)
378
- .forEach(([ruleId, count]) => {
379
- const severity = ruleId.includes("types.any") || ruleId.includes("security.") || ruleId.includes("architecture.") ? "error" :
380
- ruleId.includes("performance.") || ruleId.includes("debug.") ? "warning" : "info";
381
- const emoji = severity === "error" ? "🔴" : severity === "warning" ? "🟡" : "🔵";
382
- console.error(`${emoji} ${ruleId} - ${count} violations`);
397
+ nonBlockers
398
+ .slice(0, Math.max(0, 20 - blockers.length))
399
+ .forEach(({ ruleId, count, worstLevel }) => {
400
+ console.error(`${emojiForLevel(worstLevel)} ${ruleId} - ${count} violations`);
383
401
  });
384
402
 
385
403
  // Summary