flanders 0.6.0 → 0.7.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/README.md +245 -0
- package/lib/Flanders.d.ts +2 -1
- package/lib/Flanders.js +7 -0
- package/lib/cli.js +10 -1
- package/lib/commands/Implement.d.ts +3 -1
- package/lib/commands/Implement.js +32 -23
- package/lib/commands/Install.d.ts +0 -1
- package/lib/commands/Install.js +131 -114
- package/lib/commands/Update.d.ts +19 -0
- package/lib/commands/Update.js +90 -0
- package/lib/commands/skillArtifacts.d.ts +18 -0
- package/lib/commands/skillArtifacts.js +93 -0
- package/lib/contexts.d.ts +1 -0
- package/lib/plan/PlanFile.d.ts +8 -5
- package/lib/plan/PlanFile.js +85 -16
- package/lib/prompts/prompts.d.ts +11 -1
- package/lib/prompts/prompts.js +42 -5
- package/lib/prompts/skills.d.ts +1 -1
- package/lib/prompts/skills.js +48 -21
- package/lib/ui/BottomBlock.d.ts +7 -3
- package/lib/ui/BottomBlock.js +47 -11
- package/lib/ui/PromptHelper.d.ts +2 -0
- package/lib/ui/PromptHelper.js +11 -2
- package/lib/ui/formatters.d.ts +8 -3
- package/lib/ui/formatters.js +67 -39
- package/lib/voiceVariants.d.ts +15 -0
- package/lib/voiceVariants.js +141 -0
- package/lib/workspace/FlandersConfig.d.ts +4 -3
- package/lib/workspace/FlandersConfig.js +18 -0
- package/lib/workspace/Workspace.d.ts +2 -0
- package/lib/workspace/Workspace.js +3 -1
- package/package.json +1 -1
package/lib/commands/Install.js
CHANGED
|
@@ -1,33 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Install = void 0;
|
|
4
|
-
exports.stripYamlFrontmatter = stripYamlFrontmatter;
|
|
5
4
|
exports.parseInstallFlags = parseInstallFlags;
|
|
6
5
|
const FlandersConfig_1 = require("../workspace/FlandersConfig");
|
|
7
6
|
const PromptHelper_1 = require("../ui/PromptHelper");
|
|
8
|
-
const
|
|
9
|
-
const skills_1 = require("../prompts/skills");
|
|
7
|
+
const skillArtifacts_1 = require("./skillArtifacts");
|
|
10
8
|
const InstallModelProbe_1 = require("./InstallModelProbe");
|
|
11
|
-
const SKILLS = [
|
|
12
|
-
{ name: "flanders-spec", body: skills_1.specSkillBody },
|
|
13
|
-
{ name: "flanders-plan", body: skills_1.planSkillBody },
|
|
14
|
-
{ name: "flanders-work", body: skills_1.workSkillBody }
|
|
15
|
-
];
|
|
16
|
-
function stripYamlFrontmatter(body) {
|
|
17
|
-
if (!body.startsWith("---\n") && !body.startsWith("---\r\n")) {
|
|
18
|
-
return body;
|
|
19
|
-
}
|
|
20
|
-
const newlineAfterOpener = body.indexOf("\n") + 1;
|
|
21
|
-
const closerIndex = body.indexOf("\n---\n", newlineAfterOpener);
|
|
22
|
-
if (closerIndex === -1) {
|
|
23
|
-
const closerCrlf = body.indexOf("\n---\r\n", newlineAfterOpener);
|
|
24
|
-
if (closerCrlf === -1) {
|
|
25
|
-
return body;
|
|
26
|
-
}
|
|
27
|
-
return body.slice(closerCrlf + "\n---\r\n".length);
|
|
28
|
-
}
|
|
29
|
-
return body.slice(closerIndex + "\n---\n".length);
|
|
30
|
-
}
|
|
31
9
|
async function promptChoice(ask, args) {
|
|
32
10
|
try {
|
|
33
11
|
return await (0, PromptHelper_1.askChoice)(ask, args);
|
|
@@ -298,14 +276,29 @@ class Install {
|
|
|
298
276
|
result() {
|
|
299
277
|
return this._runPromise;
|
|
300
278
|
}
|
|
301
|
-
async _resolveCuratedChoice(headerLabel, question, curatedValues, defaultLabel, customLabel, customPlaceholder, contexts) {
|
|
279
|
+
async _resolveCuratedChoice(headerLabel, question, curatedValues, defaultLabel, customLabel, customPlaceholder, contexts, preselect) {
|
|
302
280
|
const options = curatedValues.map(v => ({ label: v }));
|
|
303
281
|
options.push({ label: defaultLabel });
|
|
304
282
|
options.push({ label: customLabel });
|
|
283
|
+
let preselectLabel;
|
|
284
|
+
let customDefault;
|
|
285
|
+
if (preselect !== undefined) {
|
|
286
|
+
if (preselect === "") {
|
|
287
|
+
preselectLabel = defaultLabel;
|
|
288
|
+
}
|
|
289
|
+
else if (curatedValues.includes(preselect)) {
|
|
290
|
+
preselectLabel = preselect;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
preselectLabel = customLabel;
|
|
294
|
+
customDefault = preselect;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
305
297
|
const option = await promptChoice(contexts.ask, {
|
|
306
298
|
header: headerLabel,
|
|
307
299
|
question,
|
|
308
|
-
options
|
|
300
|
+
options,
|
|
301
|
+
defaultLabel: preselectLabel
|
|
309
302
|
});
|
|
310
303
|
if (!option) {
|
|
311
304
|
return null;
|
|
@@ -319,7 +312,8 @@ class Install {
|
|
|
319
312
|
if (option.label === customLabel) {
|
|
320
313
|
const text = await promptText(contexts.ask, {
|
|
321
314
|
question,
|
|
322
|
-
placeholder: customPlaceholder
|
|
315
|
+
placeholder: customPlaceholder,
|
|
316
|
+
default: customDefault
|
|
323
317
|
});
|
|
324
318
|
if (text === null) {
|
|
325
319
|
return null;
|
|
@@ -331,8 +325,35 @@ class Install {
|
|
|
331
325
|
}
|
|
332
326
|
return option.label;
|
|
333
327
|
}
|
|
334
|
-
async _resolveClaudeModel(roleLabel, headerLabel, contexts) {
|
|
328
|
+
async _resolveClaudeModel(roleLabel, headerLabel, contexts, preselect) {
|
|
335
329
|
const question = `Which model should ${roleLabel} use?`;
|
|
330
|
+
let topDefault;
|
|
331
|
+
let customDefault;
|
|
332
|
+
let preselectedFamily;
|
|
333
|
+
let preselectedEntryLabel;
|
|
334
|
+
if (preselect !== undefined) {
|
|
335
|
+
if (preselect === "") {
|
|
336
|
+
topDefault = "default configured model";
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
const alias = CLAUDE_CROSS_FAMILY_ALIASES.find(a => a.value === preselect);
|
|
340
|
+
if (alias) {
|
|
341
|
+
topDefault = alias.label;
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
const family = CLAUDE_MODEL_FAMILIES.find(f => f.entries.some(e => e.value === preselect));
|
|
345
|
+
if (family) {
|
|
346
|
+
topDefault = family.family;
|
|
347
|
+
preselectedFamily = family;
|
|
348
|
+
preselectedEntryLabel = family.entries.find(e => e.value === preselect).label;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
topDefault = "enter a custom value…";
|
|
352
|
+
customDefault = preselect;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
336
357
|
topLevel: for (;;) {
|
|
337
358
|
const topOptions = [
|
|
338
359
|
...CLAUDE_MODEL_FAMILIES.map(family => ({ label: family.family })),
|
|
@@ -343,7 +364,8 @@ class Install {
|
|
|
343
364
|
const top = await promptChoice(contexts.ask, {
|
|
344
365
|
header: headerLabel,
|
|
345
366
|
question,
|
|
346
|
-
options: topOptions
|
|
367
|
+
options: topOptions,
|
|
368
|
+
defaultLabel: topDefault
|
|
347
369
|
});
|
|
348
370
|
if (!top) {
|
|
349
371
|
return null;
|
|
@@ -357,7 +379,8 @@ class Install {
|
|
|
357
379
|
if (top.label === "enter a custom value…") {
|
|
358
380
|
const text = await promptText(contexts.ask, {
|
|
359
381
|
question,
|
|
360
|
-
placeholder: "leave empty for the default configured model"
|
|
382
|
+
placeholder: "leave empty for the default configured model",
|
|
383
|
+
default: customDefault
|
|
361
384
|
});
|
|
362
385
|
if (text === null) {
|
|
363
386
|
return null;
|
|
@@ -378,7 +401,8 @@ class Install {
|
|
|
378
401
|
const choice = await promptChoice(contexts.ask, {
|
|
379
402
|
header: headerLabel,
|
|
380
403
|
question: `Which ${family.family} model should ${roleLabel} use?`,
|
|
381
|
-
options: familyOptions
|
|
404
|
+
options: familyOptions,
|
|
405
|
+
defaultLabel: family === preselectedFamily ? preselectedEntryLabel : undefined
|
|
382
406
|
});
|
|
383
407
|
if (!choice) {
|
|
384
408
|
return null;
|
|
@@ -394,7 +418,7 @@ class Install {
|
|
|
394
418
|
}
|
|
395
419
|
}
|
|
396
420
|
}
|
|
397
|
-
async _resolveRoleModel(roleLabel, headerLabel, tool, suppliedModel, contexts) {
|
|
421
|
+
async _resolveRoleModel(roleLabel, headerLabel, tool, suppliedModel, contexts, preselect) {
|
|
398
422
|
if (suppliedModel !== undefined) {
|
|
399
423
|
return suppliedModel;
|
|
400
424
|
}
|
|
@@ -402,7 +426,7 @@ class Install {
|
|
|
402
426
|
return null;
|
|
403
427
|
}
|
|
404
428
|
if (tool === "claude") {
|
|
405
|
-
return await this._resolveClaudeModel(roleLabel, headerLabel, contexts);
|
|
429
|
+
return await this._resolveClaudeModel(roleLabel, headerLabel, contexts, preselect);
|
|
406
430
|
}
|
|
407
431
|
if (!this._modelProbeCache.has(tool)) {
|
|
408
432
|
const result = await (0, InstallModelProbe_1.probeModelList)(contexts.script);
|
|
@@ -418,10 +442,20 @@ class Install {
|
|
|
418
442
|
if (probeResult.kind === "list") {
|
|
419
443
|
const options = probeResult.models.map(m => ({ label: m }));
|
|
420
444
|
options.push({ label: "default configured model" });
|
|
445
|
+
let modelDefaultLabel;
|
|
446
|
+
if (preselect !== undefined) {
|
|
447
|
+
if (preselect === "") {
|
|
448
|
+
modelDefaultLabel = "default configured model";
|
|
449
|
+
}
|
|
450
|
+
else if (probeResult.models.includes(preselect)) {
|
|
451
|
+
modelDefaultLabel = preselect;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
421
454
|
const option = await promptChoice(contexts.ask, {
|
|
422
455
|
header: headerLabel,
|
|
423
456
|
question: `Which model should ${roleLabel} use?`,
|
|
424
|
-
options
|
|
457
|
+
options,
|
|
458
|
+
defaultLabel: modelDefaultLabel
|
|
425
459
|
});
|
|
426
460
|
if (!option) {
|
|
427
461
|
return null;
|
|
@@ -433,7 +467,8 @@ class Install {
|
|
|
433
467
|
}
|
|
434
468
|
const text = await promptText(contexts.ask, {
|
|
435
469
|
question: `Which model should ${roleLabel} use?`,
|
|
436
|
-
placeholder: "leave empty for the default configured model"
|
|
470
|
+
placeholder: "leave empty for the default configured model",
|
|
471
|
+
default: preselect
|
|
437
472
|
});
|
|
438
473
|
if (text === null) {
|
|
439
474
|
return null;
|
|
@@ -443,7 +478,7 @@ class Install {
|
|
|
443
478
|
}
|
|
444
479
|
return text;
|
|
445
480
|
}
|
|
446
|
-
async _resolveRoleEffort(roleLabel, headerLabel, tool, suppliedEffort, contexts) {
|
|
481
|
+
async _resolveRoleEffort(roleLabel, headerLabel, tool, suppliedEffort, contexts, preselect) {
|
|
447
482
|
if (suppliedEffort !== undefined) {
|
|
448
483
|
return suppliedEffort;
|
|
449
484
|
}
|
|
@@ -453,10 +488,15 @@ class Install {
|
|
|
453
488
|
if (tool === "codex") {
|
|
454
489
|
const options = CODEX_EFFORT_LEVELS.map(e => ({ label: e }));
|
|
455
490
|
options.push({ label: "default configured effort" });
|
|
491
|
+
let effortDefaultLabel;
|
|
492
|
+
if (preselect !== undefined) {
|
|
493
|
+
effortDefaultLabel = preselect === "" ? "default configured effort" : preselect;
|
|
494
|
+
}
|
|
456
495
|
const option = await promptChoice(contexts.ask, {
|
|
457
496
|
header: headerLabel,
|
|
458
497
|
question: `What effort level should ${roleLabel} use?`,
|
|
459
|
-
options
|
|
498
|
+
options,
|
|
499
|
+
defaultLabel: effortDefaultLabel
|
|
460
500
|
});
|
|
461
501
|
if (!option) {
|
|
462
502
|
return null;
|
|
@@ -466,9 +506,9 @@ class Install {
|
|
|
466
506
|
}
|
|
467
507
|
return option.label === "default configured effort" ? "" : option.label;
|
|
468
508
|
}
|
|
469
|
-
return await this._resolveCuratedChoice(headerLabel, `What effort level should ${roleLabel} use?`, CLAUDE_EFFORT_LEVELS, "default configured effort", "enter a custom value…", "leave empty for the default configured effort", contexts);
|
|
509
|
+
return await this._resolveCuratedChoice(headerLabel, `What effort level should ${roleLabel} use?`, CLAUDE_EFFORT_LEVELS, "default configured effort", "enter a custom value…", "leave empty for the default configured effort", contexts, preselect);
|
|
470
510
|
}
|
|
471
|
-
async _resolveReviewer(idx, supplied, contexts) {
|
|
511
|
+
async _resolveReviewer(idx, supplied, contexts, storedReviewer) {
|
|
472
512
|
const ordinal = idx === 1 ? "" : ` ${idx}`;
|
|
473
513
|
const roleLabel = `reviewer${ordinal}`;
|
|
474
514
|
let tool;
|
|
@@ -485,7 +525,8 @@ class Install {
|
|
|
485
525
|
options: [
|
|
486
526
|
{ label: "claude", description: "Use Claude Code" },
|
|
487
527
|
{ label: "codex", description: "Use Codex CLI" }
|
|
488
|
-
]
|
|
528
|
+
],
|
|
529
|
+
defaultLabel: storedReviewer === null || storedReviewer === void 0 ? void 0 : storedReviewer.tool
|
|
489
530
|
});
|
|
490
531
|
if (!option) {
|
|
491
532
|
return null;
|
|
@@ -495,17 +536,18 @@ class Install {
|
|
|
495
536
|
}
|
|
496
537
|
tool = option.label;
|
|
497
538
|
}
|
|
498
|
-
const model = await this._resolveRoleModel(roleLabel, `Reviewer${ordinal} model`, tool, supplied === null || supplied === void 0 ? void 0 : supplied.model, contexts);
|
|
539
|
+
const model = await this._resolveRoleModel(roleLabel, `Reviewer${ordinal} model`, tool, supplied === null || supplied === void 0 ? void 0 : supplied.model, contexts, storedReviewer === null || storedReviewer === void 0 ? void 0 : storedReviewer.model);
|
|
499
540
|
if (model === null) {
|
|
500
541
|
return null;
|
|
501
542
|
}
|
|
502
|
-
const effort = await this._resolveRoleEffort(roleLabel, `Reviewer${ordinal} effort`, tool, supplied === null || supplied === void 0 ? void 0 : supplied.effort, contexts);
|
|
543
|
+
const effort = await this._resolveRoleEffort(roleLabel, `Reviewer${ordinal} effort`, tool, supplied === null || supplied === void 0 ? void 0 : supplied.effort, contexts, storedReviewer === null || storedReviewer === void 0 ? void 0 : storedReviewer.effort);
|
|
503
544
|
if (effort === null) {
|
|
504
545
|
return null;
|
|
505
546
|
}
|
|
506
547
|
return { tool, model, effort };
|
|
507
548
|
}
|
|
508
549
|
async _run(rawArgs, options, contexts) {
|
|
550
|
+
var _a;
|
|
509
551
|
try {
|
|
510
552
|
const parsed = parseInstallFlags(rawArgs);
|
|
511
553
|
if (!parsed.ok) {
|
|
@@ -523,7 +565,7 @@ class Install {
|
|
|
523
565
|
}
|
|
524
566
|
const option = await promptChoice(contexts.ask, {
|
|
525
567
|
header: "Skills tool",
|
|
526
|
-
question: "Which AI tool(s) should the skills be installed for?",
|
|
568
|
+
question: "Which AI tool(s) should the skills be installed for, neighbor?",
|
|
527
569
|
options: [
|
|
528
570
|
{ label: "claude", description: "Install skills for Claude Code" },
|
|
529
571
|
{ label: "codex", description: "Install skills for Codex CLI" },
|
|
@@ -576,6 +618,14 @@ class Install {
|
|
|
576
618
|
}
|
|
577
619
|
mode = option.label;
|
|
578
620
|
}
|
|
621
|
+
const storedConfig = await (0, FlandersConfig_1.readScope)(contexts.fs, {
|
|
622
|
+
scope: mode,
|
|
623
|
+
projectRoot: options.projectRoot,
|
|
624
|
+
homeDir: contexts.platform.homedir()
|
|
625
|
+
});
|
|
626
|
+
if (this._disposed) {
|
|
627
|
+
return 1;
|
|
628
|
+
}
|
|
579
629
|
let workerTool;
|
|
580
630
|
if (answers.workerTool !== undefined) {
|
|
581
631
|
workerTool = answers.workerTool;
|
|
@@ -585,12 +635,13 @@ class Install {
|
|
|
585
635
|
return 1;
|
|
586
636
|
}
|
|
587
637
|
const option = await promptChoice(contexts.ask, {
|
|
588
|
-
header: "Worker tool",
|
|
638
|
+
header: "Worker tool, neighborino",
|
|
589
639
|
question: "Which AI tool should the worker use?",
|
|
590
640
|
options: [
|
|
591
641
|
{ label: "claude", description: "Use Claude Code" },
|
|
592
642
|
{ label: "codex", description: "Use Codex CLI" }
|
|
593
|
-
]
|
|
643
|
+
],
|
|
644
|
+
defaultLabel: storedConfig === null || storedConfig === void 0 ? void 0 : storedConfig.worker.tool
|
|
594
645
|
});
|
|
595
646
|
if (!option) {
|
|
596
647
|
return 1;
|
|
@@ -600,19 +651,20 @@ class Install {
|
|
|
600
651
|
}
|
|
601
652
|
workerTool = option.label;
|
|
602
653
|
}
|
|
603
|
-
const workerModel = await this._resolveRoleModel("the worker", "Worker model", workerTool, answers.workerModel, contexts);
|
|
654
|
+
const workerModel = await this._resolveRoleModel("the worker", "Worker model", workerTool, answers.workerModel, contexts, storedConfig === null || storedConfig === void 0 ? void 0 : storedConfig.worker.model);
|
|
604
655
|
if (workerModel === null) {
|
|
605
656
|
return 1;
|
|
606
657
|
}
|
|
607
|
-
const workerEffort = await this._resolveRoleEffort("the worker", "Worker effort", workerTool, answers.workerEffort, contexts);
|
|
658
|
+
const workerEffort = await this._resolveRoleEffort("the worker", "Worker effort", workerTool, answers.workerEffort, contexts, storedConfig === null || storedConfig === void 0 ? void 0 : storedConfig.worker.effort);
|
|
608
659
|
if (workerEffort === null) {
|
|
609
660
|
return 1;
|
|
610
661
|
}
|
|
611
662
|
const suppliedReviewers = answers.reviewers;
|
|
663
|
+
const storedReviewers = storedConfig === null || storedConfig === void 0 ? void 0 : storedConfig.reviewers;
|
|
612
664
|
const reviewers = [];
|
|
613
665
|
if (suppliedReviewers && suppliedReviewers.length > 0) {
|
|
614
666
|
for (let i = 0; i < suppliedReviewers.length; i++) {
|
|
615
|
-
const reviewer = await this._resolveReviewer(i + 1, suppliedReviewers[i], contexts);
|
|
667
|
+
const reviewer = await this._resolveReviewer(i + 1, suppliedReviewers[i], contexts, storedReviewers === null || storedReviewers === void 0 ? void 0 : storedReviewers[i]);
|
|
616
668
|
if (reviewer === null) {
|
|
617
669
|
return 1;
|
|
618
670
|
}
|
|
@@ -622,7 +674,7 @@ class Install {
|
|
|
622
674
|
else {
|
|
623
675
|
let idx = 1;
|
|
624
676
|
for (;;) {
|
|
625
|
-
const reviewer = await this._resolveReviewer(idx, undefined, contexts);
|
|
677
|
+
const reviewer = await this._resolveReviewer(idx, undefined, contexts, storedReviewers === null || storedReviewers === void 0 ? void 0 : storedReviewers[idx - 1]);
|
|
626
678
|
if (reviewer === null) {
|
|
627
679
|
return 1;
|
|
628
680
|
}
|
|
@@ -630,13 +682,17 @@ class Install {
|
|
|
630
682
|
if (this._disposed) {
|
|
631
683
|
return 1;
|
|
632
684
|
}
|
|
685
|
+
const moreDefault = storedReviewers !== undefined
|
|
686
|
+
? (idx < storedReviewers.length ? "yes" : "no")
|
|
687
|
+
: undefined;
|
|
633
688
|
const more = await promptChoice(contexts.ask, {
|
|
634
689
|
header: "Configure another reviewer?",
|
|
635
|
-
question: "
|
|
690
|
+
question: "Okely-dokely — care to configure another reviewer?",
|
|
636
691
|
options: [
|
|
637
692
|
{ label: "no", description: "Stop adding reviewers" },
|
|
638
693
|
{ label: "yes", description: "Configure another reviewer in the ordered list" }
|
|
639
|
-
]
|
|
694
|
+
],
|
|
695
|
+
defaultLabel: moreDefault
|
|
640
696
|
});
|
|
641
697
|
if (!more) {
|
|
642
698
|
return 1;
|
|
@@ -665,6 +721,9 @@ class Install {
|
|
|
665
721
|
}
|
|
666
722
|
else {
|
|
667
723
|
const reviewerCount = reviewers.length;
|
|
724
|
+
const minimumDefault = storedConfig !== null
|
|
725
|
+
? Math.min(storedConfig.minimumReviews, reviewerCount)
|
|
726
|
+
: reviewerCount;
|
|
668
727
|
if (answers.reviewerMinimum !== undefined) {
|
|
669
728
|
minimumReviews = answers.reviewerMinimum;
|
|
670
729
|
}
|
|
@@ -676,7 +735,8 @@ class Install {
|
|
|
676
735
|
while (chosen === null) {
|
|
677
736
|
const entry = await promptText(contexts.ask, {
|
|
678
737
|
question: "Minimum reviewers that must run to a verdict in each review round",
|
|
679
|
-
placeholder: `1-${reviewerCount}, empty for ${
|
|
738
|
+
placeholder: `1-${reviewerCount}, empty for ${minimumDefault}`,
|
|
739
|
+
default: String(minimumDefault)
|
|
680
740
|
});
|
|
681
741
|
if (entry === null) {
|
|
682
742
|
return 1;
|
|
@@ -685,17 +745,12 @@ class Install {
|
|
|
685
745
|
return 1;
|
|
686
746
|
}
|
|
687
747
|
const trimmed = entry.trim();
|
|
688
|
-
|
|
689
|
-
|
|
748
|
+
const parsed = Number(trimmed);
|
|
749
|
+
if (/^\d+$/.test(trimmed) && parsed >= 1 && parsed <= reviewerCount) {
|
|
750
|
+
chosen = parsed;
|
|
690
751
|
}
|
|
691
752
|
else {
|
|
692
|
-
|
|
693
|
-
if (/^\d+$/.test(trimmed) && parsed >= 1 && parsed <= reviewerCount) {
|
|
694
|
-
chosen = parsed;
|
|
695
|
-
}
|
|
696
|
-
else {
|
|
697
|
-
contexts.output.write(`Enter an integer between 1 and ${reviewerCount}, or leave empty for ${reviewerCount}.\n`);
|
|
698
|
-
}
|
|
753
|
+
contexts.output.write(`Whoopsie — enter an integer between 1 and ${reviewerCount}, or leave empty for ${minimumDefault}.\n`);
|
|
699
754
|
}
|
|
700
755
|
}
|
|
701
756
|
minimumReviews = chosen;
|
|
@@ -715,13 +770,15 @@ class Install {
|
|
|
715
770
|
const reviewer = reviewers[i];
|
|
716
771
|
const modelLabel = reviewer.model === "" ? "default configured model" : reviewer.model;
|
|
717
772
|
const effortLabel = reviewer.effort === "" ? "default configured effort" : reviewer.effort;
|
|
773
|
+
const optionalDefault = ((_a = storedReviewers === null || storedReviewers === void 0 ? void 0 : storedReviewers[i]) === null || _a === void 0 ? void 0 : _a.optional) === true ? "yes" : "no";
|
|
718
774
|
const optionalOption = await promptChoice(contexts.ask, {
|
|
719
775
|
header: `Reviewer ${i + 1} optional`,
|
|
720
776
|
question: `Is reviewer ${i + 1} (${reviewer.tool} · ${modelLabel} · ${effortLabel}) optional?`,
|
|
721
777
|
options: [
|
|
722
778
|
{ label: "no", description: "Required — always waits out its rate-limit waits; the round never completes without its verdict" },
|
|
723
779
|
{ label: "yes", description: "Optional — reviews exactly like a required reviewer; the only difference is the round abandons it while it is in a rate-limit wait, once every required reviewer is in and the minimum is met" }
|
|
724
|
-
]
|
|
780
|
+
],
|
|
781
|
+
defaultLabel: optionalDefault
|
|
725
782
|
});
|
|
726
783
|
if (!optionalOption) {
|
|
727
784
|
return 1;
|
|
@@ -742,63 +799,23 @@ class Install {
|
|
|
742
799
|
const scopeRoot = mode === "global"
|
|
743
800
|
? contexts.platform.homedir()
|
|
744
801
|
: options.projectRoot;
|
|
745
|
-
for (const skill of SKILLS) {
|
|
746
|
-
if (!skill.body) {
|
|
747
|
-
contexts.output.writeError(`Skill "${skill.name}" has no content.\n`);
|
|
748
|
-
return 1;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
const writeClaude = skillsTool === "claude" || skillsTool === "both";
|
|
752
|
-
const writeCodex = skillsTool === "codex" || skillsTool === "both";
|
|
753
802
|
const writtenPaths = [];
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
contexts.output.writeError(
|
|
766
|
-
return 1;
|
|
803
|
+
const tools = [];
|
|
804
|
+
if (skillsTool === "claude" || skillsTool === "both") {
|
|
805
|
+
tools.push("claude");
|
|
806
|
+
}
|
|
807
|
+
if (skillsTool === "codex" || skillsTool === "both") {
|
|
808
|
+
tools.push("codex");
|
|
809
|
+
}
|
|
810
|
+
for (const tool of tools) {
|
|
811
|
+
const result = await (0, skillArtifacts_1.writeSkillArtifacts)(contexts.fs, scopeRoot, tool, () => this._disposed);
|
|
812
|
+
if (!result.ok) {
|
|
813
|
+
if (result.diagnostic !== null) {
|
|
814
|
+
contexts.output.writeError(result.diagnostic);
|
|
767
815
|
}
|
|
768
|
-
const filePath = (0, fsUtils_1.joinPath)(skillFolder, "SKILL.md");
|
|
769
|
-
try {
|
|
770
|
-
await contexts.fs.writeFile(filePath, skill.body);
|
|
771
|
-
writtenPaths.push(filePath);
|
|
772
|
-
}
|
|
773
|
-
catch {
|
|
774
|
-
contexts.output.writeError(`Cannot write file: ${filePath}\n`);
|
|
775
|
-
return 1;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
if (writeCodex) {
|
|
780
|
-
const codexPromptsRoot = (0, fsUtils_1.joinPath)(scopeRoot, ".codex/prompts");
|
|
781
|
-
try {
|
|
782
|
-
await contexts.fs.mkdir(codexPromptsRoot, { recursive: true });
|
|
783
|
-
}
|
|
784
|
-
catch {
|
|
785
|
-
contexts.output.writeError(`Cannot create destination: ${codexPromptsRoot}\n`);
|
|
786
816
|
return 1;
|
|
787
817
|
}
|
|
788
|
-
|
|
789
|
-
if (this._disposed) {
|
|
790
|
-
return 1;
|
|
791
|
-
}
|
|
792
|
-
const filePath = (0, fsUtils_1.joinPath)(codexPromptsRoot, `${skill.name}.md`);
|
|
793
|
-
try {
|
|
794
|
-
await contexts.fs.writeFile(filePath, stripYamlFrontmatter(skill.body));
|
|
795
|
-
writtenPaths.push(filePath);
|
|
796
|
-
}
|
|
797
|
-
catch {
|
|
798
|
-
contexts.output.writeError(`Cannot write file: ${filePath}\n`);
|
|
799
|
-
return 1;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
818
|
+
writtenPaths.push(...result.writtenPaths);
|
|
802
819
|
}
|
|
803
820
|
if (this._disposed) {
|
|
804
821
|
return 1;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FsContext, OutputContext } from "../contexts";
|
|
2
|
+
import type { PlatformContext } from "../workspace/Workspace";
|
|
3
|
+
export type UpdateContexts = Readonly<{
|
|
4
|
+
fs: FsContext;
|
|
5
|
+
output: OutputContext;
|
|
6
|
+
platform: PlatformContext;
|
|
7
|
+
}>;
|
|
8
|
+
export type UpdateOptions = Readonly<{
|
|
9
|
+
projectRoot: string;
|
|
10
|
+
}>;
|
|
11
|
+
export declare class Update {
|
|
12
|
+
private _disposed;
|
|
13
|
+
private _runPromise;
|
|
14
|
+
constructor(rawArgs: readonly string[], options: UpdateOptions, contexts: UpdateContexts);
|
|
15
|
+
result(): Promise<number>;
|
|
16
|
+
private _isInstalled;
|
|
17
|
+
private _run;
|
|
18
|
+
dispose(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Update = void 0;
|
|
4
|
+
const skillArtifacts_1 = require("./skillArtifacts");
|
|
5
|
+
class Update {
|
|
6
|
+
constructor(rawArgs, options, contexts) {
|
|
7
|
+
this._disposed = false;
|
|
8
|
+
this._runPromise = this._run(rawArgs, options, contexts);
|
|
9
|
+
this._runPromise.catch(() => { });
|
|
10
|
+
}
|
|
11
|
+
result() {
|
|
12
|
+
return this._runPromise;
|
|
13
|
+
}
|
|
14
|
+
async _isInstalled(fs, scopeRoot, tool) {
|
|
15
|
+
for (const path of (0, skillArtifacts_1.skillArtifactPaths)(scopeRoot, tool)) {
|
|
16
|
+
if (await fs.exists(path)) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
async _run(rawArgs, options, contexts) {
|
|
23
|
+
try {
|
|
24
|
+
if (rawArgs.length > 0) {
|
|
25
|
+
contexts.output.writeError("The update command takes no arguments.\n");
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
const homeDir = contexts.platform.homedir();
|
|
29
|
+
const destinations = [
|
|
30
|
+
{ scopeRoot: options.projectRoot, tool: "claude" },
|
|
31
|
+
{ scopeRoot: options.projectRoot, tool: "codex" },
|
|
32
|
+
{ scopeRoot: homeDir, tool: "claude" },
|
|
33
|
+
{ scopeRoot: homeDir, tool: "codex" }
|
|
34
|
+
];
|
|
35
|
+
const writtenPaths = [];
|
|
36
|
+
let found = false;
|
|
37
|
+
for (const dest of destinations) {
|
|
38
|
+
if (this._disposed) {
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
if (!(await this._isInstalled(contexts.fs, dest.scopeRoot, dest.tool))) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
found = true;
|
|
45
|
+
const result = await (0, skillArtifacts_1.writeSkillArtifacts)(contexts.fs, dest.scopeRoot, dest.tool, () => this._disposed);
|
|
46
|
+
if (!result.ok) {
|
|
47
|
+
if (result.diagnostic !== null) {
|
|
48
|
+
contexts.output.writeError(result.diagnostic);
|
|
49
|
+
}
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
writtenPaths.push(...result.writtenPaths);
|
|
53
|
+
}
|
|
54
|
+
if (this._disposed) {
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
if (!found) {
|
|
58
|
+
contexts.output.writeError("Well, hi-diddly-ho! There are no Flanders skills installed anywhere to refresh. Run npx flanders install to set them up first.\n");
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
for (const p of writtenPaths) {
|
|
62
|
+
contexts.output.write(`${p}\n`);
|
|
63
|
+
}
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
if (!this._disposed) {
|
|
68
|
+
contexts.output.writeError(`${e instanceof Error ? e.message : String(e)}\n`);
|
|
69
|
+
}
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async dispose() {
|
|
74
|
+
if (this._disposed) {
|
|
75
|
+
try {
|
|
76
|
+
await this._runPromise;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this._disposed = true;
|
|
83
|
+
try {
|
|
84
|
+
await this._runPromise;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.Update = Update;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FsContext } from "../contexts";
|
|
2
|
+
type SkillDef = Readonly<{
|
|
3
|
+
name: string;
|
|
4
|
+
body: string;
|
|
5
|
+
}>;
|
|
6
|
+
export declare const SKILLS: readonly SkillDef[];
|
|
7
|
+
export declare function skillArtifactPath(scopeRoot: string, tool: "claude" | "codex", skillName: string): string;
|
|
8
|
+
export declare function skillArtifactPaths(scopeRoot: string, tool: "claude" | "codex"): readonly string[];
|
|
9
|
+
export declare function stripYamlFrontmatter(body: string): string;
|
|
10
|
+
export type WriteSkillArtifactsResult = Readonly<{
|
|
11
|
+
ok: true;
|
|
12
|
+
writtenPaths: readonly string[];
|
|
13
|
+
}> | Readonly<{
|
|
14
|
+
ok: false;
|
|
15
|
+
diagnostic: string | null;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function writeSkillArtifacts(fs: FsContext, scopeRoot: string, tool: "claude" | "codex", isDisposed: () => boolean): Promise<WriteSkillArtifactsResult>;
|
|
18
|
+
export {};
|