@itz4blitz/agentful 1.2.0 → 1.3.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/README.md +28 -1
- package/bin/cli.js +11 -1055
- package/bin/hooks/block-file-creation.js +271 -0
- package/bin/hooks/product-spec-watcher.js +151 -0
- package/lib/index.js +0 -11
- package/lib/init.js +2 -21
- package/lib/parallel-execution.js +235 -0
- package/lib/presets.js +26 -4
- package/package.json +4 -7
- package/template/.claude/agents/architect.md +2 -2
- package/template/.claude/agents/backend.md +17 -30
- package/template/.claude/agents/frontend.md +17 -39
- package/template/.claude/agents/orchestrator.md +63 -4
- package/template/.claude/agents/product-analyzer.md +1 -1
- package/template/.claude/agents/tester.md +16 -29
- package/template/.claude/commands/agentful-generate.md +221 -14
- package/template/.claude/commands/agentful-init.md +621 -0
- package/template/.claude/commands/agentful-product.md +1 -1
- package/template/.claude/commands/agentful-start.md +99 -1
- package/template/.claude/product/EXAMPLES.md +2 -2
- package/template/.claude/product/index.md +1 -1
- package/template/.claude/settings.json +22 -0
- package/template/.claude/skills/research/SKILL.md +432 -0
- package/template/CLAUDE.md +5 -6
- package/template/bin/hooks/architect-drift-detector.js +242 -0
- package/template/bin/hooks/product-spec-watcher.js +151 -0
- package/version.json +1 -1
- package/bin/hooks/post-agent.js +0 -101
- package/bin/hooks/post-feature.js +0 -227
- package/bin/hooks/pre-agent.js +0 -118
- package/bin/hooks/pre-feature.js +0 -138
- package/lib/VALIDATION_README.md +0 -455
- package/lib/ci/claude-action-integration.js +0 -641
- package/lib/ci/index.js +0 -10
- package/lib/core/analyzer.js +0 -497
- package/lib/core/cli.js +0 -141
- package/lib/core/detectors/conventions.js +0 -342
- package/lib/core/detectors/framework.js +0 -276
- package/lib/core/detectors/index.js +0 -15
- package/lib/core/detectors/language.js +0 -199
- package/lib/core/detectors/patterns.js +0 -356
- package/lib/core/generator.js +0 -626
- package/lib/core/index.js +0 -9
- package/lib/core/output-parser.js +0 -458
- package/lib/core/storage.js +0 -515
- package/lib/core/templates.js +0 -556
- package/lib/pipeline/cli.js +0 -423
- package/lib/pipeline/engine.js +0 -928
- package/lib/pipeline/executor.js +0 -440
- package/lib/pipeline/index.js +0 -33
- package/lib/pipeline/integrations.js +0 -559
- package/lib/pipeline/schemas.js +0 -288
- package/lib/remote/client.js +0 -361
- package/lib/server/auth.js +0 -270
- package/lib/server/client-example.js +0 -190
- package/lib/server/executor.js +0 -477
- package/lib/server/index.js +0 -494
- package/lib/update-helpers.js +0 -505
- package/lib/validation.js +0 -460
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// post-feature.js
|
|
3
|
-
// Feature completion validation and tracking
|
|
4
|
-
|
|
5
|
-
import fs from 'fs';
|
|
6
|
-
import { execSync } from 'child_process';
|
|
7
|
-
|
|
8
|
-
// Helper function to run command and capture output
|
|
9
|
-
function runCommand(command, description) {
|
|
10
|
-
try {
|
|
11
|
-
const output = execSync(command, {
|
|
12
|
-
encoding: 'utf8',
|
|
13
|
-
stdio: 'pipe',
|
|
14
|
-
timeout: 60000 // 60 second timeout
|
|
15
|
-
});
|
|
16
|
-
return { success: true, output };
|
|
17
|
-
} catch (err) {
|
|
18
|
-
return { success: false, output: err.stdout || err.stderr || '' };
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Validate feature completion with automated checks
|
|
24
|
-
* @param {string} feature - Feature name
|
|
25
|
-
* @param {string} domain - Domain name (optional)
|
|
26
|
-
* @returns {object} - { errors, validationResults, exitCode }
|
|
27
|
-
*/
|
|
28
|
-
export function validateFeatureCompletion(feature, domain = '') {
|
|
29
|
-
// Exit successfully if no feature specified
|
|
30
|
-
if (!feature) {
|
|
31
|
-
return { errors: 0, validationResults: [], exitCode: 0 };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const TIMESTAMP = new Date().toISOString();
|
|
35
|
-
let errors = 0;
|
|
36
|
-
const validationResults = [];
|
|
37
|
-
|
|
38
|
-
console.log(`=== Post-Feature Validation: ${feature} ===`);
|
|
39
|
-
console.log('');
|
|
40
|
-
|
|
41
|
-
// Detect stack
|
|
42
|
-
const hasPackageJson = fs.existsSync('package.json');
|
|
43
|
-
const hasPyProject = fs.existsSync('pyproject.toml') || fs.existsSync('requirements.txt');
|
|
44
|
-
const hasGoMod = fs.existsSync('go.mod');
|
|
45
|
-
const hasPomXml = fs.existsSync('pom.xml');
|
|
46
|
-
const hasCargoToml = fs.existsSync('Cargo.toml');
|
|
47
|
-
|
|
48
|
-
// Check 1: Run tests
|
|
49
|
-
console.log('[1/4] Running tests...');
|
|
50
|
-
let testCmd = null;
|
|
51
|
-
if (hasPackageJson) testCmd = 'npm test 2>&1';
|
|
52
|
-
else if (hasPyProject) testCmd = 'pytest 2>&1';
|
|
53
|
-
else if (hasGoMod) testCmd = 'go test ./... 2>&1';
|
|
54
|
-
else if (hasPomXml) testCmd = 'mvn test 2>&1';
|
|
55
|
-
else if (hasCargoToml) testCmd = 'cargo test 2>&1';
|
|
56
|
-
|
|
57
|
-
if (testCmd) {
|
|
58
|
-
const testResult = runCommand(testCmd, 'tests');
|
|
59
|
-
if (testResult.success) {
|
|
60
|
-
console.log(' Tests: PASS');
|
|
61
|
-
validationResults.push('tests:pass');
|
|
62
|
-
} else {
|
|
63
|
-
console.log(' Tests: FAIL');
|
|
64
|
-
validationResults.push('tests:fail');
|
|
65
|
-
errors++;
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
console.log(' Tests: SKIP (no test framework detected)');
|
|
69
|
-
validationResults.push('tests:skip');
|
|
70
|
-
}
|
|
71
|
-
console.log('');
|
|
72
|
-
|
|
73
|
-
// Check 2: Type checking
|
|
74
|
-
console.log('[2/4] Running type check...');
|
|
75
|
-
let typeCmd = null;
|
|
76
|
-
if (fs.existsSync('tsconfig.json')) typeCmd = 'npx tsc --noEmit 2>&1';
|
|
77
|
-
else if (hasPyProject) typeCmd = 'mypy . --ignore-missing-imports 2>&1';
|
|
78
|
-
else if (hasGoMod) typeCmd = 'go vet ./... 2>&1';
|
|
79
|
-
else if (hasCargoToml) typeCmd = 'cargo check 2>&1';
|
|
80
|
-
|
|
81
|
-
if (typeCmd) {
|
|
82
|
-
const tscResult = runCommand(typeCmd, 'type-check');
|
|
83
|
-
if (tscResult.success) {
|
|
84
|
-
console.log(' Type Check: PASS');
|
|
85
|
-
validationResults.push('types:pass');
|
|
86
|
-
} else {
|
|
87
|
-
console.log(' Type Check: FAIL');
|
|
88
|
-
validationResults.push('types:fail');
|
|
89
|
-
errors++;
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
console.log(' Type Check: SKIP (no type checker detected)');
|
|
93
|
-
validationResults.push('types:skip');
|
|
94
|
-
}
|
|
95
|
-
console.log('');
|
|
96
|
-
|
|
97
|
-
// Check 3: Linting
|
|
98
|
-
console.log('[3/4] Running linter...');
|
|
99
|
-
let lintCmd = null;
|
|
100
|
-
if (hasPackageJson) lintCmd = 'npm run lint 2>&1';
|
|
101
|
-
else if (hasPyProject) lintCmd = 'ruff check . 2>&1 || pylint **/*.py 2>&1';
|
|
102
|
-
else if (hasGoMod) lintCmd = 'golangci-lint run 2>&1';
|
|
103
|
-
else if (hasCargoToml) lintCmd = 'cargo clippy -- -D warnings 2>&1';
|
|
104
|
-
|
|
105
|
-
if (lintCmd) {
|
|
106
|
-
const lintResult = runCommand(lintCmd, 'lint');
|
|
107
|
-
if (lintResult.success) {
|
|
108
|
-
console.log(' Lint: PASS');
|
|
109
|
-
validationResults.push('lint:pass');
|
|
110
|
-
} else {
|
|
111
|
-
console.log(' Lint: FAIL');
|
|
112
|
-
validationResults.push('lint:fail');
|
|
113
|
-
errors++;
|
|
114
|
-
}
|
|
115
|
-
} else {
|
|
116
|
-
console.log(' Lint: SKIP (no linter detected)');
|
|
117
|
-
validationResults.push('lint:skip');
|
|
118
|
-
}
|
|
119
|
-
console.log('');
|
|
120
|
-
|
|
121
|
-
// Check 4: Coverage check
|
|
122
|
-
console.log('[4/4] Checking test coverage...');
|
|
123
|
-
console.log(' Coverage: SKIP (run /agentful-validate for full coverage report)');
|
|
124
|
-
validationResults.push('coverage:skip');
|
|
125
|
-
console.log('');
|
|
126
|
-
|
|
127
|
-
// Update completion.json with validation results
|
|
128
|
-
const completionJsonPath = '.agentful/completion.json';
|
|
129
|
-
if (fs.existsSync(completionJsonPath)) {
|
|
130
|
-
try {
|
|
131
|
-
const completionContent = fs.readFileSync(completionJsonPath, 'utf8');
|
|
132
|
-
const completion = JSON.parse(completionContent);
|
|
133
|
-
|
|
134
|
-
const validationStatus = errors > 0 ? 'failed' : 'passed';
|
|
135
|
-
const validationObject = {
|
|
136
|
-
status: validationStatus,
|
|
137
|
-
timestamp: TIMESTAMP,
|
|
138
|
-
errors: errors,
|
|
139
|
-
results: validationResults
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
if (domain) {
|
|
143
|
-
// Hierarchical structure
|
|
144
|
-
if (!completion.domains) completion.domains = {};
|
|
145
|
-
if (!completion.domains[domain]) completion.domains[domain] = {};
|
|
146
|
-
if (!completion.domains[domain].features) completion.domains[domain].features = {};
|
|
147
|
-
if (!completion.domains[domain].features[feature]) completion.domains[domain].features[feature] = {};
|
|
148
|
-
completion.domains[domain].features[feature].validation = validationObject;
|
|
149
|
-
} else {
|
|
150
|
-
// Flat structure
|
|
151
|
-
if (!completion.features) completion.features = {};
|
|
152
|
-
if (!completion.features[feature]) completion.features[feature] = {};
|
|
153
|
-
completion.features[feature].validation = validationObject;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
fs.writeFileSync(completionJsonPath, JSON.stringify(completion, null, 2));
|
|
157
|
-
} catch (err) {
|
|
158
|
-
console.error('WARNING: Failed to update completion.json');
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Log to metrics
|
|
163
|
-
const metricsPath = '.agentful/agent-metrics.json';
|
|
164
|
-
if (fs.existsSync(metricsPath)) {
|
|
165
|
-
try {
|
|
166
|
-
const metricsContent = fs.readFileSync(metricsPath, 'utf8');
|
|
167
|
-
const metrics = JSON.parse(metricsContent);
|
|
168
|
-
|
|
169
|
-
if (!metrics.feature_hooks) metrics.feature_hooks = [];
|
|
170
|
-
metrics.feature_hooks.push({
|
|
171
|
-
hook: 'PostFeature',
|
|
172
|
-
feature: feature,
|
|
173
|
-
domain: domain,
|
|
174
|
-
timestamp: TIMESTAMP,
|
|
175
|
-
result: errors > 0 ? 'failed' : 'passed'
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
fs.writeFileSync(metricsPath, JSON.stringify(metrics, null, 2));
|
|
179
|
-
} catch (err) {
|
|
180
|
-
console.error('WARNING: Failed to update agent-metrics.json');
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Create git commit if all validations pass
|
|
185
|
-
if (errors === 0) {
|
|
186
|
-
console.log('=== All Validations Passed ===');
|
|
187
|
-
console.log('');
|
|
188
|
-
|
|
189
|
-
// Check if there are changes to commit
|
|
190
|
-
try {
|
|
191
|
-
execSync('git diff --quiet 2>/dev/null', { stdio: 'pipe' });
|
|
192
|
-
execSync('git diff --cached --quiet 2>/dev/null', { stdio: 'pipe' });
|
|
193
|
-
console.log('No changes to commit');
|
|
194
|
-
} catch (err) {
|
|
195
|
-
// There are changes
|
|
196
|
-
const commitMsg = domain
|
|
197
|
-
? `feat(${domain}): complete ${feature} feature`
|
|
198
|
-
: `feat: complete ${feature} feature`;
|
|
199
|
-
|
|
200
|
-
console.log('Creating git commit...');
|
|
201
|
-
try {
|
|
202
|
-
execSync('git add -A', { stdio: 'pipe' });
|
|
203
|
-
execSync(`git commit -m "${commitMsg}"`, { stdio: 'pipe' });
|
|
204
|
-
console.log(` Commit created: ${commitMsg}`);
|
|
205
|
-
} catch (err) {
|
|
206
|
-
console.log('WARNING: Git commit failed (this is non-blocking)');
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return { errors: 0, validationResults, exitCode: 0 };
|
|
211
|
-
} else {
|
|
212
|
-
console.log(`=== Validation Failed (${errors} error(s)) ===`);
|
|
213
|
-
console.log('');
|
|
214
|
-
console.log('Fix validation errors before completing feature.');
|
|
215
|
-
console.log('Run /agentful-validate for detailed output');
|
|
216
|
-
return { errors, validationResults, exitCode: 1 };
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// CLI entrypoint
|
|
221
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
222
|
-
const FEATURE = process.env.AGENTFUL_FEATURE || '';
|
|
223
|
-
const DOMAIN = process.env.AGENTFUL_DOMAIN || '';
|
|
224
|
-
|
|
225
|
-
const result = validateFeatureCompletion(FEATURE, DOMAIN);
|
|
226
|
-
process.exit(result.exitCode);
|
|
227
|
-
}
|
package/bin/hooks/pre-agent.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// pre-agent.js
|
|
3
|
-
// Validates agent preconditions before invocation
|
|
4
|
-
|
|
5
|
-
import fs from 'fs';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Validate agent preconditions
|
|
9
|
-
* @param {string} agentName - Agent name
|
|
10
|
-
* @param {string} feature - Feature name (optional)
|
|
11
|
-
* @param {string} domain - Domain name (optional)
|
|
12
|
-
* @returns {object} - { errors, exitCode }
|
|
13
|
-
*/
|
|
14
|
-
export function validateAgentPreconditions(agentName, feature = '', domain = '') {
|
|
15
|
-
// Exit successfully if no agent specified
|
|
16
|
-
if (!agentName) {
|
|
17
|
-
return { errors: 0, exitCode: 0 };
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
let errors = 0;
|
|
21
|
-
|
|
22
|
-
// Check 1: Agent file exists
|
|
23
|
-
const agentFile = `.claude/agents/${agentName}.md`;
|
|
24
|
-
if (!fs.existsSync(agentFile)) {
|
|
25
|
-
console.error(`ERROR: Agent file not found: ${agentFile}`);
|
|
26
|
-
errors++;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Check 2: Required state files exist
|
|
30
|
-
const stateFiles = ['state.json', 'completion.json', 'decisions.json'];
|
|
31
|
-
for (const file of stateFiles) {
|
|
32
|
-
const filePath = `.agentful/${file}`;
|
|
33
|
-
if (!fs.existsSync(filePath)) {
|
|
34
|
-
console.error(`ERROR: Missing state file: ${filePath}`);
|
|
35
|
-
errors++;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Check 3: Validate state.json structure
|
|
40
|
-
const stateJsonPath = '.agentful/state.json';
|
|
41
|
-
if (fs.existsSync(stateJsonPath)) {
|
|
42
|
-
try {
|
|
43
|
-
const stateContent = fs.readFileSync(stateJsonPath, 'utf8');
|
|
44
|
-
const state = JSON.parse(stateContent);
|
|
45
|
-
if (!state.current_phase) {
|
|
46
|
-
console.error('ERROR: .agentful/state.json is malformed (missing current_phase)');
|
|
47
|
-
errors++;
|
|
48
|
-
}
|
|
49
|
-
} catch (err) {
|
|
50
|
-
console.error('ERROR: .agentful/state.json is invalid JSON');
|
|
51
|
-
errors++;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Check 4: If feature specified, verify it exists in product spec
|
|
56
|
-
if (feature) {
|
|
57
|
-
let featureFound = false;
|
|
58
|
-
|
|
59
|
-
// Check hierarchical structure
|
|
60
|
-
if (domain) {
|
|
61
|
-
const featureFile = `.claude/product/domains/${domain}/features/${feature}.md`;
|
|
62
|
-
if (fs.existsSync(featureFile)) {
|
|
63
|
-
featureFound = true;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Check flat structure
|
|
68
|
-
if (!featureFound) {
|
|
69
|
-
const featureFile = `.claude/product/features/${feature}.md`;
|
|
70
|
-
if (fs.existsSync(featureFile)) {
|
|
71
|
-
featureFound = true;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (!featureFound) {
|
|
76
|
-
console.log(`WARNING: Feature '${feature}' not found in product specification`);
|
|
77
|
-
// Warning only, don't block
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Check 5: Verify orchestrator isn't blocked
|
|
82
|
-
if (fs.existsSync(stateJsonPath)) {
|
|
83
|
-
try {
|
|
84
|
-
const stateContent = fs.readFileSync(stateJsonPath, 'utf8');
|
|
85
|
-
const state = JSON.parse(stateContent);
|
|
86
|
-
const blockedOn = state.blocked_on || [];
|
|
87
|
-
if (Array.isArray(blockedOn) && blockedOn.length > 0) {
|
|
88
|
-
console.log(`WARNING: Orchestrator is blocked on decisions: ${JSON.stringify(blockedOn)}`);
|
|
89
|
-
// Warning only, don't block
|
|
90
|
-
}
|
|
91
|
-
} catch (err) {
|
|
92
|
-
// Already handled in Check 3
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Exit with error if critical checks failed
|
|
97
|
-
if (errors > 0) {
|
|
98
|
-
console.log('');
|
|
99
|
-
console.log(`Pre-agent validation failed with ${errors} error(s)`);
|
|
100
|
-
console.log(`Agent: ${agentName}`);
|
|
101
|
-
if (feature) console.log(`Feature: ${feature}`);
|
|
102
|
-
if (domain) console.log(`Domain: ${domain}`);
|
|
103
|
-
return { errors, exitCode: 1 };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// All checks passed
|
|
107
|
-
return { errors: 0, exitCode: 0 };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// CLI entrypoint
|
|
111
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
112
|
-
const AGENT_NAME = process.env.AGENTFUL_AGENT || '';
|
|
113
|
-
const FEATURE = process.env.AGENTFUL_FEATURE || '';
|
|
114
|
-
const DOMAIN = process.env.AGENTFUL_DOMAIN || '';
|
|
115
|
-
|
|
116
|
-
const result = validateAgentPreconditions(AGENT_NAME, FEATURE, DOMAIN);
|
|
117
|
-
process.exit(result.exitCode);
|
|
118
|
-
}
|
package/bin/hooks/pre-feature.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// pre-feature.js
|
|
3
|
-
// Validates feature readiness before implementation
|
|
4
|
-
|
|
5
|
-
import fs from 'fs';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Validate feature readiness before implementation
|
|
9
|
-
* @param {string} feature - Feature name
|
|
10
|
-
* @param {string} domain - Domain name (optional)
|
|
11
|
-
* @returns {object} - { errors, warnings, exitCode }
|
|
12
|
-
*/
|
|
13
|
-
export function validateFeatureReadiness(feature, domain = '') {
|
|
14
|
-
// Exit successfully if no feature specified
|
|
15
|
-
if (!feature) {
|
|
16
|
-
return { errors: 0, warnings: 0, exitCode: 0 };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
let errors = 0;
|
|
20
|
-
let warnings = 0;
|
|
21
|
-
|
|
22
|
-
// Check 1: Feature file exists
|
|
23
|
-
let featureFile = '';
|
|
24
|
-
if (domain) {
|
|
25
|
-
// Hierarchical structure
|
|
26
|
-
featureFile = `.claude/product/domains/${domain}/features/${feature}.md`;
|
|
27
|
-
} else {
|
|
28
|
-
// Flat structure
|
|
29
|
-
featureFile = `.claude/product/features/${feature}.md`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!fs.existsSync(featureFile)) {
|
|
33
|
-
console.error(`ERROR: Feature file not found: ${featureFile}`);
|
|
34
|
-
errors++;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Check 2: Verify completion.json exists
|
|
38
|
-
const completionJsonPath = '.agentful/completion.json';
|
|
39
|
-
if (!fs.existsSync(completionJsonPath)) {
|
|
40
|
-
console.error('ERROR: .agentful/completion.json not found');
|
|
41
|
-
errors++;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Check 3: Check feature dependencies
|
|
45
|
-
if (fs.existsSync(completionJsonPath)) {
|
|
46
|
-
try {
|
|
47
|
-
const completionContent = fs.readFileSync(completionJsonPath, 'utf8');
|
|
48
|
-
const completion = JSON.parse(completionContent);
|
|
49
|
-
|
|
50
|
-
// Check if feature has dependencies that are incomplete
|
|
51
|
-
if (domain) {
|
|
52
|
-
// Hierarchical: check domain-level dependencies
|
|
53
|
-
const domainStatus = completion.domains?.[domain]?.status || 'unknown';
|
|
54
|
-
if (domainStatus === 'blocked') {
|
|
55
|
-
console.error(`ERROR: Domain '${domain}' is blocked`);
|
|
56
|
-
errors++;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Check for blocking decisions
|
|
61
|
-
const decisionsJsonPath = '.agentful/decisions.json';
|
|
62
|
-
if (fs.existsSync(decisionsJsonPath)) {
|
|
63
|
-
try {
|
|
64
|
-
const decisionsContent = fs.readFileSync(decisionsJsonPath, 'utf8');
|
|
65
|
-
const decisions = JSON.parse(decisionsContent);
|
|
66
|
-
|
|
67
|
-
const featurePath = domain ? `${domain}/${feature}` : feature;
|
|
68
|
-
const blockingDecisions = (decisions.pending || [])
|
|
69
|
-
.filter(d => (d.blocking || []).some(b => b.includes(featurePath)))
|
|
70
|
-
.map(d => d.id);
|
|
71
|
-
|
|
72
|
-
if (blockingDecisions.length > 0) {
|
|
73
|
-
console.error(`ERROR: Feature '${feature}' is blocked by decisions: ${blockingDecisions.join(', ')}`);
|
|
74
|
-
console.error('Run /agentful-decide to resolve blocking decisions');
|
|
75
|
-
errors++;
|
|
76
|
-
}
|
|
77
|
-
} catch (err) {
|
|
78
|
-
// Ignore JSON parse errors for decisions.json
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
} catch (err) {
|
|
82
|
-
// Ignore JSON parse errors for completion.json
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Check 4: Tech stack compatibility (if architecture.json exists)
|
|
87
|
-
const architectureJsonPath = '.agentful/architecture.json';
|
|
88
|
-
if (fs.existsSync(architectureJsonPath)) {
|
|
89
|
-
try {
|
|
90
|
-
const archContent = fs.readFileSync(architectureJsonPath, 'utf8');
|
|
91
|
-
const arch = JSON.parse(archContent);
|
|
92
|
-
const techStack = arch.techStack;
|
|
93
|
-
|
|
94
|
-
if (!techStack || techStack === null) {
|
|
95
|
-
console.log('WARNING: Tech stack not analyzed. Run /agentful-generate');
|
|
96
|
-
warnings++;
|
|
97
|
-
}
|
|
98
|
-
} catch (err) {
|
|
99
|
-
// Ignore JSON parse errors
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Check 5: Verify required agents exist
|
|
104
|
-
const requiredAgents = ['backend', 'frontend', 'tester', 'reviewer'];
|
|
105
|
-
for (const agent of requiredAgents) {
|
|
106
|
-
const agentPath = `.claude/agents/${agent}.md`;
|
|
107
|
-
if (!fs.existsSync(agentPath)) {
|
|
108
|
-
console.log(`WARNING: Core agent missing: ${agentPath}`);
|
|
109
|
-
warnings++;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Exit with error if critical checks failed
|
|
114
|
-
if (errors > 0) {
|
|
115
|
-
console.log('');
|
|
116
|
-
console.log(`Pre-feature validation failed with ${errors} error(s)`);
|
|
117
|
-
console.log(`Feature: ${feature}`);
|
|
118
|
-
if (domain) console.log(`Domain: ${domain}`);
|
|
119
|
-
return { errors, warnings, exitCode: 1 };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (warnings > 0) {
|
|
123
|
-
console.log('');
|
|
124
|
-
console.log(`Pre-feature validation passed with ${warnings} warning(s)`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// All checks passed
|
|
128
|
-
return { errors: 0, warnings, exitCode: 0 };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// CLI entrypoint
|
|
132
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
133
|
-
const FEATURE = process.env.AGENTFUL_FEATURE || '';
|
|
134
|
-
const DOMAIN = process.env.AGENTFUL_DOMAIN || '';
|
|
135
|
-
|
|
136
|
-
const result = validateFeatureReadiness(FEATURE, DOMAIN);
|
|
137
|
-
process.exit(result.exitCode);
|
|
138
|
-
}
|