@staff0rd/assist 0.148.0 → 0.149.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/dist/index.js +266 -247
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@staff0rd/assist",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.149.0",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -2286,8 +2286,7 @@ async function notify() {
|
|
|
2286
2286
|
console.log(`Notification sent: ${notification_type} for ${projectName}`);
|
|
2287
2287
|
}
|
|
2288
2288
|
|
|
2289
|
-
// src/commands/backlog/
|
|
2290
|
-
import { existsSync as existsSync13 } from "fs";
|
|
2289
|
+
// src/commands/backlog/delete/index.ts
|
|
2291
2290
|
import chalk26 from "chalk";
|
|
2292
2291
|
|
|
2293
2292
|
// src/commands/backlog/shared.ts
|
|
@@ -2394,196 +2393,59 @@ function readStdin2() {
|
|
|
2394
2393
|
});
|
|
2395
2394
|
}
|
|
2396
2395
|
|
|
2397
|
-
// src/commands/backlog/add/shared.ts
|
|
2398
|
-
import { spawnSync } from "child_process";
|
|
2399
|
-
import { mkdtempSync, readFileSync as readFileSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync12 } from "fs";
|
|
2400
|
-
import { tmpdir } from "os";
|
|
2401
|
-
import { join as join9 } from "path";
|
|
2402
|
-
import enquirer4 from "enquirer";
|
|
2403
|
-
async function promptType() {
|
|
2404
|
-
const { type } = await enquirer4.prompt({
|
|
2405
|
-
type: "select",
|
|
2406
|
-
name: "type",
|
|
2407
|
-
message: "Type:",
|
|
2408
|
-
choices: ["story", "bug"],
|
|
2409
|
-
initial: 0
|
|
2410
|
-
});
|
|
2411
|
-
return type;
|
|
2412
|
-
}
|
|
2413
|
-
async function promptName() {
|
|
2414
|
-
const { name } = await enquirer4.prompt({
|
|
2415
|
-
type: "input",
|
|
2416
|
-
name: "name",
|
|
2417
|
-
message: "Name:",
|
|
2418
|
-
validate: (value) => value.trim().length > 0 || "Name is required"
|
|
2419
|
-
});
|
|
2420
|
-
return name.trim();
|
|
2421
|
-
}
|
|
2422
|
-
async function promptDescription() {
|
|
2423
|
-
const { useEditor } = await enquirer4.prompt({
|
|
2424
|
-
type: "confirm",
|
|
2425
|
-
name: "useEditor",
|
|
2426
|
-
message: "Open editor for description?",
|
|
2427
|
-
initial: false
|
|
2428
|
-
});
|
|
2429
|
-
if (!useEditor) {
|
|
2430
|
-
const { description } = await enquirer4.prompt({
|
|
2431
|
-
type: "input",
|
|
2432
|
-
name: "description",
|
|
2433
|
-
message: "Description (optional):"
|
|
2434
|
-
});
|
|
2435
|
-
return description.trim() || void 0;
|
|
2436
|
-
}
|
|
2437
|
-
return openEditor();
|
|
2438
|
-
}
|
|
2439
|
-
function openEditor() {
|
|
2440
|
-
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
2441
|
-
const dir = mkdtempSync(join9(tmpdir(), "assist-"));
|
|
2442
|
-
const filePath = join9(dir, "description.md");
|
|
2443
|
-
writeFileSync12(filePath, "");
|
|
2444
|
-
const result = spawnSync(editor, [filePath], { stdio: "inherit" });
|
|
2445
|
-
if (result.status !== 0) {
|
|
2446
|
-
unlinkSync2(filePath);
|
|
2447
|
-
return void 0;
|
|
2448
|
-
}
|
|
2449
|
-
const content = readFileSync11(filePath, "utf-8").trim();
|
|
2450
|
-
unlinkSync2(filePath);
|
|
2451
|
-
return content || void 0;
|
|
2452
|
-
}
|
|
2453
|
-
async function promptAcceptanceCriteria() {
|
|
2454
|
-
const criteria = [];
|
|
2455
|
-
for (; ; ) {
|
|
2456
|
-
const { criterion } = await enquirer4.prompt({
|
|
2457
|
-
type: "input",
|
|
2458
|
-
name: "criterion",
|
|
2459
|
-
message: "Acceptance criterion (empty to finish):"
|
|
2460
|
-
});
|
|
2461
|
-
if (criterion.trim() === "") break;
|
|
2462
|
-
criteria.push(criterion.trim());
|
|
2463
|
-
}
|
|
2464
|
-
return criteria;
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
// src/commands/backlog/add/index.ts
|
|
2468
|
-
var addItemSchema = backlogItemSchema.omit({ id: true, status: true });
|
|
2469
|
-
async function addFromJson() {
|
|
2470
|
-
if (process.stdin.isTTY) {
|
|
2471
|
-
console.log(chalk26.red("--json requires piped input on stdin."));
|
|
2472
|
-
return;
|
|
2473
|
-
}
|
|
2474
|
-
const input = await readStdin2();
|
|
2475
|
-
const sanitised = input.replace(
|
|
2476
|
-
/"(?:[^"\\]|\\.)*"/g,
|
|
2477
|
-
(match) => match.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")
|
|
2478
|
-
);
|
|
2479
|
-
const data = addItemSchema.parse(JSON.parse(sanitised));
|
|
2480
|
-
const items = loadBacklog();
|
|
2481
|
-
const id = getNextId(items);
|
|
2482
|
-
items.push({ ...data, id, status: "todo" });
|
|
2483
|
-
saveBacklog(items);
|
|
2484
|
-
console.log(chalk26.green(`Added item #${id}: ${data.name}`));
|
|
2485
|
-
}
|
|
2486
|
-
async function addInteractive() {
|
|
2487
|
-
const type = await promptType();
|
|
2488
|
-
const name = await promptName();
|
|
2489
|
-
const description = await promptDescription();
|
|
2490
|
-
const acceptanceCriteria2 = await promptAcceptanceCriteria();
|
|
2491
|
-
const items = loadBacklog();
|
|
2492
|
-
const id = getNextId(items);
|
|
2493
|
-
items.push({
|
|
2494
|
-
id,
|
|
2495
|
-
type,
|
|
2496
|
-
name,
|
|
2497
|
-
description,
|
|
2498
|
-
acceptanceCriteria: acceptanceCriteria2,
|
|
2499
|
-
status: "todo"
|
|
2500
|
-
});
|
|
2501
|
-
saveBacklog(items);
|
|
2502
|
-
console.log(chalk26.green(`Added item #${id}: ${name}`));
|
|
2503
|
-
}
|
|
2504
|
-
async function add(options2) {
|
|
2505
|
-
if (!existsSync13(getBacklogPath())) {
|
|
2506
|
-
console.log(
|
|
2507
|
-
chalk26.yellow(
|
|
2508
|
-
"No backlog found. Run 'assist backlog init' to create one."
|
|
2509
|
-
)
|
|
2510
|
-
);
|
|
2511
|
-
return;
|
|
2512
|
-
}
|
|
2513
|
-
if (options2.json) {
|
|
2514
|
-
await addFromJson();
|
|
2515
|
-
} else {
|
|
2516
|
-
await addInteractive();
|
|
2517
|
-
}
|
|
2518
|
-
}
|
|
2519
|
-
|
|
2520
2396
|
// src/commands/backlog/delete/index.ts
|
|
2521
|
-
import chalk27 from "chalk";
|
|
2522
2397
|
async function del(id) {
|
|
2523
2398
|
const name = removeItem(id);
|
|
2524
2399
|
if (name) {
|
|
2525
|
-
console.log(
|
|
2400
|
+
console.log(chalk26.green(`Deleted item #${id}: ${name}`));
|
|
2526
2401
|
}
|
|
2527
2402
|
}
|
|
2528
2403
|
|
|
2529
2404
|
// src/commands/backlog/done/index.ts
|
|
2530
|
-
import
|
|
2405
|
+
import chalk27 from "chalk";
|
|
2531
2406
|
async function done(id) {
|
|
2532
2407
|
const name = setStatus(id, "done");
|
|
2533
2408
|
if (name) {
|
|
2534
|
-
console.log(
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
|
|
2538
|
-
// src/commands/backlog/init/index.ts
|
|
2539
|
-
import { existsSync as existsSync14 } from "fs";
|
|
2540
|
-
import chalk29 from "chalk";
|
|
2541
|
-
async function init6() {
|
|
2542
|
-
const backlogPath = getBacklogPath();
|
|
2543
|
-
if (existsSync14(backlogPath)) {
|
|
2544
|
-
console.log(chalk29.yellow("assist.backlog.yml already exists."));
|
|
2545
|
-
return;
|
|
2409
|
+
console.log(chalk27.green(`Completed item #${id}: ${name}`));
|
|
2546
2410
|
}
|
|
2547
|
-
saveBacklog([]);
|
|
2548
|
-
console.log(chalk29.green("Created assist.backlog.yml"));
|
|
2549
2411
|
}
|
|
2550
2412
|
|
|
2551
|
-
// src/commands/backlog/
|
|
2552
|
-
import
|
|
2553
|
-
import
|
|
2413
|
+
// src/commands/backlog/next.ts
|
|
2414
|
+
import chalk33 from "chalk";
|
|
2415
|
+
import enquirer5 from "enquirer";
|
|
2554
2416
|
|
|
2555
2417
|
// src/commands/backlog/list/shared.ts
|
|
2556
|
-
import
|
|
2418
|
+
import chalk28 from "chalk";
|
|
2557
2419
|
function statusIcon(status2) {
|
|
2558
2420
|
switch (status2) {
|
|
2559
2421
|
case "todo":
|
|
2560
|
-
return
|
|
2422
|
+
return chalk28.dim("[ ]");
|
|
2561
2423
|
case "in-progress":
|
|
2562
|
-
return
|
|
2424
|
+
return chalk28.yellow("[~]");
|
|
2563
2425
|
case "done":
|
|
2564
|
-
return
|
|
2426
|
+
return chalk28.green("[x]");
|
|
2565
2427
|
}
|
|
2566
2428
|
}
|
|
2567
2429
|
function typeLabel(type) {
|
|
2568
2430
|
switch (type) {
|
|
2569
2431
|
case "bug":
|
|
2570
|
-
return
|
|
2432
|
+
return chalk28.magenta("Bug");
|
|
2571
2433
|
case "story":
|
|
2572
|
-
return
|
|
2434
|
+
return chalk28.cyan("Story");
|
|
2573
2435
|
}
|
|
2574
2436
|
}
|
|
2575
2437
|
function phaseLabel(item) {
|
|
2576
2438
|
if (!item.plan) return "";
|
|
2577
|
-
return
|
|
2439
|
+
return chalk28.dim(
|
|
2578
2440
|
` (phase ${(item.currentPhase ?? 0) + 1}/${item.plan.length})`
|
|
2579
2441
|
);
|
|
2580
2442
|
}
|
|
2581
2443
|
function printVerboseDetails(item) {
|
|
2582
2444
|
if (item.description) {
|
|
2583
|
-
console.log(` ${
|
|
2445
|
+
console.log(` ${chalk28.dim("Description:")} ${item.description}`);
|
|
2584
2446
|
}
|
|
2585
2447
|
if (item.acceptanceCriteria.length > 0) {
|
|
2586
|
-
console.log(` ${
|
|
2448
|
+
console.log(` ${chalk28.dim("Acceptance criteria:")}`);
|
|
2587
2449
|
for (const [i, criterion] of item.acceptanceCriteria.entries()) {
|
|
2588
2450
|
console.log(` ${i + 1}. ${criterion}`);
|
|
2589
2451
|
}
|
|
@@ -2591,42 +2453,8 @@ function printVerboseDetails(item) {
|
|
|
2591
2453
|
console.log();
|
|
2592
2454
|
}
|
|
2593
2455
|
|
|
2594
|
-
// src/commands/backlog/list/index.ts
|
|
2595
|
-
function filterItems(items, options2) {
|
|
2596
|
-
if (options2.status) return items.filter((i) => i.status === options2.status);
|
|
2597
|
-
if (!options2.all) return items.filter((i) => i.status !== "done");
|
|
2598
|
-
return items;
|
|
2599
|
-
}
|
|
2600
|
-
async function list2(options2) {
|
|
2601
|
-
if (!existsSync15(getBacklogPath())) {
|
|
2602
|
-
console.log(
|
|
2603
|
-
chalk31.yellow(
|
|
2604
|
-
"No backlog found. Run 'assist backlog init' to create one."
|
|
2605
|
-
)
|
|
2606
|
-
);
|
|
2607
|
-
return;
|
|
2608
|
-
}
|
|
2609
|
-
const items = filterItems(loadBacklog(), options2);
|
|
2610
|
-
if (items.length === 0) {
|
|
2611
|
-
console.log(chalk31.dim("Backlog is empty."));
|
|
2612
|
-
return;
|
|
2613
|
-
}
|
|
2614
|
-
for (const item of items) {
|
|
2615
|
-
console.log(
|
|
2616
|
-
`${statusIcon(item.status)} ${typeLabel(item.type)} ${chalk31.dim(`#${item.id}`)} ${item.name}${phaseLabel(item)}`
|
|
2617
|
-
);
|
|
2618
|
-
if (options2.verbose) {
|
|
2619
|
-
printVerboseDetails(item);
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
|
-
}
|
|
2623
|
-
|
|
2624
|
-
// src/commands/backlog/next.ts
|
|
2625
|
-
import chalk36 from "chalk";
|
|
2626
|
-
import enquirer6 from "enquirer";
|
|
2627
|
-
|
|
2628
2456
|
// src/commands/backlog/run.ts
|
|
2629
|
-
import
|
|
2457
|
+
import chalk32 from "chalk";
|
|
2630
2458
|
|
|
2631
2459
|
// src/commands/backlog/buildAuthoredPhasePrompt.ts
|
|
2632
2460
|
function buildAuthoredPhasePrompt(item, phaseIndex, phase) {
|
|
@@ -2726,16 +2554,16 @@ function buildReviewPhase() {
|
|
|
2726
2554
|
}
|
|
2727
2555
|
|
|
2728
2556
|
// src/commands/backlog/executePhase.ts
|
|
2729
|
-
import
|
|
2557
|
+
import chalk31 from "chalk";
|
|
2730
2558
|
|
|
2731
2559
|
// src/commands/backlog/resolvePhaseResult.ts
|
|
2732
|
-
import { existsSync as
|
|
2733
|
-
import
|
|
2560
|
+
import { existsSync as existsSync13, unlinkSync as unlinkSync2 } from "fs";
|
|
2561
|
+
import chalk30 from "chalk";
|
|
2734
2562
|
|
|
2735
2563
|
// src/commands/backlog/handleIncompletePhase.ts
|
|
2736
|
-
import
|
|
2564
|
+
import enquirer4 from "enquirer";
|
|
2737
2565
|
async function handleIncompletePhase() {
|
|
2738
|
-
const { action } = await
|
|
2566
|
+
const { action } = await enquirer4.prompt({
|
|
2739
2567
|
type: "select",
|
|
2740
2568
|
name: "action",
|
|
2741
2569
|
message: "Phase was not marked complete. What would you like to do?",
|
|
@@ -2747,17 +2575,17 @@ async function handleIncompletePhase() {
|
|
|
2747
2575
|
}
|
|
2748
2576
|
|
|
2749
2577
|
// src/commands/backlog/phaseDone.ts
|
|
2750
|
-
import { writeFileSync as
|
|
2751
|
-
import { join as
|
|
2752
|
-
import
|
|
2578
|
+
import { writeFileSync as writeFileSync12 } from "fs";
|
|
2579
|
+
import { join as join9 } from "path";
|
|
2580
|
+
import chalk29 from "chalk";
|
|
2753
2581
|
var PHASE_STATUS_FILE = ".assist-phase-status.json";
|
|
2754
2582
|
function getPhaseStatusPath() {
|
|
2755
|
-
return
|
|
2583
|
+
return join9(process.cwd(), PHASE_STATUS_FILE);
|
|
2756
2584
|
}
|
|
2757
2585
|
function phaseDone(id, phase) {
|
|
2758
2586
|
const phaseIndex = Number.parseInt(phase, 10);
|
|
2759
2587
|
const statusPath = getPhaseStatusPath();
|
|
2760
|
-
|
|
2588
|
+
writeFileSync12(
|
|
2761
2589
|
statusPath,
|
|
2762
2590
|
JSON.stringify({
|
|
2763
2591
|
itemId: Number.parseInt(id, 10),
|
|
@@ -2766,32 +2594,36 @@ function phaseDone(id, phase) {
|
|
|
2766
2594
|
})
|
|
2767
2595
|
);
|
|
2768
2596
|
setCurrentPhase(id, phaseIndex + 1);
|
|
2769
|
-
console.log(
|
|
2597
|
+
console.log(chalk29.green(`Phase ${phase} of item #${id} marked as complete.`));
|
|
2770
2598
|
}
|
|
2771
2599
|
|
|
2772
2600
|
// src/commands/backlog/resolvePhaseResult.ts
|
|
2773
2601
|
function cleanupMarker() {
|
|
2774
2602
|
const statusPath = getPhaseStatusPath();
|
|
2775
|
-
if (
|
|
2776
|
-
|
|
2603
|
+
if (existsSync13(statusPath)) {
|
|
2604
|
+
unlinkSync2(statusPath);
|
|
2777
2605
|
}
|
|
2778
2606
|
}
|
|
2779
2607
|
async function resolvePhaseResult(phaseIndex) {
|
|
2780
|
-
if (!
|
|
2608
|
+
if (!existsSync13(getPhaseStatusPath())) {
|
|
2781
2609
|
const action = await handleIncompletePhase();
|
|
2782
2610
|
if (action === "abort") return -1;
|
|
2783
2611
|
return action === "skip" ? 1 : 0;
|
|
2784
2612
|
}
|
|
2785
2613
|
cleanupMarker();
|
|
2786
|
-
console.log(
|
|
2614
|
+
console.log(chalk30.green(`
|
|
2787
2615
|
Phase ${phaseIndex + 1} completed.`));
|
|
2788
2616
|
return 1;
|
|
2789
2617
|
}
|
|
2790
2618
|
|
|
2791
2619
|
// src/commands/backlog/spawnClaude.ts
|
|
2792
2620
|
import { spawn as spawn3 } from "child_process";
|
|
2793
|
-
function spawnClaude(prompt) {
|
|
2794
|
-
const
|
|
2621
|
+
function spawnClaude(prompt, options2 = {}) {
|
|
2622
|
+
const args = [prompt];
|
|
2623
|
+
if (options2.allowEdits) {
|
|
2624
|
+
args.push("--permission-mode", "acceptEdits");
|
|
2625
|
+
}
|
|
2626
|
+
const child = spawn3("claude", args, {
|
|
2795
2627
|
stdio: "inherit"
|
|
2796
2628
|
});
|
|
2797
2629
|
const done2 = new Promise((resolve7, reject) => {
|
|
@@ -2802,11 +2634,11 @@ function spawnClaude(prompt) {
|
|
|
2802
2634
|
}
|
|
2803
2635
|
|
|
2804
2636
|
// src/commands/backlog/watchForMarker.ts
|
|
2805
|
-
import { existsSync as
|
|
2637
|
+
import { existsSync as existsSync14, unwatchFile, watchFile } from "fs";
|
|
2806
2638
|
function watchForMarker(child) {
|
|
2807
2639
|
const statusPath = getPhaseStatusPath();
|
|
2808
2640
|
watchFile(statusPath, { interval: 1e3 }, () => {
|
|
2809
|
-
if (
|
|
2641
|
+
if (existsSync14(statusPath)) {
|
|
2810
2642
|
unwatchFile(statusPath);
|
|
2811
2643
|
child.kill("SIGTERM");
|
|
2812
2644
|
}
|
|
@@ -2817,17 +2649,18 @@ function stopWatching() {
|
|
|
2817
2649
|
}
|
|
2818
2650
|
|
|
2819
2651
|
// src/commands/backlog/executePhase.ts
|
|
2820
|
-
async function executePhase(item, phaseIndex, phases) {
|
|
2652
|
+
async function executePhase(item, phaseIndex, phases, spawnOptions) {
|
|
2821
2653
|
const phase = phases[phaseIndex];
|
|
2822
2654
|
console.log(
|
|
2823
|
-
|
|
2655
|
+
chalk31.bold(
|
|
2824
2656
|
`
|
|
2825
2657
|
--- Phase ${phaseIndex + 1}/${phases.length}: ${phase.name} ---
|
|
2826
2658
|
`
|
|
2827
2659
|
)
|
|
2828
2660
|
);
|
|
2829
2661
|
const { child, done: done2 } = spawnClaude(
|
|
2830
|
-
buildPhasePrompt(item, phaseIndex, phase)
|
|
2662
|
+
buildPhasePrompt(item, phaseIndex, phase),
|
|
2663
|
+
spawnOptions
|
|
2831
2664
|
);
|
|
2832
2665
|
watchForMarker(child);
|
|
2833
2666
|
await done2;
|
|
@@ -2850,7 +2683,7 @@ function resolvePlan(item) {
|
|
|
2850
2683
|
}
|
|
2851
2684
|
|
|
2852
2685
|
// src/commands/backlog/run.ts
|
|
2853
|
-
async function run2(id) {
|
|
2686
|
+
async function run2(id, spawnOptions) {
|
|
2854
2687
|
const result = loadAndFindItem(id);
|
|
2855
2688
|
if (!result) return;
|
|
2856
2689
|
const { item } = result;
|
|
@@ -2859,93 +2692,98 @@ async function run2(id) {
|
|
|
2859
2692
|
if (startPhase > plan2.length) {
|
|
2860
2693
|
if (item.status !== "done") setStatus(id, "done");
|
|
2861
2694
|
console.log(
|
|
2862
|
-
|
|
2695
|
+
chalk32.green(`All phases already complete for #${id}: ${item.name}`)
|
|
2863
2696
|
);
|
|
2864
2697
|
return;
|
|
2865
2698
|
}
|
|
2866
2699
|
setStatus(id, "in-progress");
|
|
2867
|
-
console.log(
|
|
2700
|
+
console.log(chalk32.bold(`Running plan for #${id}: ${item.name}`));
|
|
2868
2701
|
if (startPhase > 0) {
|
|
2869
2702
|
console.log(
|
|
2870
|
-
|
|
2703
|
+
chalk32.dim(`Resuming from phase ${startPhase + 1}/${plan2.length}
|
|
2871
2704
|
`)
|
|
2872
2705
|
);
|
|
2873
2706
|
} else {
|
|
2874
|
-
console.log(
|
|
2707
|
+
console.log(chalk32.dim(`${plan2.length} phase(s)
|
|
2875
2708
|
`));
|
|
2876
2709
|
}
|
|
2877
2710
|
let phaseIndex = startPhase;
|
|
2878
2711
|
while (phaseIndex < plan2.length) {
|
|
2879
|
-
phaseIndex = await executePhase(item, phaseIndex, plan2);
|
|
2712
|
+
phaseIndex = await executePhase(item, phaseIndex, plan2, spawnOptions);
|
|
2880
2713
|
if (phaseIndex < 0) return;
|
|
2881
2714
|
}
|
|
2882
2715
|
const reviewPhase = buildReviewPhase();
|
|
2883
2716
|
const allPhases = [...plan2, reviewPhase];
|
|
2884
2717
|
const reviewIndex = plan2.length;
|
|
2885
|
-
const reviewResult = await executePhase(
|
|
2718
|
+
const reviewResult = await executePhase(
|
|
2719
|
+
item,
|
|
2720
|
+
reviewIndex,
|
|
2721
|
+
allPhases,
|
|
2722
|
+
spawnOptions
|
|
2723
|
+
);
|
|
2886
2724
|
if (reviewResult < 0) return;
|
|
2887
2725
|
if (item.status !== "done") setStatus(id, "done");
|
|
2888
|
-
console.log(
|
|
2726
|
+
console.log(chalk32.green(`
|
|
2889
2727
|
All phases complete for #${id}: ${item.name}`));
|
|
2890
2728
|
}
|
|
2891
2729
|
|
|
2892
2730
|
// src/commands/backlog/next.ts
|
|
2893
|
-
async function next() {
|
|
2731
|
+
async function next(options2) {
|
|
2894
2732
|
const items = loadBacklog();
|
|
2895
2733
|
const inProgress = items.find((i) => i.status === "in-progress" && i.plan);
|
|
2896
2734
|
if (inProgress) {
|
|
2897
2735
|
console.log(
|
|
2898
|
-
|
|
2736
|
+
chalk33.bold(
|
|
2899
2737
|
`Resuming in-progress item #${inProgress.id}: ${inProgress.name}`
|
|
2900
2738
|
)
|
|
2901
2739
|
);
|
|
2902
|
-
await run2(String(inProgress.id));
|
|
2740
|
+
await run2(String(inProgress.id), options2);
|
|
2903
2741
|
return;
|
|
2904
2742
|
}
|
|
2905
2743
|
const todo = items.filter((i) => i.status === "todo");
|
|
2906
2744
|
if (todo.length === 0) {
|
|
2907
|
-
console.log(
|
|
2908
|
-
await spawnClaude("/draft");
|
|
2745
|
+
console.log(chalk33.dim("No incomplete backlog items. Opening /draft..."));
|
|
2746
|
+
await spawnClaude("/draft", options2);
|
|
2909
2747
|
return;
|
|
2910
2748
|
}
|
|
2911
2749
|
if (todo.length === 1) {
|
|
2912
2750
|
const only = todo[0];
|
|
2913
|
-
console.log(
|
|
2914
|
-
await run2(String(only.id));
|
|
2751
|
+
console.log(chalk33.bold(`Starting #${only.id}: ${only.name}`));
|
|
2752
|
+
await run2(String(only.id), options2);
|
|
2915
2753
|
return;
|
|
2916
2754
|
}
|
|
2917
2755
|
const choices = todo.map((i) => ({
|
|
2918
2756
|
name: `${typeLabel(i.type)} #${i.id}: ${i.name}`,
|
|
2919
2757
|
value: String(i.id)
|
|
2920
2758
|
}));
|
|
2921
|
-
const { selected } = await
|
|
2759
|
+
const { selected } = await enquirer5.prompt({
|
|
2922
2760
|
type: "select",
|
|
2923
2761
|
name: "selected",
|
|
2924
2762
|
message: "Choose a backlog item to start:",
|
|
2925
2763
|
choices: choices.map((c) => c.name)
|
|
2926
2764
|
});
|
|
2927
2765
|
const id = selected.match(/#(\d+)/)?.[1] ?? "";
|
|
2928
|
-
await run2(id);
|
|
2766
|
+
await run2(id, options2);
|
|
2929
2767
|
}
|
|
2930
2768
|
|
|
2931
2769
|
// src/commands/backlog/plan.ts
|
|
2932
|
-
import
|
|
2770
|
+
import chalk34 from "chalk";
|
|
2933
2771
|
function plan(id) {
|
|
2934
2772
|
const result = loadAndFindItem(id);
|
|
2935
2773
|
if (!result) return;
|
|
2936
2774
|
const { item } = result;
|
|
2937
2775
|
if (!item.plan || item.plan.length === 0) {
|
|
2938
|
-
console.log(
|
|
2776
|
+
console.log(chalk34.dim("No plan defined for this item."));
|
|
2939
2777
|
return;
|
|
2940
2778
|
}
|
|
2941
|
-
console.log(
|
|
2779
|
+
console.log(chalk34.bold(item.name));
|
|
2942
2780
|
console.log();
|
|
2943
2781
|
for (const [i, phase] of item.plan.entries()) {
|
|
2944
|
-
console.log(`${
|
|
2782
|
+
console.log(`${chalk34.bold(`Phase ${i + 1}:`)} ${phase.name}`);
|
|
2945
2783
|
for (const task of phase.tasks) {
|
|
2946
2784
|
console.log(` - ${task.task}`);
|
|
2947
2785
|
if (task.verify) {
|
|
2948
|
-
console.log(` ${
|
|
2786
|
+
console.log(` ${chalk34.dim(`verify: ${task.verify}`)}`);
|
|
2949
2787
|
}
|
|
2950
2788
|
}
|
|
2951
2789
|
console.log();
|
|
@@ -2953,23 +2791,23 @@ function plan(id) {
|
|
|
2953
2791
|
}
|
|
2954
2792
|
|
|
2955
2793
|
// src/commands/backlog/start/index.ts
|
|
2956
|
-
import
|
|
2794
|
+
import chalk35 from "chalk";
|
|
2957
2795
|
async function start(id) {
|
|
2958
2796
|
const name = setStatus(id, "in-progress");
|
|
2959
2797
|
if (name) {
|
|
2960
|
-
console.log(
|
|
2798
|
+
console.log(chalk35.green(`Started item #${id}: ${name}`));
|
|
2961
2799
|
}
|
|
2962
2800
|
}
|
|
2963
2801
|
|
|
2964
2802
|
// src/shared/web.ts
|
|
2965
2803
|
import { exec } from "child_process";
|
|
2966
|
-
import { readFileSync as
|
|
2804
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
2967
2805
|
import {
|
|
2968
2806
|
createServer
|
|
2969
2807
|
} from "http";
|
|
2970
|
-
import { dirname as dirname13, join as
|
|
2808
|
+
import { dirname as dirname13, join as join10 } from "path";
|
|
2971
2809
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2972
|
-
import
|
|
2810
|
+
import chalk36 from "chalk";
|
|
2973
2811
|
function respondJson(res, status2, data) {
|
|
2974
2812
|
res.writeHead(status2, { "Content-Type": "application/json" });
|
|
2975
2813
|
res.end(JSON.stringify(data));
|
|
@@ -2979,7 +2817,7 @@ function createBundleHandler(importMetaUrl, bundlePath) {
|
|
|
2979
2817
|
let cache;
|
|
2980
2818
|
return (_req, res) => {
|
|
2981
2819
|
if (!cache) {
|
|
2982
|
-
cache =
|
|
2820
|
+
cache = readFileSync11(join10(dir, bundlePath), "utf-8");
|
|
2983
2821
|
}
|
|
2984
2822
|
res.writeHead(200, { "Content-Type": "application/javascript" });
|
|
2985
2823
|
res.end(cache);
|
|
@@ -3013,8 +2851,8 @@ function startWebServer(label2, port, handler) {
|
|
|
3013
2851
|
handler(req, res, port);
|
|
3014
2852
|
});
|
|
3015
2853
|
server.listen(port, () => {
|
|
3016
|
-
console.log(
|
|
3017
|
-
console.log(
|
|
2854
|
+
console.log(chalk36.green(`${label2}: ${url}`));
|
|
2855
|
+
console.log(chalk36.dim("Press Ctrl+C to stop"));
|
|
3018
2856
|
exec(`open ${url}`);
|
|
3019
2857
|
});
|
|
3020
2858
|
}
|
|
@@ -3161,29 +2999,210 @@ async function web(options2) {
|
|
|
3161
2999
|
);
|
|
3162
3000
|
}
|
|
3163
3001
|
|
|
3164
|
-
// src/commands/
|
|
3002
|
+
// src/commands/backlog/add/index.ts
|
|
3003
|
+
import { existsSync as existsSync15 } from "fs";
|
|
3004
|
+
import chalk37 from "chalk";
|
|
3005
|
+
|
|
3006
|
+
// src/commands/backlog/add/shared.ts
|
|
3007
|
+
import { spawnSync } from "child_process";
|
|
3008
|
+
import { mkdtempSync, readFileSync as readFileSync12, unlinkSync as unlinkSync3, writeFileSync as writeFileSync13 } from "fs";
|
|
3009
|
+
import { tmpdir } from "os";
|
|
3010
|
+
import { join as join11 } from "path";
|
|
3011
|
+
import enquirer6 from "enquirer";
|
|
3012
|
+
async function promptType() {
|
|
3013
|
+
const { type } = await enquirer6.prompt({
|
|
3014
|
+
type: "select",
|
|
3015
|
+
name: "type",
|
|
3016
|
+
message: "Type:",
|
|
3017
|
+
choices: ["story", "bug"],
|
|
3018
|
+
initial: 0
|
|
3019
|
+
});
|
|
3020
|
+
return type;
|
|
3021
|
+
}
|
|
3022
|
+
async function promptName() {
|
|
3023
|
+
const { name } = await enquirer6.prompt({
|
|
3024
|
+
type: "input",
|
|
3025
|
+
name: "name",
|
|
3026
|
+
message: "Name:",
|
|
3027
|
+
validate: (value) => value.trim().length > 0 || "Name is required"
|
|
3028
|
+
});
|
|
3029
|
+
return name.trim();
|
|
3030
|
+
}
|
|
3031
|
+
async function promptDescription() {
|
|
3032
|
+
const { useEditor } = await enquirer6.prompt({
|
|
3033
|
+
type: "confirm",
|
|
3034
|
+
name: "useEditor",
|
|
3035
|
+
message: "Open editor for description?",
|
|
3036
|
+
initial: false
|
|
3037
|
+
});
|
|
3038
|
+
if (!useEditor) {
|
|
3039
|
+
const { description } = await enquirer6.prompt({
|
|
3040
|
+
type: "input",
|
|
3041
|
+
name: "description",
|
|
3042
|
+
message: "Description (optional):"
|
|
3043
|
+
});
|
|
3044
|
+
return description.trim() || void 0;
|
|
3045
|
+
}
|
|
3046
|
+
return openEditor();
|
|
3047
|
+
}
|
|
3048
|
+
function openEditor() {
|
|
3049
|
+
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
3050
|
+
const dir = mkdtempSync(join11(tmpdir(), "assist-"));
|
|
3051
|
+
const filePath = join11(dir, "description.md");
|
|
3052
|
+
writeFileSync13(filePath, "");
|
|
3053
|
+
const result = spawnSync(editor, [filePath], { stdio: "inherit" });
|
|
3054
|
+
if (result.status !== 0) {
|
|
3055
|
+
unlinkSync3(filePath);
|
|
3056
|
+
return void 0;
|
|
3057
|
+
}
|
|
3058
|
+
const content = readFileSync12(filePath, "utf-8").trim();
|
|
3059
|
+
unlinkSync3(filePath);
|
|
3060
|
+
return content || void 0;
|
|
3061
|
+
}
|
|
3062
|
+
async function promptAcceptanceCriteria() {
|
|
3063
|
+
const criteria = [];
|
|
3064
|
+
for (; ; ) {
|
|
3065
|
+
const { criterion } = await enquirer6.prompt({
|
|
3066
|
+
type: "input",
|
|
3067
|
+
name: "criterion",
|
|
3068
|
+
message: "Acceptance criterion (empty to finish):"
|
|
3069
|
+
});
|
|
3070
|
+
if (criterion.trim() === "") break;
|
|
3071
|
+
criteria.push(criterion.trim());
|
|
3072
|
+
}
|
|
3073
|
+
return criteria;
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
// src/commands/backlog/add/index.ts
|
|
3077
|
+
var addItemSchema = backlogItemSchema.omit({ id: true, status: true });
|
|
3078
|
+
async function addFromJson() {
|
|
3079
|
+
if (process.stdin.isTTY) {
|
|
3080
|
+
console.log(chalk37.red("--json requires piped input on stdin."));
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
const input = await readStdin2();
|
|
3084
|
+
const sanitised = input.replace(
|
|
3085
|
+
/"(?:[^"\\]|\\.)*"/g,
|
|
3086
|
+
(match) => match.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")
|
|
3087
|
+
);
|
|
3088
|
+
const data = addItemSchema.parse(JSON.parse(sanitised));
|
|
3089
|
+
const items = loadBacklog();
|
|
3090
|
+
const id = getNextId(items);
|
|
3091
|
+
items.push({ ...data, id, status: "todo" });
|
|
3092
|
+
saveBacklog(items);
|
|
3093
|
+
console.log(chalk37.green(`Added item #${id}: ${data.name}`));
|
|
3094
|
+
}
|
|
3095
|
+
async function addInteractive() {
|
|
3096
|
+
const type = await promptType();
|
|
3097
|
+
const name = await promptName();
|
|
3098
|
+
const description = await promptDescription();
|
|
3099
|
+
const acceptanceCriteria2 = await promptAcceptanceCriteria();
|
|
3100
|
+
const items = loadBacklog();
|
|
3101
|
+
const id = getNextId(items);
|
|
3102
|
+
items.push({
|
|
3103
|
+
id,
|
|
3104
|
+
type,
|
|
3105
|
+
name,
|
|
3106
|
+
description,
|
|
3107
|
+
acceptanceCriteria: acceptanceCriteria2,
|
|
3108
|
+
status: "todo"
|
|
3109
|
+
});
|
|
3110
|
+
saveBacklog(items);
|
|
3111
|
+
console.log(chalk37.green(`Added item #${id}: ${name}`));
|
|
3112
|
+
}
|
|
3113
|
+
async function add(options2) {
|
|
3114
|
+
if (!existsSync15(getBacklogPath())) {
|
|
3115
|
+
console.log(
|
|
3116
|
+
chalk37.yellow(
|
|
3117
|
+
"No backlog found. Run 'assist backlog init' to create one."
|
|
3118
|
+
)
|
|
3119
|
+
);
|
|
3120
|
+
return;
|
|
3121
|
+
}
|
|
3122
|
+
if (options2.json) {
|
|
3123
|
+
await addFromJson();
|
|
3124
|
+
} else {
|
|
3125
|
+
await addInteractive();
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
// src/commands/backlog/init/index.ts
|
|
3130
|
+
import { existsSync as existsSync16 } from "fs";
|
|
3131
|
+
import chalk38 from "chalk";
|
|
3132
|
+
async function init6() {
|
|
3133
|
+
const backlogPath = getBacklogPath();
|
|
3134
|
+
if (existsSync16(backlogPath)) {
|
|
3135
|
+
console.log(chalk38.yellow("assist.backlog.yml already exists."));
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
saveBacklog([]);
|
|
3139
|
+
console.log(chalk38.green("Created assist.backlog.yml"));
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
// src/commands/backlog/list/index.ts
|
|
3143
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3144
|
+
import chalk39 from "chalk";
|
|
3145
|
+
function filterItems(items, options2) {
|
|
3146
|
+
if (options2.status) return items.filter((i) => i.status === options2.status);
|
|
3147
|
+
if (!options2.all) return items.filter((i) => i.status !== "done");
|
|
3148
|
+
return items;
|
|
3149
|
+
}
|
|
3150
|
+
async function list2(options2) {
|
|
3151
|
+
if (!existsSync17(getBacklogPath())) {
|
|
3152
|
+
console.log(
|
|
3153
|
+
chalk39.yellow(
|
|
3154
|
+
"No backlog found. Run 'assist backlog init' to create one."
|
|
3155
|
+
)
|
|
3156
|
+
);
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
const items = filterItems(loadBacklog(), options2);
|
|
3160
|
+
if (items.length === 0) {
|
|
3161
|
+
console.log(chalk39.dim("Backlog is empty."));
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
for (const item of items) {
|
|
3165
|
+
console.log(
|
|
3166
|
+
`${statusIcon(item.status)} ${typeLabel(item.type)} ${chalk39.dim(`#${item.id}`)} ${item.name}${phaseLabel(item)}`
|
|
3167
|
+
);
|
|
3168
|
+
if (options2.verbose) {
|
|
3169
|
+
printVerboseDetails(item);
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
// src/commands/backlog/registerItemCommands.ts
|
|
3165
3175
|
function registerItemCommands(cmd) {
|
|
3166
3176
|
cmd.command("init").description("Create an empty assist.backlog.yml").action(init6);
|
|
3167
3177
|
cmd.command("list").description("List all backlog items").option("--status <type>", "Filter by status (todo, in-progress, done)").option("-a, --all", "Include done items").option("-v, --verbose", "Show all item details").action(list2);
|
|
3168
3178
|
cmd.command("add").description("Add a new backlog item").option("--json", "Read item as JSON from stdin").action(add);
|
|
3169
3179
|
}
|
|
3180
|
+
|
|
3181
|
+
// src/commands/registerBacklog.ts
|
|
3170
3182
|
function registerStatusCommands(cmd) {
|
|
3171
3183
|
cmd.command("start <id>").description("Set a backlog item to in-progress").action(start);
|
|
3172
3184
|
cmd.command("done <id>").description("Set a backlog item to done").action(done);
|
|
3173
3185
|
cmd.command("delete <id>").alias("remove").description("Delete a backlog item").action(del);
|
|
3174
3186
|
cmd.command("web").description("Start a web view of the backlog").option("-p, --port <number>", "Port to listen on", "3000").action(web);
|
|
3175
3187
|
}
|
|
3176
|
-
function
|
|
3177
|
-
cmd.command("next").description("Pick and run the next backlog item, or open /draft if none").action(next);
|
|
3188
|
+
function registerPlanCommands(cmd) {
|
|
3178
3189
|
cmd.command("plan <id>").description("Display the plan for a backlog item").action(plan);
|
|
3179
3190
|
cmd.command("phase-done <id> <phase>").description("Signal that a plan phase is complete").action(phaseDone);
|
|
3180
|
-
|
|
3191
|
+
}
|
|
3192
|
+
function registerRunCommands(cmd) {
|
|
3193
|
+
cmd.command("next").description("Pick and run the next backlog item, or open /draft if none").option("--allow-edits", "Run Claude with acceptEdits permission mode").action(
|
|
3194
|
+
(opts) => next({ allowEdits: opts.allowEdits })
|
|
3195
|
+
);
|
|
3196
|
+
cmd.command("run <id>").description("Run a backlog item's plan phase-by-phase with Claude").option("--allow-edits", "Run Claude with acceptEdits permission mode").action(
|
|
3197
|
+
(id, opts) => run2(id, { allowEdits: opts.allowEdits })
|
|
3198
|
+
);
|
|
3181
3199
|
}
|
|
3182
3200
|
function registerBacklog(program2) {
|
|
3183
3201
|
const cmd = program2.command("backlog").description("Manage a backlog of work items").action(() => web({ port: "3000" }));
|
|
3184
3202
|
registerItemCommands(cmd);
|
|
3185
3203
|
registerStatusCommands(cmd);
|
|
3186
|
-
|
|
3204
|
+
registerPlanCommands(cmd);
|
|
3205
|
+
registerRunCommands(cmd);
|
|
3187
3206
|
}
|
|
3188
3207
|
|
|
3189
3208
|
// src/shared/isApprovedRead.ts
|