@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.
Files changed (59) hide show
  1. package/README.md +28 -1
  2. package/bin/cli.js +11 -1055
  3. package/bin/hooks/block-file-creation.js +271 -0
  4. package/bin/hooks/product-spec-watcher.js +151 -0
  5. package/lib/index.js +0 -11
  6. package/lib/init.js +2 -21
  7. package/lib/parallel-execution.js +235 -0
  8. package/lib/presets.js +26 -4
  9. package/package.json +4 -7
  10. package/template/.claude/agents/architect.md +2 -2
  11. package/template/.claude/agents/backend.md +17 -30
  12. package/template/.claude/agents/frontend.md +17 -39
  13. package/template/.claude/agents/orchestrator.md +63 -4
  14. package/template/.claude/agents/product-analyzer.md +1 -1
  15. package/template/.claude/agents/tester.md +16 -29
  16. package/template/.claude/commands/agentful-generate.md +221 -14
  17. package/template/.claude/commands/agentful-init.md +621 -0
  18. package/template/.claude/commands/agentful-product.md +1 -1
  19. package/template/.claude/commands/agentful-start.md +99 -1
  20. package/template/.claude/product/EXAMPLES.md +2 -2
  21. package/template/.claude/product/index.md +1 -1
  22. package/template/.claude/settings.json +22 -0
  23. package/template/.claude/skills/research/SKILL.md +432 -0
  24. package/template/CLAUDE.md +5 -6
  25. package/template/bin/hooks/architect-drift-detector.js +242 -0
  26. package/template/bin/hooks/product-spec-watcher.js +151 -0
  27. package/version.json +1 -1
  28. package/bin/hooks/post-agent.js +0 -101
  29. package/bin/hooks/post-feature.js +0 -227
  30. package/bin/hooks/pre-agent.js +0 -118
  31. package/bin/hooks/pre-feature.js +0 -138
  32. package/lib/VALIDATION_README.md +0 -455
  33. package/lib/ci/claude-action-integration.js +0 -641
  34. package/lib/ci/index.js +0 -10
  35. package/lib/core/analyzer.js +0 -497
  36. package/lib/core/cli.js +0 -141
  37. package/lib/core/detectors/conventions.js +0 -342
  38. package/lib/core/detectors/framework.js +0 -276
  39. package/lib/core/detectors/index.js +0 -15
  40. package/lib/core/detectors/language.js +0 -199
  41. package/lib/core/detectors/patterns.js +0 -356
  42. package/lib/core/generator.js +0 -626
  43. package/lib/core/index.js +0 -9
  44. package/lib/core/output-parser.js +0 -458
  45. package/lib/core/storage.js +0 -515
  46. package/lib/core/templates.js +0 -556
  47. package/lib/pipeline/cli.js +0 -423
  48. package/lib/pipeline/engine.js +0 -928
  49. package/lib/pipeline/executor.js +0 -440
  50. package/lib/pipeline/index.js +0 -33
  51. package/lib/pipeline/integrations.js +0 -559
  52. package/lib/pipeline/schemas.js +0 -288
  53. package/lib/remote/client.js +0 -361
  54. package/lib/server/auth.js +0 -270
  55. package/lib/server/client-example.js +0 -190
  56. package/lib/server/executor.js +0 -477
  57. package/lib/server/index.js +0 -494
  58. package/lib/update-helpers.js +0 -505
  59. package/lib/validation.js +0 -460
