@itz4blitz/agentful 0.1.0 → 0.1.5

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.
@@ -0,0 +1,468 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Domain Detector
5
+ * Multi-source domain detection with confidence scoring
6
+ * Identifies business domains from project structure, APIs, and code patterns
7
+ */
8
+
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+
12
+ /**
13
+ * Domain mapping table with keyword patterns
14
+ */
15
+ const DOMAIN_PATTERNS = {
16
+ 'authentication': {
17
+ keywords: ['auth', 'login', 'signup', 'signin', 'register', 'password', 'token', 'jwt', 'session', 'oauth', 'saml', '2fa', 'mfa', 'credential'],
18
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
19
+ weight: 1.0
20
+ },
21
+ 'user-management': {
22
+ keywords: ['user', 'profile', 'account', 'member', 'participant', 'subscriber', 'customer', 'tenant', 'role', 'permission', 'group'],
23
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
24
+ weight: 1.0
25
+ },
26
+ 'billing': {
27
+ keywords: ['billing', 'invoice', 'payment', 'subscription', 'pricing', 'plan', 'checkout', 'stripe', 'paypal', 'refund', 'transaction', 'order'],
28
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
29
+ weight: 1.0
30
+ },
31
+ 'content-management': {
32
+ keywords: ['content', 'article', 'post', 'blog', 'page', 'cms', 'editor', 'media', 'asset', 'document', 'publication', 'draft'],
33
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
34
+ weight: 0.9
35
+ },
36
+ 'e-commerce': {
37
+ keywords: ['product', 'cart', 'shop', 'store', 'catalog', 'inventory', 'wishlist', 'review', 'rating', 'shipping', 'discount', 'coupon'],
38
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
39
+ weight: 1.0
40
+ },
41
+ 'messaging': {
42
+ keywords: ['message', 'chat', 'conversation', 'notification', 'alert', 'email', 'sms', 'push', 'websocket', 'realtime', 'channel'],
43
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
44
+ weight: 0.9
45
+ },
46
+ 'analytics': {
47
+ keywords: ['analytics', 'metric', 'statistic', 'report', 'dashboard', 'chart', 'graph', 'tracking', 'event', 'log', 'monitor'],
48
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
49
+ weight: 0.8
50
+ },
51
+ 'search': {
52
+ keywords: ['search', 'query', 'filter', 'index', 'elasticsearch', 'algolia', 'suggestion', 'autocomplete'],
53
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
54
+ weight: 0.8
55
+ },
56
+ 'file-management': {
57
+ keywords: ['file', 'upload', 'download', 'storage', 'attachment', 'document', 'image', 'video', 's3', 'bucket', 'drive'],
58
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
59
+ weight: 0.8
60
+ },
61
+ 'social': {
62
+ keywords: ['social', 'friend', 'follow', 'like', 'comment', 'share', 'feed', 'timeline', 'activity', 'community'],
63
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
64
+ weight: 0.7
65
+ },
66
+ 'workflow': {
67
+ keywords: ['workflow', 'process', 'approval', 'task', 'job', 'queue', 'pipeline', 'automation', 'scheduler', 'cron'],
68
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
69
+ weight: 0.8
70
+ },
71
+ 'api-management': {
72
+ keywords: ['api', 'endpoint', 'route', 'controller', 'handler', 'middleware', 'graphql', 'rest', 'grpc', 'webhook'],
73
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
74
+ weight: 0.7
75
+ },
76
+ 'database': {
77
+ keywords: ['database', 'repository', 'dao', 'orm', 'migration', 'schema', 'seed', 'model', 'entity'],
78
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
79
+ weight: 0.6
80
+ },
81
+ 'configuration': {
82
+ keywords: ['config', 'setting', 'preference', 'option', 'feature-flag', 'toggle', 'localization', 'i18n', 'locale'],
83
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
84
+ weight: 0.6
85
+ },
86
+ 'security': {
87
+ keywords: ['security', 'encryption', 'hash', 'captcha', 'firewall', 'audit', 'compliance', 'policy', 'acl', 'rbac'],
88
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
89
+ weight: 0.9
90
+ },
91
+ 'integration': {
92
+ keywords: ['integration', 'webhook', 'api-client', 'sync', 'import', 'export', 'connector', 'adapter', 'proxy'],
93
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
94
+ weight: 0.7
95
+ },
96
+ 'admin': {
97
+ keywords: ['admin', 'management', 'panel', 'console', 'backend', 'portal', 'dashboard', 'control'],
98
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
99
+ weight: 0.7
100
+ }
101
+ };
102
+
103
+ /**
104
+ * Main domain detection function
105
+ * @param {string} projectRoot - Root directory of the project
106
+ * @param {Object} quickScan - Quick scan results from project analyzer
107
+ * @returns {Promise<Object>} Detected domains with confidence scores
108
+ */
109
+ export async function detectDomains(projectRoot, quickScan = {}) {
110
+ const results = {
111
+ detected: [],
112
+ confidence: {},
113
+ signals: {},
114
+ totalConfidence: 0
115
+ };
116
+
117
+ try {
118
+ // Signal 1: Directory structure analysis (40% weight)
119
+ const structureSignals = await analyzeDirectoryStructure(projectRoot);
120
+ results.signals.structure = structureSignals;
121
+
122
+ // Signal 2: API route analysis (30% weight)
123
+ const apiSignals = await analyzeApiRoutes(projectRoot, quickScan);
124
+ results.signals.api = apiSignals;
125
+
126
+ // Signal 3: Database schema analysis (20% weight)
127
+ const schemaSignals = await analyzeDatabaseSchema(projectRoot);
128
+ results.signals.schema = schemaSignals;
129
+
130
+ // Signal 4: Module boundary analysis (10% weight)
131
+ const moduleSignals = await analyzeModuleBoundaries(projectRoot);
132
+ results.signals.modules = moduleSignals;
133
+
134
+ // Aggregate signals with weights
135
+ const domainScores = {};
136
+
137
+ // Structure signals (40%)
138
+ for (const [domain, score] of Object.entries(structureSignals)) {
139
+ domainScores[domain] = (domainScores[domain] || 0) + (score * 0.4);
140
+ }
141
+
142
+ // API signals (30%)
143
+ for (const [domain, score] of Object.entries(apiSignals)) {
144
+ domainScores[domain] = (domainScores[domain] || 0) + (score * 0.3);
145
+ }
146
+
147
+ // Schema signals (20%)
148
+ for (const [domain, score] of Object.entries(schemaSignals)) {
149
+ domainScores[domain] = (domainScores[domain] || 0) + (score * 0.2);
150
+ }
151
+
152
+ // Module signals (10%)
153
+ for (const [domain, score] of Object.entries(moduleSignals)) {
154
+ domainScores[domain] = (domainScores[domain] || 0) + (score * 0.1);
155
+ }
156
+
157
+ // Filter by confidence threshold (0.3) and sort
158
+ const threshold = 0.3;
159
+ for (const [domain, score] of Object.entries(domainScores)) {
160
+ if (score >= threshold) {
161
+ results.detected.push(domain);
162
+ results.confidence[domain] = Math.min(score, 1.0);
163
+ }
164
+ }
165
+
166
+ // Sort by confidence (descending)
167
+ results.detected.sort((a, b) => results.confidence[b] - results.confidence[a]);
168
+
169
+ // Calculate total confidence
170
+ results.totalConfidence = results.detected.length > 0
171
+ ? Object.values(results.confidence).reduce((sum, conf) => sum + conf, 0) / results.detected.length
172
+ : 0;
173
+
174
+ } catch (error) {
175
+ results.error = error.message;
176
+ }
177
+
178
+ return results;
179
+ }
180
+
181
+ /**
182
+ * Analyze directory structure for domain indicators
183
+ */
184
+ async function analyzeDirectoryStructure(projectRoot) {
185
+ const signals = {};
186
+
187
+ try {
188
+ const entries = fs.readdirSync(projectRoot, { withFileTypes: true });
189
+
190
+ for (const entry of entries) {
191
+ if (!entry.isDirectory()) continue;
192
+
193
+ const dirName = entry.name.toLowerCase();
194
+
195
+ // Check each domain pattern
196
+ for (const [domain, pattern] of Object.entries(DOMAIN_PATTERNS)) {
197
+ for (const keyword of pattern.keywords) {
198
+ if (dirName.includes(keyword.toLowerCase())) {
199
+ // Exact match gets higher score
200
+ if (dirName === keyword.toLowerCase()) {
201
+ signals[domain] = Math.min(signals[domain] || 0, 0) + 0.8;
202
+ } else {
203
+ signals[domain] = Math.min(signals[domain] || 0, 0) + 0.5;
204
+ }
205
+ }
206
+ }
207
+ }
208
+
209
+ // Recursively analyze subdirectories (limit depth)
210
+ const subPath = path.join(projectRoot, entry.name);
211
+ try {
212
+ const subEntries = fs.readdirSync(subPath, { withFileTypes: true });
213
+ for (const subEntry of subEntries) {
214
+ if (subEntry.isDirectory()) {
215
+ const subDirName = subEntry.name.toLowerCase();
216
+
217
+ for (const [domain, pattern] of Object.entries(DOMAIN_PATTERNS)) {
218
+ for (const keyword of pattern.keywords) {
219
+ if (subDirName.includes(keyword.toLowerCase())) {
220
+ signals[domain] = Math.min(signals[domain] || 0, 0) + 0.3;
221
+ }
222
+ }
223
+ }
224
+ }
225
+ }
226
+ } catch (error) {
227
+ // Skip directories we can't read
228
+ }
229
+ }
230
+
231
+ } catch (error) {
232
+ // Return empty signals on error
233
+ }
234
+
235
+ return signals;
236
+ }
237
+
238
+ /**
239
+ * Analyze API routes for domain indicators
240
+ */
241
+ async function analyzeApiRoutes(projectRoot, quickScan) {
242
+ const signals = {};
243
+
244
+ try {
245
+ // Common API directory patterns
246
+ const apiDirs = [
247
+ 'api', 'routes', 'controllers', 'handlers', 'app/api',
248
+ 'src/api', 'src/routes', 'src/controllers', 'src/handlers'
249
+ ];
250
+
251
+ for (const apiDir of apiDirs) {
252
+ const apiPath = path.join(projectRoot, apiDir);
253
+
254
+ if (!fs.existsSync(apiPath)) continue;
255
+
256
+ const walkDir = (dir, depth = 0) => {
257
+ if (depth > 3) return;
258
+
259
+ try {
260
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
261
+
262
+ for (const entry of entries) {
263
+ const fullPath = path.join(dir, entry.name);
264
+
265
+ if (entry.isDirectory()) {
266
+ walkDir(fullPath, depth + 1);
267
+ } else if (entry.isFile()) {
268
+ const ext = path.extname(entry.name);
269
+ if (['.js', '.ts', '.py', '.go', '.rs'].includes(ext)) {
270
+ analyzeFileForDomains(fullPath, signals);
271
+ }
272
+ }
273
+ }
274
+ } catch (error) {
275
+ // Skip files we can't read
276
+ }
277
+ };
278
+
279
+ walkDir(apiPath);
280
+ }
281
+
282
+ } catch (error) {
283
+ // Return empty signals on error
284
+ }
285
+
286
+ return signals;
287
+ }
288
+
289
+ /**
290
+ * Analyze individual file for domain indicators
291
+ */
292
+ function analyzeFileForDomains(filePath, signals) {
293
+ try {
294
+ const content = fs.readFileSync(filePath, 'utf-8');
295
+ const fileName = path.basename(filePath).toLowerCase();
296
+
297
+ for (const [domain, pattern] of Object.entries(DOMAIN_PATTERNS)) {
298
+ for (const keyword of pattern.keywords) {
299
+ // Check filename
300
+ if (fileName.includes(keyword.toLowerCase())) {
301
+ signals[domain] = Math.min(signals[domain] || 0, 0) + 0.4;
302
+ }
303
+
304
+ // Check content with regex for word boundaries
305
+ const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
306
+ const matches = content.match(regex);
307
+ if (matches) {
308
+ // More matches = higher confidence, but cap the contribution
309
+ const matchScore = Math.min(matches.length * 0.1, 0.5);
310
+ signals[domain] = Math.min((signals[domain] || 0) + matchScore, 1.0);
311
+ }
312
+ }
313
+ }
314
+
315
+ } catch (error) {
316
+ // Skip files we can't read
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Analyze database schema files for domain indicators
322
+ */
323
+ async function analyzeDatabaseSchema(projectRoot) {
324
+ const signals = {};
325
+
326
+ try {
327
+ // Common schema file patterns
328
+ const schemaPatterns = [
329
+ '**/schema.prisma',
330
+ '**/models.py',
331
+ '**/entities/*.ts',
332
+ '**/migrations/*.sql',
333
+ '**/seeds/*.js',
334
+ '**/db/schema.ts',
335
+ '**/database/schema.*'
336
+ ];
337
+
338
+ // Look for schema directories
339
+ const schemaDirs = [
340
+ 'prisma', 'models', 'entities', 'migrations', 'schema',
341
+ 'src/models', 'src/entities', 'src/schema', 'database'
342
+ ];
343
+
344
+ for (const schemaDir of schemaDirs) {
345
+ const schemaPath = path.join(projectRoot, schemaDir);
346
+
347
+ if (!fs.existsSync(schemaPath)) continue;
348
+
349
+ const walkDir = (dir, depth = 0) => {
350
+ if (depth > 2) return;
351
+
352
+ try {
353
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
354
+
355
+ for (const entry of entries) {
356
+ const fullPath = path.join(dir, entry.name);
357
+
358
+ if (entry.isDirectory()) {
359
+ walkDir(fullPath, depth + 1);
360
+ } else if (entry.isFile()) {
361
+ const ext = path.extname(entry.name);
362
+ if (['.prisma', '.py', '.ts', '.js', '.sql'].includes(ext)) {
363
+ analyzeFileForDomains(fullPath, signals);
364
+ }
365
+ }
366
+ }
367
+ } catch (error) {
368
+ // Skip files we can't read
369
+ }
370
+ };
371
+
372
+ walkDir(schemaPath);
373
+ }
374
+
375
+ } catch (error) {
376
+ // Return empty signals on error
377
+ }
378
+
379
+ return signals;
380
+ }
381
+
382
+ /**
383
+ * Analyze module boundaries for domain indicators
384
+ */
385
+ async function analyzeModuleBoundaries(projectRoot) {
386
+ const signals = {};
387
+
388
+ try {
389
+ // Look for module boundary indicators
390
+ const moduleIndicators = [
391
+ 'package.json',
392
+ 'go.mod',
393
+ 'Cargo.toml',
394
+ 'pom.xml',
395
+ '.csproj'
396
+ ];
397
+
398
+ for (const indicator of moduleIndicators) {
399
+ const indicatorPath = path.join(projectRoot, indicator);
400
+
401
+ if (!fs.existsSync(indicatorPath)) continue;
402
+
403
+ const content = fs.readFileSync(indicatorPath, 'utf-8');
404
+
405
+ for (const [domain, pattern] of Object.entries(DOMAIN_PATTERNS)) {
406
+ for (const keyword of pattern.keywords) {
407
+ if (content.toLowerCase().includes(keyword)) {
408
+ signals[domain] = Math.min((signals[domain] || 0) + 0.2, 1.0);
409
+ }
410
+ }
411
+ }
412
+ }
413
+
414
+ // Check for feature-based module organization
415
+ const featureDirs = ['features', 'modules', 'domains', 'slices'];
416
+ for (const featureDir of featureDirs) {
417
+ const featurePath = path.join(projectRoot, featureDir);
418
+
419
+ if (!fs.existsSync(featurePath)) continue;
420
+
421
+ const entries = fs.readdirSync(featurePath, { withFileTypes: true });
422
+
423
+ for (const entry of entries) {
424
+ if (entry.isDirectory()) {
425
+ const moduleName = entry.name.toLowerCase();
426
+
427
+ for (const [domain, pattern] of Object.entries(DOMAIN_PATTERNS)) {
428
+ for (const keyword of pattern.keywords) {
429
+ if (moduleName.includes(keyword.toLowerCase())) {
430
+ signals[domain] = Math.min((signals[domain] || 0) + 0.3, 1.0);
431
+ }
432
+ }
433
+ }
434
+ }
435
+ }
436
+ }
437
+
438
+ } catch (error) {
439
+ // Return empty signals on error
440
+ }
441
+
442
+ return signals;
443
+ }
444
+
445
+ /**
446
+ * Get domain-specific keywords for a given domain
447
+ */
448
+ export function getDomainKeywords(domain) {
449
+ return DOMAIN_PATTERNS[domain]?.keywords || [];
450
+ }
451
+
452
+ /**
453
+ * Get all available domain names
454
+ */
455
+ export function getAllDomains() {
456
+ return Object.keys(DOMAIN_PATTERNS);
457
+ }
458
+
459
+ /**
460
+ * Add custom domain pattern
461
+ */
462
+ export function addCustomDomainPattern(domain, keywords, weight = 1.0) {
463
+ DOMAIN_PATTERNS[domain] = {
464
+ keywords,
465
+ extensions: ['.js', '.ts', '.py', '.go', '.rs', '.java', '.cs'],
466
+ weight
467
+ };
468
+ }