kushi-agents 5.9.1 → 5.9.3
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/bin/cli.mjs +652 -669
- package/package.json +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -1,669 +1,652 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { main } from '../src/main.mjs';
|
|
4
|
-
import { runMultiHost } from '../src/multi-host.mjs';
|
|
5
|
-
|
|
6
|
-
const args = process.argv.slice(2);
|
|
7
|
-
|
|
8
|
-
// ── bare invocation
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
await
|
|
175
|
-
process.exit(0);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ──
|
|
179
|
-
if (args.length > 0 && args[0] === '
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (args.includes('--clawpilot'))
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const name = rest.find((a) => !a.startsWith('-'));
|
|
458
|
-
if (
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (rest.includes('--
|
|
467
|
-
if (rest.includes('--
|
|
468
|
-
break;
|
|
469
|
-
}
|
|
470
|
-
case '
|
|
471
|
-
// Usage: kushi
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
if (
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
console.error(` Doctrine file missing: ${topicMap[key]}`);
|
|
654
|
-
process.exit(1);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
const content = readFileSync(docFile, 'utf8');
|
|
658
|
-
const lines = content.split('\n').slice(0, 40);
|
|
659
|
-
console.log(`\n 📖 Topic: ${key} → ${topicMap[key]}\n`);
|
|
660
|
-
console.log(lines.join('\n'));
|
|
661
|
-
console.log(`\n ... (full doctrine at: plugin/instructions/${topicMap[key]})\n`);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
async function dispatchRemember(rule) {
|
|
665
|
-
console.log(`\n ✅ Rule noted: "${rule}"`);
|
|
666
|
-
console.log(' To persist this rule, run schema-evolve from within a project context:');
|
|
667
|
-
console.log(' @Kushi remember ' + rule);
|
|
668
|
-
console.log(' This will write to Evidence/<alias>/State/CLAUDE.md\n');
|
|
669
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { main } from '../src/main.mjs';
|
|
4
|
+
import { runMultiHost } from '../src/multi-host.mjs';
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
|
|
8
|
+
// ── bare invocation ──────────────────────────────────────────────────────────
|
|
9
|
+
// Two bins share this cli: `kushi-agents` (the installer entry — npx target)
|
|
10
|
+
// and `kushi` (the post-install user verb). Behavior diverges on no-args:
|
|
11
|
+
// - `kushi-agents` (no args) on a TTY → setup wizard (first-install ergonomics)
|
|
12
|
+
// - `kushi` (no args) on a TTY → Docker-style help (post-install verb)
|
|
13
|
+
// - Either, non-TTY → welcome card (no side effects)
|
|
14
|
+
// Override env vars: KUSHI_FORCE_WIZARD=1 forces wizard; KUSHI_SKIP_WELCOME=1
|
|
15
|
+
// forces the welcome card.
|
|
16
|
+
if (args.length === 0) {
|
|
17
|
+
const path0 = await import('node:path');
|
|
18
|
+
const invokedAs = (process.argv[1] || '').toLowerCase();
|
|
19
|
+
const isInstallerBin = /kushi-agents(\.cmd|\.js|\.mjs)?$/.test(path0.basename(invokedAs));
|
|
20
|
+
const forceWelcome = process.env.KUSHI_SKIP_WELCOME === '1';
|
|
21
|
+
const forceWizard = process.env.KUSHI_FORCE_WIZARD === '1';
|
|
22
|
+
const tty = process.stdin.isTTY && !forceWelcome;
|
|
23
|
+
|
|
24
|
+
if (forceWizard || (tty && isInstallerBin)) {
|
|
25
|
+
const { runSetupWizard } = await import('../src/setup-wizard.mjs');
|
|
26
|
+
await runSetupWizard({ args: [] });
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
if (tty) {
|
|
30
|
+
// Bare `kushi` on a TTY — Docker-style help (no side effects).
|
|
31
|
+
await printHelp();
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
await printWelcome();
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── doctor verb (v5.4.0+) ───────────────────────────────────────────────────
|
|
39
|
+
if (args.length > 0 && args[0] === 'doctor') {
|
|
40
|
+
const { spawnSync } = await import('node:child_process');
|
|
41
|
+
const pathMod = await import('node:path');
|
|
42
|
+
const urlMod = await import('node:url');
|
|
43
|
+
const here = pathMod.dirname(urlMod.fileURLToPath(import.meta.url));
|
|
44
|
+
const script = pathMod.resolve(here, '..', 'plugin', 'skills', 'doctor', 'doctor.ps1');
|
|
45
|
+
const psArgs = ['-NoProfile', '-File', script];
|
|
46
|
+
if (args.includes('--json')) psArgs.push('-Json');
|
|
47
|
+
if (args.includes('--strict')) psArgs.push('-Strict');
|
|
48
|
+
const r = spawnSync('pwsh', psArgs, { stdio: 'inherit' });
|
|
49
|
+
process.exit(r.status ?? 1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── setup-wizard flag (v5.4.0+) ─────────────────────────────────────────────
|
|
53
|
+
if (args.includes('--setup-wizard')) {
|
|
54
|
+
const { runSetupWizard } = await import('../src/setup-wizard.mjs');
|
|
55
|
+
await runSetupWizard({ args });
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function printWelcome() {
|
|
60
|
+
const pathMod = await import('node:path');
|
|
61
|
+
const urlMod = await import('node:url');
|
|
62
|
+
const fsMod = await import('node:fs');
|
|
63
|
+
const here = pathMod.dirname(urlMod.fileURLToPath(import.meta.url));
|
|
64
|
+
const repoRoot = pathMod.resolve(here, '..');
|
|
65
|
+
let version = 'unknown';
|
|
66
|
+
try { version = JSON.parse(fsMod.readFileSync(pathMod.join(repoRoot, 'package.json'), 'utf-8')).version; } catch {}
|
|
67
|
+
let skillCount = 0;
|
|
68
|
+
try {
|
|
69
|
+
const skillsDir = pathMod.join(repoRoot, 'plugin', 'skills');
|
|
70
|
+
skillCount = fsMod.readdirSync(skillsDir, { withFileTypes: true })
|
|
71
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith('_'))
|
|
72
|
+
.length;
|
|
73
|
+
} catch {}
|
|
74
|
+
console.log(`
|
|
75
|
+
kushi v${version} — multi-source M365 project evidence agent
|
|
76
|
+
|
|
77
|
+
(non-interactive shell — nothing was installed)
|
|
78
|
+
|
|
79
|
+
First time? kushi doctor
|
|
80
|
+
Bootstrap a project: kushi setup <project>
|
|
81
|
+
Ask a question: kushi ask <project> "..."
|
|
82
|
+
Wizard install: npx kushi-agents --setup-wizard
|
|
83
|
+
Host install: npx kushi-agents --clawpilot | --vscode | --all-hosts
|
|
84
|
+
|
|
85
|
+
Docs: https://gim-home.github.io/kushi/
|
|
86
|
+
Skills: ${skillCount} installed in plugin/skills/
|
|
87
|
+
|
|
88
|
+
Run kushi help for the full command list.
|
|
89
|
+
`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── skill-authoring verbs (v5.0.4+) ─────────────────────────────────────────
|
|
93
|
+
// Dispatch directly to the skill-creator / skill-checker pwsh scripts.
|
|
94
|
+
const SKILL_VERBS = new Set(['create-skill', 'check-skill', 'optimize-description', 'review-evals']);
|
|
95
|
+
if (args.length > 0 && SKILL_VERBS.has(args[0])) {
|
|
96
|
+
const verb = args[0];
|
|
97
|
+
const rest = args.slice(1);
|
|
98
|
+
await dispatchSkillVerb(verb, rest);
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── lint verb (v5.1.0+) ──────────────────────────────────────────────────────
|
|
103
|
+
if (args.length > 0 && args[0] === 'lint') {
|
|
104
|
+
if (args.includes('--global')) {
|
|
105
|
+
const { runGlobalLint } = await import('../src/global-wiki-cli.mjs');
|
|
106
|
+
await runGlobalLint();
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
109
|
+
const project = args[1] || '';
|
|
110
|
+
if (!project) {
|
|
111
|
+
console.error('\n Usage: kushi lint <project>\n kushi lint --global\n');
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
await dispatchLint(project);
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── one-shot wiki verb (v5.9.1+) ─────────────────────────────────────────────
|
|
119
|
+
// `kushi wiki` — deterministic do-the-wiki: resolve root, init if missing,
|
|
120
|
+
// print path + status + clickable file:// URL. Idempotent across
|
|
121
|
+
// "create wiki" / "do wiki" / "refresh wiki" / "update wiki".
|
|
122
|
+
if (args.length > 0 && args[0] === 'wiki') {
|
|
123
|
+
const cliMod = await import('../src/global-wiki-cli.mjs');
|
|
124
|
+
await cliMod.runWiki();
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── global verb (v5.3.0+) ────────────────────────────────────────────────────
|
|
129
|
+
if (args.length > 0 && args[0] === 'global') {
|
|
130
|
+
const sub = args[1] || '';
|
|
131
|
+
const validSubs = ['init', 'status', 'ask', 'lint', 'migrate', 'set-root', 'show-root'];
|
|
132
|
+
if (!validSubs.includes(sub)) {
|
|
133
|
+
console.error('\n Usage: kushi global init Scaffold State/ at the resolved root');
|
|
134
|
+
console.error(' kushi global status Show counts + freshness');
|
|
135
|
+
console.error(' kushi global ask <question> Ask the global wiki');
|
|
136
|
+
console.error(' kushi global lint Lint the global wiki');
|
|
137
|
+
console.error(' kushi global show-root Show how the root path is resolved');
|
|
138
|
+
console.error(' kushi global set-root <path> [--scope workspace|home]');
|
|
139
|
+
console.error(' Persist root (workspace shared by default,');
|
|
140
|
+
console.error(' falls back to ~/.kushi/config.json)');
|
|
141
|
+
console.error(' kushi global migrate <new-path> Copy wiki to a new root + persist setting\n');
|
|
142
|
+
console.error(' Tip: Set the root to a OneDrive-synced SharePoint folder to share');
|
|
143
|
+
console.error(' the wiki across a team. See docs/how-to/wiki-on-sharepoint.md.\n');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
const cliMod = await import('../src/global-wiki-cli.mjs');
|
|
147
|
+
if (sub === 'init') await cliMod.runGlobalInit();
|
|
148
|
+
else if (sub === 'status') await cliMod.runGlobalStatus();
|
|
149
|
+
else if (sub === 'ask') await cliMod.runGlobalAsk(args.slice(2).join(' '));
|
|
150
|
+
else if (sub === 'lint') await cliMod.runGlobalLint();
|
|
151
|
+
else if (sub === 'migrate') await cliMod.runGlobalMigrate(args[2]);
|
|
152
|
+
else if (sub === 'set-root') {
|
|
153
|
+
const scopeIdx = args.indexOf('--scope');
|
|
154
|
+
const scope = scopeIdx > 0 && args[scopeIdx + 1] ? args[scopeIdx + 1] : null;
|
|
155
|
+
const target = args.find((a, i) => i >= 2 && !a.startsWith('--') && args[i - 1] !== '--scope');
|
|
156
|
+
await cliMod.runGlobalSetRoot(target, { scope });
|
|
157
|
+
}
|
|
158
|
+
else if (sub === 'show-root') await cliMod.runGlobalShowRoot();
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── promote verb (v5.3.0+) ───────────────────────────────────────────────────
|
|
163
|
+
if (args.length > 0 && args[0] === 'promote') {
|
|
164
|
+
const project = args[1] || '';
|
|
165
|
+
const page = args[2] || '';
|
|
166
|
+
if (!project || !page) {
|
|
167
|
+
console.error('\n Usage: kushi promote <project> <page-path>\n');
|
|
168
|
+
console.error(' Copies a project State page into the global wiki with provenance metadata.');
|
|
169
|
+
console.error(' Refuses by default if customer identifiers are detected; pass --force after review.\n');
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
const force = args.includes('--force');
|
|
173
|
+
const { runPromote } = await import('../src/global-wiki-cli.mjs');
|
|
174
|
+
await runPromote(project, page, { force });
|
|
175
|
+
process.exit(0);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── hooks verb (v5.2.0+) ─────────────────────────────────────────────────────
|
|
179
|
+
if (args.length > 0 && args[0] === 'hooks') {
|
|
180
|
+
const sub = args[1] || '';
|
|
181
|
+
const project = args[2] || '';
|
|
182
|
+
if (!sub || !project || !['list', 'test'].includes(sub)) {
|
|
183
|
+
console.error('\n Usage: kushi hooks list <project>\n kushi hooks test <project> <event>\n');
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
await dispatchHooks(sub, project, args.slice(3));
|
|
187
|
+
process.exit(0);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── explain verb (v5.2.0+) ───────────────────────────────────────────────────
|
|
191
|
+
if (args.length > 0 && args[0] === 'explain') {
|
|
192
|
+
const topic = args.slice(1).join(' ');
|
|
193
|
+
if (!topic) {
|
|
194
|
+
console.error('\n Usage: kushi explain <topic>\n\n Available topics: contradictions, refresh, state, hooks, parallel, otel, csc, graph, workiq, schema, install, evals\n');
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
await dispatchExplain(topic);
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ── remember verb (v5.2.0+) ──────────────────────────────────────────────────
|
|
202
|
+
if (args.length > 0 && args[0] === 'remember') {
|
|
203
|
+
const rule = args.slice(1).join(' ');
|
|
204
|
+
if (!rule) {
|
|
205
|
+
console.error('\n Usage: kushi remember <rule>\n\n Example: kushi remember "always use Northwind not Healthcare Accelerator"\n');
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
await dispatchRemember(rule);
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (args.includes('--help') || args.includes('-h') || args.includes('-help') || args[0] === 'help') {
|
|
213
|
+
await printHelp();
|
|
214
|
+
process.exit(0);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function printHelp() {
|
|
218
|
+
const pathMod = await import('node:path');
|
|
219
|
+
const urlMod = await import('node:url');
|
|
220
|
+
const fsMod = await import('node:fs');
|
|
221
|
+
const here = pathMod.dirname(urlMod.fileURLToPath(import.meta.url));
|
|
222
|
+
const repoRoot = pathMod.resolve(here, '..');
|
|
223
|
+
let version = 'unknown';
|
|
224
|
+
try { version = JSON.parse(fsMod.readFileSync(pathMod.join(repoRoot, 'package.json'), 'utf-8')).version; } catch {}
|
|
225
|
+
console.log(`
|
|
226
|
+
Usage: kushi [COMMAND] [ARGS...]
|
|
227
|
+
npx kushi-agents [INSTALL OPTIONS]
|
|
228
|
+
|
|
229
|
+
Kushi v${version} — multi-source M365 project evidence + Q&A agent.
|
|
230
|
+
|
|
231
|
+
Common Commands:
|
|
232
|
+
bootstrap <project> First-time setup for a project (full pull)
|
|
233
|
+
refresh <project> Incremental refresh + rebuild State/
|
|
234
|
+
ask <project> <question> Cited Q&A over Evidence/ (auto-routes; --file-back to save)
|
|
235
|
+
status <project> Show run-log for a project
|
|
236
|
+
wiki Resolve + scaffold + open the global wiki (one-shot)
|
|
237
|
+
doctor Aggregated health check (env, drift, evals, etc.)
|
|
238
|
+
|
|
239
|
+
Project Commands:
|
|
240
|
+
state <project> Re-render State/ from existing Evidence
|
|
241
|
+
references <project> Refresh shared references pool from Evidence URLs
|
|
242
|
+
consolidate <project> Merge per-user evidence into _Consolidated
|
|
243
|
+
lint <project> Run wiki-lint checks on State/
|
|
244
|
+
promote <project> <page> Move a project page into the global wiki (with redaction)
|
|
245
|
+
setup [<project>] Run the onboarding wizard / fill missing config
|
|
246
|
+
|
|
247
|
+
Global Wiki Commands:
|
|
248
|
+
global init Scaffold ~/.kushi-global/State/ (or KUSHI_GLOBAL_ROOT)
|
|
249
|
+
global status Page counts + freshness
|
|
250
|
+
global ask <question> Search the global wiki specifically
|
|
251
|
+
global lint Privacy + freshness scan
|
|
252
|
+
global show-root Show the 4-tier resolution chain
|
|
253
|
+
global set-root <path> Persist globalRoot (--scope workspace|home)
|
|
254
|
+
global migrate <new-path> Copy State/ to a new root + re-persist
|
|
255
|
+
|
|
256
|
+
Lifecycle Commands:
|
|
257
|
+
upgrade npm i -g kushi-agents@latest then re-seed assets in cwd
|
|
258
|
+
uninstall [--keep-config] Remove <cwd>/.kushi/ (preserves Evidence/, State/)
|
|
259
|
+
|
|
260
|
+
Authoring Commands:
|
|
261
|
+
create-skill <name> Scaffold a new plugin/skills/<name>/ tree
|
|
262
|
+
check-skill <name|--all> Lint a skill (or all skills) against the blueprint
|
|
263
|
+
optimize-description <s> Rewrite a skill's description per optimizer rules
|
|
264
|
+
review-evals <skill> Render an HTML eval-review viewer
|
|
265
|
+
explain <topic> Explain a kushi concept (read-only)
|
|
266
|
+
remember <rule> Persist a project convention to CLAUDE.md
|
|
267
|
+
hooks list|test <project> Inspect or fire hook events
|
|
268
|
+
|
|
269
|
+
Install (run via npx — first-time / host install):
|
|
270
|
+
npx kushi-agents --clawpilot Install to ~/.copilot/m-skills/kushi/
|
|
271
|
+
npx kushi-agents --vscode Install to ~/.vscode/chat/skills/kushi/
|
|
272
|
+
npx kushi-agents --all-hosts Install to BOTH hosts
|
|
273
|
+
npx kushi-agents --setup-wizard Interactive first-run flow
|
|
274
|
+
npx kushi-agents --uninstall Cleanly remove host install + skills-metadata entry
|
|
275
|
+
|
|
276
|
+
Profile: --profile core | standard (default) | full
|
|
277
|
+
Other: --force, --yes, --no-workspace, --no-settings, --no-instructions, --no-prompt
|
|
278
|
+
WorkIQ: --with-workiq | --workiq-path <abs> | --skip-workiq-check
|
|
279
|
+
|
|
280
|
+
Run 'kushi <command> --help' for more information on a command (where supported).
|
|
281
|
+
Docs: https://gim-home.github.io/kushi/
|
|
282
|
+
|
|
283
|
+
In VS Code Chat the prefix is "@Kushi". In Clawpilot just say "kushi <verb>".
|
|
284
|
+
`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
// ── state / refresh / bootstrap verbs (v5.9.0+) ─────────────────────────────
|
|
289
|
+
// Thin shells that exec the deterministic runners. Keeps `kushi state HCA` etc.
|
|
290
|
+
// runnable from the global bin without users having to know the runner paths.
|
|
291
|
+
if (args.length > 0 && ['state', 'refresh-runner', 'bootstrap-runner', 'discover', 'references'].includes(args[0])) {
|
|
292
|
+
const verb = args[0];
|
|
293
|
+
const project = args[1];
|
|
294
|
+
if (!project) {
|
|
295
|
+
console.error(`\n Usage: kushi ${verb} <project> [options]\n`);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
const { spawnSync } = await import('node:child_process');
|
|
299
|
+
const pathMod = await import('node:path');
|
|
300
|
+
const urlMod = await import('node:url');
|
|
301
|
+
const here = pathMod.dirname(urlMod.fileURLToPath(import.meta.url));
|
|
302
|
+
const runnerMap = {
|
|
303
|
+
state: 'pull-state.mjs',
|
|
304
|
+
references: 'pull-references.mjs',
|
|
305
|
+
discover: 'discover.mjs',
|
|
306
|
+
'refresh-runner': 'refresh.mjs',
|
|
307
|
+
'bootstrap-runner': 'bootstrap.mjs',
|
|
308
|
+
};
|
|
309
|
+
const runner = pathMod.resolve(here, '..', 'plugin', 'runners', runnerMap[verb]);
|
|
310
|
+
const passthrough = args.slice(2);
|
|
311
|
+
const r = spawnSync(process.execPath, [runner, '--project', project, ...passthrough], { stdio: 'inherit' });
|
|
312
|
+
process.exit(r.status ?? 1);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ── workspace uninstall / upgrade verbs (v5.9.0+) ───────────────────────────
|
|
316
|
+
if (args.length > 0 && args[0] === 'uninstall' && !args.includes('--clawpilot') && !args.includes('--vscode') && !args.includes('--all-hosts')) {
|
|
317
|
+
// Workspace uninstall: remove .kushi/ from cwd (preserves Evidence/, State/).
|
|
318
|
+
const fsMod = await import('node:fs');
|
|
319
|
+
const pathMod = await import('node:path');
|
|
320
|
+
const dest = pathMod.resolve(process.cwd(), '.kushi');
|
|
321
|
+
const keepConfig = args.includes('--keep-config');
|
|
322
|
+
if (!fsMod.existsSync(dest)) {
|
|
323
|
+
console.error(`\n No .kushi/ directory found at ${dest}\n`);
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
if (keepConfig) {
|
|
327
|
+
const assetDirs = ['agents', 'instructions', 'prompts', 'skills', 'templates', 'reference-packs', 'lib', 'runners'];
|
|
328
|
+
let removed = 0;
|
|
329
|
+
for (const d of assetDirs) {
|
|
330
|
+
const p = pathMod.join(dest, d);
|
|
331
|
+
if (fsMod.existsSync(p)) { fsMod.rmSync(p, { recursive: true, force: true }); removed++; }
|
|
332
|
+
}
|
|
333
|
+
console.log(`\n Removed ${removed} asset dir(s) from ${dest} (config/user/ preserved).\n`);
|
|
334
|
+
} else {
|
|
335
|
+
fsMod.rmSync(dest, { recursive: true, force: true });
|
|
336
|
+
console.log(`\n Removed ${dest}\n Evidence/ and State/ left untouched.\n`);
|
|
337
|
+
}
|
|
338
|
+
process.exit(0);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (args.length > 0 && args[0] === 'upgrade') {
|
|
342
|
+
// Upgrade: npm i -g @latest, then re-seed assets in cwd preserving config.
|
|
343
|
+
const { spawnSync } = await import('node:child_process');
|
|
344
|
+
console.log('\n Upgrading kushi-agents globally via npm...\n');
|
|
345
|
+
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
346
|
+
const r1 = spawnSync(npm, ['install', '-g', 'kushi-agents@latest'], { stdio: 'inherit' });
|
|
347
|
+
if (r1.status !== 0) {
|
|
348
|
+
console.error('\n npm install failed.\n');
|
|
349
|
+
process.exit(r1.status ?? 1);
|
|
350
|
+
}
|
|
351
|
+
console.log('\n Refreshing assets in cwd (config preserved)...\n');
|
|
352
|
+
const fsMod = await import('node:fs');
|
|
353
|
+
if (fsMod.existsSync('.kushi')) {
|
|
354
|
+
const r2 = spawnSync(npm, ['exec', '--', 'kushi-agents', '--no-prompt', '--force'], { stdio: 'inherit' });
|
|
355
|
+
process.exit(r2.status ?? 0);
|
|
356
|
+
} else {
|
|
357
|
+
console.log('\n No .kushi/ in cwd — global upgrade complete; cd into a project and run `kushi` to install.\n');
|
|
358
|
+
process.exit(0);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ── multi-host mode (v5.0.2+) ───────────────────────────────────────────────
|
|
363
|
+
// Trigger when the user passes any of: --vscode, --all-hosts, --uninstall.
|
|
364
|
+
// --clawpilot ALONE continues to route through the legacy main.mjs path so
|
|
365
|
+
// the existing target=clawpilot flow stays byte-identical.
|
|
366
|
+
const wantsVscode = args.includes('--vscode');
|
|
367
|
+
const wantsAllHosts = args.includes('--all-hosts');
|
|
368
|
+
const wantsUninstall = args.includes('--uninstall');
|
|
369
|
+
|
|
370
|
+
if (wantsVscode || wantsAllHosts || wantsUninstall) {
|
|
371
|
+
const hosts = [];
|
|
372
|
+
if (args.includes('--clawpilot')) hosts.push('clawpilot');
|
|
373
|
+
if (wantsVscode) hosts.push('vscode');
|
|
374
|
+
const all = wantsAllHosts || args.includes('--all');
|
|
375
|
+
|
|
376
|
+
runMultiHost({
|
|
377
|
+
hosts,
|
|
378
|
+
all,
|
|
379
|
+
uninstall: wantsUninstall,
|
|
380
|
+
profile: getFlag('--profile'),
|
|
381
|
+
includeWorkspace: !args.includes('--no-workspace'),
|
|
382
|
+
noPrompt: args.includes('--no-prompt'),
|
|
383
|
+
}).catch((err) => {
|
|
384
|
+
console.error(`\n ${err.message}\n`);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
});
|
|
387
|
+
} else {
|
|
388
|
+
let target = getFlag('--target');
|
|
389
|
+
if (args.includes('--clawpilot')) {
|
|
390
|
+
if (target && target !== 'clawpilot') {
|
|
391
|
+
console.error(`\n Conflicting flags: --target ${target} and --clawpilot.\n`);
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
target = 'clawpilot';
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const options = {
|
|
398
|
+
force: args.includes('--force'),
|
|
399
|
+
yes: args.includes('--yes') || args.includes('-y'),
|
|
400
|
+
noSettings: args.includes('--no-settings'),
|
|
401
|
+
noInstructions: args.includes('--no-instructions'),
|
|
402
|
+
noPrompt: args.includes('--no-prompt'),
|
|
403
|
+
target,
|
|
404
|
+
profile: getFlag('--profile'),
|
|
405
|
+
withWorkiq: args.includes('--with-workiq'),
|
|
406
|
+
workiqPath: getFlag('--workiq-path'),
|
|
407
|
+
skipWorkiqCheck: args.includes('--skip-workiq-check'),
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
main(options).catch((err) => {
|
|
411
|
+
console.error(`\n ${err.message}\n`);
|
|
412
|
+
process.exit(1);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function getFlag(flag) {
|
|
417
|
+
const idx = args.indexOf(flag);
|
|
418
|
+
if (idx !== -1 && idx + 1 < args.length) {
|
|
419
|
+
return args[idx + 1];
|
|
420
|
+
}
|
|
421
|
+
const prefix = flag + '=';
|
|
422
|
+
const match = args.find((a) => a.startsWith(prefix));
|
|
423
|
+
return match ? match.slice(prefix.length) : undefined;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ── skill-authoring verb dispatch (v5.0.4+) ─────────────────────────────────
|
|
427
|
+
async function dispatchSkillVerb(verb, rest) {
|
|
428
|
+
const { spawnSync } = await import('node:child_process');
|
|
429
|
+
const path = await import('node:path');
|
|
430
|
+
const url = await import('node:url');
|
|
431
|
+
const here = path.dirname(url.fileURLToPath(import.meta.url));
|
|
432
|
+
const repoRoot = path.resolve(here, '..');
|
|
433
|
+
const creatorDir = path.join(repoRoot, 'plugin', 'skills', 'skill-creator');
|
|
434
|
+
const checkerDir = path.join(repoRoot, 'plugin', 'skills', 'skill-checker');
|
|
435
|
+
|
|
436
|
+
let script, scriptArgs = [];
|
|
437
|
+
switch (verb) {
|
|
438
|
+
case 'create-skill': {
|
|
439
|
+
// Usage: kushi create-skill <name> [--type <t>] [--description "<d>"] [--force]
|
|
440
|
+
const name = rest.find((a) => !a.startsWith('-'));
|
|
441
|
+
if (!name) {
|
|
442
|
+
console.error('Usage: kushi-agents create-skill <name> --type <pull|writer|orchestrator|other> --description "USE WHEN ... DO NOT USE FOR ..."');
|
|
443
|
+
process.exit(1);
|
|
444
|
+
}
|
|
445
|
+
const type = pickFlag(rest, '--type') || 'other';
|
|
446
|
+
const desc = pickFlag(rest, '--description') || `USE WHEN ${name} is invoked. DO NOT USE FOR unrelated tasks.`;
|
|
447
|
+
script = path.join(creatorDir, 'scaffold.ps1');
|
|
448
|
+
scriptArgs = ['-Name', name, '-Type', type, '-Description', desc];
|
|
449
|
+
if (rest.includes('--force')) scriptArgs.push('-Force');
|
|
450
|
+
if (rest.includes('--dry-run')) scriptArgs.push('-DryRun');
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
case 'check-skill': {
|
|
454
|
+
// Usage: kushi check-skill <name> | --all [--retrofit] [--apply]
|
|
455
|
+
script = path.join(checkerDir, 'check-skill.ps1');
|
|
456
|
+
const allFlag = rest.includes('--all') || rest.includes('-All');
|
|
457
|
+
const name = rest.find((a) => !a.startsWith('-'));
|
|
458
|
+
if (allFlag) scriptArgs.push('-All');
|
|
459
|
+
else if (name) scriptArgs.push('-Skill', name);
|
|
460
|
+
else {
|
|
461
|
+
console.error('Usage: kushi-agents check-skill <name> | --all [--retrofit] [--apply] [--dry-run]');
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
if (rest.includes('--retrofit')) scriptArgs.push('-Retrofit');
|
|
465
|
+
if (rest.includes('--apply')) scriptArgs.push('-Apply');
|
|
466
|
+
if (rest.includes('--dry-run')) scriptArgs.push('-DryRun');
|
|
467
|
+
if (rest.includes('--json')) scriptArgs.push('-Json');
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
case 'optimize-description': {
|
|
471
|
+
// Usage: kushi optimize-description <skill>
|
|
472
|
+
const name = rest.find((a) => !a.startsWith('-'));
|
|
473
|
+
if (!name) {
|
|
474
|
+
console.error('Usage: kushi-agents optimize-description <skill>');
|
|
475
|
+
process.exit(1);
|
|
476
|
+
}
|
|
477
|
+
script = path.join(checkerDir, 'check-skill.ps1');
|
|
478
|
+
scriptArgs = ['-Skill', name, '-OptimizeDescription'];
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
case 'review-evals': {
|
|
482
|
+
// Usage: kushi review-evals <skill>
|
|
483
|
+
const name = rest.find((a) => !a.startsWith('-'));
|
|
484
|
+
if (!name) {
|
|
485
|
+
console.error('Usage: kushi-agents review-evals <skill>');
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
script = path.join(checkerDir, 'check-skill.ps1');
|
|
489
|
+
scriptArgs = ['-Skill', name, '-Review'];
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
default:
|
|
493
|
+
console.error(`Unknown skill verb: ${verb}`);
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const result = spawnSync('pwsh', ['-NoProfile', '-File', script, ...scriptArgs], { stdio: 'inherit' });
|
|
498
|
+
process.exit(result.status ?? 1);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function dispatchLint(project) {
|
|
502
|
+
const { spawn } = await import('node:child_process');
|
|
503
|
+
const { resolve } = await import('node:path');
|
|
504
|
+
const { fileURLToPath } = await import('node:url');
|
|
505
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
506
|
+
const scriptPath = resolve(__dirname, '..', 'plugin', 'skills', 'lint-state', 'lint.ps1');
|
|
507
|
+
|
|
508
|
+
const cwd = process.cwd();
|
|
509
|
+
const { readdirSync, existsSync } = await import('node:fs');
|
|
510
|
+
let stateDir = '';
|
|
511
|
+
|
|
512
|
+
const evidenceDir = resolve(cwd, project, 'Evidence');
|
|
513
|
+
if (existsSync(evidenceDir)) {
|
|
514
|
+
const aliases = readdirSync(evidenceDir, { withFileTypes: true })
|
|
515
|
+
.filter(d => d.isDirectory() && !d.name.startsWith('_'));
|
|
516
|
+
for (const alias of aliases) {
|
|
517
|
+
const candidate = resolve(evidenceDir, alias.name, 'State');
|
|
518
|
+
if (existsSync(candidate)) { stateDir = candidate; break; }
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (!stateDir) {
|
|
523
|
+
const direct = resolve(cwd, project, 'State');
|
|
524
|
+
if (existsSync(direct)) { stateDir = direct; }
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!stateDir) {
|
|
528
|
+
console.error(`\n Could not find State/ directory for project '${project}'.`);
|
|
529
|
+
console.error(` Looked in: ${evidenceDir}/*/State/ and ${resolve(cwd, project, 'State')}/`);
|
|
530
|
+
console.error(` Run 'kushi state ${project}' first to build State/.\n`);
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const child = spawn('pwsh', ['-NoProfile', '-File', scriptPath, '-StateDir', stateDir], {
|
|
535
|
+
stdio: 'inherit',
|
|
536
|
+
cwd: resolve(__dirname, '..'),
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
return new Promise((res, rej) => {
|
|
540
|
+
child.on('close', (code) => {
|
|
541
|
+
if (code !== 0) rej(new Error(`lint-state exited with code ${code}`));
|
|
542
|
+
else res();
|
|
543
|
+
});
|
|
544
|
+
child.on('error', rej);
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function pickFlag(args, flag) {
|
|
549
|
+
const idx = args.indexOf(flag);
|
|
550
|
+
if (idx !== -1 && idx + 1 < args.length) return args[idx + 1];
|
|
551
|
+
const prefix = flag + '=';
|
|
552
|
+
const m = args.find((a) => a.startsWith(prefix));
|
|
553
|
+
return m ? m.slice(prefix.length) : undefined;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// ── v5.2.0 dispatch helpers ──────────────────────────────────────────────────
|
|
557
|
+
|
|
558
|
+
async function dispatchHooks(sub, project, extra) {
|
|
559
|
+
const { spawn } = await import('node:child_process');
|
|
560
|
+
const { resolve } = await import('node:path');
|
|
561
|
+
const { fileURLToPath } = await import('node:url');
|
|
562
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
563
|
+
const invokeHooks = resolve(__dirname, '..', 'plugin', 'skills', '_shared', 'Invoke-Hooks.ps1');
|
|
564
|
+
|
|
565
|
+
if (sub === 'list') {
|
|
566
|
+
// List hooks from .kushi/hooks.yml
|
|
567
|
+
const { existsSync, readFileSync } = await import('node:fs');
|
|
568
|
+
const hooksYml = resolve(process.cwd(), project, '.kushi', 'hooks.yml');
|
|
569
|
+
if (!existsSync(hooksYml)) {
|
|
570
|
+
console.log(`\n No hooks configured for '${project}'. Create ${hooksYml} to add hooks.\n`);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
console.log(`\n Hooks for '${project}' (${hooksYml}):\n`);
|
|
574
|
+
console.log(readFileSync(hooksYml, 'utf8'));
|
|
575
|
+
} else if (sub === 'test') {
|
|
576
|
+
const event = extra[0] || 'post-pull';
|
|
577
|
+
const script = `
|
|
578
|
+
$payload = @{ project = '${project}'; source = 'test'; success = $true; duration_ms = 0; event = '${event}' }
|
|
579
|
+
& '${invokeHooks.replace(/\\/g, '\\\\')}' -ProjectRoot '${resolve(process.cwd(), project).replace(/\\/g, '\\\\')}' -Event '${event}' -Payload $payload
|
|
580
|
+
`;
|
|
581
|
+
const child = spawn('pwsh', ['-NoProfile', '-Command', script], { stdio: 'inherit' });
|
|
582
|
+
return new Promise((res, rej) => {
|
|
583
|
+
child.on('close', (code) => { if (code !== 0) rej(new Error(`hooks test exited ${code}`)); else res(); });
|
|
584
|
+
child.on('error', rej);
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
async function dispatchExplain(topic) {
|
|
590
|
+
const { resolve } = await import('node:path');
|
|
591
|
+
const { existsSync, readFileSync } = await import('node:fs');
|
|
592
|
+
const { fileURLToPath } = await import('node:url');
|
|
593
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
594
|
+
const repoRoot = resolve(__dirname, '..');
|
|
595
|
+
const instructionsDir = resolve(repoRoot, 'plugin', 'instructions');
|
|
596
|
+
|
|
597
|
+
const topicMap = {
|
|
598
|
+
contradictions: 'living-wiki.instructions.md',
|
|
599
|
+
conflicts: 'living-wiki.instructions.md',
|
|
600
|
+
refresh: 'parallel-execution.instructions.md',
|
|
601
|
+
pull: 'parallel-execution.instructions.md',
|
|
602
|
+
state: 'living-wiki.instructions.md',
|
|
603
|
+
'build-state': 'living-wiki.instructions.md',
|
|
604
|
+
wiki: 'living-wiki.instructions.md',
|
|
605
|
+
hooks: 'hooks.instructions.md',
|
|
606
|
+
events: 'hooks.instructions.md',
|
|
607
|
+
webhooks: 'hooks.instructions.md',
|
|
608
|
+
parallel: 'parallel-execution.instructions.md',
|
|
609
|
+
workers: 'parallel-execution.instructions.md',
|
|
610
|
+
otel: 'otel.instructions.md',
|
|
611
|
+
telemetry: 'otel.instructions.md',
|
|
612
|
+
tracing: 'otel.instructions.md',
|
|
613
|
+
csc: 'comprehensive-structured-capture.instructions.md',
|
|
614
|
+
capture: 'comprehensive-structured-capture.instructions.md',
|
|
615
|
+
graph: 'entity-graph.instructions.md',
|
|
616
|
+
entities: 'entity-graph.instructions.md',
|
|
617
|
+
workiq: 'workiq-only.instructions.md',
|
|
618
|
+
schema: 'schema-evolve.instructions.md',
|
|
619
|
+
conventions: 'schema-evolve.instructions.md',
|
|
620
|
+
remember: 'schema-evolve.instructions.md',
|
|
621
|
+
install: 'multi-host-install.instructions.md',
|
|
622
|
+
setup: 'multi-host-install.instructions.md',
|
|
623
|
+
evals: 'skill-evals.instructions.md',
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
const key = Object.keys(topicMap).find(k => topic.toLowerCase().includes(k));
|
|
627
|
+
if (!key) {
|
|
628
|
+
console.log(`\n Topic not found: "${topic}"\n`);
|
|
629
|
+
console.log(' Available topics:', Object.keys(topicMap).filter((v, i, a) => a.indexOf(v) === i).join(', '));
|
|
630
|
+
console.log('');
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const docFile = resolve(instructionsDir, topicMap[key]);
|
|
635
|
+
if (!existsSync(docFile)) {
|
|
636
|
+
console.error(` Doctrine file missing: ${topicMap[key]}`);
|
|
637
|
+
process.exit(1);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const content = readFileSync(docFile, 'utf8');
|
|
641
|
+
const lines = content.split('\n').slice(0, 40);
|
|
642
|
+
console.log(`\n 📖 Topic: ${key} → ${topicMap[key]}\n`);
|
|
643
|
+
console.log(lines.join('\n'));
|
|
644
|
+
console.log(`\n ... (full doctrine at: plugin/instructions/${topicMap[key]})\n`);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async function dispatchRemember(rule) {
|
|
648
|
+
console.log(`\n ✅ Rule noted: "${rule}"`);
|
|
649
|
+
console.log(' To persist this rule, run schema-evolve from within a project context:');
|
|
650
|
+
console.log(' @Kushi remember ' + rule);
|
|
651
|
+
console.log(' This will write to Evidence/<alias>/State/CLAUDE.md\n');
|
|
652
|
+
}
|