@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.
- package/bin/hooks/context-awareness.js +1 -1
- package/lib/parallel-execution.js +26 -2
- package/package.json +1 -1
- package/template/.claude/agents/backend.md +56 -1
- package/template/.claude/agents/fixer.md +31 -5
- package/template/.claude/agents/frontend.md +61 -1
- package/template/.claude/agents/orchestrator.md +126 -10
- package/template/.claude/agents/tester.md +32 -1
- package/template/.claude/commands/agentful.md +17 -8
- package/template/CLAUDE.md +31 -8
- package/template/bin/hooks/architect-drift-detector.js +195 -28
- package/template/bin/hooks/context-awareness.js +369 -0
- package/template/bin/hooks/health-check.js +1 -1
- package/template/bin/hooks/mcp-health-check.js +51 -0
- package/template/bin/hooks/post-action-suggestions.js +75 -0
- package/template/bin/hooks/session-start.js +93 -0
- package/version.json +1 -1
|
@@ -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
|
|
37
|
-
|
|
38
|
-
|
|
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,
|
|
59
|
+
// If drift detected, evaluate if it's meaningful
|
|
57
60
|
if (driftReasons.length > 0) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
95
|
-
'requirements.txt',
|
|
96
|
-
'pyproject.toml',
|
|
97
|
-
'Pipfile',
|
|
98
|
-
'go.mod',
|
|
99
|
-
'
|
|
100
|
-
'
|
|
101
|
-
'
|
|
102
|
-
'
|
|
103
|
-
'
|
|
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
|
-
|
|
109
|
-
if (!fs.existsSync(file)) continue;
|
|
113
|
+
const newLibraries = [];
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
|
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();
|