@itz4blitz/agentful 0.4.0 → 1.0.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.
Files changed (93) hide show
  1. package/README.md +131 -16
  2. package/bin/cli.js +1031 -47
  3. package/bin/hooks/README.md +338 -82
  4. package/bin/hooks/analyze-trigger.js +69 -0
  5. package/bin/hooks/block-random-docs.js +77 -0
  6. package/bin/hooks/health-check.js +153 -0
  7. package/bin/hooks/post-agent.js +101 -0
  8. package/bin/hooks/post-feature.js +227 -0
  9. package/bin/hooks/pre-agent.js +118 -0
  10. package/bin/hooks/pre-feature.js +138 -0
  11. package/lib/VALIDATION_README.md +455 -0
  12. package/lib/atomic.js +350 -0
  13. package/lib/ci/claude-action-integration.js +641 -0
  14. package/lib/ci/index.js +10 -0
  15. package/lib/core/CLAUDE_EXECUTOR.md +371 -0
  16. package/lib/core/README.md +321 -0
  17. package/lib/core/analyzer.js +497 -0
  18. package/lib/core/claude-executor.example.js +210 -0
  19. package/lib/core/claude-executor.js +1046 -0
  20. package/lib/core/cli.js +141 -0
  21. package/lib/core/detectors/conventions.js +342 -0
  22. package/lib/core/detectors/framework.js +276 -0
  23. package/lib/core/detectors/index.js +15 -0
  24. package/lib/core/detectors/language.js +199 -0
  25. package/lib/core/detectors/patterns.js +356 -0
  26. package/lib/core/generator.js +626 -0
  27. package/lib/core/index.js +9 -0
  28. package/lib/core/output-parser.example.js +250 -0
  29. package/lib/core/output-parser.js +458 -0
  30. package/lib/core/storage.js +515 -0
  31. package/lib/core/templates.js +556 -0
  32. package/lib/index.js +32 -0
  33. package/lib/init.js +252 -21
  34. package/lib/pipeline/cli.js +423 -0
  35. package/lib/pipeline/engine.js +928 -0
  36. package/lib/pipeline/executor.js +440 -0
  37. package/lib/pipeline/index.js +33 -0
  38. package/lib/pipeline/integrations.js +559 -0
  39. package/lib/pipeline/schemas.js +288 -0
  40. package/lib/presets.js +207 -0
  41. package/lib/remote/client.js +361 -0
  42. package/lib/server/auth.js +286 -0
  43. package/lib/server/client-example.js +190 -0
  44. package/lib/server/executor.js +426 -0
  45. package/lib/server/index.js +469 -0
  46. package/lib/update-helpers.js +505 -0
  47. package/lib/validation.js +460 -0
  48. package/package.json +19 -2
  49. package/template/.claude/agents/architect.md +260 -0
  50. package/template/.claude/agents/backend.md +203 -0
  51. package/template/.claude/agents/fixer.md +244 -0
  52. package/template/.claude/agents/frontend.md +232 -0
  53. package/template/.claude/agents/orchestrator.md +528 -0
  54. package/template/.claude/agents/product-analyzer.md +1130 -0
  55. package/template/.claude/agents/reviewer.md +229 -0
  56. package/template/.claude/agents/tester.md +242 -0
  57. package/{.claude → template/.claude}/commands/agentful-analyze.md +151 -43
  58. package/template/.claude/commands/agentful-decide.md +470 -0
  59. package/{.claude → template/.claude}/commands/agentful-product.md +89 -5
  60. package/template/.claude/commands/agentful-start.md +432 -0
  61. package/{.claude → template/.claude}/commands/agentful-status.md +88 -3
  62. package/template/.claude/commands/agentful-update.md +402 -0
  63. package/template/.claude/commands/agentful-validate.md +369 -0
  64. package/{.claude → template/.claude}/commands/agentful.md +110 -183
  65. package/template/.claude/product/EXAMPLES.md +167 -0
  66. package/{.claude → template/.claude}/settings.json +9 -13
  67. package/{.claude → template/.claude}/skills/conversation/SKILL.md +13 -7
  68. package/template/.claude/skills/deployment/SKILL.md +116 -0
  69. package/template/.claude/skills/product-planning/SKILL.md +463 -0
  70. package/template/.claude/skills/testing/SKILL.md +228 -0
  71. package/template/.claude/skills/validation/SKILL.md +650 -0
  72. package/template/CLAUDE.md +73 -5
  73. package/template/bin/hooks/block-random-docs.js +121 -0
  74. package/version.json +1 -1
  75. package/.claude/agents/architect.md +0 -524
  76. package/.claude/agents/backend.md +0 -315
  77. package/.claude/agents/fixer.md +0 -263
  78. package/.claude/agents/frontend.md +0 -274
  79. package/.claude/agents/orchestrator.md +0 -283
  80. package/.claude/agents/product-analyzer.md +0 -792
  81. package/.claude/agents/reviewer.md +0 -332
  82. package/.claude/agents/tester.md +0 -410
  83. package/.claude/commands/agentful-decide.md +0 -214
  84. package/.claude/commands/agentful-start.md +0 -182
  85. package/.claude/commands/agentful-validate.md +0 -127
  86. package/.claude/product/EXAMPLES.md +0 -610
  87. package/.claude/product/README.md +0 -326
  88. package/.claude/skills/validation/SKILL.md +0 -271
  89. package/bin/hooks/analyze-trigger.sh +0 -57
  90. package/bin/hooks/health-check.sh +0 -36
  91. /package/{.claude → template/.claude}/commands/agentful-generate.md +0 -0
  92. /package/{.claude → template/.claude}/product/index.md +0 -0
  93. /package/{.claude → template/.claude}/skills/product-tracking/SKILL.md +0 -0
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+ // health-check.js
3
+ // Comprehensive startup health check for agentful
4
+
5
+ import fs from 'fs';
6
+ import { execSync } from 'child_process';
7
+
8
+ let errors = 0;
9
+ let warnings = 0;
10
+
11
+ // === CRITICAL CHECKS (must pass) ===
12
+
13
+ // Check 1: .agentful directory
14
+ if (!fs.existsSync('.agentful')) {
15
+ console.log('❌ Agentful not initialized.');
16
+ console.log(' Run: npx @itz4blitz/agentful init');
17
+ process.exit(0);
18
+ }
19
+
20
+ // Check 2: Core state files
21
+ const stateFiles = ['state.json', 'completion.json', 'decisions.json'];
22
+ for (const file of stateFiles) {
23
+ const filePath = `.agentful/${file}`;
24
+ if (!fs.existsSync(filePath)) {
25
+ console.log(`❌ Missing .agentful/${file}`);
26
+ errors++;
27
+ }
28
+ }
29
+
30
+ // Check 3: .claude directory structure
31
+ const claudeDirs = ['agents', 'commands', 'product', 'skills'];
32
+ for (const dir of claudeDirs) {
33
+ const dirPath = `.claude/${dir}`;
34
+ if (!fs.existsSync(dirPath)) {
35
+ console.log(`❌ Missing .claude/${dir}/`);
36
+ errors++;
37
+ }
38
+ }
39
+
40
+ // Check 4: Core agents
41
+ const coreAgents = [
42
+ 'orchestrator',
43
+ 'backend',
44
+ 'frontend',
45
+ 'tester',
46
+ 'reviewer',
47
+ 'fixer',
48
+ 'architect',
49
+ 'product-analyzer'
50
+ ];
51
+ for (const agent of coreAgents) {
52
+ const agentPath = `.claude/agents/${agent}.md`;
53
+ if (!fs.existsSync(agentPath)) {
54
+ console.log(`❌ Missing core agent: ${agentPath}`);
55
+ errors++;
56
+ }
57
+ }
58
+
59
+ // Check 5: Product specification
60
+ if (!fs.existsSync('.claude/product/index.md')) {
61
+ // Check for hierarchical structure
62
+ let hasHierarchicalSpec = false;
63
+ if (fs.existsSync('.claude/product/domains')) {
64
+ try {
65
+ const domains = fs.readdirSync('.claude/product/domains');
66
+ for (const domain of domains) {
67
+ if (fs.existsSync(`.claude/product/domains/${domain}/index.md`)) {
68
+ hasHierarchicalSpec = true;
69
+ break;
70
+ }
71
+ }
72
+ } catch (err) {
73
+ // Ignore errors
74
+ }
75
+ }
76
+
77
+ if (!hasHierarchicalSpec) {
78
+ console.log('⚠️ No product specification found');
79
+ console.log(' Create .claude/product/index.md or run /agentful-product');
80
+ warnings++;
81
+ }
82
+ }
83
+
84
+ // Check 6: Settings file
85
+ const settingsPath = '.claude/settings.json';
86
+ if (!fs.existsSync(settingsPath)) {
87
+ console.log('❌ Missing .claude/settings.json');
88
+ errors++;
89
+ } else {
90
+ try {
91
+ const settingsContent = fs.readFileSync(settingsPath, 'utf8');
92
+ JSON.parse(settingsContent);
93
+ } catch (err) {
94
+ console.log('❌ Invalid JSON in .claude/settings.json');
95
+ errors++;
96
+ }
97
+ }
98
+
99
+ // === WARNING CHECKS (nice to have) ===
100
+
101
+ // Check: Architecture analysis
102
+ const architecturePath = '.agentful/architecture.json';
103
+ if (!fs.existsSync(architecturePath)) {
104
+ console.log('⚠️ Tech stack not analyzed. Run /agentful-generate to:');
105
+ console.log(' - Detect your tech stack');
106
+ console.log(' - Discover business domains');
107
+ console.log(' - Generate specialized agents');
108
+ warnings++;
109
+ } else {
110
+ try {
111
+ const archContent = fs.readFileSync(architecturePath, 'utf8');
112
+ const arch = JSON.parse(archContent);
113
+ if (!arch.techStack || !arch.domains) {
114
+ console.log('⚠️ .agentful/architecture.json is malformed');
115
+ console.log(' Run /agentful-generate to regenerate');
116
+ warnings++;
117
+ }
118
+ } catch (err) {
119
+ console.log('⚠️ .agentful/architecture.json is malformed');
120
+ console.log(' Run /agentful-generate to regenerate');
121
+ warnings++;
122
+ }
123
+ }
124
+
125
+ // Check: Node version
126
+ try {
127
+ const nodeVersion = process.version.replace('v', '');
128
+ const majorVersion = parseInt(nodeVersion.split('.')[0], 10);
129
+ if (majorVersion < 22) {
130
+ console.log(`⚠️ Node.js ${nodeVersion} detected. Requires >=22.0.0`);
131
+ warnings++;
132
+ }
133
+ } catch (err) {
134
+ // Ignore errors
135
+ }
136
+
137
+ // === SUMMARY ===
138
+
139
+ if (errors > 0) {
140
+ console.log('');
141
+ console.log(`❌ Found ${errors} critical issue(s)`);
142
+ console.log(' Run: npx @itz4blitz/agentful init');
143
+ process.exit(0);
144
+ }
145
+
146
+ if (warnings > 0) {
147
+ console.log('');
148
+ console.log(`⚠️ Agentful ready with ${warnings} warning(s)`);
149
+ } else {
150
+ console.log('✅ Agentful ready');
151
+ }
152
+
153
+ process.exit(0);
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ // post-agent.js
3
+ // Tracks agent execution metrics
4
+
5
+ import fs from 'fs';
6
+
7
+ const METRICS_FILE = '.agentful/agent-metrics.json';
8
+
9
+ /**
10
+ * Track agent execution metrics
11
+ * @param {Object} options - Tracking options
12
+ * @param {string} options.agentName - Name of the agent
13
+ * @param {string} [options.feature] - Feature being worked on
14
+ * @param {string} [options.domain] - Domain context
15
+ * @param {string} [options.timestamp] - ISO timestamp (defaults to current time)
16
+ * @returns {Object} - Result with success flag and optional error
17
+ */
18
+ export function trackAgentMetrics({ agentName, feature = '', domain = '', timestamp = new Date().toISOString() }) {
19
+ // Exit successfully if no agent specified
20
+ if (!agentName) {
21
+ return { success: true };
22
+ }
23
+
24
+ // Ensure .agentful directory exists
25
+ const metricsDir = METRICS_FILE.substring(0, METRICS_FILE.lastIndexOf('/'));
26
+ if (!fs.existsSync(metricsDir)) {
27
+ fs.mkdirSync(metricsDir, { recursive: true });
28
+ }
29
+
30
+ // Create metrics file if it doesn't exist
31
+ if (!fs.existsSync(METRICS_FILE)) {
32
+ const initialMetrics = {
33
+ invocations: {},
34
+ last_invocation: null,
35
+ feature_hooks: []
36
+ };
37
+ fs.writeFileSync(METRICS_FILE, JSON.stringify(initialMetrics, null, 2));
38
+ }
39
+
40
+ // Validate and read existing metrics file
41
+ let metrics;
42
+ if (!fs.existsSync(METRICS_FILE)) {
43
+ // File doesn't exist, create new structure
44
+ metrics = {
45
+ invocations: {},
46
+ last_invocation: null,
47
+ feature_hooks: []
48
+ };
49
+ } else {
50
+ try {
51
+ const metricsContent = fs.readFileSync(METRICS_FILE, 'utf8');
52
+ metrics = JSON.parse(metricsContent);
53
+ } catch (err) {
54
+ console.log('WARNING: agent-metrics.json is corrupted, recreating');
55
+ metrics = {
56
+ invocations: {},
57
+ last_invocation: null,
58
+ feature_hooks: []
59
+ };
60
+ }
61
+ }
62
+
63
+ // Update invocation count for this agent
64
+ const currentCount = metrics.invocations[agentName] || 0;
65
+ const newCount = currentCount + 1;
66
+
67
+ // Update metrics
68
+ metrics.invocations[agentName] = newCount;
69
+ metrics.last_invocation = {
70
+ agent: agentName,
71
+ timestamp: timestamp,
72
+ feature: feature,
73
+ domain: domain
74
+ };
75
+
76
+ // Write updated metrics
77
+ try {
78
+ fs.writeFileSync(METRICS_FILE, JSON.stringify(metrics, null, 2));
79
+ return { success: true };
80
+ } catch (err) {
81
+ console.error('WARNING: Failed to write agent-metrics.json');
82
+ return { success: true, error: err.message };
83
+ }
84
+ }
85
+
86
+ // CLI entrypoint
87
+ if (import.meta.url === `file://${process.argv[1]}`) {
88
+ const AGENT_NAME = process.env.AGENTFUL_AGENT || '';
89
+ const FEATURE = process.env.AGENTFUL_FEATURE || '';
90
+ const DOMAIN = process.env.AGENTFUL_DOMAIN || '';
91
+ const TIMESTAMP = new Date().toISOString();
92
+
93
+ trackAgentMetrics({
94
+ agentName: AGENT_NAME,
95
+ feature: FEATURE,
96
+ domain: DOMAIN,
97
+ timestamp: TIMESTAMP
98
+ });
99
+
100
+ process.exit(0);
101
+ }
@@ -0,0 +1,227 @@
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
+ }
@@ -0,0 +1,118 @@
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
+ }