@itz4blitz/agentful 0.2.1 → 0.4.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/.claude/agents/orchestrator.md +121 -610
- package/.claude/agents/product-analyzer.md +3 -10
- package/.claude/commands/agentful-generate.md +206 -0
- package/.claude/commands/agentful-product.md +3 -3
- package/.claude/commands/agentful.md +1 -12
- package/.claude/product/EXAMPLES.md +2 -2
- package/.claude/product/README.md +9 -27
- package/.claude/skills/conversation/SKILL.md +152 -975
- package/.claude/skills/product-tracking/SKILL.md +10 -21
- package/README.md +6 -5
- package/bin/cli.js +108 -583
- package/bin/hooks/health-check.sh +16 -16
- package/lib/index.js +6 -36
- package/lib/init.js +411 -0
- package/package.json +1 -2
- package/template/CLAUDE.md +11 -11
- package/version.json +1 -1
- package/.claude/commands/agentful-agents.md +0 -668
- package/.claude/commands/agentful-skills.md +0 -635
- package/.claude/product/CHANGES.md +0 -276
- package/lib/agent-generator.js +0 -778
- package/lib/domain-detector.js +0 -468
- package/lib/domain-structure-generator.js +0 -770
- package/lib/project-analyzer.js +0 -701
- package/lib/tech-stack-detector.js +0 -1091
- package/lib/template-engine.js +0 -240
- package/template/PRODUCT.md +0 -584
- package/templates/agents/domain-agent.template.md +0 -208
- package/templates/agents/tech-agent.template.md +0 -124
package/lib/template-engine.js
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Template Engine
|
|
3
|
-
*
|
|
4
|
-
* Simple template engine for variable interpolation in agent templates.
|
|
5
|
-
* Supports nested objects, arrays, and conditional rendering.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
class TemplateEngine {
|
|
9
|
-
/**
|
|
10
|
-
* Render template with data
|
|
11
|
-
*/
|
|
12
|
-
static render(template, data) {
|
|
13
|
-
let content = template;
|
|
14
|
-
|
|
15
|
-
// Handle arrays and objects with special formatting FIRST
|
|
16
|
-
// This allows formatComplexTypes to format arrays before conditionals/loops process them
|
|
17
|
-
content = this.formatComplexTypes(content, data);
|
|
18
|
-
|
|
19
|
-
// Handle conditionals and loops (after complex types are formatted)
|
|
20
|
-
content = this.processConditionals(content, data);
|
|
21
|
-
content = this.processLoops(content, data);
|
|
22
|
-
|
|
23
|
-
// Replace simple variables: {{variable}}
|
|
24
|
-
content = this.replaceVariables(content, data);
|
|
25
|
-
|
|
26
|
-
// Add timestamp if needed
|
|
27
|
-
if (content.includes('{{generated_at}}')) {
|
|
28
|
-
content = content.replace(/\{\{generated_at\}\}/g, new Date().toISOString());
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return content;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Process {{#if variable}}...{{else}}...{{/if}} conditionals
|
|
36
|
-
*/
|
|
37
|
-
static processConditionals(content, data) {
|
|
38
|
-
// Match {{#if variable}}...{{else}}...{{/if}} or {{#if variable}}...{{/if}}
|
|
39
|
-
const ifRegex = /\{\{#if\s+(\w+(?:\.\w+)*)\s*\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g;
|
|
40
|
-
|
|
41
|
-
return content.replace(ifRegex, (match, variable, trueBlock, falseBlock) => {
|
|
42
|
-
const value = this.getNestedValue(data, variable);
|
|
43
|
-
const isTruthy = this.isTruthy(value);
|
|
44
|
-
|
|
45
|
-
if (isTruthy) {
|
|
46
|
-
return trueBlock || '';
|
|
47
|
-
} else {
|
|
48
|
-
return falseBlock || '';
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Process {{#each array}}...{{/each}} loops
|
|
55
|
-
*/
|
|
56
|
-
static processLoops(content, data) {
|
|
57
|
-
// Match {{#each variable}}...{{/each}}
|
|
58
|
-
const eachRegex = /\{\{#each\s+(\w+(?:\.\w+)*)\s*\}\}([\s\S]*?)\{\{\/each\}\}/g;
|
|
59
|
-
|
|
60
|
-
return content.replace(eachRegex, (match, variable, block) => {
|
|
61
|
-
const array = this.getNestedValue(data, variable);
|
|
62
|
-
|
|
63
|
-
if (!Array.isArray(array) || array.length === 0) {
|
|
64
|
-
return '';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return array.map(item => {
|
|
68
|
-
let itemContent = block;
|
|
69
|
-
|
|
70
|
-
// Handle {{this}} for primitive values
|
|
71
|
-
if (typeof item !== 'object') {
|
|
72
|
-
itemContent = itemContent.replace(/\{\{this\}\}/g, String(item));
|
|
73
|
-
} else {
|
|
74
|
-
// Handle {{this.property}} for objects
|
|
75
|
-
itemContent = itemContent.replace(/\{\{this\.(\w+)\}\}/g, (m, prop) => {
|
|
76
|
-
const value = item[prop];
|
|
77
|
-
return value !== undefined ? String(value) : '';
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Handle {{this}} for objects
|
|
81
|
-
// Smart default: if object has 'code' or 'name' property, use that; otherwise stringify
|
|
82
|
-
itemContent = itemContent.replace(/\{\{this\}\}/g, () => {
|
|
83
|
-
if (item.code !== undefined) return String(item.code);
|
|
84
|
-
if (item.name !== undefined) return String(item.name);
|
|
85
|
-
return JSON.stringify(item);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return itemContent;
|
|
90
|
-
}).join('');
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Get nested value from object using dot notation
|
|
96
|
-
*/
|
|
97
|
-
static getNestedValue(obj, path) {
|
|
98
|
-
return path.split('.').reduce((current, prop) => {
|
|
99
|
-
return current?.[prop];
|
|
100
|
-
}, obj);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Check if value is truthy (for conditionals)
|
|
105
|
-
*/
|
|
106
|
-
static isTruthy(value) {
|
|
107
|
-
if (value === null || value === undefined) return false;
|
|
108
|
-
if (typeof value === 'boolean') return value;
|
|
109
|
-
if (typeof value === 'number') return value !== 0;
|
|
110
|
-
if (typeof value === 'string') return value.length > 0;
|
|
111
|
-
if (Array.isArray(value)) return value.length > 0;
|
|
112
|
-
if (typeof value === 'object') return Object.keys(value).length > 0;
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Replace simple variables
|
|
118
|
-
*/
|
|
119
|
-
static replaceVariables(content, data) {
|
|
120
|
-
for (const [key, value] of Object.entries(data)) {
|
|
121
|
-
const placeholder = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
|
|
122
|
-
content = content.replace(placeholder, this.formatValue(value));
|
|
123
|
-
}
|
|
124
|
-
return content;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Format complex types (arrays, objects)
|
|
129
|
-
*/
|
|
130
|
-
static formatComplexTypes(content, data) {
|
|
131
|
-
// Handle code_samples first (before generic samples check)
|
|
132
|
-
if (data.codeSamples && Array.isArray(data.codeSamples)) {
|
|
133
|
-
const placeholder = new RegExp('\\{\\{code_samples\\}\\}', 'g');
|
|
134
|
-
const formatted = this.formatSamples(data.codeSamples);
|
|
135
|
-
content = content.replace(placeholder, formatted);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Handle samples array
|
|
139
|
-
if (data.samples && Array.isArray(data.samples)) {
|
|
140
|
-
const placeholder = new RegExp('\\{\\{samples\\}\\}', 'g');
|
|
141
|
-
const formatted = this.formatSamples(data.samples);
|
|
142
|
-
content = content.replace(placeholder, formatted);
|
|
143
|
-
}
|
|
144
|
-
// Handle patterns array - format as bullet points
|
|
145
|
-
if (data.patterns && Array.isArray(data.patterns)) {
|
|
146
|
-
const placeholder = new RegExp('\\{\\{patterns\\}\\}', 'g');
|
|
147
|
-
const formatted = data.patterns
|
|
148
|
-
.map(p => {
|
|
149
|
-
if (typeof p === 'string') return `- ${p}`;
|
|
150
|
-
if (p.keyword && p.context) {
|
|
151
|
-
return `- **${p.keyword}**: Found in codebase`;
|
|
152
|
-
}
|
|
153
|
-
return JSON.stringify(p);
|
|
154
|
-
})
|
|
155
|
-
.join('\n');
|
|
156
|
-
content = content.replace(placeholder, formatted);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Handle conventions array
|
|
160
|
-
if (data.conventions && Array.isArray(data.conventions)) {
|
|
161
|
-
const placeholder = new RegExp('\\{\\{conventions\\}\\}', 'g');
|
|
162
|
-
const formatted = data.conventions
|
|
163
|
-
.filter(c => c && c.trim())
|
|
164
|
-
.map(c => `- ${c}`)
|
|
165
|
-
.join('\n');
|
|
166
|
-
content = content.replace(placeholder, formatted || 'No specific conventions detected');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Handle features array
|
|
170
|
-
if (data.features && Array.isArray(data.features)) {
|
|
171
|
-
const placeholder = new RegExp('\\{\\{features\\}\\}', 'g');
|
|
172
|
-
const formatted = data.features
|
|
173
|
-
.map(f => `- **${f.name}**: ${f.description || 'No description'}`)
|
|
174
|
-
.join('\n');
|
|
175
|
-
content = content.replace(placeholder, formatted || 'No features detected');
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Handle endpoints array
|
|
179
|
-
if (data.endpoints && Array.isArray(data.endpoints)) {
|
|
180
|
-
const placeholder = new RegExp('\\{\\{endpoints\\}\\}', 'g');
|
|
181
|
-
const formatted = data.endpoints
|
|
182
|
-
.map(e => `- \`${e.code || e}\``)
|
|
183
|
-
.join('\n');
|
|
184
|
-
content = content.replace(placeholder, formatted || 'No endpoints detected');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Handle models array
|
|
188
|
-
if (data.models && Array.isArray(data.models)) {
|
|
189
|
-
const placeholder = new RegExp('\\{\\{models\\}\\}', 'g');
|
|
190
|
-
const formatted = data.models
|
|
191
|
-
.map(m => `- \`${m.code || m}\``)
|
|
192
|
-
.join('\n');
|
|
193
|
-
content = content.replace(placeholder, formatted || 'No models detected');
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return content;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Format code samples for display
|
|
201
|
-
*/
|
|
202
|
-
static formatSamples(samples) {
|
|
203
|
-
if (!samples || samples.length === 0) {
|
|
204
|
-
return 'No code samples available yet.';
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return samples
|
|
208
|
-
.slice(0, 5) // Limit to 5 samples
|
|
209
|
-
.map(sample => {
|
|
210
|
-
if (typeof sample === 'string') return sample;
|
|
211
|
-
|
|
212
|
-
const path = sample.path || 'unknown';
|
|
213
|
-
const content = sample.content || '';
|
|
214
|
-
|
|
215
|
-
return `#### ${path}\n\`\`\`\n${content.substring(0, 800)}${content.length > 800 ? '\n...' : ''}\n\`\`\``;
|
|
216
|
-
})
|
|
217
|
-
.join('\n\n');
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Format value for template
|
|
222
|
-
*/
|
|
223
|
-
static formatValue(value) {
|
|
224
|
-
if (value === null || value === undefined) {
|
|
225
|
-
return '';
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (Array.isArray(value)) {
|
|
229
|
-
return value.join(', ');
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (typeof value === 'object') {
|
|
233
|
-
return JSON.stringify(value, null, 2);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return String(value);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export default TemplateEngine;
|