@fermindi/pwn-cli 0.1.0 → 0.2.0
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/LICENSE +21 -21
- package/README.md +265 -251
- package/cli/batch.js +333 -333
- package/cli/codespaces.js +303 -303
- package/cli/index.js +98 -91
- package/cli/inject.js +78 -53
- package/cli/knowledge.js +531 -531
- package/cli/migrate.js +466 -0
- package/cli/notify.js +135 -135
- package/cli/patterns.js +665 -665
- package/cli/status.js +91 -91
- package/cli/validate.js +61 -61
- package/package.json +70 -70
- package/src/core/inject.js +208 -128
- package/src/core/state.js +91 -91
- package/src/core/validate.js +202 -202
- package/src/core/workspace.js +176 -176
- package/src/index.js +20 -20
- package/src/knowledge/gc.js +308 -308
- package/src/knowledge/lifecycle.js +401 -401
- package/src/knowledge/promote.js +364 -364
- package/src/knowledge/references.js +342 -342
- package/src/patterns/matcher.js +218 -218
- package/src/patterns/registry.js +375 -375
- package/src/patterns/triggers.js +423 -423
- package/src/services/batch-service.js +849 -849
- package/src/services/notification-service.js +342 -342
- package/templates/codespaces/devcontainer.json +52 -52
- package/templates/codespaces/setup.sh +70 -70
- package/templates/workspace/.ai/README.md +164 -164
- package/templates/workspace/.ai/agents/README.md +204 -204
- package/templates/workspace/.ai/agents/claude.md +625 -625
- package/templates/workspace/.ai/config/README.md +79 -79
- package/templates/workspace/.ai/config/notifications.template.json +20 -20
- package/templates/workspace/.ai/memory/deadends.md +79 -79
- package/templates/workspace/.ai/memory/decisions.md +58 -58
- package/templates/workspace/.ai/memory/patterns.md +65 -65
- package/templates/workspace/.ai/patterns/backend/README.md +126 -126
- package/templates/workspace/.ai/patterns/frontend/README.md +103 -103
- package/templates/workspace/.ai/patterns/index.md +256 -256
- package/templates/workspace/.ai/patterns/triggers.json +1087 -1087
- package/templates/workspace/.ai/patterns/universal/README.md +141 -141
- package/templates/workspace/.ai/state.template.json +8 -8
- package/templates/workspace/.ai/tasks/active.md +77 -77
- package/templates/workspace/.ai/tasks/backlog.md +95 -95
- package/templates/workspace/.ai/workflows/batch-task.md +356 -356
package/cli/patterns.js
CHANGED
|
@@ -1,665 +1,665 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PWN CLI - Patterns Command
|
|
3
|
-
*
|
|
4
|
-
* Commands:
|
|
5
|
-
* pwn patterns List all patterns
|
|
6
|
-
* pwn patterns list List all patterns
|
|
7
|
-
* pwn patterns list --category List patterns by category
|
|
8
|
-
* pwn patterns show <id> Show pattern details
|
|
9
|
-
* pwn patterns add <id> Add new pattern
|
|
10
|
-
* pwn patterns remove <id> Remove pattern
|
|
11
|
-
* pwn patterns search <query> Search patterns
|
|
12
|
-
* pwn patterns triggers List all triggers
|
|
13
|
-
* pwn patterns triggers add Add new trigger
|
|
14
|
-
* pwn patterns triggers remove Remove trigger
|
|
15
|
-
* pwn patterns eval <file> Evaluate triggers for a file
|
|
16
|
-
* pwn patterns stats Show usage statistics
|
|
17
|
-
* pwn patterns sync Sync registry with filesystem
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { existsSync, readFileSync } from 'fs';
|
|
21
|
-
import { join, resolve, relative } from 'path';
|
|
22
|
-
import { PatternRegistry, createRegistry } from '../src/patterns/registry.js';
|
|
23
|
-
import { TriggerEngine, createTriggerEngine } from '../src/patterns/triggers.js';
|
|
24
|
-
|
|
25
|
-
export default async function patterns(args) {
|
|
26
|
-
const subcommand = args[0] || 'list';
|
|
27
|
-
const subArgs = args.slice(1);
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
switch (subcommand) {
|
|
31
|
-
case 'list':
|
|
32
|
-
await listPatterns(subArgs);
|
|
33
|
-
break;
|
|
34
|
-
|
|
35
|
-
case 'show':
|
|
36
|
-
await showPattern(subArgs);
|
|
37
|
-
break;
|
|
38
|
-
|
|
39
|
-
case 'add':
|
|
40
|
-
await addPattern(subArgs);
|
|
41
|
-
break;
|
|
42
|
-
|
|
43
|
-
case 'remove':
|
|
44
|
-
await removePattern(subArgs);
|
|
45
|
-
break;
|
|
46
|
-
|
|
47
|
-
case 'search':
|
|
48
|
-
await searchPatterns(subArgs);
|
|
49
|
-
break;
|
|
50
|
-
|
|
51
|
-
case 'triggers':
|
|
52
|
-
await handleTriggers(subArgs);
|
|
53
|
-
break;
|
|
54
|
-
|
|
55
|
-
case 'eval':
|
|
56
|
-
await evaluateFile(subArgs);
|
|
57
|
-
break;
|
|
58
|
-
|
|
59
|
-
case 'stats':
|
|
60
|
-
await showStats();
|
|
61
|
-
break;
|
|
62
|
-
|
|
63
|
-
case 'sync':
|
|
64
|
-
await syncRegistry();
|
|
65
|
-
break;
|
|
66
|
-
|
|
67
|
-
case 'config':
|
|
68
|
-
await showConfig();
|
|
69
|
-
break;
|
|
70
|
-
|
|
71
|
-
case '--help':
|
|
72
|
-
case '-h':
|
|
73
|
-
showHelp();
|
|
74
|
-
break;
|
|
75
|
-
|
|
76
|
-
default:
|
|
77
|
-
// If it looks like a pattern ID, show it
|
|
78
|
-
if (subcommand.includes('/')) {
|
|
79
|
-
await showPattern([subcommand, ...subArgs]);
|
|
80
|
-
} else {
|
|
81
|
-
console.log(`Unknown subcommand: ${subcommand}`);
|
|
82
|
-
console.log('Run: pwn patterns --help');
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
} catch (error) {
|
|
86
|
-
console.error(`Error: ${error.message}`);
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* List all patterns
|
|
93
|
-
*/
|
|
94
|
-
async function listPatterns(args) {
|
|
95
|
-
const registry = createRegistry(process.cwd());
|
|
96
|
-
await registry.load();
|
|
97
|
-
|
|
98
|
-
const patterns = registry.getAll();
|
|
99
|
-
|
|
100
|
-
if (patterns.length === 0) {
|
|
101
|
-
console.log('No patterns found.');
|
|
102
|
-
console.log('Run: pwn patterns sync to scan patterns directory');
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Parse options
|
|
107
|
-
const categoryFilter = args.find(a => a.startsWith('--category='))?.split('=')[1];
|
|
108
|
-
const showAll = args.includes('--all') || args.includes('-a');
|
|
109
|
-
|
|
110
|
-
let filtered = patterns;
|
|
111
|
-
if (categoryFilter) {
|
|
112
|
-
filtered = registry.getByCategory(categoryFilter);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Group by category
|
|
116
|
-
const byCategory = {};
|
|
117
|
-
for (const pattern of filtered) {
|
|
118
|
-
if (!byCategory[pattern.category]) {
|
|
119
|
-
byCategory[pattern.category] = [];
|
|
120
|
-
}
|
|
121
|
-
byCategory[pattern.category].push(pattern);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
console.log('\nPatterns Registry\n');
|
|
125
|
-
|
|
126
|
-
for (const [category, categoryPatterns] of Object.entries(byCategory)) {
|
|
127
|
-
console.log(`${getCategoryIcon(category)} ${category.toUpperCase()}`);
|
|
128
|
-
|
|
129
|
-
for (const pattern of categoryPatterns) {
|
|
130
|
-
const usage = pattern.metadata.usageCount > 0
|
|
131
|
-
? ` (used ${pattern.metadata.usageCount}x)`
|
|
132
|
-
: '';
|
|
133
|
-
console.log(` ${pattern.id}${usage}`);
|
|
134
|
-
|
|
135
|
-
if (showAll) {
|
|
136
|
-
console.log(` ${pattern.description}`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
console.log();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
console.log(`Total: ${patterns.length} patterns`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Show pattern details
|
|
147
|
-
*/
|
|
148
|
-
async function showPattern(args) {
|
|
149
|
-
const patternId = args[0];
|
|
150
|
-
|
|
151
|
-
if (!patternId) {
|
|
152
|
-
console.log('Usage: pwn patterns show <pattern-id>');
|
|
153
|
-
console.log('Example: pwn patterns show frontend/react');
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const registry = createRegistry(process.cwd());
|
|
158
|
-
await registry.load();
|
|
159
|
-
|
|
160
|
-
const pattern = registry.get(patternId);
|
|
161
|
-
|
|
162
|
-
if (!pattern) {
|
|
163
|
-
console.log(`Pattern not found: ${patternId}`);
|
|
164
|
-
console.log('\nAvailable patterns:');
|
|
165
|
-
registry.getAll().slice(0, 10).forEach(p => console.log(` ${p.id}`));
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
console.log(`\n${pattern.name}`);
|
|
170
|
-
console.log('='.repeat(pattern.name.length));
|
|
171
|
-
console.log(`\nID: ${pattern.id}`);
|
|
172
|
-
console.log(`Category: ${pattern.category}`);
|
|
173
|
-
console.log(`Path: ${pattern.path}`);
|
|
174
|
-
console.log(`Description: ${pattern.description}`);
|
|
175
|
-
console.log(`\nUsage: ${pattern.metadata.usageCount} times`);
|
|
176
|
-
|
|
177
|
-
if (pattern.metadata.lastUsed) {
|
|
178
|
-
console.log(`Last used: ${new Date(pattern.metadata.lastUsed).toLocaleString()}`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (pattern.triggers.length > 0) {
|
|
182
|
-
console.log(`\nTriggers: ${pattern.triggers.join(', ')}`);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Show content preview
|
|
186
|
-
const content = registry.getContent(patternId);
|
|
187
|
-
if (content) {
|
|
188
|
-
console.log('\n--- Content Preview ---');
|
|
189
|
-
const lines = content.split('\n').slice(0, 20);
|
|
190
|
-
console.log(lines.join('\n'));
|
|
191
|
-
if (content.split('\n').length > 20) {
|
|
192
|
-
console.log('...');
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Add new pattern
|
|
199
|
-
*/
|
|
200
|
-
async function addPattern(args) {
|
|
201
|
-
const patternId = args[0];
|
|
202
|
-
|
|
203
|
-
if (!patternId || !patternId.includes('/')) {
|
|
204
|
-
console.log('Usage: pwn patterns add <category>/<name>');
|
|
205
|
-
console.log('Example: pwn patterns add frontend/nextjs');
|
|
206
|
-
console.log('\nCategories: frontend, backend, universal');
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const [category, name] = patternId.split('/');
|
|
211
|
-
|
|
212
|
-
if (!['frontend', 'backend', 'universal'].includes(category)) {
|
|
213
|
-
console.log(`Invalid category: ${category}`);
|
|
214
|
-
console.log('Valid categories: frontend, backend, universal');
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const description = args.slice(1).join(' ') || `${name} patterns and best practices`;
|
|
219
|
-
|
|
220
|
-
const registry = createRegistry(process.cwd());
|
|
221
|
-
await registry.load();
|
|
222
|
-
|
|
223
|
-
// Check if already exists
|
|
224
|
-
if (registry.get(patternId)) {
|
|
225
|
-
console.log(`Pattern already exists: ${patternId}`);
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
await registry.register({
|
|
230
|
-
id: patternId,
|
|
231
|
-
name: name.charAt(0).toUpperCase() + name.slice(1).replace(/-/g, ' '),
|
|
232
|
-
category,
|
|
233
|
-
path: `patterns/${category}/${name}`,
|
|
234
|
-
description
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
console.log(`Created pattern: ${patternId}`);
|
|
238
|
-
console.log(`Directory: .ai/patterns/${category}/${name}/`);
|
|
239
|
-
console.log('\nNext steps:');
|
|
240
|
-
console.log(` 1. Edit .ai/patterns/${category}/${name}/README.md`);
|
|
241
|
-
console.log(' 2. Add trigger: pwn patterns triggers add');
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Remove pattern
|
|
246
|
-
*/
|
|
247
|
-
async function removePattern(args) {
|
|
248
|
-
const patternId = args[0];
|
|
249
|
-
|
|
250
|
-
if (!patternId) {
|
|
251
|
-
console.log('Usage: pwn patterns remove <pattern-id>');
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const registry = createRegistry(process.cwd());
|
|
256
|
-
await registry.load();
|
|
257
|
-
|
|
258
|
-
const pattern = registry.get(patternId);
|
|
259
|
-
if (!pattern) {
|
|
260
|
-
console.log(`Pattern not found: ${patternId}`);
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
await registry.remove(patternId);
|
|
265
|
-
console.log(`Removed pattern from registry: ${patternId}`);
|
|
266
|
-
console.log('Note: Pattern directory was not deleted');
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Search patterns
|
|
271
|
-
*/
|
|
272
|
-
async function searchPatterns(args) {
|
|
273
|
-
const query = args.join(' ');
|
|
274
|
-
|
|
275
|
-
if (!query) {
|
|
276
|
-
console.log('Usage: pwn patterns search <query>');
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const registry = createRegistry(process.cwd());
|
|
281
|
-
await registry.load();
|
|
282
|
-
|
|
283
|
-
const results = registry.search(query);
|
|
284
|
-
|
|
285
|
-
if (results.length === 0) {
|
|
286
|
-
console.log(`No patterns found matching: ${query}`);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
console.log(`\nFound ${results.length} pattern(s) matching "${query}":\n`);
|
|
291
|
-
|
|
292
|
-
for (const pattern of results) {
|
|
293
|
-
console.log(` ${pattern.id}`);
|
|
294
|
-
console.log(` ${pattern.description}\n`);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Handle trigger subcommands
|
|
300
|
-
*/
|
|
301
|
-
async function handleTriggers(args) {
|
|
302
|
-
const action = args[0] || 'list';
|
|
303
|
-
const actionArgs = args.slice(1);
|
|
304
|
-
|
|
305
|
-
const engine = createTriggerEngine(process.cwd());
|
|
306
|
-
await engine.load();
|
|
307
|
-
|
|
308
|
-
switch (action) {
|
|
309
|
-
case 'list':
|
|
310
|
-
await listTriggers(engine);
|
|
311
|
-
break;
|
|
312
|
-
|
|
313
|
-
case 'add':
|
|
314
|
-
await addTrigger(engine, actionArgs);
|
|
315
|
-
break;
|
|
316
|
-
|
|
317
|
-
case 'remove':
|
|
318
|
-
await removeTrigger(engine, actionArgs);
|
|
319
|
-
break;
|
|
320
|
-
|
|
321
|
-
case 'enable':
|
|
322
|
-
await setTriggerEnabled(engine, actionArgs[0], true);
|
|
323
|
-
break;
|
|
324
|
-
|
|
325
|
-
case 'disable':
|
|
326
|
-
await setTriggerEnabled(engine, actionArgs[0], false);
|
|
327
|
-
break;
|
|
328
|
-
|
|
329
|
-
default:
|
|
330
|
-
console.log('Trigger commands:');
|
|
331
|
-
console.log(' pwn patterns triggers list');
|
|
332
|
-
console.log(' pwn patterns triggers add');
|
|
333
|
-
console.log(' pwn patterns triggers remove <id>');
|
|
334
|
-
console.log(' pwn patterns triggers enable <id>');
|
|
335
|
-
console.log(' pwn patterns triggers disable <id>');
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* List triggers
|
|
341
|
-
*/
|
|
342
|
-
async function listTriggers(engine) {
|
|
343
|
-
const triggers = engine.getAll();
|
|
344
|
-
const summary = engine.getSummary();
|
|
345
|
-
|
|
346
|
-
if (triggers.length === 0) {
|
|
347
|
-
console.log('No triggers configured.');
|
|
348
|
-
console.log('Run: pwn patterns triggers add');
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
console.log('\nTrigger Registry\n');
|
|
353
|
-
|
|
354
|
-
const byType = {};
|
|
355
|
-
for (const trigger of triggers) {
|
|
356
|
-
if (!byType[trigger.type]) {
|
|
357
|
-
byType[trigger.type] = [];
|
|
358
|
-
}
|
|
359
|
-
byType[trigger.type].push(trigger);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
for (const [type, typeTriggers] of Object.entries(byType)) {
|
|
363
|
-
console.log(`${getTriggerTypeIcon(type)} ${type.toUpperCase()}`);
|
|
364
|
-
|
|
365
|
-
for (const trigger of typeTriggers) {
|
|
366
|
-
const status = trigger.enabled ? '' : ' [disabled]';
|
|
367
|
-
const value = Array.isArray(trigger.value)
|
|
368
|
-
? trigger.value.join(', ')
|
|
369
|
-
: trigger.value;
|
|
370
|
-
console.log(` ${trigger.name}${status}`);
|
|
371
|
-
console.log(` Match: ${value}`);
|
|
372
|
-
console.log(` Patterns: ${trigger.patterns.join(', ')}`);
|
|
373
|
-
}
|
|
374
|
-
console.log();
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
console.log(`Total: ${summary.totalTriggers} triggers (${summary.disabledCount} disabled)`);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Add trigger interactively
|
|
382
|
-
*/
|
|
383
|
-
async function addTrigger(engine, args) {
|
|
384
|
-
// Parse args for quick add
|
|
385
|
-
const type = args.find(a => ['fileExt', 'path', 'import', 'keyword', 'command'].includes(a));
|
|
386
|
-
const valueIndex = args.indexOf('--value');
|
|
387
|
-
const value = valueIndex !== -1 ? args[valueIndex + 1] : null;
|
|
388
|
-
const patternIndex = args.indexOf('--patterns');
|
|
389
|
-
const patterns = patternIndex !== -1 ? args[patternIndex + 1]?.split(',') : null;
|
|
390
|
-
const name = args.find(a => !a.startsWith('--') && a !== type);
|
|
391
|
-
|
|
392
|
-
if (type && value && patterns) {
|
|
393
|
-
// Quick add mode
|
|
394
|
-
await engine.register({
|
|
395
|
-
id: `${type}-${Date.now()}`,
|
|
396
|
-
name: name || `${type} trigger`,
|
|
397
|
-
type,
|
|
398
|
-
value,
|
|
399
|
-
patterns,
|
|
400
|
-
description: `Auto-generated trigger for ${value}`
|
|
401
|
-
});
|
|
402
|
-
console.log(`Added trigger: ${type} matching ${value}`);
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
console.log('Add trigger (interactive mode not available in CLI)');
|
|
407
|
-
console.log('\nUsage:');
|
|
408
|
-
console.log(' pwn patterns triggers add <type> --value <pattern> --patterns <p1,p2>');
|
|
409
|
-
console.log('\nExample:');
|
|
410
|
-
console.log(' pwn patterns triggers add fileExt --value "*.vue" --patterns frontend/vue');
|
|
411
|
-
console.log('\nTypes: fileExt, path, import, keyword, command');
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* Remove trigger
|
|
416
|
-
*/
|
|
417
|
-
async function removeTrigger(engine, args) {
|
|
418
|
-
const triggerId = args[0];
|
|
419
|
-
|
|
420
|
-
if (!triggerId) {
|
|
421
|
-
console.log('Usage: pwn patterns triggers remove <trigger-id>');
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
await engine.remove(triggerId);
|
|
426
|
-
console.log(`Removed trigger: ${triggerId}`);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Enable/disable trigger
|
|
431
|
-
*/
|
|
432
|
-
async function setTriggerEnabled(engine, triggerId, enabled) {
|
|
433
|
-
if (!triggerId) {
|
|
434
|
-
console.log(`Usage: pwn patterns triggers ${enabled ? 'enable' : 'disable'} <trigger-id>`);
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
await engine.setEnabled(triggerId, enabled);
|
|
439
|
-
console.log(`Trigger ${triggerId} ${enabled ? 'enabled' : 'disabled'}`);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* Evaluate triggers for a file
|
|
444
|
-
*/
|
|
445
|
-
async function evaluateFile(args) {
|
|
446
|
-
const filePath = args[0];
|
|
447
|
-
|
|
448
|
-
if (!filePath) {
|
|
449
|
-
console.log('Usage: pwn patterns eval <file-path>');
|
|
450
|
-
console.log('Example: pwn patterns eval src/components/Button.tsx');
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const fullPath = resolve(filePath);
|
|
455
|
-
|
|
456
|
-
if (!existsSync(fullPath)) {
|
|
457
|
-
console.log(`File not found: ${filePath}`);
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const engine = createTriggerEngine(process.cwd());
|
|
462
|
-
await engine.load();
|
|
463
|
-
|
|
464
|
-
// Read file content for content-based triggers
|
|
465
|
-
const content = readFileSync(fullPath, 'utf8');
|
|
466
|
-
|
|
467
|
-
// Evaluate triggers
|
|
468
|
-
const result = engine.evaluateContent(content, fullPath);
|
|
469
|
-
|
|
470
|
-
console.log(`\nEvaluating: ${relative(process.cwd(), fullPath)}\n`);
|
|
471
|
-
|
|
472
|
-
if (result.matchedTriggers.length === 0) {
|
|
473
|
-
console.log('No triggers matched.');
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
console.log('Matched Triggers:');
|
|
478
|
-
for (const match of result.matchedTriggers) {
|
|
479
|
-
console.log(` ${match.trigger.name}`);
|
|
480
|
-
console.log(` Type: ${match.trigger.type}`);
|
|
481
|
-
console.log(` Reason: ${match.reason}`);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
console.log('\nPatterns to Load:');
|
|
485
|
-
for (const patternId of result.patterns) {
|
|
486
|
-
console.log(` ${patternId}`);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Load pattern content
|
|
490
|
-
const registry = createRegistry(process.cwd());
|
|
491
|
-
await registry.load();
|
|
492
|
-
|
|
493
|
-
console.log('\n--- Pattern Content Preview ---');
|
|
494
|
-
for (const patternId of result.patterns.slice(0, 2)) {
|
|
495
|
-
const content = registry.getContent(patternId);
|
|
496
|
-
if (content) {
|
|
497
|
-
console.log(`\n[${patternId}]`);
|
|
498
|
-
const preview = content.split('\n').slice(0, 10).join('\n');
|
|
499
|
-
console.log(preview);
|
|
500
|
-
console.log('...');
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* Show usage statistics
|
|
507
|
-
*/
|
|
508
|
-
async function showStats() {
|
|
509
|
-
const registry = createRegistry(process.cwd());
|
|
510
|
-
await registry.load();
|
|
511
|
-
|
|
512
|
-
const engine = createTriggerEngine(process.cwd());
|
|
513
|
-
await engine.load();
|
|
514
|
-
|
|
515
|
-
const patternStats = registry.getStats();
|
|
516
|
-
const triggerStats = engine.getSummary();
|
|
517
|
-
|
|
518
|
-
console.log('\nPattern System Statistics\n');
|
|
519
|
-
|
|
520
|
-
console.log('Patterns:');
|
|
521
|
-
console.log(` Total: ${patternStats.totalPatterns}`);
|
|
522
|
-
for (const [category, count] of Object.entries(patternStats.byCategory)) {
|
|
523
|
-
console.log(` ${category}: ${count}`);
|
|
524
|
-
}
|
|
525
|
-
console.log(` Total usage: ${patternStats.totalUsage} times`);
|
|
526
|
-
if (patternStats.mostUsed) {
|
|
527
|
-
console.log(` Most used: ${patternStats.mostUsed.id} (${patternStats.mostUsed.count}x)`);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
console.log('\nTriggers:');
|
|
531
|
-
console.log(` Total: ${triggerStats.totalTriggers}`);
|
|
532
|
-
for (const [type, count] of Object.entries(triggerStats.byType)) {
|
|
533
|
-
console.log(` ${type}: ${count}`);
|
|
534
|
-
}
|
|
535
|
-
console.log(` Disabled: ${triggerStats.disabledCount}`);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
/**
|
|
539
|
-
* Sync registry with filesystem
|
|
540
|
-
*/
|
|
541
|
-
async function syncRegistry() {
|
|
542
|
-
console.log('Scanning patterns directory...\n');
|
|
543
|
-
|
|
544
|
-
const registry = createRegistry(process.cwd());
|
|
545
|
-
await registry.scan();
|
|
546
|
-
|
|
547
|
-
const patterns = registry.getAll();
|
|
548
|
-
|
|
549
|
-
console.log(`Found ${patterns.length} patterns:`);
|
|
550
|
-
for (const pattern of patterns) {
|
|
551
|
-
console.log(` ${pattern.id}`);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
console.log('\nRegistry synced successfully.');
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
/**
|
|
558
|
-
* Show configuration
|
|
559
|
-
*/
|
|
560
|
-
async function showConfig() {
|
|
561
|
-
const cwd = process.cwd();
|
|
562
|
-
const aiPath = join(cwd, '.ai');
|
|
563
|
-
const patternsPath = join(aiPath, 'patterns');
|
|
564
|
-
|
|
565
|
-
console.log('\nPattern System Configuration\n');
|
|
566
|
-
|
|
567
|
-
console.log('Paths:');
|
|
568
|
-
console.log(` Workspace: ${cwd}`);
|
|
569
|
-
console.log(` AI Directory: ${aiPath}`);
|
|
570
|
-
console.log(` Patterns: ${patternsPath}`);
|
|
571
|
-
console.log(` Registry: ${join(patternsPath, 'registry.json')}`);
|
|
572
|
-
console.log(` Triggers: ${join(patternsPath, 'triggers.json')}`);
|
|
573
|
-
|
|
574
|
-
console.log('\nDirectory Structure:');
|
|
575
|
-
console.log(' patterns/');
|
|
576
|
-
console.log(' frontend/ Frontend patterns (React, Vue, etc.)');
|
|
577
|
-
console.log(' backend/ Backend patterns (Express, DB, etc.)');
|
|
578
|
-
console.log(' universal/ Universal patterns (TS, testing, etc.)');
|
|
579
|
-
console.log(' registry.json Pattern metadata');
|
|
580
|
-
console.log(' triggers.json Trigger definitions');
|
|
581
|
-
|
|
582
|
-
const registry = createRegistry(cwd);
|
|
583
|
-
await registry.load();
|
|
584
|
-
|
|
585
|
-
const engine = createTriggerEngine(cwd);
|
|
586
|
-
await engine.load();
|
|
587
|
-
|
|
588
|
-
console.log('\nStatus:');
|
|
589
|
-
console.log(` Patterns loaded: ${registry.getAll().length}`);
|
|
590
|
-
console.log(` Triggers loaded: ${engine.getAll().length}`);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
/**
|
|
594
|
-
* Show help
|
|
595
|
-
*/
|
|
596
|
-
function showHelp() {
|
|
597
|
-
console.log(`
|
|
598
|
-
PWN Patterns - Pattern Management
|
|
599
|
-
|
|
600
|
-
Usage: pwn patterns <command> [options]
|
|
601
|
-
|
|
602
|
-
Commands:
|
|
603
|
-
list List all patterns
|
|
604
|
-
show <id> Show pattern details
|
|
605
|
-
add <id> Add new pattern (category/name)
|
|
606
|
-
remove <id> Remove pattern from registry
|
|
607
|
-
search <query> Search patterns by name/description
|
|
608
|
-
triggers Manage triggers (list, add, remove)
|
|
609
|
-
eval <file> Evaluate triggers for a file
|
|
610
|
-
stats Show usage statistics
|
|
611
|
-
sync Sync registry with filesystem
|
|
612
|
-
config Show configuration
|
|
613
|
-
|
|
614
|
-
Pattern Commands:
|
|
615
|
-
pwn patterns list List all patterns
|
|
616
|
-
pwn patterns list --category=frontend Filter by category
|
|
617
|
-
pwn patterns show frontend/react Show pattern details
|
|
618
|
-
pwn patterns add backend/graphql Add new pattern
|
|
619
|
-
pwn patterns search "api" Search patterns
|
|
620
|
-
|
|
621
|
-
Trigger Commands:
|
|
622
|
-
pwn patterns triggers List all triggers
|
|
623
|
-
pwn patterns triggers add <type> --value <v> --patterns <p>
|
|
624
|
-
pwn patterns triggers remove <id> Remove trigger
|
|
625
|
-
pwn patterns triggers enable <id> Enable trigger
|
|
626
|
-
pwn patterns triggers disable <id> Disable trigger
|
|
627
|
-
|
|
628
|
-
Evaluation:
|
|
629
|
-
pwn patterns eval src/App.tsx Evaluate file triggers
|
|
630
|
-
|
|
631
|
-
Categories: frontend, backend, universal
|
|
632
|
-
Trigger Types: fileExt, path, import, keyword, command
|
|
633
|
-
|
|
634
|
-
Examples:
|
|
635
|
-
pwn patterns eval src/components/Button.tsx
|
|
636
|
-
pwn patterns triggers add fileExt --value "*.vue" --patterns frontend/vue
|
|
637
|
-
pwn patterns add frontend/svelte "Svelte component patterns"
|
|
638
|
-
`);
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* Get icon for category
|
|
643
|
-
*/
|
|
644
|
-
function getCategoryIcon(category) {
|
|
645
|
-
const icons = {
|
|
646
|
-
frontend: '🎨',
|
|
647
|
-
backend: '⚙️',
|
|
648
|
-
universal: '🔧'
|
|
649
|
-
};
|
|
650
|
-
return icons[category] || '📁';
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
/**
|
|
654
|
-
* Get icon for trigger type
|
|
655
|
-
*/
|
|
656
|
-
function getTriggerTypeIcon(type) {
|
|
657
|
-
const icons = {
|
|
658
|
-
fileExt: '📄',
|
|
659
|
-
path: '📂',
|
|
660
|
-
import: '📦',
|
|
661
|
-
keyword: '🔑',
|
|
662
|
-
command: '💻'
|
|
663
|
-
};
|
|
664
|
-
return icons[type] || '🔹';
|
|
665
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* PWN CLI - Patterns Command
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* pwn patterns List all patterns
|
|
6
|
+
* pwn patterns list List all patterns
|
|
7
|
+
* pwn patterns list --category List patterns by category
|
|
8
|
+
* pwn patterns show <id> Show pattern details
|
|
9
|
+
* pwn patterns add <id> Add new pattern
|
|
10
|
+
* pwn patterns remove <id> Remove pattern
|
|
11
|
+
* pwn patterns search <query> Search patterns
|
|
12
|
+
* pwn patterns triggers List all triggers
|
|
13
|
+
* pwn patterns triggers add Add new trigger
|
|
14
|
+
* pwn patterns triggers remove Remove trigger
|
|
15
|
+
* pwn patterns eval <file> Evaluate triggers for a file
|
|
16
|
+
* pwn patterns stats Show usage statistics
|
|
17
|
+
* pwn patterns sync Sync registry with filesystem
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { existsSync, readFileSync } from 'fs';
|
|
21
|
+
import { join, resolve, relative } from 'path';
|
|
22
|
+
import { PatternRegistry, createRegistry } from '../src/patterns/registry.js';
|
|
23
|
+
import { TriggerEngine, createTriggerEngine } from '../src/patterns/triggers.js';
|
|
24
|
+
|
|
25
|
+
export default async function patterns(args) {
|
|
26
|
+
const subcommand = args[0] || 'list';
|
|
27
|
+
const subArgs = args.slice(1);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
switch (subcommand) {
|
|
31
|
+
case 'list':
|
|
32
|
+
await listPatterns(subArgs);
|
|
33
|
+
break;
|
|
34
|
+
|
|
35
|
+
case 'show':
|
|
36
|
+
await showPattern(subArgs);
|
|
37
|
+
break;
|
|
38
|
+
|
|
39
|
+
case 'add':
|
|
40
|
+
await addPattern(subArgs);
|
|
41
|
+
break;
|
|
42
|
+
|
|
43
|
+
case 'remove':
|
|
44
|
+
await removePattern(subArgs);
|
|
45
|
+
break;
|
|
46
|
+
|
|
47
|
+
case 'search':
|
|
48
|
+
await searchPatterns(subArgs);
|
|
49
|
+
break;
|
|
50
|
+
|
|
51
|
+
case 'triggers':
|
|
52
|
+
await handleTriggers(subArgs);
|
|
53
|
+
break;
|
|
54
|
+
|
|
55
|
+
case 'eval':
|
|
56
|
+
await evaluateFile(subArgs);
|
|
57
|
+
break;
|
|
58
|
+
|
|
59
|
+
case 'stats':
|
|
60
|
+
await showStats();
|
|
61
|
+
break;
|
|
62
|
+
|
|
63
|
+
case 'sync':
|
|
64
|
+
await syncRegistry();
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
case 'config':
|
|
68
|
+
await showConfig();
|
|
69
|
+
break;
|
|
70
|
+
|
|
71
|
+
case '--help':
|
|
72
|
+
case '-h':
|
|
73
|
+
showHelp();
|
|
74
|
+
break;
|
|
75
|
+
|
|
76
|
+
default:
|
|
77
|
+
// If it looks like a pattern ID, show it
|
|
78
|
+
if (subcommand.includes('/')) {
|
|
79
|
+
await showPattern([subcommand, ...subArgs]);
|
|
80
|
+
} else {
|
|
81
|
+
console.log(`Unknown subcommand: ${subcommand}`);
|
|
82
|
+
console.log('Run: pwn patterns --help');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(`Error: ${error.message}`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* List all patterns
|
|
93
|
+
*/
|
|
94
|
+
async function listPatterns(args) {
|
|
95
|
+
const registry = createRegistry(process.cwd());
|
|
96
|
+
await registry.load();
|
|
97
|
+
|
|
98
|
+
const patterns = registry.getAll();
|
|
99
|
+
|
|
100
|
+
if (patterns.length === 0) {
|
|
101
|
+
console.log('No patterns found.');
|
|
102
|
+
console.log('Run: pwn patterns sync to scan patterns directory');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Parse options
|
|
107
|
+
const categoryFilter = args.find(a => a.startsWith('--category='))?.split('=')[1];
|
|
108
|
+
const showAll = args.includes('--all') || args.includes('-a');
|
|
109
|
+
|
|
110
|
+
let filtered = patterns;
|
|
111
|
+
if (categoryFilter) {
|
|
112
|
+
filtered = registry.getByCategory(categoryFilter);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Group by category
|
|
116
|
+
const byCategory = {};
|
|
117
|
+
for (const pattern of filtered) {
|
|
118
|
+
if (!byCategory[pattern.category]) {
|
|
119
|
+
byCategory[pattern.category] = [];
|
|
120
|
+
}
|
|
121
|
+
byCategory[pattern.category].push(pattern);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log('\nPatterns Registry\n');
|
|
125
|
+
|
|
126
|
+
for (const [category, categoryPatterns] of Object.entries(byCategory)) {
|
|
127
|
+
console.log(`${getCategoryIcon(category)} ${category.toUpperCase()}`);
|
|
128
|
+
|
|
129
|
+
for (const pattern of categoryPatterns) {
|
|
130
|
+
const usage = pattern.metadata.usageCount > 0
|
|
131
|
+
? ` (used ${pattern.metadata.usageCount}x)`
|
|
132
|
+
: '';
|
|
133
|
+
console.log(` ${pattern.id}${usage}`);
|
|
134
|
+
|
|
135
|
+
if (showAll) {
|
|
136
|
+
console.log(` ${pattern.description}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
console.log();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log(`Total: ${patterns.length} patterns`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Show pattern details
|
|
147
|
+
*/
|
|
148
|
+
async function showPattern(args) {
|
|
149
|
+
const patternId = args[0];
|
|
150
|
+
|
|
151
|
+
if (!patternId) {
|
|
152
|
+
console.log('Usage: pwn patterns show <pattern-id>');
|
|
153
|
+
console.log('Example: pwn patterns show frontend/react');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const registry = createRegistry(process.cwd());
|
|
158
|
+
await registry.load();
|
|
159
|
+
|
|
160
|
+
const pattern = registry.get(patternId);
|
|
161
|
+
|
|
162
|
+
if (!pattern) {
|
|
163
|
+
console.log(`Pattern not found: ${patternId}`);
|
|
164
|
+
console.log('\nAvailable patterns:');
|
|
165
|
+
registry.getAll().slice(0, 10).forEach(p => console.log(` ${p.id}`));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log(`\n${pattern.name}`);
|
|
170
|
+
console.log('='.repeat(pattern.name.length));
|
|
171
|
+
console.log(`\nID: ${pattern.id}`);
|
|
172
|
+
console.log(`Category: ${pattern.category}`);
|
|
173
|
+
console.log(`Path: ${pattern.path}`);
|
|
174
|
+
console.log(`Description: ${pattern.description}`);
|
|
175
|
+
console.log(`\nUsage: ${pattern.metadata.usageCount} times`);
|
|
176
|
+
|
|
177
|
+
if (pattern.metadata.lastUsed) {
|
|
178
|
+
console.log(`Last used: ${new Date(pattern.metadata.lastUsed).toLocaleString()}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (pattern.triggers.length > 0) {
|
|
182
|
+
console.log(`\nTriggers: ${pattern.triggers.join(', ')}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Show content preview
|
|
186
|
+
const content = registry.getContent(patternId);
|
|
187
|
+
if (content) {
|
|
188
|
+
console.log('\n--- Content Preview ---');
|
|
189
|
+
const lines = content.split('\n').slice(0, 20);
|
|
190
|
+
console.log(lines.join('\n'));
|
|
191
|
+
if (content.split('\n').length > 20) {
|
|
192
|
+
console.log('...');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Add new pattern
|
|
199
|
+
*/
|
|
200
|
+
async function addPattern(args) {
|
|
201
|
+
const patternId = args[0];
|
|
202
|
+
|
|
203
|
+
if (!patternId || !patternId.includes('/')) {
|
|
204
|
+
console.log('Usage: pwn patterns add <category>/<name>');
|
|
205
|
+
console.log('Example: pwn patterns add frontend/nextjs');
|
|
206
|
+
console.log('\nCategories: frontend, backend, universal');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const [category, name] = patternId.split('/');
|
|
211
|
+
|
|
212
|
+
if (!['frontend', 'backend', 'universal'].includes(category)) {
|
|
213
|
+
console.log(`Invalid category: ${category}`);
|
|
214
|
+
console.log('Valid categories: frontend, backend, universal');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const description = args.slice(1).join(' ') || `${name} patterns and best practices`;
|
|
219
|
+
|
|
220
|
+
const registry = createRegistry(process.cwd());
|
|
221
|
+
await registry.load();
|
|
222
|
+
|
|
223
|
+
// Check if already exists
|
|
224
|
+
if (registry.get(patternId)) {
|
|
225
|
+
console.log(`Pattern already exists: ${patternId}`);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
await registry.register({
|
|
230
|
+
id: patternId,
|
|
231
|
+
name: name.charAt(0).toUpperCase() + name.slice(1).replace(/-/g, ' '),
|
|
232
|
+
category,
|
|
233
|
+
path: `patterns/${category}/${name}`,
|
|
234
|
+
description
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
console.log(`Created pattern: ${patternId}`);
|
|
238
|
+
console.log(`Directory: .ai/patterns/${category}/${name}/`);
|
|
239
|
+
console.log('\nNext steps:');
|
|
240
|
+
console.log(` 1. Edit .ai/patterns/${category}/${name}/README.md`);
|
|
241
|
+
console.log(' 2. Add trigger: pwn patterns triggers add');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Remove pattern
|
|
246
|
+
*/
|
|
247
|
+
async function removePattern(args) {
|
|
248
|
+
const patternId = args[0];
|
|
249
|
+
|
|
250
|
+
if (!patternId) {
|
|
251
|
+
console.log('Usage: pwn patterns remove <pattern-id>');
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const registry = createRegistry(process.cwd());
|
|
256
|
+
await registry.load();
|
|
257
|
+
|
|
258
|
+
const pattern = registry.get(patternId);
|
|
259
|
+
if (!pattern) {
|
|
260
|
+
console.log(`Pattern not found: ${patternId}`);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
await registry.remove(patternId);
|
|
265
|
+
console.log(`Removed pattern from registry: ${patternId}`);
|
|
266
|
+
console.log('Note: Pattern directory was not deleted');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Search patterns
|
|
271
|
+
*/
|
|
272
|
+
async function searchPatterns(args) {
|
|
273
|
+
const query = args.join(' ');
|
|
274
|
+
|
|
275
|
+
if (!query) {
|
|
276
|
+
console.log('Usage: pwn patterns search <query>');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const registry = createRegistry(process.cwd());
|
|
281
|
+
await registry.load();
|
|
282
|
+
|
|
283
|
+
const results = registry.search(query);
|
|
284
|
+
|
|
285
|
+
if (results.length === 0) {
|
|
286
|
+
console.log(`No patterns found matching: ${query}`);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
console.log(`\nFound ${results.length} pattern(s) matching "${query}":\n`);
|
|
291
|
+
|
|
292
|
+
for (const pattern of results) {
|
|
293
|
+
console.log(` ${pattern.id}`);
|
|
294
|
+
console.log(` ${pattern.description}\n`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Handle trigger subcommands
|
|
300
|
+
*/
|
|
301
|
+
async function handleTriggers(args) {
|
|
302
|
+
const action = args[0] || 'list';
|
|
303
|
+
const actionArgs = args.slice(1);
|
|
304
|
+
|
|
305
|
+
const engine = createTriggerEngine(process.cwd());
|
|
306
|
+
await engine.load();
|
|
307
|
+
|
|
308
|
+
switch (action) {
|
|
309
|
+
case 'list':
|
|
310
|
+
await listTriggers(engine);
|
|
311
|
+
break;
|
|
312
|
+
|
|
313
|
+
case 'add':
|
|
314
|
+
await addTrigger(engine, actionArgs);
|
|
315
|
+
break;
|
|
316
|
+
|
|
317
|
+
case 'remove':
|
|
318
|
+
await removeTrigger(engine, actionArgs);
|
|
319
|
+
break;
|
|
320
|
+
|
|
321
|
+
case 'enable':
|
|
322
|
+
await setTriggerEnabled(engine, actionArgs[0], true);
|
|
323
|
+
break;
|
|
324
|
+
|
|
325
|
+
case 'disable':
|
|
326
|
+
await setTriggerEnabled(engine, actionArgs[0], false);
|
|
327
|
+
break;
|
|
328
|
+
|
|
329
|
+
default:
|
|
330
|
+
console.log('Trigger commands:');
|
|
331
|
+
console.log(' pwn patterns triggers list');
|
|
332
|
+
console.log(' pwn patterns triggers add');
|
|
333
|
+
console.log(' pwn patterns triggers remove <id>');
|
|
334
|
+
console.log(' pwn patterns triggers enable <id>');
|
|
335
|
+
console.log(' pwn patterns triggers disable <id>');
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* List triggers
|
|
341
|
+
*/
|
|
342
|
+
async function listTriggers(engine) {
|
|
343
|
+
const triggers = engine.getAll();
|
|
344
|
+
const summary = engine.getSummary();
|
|
345
|
+
|
|
346
|
+
if (triggers.length === 0) {
|
|
347
|
+
console.log('No triggers configured.');
|
|
348
|
+
console.log('Run: pwn patterns triggers add');
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
console.log('\nTrigger Registry\n');
|
|
353
|
+
|
|
354
|
+
const byType = {};
|
|
355
|
+
for (const trigger of triggers) {
|
|
356
|
+
if (!byType[trigger.type]) {
|
|
357
|
+
byType[trigger.type] = [];
|
|
358
|
+
}
|
|
359
|
+
byType[trigger.type].push(trigger);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (const [type, typeTriggers] of Object.entries(byType)) {
|
|
363
|
+
console.log(`${getTriggerTypeIcon(type)} ${type.toUpperCase()}`);
|
|
364
|
+
|
|
365
|
+
for (const trigger of typeTriggers) {
|
|
366
|
+
const status = trigger.enabled ? '' : ' [disabled]';
|
|
367
|
+
const value = Array.isArray(trigger.value)
|
|
368
|
+
? trigger.value.join(', ')
|
|
369
|
+
: trigger.value;
|
|
370
|
+
console.log(` ${trigger.name}${status}`);
|
|
371
|
+
console.log(` Match: ${value}`);
|
|
372
|
+
console.log(` Patterns: ${trigger.patterns.join(', ')}`);
|
|
373
|
+
}
|
|
374
|
+
console.log();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
console.log(`Total: ${summary.totalTriggers} triggers (${summary.disabledCount} disabled)`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Add trigger interactively
|
|
382
|
+
*/
|
|
383
|
+
async function addTrigger(engine, args) {
|
|
384
|
+
// Parse args for quick add
|
|
385
|
+
const type = args.find(a => ['fileExt', 'path', 'import', 'keyword', 'command'].includes(a));
|
|
386
|
+
const valueIndex = args.indexOf('--value');
|
|
387
|
+
const value = valueIndex !== -1 ? args[valueIndex + 1] : null;
|
|
388
|
+
const patternIndex = args.indexOf('--patterns');
|
|
389
|
+
const patterns = patternIndex !== -1 ? args[patternIndex + 1]?.split(',') : null;
|
|
390
|
+
const name = args.find(a => !a.startsWith('--') && a !== type);
|
|
391
|
+
|
|
392
|
+
if (type && value && patterns) {
|
|
393
|
+
// Quick add mode
|
|
394
|
+
await engine.register({
|
|
395
|
+
id: `${type}-${Date.now()}`,
|
|
396
|
+
name: name || `${type} trigger`,
|
|
397
|
+
type,
|
|
398
|
+
value,
|
|
399
|
+
patterns,
|
|
400
|
+
description: `Auto-generated trigger for ${value}`
|
|
401
|
+
});
|
|
402
|
+
console.log(`Added trigger: ${type} matching ${value}`);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
console.log('Add trigger (interactive mode not available in CLI)');
|
|
407
|
+
console.log('\nUsage:');
|
|
408
|
+
console.log(' pwn patterns triggers add <type> --value <pattern> --patterns <p1,p2>');
|
|
409
|
+
console.log('\nExample:');
|
|
410
|
+
console.log(' pwn patterns triggers add fileExt --value "*.vue" --patterns frontend/vue');
|
|
411
|
+
console.log('\nTypes: fileExt, path, import, keyword, command');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Remove trigger
|
|
416
|
+
*/
|
|
417
|
+
async function removeTrigger(engine, args) {
|
|
418
|
+
const triggerId = args[0];
|
|
419
|
+
|
|
420
|
+
if (!triggerId) {
|
|
421
|
+
console.log('Usage: pwn patterns triggers remove <trigger-id>');
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
await engine.remove(triggerId);
|
|
426
|
+
console.log(`Removed trigger: ${triggerId}`);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Enable/disable trigger
|
|
431
|
+
*/
|
|
432
|
+
async function setTriggerEnabled(engine, triggerId, enabled) {
|
|
433
|
+
if (!triggerId) {
|
|
434
|
+
console.log(`Usage: pwn patterns triggers ${enabled ? 'enable' : 'disable'} <trigger-id>`);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
await engine.setEnabled(triggerId, enabled);
|
|
439
|
+
console.log(`Trigger ${triggerId} ${enabled ? 'enabled' : 'disabled'}`);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Evaluate triggers for a file
|
|
444
|
+
*/
|
|
445
|
+
async function evaluateFile(args) {
|
|
446
|
+
const filePath = args[0];
|
|
447
|
+
|
|
448
|
+
if (!filePath) {
|
|
449
|
+
console.log('Usage: pwn patterns eval <file-path>');
|
|
450
|
+
console.log('Example: pwn patterns eval src/components/Button.tsx');
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const fullPath = resolve(filePath);
|
|
455
|
+
|
|
456
|
+
if (!existsSync(fullPath)) {
|
|
457
|
+
console.log(`File not found: ${filePath}`);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const engine = createTriggerEngine(process.cwd());
|
|
462
|
+
await engine.load();
|
|
463
|
+
|
|
464
|
+
// Read file content for content-based triggers
|
|
465
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
466
|
+
|
|
467
|
+
// Evaluate triggers
|
|
468
|
+
const result = engine.evaluateContent(content, fullPath);
|
|
469
|
+
|
|
470
|
+
console.log(`\nEvaluating: ${relative(process.cwd(), fullPath)}\n`);
|
|
471
|
+
|
|
472
|
+
if (result.matchedTriggers.length === 0) {
|
|
473
|
+
console.log('No triggers matched.');
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
console.log('Matched Triggers:');
|
|
478
|
+
for (const match of result.matchedTriggers) {
|
|
479
|
+
console.log(` ${match.trigger.name}`);
|
|
480
|
+
console.log(` Type: ${match.trigger.type}`);
|
|
481
|
+
console.log(` Reason: ${match.reason}`);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
console.log('\nPatterns to Load:');
|
|
485
|
+
for (const patternId of result.patterns) {
|
|
486
|
+
console.log(` ${patternId}`);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Load pattern content
|
|
490
|
+
const registry = createRegistry(process.cwd());
|
|
491
|
+
await registry.load();
|
|
492
|
+
|
|
493
|
+
console.log('\n--- Pattern Content Preview ---');
|
|
494
|
+
for (const patternId of result.patterns.slice(0, 2)) {
|
|
495
|
+
const content = registry.getContent(patternId);
|
|
496
|
+
if (content) {
|
|
497
|
+
console.log(`\n[${patternId}]`);
|
|
498
|
+
const preview = content.split('\n').slice(0, 10).join('\n');
|
|
499
|
+
console.log(preview);
|
|
500
|
+
console.log('...');
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Show usage statistics
|
|
507
|
+
*/
|
|
508
|
+
async function showStats() {
|
|
509
|
+
const registry = createRegistry(process.cwd());
|
|
510
|
+
await registry.load();
|
|
511
|
+
|
|
512
|
+
const engine = createTriggerEngine(process.cwd());
|
|
513
|
+
await engine.load();
|
|
514
|
+
|
|
515
|
+
const patternStats = registry.getStats();
|
|
516
|
+
const triggerStats = engine.getSummary();
|
|
517
|
+
|
|
518
|
+
console.log('\nPattern System Statistics\n');
|
|
519
|
+
|
|
520
|
+
console.log('Patterns:');
|
|
521
|
+
console.log(` Total: ${patternStats.totalPatterns}`);
|
|
522
|
+
for (const [category, count] of Object.entries(patternStats.byCategory)) {
|
|
523
|
+
console.log(` ${category}: ${count}`);
|
|
524
|
+
}
|
|
525
|
+
console.log(` Total usage: ${patternStats.totalUsage} times`);
|
|
526
|
+
if (patternStats.mostUsed) {
|
|
527
|
+
console.log(` Most used: ${patternStats.mostUsed.id} (${patternStats.mostUsed.count}x)`);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
console.log('\nTriggers:');
|
|
531
|
+
console.log(` Total: ${triggerStats.totalTriggers}`);
|
|
532
|
+
for (const [type, count] of Object.entries(triggerStats.byType)) {
|
|
533
|
+
console.log(` ${type}: ${count}`);
|
|
534
|
+
}
|
|
535
|
+
console.log(` Disabled: ${triggerStats.disabledCount}`);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Sync registry with filesystem
|
|
540
|
+
*/
|
|
541
|
+
async function syncRegistry() {
|
|
542
|
+
console.log('Scanning patterns directory...\n');
|
|
543
|
+
|
|
544
|
+
const registry = createRegistry(process.cwd());
|
|
545
|
+
await registry.scan();
|
|
546
|
+
|
|
547
|
+
const patterns = registry.getAll();
|
|
548
|
+
|
|
549
|
+
console.log(`Found ${patterns.length} patterns:`);
|
|
550
|
+
for (const pattern of patterns) {
|
|
551
|
+
console.log(` ${pattern.id}`);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
console.log('\nRegistry synced successfully.');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Show configuration
|
|
559
|
+
*/
|
|
560
|
+
async function showConfig() {
|
|
561
|
+
const cwd = process.cwd();
|
|
562
|
+
const aiPath = join(cwd, '.ai');
|
|
563
|
+
const patternsPath = join(aiPath, 'patterns');
|
|
564
|
+
|
|
565
|
+
console.log('\nPattern System Configuration\n');
|
|
566
|
+
|
|
567
|
+
console.log('Paths:');
|
|
568
|
+
console.log(` Workspace: ${cwd}`);
|
|
569
|
+
console.log(` AI Directory: ${aiPath}`);
|
|
570
|
+
console.log(` Patterns: ${patternsPath}`);
|
|
571
|
+
console.log(` Registry: ${join(patternsPath, 'registry.json')}`);
|
|
572
|
+
console.log(` Triggers: ${join(patternsPath, 'triggers.json')}`);
|
|
573
|
+
|
|
574
|
+
console.log('\nDirectory Structure:');
|
|
575
|
+
console.log(' patterns/');
|
|
576
|
+
console.log(' frontend/ Frontend patterns (React, Vue, etc.)');
|
|
577
|
+
console.log(' backend/ Backend patterns (Express, DB, etc.)');
|
|
578
|
+
console.log(' universal/ Universal patterns (TS, testing, etc.)');
|
|
579
|
+
console.log(' registry.json Pattern metadata');
|
|
580
|
+
console.log(' triggers.json Trigger definitions');
|
|
581
|
+
|
|
582
|
+
const registry = createRegistry(cwd);
|
|
583
|
+
await registry.load();
|
|
584
|
+
|
|
585
|
+
const engine = createTriggerEngine(cwd);
|
|
586
|
+
await engine.load();
|
|
587
|
+
|
|
588
|
+
console.log('\nStatus:');
|
|
589
|
+
console.log(` Patterns loaded: ${registry.getAll().length}`);
|
|
590
|
+
console.log(` Triggers loaded: ${engine.getAll().length}`);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Show help
|
|
595
|
+
*/
|
|
596
|
+
function showHelp() {
|
|
597
|
+
console.log(`
|
|
598
|
+
PWN Patterns - Pattern Management
|
|
599
|
+
|
|
600
|
+
Usage: pwn patterns <command> [options]
|
|
601
|
+
|
|
602
|
+
Commands:
|
|
603
|
+
list List all patterns
|
|
604
|
+
show <id> Show pattern details
|
|
605
|
+
add <id> Add new pattern (category/name)
|
|
606
|
+
remove <id> Remove pattern from registry
|
|
607
|
+
search <query> Search patterns by name/description
|
|
608
|
+
triggers Manage triggers (list, add, remove)
|
|
609
|
+
eval <file> Evaluate triggers for a file
|
|
610
|
+
stats Show usage statistics
|
|
611
|
+
sync Sync registry with filesystem
|
|
612
|
+
config Show configuration
|
|
613
|
+
|
|
614
|
+
Pattern Commands:
|
|
615
|
+
pwn patterns list List all patterns
|
|
616
|
+
pwn patterns list --category=frontend Filter by category
|
|
617
|
+
pwn patterns show frontend/react Show pattern details
|
|
618
|
+
pwn patterns add backend/graphql Add new pattern
|
|
619
|
+
pwn patterns search "api" Search patterns
|
|
620
|
+
|
|
621
|
+
Trigger Commands:
|
|
622
|
+
pwn patterns triggers List all triggers
|
|
623
|
+
pwn patterns triggers add <type> --value <v> --patterns <p>
|
|
624
|
+
pwn patterns triggers remove <id> Remove trigger
|
|
625
|
+
pwn patterns triggers enable <id> Enable trigger
|
|
626
|
+
pwn patterns triggers disable <id> Disable trigger
|
|
627
|
+
|
|
628
|
+
Evaluation:
|
|
629
|
+
pwn patterns eval src/App.tsx Evaluate file triggers
|
|
630
|
+
|
|
631
|
+
Categories: frontend, backend, universal
|
|
632
|
+
Trigger Types: fileExt, path, import, keyword, command
|
|
633
|
+
|
|
634
|
+
Examples:
|
|
635
|
+
pwn patterns eval src/components/Button.tsx
|
|
636
|
+
pwn patterns triggers add fileExt --value "*.vue" --patterns frontend/vue
|
|
637
|
+
pwn patterns add frontend/svelte "Svelte component patterns"
|
|
638
|
+
`);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Get icon for category
|
|
643
|
+
*/
|
|
644
|
+
function getCategoryIcon(category) {
|
|
645
|
+
const icons = {
|
|
646
|
+
frontend: '🎨',
|
|
647
|
+
backend: '⚙️',
|
|
648
|
+
universal: '🔧'
|
|
649
|
+
};
|
|
650
|
+
return icons[category] || '📁';
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Get icon for trigger type
|
|
655
|
+
*/
|
|
656
|
+
function getTriggerTypeIcon(type) {
|
|
657
|
+
const icons = {
|
|
658
|
+
fileExt: '📄',
|
|
659
|
+
path: '📂',
|
|
660
|
+
import: '📦',
|
|
661
|
+
keyword: '🔑',
|
|
662
|
+
command: '💻'
|
|
663
|
+
};
|
|
664
|
+
return icons[type] || '🔹';
|
|
665
|
+
}
|