@fermindi/pwn-cli 0.1.1 → 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 -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/status.js +91 -91
  12. package/cli/validate.js +61 -61
  13. package/package.json +70 -70
  14. package/src/core/inject.js +208 -204
  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/knowledge.js CHANGED
@@ -1,531 +1,531 @@
1
- import { existsSync } from 'fs';
2
- import { join } from 'path';
3
- import {
4
- initKnowledgeRegistry,
5
- syncKnowledge,
6
- getKnowledgeStats,
7
- getAllKnowledge,
8
- getKnowledgeItem,
9
- recordAccess,
10
- recordApplication,
11
- changeStatus
12
- } from '../src/knowledge/lifecycle.js';
13
- import {
14
- updateReferences,
15
- addReference,
16
- findBrokenReferences,
17
- getReferenceStats
18
- } from '../src/knowledge/references.js';
19
- import {
20
- dryRunGC,
21
- executeGC,
22
- getGCStats,
23
- getArchivalCandidates,
24
- restoreFromArchive
25
- } from '../src/knowledge/gc.js';
26
- import {
27
- checkPromotionEligibility,
28
- getPromotionCandidates,
29
- promoteToPattern,
30
- getPromotionStats,
31
- suggestPromotion
32
- } from '../src/knowledge/promote.js';
33
-
34
- const cwd = process.cwd();
35
-
36
- export default async function knowledge(args) {
37
- const subcommand = args[0];
38
-
39
- // Check for PWN workspace
40
- if (!existsSync(join(cwd, '.ai'))) {
41
- console.log('No PWN workspace found in current directory.');
42
- console.log('Run: pwn inject');
43
- process.exit(1);
44
- }
45
-
46
- switch (subcommand) {
47
- case 'status':
48
- await showStatus();
49
- break;
50
-
51
- case 'sync':
52
- await runSync();
53
- break;
54
-
55
- case 'check':
56
- await checkReferences();
57
- break;
58
-
59
- case 'gc':
60
- await runGC(args.slice(1));
61
- break;
62
-
63
- case 'promote':
64
- await runPromote(args.slice(1));
65
- break;
66
-
67
- case 'link':
68
- await runLink(args.slice(1));
69
- break;
70
-
71
- case 'show':
72
- await showItem(args[1]);
73
- break;
74
-
75
- case 'access':
76
- await recordItemAccess(args[1]);
77
- break;
78
-
79
- case 'apply':
80
- await recordItemApplication(args[1], args[2]);
81
- break;
82
-
83
- case 'restore':
84
- await runRestore(args[1]);
85
- break;
86
-
87
- default:
88
- showHelp();
89
- }
90
- }
91
-
92
- function showHelp() {
93
- console.log(`
94
- PWN Knowledge Lifecycle
95
-
96
- Usage: pwn knowledge <command> [options]
97
-
98
- Commands:
99
- status Show knowledge system status
100
- sync Sync registry with markdown files
101
- check Find broken references
102
- gc Garbage collection (dry-run)
103
- gc --apply Execute garbage collection
104
- gc --no-archive Delete instead of archive
105
- promote <id> Promote decision to pattern
106
- promote --list List promotion candidates
107
- link <id> <target> Add reference link
108
- show <id> Show knowledge item details
109
- access <id> Record an access to item
110
- apply <id> <ctx> Record application in context
111
- restore <id> Restore archived item
112
-
113
- Examples:
114
- pwn knowledge status
115
- pwn knowledge sync
116
- pwn knowledge gc
117
- pwn knowledge gc --apply
118
- pwn knowledge promote DEC-005
119
- pwn knowledge link DEC-001 src/auth/login.ts
120
- pwn knowledge show DEC-001
121
- `);
122
- }
123
-
124
- async function showStatus() {
125
- // Initialize if needed
126
- initKnowledgeRegistry(cwd);
127
-
128
- const stats = getKnowledgeStats(cwd);
129
- const refStats = getReferenceStats(cwd);
130
- const gcStats = getGCStats(cwd);
131
- const promoStats = getPromotionStats(cwd);
132
-
133
- console.log('\n PWN Knowledge Lifecycle Status\n');
134
-
135
- console.log('Knowledge Items:');
136
- console.log(` Total: ${stats.total}`);
137
- console.log(` Decisions: ${stats.decisions}`);
138
- console.log(` Dead-ends: ${stats.deadends}`);
139
- console.log(` Active: ${stats.active}`);
140
- console.log(` Candidates: ${stats.candidatePatterns}`);
141
- console.log(` Archived: ${stats.archived}`);
142
- console.log(` Garbage: ${stats.garbage}`);
143
-
144
- console.log('\nReferences:');
145
- console.log(` Total refs: ${refStats.totalRefs}`);
146
- console.log(` Avg per item: ${refStats.avgRefsPerItem}`);
147
- console.log(` Orphaned items: ${refStats.orphaned}`);
148
-
149
- if (refStats.mostReferenced.length > 0) {
150
- console.log('\n Most Referenced:');
151
- for (const item of refStats.mostReferenced.slice(0, 3)) {
152
- console.log(` ${item.id}: ${item.count} refs - ${item.title.substring(0, 40)}`);
153
- }
154
- }
155
-
156
- console.log('\nGarbage Collection:');
157
- console.log(` Last GC: ${gcStats.lastGC}`);
158
- console.log(` Ready for GC: ${gcStats.garbageCount}`);
159
- console.log(` Archive candidates: ${gcStats.archivalCandidates}`);
160
- console.log(` Broken refs: ${gcStats.brokenReferences}`);
161
-
162
- console.log('\nPattern Promotion:');
163
- console.log(` Promoted: ${promoStats.promotedCount}`);
164
- console.log(` Candidates: ${promoStats.candidateCount}`);
165
- console.log(` Eligible: ${promoStats.eligibleCount}`);
166
-
167
- const suggestion = suggestPromotion(cwd);
168
- if (suggestion) {
169
- console.log(`\n Suggestion: ${suggestion.message}`);
170
- }
171
-
172
- console.log(`\nLast Sync: ${stats.lastSync || 'Never'}`);
173
- console.log('');
174
- }
175
-
176
- async function runSync() {
177
- console.log('Syncing knowledge registry with markdown files...\n');
178
-
179
- const result = syncKnowledge(cwd);
180
-
181
- if (result.added.length > 0) {
182
- console.log(`Added ${result.added.length} items:`);
183
- for (const id of result.added) {
184
- console.log(` + ${id}`);
185
- }
186
- }
187
-
188
- if (result.updated.length > 0) {
189
- console.log(`Updated ${result.updated.length} items`);
190
- }
191
-
192
- if (result.removed.length > 0) {
193
- console.log(`Marked ${result.removed.length} items as garbage:`);
194
- for (const id of result.removed) {
195
- console.log(` - ${id}`);
196
- }
197
- }
198
-
199
- // Also update file references
200
- console.log('\nScanning for file references...');
201
- const refResult = updateReferences(cwd);
202
-
203
- if (refResult.error) {
204
- console.log(` Error: ${refResult.error}`);
205
- } else {
206
- console.log(` Updated: ${refResult.updated.length} items`);
207
- console.log(` Orphaned: ${refResult.orphaned.length} items`);
208
- if (refResult.newRefs.length > 0) {
209
- console.log(` New references found: ${refResult.newRefs.length}`);
210
- }
211
- }
212
-
213
- console.log('\nSync complete.');
214
- }
215
-
216
- async function checkReferences() {
217
- console.log('Checking for broken references...\n');
218
-
219
- const broken = findBrokenReferences(cwd);
220
-
221
- if (broken.length === 0) {
222
- console.log('No broken references found.');
223
- return;
224
- }
225
-
226
- console.log(`Found ${broken.length} items with broken references:\n`);
227
-
228
- for (const item of broken) {
229
- console.log(`${item.id}:`);
230
- if (item.brokenFiles.length > 0) {
231
- console.log(' Missing files:');
232
- for (const file of item.brokenFiles) {
233
- console.log(` - ${file}`);
234
- }
235
- }
236
- if (item.brokenDecisions.length > 0) {
237
- console.log(' Missing decisions:');
238
- for (const dec of item.brokenDecisions) {
239
- console.log(` - ${dec}`);
240
- }
241
- }
242
- }
243
-
244
- console.log('\nRun `pwn knowledge gc --apply` to clean broken references.');
245
- }
246
-
247
- async function runGC(args) {
248
- const apply = args.includes('--apply');
249
- const archive = !args.includes('--no-archive');
250
-
251
- if (!apply) {
252
- console.log('Garbage Collection (Dry Run)\n');
253
- const result = dryRunGC(cwd);
254
-
255
- if (result.marked.length === 0 && result.cleaned.length === 0) {
256
- console.log('Nothing to collect. Knowledge base is clean.');
257
- return;
258
- }
259
-
260
- if (result.marked.length > 0) {
261
- console.log(`Items to be ${archive ? 'archived' : 'deleted'}:`);
262
- for (const item of result.details.garbage) {
263
- console.log(` ${item.id}: ${item.reason} - ${item.title}`);
264
- }
265
- }
266
-
267
- if (result.cleaned.length > 0) {
268
- console.log(`\nBroken references to clean: ${result.cleaned.length}`);
269
- }
270
-
271
- console.log('\nRun with --apply to execute.');
272
- return;
273
- }
274
-
275
- console.log(`Executing Garbage Collection (${archive ? 'archive' : 'delete'} mode)...\n`);
276
-
277
- const result = executeGC({ archive, cleanBroken: true }, cwd);
278
-
279
- if (result.archived.length > 0) {
280
- console.log(`Archived ${result.archived.length} items:`);
281
- for (const id of result.archived) {
282
- console.log(` ${id}`);
283
- }
284
- }
285
-
286
- if (result.deleted.length > 0) {
287
- console.log(`Deleted ${result.deleted.length} items:`);
288
- for (const id of result.deleted) {
289
- console.log(` ${id}`);
290
- }
291
- }
292
-
293
- if (result.cleaned.length > 0) {
294
- console.log(`Cleaned references in ${result.cleaned.length} items`);
295
- }
296
-
297
- if (result.marked.length === 0 && result.cleaned.length === 0) {
298
- console.log('Nothing to collect.');
299
- }
300
-
301
- console.log('\nGarbage collection complete.');
302
- }
303
-
304
- async function runPromote(args) {
305
- if (args.includes('--list')) {
306
- const candidates = getPromotionCandidates(cwd);
307
-
308
- if (candidates.length === 0) {
309
- console.log('No promotion candidates found.');
310
- console.log('Decisions become candidates after being applied 3+ times in 2+ contexts.');
311
- return;
312
- }
313
-
314
- console.log('Promotion Candidates:\n');
315
- for (const c of candidates) {
316
- const status = c.isEligible ? '[READY]' : '[pending]';
317
- console.log(`${status} ${c.id}: ${c.title}`);
318
- console.log(` Applied: ${c.eligibility.appliedCount.current}/${c.eligibility.appliedCount.required}`);
319
- console.log(` Contexts: ${c.eligibility.appliedContexts.current}/${c.eligibility.appliedContexts.required}`);
320
- console.log(` Accessed: ${c.eligibility.accessCount.current}/${c.eligibility.accessCount.required}`);
321
- console.log('');
322
- }
323
- return;
324
- }
325
-
326
- const id = args[0];
327
- if (!id) {
328
- console.log('Usage: pwn knowledge promote <id>');
329
- console.log(' pwn knowledge promote --list');
330
- return;
331
- }
332
-
333
- // Check eligibility
334
- const eligibility = checkPromotionEligibility(id, cwd);
335
-
336
- if (!eligibility.eligible) {
337
- console.log(`${id} is not eligible for promotion.\n`);
338
- console.log('Criteria:');
339
- for (const [key, val] of Object.entries(eligibility.criteria)) {
340
- const status = val.met ? '[x]' : '[ ]';
341
- console.log(` ${status} ${key}: ${val.current}/${val.required}`);
342
- }
343
-
344
- if (args.includes('--force')) {
345
- console.log('\n--force flag detected. Proceeding anyway...');
346
- } else {
347
- console.log('\nUse --force to promote anyway.');
348
- return;
349
- }
350
- }
351
-
352
- // Get category
353
- const categoryArg = args.find(a => a.startsWith('--category='));
354
- const category = categoryArg ? categoryArg.split('=')[1] : undefined;
355
-
356
- const result = promoteToPattern(id, {
357
- category,
358
- force: args.includes('--force')
359
- }, cwd);
360
-
361
- if (result.success) {
362
- console.log(`Promoted ${id} to pattern!`);
363
- console.log(` File: .ai/${result.patternFile}`);
364
- console.log(` Category: ${result.category}`);
365
- console.log('\nEdit the pattern file to add details and examples.');
366
- } else {
367
- console.log(`Failed to promote: ${result.error}`);
368
- }
369
- }
370
-
371
- async function runLink(args) {
372
- const [id, target, typeArg] = args;
373
-
374
- if (!id || !target) {
375
- console.log('Usage: pwn knowledge link <id> <target> [--type=file|decision|task]');
376
- console.log('');
377
- console.log('Examples:');
378
- console.log(' pwn knowledge link DEC-001 src/auth/login.ts');
379
- console.log(' pwn knowledge link DEC-001 DEC-002 --type=decision');
380
- console.log(' pwn knowledge link DEC-001 TASK-005 --type=task');
381
- return;
382
- }
383
-
384
- // Infer type if not provided
385
- let targetType = 'file';
386
- if (typeArg?.startsWith('--type=')) {
387
- targetType = typeArg.split('=')[1];
388
- } else if (target.startsWith('DEC-') || target.startsWith('DE-')) {
389
- targetType = 'decision';
390
- } else if (target.startsWith('TASK-')) {
391
- targetType = 'task';
392
- }
393
-
394
- const result = addReference(id, target, targetType, cwd);
395
-
396
- if (!result) {
397
- console.log(`Failed to add link. Item ${id} not found.`);
398
- return;
399
- }
400
-
401
- console.log(`Linked ${id} -> ${target} (${targetType})`);
402
- }
403
-
404
- async function showItem(id) {
405
- if (!id) {
406
- console.log('Usage: pwn knowledge show <id>');
407
- return;
408
- }
409
-
410
- const item = getKnowledgeItem(id, cwd);
411
-
412
- if (!item) {
413
- console.log(`Item ${id} not found.`);
414
- return;
415
- }
416
-
417
- console.log(`\n${item.id}: ${item.title}\n`);
418
- console.log(`Type: ${item.type}`);
419
- console.log(`Status: ${item.status}`);
420
- console.log(`Created: ${item.createdAt || 'Unknown'}`);
421
- console.log(`Last Accessed: ${item.lastAccessed || 'Never'}`);
422
- console.log(`Access Count: ${item.accessCount || 0}`);
423
- console.log(`Applied Count: ${item.appliedCount || 0}`);
424
-
425
- if (item.relatedFiles?.length > 0) {
426
- console.log(`\nRelated Files (${item.relatedFiles.length}):`);
427
- for (const file of item.relatedFiles) {
428
- console.log(` - ${file}`);
429
- }
430
- }
431
-
432
- if (item.relatedDecisions?.length > 0) {
433
- console.log(`\nRelated Decisions (${item.relatedDecisions.length}):`);
434
- for (const dec of item.relatedDecisions) {
435
- console.log(` - ${dec}`);
436
- }
437
- }
438
-
439
- if (item.relatedTasks?.length > 0) {
440
- console.log(`\nRelated Tasks (${item.relatedTasks.length}):`);
441
- for (const task of item.relatedTasks) {
442
- console.log(` - ${task}`);
443
- }
444
- }
445
-
446
- if (item.appliedContexts?.length > 0) {
447
- console.log(`\nApplied Contexts (${item.appliedContexts.length}):`);
448
- for (const ctx of item.appliedContexts) {
449
- console.log(` - ${ctx}`);
450
- }
451
- }
452
-
453
- // Check promotion eligibility for decisions
454
- if (item.type === 'decision' && item.status === 'active') {
455
- const elig = checkPromotionEligibility(id, cwd);
456
- console.log(`\nPromotion Status: ${elig.eligible ? 'ELIGIBLE' : 'Not eligible'}`);
457
- if (!elig.eligible) {
458
- for (const [key, val] of Object.entries(elig.criteria)) {
459
- if (!val.met) {
460
- console.log(` - Need ${val.required - val.current} more ${key}`);
461
- }
462
- }
463
- }
464
- }
465
-
466
- console.log('');
467
- }
468
-
469
- async function recordItemAccess(id) {
470
- if (!id) {
471
- console.log('Usage: pwn knowledge access <id>');
472
- return;
473
- }
474
-
475
- const result = recordAccess(id, cwd);
476
-
477
- if (!result) {
478
- console.log(`Item ${id} not found.`);
479
- return;
480
- }
481
-
482
- console.log(`Recorded access to ${id} (total: ${result.accessCount})`);
483
- }
484
-
485
- async function recordItemApplication(id, context) {
486
- if (!id || !context) {
487
- console.log('Usage: pwn knowledge apply <id> <context>');
488
- console.log('');
489
- console.log('Example:');
490
- console.log(' pwn knowledge apply DEC-001 "user-auth-feature"');
491
- return;
492
- }
493
-
494
- const result = recordApplication(id, context, cwd);
495
-
496
- if (!result) {
497
- console.log(`Item ${id} not found.`);
498
- return;
499
- }
500
-
501
- console.log(`Recorded application of ${id} in "${context}"`);
502
- console.log(` Applied: ${result.appliedCount} times`);
503
- console.log(` Contexts: ${result.appliedContexts.length}`);
504
-
505
- if (result.status === 'candidate_pattern') {
506
- console.log(`\n${id} is now a candidate for promotion to pattern!`);
507
- console.log('Run: pwn knowledge promote --list');
508
- }
509
- }
510
-
511
- async function runRestore(id) {
512
- if (!id) {
513
- console.log('Usage: pwn knowledge restore <id>');
514
- return;
515
- }
516
-
517
- const result = restoreFromArchive(id, cwd);
518
-
519
- if (!result) {
520
- console.log(`Item ${id} not found.`);
521
- return;
522
- }
523
-
524
- if (result.error) {
525
- console.log(`Error: ${result.error}`);
526
- return;
527
- }
528
-
529
- console.log(`Restored ${id} from archive.`);
530
- console.log(`Status: ${result.status}`);
531
- }
1
+ import { existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import {
4
+ initKnowledgeRegistry,
5
+ syncKnowledge,
6
+ getKnowledgeStats,
7
+ getAllKnowledge,
8
+ getKnowledgeItem,
9
+ recordAccess,
10
+ recordApplication,
11
+ changeStatus
12
+ } from '../src/knowledge/lifecycle.js';
13
+ import {
14
+ updateReferences,
15
+ addReference,
16
+ findBrokenReferences,
17
+ getReferenceStats
18
+ } from '../src/knowledge/references.js';
19
+ import {
20
+ dryRunGC,
21
+ executeGC,
22
+ getGCStats,
23
+ getArchivalCandidates,
24
+ restoreFromArchive
25
+ } from '../src/knowledge/gc.js';
26
+ import {
27
+ checkPromotionEligibility,
28
+ getPromotionCandidates,
29
+ promoteToPattern,
30
+ getPromotionStats,
31
+ suggestPromotion
32
+ } from '../src/knowledge/promote.js';
33
+
34
+ const cwd = process.cwd();
35
+
36
+ export default async function knowledge(args) {
37
+ const subcommand = args[0];
38
+
39
+ // Check for PWN workspace
40
+ if (!existsSync(join(cwd, '.ai'))) {
41
+ console.log('No PWN workspace found in current directory.');
42
+ console.log('Run: pwn inject');
43
+ process.exit(1);
44
+ }
45
+
46
+ switch (subcommand) {
47
+ case 'status':
48
+ await showStatus();
49
+ break;
50
+
51
+ case 'sync':
52
+ await runSync();
53
+ break;
54
+
55
+ case 'check':
56
+ await checkReferences();
57
+ break;
58
+
59
+ case 'gc':
60
+ await runGC(args.slice(1));
61
+ break;
62
+
63
+ case 'promote':
64
+ await runPromote(args.slice(1));
65
+ break;
66
+
67
+ case 'link':
68
+ await runLink(args.slice(1));
69
+ break;
70
+
71
+ case 'show':
72
+ await showItem(args[1]);
73
+ break;
74
+
75
+ case 'access':
76
+ await recordItemAccess(args[1]);
77
+ break;
78
+
79
+ case 'apply':
80
+ await recordItemApplication(args[1], args[2]);
81
+ break;
82
+
83
+ case 'restore':
84
+ await runRestore(args[1]);
85
+ break;
86
+
87
+ default:
88
+ showHelp();
89
+ }
90
+ }
91
+
92
+ function showHelp() {
93
+ console.log(`
94
+ PWN Knowledge Lifecycle
95
+
96
+ Usage: pwn knowledge <command> [options]
97
+
98
+ Commands:
99
+ status Show knowledge system status
100
+ sync Sync registry with markdown files
101
+ check Find broken references
102
+ gc Garbage collection (dry-run)
103
+ gc --apply Execute garbage collection
104
+ gc --no-archive Delete instead of archive
105
+ promote <id> Promote decision to pattern
106
+ promote --list List promotion candidates
107
+ link <id> <target> Add reference link
108
+ show <id> Show knowledge item details
109
+ access <id> Record an access to item
110
+ apply <id> <ctx> Record application in context
111
+ restore <id> Restore archived item
112
+
113
+ Examples:
114
+ pwn knowledge status
115
+ pwn knowledge sync
116
+ pwn knowledge gc
117
+ pwn knowledge gc --apply
118
+ pwn knowledge promote DEC-005
119
+ pwn knowledge link DEC-001 src/auth/login.ts
120
+ pwn knowledge show DEC-001
121
+ `);
122
+ }
123
+
124
+ async function showStatus() {
125
+ // Initialize if needed
126
+ initKnowledgeRegistry(cwd);
127
+
128
+ const stats = getKnowledgeStats(cwd);
129
+ const refStats = getReferenceStats(cwd);
130
+ const gcStats = getGCStats(cwd);
131
+ const promoStats = getPromotionStats(cwd);
132
+
133
+ console.log('\n PWN Knowledge Lifecycle Status\n');
134
+
135
+ console.log('Knowledge Items:');
136
+ console.log(` Total: ${stats.total}`);
137
+ console.log(` Decisions: ${stats.decisions}`);
138
+ console.log(` Dead-ends: ${stats.deadends}`);
139
+ console.log(` Active: ${stats.active}`);
140
+ console.log(` Candidates: ${stats.candidatePatterns}`);
141
+ console.log(` Archived: ${stats.archived}`);
142
+ console.log(` Garbage: ${stats.garbage}`);
143
+
144
+ console.log('\nReferences:');
145
+ console.log(` Total refs: ${refStats.totalRefs}`);
146
+ console.log(` Avg per item: ${refStats.avgRefsPerItem}`);
147
+ console.log(` Orphaned items: ${refStats.orphaned}`);
148
+
149
+ if (refStats.mostReferenced.length > 0) {
150
+ console.log('\n Most Referenced:');
151
+ for (const item of refStats.mostReferenced.slice(0, 3)) {
152
+ console.log(` ${item.id}: ${item.count} refs - ${item.title.substring(0, 40)}`);
153
+ }
154
+ }
155
+
156
+ console.log('\nGarbage Collection:');
157
+ console.log(` Last GC: ${gcStats.lastGC}`);
158
+ console.log(` Ready for GC: ${gcStats.garbageCount}`);
159
+ console.log(` Archive candidates: ${gcStats.archivalCandidates}`);
160
+ console.log(` Broken refs: ${gcStats.brokenReferences}`);
161
+
162
+ console.log('\nPattern Promotion:');
163
+ console.log(` Promoted: ${promoStats.promotedCount}`);
164
+ console.log(` Candidates: ${promoStats.candidateCount}`);
165
+ console.log(` Eligible: ${promoStats.eligibleCount}`);
166
+
167
+ const suggestion = suggestPromotion(cwd);
168
+ if (suggestion) {
169
+ console.log(`\n Suggestion: ${suggestion.message}`);
170
+ }
171
+
172
+ console.log(`\nLast Sync: ${stats.lastSync || 'Never'}`);
173
+ console.log('');
174
+ }
175
+
176
+ async function runSync() {
177
+ console.log('Syncing knowledge registry with markdown files...\n');
178
+
179
+ const result = syncKnowledge(cwd);
180
+
181
+ if (result.added.length > 0) {
182
+ console.log(`Added ${result.added.length} items:`);
183
+ for (const id of result.added) {
184
+ console.log(` + ${id}`);
185
+ }
186
+ }
187
+
188
+ if (result.updated.length > 0) {
189
+ console.log(`Updated ${result.updated.length} items`);
190
+ }
191
+
192
+ if (result.removed.length > 0) {
193
+ console.log(`Marked ${result.removed.length} items as garbage:`);
194
+ for (const id of result.removed) {
195
+ console.log(` - ${id}`);
196
+ }
197
+ }
198
+
199
+ // Also update file references
200
+ console.log('\nScanning for file references...');
201
+ const refResult = updateReferences(cwd);
202
+
203
+ if (refResult.error) {
204
+ console.log(` Error: ${refResult.error}`);
205
+ } else {
206
+ console.log(` Updated: ${refResult.updated.length} items`);
207
+ console.log(` Orphaned: ${refResult.orphaned.length} items`);
208
+ if (refResult.newRefs.length > 0) {
209
+ console.log(` New references found: ${refResult.newRefs.length}`);
210
+ }
211
+ }
212
+
213
+ console.log('\nSync complete.');
214
+ }
215
+
216
+ async function checkReferences() {
217
+ console.log('Checking for broken references...\n');
218
+
219
+ const broken = findBrokenReferences(cwd);
220
+
221
+ if (broken.length === 0) {
222
+ console.log('No broken references found.');
223
+ return;
224
+ }
225
+
226
+ console.log(`Found ${broken.length} items with broken references:\n`);
227
+
228
+ for (const item of broken) {
229
+ console.log(`${item.id}:`);
230
+ if (item.brokenFiles.length > 0) {
231
+ console.log(' Missing files:');
232
+ for (const file of item.brokenFiles) {
233
+ console.log(` - ${file}`);
234
+ }
235
+ }
236
+ if (item.brokenDecisions.length > 0) {
237
+ console.log(' Missing decisions:');
238
+ for (const dec of item.brokenDecisions) {
239
+ console.log(` - ${dec}`);
240
+ }
241
+ }
242
+ }
243
+
244
+ console.log('\nRun `pwn knowledge gc --apply` to clean broken references.');
245
+ }
246
+
247
+ async function runGC(args) {
248
+ const apply = args.includes('--apply');
249
+ const archive = !args.includes('--no-archive');
250
+
251
+ if (!apply) {
252
+ console.log('Garbage Collection (Dry Run)\n');
253
+ const result = dryRunGC(cwd);
254
+
255
+ if (result.marked.length === 0 && result.cleaned.length === 0) {
256
+ console.log('Nothing to collect. Knowledge base is clean.');
257
+ return;
258
+ }
259
+
260
+ if (result.marked.length > 0) {
261
+ console.log(`Items to be ${archive ? 'archived' : 'deleted'}:`);
262
+ for (const item of result.details.garbage) {
263
+ console.log(` ${item.id}: ${item.reason} - ${item.title}`);
264
+ }
265
+ }
266
+
267
+ if (result.cleaned.length > 0) {
268
+ console.log(`\nBroken references to clean: ${result.cleaned.length}`);
269
+ }
270
+
271
+ console.log('\nRun with --apply to execute.');
272
+ return;
273
+ }
274
+
275
+ console.log(`Executing Garbage Collection (${archive ? 'archive' : 'delete'} mode)...\n`);
276
+
277
+ const result = executeGC({ archive, cleanBroken: true }, cwd);
278
+
279
+ if (result.archived.length > 0) {
280
+ console.log(`Archived ${result.archived.length} items:`);
281
+ for (const id of result.archived) {
282
+ console.log(` ${id}`);
283
+ }
284
+ }
285
+
286
+ if (result.deleted.length > 0) {
287
+ console.log(`Deleted ${result.deleted.length} items:`);
288
+ for (const id of result.deleted) {
289
+ console.log(` ${id}`);
290
+ }
291
+ }
292
+
293
+ if (result.cleaned.length > 0) {
294
+ console.log(`Cleaned references in ${result.cleaned.length} items`);
295
+ }
296
+
297
+ if (result.marked.length === 0 && result.cleaned.length === 0) {
298
+ console.log('Nothing to collect.');
299
+ }
300
+
301
+ console.log('\nGarbage collection complete.');
302
+ }
303
+
304
+ async function runPromote(args) {
305
+ if (args.includes('--list')) {
306
+ const candidates = getPromotionCandidates(cwd);
307
+
308
+ if (candidates.length === 0) {
309
+ console.log('No promotion candidates found.');
310
+ console.log('Decisions become candidates after being applied 3+ times in 2+ contexts.');
311
+ return;
312
+ }
313
+
314
+ console.log('Promotion Candidates:\n');
315
+ for (const c of candidates) {
316
+ const status = c.isEligible ? '[READY]' : '[pending]';
317
+ console.log(`${status} ${c.id}: ${c.title}`);
318
+ console.log(` Applied: ${c.eligibility.appliedCount.current}/${c.eligibility.appliedCount.required}`);
319
+ console.log(` Contexts: ${c.eligibility.appliedContexts.current}/${c.eligibility.appliedContexts.required}`);
320
+ console.log(` Accessed: ${c.eligibility.accessCount.current}/${c.eligibility.accessCount.required}`);
321
+ console.log('');
322
+ }
323
+ return;
324
+ }
325
+
326
+ const id = args[0];
327
+ if (!id) {
328
+ console.log('Usage: pwn knowledge promote <id>');
329
+ console.log(' pwn knowledge promote --list');
330
+ return;
331
+ }
332
+
333
+ // Check eligibility
334
+ const eligibility = checkPromotionEligibility(id, cwd);
335
+
336
+ if (!eligibility.eligible) {
337
+ console.log(`${id} is not eligible for promotion.\n`);
338
+ console.log('Criteria:');
339
+ for (const [key, val] of Object.entries(eligibility.criteria)) {
340
+ const status = val.met ? '[x]' : '[ ]';
341
+ console.log(` ${status} ${key}: ${val.current}/${val.required}`);
342
+ }
343
+
344
+ if (args.includes('--force')) {
345
+ console.log('\n--force flag detected. Proceeding anyway...');
346
+ } else {
347
+ console.log('\nUse --force to promote anyway.');
348
+ return;
349
+ }
350
+ }
351
+
352
+ // Get category
353
+ const categoryArg = args.find(a => a.startsWith('--category='));
354
+ const category = categoryArg ? categoryArg.split('=')[1] : undefined;
355
+
356
+ const result = promoteToPattern(id, {
357
+ category,
358
+ force: args.includes('--force')
359
+ }, cwd);
360
+
361
+ if (result.success) {
362
+ console.log(`Promoted ${id} to pattern!`);
363
+ console.log(` File: .ai/${result.patternFile}`);
364
+ console.log(` Category: ${result.category}`);
365
+ console.log('\nEdit the pattern file to add details and examples.');
366
+ } else {
367
+ console.log(`Failed to promote: ${result.error}`);
368
+ }
369
+ }
370
+
371
+ async function runLink(args) {
372
+ const [id, target, typeArg] = args;
373
+
374
+ if (!id || !target) {
375
+ console.log('Usage: pwn knowledge link <id> <target> [--type=file|decision|task]');
376
+ console.log('');
377
+ console.log('Examples:');
378
+ console.log(' pwn knowledge link DEC-001 src/auth/login.ts');
379
+ console.log(' pwn knowledge link DEC-001 DEC-002 --type=decision');
380
+ console.log(' pwn knowledge link DEC-001 TASK-005 --type=task');
381
+ return;
382
+ }
383
+
384
+ // Infer type if not provided
385
+ let targetType = 'file';
386
+ if (typeArg?.startsWith('--type=')) {
387
+ targetType = typeArg.split('=')[1];
388
+ } else if (target.startsWith('DEC-') || target.startsWith('DE-')) {
389
+ targetType = 'decision';
390
+ } else if (target.startsWith('TASK-')) {
391
+ targetType = 'task';
392
+ }
393
+
394
+ const result = addReference(id, target, targetType, cwd);
395
+
396
+ if (!result) {
397
+ console.log(`Failed to add link. Item ${id} not found.`);
398
+ return;
399
+ }
400
+
401
+ console.log(`Linked ${id} -> ${target} (${targetType})`);
402
+ }
403
+
404
+ async function showItem(id) {
405
+ if (!id) {
406
+ console.log('Usage: pwn knowledge show <id>');
407
+ return;
408
+ }
409
+
410
+ const item = getKnowledgeItem(id, cwd);
411
+
412
+ if (!item) {
413
+ console.log(`Item ${id} not found.`);
414
+ return;
415
+ }
416
+
417
+ console.log(`\n${item.id}: ${item.title}\n`);
418
+ console.log(`Type: ${item.type}`);
419
+ console.log(`Status: ${item.status}`);
420
+ console.log(`Created: ${item.createdAt || 'Unknown'}`);
421
+ console.log(`Last Accessed: ${item.lastAccessed || 'Never'}`);
422
+ console.log(`Access Count: ${item.accessCount || 0}`);
423
+ console.log(`Applied Count: ${item.appliedCount || 0}`);
424
+
425
+ if (item.relatedFiles?.length > 0) {
426
+ console.log(`\nRelated Files (${item.relatedFiles.length}):`);
427
+ for (const file of item.relatedFiles) {
428
+ console.log(` - ${file}`);
429
+ }
430
+ }
431
+
432
+ if (item.relatedDecisions?.length > 0) {
433
+ console.log(`\nRelated Decisions (${item.relatedDecisions.length}):`);
434
+ for (const dec of item.relatedDecisions) {
435
+ console.log(` - ${dec}`);
436
+ }
437
+ }
438
+
439
+ if (item.relatedTasks?.length > 0) {
440
+ console.log(`\nRelated Tasks (${item.relatedTasks.length}):`);
441
+ for (const task of item.relatedTasks) {
442
+ console.log(` - ${task}`);
443
+ }
444
+ }
445
+
446
+ if (item.appliedContexts?.length > 0) {
447
+ console.log(`\nApplied Contexts (${item.appliedContexts.length}):`);
448
+ for (const ctx of item.appliedContexts) {
449
+ console.log(` - ${ctx}`);
450
+ }
451
+ }
452
+
453
+ // Check promotion eligibility for decisions
454
+ if (item.type === 'decision' && item.status === 'active') {
455
+ const elig = checkPromotionEligibility(id, cwd);
456
+ console.log(`\nPromotion Status: ${elig.eligible ? 'ELIGIBLE' : 'Not eligible'}`);
457
+ if (!elig.eligible) {
458
+ for (const [key, val] of Object.entries(elig.criteria)) {
459
+ if (!val.met) {
460
+ console.log(` - Need ${val.required - val.current} more ${key}`);
461
+ }
462
+ }
463
+ }
464
+ }
465
+
466
+ console.log('');
467
+ }
468
+
469
+ async function recordItemAccess(id) {
470
+ if (!id) {
471
+ console.log('Usage: pwn knowledge access <id>');
472
+ return;
473
+ }
474
+
475
+ const result = recordAccess(id, cwd);
476
+
477
+ if (!result) {
478
+ console.log(`Item ${id} not found.`);
479
+ return;
480
+ }
481
+
482
+ console.log(`Recorded access to ${id} (total: ${result.accessCount})`);
483
+ }
484
+
485
+ async function recordItemApplication(id, context) {
486
+ if (!id || !context) {
487
+ console.log('Usage: pwn knowledge apply <id> <context>');
488
+ console.log('');
489
+ console.log('Example:');
490
+ console.log(' pwn knowledge apply DEC-001 "user-auth-feature"');
491
+ return;
492
+ }
493
+
494
+ const result = recordApplication(id, context, cwd);
495
+
496
+ if (!result) {
497
+ console.log(`Item ${id} not found.`);
498
+ return;
499
+ }
500
+
501
+ console.log(`Recorded application of ${id} in "${context}"`);
502
+ console.log(` Applied: ${result.appliedCount} times`);
503
+ console.log(` Contexts: ${result.appliedContexts.length}`);
504
+
505
+ if (result.status === 'candidate_pattern') {
506
+ console.log(`\n${id} is now a candidate for promotion to pattern!`);
507
+ console.log('Run: pwn knowledge promote --list');
508
+ }
509
+ }
510
+
511
+ async function runRestore(id) {
512
+ if (!id) {
513
+ console.log('Usage: pwn knowledge restore <id>');
514
+ return;
515
+ }
516
+
517
+ const result = restoreFromArchive(id, cwd);
518
+
519
+ if (!result) {
520
+ console.log(`Item ${id} not found.`);
521
+ return;
522
+ }
523
+
524
+ if (result.error) {
525
+ console.log(`Error: ${result.error}`);
526
+ return;
527
+ }
528
+
529
+ console.log(`Restored ${id} from archive.`);
530
+ console.log(`Status: ${result.status}`);
531
+ }