@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.
- package/.claude/agents/architect.md +283 -11
- package/.claude/agents/backend.md +282 -218
- package/.claude/agents/frontend.md +242 -319
- package/.claude/agents/orchestrator.md +27 -27
- package/.claude/agents/reviewer.md +1 -1
- package/.claude/agents/tester.md +375 -284
- package/.claude/commands/agentful-decide.md +104 -29
- package/.claude/commands/agentful-start.md +18 -16
- package/.claude/commands/agentful-status.md +28 -22
- package/.claude/commands/agentful-validate.md +42 -20
- package/.claude/commands/agentful.md +329 -0
- package/.claude/product/README.md +1 -1
- package/.claude/product/index.md +1 -1
- package/.claude/settings.json +4 -3
- package/.claude/skills/conversation/SKILL.md +1130 -0
- package/LICENSE +1 -1
- package/README.md +557 -222
- package/bin/cli.js +319 -36
- package/lib/agent-generator.js +685 -0
- package/lib/domain-detector.js +468 -0
- package/lib/domain-structure-generator.js +770 -0
- package/lib/index.js +40 -0
- package/lib/project-analyzer.js +701 -0
- package/lib/tech-stack-detector.js +1091 -0
- package/lib/template-engine.js +153 -0
- package/package.json +14 -5
- package/template/CLAUDE.md +62 -21
- package/template/PRODUCT.md +89 -1
|
@@ -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
|
+
}
|