@itz4blitz/agentful 0.4.0 → 0.5.1

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 +133 -5
  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 +232 -9
  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,141 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Codebase Analyzer CLI
5
+ *
6
+ * Simple CLI for testing the analyzer
7
+ *
8
+ * Usage:
9
+ * node lib/core/cli.js [options]
10
+ *
11
+ * Options:
12
+ * --project <path> Project root path (default: current directory)
13
+ * --output <path> Output file path (default: .agentful/architecture.json)
14
+ * --force Force re-analysis even if cache is fresh
15
+ * --verbose Show detailed progress
16
+ */
17
+
18
+ import { CodebaseAnalyzer } from './analyzer.js';
19
+
20
+ const args = process.argv.slice(2);
21
+
22
+ // Parse CLI arguments
23
+ const options = {
24
+ projectRoot: process.cwd(),
25
+ outputPath: '.agentful/architecture.json',
26
+ force: false,
27
+ verbose: false
28
+ };
29
+
30
+ for (let i = 0; i < args.length; i++) {
31
+ const arg = args[i];
32
+
33
+ if (arg === '--project' && args[i + 1]) {
34
+ options.projectRoot = args[++i];
35
+ } else if (arg === '--output' && args[i + 1]) {
36
+ options.outputPath = args[++i];
37
+ } else if (arg === '--force') {
38
+ options.force = true;
39
+ } else if (arg === '--verbose') {
40
+ options.verbose = true;
41
+ } else if (arg === '--help' || arg === '-h') {
42
+ console.log(`
43
+ Codebase Analyzer CLI
44
+
45
+ Usage:
46
+ node lib/core/cli.js [options]
47
+
48
+ Options:
49
+ --project <path> Project root path (default: current directory)
50
+ --output <path> Output file path (default: .agentful/architecture.json)
51
+ --force Force re-analysis even if cache is fresh
52
+ --verbose Show detailed progress
53
+ --help, -h Show this help message
54
+
55
+ Examples:
56
+ # Analyze current directory
57
+ node lib/core/cli.js
58
+
59
+ # Analyze specific project
60
+ node lib/core/cli.js --project /path/to/project
61
+
62
+ # Force re-analysis
63
+ node lib/core/cli.js --force --verbose
64
+ `);
65
+ process.exit(0);
66
+ }
67
+ }
68
+
69
+ // Create analyzer
70
+ const analyzer = new CodebaseAnalyzer(options);
71
+
72
+ // Setup event listeners
73
+ analyzer.on('start', ({ projectRoot }) => {
74
+ console.log(`\n🔍 Analyzing codebase: ${projectRoot}\n`);
75
+ });
76
+
77
+ analyzer.on('progress', ({ stage, progress, fileCount, count }) => {
78
+ if (options.verbose) {
79
+ let message = ` ${stage}: ${progress}%`;
80
+ if (fileCount) message += ` (${fileCount} files)`;
81
+ if (count) message += ` (${count} detected)`;
82
+ console.log(message);
83
+ }
84
+ });
85
+
86
+ analyzer.on('warning', ({ message }) => {
87
+ if (options.verbose) {
88
+ console.warn(` ⚠️ ${message}`);
89
+ }
90
+ });
91
+
92
+ analyzer.on('written', ({ path }) => {
93
+ console.log(`\n✅ Analysis written to: ${path}\n`);
94
+ });
95
+
96
+ analyzer.on('complete', ({ duration, analysis }) => {
97
+ console.log(`⏱️ Completed in ${duration}ms\n`);
98
+
99
+ // Print summary
100
+ console.log('📊 Summary:');
101
+ console.log(` Files analyzed: ${analysis.fileCount}`);
102
+ console.log(` Confidence: ${analysis.confidence}%`);
103
+ console.log(` Primary language: ${analysis.primaryLanguage || 'unknown'}`);
104
+ console.log(` Languages: ${analysis.languages.map(l => l.name).join(', ')}`);
105
+ console.log(` Frameworks: ${analysis.frameworks.map(f => f.name).join(', ') || 'none'}`);
106
+
107
+ if (analysis.recommendations.length > 0) {
108
+ console.log(`\n💡 Recommendations:`);
109
+ analysis.recommendations.forEach((rec, i) => {
110
+ console.log(` ${i + 1}. [${rec.priority}] ${rec.message}`);
111
+ console.log(` → ${rec.action}`);
112
+ });
113
+ }
114
+
115
+ console.log('');
116
+ });
117
+
118
+ analyzer.on('error', (error) => {
119
+ console.error(`\n❌ Analysis failed: ${error.message}\n`);
120
+ if (options.verbose) {
121
+ console.error(error.stack);
122
+ }
123
+ process.exit(1);
124
+ });
125
+
126
+ // Run analysis
127
+ (async () => {
128
+ try {
129
+ if (options.force) {
130
+ await analyzer.analyze();
131
+ } else {
132
+ await analyzer.analyzeWithCache();
133
+ }
134
+ } catch (error) {
135
+ console.error(`\n❌ Unexpected error: ${error.message}\n`);
136
+ if (options.verbose) {
137
+ console.error(error.stack);
138
+ }
139
+ process.exit(1);
140
+ }
141
+ })();
@@ -0,0 +1,342 @@
1
+ import path from 'path';
2
+ import fs from 'fs/promises';
3
+
4
+ /**
5
+ * Convention Detector
6
+ *
7
+ * Extracts coding conventions and style guidelines:
8
+ * - Naming conventions (camelCase, PascalCase, snake_case, kebab-case)
9
+ * - File structure organization (feature-based, layer-based, domain-driven)
10
+ * - Code style (indentation, quotes, semicolons)
11
+ * - Import style (named, default, relative vs absolute)
12
+ */
13
+
14
+ /**
15
+ * Detect naming conventions from files
16
+ *
17
+ * @param {string[]} files - Array of file paths
18
+ * @returns {Object} Naming convention analysis
19
+ */
20
+ export function detectNamingConventions(files) {
21
+ const patterns = {
22
+ camelCase: 0,
23
+ PascalCase: 0,
24
+ 'snake_case': 0,
25
+ 'kebab-case': 0
26
+ };
27
+
28
+ for (const file of files) {
29
+ const basename = path.basename(file, path.extname(file));
30
+
31
+ if (/^[a-z][a-zA-Z0-9]*$/.test(basename)) {
32
+ patterns.camelCase++;
33
+ } else if (/^[A-Z][a-zA-Z0-9]*$/.test(basename)) {
34
+ patterns.PascalCase++;
35
+ } else if (/^[a-z][a-z0-9_]*$/.test(basename)) {
36
+ patterns['snake_case']++;
37
+ } else if (/^[a-z][a-z0-9-]*$/.test(basename)) {
38
+ patterns['kebab-case']++;
39
+ }
40
+ }
41
+
42
+ // Determine dominant pattern
43
+ const sortedPatterns = Object.entries(patterns)
44
+ .sort(([, a], [, b]) => b - a);
45
+
46
+ const totalFiles = files.length;
47
+ const [dominant, count] = sortedPatterns[0];
48
+ const confidence = totalFiles > 0 ? Math.round((count / totalFiles) * 100) : 0;
49
+
50
+ return {
51
+ dominant,
52
+ confidence,
53
+ distribution: patterns
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Detect file structure organization
59
+ *
60
+ * @param {string[]} files - Array of file paths
61
+ * @returns {Object} File structure analysis
62
+ */
63
+ export function detectFileStructure(files) {
64
+ const indicators = {
65
+ 'feature-based': 0,
66
+ 'layer-based': 0,
67
+ 'domain-driven': 0,
68
+ 'atomic': 0
69
+ };
70
+
71
+ // Feature-based indicators
72
+ const hasFeatureDirs = files.some(file =>
73
+ file.includes('/features/') || file.match(/\/[a-z]+-feature\//)
74
+ );
75
+ if (hasFeatureDirs) indicators['feature-based'] += 3;
76
+
77
+ // Layer-based indicators (controllers, models, views pattern)
78
+ const layers = ['controllers', 'models', 'views', 'services', 'repositories'];
79
+ const layerCount = layers.filter(layer =>
80
+ files.some(file => file.includes(`/${layer}/`))
81
+ ).length;
82
+ indicators['layer-based'] = layerCount;
83
+
84
+ // Domain-driven indicators
85
+ const hasDomains = files.some(file =>
86
+ file.includes('/domains/') || file.includes('/domain/')
87
+ );
88
+ const hasAggregates = files.some(file =>
89
+ file.includes('/aggregates/') || file.includes('/entities/')
90
+ );
91
+ if (hasDomains || hasAggregates) {
92
+ indicators['domain-driven'] += 2;
93
+ }
94
+
95
+ // Atomic design indicators
96
+ const hasAtoms = files.some(file =>
97
+ file.includes('/atoms/') || file.includes('/molecules/') || file.includes('/organisms/')
98
+ );
99
+ if (hasAtoms) indicators['atomic'] += 3;
100
+
101
+ // Determine dominant structure
102
+ const sortedIndicators = Object.entries(indicators)
103
+ .sort(([, a], [, b]) => b - a);
104
+
105
+ const [structure, score] = sortedIndicators[0];
106
+
107
+ return {
108
+ structure: score > 0 ? structure : 'mixed',
109
+ confidence: Math.min(90, score * 20),
110
+ indicators
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Detect code style conventions from file content
116
+ *
117
+ * @param {string} content - File content to analyze
118
+ * @returns {Object} Code style analysis
119
+ */
120
+ export function detectCodeStyle(content) {
121
+ const style = {
122
+ indentation: 'unknown',
123
+ quotes: 'unknown',
124
+ semicolons: 'unknown',
125
+ trailingCommas: 'unknown'
126
+ };
127
+
128
+ // Detect indentation
129
+ const lines = content.split('\n');
130
+ let twoSpaces = 0;
131
+ let fourSpaces = 0;
132
+ let tabs = 0;
133
+
134
+ for (const line of lines) {
135
+ const match = line.match(/^(\s+)/);
136
+ if (match) {
137
+ const indent = match[1];
138
+ if (indent === ' ') twoSpaces++;
139
+ else if (indent === ' ') fourSpaces++;
140
+ else if (indent.includes('\t')) tabs++;
141
+ }
142
+ }
143
+
144
+ if (twoSpaces > fourSpaces && twoSpaces > tabs) {
145
+ style.indentation = '2 spaces';
146
+ } else if (fourSpaces > twoSpaces && fourSpaces > tabs) {
147
+ style.indentation = '4 spaces';
148
+ } else if (tabs > twoSpaces && tabs > fourSpaces) {
149
+ style.indentation = 'tabs';
150
+ }
151
+
152
+ // Detect quotes
153
+ const singleQuotes = (content.match(/'\w+'/g) || []).length;
154
+ const doubleQuotes = (content.match(/"\w+"/g) || []).length;
155
+ const backticks = (content.match(/`[^`]+`/g) || []).length;
156
+
157
+ if (singleQuotes > doubleQuotes && singleQuotes > backticks) {
158
+ style.quotes = 'single';
159
+ } else if (doubleQuotes > singleQuotes && doubleQuotes > backticks) {
160
+ style.quotes = 'double';
161
+ } else if (backticks > 0) {
162
+ style.quotes = 'template literals preferred';
163
+ }
164
+
165
+ // Detect semicolons
166
+ const withSemicolon = (content.match(/;$/gm) || []).length;
167
+ const withoutSemicolon = (content.match(/[^;{}\s]$/gm) || []).length;
168
+
169
+ style.semicolons = withSemicolon > withoutSemicolon ? 'required' : 'omitted';
170
+
171
+ // Detect trailing commas
172
+ const trailingCommas = (content.match(/,\s*[\]}]/g) || []).length;
173
+ style.trailingCommas = trailingCommas > 5 ? 'preferred' : 'avoided';
174
+
175
+ return style;
176
+ }
177
+
178
+ /**
179
+ * Detect import style conventions
180
+ *
181
+ * @param {string} content - File content to analyze
182
+ * @returns {Object} Import style analysis
183
+ */
184
+ export function detectImportStyle(content) {
185
+ const style = {
186
+ namedImports: 0,
187
+ defaultImports: 0,
188
+ namespaceImports: 0,
189
+ relativeImports: 0,
190
+ absoluteImports: 0,
191
+ aliasedImports: 0
192
+ };
193
+
194
+ const importRegex = /import\s+(?:{[^}]+}|[\w]+|\*\s+as\s+[\w]+)\s+from\s+['"]([^'"]+)['"]/g;
195
+ let match;
196
+
197
+ while ((match = importRegex.exec(content)) !== null) {
198
+ const importStatement = match[0];
199
+ const importPath = match[1];
200
+
201
+ // Count import types
202
+ if (importStatement.includes('{')) {
203
+ style.namedImports++;
204
+ } else if (importStatement.includes('* as')) {
205
+ style.namespaceImports++;
206
+ } else {
207
+ style.defaultImports++;
208
+ }
209
+
210
+ // Count path types
211
+ if (importPath.startsWith('.')) {
212
+ style.relativeImports++;
213
+ } else if (importPath.startsWith('@/') || importPath.startsWith('~/')) {
214
+ style.aliasedImports++;
215
+ } else {
216
+ style.absoluteImports++;
217
+ }
218
+ }
219
+
220
+ // Determine preferences
221
+ const total = style.namedImports + style.defaultImports + style.namespaceImports;
222
+ const preference = total > 0 ? {
223
+ importType: style.namedImports > style.defaultImports ? 'named' : 'default',
224
+ pathStyle: style.aliasedImports > style.relativeImports ? 'aliased' :
225
+ style.relativeImports > 0 ? 'relative' : 'absolute'
226
+ } : null;
227
+
228
+ return {
229
+ ...style,
230
+ preference
231
+ };
232
+ }
233
+
234
+ /**
235
+ * Detect ESLint/Prettier configuration
236
+ *
237
+ * @param {string[]} files - Array of file paths
238
+ * @param {string} projectRoot - Absolute path to project root
239
+ * @returns {Promise<Object>} Linting configuration
240
+ */
241
+ export async function detectLintingConfig(files, projectRoot) {
242
+ const config = {
243
+ eslint: false,
244
+ prettier: false,
245
+ styleGuide: null
246
+ };
247
+
248
+ // Check for config files
249
+ const hasEslint = files.some(file =>
250
+ file.endsWith('.eslintrc') ||
251
+ file.endsWith('.eslintrc.js') ||
252
+ file.endsWith('.eslintrc.json') ||
253
+ file.endsWith('eslint.config.js')
254
+ );
255
+
256
+ const hasPrettier = files.some(file =>
257
+ file.endsWith('.prettierrc') ||
258
+ file.endsWith('.prettierrc.js') ||
259
+ file.endsWith('.prettierrc.json') ||
260
+ file.endsWith('prettier.config.js')
261
+ );
262
+
263
+ config.eslint = hasEslint;
264
+ config.prettier = hasPrettier;
265
+
266
+ // Try to detect style guide from package.json
267
+ try {
268
+ const packageJsonPath = path.join(projectRoot, 'package.json');
269
+ const content = await fs.readFile(packageJsonPath, 'utf-8');
270
+ const packageJson = JSON.parse(content);
271
+
272
+ const allDeps = {
273
+ ...packageJson.dependencies,
274
+ ...packageJson.devDependencies
275
+ };
276
+
277
+ if (allDeps['eslint-config-airbnb']) {
278
+ config.styleGuide = 'airbnb';
279
+ } else if (allDeps['eslint-config-standard']) {
280
+ config.styleGuide = 'standard';
281
+ } else if (allDeps['eslint-config-google']) {
282
+ config.styleGuide = 'google';
283
+ } else if (allDeps['@typescript-eslint/eslint-plugin']) {
284
+ config.styleGuide = 'typescript';
285
+ }
286
+ } catch (error) {
287
+ // Can't read package.json, skip style guide detection
288
+ }
289
+
290
+ return config;
291
+ }
292
+
293
+ /**
294
+ * Detect all conventions in project
295
+ *
296
+ * @param {string[]} files - Array of file paths
297
+ * @param {string} projectRoot - Absolute path to project root
298
+ * @returns {Promise<Object>} All detected conventions
299
+ */
300
+ export async function detectConventions(files, projectRoot) {
301
+ const naming = detectNamingConventions(files);
302
+ const fileStructure = detectFileStructure(files);
303
+ const linting = await detectLintingConfig(files, projectRoot);
304
+
305
+ // Sample a few files for code style analysis
306
+ let codeStyle = null;
307
+ let importStyle = null;
308
+
309
+ const sampleFiles = files
310
+ .filter(file => /\.(js|ts|jsx|tsx)$/.test(file))
311
+ .slice(0, 3);
312
+
313
+ if (sampleFiles.length > 0) {
314
+ try {
315
+ const samplePath = path.join(projectRoot, sampleFiles[0]);
316
+ const content = await fs.readFile(samplePath, 'utf-8');
317
+ codeStyle = detectCodeStyle(content);
318
+ importStyle = detectImportStyle(content);
319
+ } catch (error) {
320
+ // Can't read sample file, skip style detection
321
+ }
322
+ }
323
+
324
+ return {
325
+ naming: naming.dominant,
326
+ namingConfidence: naming.confidence,
327
+ fileStructure: fileStructure.structure,
328
+ structureConfidence: fileStructure.confidence,
329
+ codeStyle,
330
+ importStyle,
331
+ linting
332
+ };
333
+ }
334
+
335
+ export default {
336
+ detectConventions,
337
+ detectNamingConventions,
338
+ detectFileStructure,
339
+ detectCodeStyle,
340
+ detectImportStyle,
341
+ detectLintingConfig
342
+ };