gemkit-cli 0.2.3 → 0.3.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.
- package/README.md +141 -7
- package/dist/commands/agent/index.d.ts +9 -0
- package/dist/commands/agent/index.js +1329 -0
- package/dist/commands/cache/index.d.ts +5 -0
- package/dist/commands/cache/index.js +43 -0
- package/dist/commands/catalog/index.d.ts +2 -0
- package/dist/commands/catalog/index.js +57 -0
- package/dist/commands/config/index.d.ts +7 -0
- package/dist/commands/config/index.js +122 -0
- package/dist/commands/convert/index.d.ts +8 -0
- package/dist/commands/convert/index.js +391 -0
- package/dist/commands/doctor/index.d.ts +2 -0
- package/dist/commands/doctor/index.js +243 -0
- package/dist/commands/extension/index.d.ts +5 -0
- package/dist/commands/extension/index.js +52 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.js +37 -0
- package/dist/commands/init/index.d.ts +6 -0
- package/dist/commands/init/index.js +345 -0
- package/dist/commands/new/index.d.ts +5 -0
- package/dist/commands/new/index.js +49 -0
- package/dist/commands/office/index.d.ts +5 -0
- package/dist/commands/office/index.js +283 -0
- package/dist/commands/paste/index.d.ts +10 -0
- package/dist/commands/paste/index.js +533 -0
- package/dist/commands/plan/index.d.ts +8 -0
- package/dist/commands/plan/index.js +247 -0
- package/dist/commands/session/index.d.ts +8 -0
- package/dist/commands/session/index.js +289 -0
- package/dist/commands/tokens/index.d.ts +6 -0
- package/dist/commands/tokens/index.js +148 -0
- package/dist/commands/update/index.d.ts +26 -0
- package/dist/commands/update/index.js +199 -0
- package/dist/commands/versions/index.d.ts +5 -0
- package/dist/commands/versions/index.js +39 -0
- package/dist/domains/agent/index.d.ts +8 -0
- package/dist/domains/agent/index.js +8 -0
- package/dist/domains/agent/mappings.d.ts +32 -0
- package/dist/domains/agent/mappings.js +164 -0
- package/dist/domains/agent/profile.d.ts +26 -0
- package/dist/domains/agent/profile.js +225 -0
- package/dist/domains/agent/pty-context.d.ts +11 -0
- package/dist/domains/agent/pty-context.js +83 -0
- package/dist/domains/agent/pty-providers.d.ts +18 -0
- package/dist/domains/agent/pty-providers.js +66 -0
- package/dist/domains/agent/pty-session.d.ts +33 -0
- package/dist/domains/agent/pty-session.js +82 -0
- package/dist/domains/agent/pty-types.d.ts +127 -0
- package/dist/domains/agent/pty-types.js +4 -0
- package/dist/domains/agent/search.d.ts +45 -0
- package/dist/domains/agent/search.js +614 -0
- package/dist/domains/agent/types.d.ts +78 -0
- package/dist/domains/agent/types.js +5 -0
- package/dist/domains/agent-office/documents-scanner.d.ts +9 -0
- package/dist/domains/agent-office/documents-scanner.js +143 -0
- package/dist/domains/agent-office/event-emitter.d.ts +43 -0
- package/dist/domains/agent-office/event-emitter.js +86 -0
- package/dist/domains/agent-office/file-watcher.d.ts +40 -0
- package/dist/domains/agent-office/file-watcher.js +173 -0
- package/dist/domains/agent-office/icons.d.ts +11 -0
- package/dist/domains/agent-office/icons.js +36 -0
- package/dist/domains/agent-office/index.d.ts +12 -0
- package/dist/domains/agent-office/index.js +20 -0
- package/dist/domains/agent-office/renderer/web/assets.d.ts +11 -0
- package/dist/domains/agent-office/renderer/web/assets.js +3419 -0
- package/dist/domains/agent-office/renderer/web/server.d.ts +42 -0
- package/dist/domains/agent-office/renderer/web/server.js +228 -0
- package/dist/domains/agent-office/renderer/web.d.ts +30 -0
- package/dist/domains/agent-office/renderer/web.js +111 -0
- package/dist/domains/agent-office/session-bridge.d.ts +23 -0
- package/dist/domains/agent-office/session-bridge.js +171 -0
- package/dist/domains/agent-office/state-machine.d.ts +5 -0
- package/dist/domains/agent-office/state-machine.js +82 -0
- package/dist/domains/agent-office/types.d.ts +91 -0
- package/dist/domains/agent-office/types.js +4 -0
- package/dist/domains/cache/index.d.ts +1 -0
- package/dist/domains/cache/index.js +1 -0
- package/dist/domains/cache/manager.d.ts +22 -0
- package/dist/domains/cache/manager.js +84 -0
- package/dist/domains/config/index.d.ts +5 -0
- package/dist/domains/config/index.js +5 -0
- package/dist/domains/config/manager.d.ts +24 -0
- package/dist/domains/config/manager.js +85 -0
- package/dist/domains/config/schema.d.ts +17 -0
- package/dist/domains/config/schema.js +96 -0
- package/dist/domains/convert/converter.d.ts +78 -0
- package/dist/domains/convert/converter.js +471 -0
- package/dist/domains/convert/index.d.ts +5 -0
- package/dist/domains/convert/index.js +5 -0
- package/dist/domains/convert/types.d.ts +88 -0
- package/dist/domains/convert/types.js +18 -0
- package/dist/domains/github/download.d.ts +12 -0
- package/dist/domains/github/download.js +51 -0
- package/dist/domains/github/index.d.ts +2 -0
- package/dist/domains/github/index.js +2 -0
- package/dist/domains/github/releases.d.ts +16 -0
- package/dist/domains/github/releases.js +68 -0
- package/dist/domains/installation/conflict.d.ts +13 -0
- package/dist/domains/installation/conflict.js +38 -0
- package/dist/domains/installation/file-sync.d.ts +16 -0
- package/dist/domains/installation/file-sync.js +77 -0
- package/dist/domains/installation/index.d.ts +3 -0
- package/dist/domains/installation/index.js +3 -0
- package/dist/domains/installation/metadata.d.ts +20 -0
- package/dist/domains/installation/metadata.js +52 -0
- package/dist/domains/plan/index.d.ts +2 -0
- package/dist/domains/plan/index.js +2 -0
- package/dist/domains/plan/resolver.d.ts +24 -0
- package/dist/domains/plan/resolver.js +164 -0
- package/dist/domains/plan/types.d.ts +13 -0
- package/dist/domains/plan/types.js +4 -0
- package/dist/domains/session/env.d.ts +51 -0
- package/dist/domains/session/env.js +118 -0
- package/dist/domains/session/index.d.ts +8 -0
- package/dist/domains/session/index.js +8 -0
- package/dist/domains/session/manager.d.ts +56 -0
- package/dist/domains/session/manager.js +205 -0
- package/dist/domains/session/paths.d.ts +6 -0
- package/dist/domains/session/paths.js +6 -0
- package/dist/domains/session/types.d.ts +121 -0
- package/dist/domains/session/types.js +5 -0
- package/dist/domains/session/writer.d.ts +82 -0
- package/dist/domains/session/writer.js +431 -0
- package/dist/domains/tokens/index.d.ts +5 -0
- package/dist/domains/tokens/index.js +5 -0
- package/dist/domains/tokens/pricing.d.ts +38 -0
- package/dist/domains/tokens/pricing.js +129 -0
- package/dist/domains/tokens/scanner.d.ts +42 -0
- package/dist/domains/tokens/scanner.js +168 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +90 -59
- package/dist/services/aipty.d.ts +76 -0
- package/dist/services/aipty.js +276 -0
- package/dist/services/archive.d.ts +22 -0
- package/dist/services/archive.js +53 -0
- package/dist/services/auto-update.d.ts +26 -0
- package/dist/services/auto-update.js +117 -0
- package/dist/services/hash.d.ts +36 -0
- package/dist/services/hash.js +63 -0
- package/dist/services/logger.d.ts +28 -0
- package/dist/services/logger.js +102 -0
- package/dist/services/music.d.ts +67 -0
- package/dist/services/music.js +290 -0
- package/dist/services/npm.d.ts +22 -0
- package/dist/services/npm.js +65 -0
- package/dist/services/pty-client.d.ts +66 -0
- package/dist/services/pty-client.js +154 -0
- package/dist/services/pty-server.d.ts +102 -0
- package/dist/services/pty-server.js +613 -0
- package/dist/types/index.d.ts +155 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/colors.d.ts +43 -0
- package/dist/utils/colors.js +98 -0
- package/dist/utils/errors.d.ts +24 -0
- package/dist/utils/errors.js +56 -0
- package/dist/utils/paths.d.ts +46 -0
- package/dist/utils/paths.js +89 -0
- package/dist/utils/platform.d.ts +11 -0
- package/dist/utils/platform.js +31 -0
- package/package.json +55 -54
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-Skill Composer Search - BM25 search engine for Agent+Skills combinations
|
|
3
|
+
* Ported from Python core.py with same logic
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { getExtensionsDir } from '../../utils/paths.js';
|
|
8
|
+
// ============ CONFIGURATION ============
|
|
9
|
+
const DATA_DIR = '.gemini/extensions/spawn-agent/data';
|
|
10
|
+
const MAX_RESULTS = 3;
|
|
11
|
+
// CSV Configuration for different search modes
|
|
12
|
+
const CSV_CONFIG = {
|
|
13
|
+
combination: {
|
|
14
|
+
file: 'combinations.csv',
|
|
15
|
+
searchCols: ['keywords', 'description', 'use_when', 'agent', 'skills', 'intent', 'domain'],
|
|
16
|
+
outputCols: ['id', 'agent', 'skills', 'intent', 'domain', 'complexity', 'description', 'use_when']
|
|
17
|
+
},
|
|
18
|
+
intent: {
|
|
19
|
+
file: 'intents.csv',
|
|
20
|
+
searchCols: ['primary_keywords', 'expanded_keywords', 'question_patterns'],
|
|
21
|
+
outputCols: ['intent', 'agent', 'primary_keywords', 'expanded_keywords', 'action_verbs']
|
|
22
|
+
},
|
|
23
|
+
domain: {
|
|
24
|
+
file: 'domains.csv',
|
|
25
|
+
searchCols: ['keywords', 'frameworks', 'file_patterns', 'technical_terms'],
|
|
26
|
+
outputCols: ['domain', 'base_skills', 'enhanced_skills', 'keywords', 'frameworks']
|
|
27
|
+
},
|
|
28
|
+
synonym: {
|
|
29
|
+
file: 'synonyms.csv',
|
|
30
|
+
searchCols: ['word', 'synonyms'],
|
|
31
|
+
outputCols: ['word', 'synonyms', 'category']
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
// Complexity keywords for skill count determination
|
|
35
|
+
const COMPLEXITY_KEYWORDS = {
|
|
36
|
+
simple: ['quick', 'simple', 'basic', 'small', 'minor', 'trivial', 'easy', 'straightforward'],
|
|
37
|
+
standard: [],
|
|
38
|
+
full: ['comprehensive', 'full', 'complete', 'entire', 'thorough', 'detailed'],
|
|
39
|
+
complex: ['deep', 'extensive', 'advanced', 'complex', 'sophisticated', 'enterprise', 'production']
|
|
40
|
+
};
|
|
41
|
+
// Max skills per complexity level
|
|
42
|
+
const COMPLEXITY_MAX_SKILLS = {
|
|
43
|
+
simple: 1,
|
|
44
|
+
standard: 2,
|
|
45
|
+
full: 3,
|
|
46
|
+
complex: 5
|
|
47
|
+
};
|
|
48
|
+
// Intent to agent mapping
|
|
49
|
+
const INTENT_AGENT_MAP = {
|
|
50
|
+
research: 'researcher',
|
|
51
|
+
plan: 'planner',
|
|
52
|
+
execute: 'code-executor',
|
|
53
|
+
debug: 'debugger',
|
|
54
|
+
review: 'code-reviewer',
|
|
55
|
+
test: 'tester',
|
|
56
|
+
design: 'ui-ux-designer',
|
|
57
|
+
docs: 'docs-manager',
|
|
58
|
+
git: 'git-manager',
|
|
59
|
+
manage: 'project-manager'
|
|
60
|
+
};
|
|
61
|
+
// Domain to skill mapping (fallback)
|
|
62
|
+
const DOMAIN_SKILL_MAP = {
|
|
63
|
+
frontend: ['frontend-design'],
|
|
64
|
+
backend: ['backend-development'],
|
|
65
|
+
auth: ['better-auth', 'backend-development'],
|
|
66
|
+
payment: ['payment-integration'],
|
|
67
|
+
database: ['databases'],
|
|
68
|
+
mobile: ['mobile-development'],
|
|
69
|
+
fullstack: ['web-frameworks', 'backend-development'],
|
|
70
|
+
ecommerce: ['shopify'],
|
|
71
|
+
media: ['ai-multimodal'],
|
|
72
|
+
ai: ['ai-multimodal'],
|
|
73
|
+
quality: ['code-review'],
|
|
74
|
+
codebase: ['repomix'],
|
|
75
|
+
general: ['research']
|
|
76
|
+
};
|
|
77
|
+
// ============ BM25 IMPLEMENTATION ============
|
|
78
|
+
class BM25 {
|
|
79
|
+
k1;
|
|
80
|
+
b;
|
|
81
|
+
corpus = [];
|
|
82
|
+
docLengths = [];
|
|
83
|
+
avgdl = 0;
|
|
84
|
+
idf = new Map();
|
|
85
|
+
docFreqs = new Map();
|
|
86
|
+
N = 0;
|
|
87
|
+
constructor(k1 = 1.5, b = 0.75) {
|
|
88
|
+
this.k1 = k1;
|
|
89
|
+
this.b = b;
|
|
90
|
+
}
|
|
91
|
+
tokenize(text) {
|
|
92
|
+
const cleaned = String(text).toLowerCase().replace(/[^\w\s]/g, ' ');
|
|
93
|
+
return cleaned.split(/\s+/).filter(w => w.length > 1);
|
|
94
|
+
}
|
|
95
|
+
fit(documents) {
|
|
96
|
+
this.corpus = documents.map(doc => this.tokenize(doc));
|
|
97
|
+
this.N = this.corpus.length;
|
|
98
|
+
if (this.N === 0)
|
|
99
|
+
return;
|
|
100
|
+
this.docLengths = this.corpus.map(doc => doc.length);
|
|
101
|
+
this.avgdl = this.docLengths.reduce((a, b) => a + b, 0) / this.N;
|
|
102
|
+
for (const doc of this.corpus) {
|
|
103
|
+
const seen = new Set();
|
|
104
|
+
for (const word of doc) {
|
|
105
|
+
if (!seen.has(word)) {
|
|
106
|
+
this.docFreqs.set(word, (this.docFreqs.get(word) || 0) + 1);
|
|
107
|
+
seen.add(word);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const [word, freq] of this.docFreqs.entries()) {
|
|
112
|
+
this.idf.set(word, Math.log((this.N - freq + 0.5) / (freq + 0.5) + 1));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
score(query) {
|
|
116
|
+
const queryTokens = this.tokenize(query);
|
|
117
|
+
const scores = [];
|
|
118
|
+
for (let idx = 0; idx < this.corpus.length; idx++) {
|
|
119
|
+
const doc = this.corpus[idx];
|
|
120
|
+
let score = 0;
|
|
121
|
+
const docLen = this.docLengths[idx];
|
|
122
|
+
const termFreqs = new Map();
|
|
123
|
+
for (const word of doc) {
|
|
124
|
+
termFreqs.set(word, (termFreqs.get(word) || 0) + 1);
|
|
125
|
+
}
|
|
126
|
+
for (const token of queryTokens) {
|
|
127
|
+
const idfVal = this.idf.get(token);
|
|
128
|
+
if (idfVal !== undefined) {
|
|
129
|
+
const tf = termFreqs.get(token) || 0;
|
|
130
|
+
const numerator = tf * (this.k1 + 1);
|
|
131
|
+
const denominator = tf + this.k1 * (1 - this.b + this.b * docLen / this.avgdl);
|
|
132
|
+
score += idfVal * numerator / denominator;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
scores.push([idx, score]);
|
|
136
|
+
}
|
|
137
|
+
return scores.sort((a, b) => b[1] - a[1]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// ============ CSV UTILITIES ============
|
|
141
|
+
function parseCSVLine(line) {
|
|
142
|
+
const values = [];
|
|
143
|
+
let current = '';
|
|
144
|
+
let inQuotes = false;
|
|
145
|
+
for (let i = 0; i < line.length; i++) {
|
|
146
|
+
const char = line[i];
|
|
147
|
+
if (char === '"') {
|
|
148
|
+
inQuotes = !inQuotes;
|
|
149
|
+
}
|
|
150
|
+
else if (char === ',' && !inQuotes) {
|
|
151
|
+
values.push(current);
|
|
152
|
+
current = '';
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
current += char;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
values.push(current);
|
|
159
|
+
return values;
|
|
160
|
+
}
|
|
161
|
+
function loadCSV(filepath) {
|
|
162
|
+
if (!existsSync(filepath))
|
|
163
|
+
return [];
|
|
164
|
+
const content = readFileSync(filepath, 'utf-8');
|
|
165
|
+
const lines = content.trim().split('\n');
|
|
166
|
+
if (lines.length < 2)
|
|
167
|
+
return [];
|
|
168
|
+
const headers = lines[0].split(',').map(h => h.trim());
|
|
169
|
+
const rows = [];
|
|
170
|
+
for (let i = 1; i < lines.length; i++) {
|
|
171
|
+
const values = parseCSVLine(lines[i]);
|
|
172
|
+
const row = {};
|
|
173
|
+
headers.forEach((header, idx) => {
|
|
174
|
+
row[header] = values[idx]?.trim() || '';
|
|
175
|
+
});
|
|
176
|
+
rows.push(row);
|
|
177
|
+
}
|
|
178
|
+
return rows;
|
|
179
|
+
}
|
|
180
|
+
function searchCSV(filepath, searchCols, outputCols, query, maxResults) {
|
|
181
|
+
if (!existsSync(filepath))
|
|
182
|
+
return [];
|
|
183
|
+
const data = loadCSV(filepath);
|
|
184
|
+
const documents = data.map(row => searchCols.map(col => String(row[col] || '')).join(' '));
|
|
185
|
+
const bm25 = new BM25();
|
|
186
|
+
bm25.fit(documents);
|
|
187
|
+
const ranked = bm25.score(query);
|
|
188
|
+
const results = [];
|
|
189
|
+
for (const [idx, score] of ranked.slice(0, maxResults)) {
|
|
190
|
+
if (score > 0) {
|
|
191
|
+
const row = data[idx];
|
|
192
|
+
const result = {};
|
|
193
|
+
for (const col of outputCols) {
|
|
194
|
+
if (col in row)
|
|
195
|
+
result[col] = row[col];
|
|
196
|
+
}
|
|
197
|
+
result._score = Math.round(score * 10000) / 10000;
|
|
198
|
+
results.push(result);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return results;
|
|
202
|
+
}
|
|
203
|
+
// ============ SYNONYM EXPANSION ============
|
|
204
|
+
function loadSynonyms() {
|
|
205
|
+
const filepath = join(DATA_DIR, 'synonyms.csv');
|
|
206
|
+
if (!existsSync(filepath))
|
|
207
|
+
return new Map();
|
|
208
|
+
const data = loadCSV(filepath);
|
|
209
|
+
const synonyms = new Map();
|
|
210
|
+
for (const row of data) {
|
|
211
|
+
const word = (row.word || '').toLowerCase().trim();
|
|
212
|
+
const syns = (row.synonyms || '').toLowerCase().trim();
|
|
213
|
+
if (word && syns) {
|
|
214
|
+
synonyms.set(word, syns.split(',').map(s => s.trim()));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return synonyms;
|
|
218
|
+
}
|
|
219
|
+
function expandQuery(query, synonyms) {
|
|
220
|
+
if (!synonyms)
|
|
221
|
+
synonyms = loadSynonyms();
|
|
222
|
+
const words = query.toLowerCase().replace(/[^\w\s]/g, ' ').split(/\s+/);
|
|
223
|
+
const expanded = new Set(words);
|
|
224
|
+
for (const word of words) {
|
|
225
|
+
const syns = synonyms.get(word);
|
|
226
|
+
if (syns) {
|
|
227
|
+
syns.forEach(s => expanded.add(s));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return Array.from(expanded).join(' ');
|
|
231
|
+
}
|
|
232
|
+
// ============ DETECTION FUNCTIONS ============
|
|
233
|
+
export function detectComplexity(query) {
|
|
234
|
+
const queryLower = query.toLowerCase();
|
|
235
|
+
for (const [level, keywords] of Object.entries(COMPLEXITY_KEYWORDS)) {
|
|
236
|
+
for (const kw of keywords) {
|
|
237
|
+
if (queryLower.includes(kw)) {
|
|
238
|
+
return level;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return 'standard';
|
|
243
|
+
}
|
|
244
|
+
export function detectIntent(query) {
|
|
245
|
+
const queryLower = query.toLowerCase();
|
|
246
|
+
const words = queryLower.split(/\s+/);
|
|
247
|
+
const firstWord = words[0] || '';
|
|
248
|
+
// High-priority phrase patterns
|
|
249
|
+
const highPriorityPatterns = [
|
|
250
|
+
[/system architecture/, 'plan'],
|
|
251
|
+
[/design.*architecture/, 'plan'],
|
|
252
|
+
[/architect.*system/, 'plan'],
|
|
253
|
+
[/create.*roadmap/, 'plan'],
|
|
254
|
+
[/build.*roadmap/, 'plan'],
|
|
255
|
+
[/create.*strategy/, 'plan'],
|
|
256
|
+
[/stunning/, 'design'],
|
|
257
|
+
[/gorgeous/, 'design'],
|
|
258
|
+
[/beautiful.*ui/, 'design'],
|
|
259
|
+
[/beautiful.*component/, 'design'],
|
|
260
|
+
[/beautiful.*page/, 'design'],
|
|
261
|
+
[/write.*documentation/, 'docs'],
|
|
262
|
+
[/write.*docs/, 'docs'],
|
|
263
|
+
[/create.*documentation/, 'docs'],
|
|
264
|
+
[/research.*codebase/, 'research'],
|
|
265
|
+
[/deep research/, 'research'],
|
|
266
|
+
];
|
|
267
|
+
for (const [pattern, intent] of highPriorityPatterns) {
|
|
268
|
+
if (typeof pattern === 'string' ? queryLower.includes(pattern) : pattern.test(queryLower)) {
|
|
269
|
+
return intent;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Primary action verbs - first word priority
|
|
273
|
+
const primaryActionVerbs = {
|
|
274
|
+
implement: 'execute', build: 'execute', create: 'execute', add: 'execute',
|
|
275
|
+
make: 'execute', code: 'execute', develop: 'execute', write: 'execute',
|
|
276
|
+
setup: 'execute', configure: 'execute',
|
|
277
|
+
test: 'test', verify: 'test', validate: 'test',
|
|
278
|
+
debug: 'debug', fix: 'debug', troubleshoot: 'debug',
|
|
279
|
+
review: 'review', audit: 'review',
|
|
280
|
+
document: 'docs',
|
|
281
|
+
plan: 'plan',
|
|
282
|
+
design: 'design',
|
|
283
|
+
research: 'research', analyze: 'research', investigate: 'research',
|
|
284
|
+
};
|
|
285
|
+
if (firstWord in primaryActionVerbs) {
|
|
286
|
+
return primaryActionVerbs[firstWord];
|
|
287
|
+
}
|
|
288
|
+
// Priority phrases
|
|
289
|
+
const priorityPhrases = [
|
|
290
|
+
['best practices', 'research'], ['how does', 'research'], ['how do', 'research'],
|
|
291
|
+
['what is', 'research'], ['why is', 'research'], ['compare', 'research'],
|
|
292
|
+
['investigate', 'research'], ['research', 'research'], ['understand', 'research'],
|
|
293
|
+
['system architecture', 'plan'], ['implementation plan', 'plan'], ['roadmap', 'plan'],
|
|
294
|
+
['strategy for', 'plan'], ['blueprint', 'plan'],
|
|
295
|
+
['fix the', 'debug'], ['debug the', 'debug'], ['not working', 'debug'],
|
|
296
|
+
['is broken', 'debug'], ['error in', 'debug'], ['fix bug', 'debug'],
|
|
297
|
+
['test the', 'test'], ['run tests', 'test'], ['verify the', 'test'],
|
|
298
|
+
['review the', 'review'], ['audit the', 'review'], ['check code', 'review'],
|
|
299
|
+
['document the', 'docs'], ['write docs', 'docs'], ['api documentation', 'docs'],
|
|
300
|
+
];
|
|
301
|
+
for (const [phrase, intent] of priorityPhrases) {
|
|
302
|
+
if (queryLower.includes(phrase)) {
|
|
303
|
+
return intent;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Intent detection keywords (fallback scoring)
|
|
307
|
+
const intentKeywords = {
|
|
308
|
+
debug: ['fix', 'debug', 'troubleshoot', 'error', 'issue', 'bug', 'broken', 'failing', 'crash'],
|
|
309
|
+
review: ['review', 'audit', 'assess', 'check code', 'pr review', 'security audit'],
|
|
310
|
+
test: ['test', 'verify', 'validate', 'qa', 'coverage', 'unit test', 'e2e test'],
|
|
311
|
+
design: ['beautiful', 'stunning', 'gorgeous', 'ui design', 'ux design', 'mockup', 'layout'],
|
|
312
|
+
docs: ['document', 'documentation', 'readme', 'api docs', 'technical writing'],
|
|
313
|
+
git: ['commit', 'push', 'pull', 'merge', 'branch', 'git', 'stage'],
|
|
314
|
+
manage: ['status', 'progress', 'track', 'milestone', 'sprint'],
|
|
315
|
+
plan: ['plan', 'architect', 'roadmap', 'strategy', 'architecture design'],
|
|
316
|
+
execute: ['implement', 'build', 'create', 'add', 'make', 'code', 'develop'],
|
|
317
|
+
research: ['research', 'investigate', 'explore', 'study', 'analyze', 'examine', 'learn']
|
|
318
|
+
};
|
|
319
|
+
const scores = {};
|
|
320
|
+
for (const [intent, keywords] of Object.entries(intentKeywords)) {
|
|
321
|
+
const score = keywords.filter(kw => queryLower.includes(kw)).length;
|
|
322
|
+
if (score > 0)
|
|
323
|
+
scores[intent] = score;
|
|
324
|
+
}
|
|
325
|
+
if (Object.keys(scores).length > 0) {
|
|
326
|
+
return Object.entries(scores).sort((a, b) => b[1] - a[1])[0][0];
|
|
327
|
+
}
|
|
328
|
+
return 'execute';
|
|
329
|
+
}
|
|
330
|
+
export function detectDomain(query) {
|
|
331
|
+
const queryLower = query.toLowerCase();
|
|
332
|
+
// Priority domain phrases
|
|
333
|
+
const domainPhrases = [
|
|
334
|
+
['flutter', 'mobile'], ['react native', 'mobile'], ['swift', 'mobile'],
|
|
335
|
+
['kotlin', 'mobile'], ['ios app', 'mobile'], ['android app', 'mobile'],
|
|
336
|
+
['next.js', 'fullstack'], ['nextjs', 'fullstack'], ['nuxt', 'fullstack'],
|
|
337
|
+
['sveltekit', 'fullstack'], ['remix', 'fullstack'], ['server component', 'fullstack'],
|
|
338
|
+
['openai', 'ai'], ['anthropic', 'ai'], ['claude', 'ai'], ['langchain', 'ai'],
|
|
339
|
+
['llm', 'ai'], ['chatbot', 'ai'], ['rag', 'ai'], ['embedding', 'ai'], ['gpt', 'ai'],
|
|
340
|
+
['angular', 'frontend'], ['react', 'frontend'], ['vue', 'frontend'],
|
|
341
|
+
['svelte', 'frontend'], ['tailwind', 'frontend'],
|
|
342
|
+
['nestjs', 'backend'], ['express', 'backend'], ['fastify', 'backend'],
|
|
343
|
+
['hono', 'backend'], ['fastapi', 'backend'], ['websocket', 'backend'],
|
|
344
|
+
['shopify', 'ecommerce'], ['product catalog', 'ecommerce'], ['checkout flow', 'ecommerce'],
|
|
345
|
+
['stripe', 'payment'], ['paypal', 'payment'], ['subscription', 'payment'], ['billing', 'payment'],
|
|
346
|
+
['oauth', 'auth'], ['authentication', 'auth'], ['login', 'auth'], ['signup', 'auth'],
|
|
347
|
+
['prisma', 'database'], ['drizzle', 'database'], ['postgresql', 'database'],
|
|
348
|
+
['mongodb', 'database'], ['mysql', 'database'],
|
|
349
|
+
['pdf', 'media'], ['image', 'media'], ['video', 'media'], ['audio', 'media'],
|
|
350
|
+
['codebase', 'codebase'], ['repository', 'codebase'],
|
|
351
|
+
];
|
|
352
|
+
for (const [phrase, domain] of domainPhrases) {
|
|
353
|
+
if (queryLower.includes(phrase)) {
|
|
354
|
+
return domain;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Domain detection keywords (fallback scoring)
|
|
358
|
+
const domainKeywords = {
|
|
359
|
+
auth: ['login', 'signup', 'authentication', 'oauth', 'jwt', 'session', 'password', '2fa'],
|
|
360
|
+
payment: ['payment', 'checkout', 'billing', 'stripe', 'subscription', 'invoice', 'cart'],
|
|
361
|
+
database: ['database', 'sql', 'query', 'migration', 'schema', 'prisma', 'postgresql'],
|
|
362
|
+
frontend: ['ui', 'component', 'react', 'vue', 'css', 'tailwind', 'button', 'form', 'modal'],
|
|
363
|
+
backend: ['api', 'endpoint', 'server', 'service', 'route', 'rest', 'graphql', 'middleware'],
|
|
364
|
+
mobile: ['mobile', 'ios', 'android', 'react native', 'flutter', 'app', 'native'],
|
|
365
|
+
fullstack: ['fullstack', 'nextjs', 'next.js', 'nuxt', 'sveltekit', 'app router'],
|
|
366
|
+
ecommerce: ['ecommerce', 'shopify', 'store', 'product', 'cart', 'checkout', 'order'],
|
|
367
|
+
media: ['image', 'video', 'audio', 'media', 'upload', 'file', 'ocr'],
|
|
368
|
+
ai: ['ai', 'llm', 'gpt', 'claude', 'machine learning', 'embedding', 'vector', 'openai'],
|
|
369
|
+
quality: ['code review', 'lint', 'test', 'coverage', 'quality', 'refactor'],
|
|
370
|
+
codebase: ['codebase', 'repository', 'repo', 'project structure'],
|
|
371
|
+
general: []
|
|
372
|
+
};
|
|
373
|
+
const scores = {};
|
|
374
|
+
for (const [domain, keywords] of Object.entries(domainKeywords)) {
|
|
375
|
+
const score = keywords.filter(kw => queryLower.includes(kw)).length;
|
|
376
|
+
if (score > 0)
|
|
377
|
+
scores[domain] = score;
|
|
378
|
+
}
|
|
379
|
+
if (Object.keys(scores).length > 0) {
|
|
380
|
+
return Object.entries(scores).sort((a, b) => b[1] - a[1])[0][0];
|
|
381
|
+
}
|
|
382
|
+
return 'general';
|
|
383
|
+
}
|
|
384
|
+
// ============ MAIN SEARCH FUNCTIONS ============
|
|
385
|
+
export function searchCombinations(query, maxResults = MAX_RESULTS, expand = true) {
|
|
386
|
+
const config = CSV_CONFIG.combination;
|
|
387
|
+
const filepath = join(DATA_DIR, config.file);
|
|
388
|
+
let searchQuery = query;
|
|
389
|
+
if (expand) {
|
|
390
|
+
const synonyms = loadSynonyms();
|
|
391
|
+
searchQuery = expandQuery(query, synonyms);
|
|
392
|
+
}
|
|
393
|
+
const results = searchCSV(filepath, config.searchCols, config.outputCols, searchQuery, maxResults);
|
|
394
|
+
return {
|
|
395
|
+
query,
|
|
396
|
+
expandedQuery: expand ? searchQuery : undefined,
|
|
397
|
+
detectedIntent: detectIntent(query),
|
|
398
|
+
detectedDomain: detectDomain(query),
|
|
399
|
+
detectedComplexity: detectComplexity(query),
|
|
400
|
+
count: results.length,
|
|
401
|
+
results
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
export function searchCombinationsFiltered(query, intentFilter, domainFilter, maxResults = MAX_RESULTS, expand = true) {
|
|
405
|
+
const config = CSV_CONFIG.combination;
|
|
406
|
+
const filepath = join(DATA_DIR, config.file);
|
|
407
|
+
if (!existsSync(filepath)) {
|
|
408
|
+
return { results: [], count: 0 };
|
|
409
|
+
}
|
|
410
|
+
let data = loadCSV(filepath);
|
|
411
|
+
// Apply filters
|
|
412
|
+
if (intentFilter) {
|
|
413
|
+
data = data.filter(row => (row.intent || '').toLowerCase() === intentFilter.toLowerCase());
|
|
414
|
+
}
|
|
415
|
+
if (domainFilter) {
|
|
416
|
+
data = data.filter(row => (row.domain || '').toLowerCase() === domainFilter.toLowerCase());
|
|
417
|
+
}
|
|
418
|
+
if (data.length === 0) {
|
|
419
|
+
return { results: [], count: 0 };
|
|
420
|
+
}
|
|
421
|
+
let searchQuery = query;
|
|
422
|
+
if (expand) {
|
|
423
|
+
const synonyms = loadSynonyms();
|
|
424
|
+
searchQuery = expandQuery(query, synonyms);
|
|
425
|
+
}
|
|
426
|
+
const documents = data.map(row => config.searchCols.map(col => String(row[col] || '')).join(' '));
|
|
427
|
+
const bm25 = new BM25();
|
|
428
|
+
bm25.fit(documents);
|
|
429
|
+
const ranked = bm25.score(searchQuery);
|
|
430
|
+
const results = [];
|
|
431
|
+
for (const [idx, score] of ranked.slice(0, maxResults)) {
|
|
432
|
+
if (score > 0) {
|
|
433
|
+
const row = data[idx];
|
|
434
|
+
const result = {};
|
|
435
|
+
for (const col of config.outputCols) {
|
|
436
|
+
if (col in row)
|
|
437
|
+
result[col] = row[col];
|
|
438
|
+
}
|
|
439
|
+
result._score = Math.round(score * 10000) / 10000;
|
|
440
|
+
results.push(result);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return { results, count: results.length };
|
|
444
|
+
}
|
|
445
|
+
export function getBestCombination(query) {
|
|
446
|
+
const detectedIntent = detectIntent(query);
|
|
447
|
+
const detectedDomain = detectDomain(query);
|
|
448
|
+
const detectedComplexity = detectComplexity(query);
|
|
449
|
+
// Priority intents - try intent-filtered search first
|
|
450
|
+
const priorityIntents = ['debug', 'review', 'test', 'design', 'docs', 'research', 'plan'];
|
|
451
|
+
if (priorityIntents.includes(detectedIntent)) {
|
|
452
|
+
const filtered = searchCombinationsFiltered(query, detectedIntent, undefined, 1, true);
|
|
453
|
+
if (filtered.results.length > 0) {
|
|
454
|
+
const best = filtered.results[0];
|
|
455
|
+
const skillsStr = best.skills || '';
|
|
456
|
+
const skillsList = skillsStr.split('|').map((s) => s.trim()).filter(Boolean);
|
|
457
|
+
return {
|
|
458
|
+
agent: INTENT_AGENT_MAP[detectedIntent] || best.agent || 'code-executor',
|
|
459
|
+
skills: skillsList,
|
|
460
|
+
intent: detectedIntent,
|
|
461
|
+
domain: best.domain || detectedDomain,
|
|
462
|
+
complexity: best.complexity || detectedComplexity,
|
|
463
|
+
score: best._score || 0,
|
|
464
|
+
fallback: false,
|
|
465
|
+
description: best.description || '',
|
|
466
|
+
useWhen: best.use_when || ''
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
// For execute intent with specific domain, try domain-filtered search
|
|
471
|
+
const priorityDomains = ['mobile', 'ai', 'payment', 'ecommerce', 'media', 'auth', 'database', 'fullstack'];
|
|
472
|
+
if (detectedIntent === 'execute' && priorityDomains.includes(detectedDomain)) {
|
|
473
|
+
const filtered = searchCombinationsFiltered(query, 'execute', detectedDomain, 1, true);
|
|
474
|
+
if (filtered.results.length > 0) {
|
|
475
|
+
const best = filtered.results[0];
|
|
476
|
+
const skillsStr = best.skills || '';
|
|
477
|
+
const skillsList = skillsStr.split('|').map((s) => s.trim()).filter(Boolean);
|
|
478
|
+
return {
|
|
479
|
+
agent: 'code-executor',
|
|
480
|
+
skills: skillsList,
|
|
481
|
+
intent: detectedIntent,
|
|
482
|
+
domain: detectedDomain,
|
|
483
|
+
complexity: best.complexity || detectedComplexity,
|
|
484
|
+
score: best._score || 0,
|
|
485
|
+
fallback: false,
|
|
486
|
+
description: best.description || '',
|
|
487
|
+
useWhen: best.use_when || ''
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// Default: search all combinations
|
|
492
|
+
const result = searchCombinations(query, 1, true);
|
|
493
|
+
if (result.count === 0) {
|
|
494
|
+
// Fallback: use detected intent/domain to construct a basic combination
|
|
495
|
+
const agent = INTENT_AGENT_MAP[detectedIntent] || 'code-executor';
|
|
496
|
+
const skills = DOMAIN_SKILL_MAP[detectedDomain] || ['research'];
|
|
497
|
+
return {
|
|
498
|
+
agent,
|
|
499
|
+
skills,
|
|
500
|
+
intent: detectedIntent,
|
|
501
|
+
domain: detectedDomain,
|
|
502
|
+
complexity: detectedComplexity,
|
|
503
|
+
score: 0,
|
|
504
|
+
fallback: true,
|
|
505
|
+
description: `Fallback combination based on detected intent (${detectedIntent}) and domain (${detectedDomain})`
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
const best = result.results[0];
|
|
509
|
+
const skillsStr = best.skills || '';
|
|
510
|
+
const skillsList = skillsStr.split('|').map((s) => s.trim()).filter(Boolean);
|
|
511
|
+
return {
|
|
512
|
+
agent: best.agent || 'code-executor',
|
|
513
|
+
skills: skillsList,
|
|
514
|
+
intent: best.intent || detectedIntent,
|
|
515
|
+
domain: best.domain || detectedDomain,
|
|
516
|
+
complexity: best.complexity || detectedComplexity,
|
|
517
|
+
score: best._score || 0,
|
|
518
|
+
fallback: false,
|
|
519
|
+
description: best.description || '',
|
|
520
|
+
useWhen: best.use_when || ''
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Main search function - returns multiple results
|
|
525
|
+
*/
|
|
526
|
+
export function searchAgentSkillCombination(task, options) {
|
|
527
|
+
const { top = 5, forceIntent, forceDomain, maxSkills } = options || {};
|
|
528
|
+
// If forcing intent or domain, use filtered search
|
|
529
|
+
if (forceIntent || forceDomain) {
|
|
530
|
+
const filtered = searchCombinationsFiltered(task, forceIntent, forceDomain, top, true);
|
|
531
|
+
return filtered.results.map(r => {
|
|
532
|
+
const skillsStr = r.skills || '';
|
|
533
|
+
let skillsList = skillsStr.split('|').map((s) => s.trim()).filter(Boolean);
|
|
534
|
+
if (maxSkills) {
|
|
535
|
+
skillsList = skillsList.slice(0, maxSkills);
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
agent: forceIntent ? INTENT_AGENT_MAP[forceIntent] : r.agent || 'code-executor',
|
|
539
|
+
skills: skillsList,
|
|
540
|
+
intent: r.intent || forceIntent || detectIntent(task),
|
|
541
|
+
domain: r.domain || forceDomain || detectDomain(task),
|
|
542
|
+
complexity: r.complexity || detectComplexity(task),
|
|
543
|
+
score: r._score || 0,
|
|
544
|
+
fallback: false,
|
|
545
|
+
description: r.description || '',
|
|
546
|
+
useWhen: r.use_when || ''
|
|
547
|
+
};
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
// Standard search
|
|
551
|
+
const result = searchCombinations(task, top, true);
|
|
552
|
+
if (result.count === 0) {
|
|
553
|
+
// Return fallback
|
|
554
|
+
const best = getBestCombination(task);
|
|
555
|
+
return [best];
|
|
556
|
+
}
|
|
557
|
+
return result.results.map(r => {
|
|
558
|
+
const skillsStr = r.skills || '';
|
|
559
|
+
let skillsList = skillsStr.split('|').map((s) => s.trim()).filter(Boolean);
|
|
560
|
+
if (maxSkills) {
|
|
561
|
+
skillsList = skillsList.slice(0, maxSkills);
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
agent: r.agent || 'code-executor',
|
|
565
|
+
skills: skillsList,
|
|
566
|
+
intent: r.intent || result.detectedIntent,
|
|
567
|
+
domain: r.domain || result.detectedDomain,
|
|
568
|
+
complexity: r.complexity || result.detectedComplexity,
|
|
569
|
+
score: r._score || 0,
|
|
570
|
+
fallback: false,
|
|
571
|
+
description: r.description || '',
|
|
572
|
+
useWhen: r.use_when || ''
|
|
573
|
+
};
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
// ============ SKILL UTILITIES (backward compatibility) ============
|
|
577
|
+
/**
|
|
578
|
+
* List all available skills from extensions
|
|
579
|
+
*/
|
|
580
|
+
export function listAllSkills(projectDir) {
|
|
581
|
+
const extensionsDir = getExtensionsDir(projectDir);
|
|
582
|
+
const skills = [];
|
|
583
|
+
if (!existsSync(extensionsDir)) {
|
|
584
|
+
return skills;
|
|
585
|
+
}
|
|
586
|
+
const extensions = readdirSync(extensionsDir, { withFileTypes: true })
|
|
587
|
+
.filter(d => d.isDirectory())
|
|
588
|
+
.map(d => d.name);
|
|
589
|
+
for (const ext of extensions) {
|
|
590
|
+
const skillPath = join(extensionsDir, ext, 'SKILL.md');
|
|
591
|
+
if (existsSync(skillPath)) {
|
|
592
|
+
const content = readFileSync(skillPath, 'utf-8');
|
|
593
|
+
skills.push({
|
|
594
|
+
name: ext,
|
|
595
|
+
path: skillPath,
|
|
596
|
+
content,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return skills;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Load skill content by name
|
|
604
|
+
*/
|
|
605
|
+
export function loadSkillContent(skillName, projectDir) {
|
|
606
|
+
const extensionsDir = getExtensionsDir(projectDir);
|
|
607
|
+
const skillPath = join(extensionsDir, skillName, 'SKILL.md');
|
|
608
|
+
if (!existsSync(skillPath)) {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
return readFileSync(skillPath, 'utf-8');
|
|
612
|
+
}
|
|
613
|
+
// Re-export for backward compatibility
|
|
614
|
+
export { loadSynonyms, expandQuery, INTENT_AGENT_MAP, DOMAIN_SKILL_MAP, COMPLEXITY_MAX_SKILLS };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent type definitions for MVP
|
|
3
|
+
* Aligned with Python core.py types
|
|
4
|
+
*/
|
|
5
|
+
export type Intent = 'research' | 'plan' | 'execute' | 'debug' | 'review' | 'test' | 'design' | 'docs' | 'git' | 'manage';
|
|
6
|
+
export type Domain = 'frontend' | 'backend' | 'auth' | 'payment' | 'database' | 'mobile' | 'fullstack' | 'ecommerce' | 'media' | 'ai' | 'quality' | 'codebase' | 'general';
|
|
7
|
+
export type Complexity = 'simple' | 'standard' | 'full' | 'complex';
|
|
8
|
+
export type CliProvider = 'gemini' | 'claude';
|
|
9
|
+
export interface AgentProfile {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
model: string;
|
|
13
|
+
skills?: string[];
|
|
14
|
+
tools?: string[];
|
|
15
|
+
content: string;
|
|
16
|
+
filePath: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SpawnOptions {
|
|
19
|
+
prompt: string;
|
|
20
|
+
agent?: string;
|
|
21
|
+
skills?: string;
|
|
22
|
+
context?: string;
|
|
23
|
+
model?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface SpawnResult {
|
|
26
|
+
success: boolean;
|
|
27
|
+
output?: string;
|
|
28
|
+
error?: string;
|
|
29
|
+
exitCode: number;
|
|
30
|
+
prompt?: string;
|
|
31
|
+
agentRole?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface SearchResult {
|
|
34
|
+
agent: string;
|
|
35
|
+
skills: string[];
|
|
36
|
+
intent: Intent;
|
|
37
|
+
domain: Domain;
|
|
38
|
+
complexity: Complexity;
|
|
39
|
+
score: number;
|
|
40
|
+
fallback: boolean;
|
|
41
|
+
description: string;
|
|
42
|
+
useWhen?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface SkillInfo {
|
|
45
|
+
name: string;
|
|
46
|
+
path: string;
|
|
47
|
+
content: string;
|
|
48
|
+
}
|
|
49
|
+
export interface CombinationRow {
|
|
50
|
+
id: string;
|
|
51
|
+
agent: string;
|
|
52
|
+
skills: string;
|
|
53
|
+
intent: string;
|
|
54
|
+
domain: string;
|
|
55
|
+
complexity: string;
|
|
56
|
+
keywords: string;
|
|
57
|
+
description: string;
|
|
58
|
+
use_when: string;
|
|
59
|
+
}
|
|
60
|
+
export interface IntentRow {
|
|
61
|
+
intent: string;
|
|
62
|
+
agent: string;
|
|
63
|
+
primary_keywords: string;
|
|
64
|
+
expanded_keywords: string;
|
|
65
|
+
action_verbs: string;
|
|
66
|
+
}
|
|
67
|
+
export interface DomainRow {
|
|
68
|
+
domain: string;
|
|
69
|
+
base_skills: string;
|
|
70
|
+
enhanced_skills: string;
|
|
71
|
+
keywords: string;
|
|
72
|
+
frameworks: string;
|
|
73
|
+
}
|
|
74
|
+
export interface SynonymRow {
|
|
75
|
+
word: string;
|
|
76
|
+
synonyms: string;
|
|
77
|
+
category: string;
|
|
78
|
+
}
|