ccsetup 1.1.1 → 1.2.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 +144 -342
- package/bin/create-project.js +1246 -90
- package/bin/lib/claudeInterface.js +209 -0
- package/lib/aiAgentSelector.js +155 -0
- package/lib/templates/README.md +176 -0
- package/lib/templates/catalog.js +230 -0
- package/lib/templates/filter.js +257 -0
- package/lib/templates/index.js +45 -0
- package/lib/templates/metadata/agents.json +413 -0
- package/lib/templates/metadata-extractor.js +329 -0
- package/lib/templates/search.js +356 -0
- package/package.json +13 -5
- package/template/{agents → .claude/agents}/checker.md +29 -0
- package/template/.claude/settings.json +32 -0
- package/template/.claude/skills/codex-review/SKILL.md +139 -0
- package/template/.claude/skills/prd/SKILL.md +343 -0
- package/template/.claude/skills/ralph/SKILL.md +339 -0
- package/template/.claude/skills/secops/SKILL.md +259 -0
- package/template/.codex/skills/codex-review/SKILL.md +139 -0
- package/template/.codex/skills/prd/SKILL.md +343 -0
- package/template/.codex/skills/ralph/SKILL.md +339 -0
- package/template/AGENTS.md +43 -0
- package/template/CLAUDE.md +141 -21
- package/template/CONTRIBUTING.md +37 -0
- package/template/agents/README.md +15 -171
- package/template/docs/ROADMAP.md +0 -36
- package/template/docs/agent-orchestration.md +24 -141
- package/template/docs/codex-setup.md +32 -0
- package/template/hooks/codex-review/index.js +105 -0
- package/template/hooks/workflow-selector/index.js +398 -0
- package/template/scripts/codex-review/codex-review.sh +266 -0
- package/template/scripts/ralph/CLAUDE.md +174 -0
- package/template/scripts/ralph/CODEX.md +76 -0
- package/template/scripts/ralph/ralph.sh +150 -0
- package/template/tickets/ticket-list.md +17 -68
- package/template/agents/ai-engineer.md +0 -31
- package/template/agents/api-documenter.md +0 -31
- package/template/agents/architect-review.md +0 -42
- package/template/agents/backend-architect.md +0 -29
- package/template/agents/business-analyst.md +0 -34
- package/template/agents/c-pro.md +0 -34
- package/template/agents/cloud-architect.md +0 -31
- package/template/agents/code-reviewer.md +0 -28
- package/template/agents/content-marketer.md +0 -34
- package/template/agents/context-manager.md +0 -63
- package/template/agents/cpp-pro.md +0 -37
- package/template/agents/customer-support.md +0 -34
- package/template/agents/data-engineer.md +0 -31
- package/template/agents/data-scientist.md +0 -28
- package/template/agents/database-admin.md +0 -31
- package/template/agents/database-optimizer.md +0 -31
- package/template/agents/debugger.md +0 -29
- package/template/agents/deployment-engineer.md +0 -31
- package/template/agents/devops-troubleshooter.md +0 -31
- package/template/agents/dx-optimizer.md +0 -62
- package/template/agents/error-detective.md +0 -31
- package/template/agents/frontend-developer.md +0 -30
- package/template/agents/golang-pro.md +0 -31
- package/template/agents/graphql-architect.md +0 -31
- package/template/agents/incident-responder.md +0 -73
- package/template/agents/javascript-pro.md +0 -34
- package/template/agents/legacy-modernizer.md +0 -31
- package/template/agents/ml-engineer.md +0 -31
- package/template/agents/mlops-engineer.md +0 -56
- package/template/agents/mobile-developer.md +0 -31
- package/template/agents/network-engineer.md +0 -31
- package/template/agents/payment-integration.md +0 -31
- package/template/agents/performance-engineer.md +0 -31
- package/template/agents/prompt-engineer.md +0 -58
- package/template/agents/python-pro.md +0 -31
- package/template/agents/quant-analyst.md +0 -31
- package/template/agents/risk-manager.md +0 -40
- package/template/agents/rust-pro.md +0 -34
- package/template/agents/sales-automator.md +0 -34
- package/template/agents/search-specialist.md +0 -58
- package/template/agents/security-auditor.md +0 -31
- package/template/agents/sql-pro.md +0 -34
- package/template/agents/terraform-specialist.md +0 -34
- package/template/agents/test-automator.md +0 -31
- /package/template/{agents → .claude/agents}/backend.md +0 -0
- /package/template/{agents → .claude/agents}/blockchain.md +0 -0
- /package/template/{agents → .claude/agents}/coder.md +0 -0
- /package/template/{agents → .claude/agents}/frontend.md +0 -0
- /package/template/{agents → .claude/agents}/planner.md +0 -0
- /package/template/{agents → .claude/agents}/researcher.md +0 -0
- /package/template/{agents → .claude/agents}/shadcn.md +0 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class MetadataExtractor {
|
|
5
|
+
static parseFrontmatter(content) {
|
|
6
|
+
const lines = content.split('\n');
|
|
7
|
+
|
|
8
|
+
if (lines[0] !== '---') {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const frontmatter = {};
|
|
13
|
+
let inFrontmatter = true;
|
|
14
|
+
let lineIndex = 1;
|
|
15
|
+
|
|
16
|
+
while (lineIndex < lines.length && inFrontmatter) {
|
|
17
|
+
const line = lines[lineIndex];
|
|
18
|
+
if (line === '---') {
|
|
19
|
+
inFrontmatter = false;
|
|
20
|
+
} else if (line.includes(':')) {
|
|
21
|
+
const [key, ...valueParts] = line.split(':');
|
|
22
|
+
const value = valueParts.join(':').trim();
|
|
23
|
+
frontmatter[key.trim()] = value;
|
|
24
|
+
}
|
|
25
|
+
lineIndex++;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return frontmatter;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static extractTagsFromContent(frontmatter) {
|
|
32
|
+
const tags = [];
|
|
33
|
+
|
|
34
|
+
if (frontmatter.tools) {
|
|
35
|
+
const tools = frontmatter.tools.split(',').map(t => t.trim().toLowerCase());
|
|
36
|
+
tags.push(...tools);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (frontmatter.description) {
|
|
40
|
+
const desc = frontmatter.description.toLowerCase();
|
|
41
|
+
|
|
42
|
+
if (desc.includes('planning') || desc.includes('roadmap') || desc.includes('architecture')) {
|
|
43
|
+
tags.push('planning', 'architecture');
|
|
44
|
+
}
|
|
45
|
+
if (desc.includes('implement') || desc.includes('code') || desc.includes('develop')) {
|
|
46
|
+
tags.push('development', 'implementation');
|
|
47
|
+
}
|
|
48
|
+
if (desc.includes('test') || desc.includes('qa') || desc.includes('quality')) {
|
|
49
|
+
tags.push('testing', 'qa');
|
|
50
|
+
}
|
|
51
|
+
if (desc.includes('security') || desc.includes('audit')) {
|
|
52
|
+
tags.push('security');
|
|
53
|
+
}
|
|
54
|
+
if (desc.includes('frontend') || desc.includes('ui') || desc.includes('react')) {
|
|
55
|
+
tags.push('frontend', 'ui');
|
|
56
|
+
}
|
|
57
|
+
if (desc.includes('backend') || desc.includes('api') || desc.includes('server')) {
|
|
58
|
+
tags.push('backend', 'api');
|
|
59
|
+
}
|
|
60
|
+
if (desc.includes('database') || desc.includes('sql')) {
|
|
61
|
+
tags.push('database');
|
|
62
|
+
}
|
|
63
|
+
if (desc.includes('performance') || desc.includes('optimization')) {
|
|
64
|
+
tags.push('performance');
|
|
65
|
+
}
|
|
66
|
+
if (desc.includes('mobile') || desc.includes('ios') || desc.includes('android')) {
|
|
67
|
+
tags.push('mobile');
|
|
68
|
+
}
|
|
69
|
+
if (desc.includes('devops') || desc.includes('deployment') || desc.includes('ci/cd')) {
|
|
70
|
+
tags.push('devops', 'deployment');
|
|
71
|
+
}
|
|
72
|
+
if (desc.includes('blockchain') || desc.includes('web3') || desc.includes('smart contract')) {
|
|
73
|
+
tags.push('blockchain', 'web3');
|
|
74
|
+
}
|
|
75
|
+
if (desc.includes('ml') || desc.includes('machine learning') || desc.includes('ai')) {
|
|
76
|
+
tags.push('ml', 'ai');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return [...new Set(tags)];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static categorizeAgent(name, description, tags) {
|
|
84
|
+
const nameLower = name.toLowerCase();
|
|
85
|
+
const descLower = description.toLowerCase();
|
|
86
|
+
|
|
87
|
+
if (nameLower.includes('planner') || nameLower.includes('architect') ||
|
|
88
|
+
descLower.includes('planning') || descLower.includes('architecture')) {
|
|
89
|
+
return 'planning';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (nameLower.includes('coder') || nameLower.includes('developer') ||
|
|
93
|
+
descLower.includes('implement') || descLower.includes('code')) {
|
|
94
|
+
return 'development';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (nameLower.includes('checker') || nameLower.includes('test') || nameLower.includes('qa') ||
|
|
98
|
+
descLower.includes('testing') || descLower.includes('quality') || descLower.includes('review')) {
|
|
99
|
+
return 'qa';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (nameLower.includes('frontend') || nameLower.includes('ui') ||
|
|
103
|
+
descLower.includes('frontend') || descLower.includes('react') || descLower.includes('vue')) {
|
|
104
|
+
return 'frontend';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (nameLower.includes('backend') || nameLower.includes('api') ||
|
|
108
|
+
descLower.includes('backend') || descLower.includes('server') || descLower.includes('api')) {
|
|
109
|
+
return 'backend';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (nameLower.includes('security') || nameLower.includes('audit') ||
|
|
113
|
+
descLower.includes('security') || descLower.includes('audit')) {
|
|
114
|
+
return 'security';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (nameLower.includes('devops') || nameLower.includes('deploy') ||
|
|
118
|
+
descLower.includes('devops') || descLower.includes('deployment') || descLower.includes('infrastructure')) {
|
|
119
|
+
return 'devops';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (nameLower.includes('database') || nameLower.includes('sql') ||
|
|
123
|
+
descLower.includes('database') || descLower.includes('sql')) {
|
|
124
|
+
return 'database';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (nameLower.includes('blockchain') || nameLower.includes('web3') ||
|
|
128
|
+
descLower.includes('blockchain') || descLower.includes('web3') || descLower.includes('smart contract')) {
|
|
129
|
+
return 'blockchain';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (nameLower.includes('ml') || nameLower.includes('ai') || nameLower.includes('data') ||
|
|
133
|
+
descLower.includes('machine learning') || descLower.includes('data science') || descLower.includes('ai')) {
|
|
134
|
+
return 'ai-ml';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (nameLower.includes('mobile') || descLower.includes('mobile') ||
|
|
138
|
+
descLower.includes('ios') || descLower.includes('android')) {
|
|
139
|
+
return 'mobile';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return 'general';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static formatName(name) {
|
|
146
|
+
return name.split('-').map(word =>
|
|
147
|
+
word.charAt(0).toUpperCase() + word.slice(1)
|
|
148
|
+
).join(' ') + ' Agent';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
static extractFromAgent(filePath) {
|
|
152
|
+
try {
|
|
153
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
154
|
+
const frontmatter = this.parseFrontmatter(content);
|
|
155
|
+
|
|
156
|
+
if (!frontmatter || !frontmatter.name) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const tags = this.extractTagsFromContent(frontmatter);
|
|
161
|
+
const category = this.categorizeAgent(frontmatter.name, frontmatter.description || '', tags);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
id: `agent-${frontmatter.name}`,
|
|
165
|
+
name: this.formatName(frontmatter.name),
|
|
166
|
+
category: 'agents',
|
|
167
|
+
subcategory: category,
|
|
168
|
+
description: frontmatter.description || 'No description available',
|
|
169
|
+
tags: tags,
|
|
170
|
+
tools: frontmatter.tools ? frontmatter.tools.split(',').map(t => t.trim()) : [],
|
|
171
|
+
version: '1.0.0',
|
|
172
|
+
dependencies: [],
|
|
173
|
+
files: [path.basename(filePath)],
|
|
174
|
+
author: 'ccsetup',
|
|
175
|
+
examples: this.extractExamplesFromDescription(frontmatter.description || ''),
|
|
176
|
+
workflows: this.extractWorkflowsFromContent(content)
|
|
177
|
+
};
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.warn(`Warning: Could not extract metadata from ${filePath}: ${error.message}`);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
static extractExamplesFromDescription(description) {
|
|
185
|
+
const examples = [];
|
|
186
|
+
|
|
187
|
+
if (description.includes('planning')) {
|
|
188
|
+
examples.push('Breaking down feature implementations', 'Creating technical roadmaps');
|
|
189
|
+
}
|
|
190
|
+
if (description.includes('implement') || description.includes('code')) {
|
|
191
|
+
examples.push('Feature implementation', 'Bug fixes and optimizations');
|
|
192
|
+
}
|
|
193
|
+
if (description.includes('test') || description.includes('qa')) {
|
|
194
|
+
examples.push('Test automation', 'Quality assurance reviews');
|
|
195
|
+
}
|
|
196
|
+
if (description.includes('frontend') || description.includes('ui')) {
|
|
197
|
+
examples.push('UI component development', 'Responsive design implementation');
|
|
198
|
+
}
|
|
199
|
+
if (description.includes('backend') || description.includes('api')) {
|
|
200
|
+
examples.push('API development', 'Server-side optimization');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return examples.length > 0 ? examples : ['General development tasks'];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
static extractWorkflowsFromContent(content) {
|
|
207
|
+
const workflows = [];
|
|
208
|
+
|
|
209
|
+
if (content.includes('feature') || content.includes('implement')) {
|
|
210
|
+
workflows.push('Feature Development');
|
|
211
|
+
}
|
|
212
|
+
if (content.includes('bug') || content.includes('fix')) {
|
|
213
|
+
workflows.push('Bug Fix');
|
|
214
|
+
}
|
|
215
|
+
if (content.includes('refactor') || content.includes('optimization')) {
|
|
216
|
+
workflows.push('Refactoring');
|
|
217
|
+
}
|
|
218
|
+
if (content.includes('test') || content.includes('qa')) {
|
|
219
|
+
workflows.push('QA');
|
|
220
|
+
}
|
|
221
|
+
if (content.includes('api') || content.includes('backend')) {
|
|
222
|
+
workflows.push('API Development');
|
|
223
|
+
}
|
|
224
|
+
if (content.includes('ui') || content.includes('frontend') || content.includes('component')) {
|
|
225
|
+
workflows.push('UI Component');
|
|
226
|
+
}
|
|
227
|
+
if (content.includes('blockchain') || content.includes('web3') || content.includes('smart contract')) {
|
|
228
|
+
workflows.push('Blockchain Development');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return workflows.length > 0 ? workflows : ['General'];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
static generateCatalogFromAgents(agentsDir = null) {
|
|
235
|
+
if (!agentsDir) {
|
|
236
|
+
agentsDir = path.join(__dirname, '../../template/.claude/agents');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const agents = [];
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const files = fs.readdirSync(agentsDir);
|
|
243
|
+
|
|
244
|
+
for (const file of files) {
|
|
245
|
+
if (file.endsWith('.md') && file !== 'README.md') {
|
|
246
|
+
const filePath = path.join(agentsDir, file);
|
|
247
|
+
const metadata = this.extractFromAgent(filePath);
|
|
248
|
+
|
|
249
|
+
if (metadata) {
|
|
250
|
+
agents.push(metadata);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.error('Error generating catalog from agents:', error.message);
|
|
256
|
+
return { agents: [] };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
agents: agents.sort((a, b) => a.name.localeCompare(b.name)),
|
|
261
|
+
categories: this.generateCategories(agents),
|
|
262
|
+
stats: {
|
|
263
|
+
totalAgents: agents.length,
|
|
264
|
+
categories: [...new Set(agents.map(a => a.subcategory))].length,
|
|
265
|
+
lastUpdated: new Date().toISOString()
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
static generateCategories(agents) {
|
|
271
|
+
const categories = {};
|
|
272
|
+
|
|
273
|
+
for (const agent of agents) {
|
|
274
|
+
if (!categories[agent.subcategory]) {
|
|
275
|
+
categories[agent.subcategory] = {
|
|
276
|
+
name: agent.subcategory,
|
|
277
|
+
displayName: this.formatCategoryName(agent.subcategory),
|
|
278
|
+
count: 0,
|
|
279
|
+
agents: []
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
categories[agent.subcategory].count++;
|
|
284
|
+
categories[agent.subcategory].agents.push(agent.id);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return categories;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
static formatCategoryName(category) {
|
|
291
|
+
const categoryNames = {
|
|
292
|
+
'planning': 'Planning & Architecture',
|
|
293
|
+
'development': 'Development & Implementation',
|
|
294
|
+
'qa': 'Quality Assurance & Testing',
|
|
295
|
+
'frontend': 'Frontend Development',
|
|
296
|
+
'backend': 'Backend Development',
|
|
297
|
+
'security': 'Security & Auditing',
|
|
298
|
+
'devops': 'DevOps & Infrastructure',
|
|
299
|
+
'database': 'Database & Data',
|
|
300
|
+
'blockchain': 'Blockchain & Web3',
|
|
301
|
+
'ai-ml': 'AI & Machine Learning',
|
|
302
|
+
'mobile': 'Mobile Development',
|
|
303
|
+
'general': 'General Purpose'
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
return categoryNames[category] || category.charAt(0).toUpperCase() + category.slice(1);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
static saveCatalogToFile(catalog, outputPath = null) {
|
|
310
|
+
if (!outputPath) {
|
|
311
|
+
outputPath = path.join(__dirname, 'metadata/agents.json');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const dir = path.dirname(outputPath);
|
|
316
|
+
if (!fs.existsSync(dir)) {
|
|
317
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
fs.writeFileSync(outputPath, JSON.stringify(catalog, null, 2), 'utf8');
|
|
321
|
+
return true;
|
|
322
|
+
} catch (error) {
|
|
323
|
+
console.error('Error saving catalog to file:', error.message);
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
module.exports = MetadataExtractor;
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
class TemplateSearch {
|
|
2
|
+
constructor(templates = []) {
|
|
3
|
+
this.templates = templates;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
setTemplates(templates) {
|
|
7
|
+
this.templates = templates;
|
|
8
|
+
return this;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
search(query, options = {}) {
|
|
12
|
+
if (!query || typeof query !== 'string' || query.trim() === '') {
|
|
13
|
+
return new TemplateSearch(this.templates);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
fields = ['name', 'description', 'tags', 'examples'],
|
|
18
|
+
caseSensitive = false,
|
|
19
|
+
exactMatch = false,
|
|
20
|
+
minScore = 0,
|
|
21
|
+
fuzzy = false,
|
|
22
|
+
fuzzyThreshold = 0.6
|
|
23
|
+
} = options;
|
|
24
|
+
|
|
25
|
+
const searchQuery = caseSensitive ? query.trim() : query.toLowerCase().trim();
|
|
26
|
+
const searchResults = [];
|
|
27
|
+
|
|
28
|
+
for (const template of this.templates) {
|
|
29
|
+
const score = this.calculateScore(template, searchQuery, fields, {
|
|
30
|
+
caseSensitive,
|
|
31
|
+
exactMatch,
|
|
32
|
+
fuzzy,
|
|
33
|
+
fuzzyThreshold
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (score >= minScore) {
|
|
37
|
+
searchResults.push({
|
|
38
|
+
template,
|
|
39
|
+
score,
|
|
40
|
+
matches: this.getMatches(template, searchQuery, fields, caseSensitive)
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const sortedResults = searchResults
|
|
46
|
+
.sort((a, b) => b.score - a.score)
|
|
47
|
+
.map(result => ({
|
|
48
|
+
...result.template,
|
|
49
|
+
_searchScore: result.score,
|
|
50
|
+
_searchMatches: result.matches
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
return new TemplateSearch(sortedResults);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
calculateScore(template, query, fields, options) {
|
|
57
|
+
let totalScore = 0;
|
|
58
|
+
let maxPossibleScore = 0;
|
|
59
|
+
|
|
60
|
+
for (const field of fields) {
|
|
61
|
+
const fieldValue = this.getFieldValue(template, field);
|
|
62
|
+
if (!fieldValue) continue;
|
|
63
|
+
|
|
64
|
+
const fieldWeight = this.getFieldWeight(field);
|
|
65
|
+
maxPossibleScore += fieldWeight;
|
|
66
|
+
|
|
67
|
+
const fieldScore = this.scoreField(fieldValue, query, options);
|
|
68
|
+
totalScore += fieldScore * fieldWeight;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return maxPossibleScore > 0 ? totalScore / maxPossibleScore : 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getFieldValue(template, field) {
|
|
75
|
+
if (field === 'tags' && Array.isArray(template.tags)) {
|
|
76
|
+
return template.tags.join(' ');
|
|
77
|
+
}
|
|
78
|
+
if (field === 'examples' && Array.isArray(template.examples)) {
|
|
79
|
+
return template.examples.join(' ');
|
|
80
|
+
}
|
|
81
|
+
return template[field] || '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getFieldWeight(field) {
|
|
85
|
+
const weights = {
|
|
86
|
+
name: 3,
|
|
87
|
+
description: 2,
|
|
88
|
+
tags: 1.5,
|
|
89
|
+
examples: 1,
|
|
90
|
+
subcategory: 1,
|
|
91
|
+
category: 0.5
|
|
92
|
+
};
|
|
93
|
+
return weights[field] || 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
scoreField(fieldValue, query, options) {
|
|
97
|
+
if (!fieldValue || typeof fieldValue !== 'string') {
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const text = options.caseSensitive ? fieldValue : fieldValue.toLowerCase();
|
|
102
|
+
|
|
103
|
+
if (options.exactMatch) {
|
|
104
|
+
return text === query ? 1 : 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (options.fuzzy) {
|
|
108
|
+
return this.fuzzyMatch(text, query, options.fuzzyThreshold);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (text.includes(query)) {
|
|
112
|
+
if (text === query) return 1;
|
|
113
|
+
if (text.startsWith(query)) return 0.9;
|
|
114
|
+
if (text.endsWith(query)) return 0.8;
|
|
115
|
+
|
|
116
|
+
const queryLength = query.length;
|
|
117
|
+
const textLength = text.length;
|
|
118
|
+
return 0.5 + (queryLength / textLength) * 0.3;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const words = query.split(/\s+/);
|
|
122
|
+
const matchedWords = words.filter(word => text.includes(word));
|
|
123
|
+
|
|
124
|
+
if (matchedWords.length > 0) {
|
|
125
|
+
return (matchedWords.length / words.length) * 0.4;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
fuzzyMatch(text, query, threshold) {
|
|
132
|
+
const distance = this.levenshteinDistance(text, query);
|
|
133
|
+
const maxLength = Math.max(text.length, query.length);
|
|
134
|
+
const similarity = 1 - (distance / maxLength);
|
|
135
|
+
|
|
136
|
+
return similarity >= threshold ? similarity : 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
levenshteinDistance(str1, str2) {
|
|
140
|
+
const matrix = [];
|
|
141
|
+
|
|
142
|
+
for (let i = 0; i <= str2.length; i++) {
|
|
143
|
+
matrix[i] = [i];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (let j = 0; j <= str1.length; j++) {
|
|
147
|
+
matrix[0][j] = j;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (let i = 1; i <= str2.length; i++) {
|
|
151
|
+
for (let j = 1; j <= str1.length; j++) {
|
|
152
|
+
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
|
|
153
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
154
|
+
} else {
|
|
155
|
+
matrix[i][j] = Math.min(
|
|
156
|
+
matrix[i - 1][j - 1] + 1,
|
|
157
|
+
matrix[i][j - 1] + 1,
|
|
158
|
+
matrix[i - 1][j] + 1
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return matrix[str2.length][str1.length];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
getMatches(template, query, fields, caseSensitive) {
|
|
168
|
+
const matches = [];
|
|
169
|
+
const searchQuery = caseSensitive ? query : query.toLowerCase();
|
|
170
|
+
|
|
171
|
+
for (const field of fields) {
|
|
172
|
+
const fieldValue = this.getFieldValue(template, field);
|
|
173
|
+
if (!fieldValue) continue;
|
|
174
|
+
|
|
175
|
+
const text = caseSensitive ? fieldValue : fieldValue.toLowerCase();
|
|
176
|
+
|
|
177
|
+
if (text.includes(searchQuery)) {
|
|
178
|
+
const startIndex = text.indexOf(searchQuery);
|
|
179
|
+
matches.push({
|
|
180
|
+
field,
|
|
181
|
+
value: fieldValue,
|
|
182
|
+
matchStart: startIndex,
|
|
183
|
+
matchEnd: startIndex + searchQuery.length,
|
|
184
|
+
matchText: fieldValue.substring(startIndex, startIndex + searchQuery.length)
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return matches;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
searchByName(query, options = {}) {
|
|
193
|
+
return this.search(query, { ...options, fields: ['name'] });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
searchByDescription(query, options = {}) {
|
|
197
|
+
return this.search(query, { ...options, fields: ['description'] });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
searchByTags(query, options = {}) {
|
|
201
|
+
return this.search(query, { ...options, fields: ['tags'] });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
searchByMultipleTerms(terms, options = {}) {
|
|
205
|
+
if (!Array.isArray(terms) || terms.length === 0) {
|
|
206
|
+
return new TemplateSearch(this.templates);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const {
|
|
210
|
+
matchMode = 'any',
|
|
211
|
+
...searchOptions
|
|
212
|
+
} = options;
|
|
213
|
+
|
|
214
|
+
if (matchMode === 'all') {
|
|
215
|
+
let results = this.templates;
|
|
216
|
+
for (const term of terms) {
|
|
217
|
+
results = new TemplateSearch(results).search(term, searchOptions).getResults();
|
|
218
|
+
}
|
|
219
|
+
return new TemplateSearch(results);
|
|
220
|
+
} else {
|
|
221
|
+
const allResults = new Map();
|
|
222
|
+
|
|
223
|
+
for (const term of terms) {
|
|
224
|
+
const termResults = this.search(term, searchOptions).getResults();
|
|
225
|
+
for (const result of termResults) {
|
|
226
|
+
const existingResult = allResults.get(result.id);
|
|
227
|
+
if (existingResult) {
|
|
228
|
+
existingResult._searchScore = Math.max(
|
|
229
|
+
existingResult._searchScore || 0,
|
|
230
|
+
result._searchScore || 0
|
|
231
|
+
);
|
|
232
|
+
} else {
|
|
233
|
+
allResults.set(result.id, result);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const combinedResults = Array.from(allResults.values())
|
|
239
|
+
.sort((a, b) => (b._searchScore || 0) - (a._searchScore || 0));
|
|
240
|
+
|
|
241
|
+
return new TemplateSearch(combinedResults);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
highlight(text, query, options = {}) {
|
|
246
|
+
if (!text || !query) return text;
|
|
247
|
+
|
|
248
|
+
const {
|
|
249
|
+
caseSensitive = false,
|
|
250
|
+
highlightTag = 'mark',
|
|
251
|
+
className = 'search-highlight'
|
|
252
|
+
} = options;
|
|
253
|
+
|
|
254
|
+
const searchText = caseSensitive ? text : text.toLowerCase();
|
|
255
|
+
const searchQuery = caseSensitive ? query : query.toLowerCase();
|
|
256
|
+
|
|
257
|
+
if (!searchText.includes(searchQuery)) {
|
|
258
|
+
return text;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const startTag = className
|
|
262
|
+
? `<${highlightTag} class="${className}">`
|
|
263
|
+
: `<${highlightTag}>`;
|
|
264
|
+
const endTag = `</${highlightTag}>`;
|
|
265
|
+
|
|
266
|
+
const regex = new RegExp(
|
|
267
|
+
this.escapeRegExp(query),
|
|
268
|
+
caseSensitive ? 'g' : 'gi'
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
return text.replace(regex, `${startTag}$&${endTag}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
escapeRegExp(string) {
|
|
275
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
getSuggestions(query, options = {}) {
|
|
279
|
+
const {
|
|
280
|
+
maxSuggestions = 5,
|
|
281
|
+
minLength = 2,
|
|
282
|
+
includePartialMatches = true
|
|
283
|
+
} = options;
|
|
284
|
+
|
|
285
|
+
if (!query || query.length < minLength) {
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const suggestions = new Set();
|
|
290
|
+
const queryLower = query.toLowerCase();
|
|
291
|
+
|
|
292
|
+
for (const template of this.templates) {
|
|
293
|
+
if (template.name && template.name.toLowerCase().startsWith(queryLower)) {
|
|
294
|
+
suggestions.add(template.name);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (template.tags && Array.isArray(template.tags)) {
|
|
298
|
+
for (const tag of template.tags) {
|
|
299
|
+
if (tag.toLowerCase().startsWith(queryLower)) {
|
|
300
|
+
suggestions.add(tag);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (includePartialMatches) {
|
|
306
|
+
if (template.description && template.description.toLowerCase().includes(queryLower)) {
|
|
307
|
+
const words = template.description.split(/\s+/);
|
|
308
|
+
for (const word of words) {
|
|
309
|
+
if (word.toLowerCase().startsWith(queryLower) && word.length >= minLength) {
|
|
310
|
+
suggestions.add(word);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (suggestions.size >= maxSuggestions) {
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return Array.from(suggestions).slice(0, maxSuggestions);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
getResults() {
|
|
325
|
+
return [...this.templates];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
getCount() {
|
|
329
|
+
return this.templates.length;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
first() {
|
|
333
|
+
return this.templates.length > 0 ? this.templates[0] : null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
isEmpty() {
|
|
337
|
+
return this.templates.length === 0;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
sortByRelevance() {
|
|
341
|
+
const sorted = [...this.templates].sort((a, b) => {
|
|
342
|
+
const scoreA = a._searchScore || 0;
|
|
343
|
+
const scoreB = b._searchScore || 0;
|
|
344
|
+
return scoreB - scoreA;
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
return new TemplateSearch(sorted);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
static createFromCatalog(catalog) {
|
|
351
|
+
const templates = catalog?.agents || [];
|
|
352
|
+
return new TemplateSearch(templates);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
module.exports = TemplateSearch;
|