@fermindi/pwn-cli 0.1.1 → 0.3.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 (48) 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 +112 -91
  6. package/cli/inject.js +90 -67
  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/save.js +206 -0
  12. package/cli/status.js +91 -91
  13. package/cli/update.js +189 -0
  14. package/cli/validate.js +61 -61
  15. package/package.json +70 -70
  16. package/src/core/inject.js +300 -204
  17. package/src/core/state.js +91 -91
  18. package/src/core/validate.js +202 -202
  19. package/src/core/workspace.js +176 -176
  20. package/src/index.js +20 -20
  21. package/src/knowledge/gc.js +308 -308
  22. package/src/knowledge/lifecycle.js +401 -401
  23. package/src/knowledge/promote.js +364 -364
  24. package/src/knowledge/references.js +342 -342
  25. package/src/patterns/matcher.js +218 -218
  26. package/src/patterns/registry.js +375 -375
  27. package/src/patterns/triggers.js +423 -423
  28. package/src/services/batch-service.js +849 -849
  29. package/src/services/notification-service.js +342 -342
  30. package/templates/codespaces/devcontainer.json +52 -52
  31. package/templates/codespaces/setup.sh +70 -70
  32. package/templates/workspace/.ai/README.md +164 -164
  33. package/templates/workspace/.ai/agents/README.md +204 -204
  34. package/templates/workspace/.ai/agents/claude.md +625 -625
  35. package/templates/workspace/.ai/config/README.md +79 -79
  36. package/templates/workspace/.ai/config/notifications.template.json +20 -20
  37. package/templates/workspace/.ai/memory/deadends.md +79 -79
  38. package/templates/workspace/.ai/memory/decisions.md +58 -58
  39. package/templates/workspace/.ai/memory/patterns.md +65 -65
  40. package/templates/workspace/.ai/patterns/backend/README.md +126 -126
  41. package/templates/workspace/.ai/patterns/frontend/README.md +103 -103
  42. package/templates/workspace/.ai/patterns/index.md +256 -256
  43. package/templates/workspace/.ai/patterns/triggers.json +1087 -1087
  44. package/templates/workspace/.ai/patterns/universal/README.md +141 -141
  45. package/templates/workspace/.ai/state.template.json +8 -8
  46. package/templates/workspace/.ai/tasks/active.md +77 -77
  47. package/templates/workspace/.ai/tasks/backlog.md +95 -95
  48. 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
+ }