@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.
- package/LICENSE +21 -21
- package/README.md +265 -251
- package/cli/batch.js +333 -333
- package/cli/codespaces.js +303 -303
- package/cli/index.js +98 -91
- package/cli/inject.js +78 -67
- package/cli/knowledge.js +531 -531
- package/cli/migrate.js +466 -0
- package/cli/notify.js +135 -135
- package/cli/patterns.js +665 -665
- package/cli/status.js +91 -91
- package/cli/validate.js +61 -61
- package/package.json +70 -70
- package/src/core/inject.js +208 -204
- package/src/core/state.js +91 -91
- package/src/core/validate.js +202 -202
- package/src/core/workspace.js +176 -176
- package/src/index.js +20 -20
- package/src/knowledge/gc.js +308 -308
- package/src/knowledge/lifecycle.js +401 -401
- package/src/knowledge/promote.js +364 -364
- package/src/knowledge/references.js +342 -342
- package/src/patterns/matcher.js +218 -218
- package/src/patterns/registry.js +375 -375
- package/src/patterns/triggers.js +423 -423
- package/src/services/batch-service.js +849 -849
- package/src/services/notification-service.js +342 -342
- package/templates/codespaces/devcontainer.json +52 -52
- package/templates/codespaces/setup.sh +70 -70
- package/templates/workspace/.ai/README.md +164 -164
- package/templates/workspace/.ai/agents/README.md +204 -204
- package/templates/workspace/.ai/agents/claude.md +625 -625
- package/templates/workspace/.ai/config/README.md +79 -79
- package/templates/workspace/.ai/config/notifications.template.json +20 -20
- package/templates/workspace/.ai/memory/deadends.md +79 -79
- package/templates/workspace/.ai/memory/decisions.md +58 -58
- package/templates/workspace/.ai/memory/patterns.md +65 -65
- package/templates/workspace/.ai/patterns/backend/README.md +126 -126
- package/templates/workspace/.ai/patterns/frontend/README.md +103 -103
- package/templates/workspace/.ai/patterns/index.md +256 -256
- package/templates/workspace/.ai/patterns/triggers.json +1087 -1087
- package/templates/workspace/.ai/patterns/universal/README.md +141 -141
- package/templates/workspace/.ai/state.template.json +8 -8
- package/templates/workspace/.ai/tasks/active.md +77 -77
- package/templates/workspace/.ai/tasks/backlog.md +95 -95
- package/templates/workspace/.ai/workflows/batch-task.md +356 -356
package/cli/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
|
+
}
|