@itz4blitz/agentful 1.4.0 → 1.5.0

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.
@@ -32,10 +32,13 @@ function detectArchitectDrift() {
32
32
 
33
33
  // Check various drift indicators
34
34
  const driftReasons = [];
35
+ const newLibraries = [];
35
36
 
36
- // 1. Check if dependency files changed
37
- if (dependenciesChanged(arch)) {
38
- driftReasons.push('dependencies_changed');
37
+ // 1. Check if NEW dependencies added (not just version bumps)
38
+ const depChange = dependenciesChanged(arch);
39
+ if (depChange.changed) {
40
+ newLibraries.push(...depChange.newLibraries);
41
+ driftReasons.push('new_libraries_added');
39
42
  }
40
43
 
41
44
  // 2. Check if tech stack files modified
@@ -53,12 +56,15 @@ function detectArchitectDrift() {
53
56
  driftReasons.push('new_patterns_detected');
54
57
  }
55
58
 
56
- // If drift detected, mark for re-analysis
59
+ // If drift detected, evaluate if it's meaningful
57
60
  if (driftReasons.length > 0) {
58
- markForReanalysis(arch, driftReasons);
59
- console.log(`⚠️ Architect drift detected: ${driftReasons.join(', ')}`);
60
- console.log(' Run /agentful-generate to update skills and agents');
61
- return true;
61
+ // Only trigger if the drift is significant
62
+ if (isMeaningfulDrift(driftReasons, arch, newLibraries)) {
63
+ markForReanalysis(arch, driftReasons, newLibraries);
64
+ console.log(`⚠️ Architecture changed: ${formatDriftReasons(driftReasons, newLibraries)}`);
65
+ console.log(' Run /agentful-generate to update skills and agents');
66
+ return true;
67
+ }
62
68
  }
63
69
 
64
70
  return false;
@@ -87,36 +93,110 @@ function loadArchitecture() {
87
93
 
88
94
  /**
89
95
  * Check if dependency files changed
96
+ * Returns: { changed: boolean, newLibraries: string[] }
90
97
  */
91
98
  function dependenciesChanged(arch) {
92
99
  const depFiles = [
93
- 'package.json',
94
- 'package-lock.json',
95
- 'requirements.txt',
96
- 'pyproject.toml',
97
- 'Pipfile',
98
- 'go.mod',
99
- 'go.sum',
100
- 'Gemfile',
101
- 'Gemfile.lock',
102
- 'composer.json',
103
- 'pom.xml',
104
- 'build.gradle',
105
- 'Cargo.toml'
100
+ { file: 'package.json', type: 'json', key: 'dependencies' },
101
+ { file: 'package.json', type: 'json', key: 'devDependencies' },
102
+ { file: 'requirements.txt', type: 'txt' },
103
+ { file: 'pyproject.toml', type: 'toml', key: 'dependencies' },
104
+ { file: 'Pipfile', type: 'toml', key: 'packages' },
105
+ { file: 'go.mod', type: 'go' },
106
+ { file: 'Gemfile', type: 'ruby' },
107
+ { file: 'composer.json', type: 'json', key: 'require' },
108
+ { file: 'pom.xml', type: 'xml' },
109
+ { file: 'build.gradle', type: 'gradle' },
110
+ { file: 'Cargo.toml', type: 'toml', key: 'dependencies' }
106
111
  ];
107
112
 
108
- for (const file of depFiles) {
109
- if (!fs.existsSync(file)) continue;
113
+ const newLibraries = [];
110
114
 
111
- const currentHash = hashFile(file);
112
- const previousHash = arch.dependency_hashes?.[file];
115
+ for (const depFile of depFiles) {
116
+ if (!fs.existsSync(depFile.file)) continue;
117
+
118
+ const currentHash = hashFile(depFile.file);
119
+ const previousHash = arch.dependency_hashes?.[depFile.file];
113
120
 
114
121
  if (previousHash && currentHash !== previousHash) {
115
- return true;
122
+ // Check if NEW libraries were added (not just version bumps)
123
+ const added = getNewLibraries(depFile.file, depFile, arch);
124
+ if (added.length > 0) {
125
+ newLibraries.push(...added);
126
+ }
116
127
  }
117
128
  }
118
129
 
119
- return false;
130
+ return {
131
+ changed: newLibraries.length > 0,
132
+ newLibraries
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Get list of new libraries added since last analysis
138
+ */
139
+ function getNewLibraries(filePath, fileInfo, arch) {
140
+ try {
141
+ const content = fs.readFileSync(filePath, 'utf8');
142
+ const previousContent = arch.previous_dependencies?.[filePath] || '';
143
+
144
+ if (!previousContent) return []; // First time, can't determine new vs old
145
+
146
+ // Parse current and previous dependencies
147
+ const currentDeps = parseDependencies(content, fileInfo);
148
+ const previousDeps = parseDependencies(previousContent, fileInfo);
149
+
150
+ // Find libraries in current but not in previous
151
+ const newLibs = Object.keys(currentDeps).filter(lib => !previousDeps[lib]);
152
+
153
+ return newLibs;
154
+ } catch (error) {
155
+ return [];
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Parse dependencies from various file formats
161
+ */
162
+ function parseDependencies(content, fileInfo) {
163
+ const deps = {};
164
+
165
+ try {
166
+ if (fileInfo.file === 'package.json') {
167
+ const json = JSON.parse(content);
168
+ const section = json[fileInfo.key] || {};
169
+ Object.keys(section).forEach(key => {
170
+ deps[key] = section[key];
171
+ });
172
+ } else if (fileInfo.file === 'requirements.txt') {
173
+ content.split('\n').forEach(line => {
174
+ line = line.trim();
175
+ if (line && !line.startsWith('#')) {
176
+ const name = line.split('==')[0].split('>=')[0].split('<=')[0].trim();
177
+ if (name) deps[name] = line;
178
+ }
179
+ });
180
+ } else if (fileInfo.file === 'go.mod') {
181
+ const lines = content.split('\n');
182
+ let inRequire = false;
183
+ lines.forEach(line => {
184
+ if (line.trim().startsWith('require (')) inRequire = true;
185
+ else if (line.trim() === ')') inRequire = false;
186
+ else if (inRequire || line.trim().startsWith('require ')) {
187
+ const parts = line.trim().split(/\s+/);
188
+ if (parts.length >= 2 && parts[0] !== 'require') {
189
+ deps[parts[0]] = parts[1];
190
+ }
191
+ }
192
+ });
193
+ }
194
+ // Add more formats as needed...
195
+ } catch (error) {
196
+ // Silently fail parsing
197
+ }
198
+
199
+ return deps;
120
200
  }
121
201
 
122
202
  /**
@@ -220,14 +300,101 @@ function hashFile(filePath) {
220
300
  return crypto.createHash('md5').update(content).digest('hex');
221
301
  }
222
302
 
303
+ /**
304
+ * Determine if drift is meaningful enough to trigger re-analysis
305
+ *
306
+ * Meaningful changes:
307
+ * - NEW libraries added (not version bumps)
308
+ * - Tech stack fundamentally changed (switched frameworks)
309
+ * - Significant new patterns (20%+ file growth AND 50+ new files)
310
+ * - Stale analysis (>30 days)
311
+ *
312
+ * NOT meaningful:
313
+ * - Version bumps only
314
+ * - Small file additions
315
+ * - Recent analysis (<30 days ago)
316
+ */
317
+ function isMeaningfulDrift(reasons, arch, newLibraries) {
318
+ // New libraries are ALWAYS meaningful (might create new skills/agents)
319
+ if (reasons.includes('new_libraries_added')) {
320
+ return true;
321
+ }
322
+
323
+ // If analysis is stale (>30 days), any drift is meaningful
324
+ if (analysisIsStale(arch)) {
325
+ const daysSinceAnalysis = getDaysSinceAnalysis(arch);
326
+ if (daysSinceAnalysis > 30) {
327
+ return true;
328
+ }
329
+ }
330
+
331
+ // Tech stack changes are always meaningful
332
+ if (reasons.includes('tech_stack_modified')) {
333
+ return true;
334
+ }
335
+
336
+ // New patterns are meaningful if significant growth
337
+ if (reasons.includes('new_patterns_detected')) {
338
+ const currentFileCount = countSourceFiles();
339
+ const previousFileCount = arch.file_count || 0;
340
+ const growthRatio = (currentFileCount - previousFileCount) / Math.max(previousFileCount, 1);
341
+
342
+ // Only if >20% growth AND at least 50 new files
343
+ const newFileCount = currentFileCount - previousFileCount;
344
+ if (growthRatio > 0.2 && newFileCount > 50) {
345
+ return true;
346
+ }
347
+ }
348
+
349
+ return false;
350
+ }
351
+
352
+ /**
353
+ * Get days since last analysis
354
+ */
355
+ function getDaysSinceAnalysis(arch) {
356
+ if (!arch.last_analysis) return 999;
357
+
358
+ const lastAnalysis = new Date(arch.last_analysis);
359
+ return (Date.now() - lastAnalysis.getTime()) / (1000 * 60 * 60 * 24);
360
+ }
361
+
362
+ /**
363
+ * Format drift reasons for display
364
+ */
365
+ function formatDriftReasons(reasons, newLibraries) {
366
+ const labels = {
367
+ 'new_libraries_added': `new libraries: ${newLibraries.slice(0, 3).join(', ')}${newLibraries.length > 3 ? '...' : ''}`,
368
+ 'tech_stack_modified': 'tech stack changed',
369
+ 'analysis_stale': 'analysis outdated',
370
+ 'new_patterns_detected': 'new code patterns detected'
371
+ };
372
+
373
+ return reasons.map(r => labels[r] || r).join(', ');
374
+ }
375
+
223
376
  /**
224
377
  * Mark architecture.json for re-analysis
378
+ * Also stores current dependencies for next comparison
225
379
  */
226
- function markForReanalysis(arch, reasons) {
380
+ function markForReanalysis(arch, reasons, newLibraries) {
227
381
  arch.needs_reanalysis = true;
228
382
  arch.drift_reasons = reasons;
383
+ arch.new_libraries = newLibraries;
229
384
  arch.drift_detected_at = new Date().toISOString();
230
385
 
386
+ // Store current dependencies for future comparison
387
+ if (!arch.previous_dependencies) {
388
+ arch.previous_dependencies = {};
389
+ }
390
+
391
+ const depFiles = ['package.json', 'requirements.txt', 'go.mod', 'Pipfile', 'Cargo.toml'];
392
+ depFiles.forEach(file => {
393
+ if (fs.existsSync(file)) {
394
+ arch.previous_dependencies[file] = fs.readFileSync(file, 'utf8');
395
+ }
396
+ });
397
+
231
398
  // Ensure directory exists
232
399
  const dir = path.dirname(ARCHITECTURE_FILE);
233
400
  if (!fs.existsSync(dir)) {
@@ -0,0 +1,369 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Context Awareness Module
5
+ *
6
+ * Analyzes project state and provides intelligent suggestions for next actions.
7
+ * Used by session-start and post-action hooks.
8
+ */
9
+
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import { fileURLToPath } from 'url';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+
17
+ /**
18
+ * Read JSON file safely
19
+ */
20
+ function readJSON(filePath) {
21
+ try {
22
+ if (!fs.existsSync(filePath)) return null;
23
+ const content = fs.readFileSync(filePath, 'utf8');
24
+ return JSON.parse(content);
25
+ } catch (error) {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Check if file exists
32
+ */
33
+ function fileExists(filePath) {
34
+ try {
35
+ return fs.existsSync(filePath);
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Analyze project state and return context
43
+ */
44
+ export function analyzeProjectState(projectRoot = process.cwd()) {
45
+ const state = {
46
+ hasProductSpec: false,
47
+ hasArchitecture: false,
48
+ hasState: false,
49
+ hasCompletion: false,
50
+ hasPendingDecisions: false,
51
+ architectureValid: true,
52
+ architectureIssues: [],
53
+ currentPhase: 'idle',
54
+ completionPercent: 0,
55
+ totalFeatures: 0,
56
+ completedFeatures: 0,
57
+ pendingDecisionCount: 0,
58
+ blockingIssues: [],
59
+ suggestedActions: []
60
+ };
61
+
62
+ // Check product spec
63
+ const productIndex = path.join(projectRoot, '.claude/product/index.md');
64
+ const productDomains = path.join(projectRoot, '.claude/product/domains');
65
+ state.hasProductSpec = fileExists(productIndex) || fileExists(productDomains);
66
+
67
+ // Check architecture
68
+ const architecturePath = path.join(projectRoot, '.agentful/architecture.json');
69
+ if (fileExists(architecturePath)) {
70
+ state.hasArchitecture = true;
71
+ const arch = readJSON(architecturePath);
72
+
73
+ if (arch) {
74
+ // Validate architecture structure
75
+ if (!arch.techStack || !arch.agents) {
76
+ state.architectureValid = false;
77
+ state.architectureIssues.push('Missing techStack or agents fields');
78
+ }
79
+
80
+ // Check if architecture is stale (older than package.json)
81
+ try {
82
+ const packagePath = path.join(projectRoot, 'package.json');
83
+ if (fileExists(packagePath)) {
84
+ const archStat = fs.statSync(architecturePath);
85
+ const pkgStat = fs.statSync(packagePath);
86
+
87
+ if (pkgStat.mtime > archStat.mtime) {
88
+ state.architectureValid = false;
89
+ state.architectureIssues.push('Architecture older than package.json - may need regeneration');
90
+ }
91
+ }
92
+ } catch (error) {
93
+ // Ignore stat errors
94
+ }
95
+ } else {
96
+ state.architectureValid = false;
97
+ state.architectureIssues.push('Invalid JSON format');
98
+ }
99
+ }
100
+
101
+ // Check state
102
+ const statePath = path.join(projectRoot, '.agentful/state.json');
103
+ if (fileExists(statePath)) {
104
+ state.hasState = true;
105
+ const stateData = readJSON(statePath);
106
+ if (stateData) {
107
+ state.currentPhase = stateData.current_phase || 'idle';
108
+ }
109
+ }
110
+
111
+ // Check completion
112
+ const completionPath = path.join(projectRoot, '.agentful/completion.json');
113
+ if (fileExists(completionPath)) {
114
+ state.hasCompletion = true;
115
+ const completion = readJSON(completionPath);
116
+
117
+ if (completion && completion.features) {
118
+ const features = Object.values(completion.features);
119
+ state.totalFeatures = features.length;
120
+ state.completedFeatures = features.filter(f => f.completion >= 100).length;
121
+
122
+ if (state.totalFeatures > 0) {
123
+ state.completionPercent = Math.round((state.completedFeatures / state.totalFeatures) * 100);
124
+ }
125
+ }
126
+ }
127
+
128
+ // Check decisions
129
+ const decisionsPath = path.join(projectRoot, '.agentful/decisions.json');
130
+ if (fileExists(decisionsPath)) {
131
+ const decisions = readJSON(decisionsPath);
132
+
133
+ if (decisions && decisions.pending) {
134
+ state.pendingDecisionCount = decisions.pending.length;
135
+ state.hasPendingDecisions = state.pendingDecisionCount > 0;
136
+ }
137
+ }
138
+
139
+ // Determine blocking issues
140
+ if (!state.hasProductSpec) {
141
+ state.blockingIssues.push('No product specification found');
142
+ }
143
+
144
+ if (state.hasArchitecture && !state.architectureValid) {
145
+ state.blockingIssues.push('Architecture needs attention');
146
+ }
147
+
148
+ if (state.hasPendingDecisions) {
149
+ state.blockingIssues.push(`${state.pendingDecisionCount} pending decision(s)`);
150
+ }
151
+
152
+ // Generate suggested actions
153
+ state.suggestedActions = generateSuggestions(state);
154
+
155
+ return state;
156
+ }
157
+
158
+ /**
159
+ * Generate smart suggestions based on project state
160
+ */
161
+ function generateSuggestions(state) {
162
+ const suggestions = [];
163
+
164
+ // First-time setup
165
+ if (!state.hasProductSpec) {
166
+ suggestions.push({
167
+ priority: 'critical',
168
+ action: 'Create product specification',
169
+ command: 'Edit .claude/product/index.md',
170
+ description: 'Define your product requirements'
171
+ });
172
+ return suggestions; // Block other suggestions until product spec exists
173
+ }
174
+
175
+ if (!state.hasArchitecture) {
176
+ suggestions.push({
177
+ priority: 'critical',
178
+ action: 'Generate architecture',
179
+ command: '/agentful-generate',
180
+ description: 'Analyze tech stack and create specialized agents'
181
+ });
182
+ return suggestions;
183
+ }
184
+
185
+ // Architecture issues
186
+ if (state.hasArchitecture && !state.architectureValid) {
187
+ suggestions.push({
188
+ priority: 'high',
189
+ action: 'Fix architecture',
190
+ command: '/agentful-generate',
191
+ description: state.architectureIssues.join('; ')
192
+ });
193
+ }
194
+
195
+ // Pending decisions block work
196
+ if (state.hasPendingDecisions) {
197
+ suggestions.push({
198
+ priority: 'high',
199
+ action: 'Answer pending decisions',
200
+ command: '/agentful-decide',
201
+ description: `${state.pendingDecisionCount} decision(s) blocking progress`
202
+ });
203
+ }
204
+
205
+ // Phase-specific suggestions
206
+ if (state.currentPhase === 'idle' || state.currentPhase === 'planning') {
207
+ suggestions.push({
208
+ priority: 'medium',
209
+ action: 'Start development',
210
+ command: '/agentful-start',
211
+ description: 'Begin or resume structured development'
212
+ });
213
+ }
214
+
215
+ if (state.currentPhase === 'implementation' || state.currentPhase === 'testing') {
216
+ suggestions.push({
217
+ priority: 'medium',
218
+ action: 'Check progress',
219
+ command: '/agentful-status',
220
+ description: `Current: ${state.completionPercent}% complete (${state.completedFeatures}/${state.totalFeatures} features)`
221
+ });
222
+ }
223
+
224
+ // Validation suggestion
225
+ if (state.hasCompletion && state.completionPercent > 0) {
226
+ suggestions.push({
227
+ priority: 'low',
228
+ action: 'Run quality checks',
229
+ command: '/agentful-validate',
230
+ description: 'Verify code quality and production readiness'
231
+ });
232
+ }
233
+
234
+ // Product analysis
235
+ suggestions.push({
236
+ priority: 'low',
237
+ action: 'Analyze product spec',
238
+ command: '/agentful-product',
239
+ description: 'Check for gaps and ambiguities'
240
+ });
241
+
242
+ return suggestions;
243
+ }
244
+
245
+ /**
246
+ * Format suggestions for display
247
+ */
248
+ export function formatSuggestions(suggestions, options = {}) {
249
+ const { maxSuggestions = 5, includeNumbers = true } = options;
250
+
251
+ if (suggestions.length === 0) {
252
+ return '💡 All set! Type a command or ask what you need.';
253
+ }
254
+
255
+ // Sort by priority
256
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
257
+ const sorted = suggestions.sort((a, b) => {
258
+ return priorityOrder[a.priority] - priorityOrder[b.priority];
259
+ });
260
+
261
+ // Take top N
262
+ const topSuggestions = sorted.slice(0, maxSuggestions);
263
+
264
+ // Format output
265
+ let output = '💡 Suggested next steps:\n';
266
+
267
+ topSuggestions.forEach((suggestion, index) => {
268
+ const number = includeNumbers ? `${index + 1}. ` : ' • ';
269
+ const icon = suggestion.priority === 'critical' ? '🔴' :
270
+ suggestion.priority === 'high' ? '⚠️ ' : '';
271
+
272
+ output += ` ${number}${icon}${suggestion.action} → ${suggestion.command}\n`;
273
+ if (suggestion.description) {
274
+ output += ` ${suggestion.description}\n`;
275
+ }
276
+ });
277
+
278
+ return output.trim();
279
+ }
280
+
281
+ /**
282
+ * Generate session start message
283
+ */
284
+ export function generateSessionStartMessage(projectRoot = process.cwd()) {
285
+ const state = analyzeProjectState(projectRoot);
286
+ let message = '';
287
+
288
+ // Status line
289
+ if (state.hasCompletion && state.totalFeatures > 0) {
290
+ message += `📊 Project Status: ${state.completionPercent}% complete (${state.completedFeatures}/${state.totalFeatures} features)\n`;
291
+ } else if (state.hasArchitecture) {
292
+ message += '📊 Project Status: Architecture ready, no active development\n';
293
+ } else {
294
+ message += '📊 Project Status: Initial setup\n';
295
+ }
296
+
297
+ // Blocking issues
298
+ if (state.blockingIssues.length > 0) {
299
+ state.blockingIssues.forEach(issue => {
300
+ message += `⚠️ ${issue}\n`;
301
+ });
302
+ message += '\n';
303
+ }
304
+
305
+ // Current phase
306
+ if (state.currentPhase !== 'idle') {
307
+ message += `🔄 Current Phase: ${state.currentPhase}\n\n`;
308
+ }
309
+
310
+ // Suggestions
311
+ message += formatSuggestions(state.suggestedActions, { maxSuggestions: 3 });
312
+
313
+ return message;
314
+ }
315
+
316
+ /**
317
+ * Generate post-action suggestions
318
+ */
319
+ export function generatePostActionSuggestions(action, projectRoot = process.cwd()) {
320
+ const state = analyzeProjectState(projectRoot);
321
+
322
+ // Action-specific follow-ups
323
+ const actionMap = {
324
+ 'agentful-generate': () => {
325
+ if (state.completionPercent === 0) {
326
+ return [{
327
+ priority: 'high',
328
+ action: 'Start development',
329
+ command: '/agentful-start',
330
+ description: 'Begin building features'
331
+ }];
332
+ }
333
+ return [];
334
+ },
335
+
336
+ 'agentful-start': () => [{
337
+ priority: 'medium',
338
+ action: 'Monitor progress',
339
+ command: '/agentful-status',
340
+ description: 'Check completion and active work'
341
+ }],
342
+
343
+ 'agentful-decide': () => {
344
+ if (state.pendingDecisionCount === 0) {
345
+ return [{
346
+ priority: 'high',
347
+ action: 'Resume development',
348
+ command: '/agentful-start',
349
+ description: 'Continue with unblocked work'
350
+ }];
351
+ }
352
+ return [];
353
+ },
354
+
355
+ 'agentful-validate': () => [{
356
+ priority: 'medium',
357
+ action: 'Fix validation issues',
358
+ command: 'Review output and address failures',
359
+ description: 'Fixer agent can auto-resolve some issues'
360
+ }]
361
+ };
362
+
363
+ const specificSuggestions = actionMap[action]?.() || [];
364
+ const generalSuggestions = state.suggestedActions.filter(s =>
365
+ s.command !== `/${action}`
366
+ );
367
+
368
+ return [...specificSuggestions, ...generalSuggestions];
369
+ }
@@ -110,7 +110,7 @@ if (!fs.existsSync(architecturePath)) {
110
110
  try {
111
111
  const archContent = fs.readFileSync(architecturePath, 'utf8');
112
112
  const arch = JSON.parse(archContent);
113
- if (!arch.techStack || !arch.domains) {
113
+ if ((!arch.tech_stack && !arch.techStack) || !arch.domains) {
114
114
  console.log('⚠️ .agentful/architecture.json is malformed');
115
115
  console.log(' Run /agentful-generate to regenerate');
116
116
  warnings++;
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server Health Check Hook
4
+ *
5
+ * Checks if the Agentful MCP server is running and properly configured.
6
+ * This should be run in the SessionStart hook after the main health check.
7
+ */
8
+
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+
12
+ /**
13
+ * Check if MCP server tools are available
14
+ * This is a basic check - actual tool testing happens when tools are called
15
+ */
16
+ function checkMCPServerHealth() {
17
+ const mcpServerPath = path.resolve(process.cwd(), 'mcp-server');
18
+
19
+ // Skip check if MCP server directory doesn't exist
20
+ if (!fs.existsSync(mcpServerPath)) {
21
+ return; // Silent skip - MCP server is optional
22
+ }
23
+
24
+ // Check if MCP server is built
25
+ const distPath = path.join(mcpServerPath, 'dist');
26
+ if (!fs.existsSync(distPath)) {
27
+ console.log('⚠️ MCP server not built. Run: cd mcp-server && npm run build');
28
+ return;
29
+ }
30
+
31
+ // Check if WASM file exists
32
+ const wasmPaths = [
33
+ path.join(mcpServerPath, 'dist/infrastructure/sql-wasm.wasm'),
34
+ path.join(mcpServerPath, 'dist/sql-wasm.wasm'),
35
+ path.join(mcpServerPath, 'sql-wasm.wasm')
36
+ ];
37
+
38
+ const wasmExists = wasmPaths.some(p => fs.existsSync(p));
39
+ if (!wasmExists) {
40
+ console.log('⚠️ MCP server WASM file missing. Run: cd mcp-server && npm run build');
41
+ return;
42
+ }
43
+
44
+ // All checks passed
45
+ if (process.env.AGENTFUL_LOG_LEVEL === 'debug') {
46
+ console.log('✅ MCP server health check passed');
47
+ }
48
+ }
49
+
50
+ // Run health check
51
+ checkMCPServerHealth();