@@ -1,199 +0,0 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
-
4
- /**
5
- * Language Detector
6
- *
7
- * Detects programming languages used in the project based on:
8
- * - File extensions
9
- * - File content analysis
10
- * - Configuration files
11
- */
12
-
13
- /**
14
- * Language detection rules
15
- * Maps file extensions to languages
16
- */
17
- const LANGUAGE_EXTENSIONS = {
18
- '.js': 'JavaScript',
19
- '.mjs': 'JavaScript',
20
- '.cjs': 'JavaScript',
21
- '.jsx': 'JavaScript',
22
- '.ts': 'TypeScript',
23
- '.tsx': 'TypeScript',
24
- '.py': 'Python',
25
- '.java': 'Java',
26
- '.go': 'Go',
27
- '.rs': 'Rust',
28
- '.rb': 'Ruby',
29
- '.php': 'PHP',
30
- '.cs': 'C#',
31
- '.cpp': 'C++',
32
- '.c': 'C',
33
- '.h': 'C',
34
- '.hpp': 'C++',
35
- '.swift': 'Swift',
36
- '.kt': 'Kotlin',
37
- '.scala': 'Scala',
38
- '.r': 'R',
39
- '.jl': 'Julia',
40
- '.lua': 'Lua',
41
- '.sh': 'Shell',
42
- '.bash': 'Shell',
43
- '.zsh': 'Shell'
44
- };
45
-
46
- /**
47
- * Language detection from config files
48
- */
49
- const CONFIG_FILES = {
50
- 'tsconfig.json': 'TypeScript',
51
- 'jsconfig.json': 'JavaScript',
52
- 'package.json': 'JavaScript',
53
- 'requirements.txt': 'Python',
54
- 'setup.py': 'Python',
55
- 'Pipfile': 'Python',
56
- 'pyproject.toml': 'Python',
57
- 'go.mod': 'Go',
58
- 'Cargo.toml': 'Rust',
59
- 'pom.xml': 'Java',
60
- 'build.gradle': 'Java',
61
- 'Gemfile': 'Ruby',
62
- 'composer.json': 'PHP'
63
- };
64
-
65
- /**
66
- * Detect languages from file list
67
- *
68
- * @param {string[]} files - Array of file paths relative to project root
69
- * @param {string} projectRoot - Absolute path to project root
70
- * @returns {Promise<Object[]>} Array of detected languages with confidence scores
71
- */
72
- export async function detectLanguages(files, projectRoot) {
73
- const languageCounts = {};
74
- const configEvidence = {};
75
-
76
- // Count files by extension
77
- for (const file of files) {
78
- const ext = path.extname(file).toLowerCase();
79
- const language = LANGUAGE_EXTENSIONS[ext];
80
-
81
- if (language) {
82
- if (!languageCounts[language]) {
83
- languageCounts[language] = { files: 0, extensions: new Set() };
84
- }
85
- languageCounts[language].files++;
86
- languageCounts[language].extensions.add(ext);
87
- }
88
-
89
- // Check for config files
90
- const basename = path.basename(file);
91
- const configLang = CONFIG_FILES[basename];
92
- if (configLang) {
93
- configEvidence[configLang] = (configEvidence[configLang] || 0) + 1;
94
- }
95
- }
96
-
97
- // Calculate confidence scores
98
- const totalFiles = files.length;
99
- const languages = [];
100
-
101
- for (const [language, data] of Object.entries(languageCounts)) {
102
- const filePercentage = (data.files / totalFiles) * 100;
103
- const configBonus = configEvidence[language] ? 10 : 0;
104
- const extensionDiversity = data.extensions.size > 1 ? 5 : 0;
105
-
106
- let confidence = Math.min(95, Math.round(filePercentage * 0.8 + configBonus + extensionDiversity));
107
-
108
- // TypeScript bonus: if tsconfig.json exists
109
- if (language === 'TypeScript' && configEvidence['TypeScript']) {
110
- confidence = Math.min(95, confidence + 10);
111
- }
112
-
113
- languages.push({
114
- name: language,
115
- confidence,
116
- files: data.files,
117
- percentage: Math.round(filePercentage * 10) / 10,
118
- extensions: Array.from(data.extensions)
119
- });
120
- }
121
-
122
- // Sort by file count descending
123
- languages.sort((a, b) => b.files - a.files);
124
-
125
- return languages;
126
- }
127
-
128
- /**
129
- * Detect primary language (highest confidence)
130
- *
131
- * @param {Object[]} languages - Array of detected languages
132
- * @returns {Object|null} Primary language or null if none detected
133
- */
134
- export function getPrimaryLanguage(languages) {
135
- if (languages.length === 0) return null;
136
-
137
- // Return language with highest confidence
138
- return languages.reduce((prev, current) =>
139
- current.confidence > prev.confidence ? current : prev
140
- );
141
- }
142
-
143
- /**
144
- * Analyze language-specific patterns in content
145
- *
146
- * @param {string} content - File content to analyze
147
- * @param {string} language - Language to check for
148
- * @returns {Object} Pattern detection result
149
- */
150
- export function analyzeLanguagePatterns(content, language) {
151
- const patterns = {
152
- TypeScript: {
153
- features: [
154
- { name: 'interfaces', regex: /interface\s+\w+/g },
155
- { name: 'types', regex: /type\s+\w+\s*=/g },
156
- { name: 'generics', regex: /<[A-Z]\w*>/g },
157
- { name: 'decorators', regex: /@\w+/g }
158
- ]
159
- },
160
- JavaScript: {
161
- features: [
162
- { name: 'async/await', regex: /async\s+function|await\s+/g },
163
- { name: 'arrow functions', regex: /=>\s*{|=>\s*\w+/g },
164
- { name: 'destructuring', regex: /const\s*{[^}]+}\s*=/g },
165
- { name: 'template literals', regex: /`[^`]*\${[^}]*}[^`]*`/g }
166
- ]
167
- },
168
- Python: {
169
- features: [
170
- { name: 'classes', regex: /class\s+\w+:/g },
171
- { name: 'decorators', regex: /@\w+/g },
172
- { name: 'list comprehensions', regex: /\[[^\]]+for\s+\w+\s+in\s+[^\]]+\]/g },
173
- { name: 'type hints', regex: /:\s*\w+\s*(?:=|\))/g }
174
- ]
175
- }
176
- };
177
-
178
- const langPatterns = patterns[language];
179
- if (!langPatterns) {
180
- return { detected: false };
181
- }
182
-
183
- const detected = {};
184
- for (const { name, regex } of langPatterns.features) {
185
- const matches = content.match(regex);
186
- detected[name] = matches ? matches.length : 0;
187
- }
188
-
189
- return {
190
- detected: true,
191
- features: detected
192
- };
193
- }
194
-
195
- export default {
196
- detectLanguages,
197
- getPrimaryLanguage,
198
- analyzeLanguagePatterns
199
- };
@@ -1,356 +0,0 @@
1
- import path from 'path';
2
- import fs from 'fs/promises';
3
-
4
- /**
5
- * Pattern Detector
6
- *
7
- * Detects code patterns and architectural styles:
8
- * - Component patterns (React, Vue, etc.)
9
- * - API patterns (REST, GraphQL, RPC)
10
- * - Database patterns (SQL, NoSQL, ORM)
11
- * - Test patterns (unit, integration, e2e)
12
- * - Authentication patterns (JWT, OAuth, sessions)
13
- */
14
-
15
- /**
16
- * Detect component patterns
17
- *
18
- * @param {string[]} files - Array of file paths
19
- * @param {string} projectRoot - Absolute path to project root
20
- * @returns {Promise<Object>} Component pattern detection result
21
- */
22
- export async function detectComponentPatterns(files, projectRoot) {
23
- const componentFiles = files.filter(file =>
24
- /\.(jsx|tsx|vue|svelte)$/.test(file) ||
25
- file.includes('/components/') ||
26
- file.includes('/ui/')
27
- );
28
-
29
- if (componentFiles.length === 0) {
30
- return { detected: false };
31
- }
32
-
33
- const examples = componentFiles.slice(0, 5);
34
- const patterns = {
35
- functional: 0,
36
- class: 0,
37
- hooks: 0
38
- };
39
-
40
- // Sample a few files to detect patterns
41
- for (const file of examples.slice(0, 3)) {
42
- try {
43
- const filePath = path.join(projectRoot, file);
44
- const content = await fs.readFile(filePath, 'utf-8');
45
-
46
- if (/export\s+default\s+function|const\s+\w+\s*=\s*\(\)/.test(content)) {
47
- patterns.functional++;
48
- }
49
- if (/class\s+\w+\s+extends/.test(content)) {
50
- patterns.class++;
51
- }
52
- if (/use[A-Z]\w+/.test(content)) {
53
- patterns.hooks++;
54
- }
55
- } catch (error) {
56
- // Skip files that can't be read
57
- }
58
- }
59
-
60
- return {
61
- detected: true,
62
- count: componentFiles.length,
63
- examples: examples.map(f => path.basename(f)),
64
- style: patterns.functional > patterns.class ? 'functional' : 'class',
65
- usesHooks: patterns.hooks > 0
66
- };
67
- }
68
-
69
- /**
70
- * Detect API patterns
71
- *
72
- * @param {string[]} files - Array of file paths
73
- * @param {string} projectRoot - Absolute path to project root
74
- * @returns {Promise<Object>} API pattern detection result
75
- */
76
- export async function detectAPIPatterns(files, projectRoot) {
77
- const apiFiles = files.filter(file =>
78
- file.includes('/api/') ||
79
- file.includes('/routes/') ||
80
- file.includes('/controllers/') ||
81
- file.includes('/endpoints/')
82
- );
83
-
84
- if (apiFiles.length === 0) {
85
- return { detected: false };
86
- }
87
-
88
- const patterns = {
89
- rest: 0,
90
- graphql: 0,
91
- rpc: 0
92
- };
93
-
94
- // Check for GraphQL files
95
- const hasGraphQL = files.some(file =>
96
- file.endsWith('.graphql') ||
97
- file.endsWith('.gql') ||
98
- file.includes('schema.') ||
99
- file.includes('resolvers')
100
- );
101
- if (hasGraphQL) patterns.graphql++;
102
-
103
- // Check for RPC/tRPC
104
- const hasRPC = files.some(file =>
105
- file.includes('trpc') ||
106
- file.includes('.proto') ||
107
- file.includes('grpc')
108
- );
109
- if (hasRPC) patterns.rpc++;
110
-
111
- // Default to REST if API files exist
112
- if (apiFiles.length > 0) patterns.rest++;
113
-
114
- // Determine pattern directories
115
- const apiDirs = new Set();
116
- for (const file of apiFiles) {
117
- const dir = path.dirname(file);
118
- const parts = dir.split('/');
119
- if (parts.includes('api')) {
120
- apiDirs.add(parts.slice(0, parts.indexOf('api') + 1).join('/'));
121
- }
122
- }
123
-
124
- return {
125
- detected: true,
126
- pattern: Array.from(apiDirs)[0] || 'api/',
127
- count: apiFiles.length,
128
- type: patterns.graphql > 0 ? 'GraphQL' : patterns.rpc > 0 ? 'RPC' : 'REST',
129
- examples: apiFiles.slice(0, 3).map(f => path.basename(f))
130
- };
131
- }
132
-
133
- /**
134
- * Detect database patterns
135
- *
136
- * @param {string[]} files - Array of file paths
137
- * @param {string} projectRoot - Absolute path to project root
138
- * @returns {Promise<Object>} Database pattern detection result
139
- */
140
- export async function detectDatabasePatterns(files, projectRoot) {
141
- const dbFiles = files.filter(file =>
142
- file.includes('/models/') ||
143
- file.includes('/entities/') ||
144
- file.includes('/schema') ||
145
- file.includes('/migrations/') ||
146
- file.includes('prisma/') ||
147
- file.includes('database/')
148
- );
149
-
150
- const patterns = {
151
- orm: null,
152
- migrations: false,
153
- type: 'unknown'
154
- };
155
-
156
- // Check for ORM
157
- if (files.some(file => file.includes('prisma/schema.prisma'))) {
158
- patterns.orm = 'Prisma';
159
- patterns.type = 'SQL';
160
- } else if (files.some(file => file.includes('drizzle.config'))) {
161
- patterns.orm = 'Drizzle';
162
- patterns.type = 'SQL';
163
- } else if (files.some(file => file.includes('typeorm') || file.endsWith('.entity.ts'))) {
164
- patterns.orm = 'TypeORM';
165
- patterns.type = 'SQL';
166
- } else if (files.some(file => file.includes('sequelize'))) {
167
- patterns.orm = 'Sequelize';
168
- patterns.type = 'SQL';
169
- } else if (files.some(file => file.includes('mongoose'))) {
170
- patterns.orm = 'Mongoose';
171
- patterns.type = 'NoSQL';
172
- }
173
-
174
- // Check for migrations
175
- patterns.migrations = files.some(file =>
176
- file.includes('/migrations/') ||
177
- file.includes('migrate')
178
- );
179
-
180
- if (dbFiles.length === 0 && !patterns.orm) {
181
- return { detected: false };
182
- }
183
-
184
- return {
185
- detected: true,
186
- orm: patterns.orm,
187
- type: patterns.type,
188
- migrations: patterns.migrations,
189
- files: dbFiles.length,
190
- examples: dbFiles.slice(0, 3).map(f => path.basename(f))
191
- };
192
- }
193
-
194
- /**
195
- * Detect test patterns
196
- *
197
- * @param {string[]} files - Array of file paths
198
- * @param {string} projectRoot - Absolute path to project root
199
- * @returns {Promise<Object>} Test pattern detection result
200
- */
201
- export async function detectTestPatterns(files, projectRoot) {
202
- const testFiles = files.filter(file =>
203
- /\.(test|spec)\.(js|ts|jsx|tsx)$/.test(file) ||
204
- file.includes('/tests/') ||
205
- file.includes('/__tests__/') ||
206
- file.includes('/test/')
207
- );
208
-
209
- if (testFiles.length === 0) {
210
- return { detected: false };
211
- }
212
-
213
- const patterns = {
214
- unit: 0,
215
- integration: 0,
216
- e2e: 0
217
- };
218
-
219
- // Categorize tests by directory
220
- for (const file of testFiles) {
221
- if (file.includes('/unit/') || file.includes('.unit.')) {
222
- patterns.unit++;
223
- } else if (file.includes('/integration/') || file.includes('.integration.')) {
224
- patterns.integration++;
225
- } else if (file.includes('/e2e/') || file.includes('.e2e.') || file.includes('cypress/') || file.includes('playwright/')) {
226
- patterns.e2e++;
227
- } else {
228
- // Default to unit tests
229
- patterns.unit++;
230
- }
231
- }
232
-
233
- // Detect test framework from file content
234
- let framework = 'unknown';
235
- try {
236
- const sampleFile = path.join(projectRoot, testFiles[0]);
237
- const content = await fs.readFile(sampleFile, 'utf-8');
238
-
239
- if (content.includes('vitest') || content.includes('describe.only')) {
240
- framework = 'vitest';
241
- } else if (content.includes('jest') || content.includes('@jest')) {
242
- framework = 'jest';
243
- } else if (content.includes('mocha') || content.includes('chai')) {
244
- framework = 'mocha';
245
- } else if (content.includes('cypress')) {
246
- framework = 'cypress';
247
- } else if (content.includes('playwright')) {
248
- framework = 'playwright';
249
- } else if (/describe\(|it\(|test\(/.test(content)) {
250
- framework = 'jest'; // Default assumption
251
- }
252
- } catch (error) {
253
- // Can't read file, framework remains unknown
254
- }
255
-
256
- return {
257
- detected: true,
258
- framework,
259
- count: testFiles.length,
260
- types: {
261
- unit: patterns.unit,
262
- integration: patterns.integration,
263
- e2e: patterns.e2e
264
- },
265
- coverage: files.some(file => file.includes('coverage/')) || files.some(file => file.includes('.coverage'))
266
- };
267
- }
268
-
269
- /**
270
- * Detect authentication patterns
271
- *
272
- * @param {string[]} files - Array of file paths
273
- * @param {string} projectRoot - Absolute path to project root
274
- * @returns {Promise<Object>} Auth pattern detection result
275
- */
276
- export async function detectAuthPatterns(files, projectRoot) {
277
- const authFiles = files.filter(file =>
278
- file.includes('/auth/') ||
279
- file.includes('authentication') ||
280
- file.includes('login') ||
281
- file.includes('session') ||
282
- file.includes('jwt') ||
283
- file.includes('oauth')
284
- );
285
-
286
- if (authFiles.length === 0) {
287
- return { detected: false };
288
- }
289
-
290
- const patterns = {
291
- jwt: false,
292
- oauth: false,
293
- sessions: false,
294
- nextAuth: false
295
- };
296
-
297
- // Check for specific auth patterns
298
- patterns.jwt = authFiles.some(file =>
299
- file.includes('jwt') || file.includes('token')
300
- );
301
-
302
- patterns.oauth = authFiles.some(file =>
303
- file.includes('oauth') || file.includes('passport')
304
- );
305
-
306
- patterns.sessions = authFiles.some(file =>
307
- file.includes('session')
308
- );
309
-
310
- patterns.nextAuth = files.some(file =>
311
- file.includes('next-auth') || file.includes('[...nextauth]')
312
- );
313
-
314
- return {
315
- detected: true,
316
- methods: Object.entries(patterns)
317
- .filter(([_, detected]) => detected)
318
- .map(([method]) => method),
319
- files: authFiles.length,
320
- examples: authFiles.slice(0, 3).map(f => path.basename(f))
321
- };
322
- }
323
-
324
- /**
325
- * Detect all patterns in project
326
- *
327
- * @param {string[]} files - Array of file paths
328
- * @param {string} projectRoot - Absolute path to project root
329
- * @returns {Promise<Object>} All detected patterns
330
- */
331
- export async function detectPatterns(files, projectRoot) {
332
- const [components, api, database, tests, auth] = await Promise.all([
333
- detectComponentPatterns(files, projectRoot),
334
- detectAPIPatterns(files, projectRoot),
335
- detectDatabasePatterns(files, projectRoot),
336
- detectTestPatterns(files, projectRoot),
337
- detectAuthPatterns(files, projectRoot)
338
- ]);
339
-
340
- return {
341
- components,
342
- api,
343
- database,
344
- tests,
345
- auth
346
- };
347
- }
348
-
349
- export default {
350
- detectPatterns,
351
- detectComponentPatterns,
352
- detectAPIPatterns,
353
- detectDatabasePatterns,
354
- detectTestPatterns,
355
- detectAuthPatterns
356
- };