@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.
Files changed (46) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +265 -251
  3. package/cli/batch.js +333 -333
  4. package/cli/codespaces.js +303 -303
  5. package/cli/index.js +98 -91
  6. package/cli/inject.js +78 -53
  7. package/cli/knowledge.js +531 -531
  8. package/cli/migrate.js +466 -0
  9. package/cli/notify.js +135 -135
  10. package/cli/patterns.js +665 -665
  11. package/cli/status.js +91 -91
  12. package/cli/validate.js +61 -61
  13. package/package.json +70 -70
  14. package/src/core/inject.js +208 -128
  15. package/src/core/state.js +91 -91
  16. package/src/core/validate.js +202 -202
  17. package/src/core/workspace.js +176 -176
  18. package/src/index.js +20 -20
  19. package/src/knowledge/gc.js +308 -308
  20. package/src/knowledge/lifecycle.js +401 -401
  21. package/src/knowledge/promote.js +364 -364
  22. package/src/knowledge/references.js +342 -342
  23. package/src/patterns/matcher.js +218 -218
  24. package/src/patterns/registry.js +375 -375
  25. package/src/patterns/triggers.js +423 -423
  26. package/src/services/batch-service.js +849 -849
  27. package/src/services/notification-service.js +342 -342
  28. package/templates/codespaces/devcontainer.json +52 -52
  29. package/templates/codespaces/setup.sh +70 -70
  30. package/templates/workspace/.ai/README.md +164 -164
  31. package/templates/workspace/.ai/agents/README.md +204 -204
  32. package/templates/workspace/.ai/agents/claude.md +625 -625
  33. package/templates/workspace/.ai/config/README.md +79 -79
  34. package/templates/workspace/.ai/config/notifications.template.json +20 -20
  35. package/templates/workspace/.ai/memory/deadends.md +79 -79
  36. package/templates/workspace/.ai/memory/decisions.md +58 -58
  37. package/templates/workspace/.ai/memory/patterns.md +65 -65
  38. package/templates/workspace/.ai/patterns/backend/README.md +126 -126
  39. package/templates/workspace/.ai/patterns/frontend/README.md +103 -103
  40. package/templates/workspace/.ai/patterns/index.md +256 -256
  41. package/templates/workspace/.ai/patterns/triggers.json +1087 -1087
  42. package/templates/workspace/.ai/patterns/universal/README.md +141 -141
  43. package/templates/workspace/.ai/state.template.json +8 -8
  44. package/templates/workspace/.ai/tasks/active.md +77 -77
  45. package/templates/workspace/.ai/tasks/backlog.md +95 -95
  46. 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
+ }