@staff0rd/assist 0.227.0 → 0.229.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 +850 -801
- package/package.json +2 -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.229.0",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -44,6 +44,7 @@ var package_default = {
|
|
|
44
44
|
chalk: "^5.6.2",
|
|
45
45
|
commander: "^14.0.2",
|
|
46
46
|
diff: "^8.0.2",
|
|
47
|
+
"drizzle-orm": "^0.45.2",
|
|
47
48
|
enquirer: "^2.4.1",
|
|
48
49
|
entities: "^7.0.1",
|
|
49
50
|
"is-wsl": "^3.1.0",
|
|
@@ -413,21 +414,93 @@ function getTranscriptConfig() {
|
|
|
413
414
|
return config.transcript;
|
|
414
415
|
}
|
|
415
416
|
|
|
416
|
-
// src/commands/backlog/
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
417
|
+
// src/commands/backlog/deleteItem.ts
|
|
418
|
+
import { eq } from "drizzle-orm";
|
|
419
|
+
|
|
420
|
+
// src/commands/backlog/backlogSchema.ts
|
|
421
|
+
import {
|
|
422
|
+
foreignKey,
|
|
423
|
+
index,
|
|
424
|
+
integer,
|
|
425
|
+
pgTable,
|
|
426
|
+
primaryKey,
|
|
427
|
+
text
|
|
428
|
+
} from "drizzle-orm/pg-core";
|
|
429
|
+
var items = pgTable(
|
|
430
|
+
"items",
|
|
431
|
+
{
|
|
432
|
+
id: integer().generatedByDefaultAsIdentity().primaryKey(),
|
|
433
|
+
origin: text().notNull(),
|
|
434
|
+
type: text().notNull().default("story"),
|
|
435
|
+
name: text().notNull(),
|
|
436
|
+
description: text(),
|
|
437
|
+
acceptanceCriteria: text("acceptance_criteria").notNull().default("[]"),
|
|
438
|
+
status: text().notNull().default("todo"),
|
|
439
|
+
currentPhase: integer("current_phase")
|
|
440
|
+
},
|
|
441
|
+
(t) => [index("items_origin_idx").on(t.origin)]
|
|
442
|
+
);
|
|
443
|
+
var comments = pgTable("comments", {
|
|
444
|
+
id: integer().generatedByDefaultAsIdentity().primaryKey(),
|
|
445
|
+
itemId: integer("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
|
|
446
|
+
idx: integer().notNull(),
|
|
447
|
+
text: text().notNull(),
|
|
448
|
+
phase: integer(),
|
|
449
|
+
timestamp: text().notNull(),
|
|
450
|
+
type: text().notNull().default("comment")
|
|
451
|
+
});
|
|
452
|
+
var links = pgTable(
|
|
453
|
+
"links",
|
|
454
|
+
{
|
|
455
|
+
itemId: integer("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
|
|
456
|
+
type: text().notNull(),
|
|
457
|
+
targetId: integer("target_id").notNull()
|
|
458
|
+
},
|
|
459
|
+
(t) => [primaryKey({ columns: [t.itemId, t.type, t.targetId] })]
|
|
460
|
+
);
|
|
461
|
+
var planPhases = pgTable(
|
|
462
|
+
"plan_phases",
|
|
463
|
+
{
|
|
464
|
+
itemId: integer("item_id").notNull().references(() => items.id, { onDelete: "cascade" }),
|
|
465
|
+
idx: integer().notNull(),
|
|
466
|
+
name: text().notNull(),
|
|
467
|
+
manualChecks: text("manual_checks")
|
|
468
|
+
},
|
|
469
|
+
(t) => [primaryKey({ columns: [t.itemId, t.idx] })]
|
|
470
|
+
);
|
|
471
|
+
var planTasks = pgTable(
|
|
472
|
+
"plan_tasks",
|
|
473
|
+
{
|
|
474
|
+
itemId: integer("item_id").notNull(),
|
|
475
|
+
phaseIdx: integer("phase_idx").notNull(),
|
|
476
|
+
idx: integer().notNull(),
|
|
477
|
+
task: text().notNull()
|
|
478
|
+
},
|
|
479
|
+
(t) => [
|
|
480
|
+
primaryKey({ columns: [t.itemId, t.phaseIdx, t.idx] }),
|
|
481
|
+
foreignKey({
|
|
482
|
+
columns: [t.itemId, t.phaseIdx],
|
|
483
|
+
foreignColumns: [planPhases.itemId, planPhases.idx]
|
|
484
|
+
}).onDelete("cascade")
|
|
485
|
+
]
|
|
486
|
+
);
|
|
487
|
+
var metadata = pgTable("metadata", {
|
|
488
|
+
key: text().primaryKey(),
|
|
489
|
+
value: text().notNull()
|
|
490
|
+
});
|
|
491
|
+
var backlogSchema = {
|
|
492
|
+
items,
|
|
493
|
+
comments,
|
|
494
|
+
links,
|
|
495
|
+
planPhases,
|
|
496
|
+
planTasks,
|
|
497
|
+
metadata
|
|
498
|
+
};
|
|
423
499
|
|
|
424
500
|
// src/commands/backlog/deleteItem.ts
|
|
425
|
-
async function deleteItem(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const result = await tx.run("DELETE FROM items WHERE id = ?", [id]);
|
|
429
|
-
return result.changes > 0;
|
|
430
|
-
});
|
|
501
|
+
async function deleteItem(orm, id) {
|
|
502
|
+
const [row] = await orm.delete(items).where(eq(items.id, id)).returning({ name: items.name });
|
|
503
|
+
return row?.name;
|
|
431
504
|
}
|
|
432
505
|
|
|
433
506
|
// src/commands/backlog/migrateLocalBacklog.ts
|
|
@@ -469,74 +542,70 @@ function gitPullBacklog(dir) {
|
|
|
469
542
|
}
|
|
470
543
|
}
|
|
471
544
|
|
|
545
|
+
// src/commands/backlog/itemColumns.ts
|
|
546
|
+
function itemColumns(item, origin) {
|
|
547
|
+
return {
|
|
548
|
+
origin,
|
|
549
|
+
type: item.type,
|
|
550
|
+
name: item.name,
|
|
551
|
+
description: item.description ?? null,
|
|
552
|
+
acceptanceCriteria: JSON.stringify(item.acceptanceCriteria),
|
|
553
|
+
status: item.status,
|
|
554
|
+
currentPhase: item.currentPhase ?? null
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
472
558
|
// src/commands/backlog/insertItem.ts
|
|
473
559
|
async function insertItem(db, item, origin) {
|
|
474
|
-
const row = await db.
|
|
475
|
-
`INSERT INTO items (origin, type, name, description, acceptance_criteria, status, current_phase)
|
|
476
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
477
|
-
RETURNING id`,
|
|
478
|
-
[
|
|
479
|
-
origin,
|
|
480
|
-
item.type,
|
|
481
|
-
item.name,
|
|
482
|
-
item.description ?? null,
|
|
483
|
-
JSON.stringify(item.acceptanceCriteria),
|
|
484
|
-
item.status,
|
|
485
|
-
item.currentPhase ?? null
|
|
486
|
-
]
|
|
487
|
-
);
|
|
560
|
+
const [row] = await db.insert(items).values(itemColumns(item, origin)).returning({ id: items.id });
|
|
488
561
|
if (!row) throw new Error("Failed to insert backlog item");
|
|
489
562
|
return row.id;
|
|
490
563
|
}
|
|
491
564
|
|
|
492
565
|
// src/commands/backlog/insertItemRelations.ts
|
|
493
566
|
async function insertComments(db, item) {
|
|
494
|
-
if (!item.comments) return;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
567
|
+
if (!item.comments?.length) return;
|
|
568
|
+
await db.insert(comments).values(
|
|
569
|
+
item.comments.map((c, i) => ({
|
|
570
|
+
id: c.id,
|
|
571
|
+
itemId: item.id,
|
|
572
|
+
idx: i,
|
|
573
|
+
text: c.text,
|
|
574
|
+
phase: c.phase ?? null,
|
|
575
|
+
timestamp: c.timestamp,
|
|
576
|
+
type: c.type
|
|
577
|
+
}))
|
|
578
|
+
);
|
|
509
579
|
}
|
|
510
580
|
async function insertLinks(db, item) {
|
|
511
|
-
if (!item.links) return;
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
581
|
+
if (!item.links?.length) return;
|
|
582
|
+
await db.insert(links).values(
|
|
583
|
+
item.links.map((l) => ({
|
|
584
|
+
itemId: item.id,
|
|
585
|
+
type: l.type,
|
|
586
|
+
targetId: l.targetId
|
|
587
|
+
}))
|
|
588
|
+
);
|
|
518
589
|
}
|
|
519
590
|
async function insertPlan(db, item) {
|
|
520
|
-
if (!item.plan) return;
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
}
|
|
539
|
-
}
|
|
591
|
+
if (!item.plan?.length) return;
|
|
592
|
+
await db.insert(planPhases).values(
|
|
593
|
+
item.plan.map((phase, pi) => ({
|
|
594
|
+
itemId: item.id,
|
|
595
|
+
idx: pi,
|
|
596
|
+
name: phase.name,
|
|
597
|
+
manualChecks: phase.manualChecks ? JSON.stringify(phase.manualChecks) : null
|
|
598
|
+
}))
|
|
599
|
+
);
|
|
600
|
+
const tasks = item.plan.flatMap(
|
|
601
|
+
(phase, pi) => phase.tasks.map((task, ti) => ({
|
|
602
|
+
itemId: item.id,
|
|
603
|
+
phaseIdx: pi,
|
|
604
|
+
idx: ti,
|
|
605
|
+
task: task.task
|
|
606
|
+
}))
|
|
607
|
+
);
|
|
608
|
+
if (tasks.length) await db.insert(planTasks).values(tasks);
|
|
540
609
|
}
|
|
541
610
|
async function insertItemRelations(db, item) {
|
|
542
611
|
await insertComments(db, item);
|
|
@@ -559,98 +628,138 @@ function remap(item, newId, oldToNew) {
|
|
|
559
628
|
};
|
|
560
629
|
return remapped;
|
|
561
630
|
}
|
|
562
|
-
async function importItemsRemapped(
|
|
563
|
-
return
|
|
631
|
+
async function importItemsRemapped(orm, items2, origin) {
|
|
632
|
+
return orm.transaction(async (tx) => {
|
|
564
633
|
const oldToNew = /* @__PURE__ */ new Map();
|
|
565
|
-
for (const item of
|
|
634
|
+
for (const item of items2) {
|
|
566
635
|
oldToNew.set(item.id, await insertItem(tx, item, origin));
|
|
567
636
|
}
|
|
568
|
-
for (const item of
|
|
637
|
+
for (const item of items2) {
|
|
569
638
|
const newId = oldToNew.get(item.id);
|
|
570
639
|
if (newId === void 0) continue;
|
|
571
640
|
await insertItemRelations(tx, remap(item, newId, oldToNew));
|
|
572
641
|
}
|
|
573
|
-
return
|
|
642
|
+
return items2.length;
|
|
574
643
|
});
|
|
575
644
|
}
|
|
576
645
|
|
|
577
|
-
// src/commands/backlog/
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
646
|
+
// src/commands/backlog/loadAllItems.ts
|
|
647
|
+
import { asc as asc2, eq as eq2 } from "drizzle-orm";
|
|
648
|
+
|
|
649
|
+
// src/commands/backlog/loadRelations.ts
|
|
650
|
+
import { asc, inArray } from "drizzle-orm";
|
|
651
|
+
function groupByItem(rows) {
|
|
652
|
+
const map = /* @__PURE__ */ new Map();
|
|
653
|
+
for (const row of rows) {
|
|
654
|
+
const bucket = map.get(row.itemId);
|
|
655
|
+
if (bucket) bucket.push(row);
|
|
656
|
+
else map.set(row.itemId, [row]);
|
|
657
|
+
}
|
|
658
|
+
return map;
|
|
659
|
+
}
|
|
660
|
+
var selectComments = (orm, ids) => orm.select().from(comments).where(inArray(comments.itemId, ids)).orderBy(asc(comments.itemId), asc(comments.idx));
|
|
661
|
+
var selectLinks = (orm, ids) => orm.select().from(links).where(inArray(links.itemId, ids)).orderBy(asc(links.itemId));
|
|
662
|
+
var selectPhases = (orm, ids) => orm.select().from(planPhases).where(inArray(planPhases.itemId, ids)).orderBy(asc(planPhases.itemId), asc(planPhases.idx));
|
|
663
|
+
var selectTasks = (orm, ids) => orm.select().from(planTasks).where(inArray(planTasks.itemId, ids)).orderBy(
|
|
664
|
+
asc(planTasks.itemId),
|
|
665
|
+
asc(planTasks.phaseIdx),
|
|
666
|
+
asc(planTasks.idx)
|
|
667
|
+
);
|
|
668
|
+
async function loadRelations(orm, ids) {
|
|
669
|
+
const [commentRows, linkRows, phaseRows, taskRows] = await Promise.all([
|
|
670
|
+
selectComments(orm, ids),
|
|
671
|
+
selectLinks(orm, ids),
|
|
672
|
+
selectPhases(orm, ids),
|
|
673
|
+
selectTasks(orm, ids)
|
|
674
|
+
]);
|
|
675
|
+
return {
|
|
676
|
+
comments: groupByItem(commentRows),
|
|
677
|
+
links: groupByItem(linkRows),
|
|
678
|
+
phases: groupByItem(phaseRows),
|
|
679
|
+
tasks: groupByItem(taskRows)
|
|
680
|
+
};
|
|
593
681
|
}
|
|
594
682
|
|
|
595
|
-
// src/commands/backlog/
|
|
596
|
-
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
683
|
+
// src/commands/backlog/rowToItem.ts
|
|
684
|
+
function rowToComment(c) {
|
|
685
|
+
const comment3 = {
|
|
686
|
+
id: c.id,
|
|
687
|
+
text: c.text,
|
|
688
|
+
timestamp: c.timestamp,
|
|
689
|
+
type: c.type
|
|
690
|
+
};
|
|
691
|
+
if (c.phase != null) comment3.phase = c.phase;
|
|
692
|
+
return comment3;
|
|
693
|
+
}
|
|
694
|
+
function rowToLink(l) {
|
|
695
|
+
return { type: l.type, targetId: l.targetId };
|
|
696
|
+
}
|
|
697
|
+
function groupTasksByPhase(taskRows) {
|
|
698
|
+
const byPhase = /* @__PURE__ */ new Map();
|
|
699
|
+
for (const t of taskRows) {
|
|
700
|
+
const bucket = byPhase.get(t.phaseIdx);
|
|
701
|
+
if (bucket) bucket.push(t);
|
|
702
|
+
else byPhase.set(t.phaseIdx, [t]);
|
|
703
|
+
}
|
|
704
|
+
return byPhase;
|
|
705
|
+
}
|
|
706
|
+
function rowToPhase(p, byPhase) {
|
|
601
707
|
const phase = {
|
|
602
708
|
name: p.name,
|
|
603
|
-
tasks:
|
|
709
|
+
tasks: (byPhase.get(p.idx) ?? []).map((t) => ({ task: t.task }))
|
|
604
710
|
};
|
|
605
|
-
if (p.
|
|
606
|
-
phase.manualChecks = JSON.parse(p.manual_checks);
|
|
607
|
-
}
|
|
711
|
+
if (p.manualChecks) phase.manualChecks = JSON.parse(p.manualChecks);
|
|
608
712
|
return phase;
|
|
609
713
|
}
|
|
610
|
-
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
[itemId]
|
|
614
|
-
);
|
|
615
|
-
if (phases.length === 0) return void 0;
|
|
616
|
-
return Promise.all(phases.map((p) => toPhase(db, itemId, p)));
|
|
714
|
+
function buildPlan(phaseRows, taskRows) {
|
|
715
|
+
const byPhase = groupTasksByPhase(taskRows);
|
|
716
|
+
return phaseRows.map((p) => rowToPhase(p, byPhase));
|
|
617
717
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
return db.all(
|
|
622
|
-
'SELECT type, target_id as "targetId" FROM links WHERE item_id = ?',
|
|
623
|
-
[itemId]
|
|
624
|
-
);
|
|
718
|
+
function assignOptionalColumns(item, row) {
|
|
719
|
+
if (row.description != null) item.description = row.description;
|
|
720
|
+
if (row.currentPhase != null) item.currentPhase = row.currentPhase;
|
|
625
721
|
}
|
|
626
|
-
|
|
627
|
-
const comments2 = await loadComments(db, row.id);
|
|
628
|
-
const links = await loadLinks(db, row.id);
|
|
629
|
-
const plan2 = await loadPlan(db, row.id);
|
|
722
|
+
function baseItem(row) {
|
|
630
723
|
const item = {
|
|
631
724
|
id: row.id,
|
|
632
725
|
type: row.type,
|
|
633
726
|
name: row.name,
|
|
634
|
-
acceptanceCriteria: JSON.parse(row.
|
|
727
|
+
acceptanceCriteria: JSON.parse(row.acceptanceCriteria),
|
|
635
728
|
status: row.status,
|
|
636
729
|
origin: row.origin
|
|
637
730
|
};
|
|
638
|
-
|
|
639
|
-
if (row.current_phase != null) item.currentPhase = row.current_phase;
|
|
640
|
-
if (comments2.length > 0) item.comments = comments2;
|
|
641
|
-
if (links && links.length > 0) item.links = links;
|
|
642
|
-
if (plan2) item.plan = plan2;
|
|
731
|
+
assignOptionalColumns(item, row);
|
|
643
732
|
return item;
|
|
644
733
|
}
|
|
645
|
-
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
734
|
+
function attachComments(item, rel, id) {
|
|
735
|
+
const comments3 = (rel.comments.get(id) ?? []).map(rowToComment);
|
|
736
|
+
if (comments3.length > 0) item.comments = comments3;
|
|
737
|
+
}
|
|
738
|
+
function attachLinks(item, rel, id) {
|
|
739
|
+
const links2 = (rel.links.get(id) ?? []).map(rowToLink);
|
|
740
|
+
if (links2.length > 0) item.links = links2;
|
|
741
|
+
}
|
|
742
|
+
function attachPlan(item, rel, id) {
|
|
743
|
+
const phases = rel.phases.get(id) ?? [];
|
|
744
|
+
if (phases.length > 0) item.plan = buildPlan(phases, rel.tasks.get(id) ?? []);
|
|
745
|
+
}
|
|
746
|
+
function rowToItem(row, rel) {
|
|
747
|
+
const item = baseItem(row);
|
|
748
|
+
attachComments(item, rel, row.id);
|
|
749
|
+
attachLinks(item, rel, row.id);
|
|
750
|
+
attachPlan(item, rel, row.id);
|
|
751
|
+
return item;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/commands/backlog/loadAllItems.ts
|
|
755
|
+
async function loadAllItems(orm, origin) {
|
|
756
|
+
const rows = await orm.select().from(items).where(origin === void 0 ? void 0 : eq2(items.origin, origin)).orderBy(asc2(items.id));
|
|
757
|
+
if (rows.length === 0) return [];
|
|
758
|
+
const rel = await loadRelations(
|
|
759
|
+
orm,
|
|
760
|
+
rows.map((r) => r.id)
|
|
652
761
|
);
|
|
653
|
-
return
|
|
762
|
+
return rows.map((row) => rowToItem(row, rel));
|
|
654
763
|
}
|
|
655
764
|
|
|
656
765
|
// src/commands/backlog/parseBacklogJsonl.ts
|
|
@@ -707,22 +816,22 @@ function parseBacklogJsonl(path52) {
|
|
|
707
816
|
function jsonlPath(dir) {
|
|
708
817
|
return join4(dir, ".assist", "backlog.jsonl");
|
|
709
818
|
}
|
|
710
|
-
async function verifyImport(
|
|
711
|
-
const reloaded = await loadAllItems(
|
|
819
|
+
async function verifyImport(orm, origin, items2, imported) {
|
|
820
|
+
const reloaded = await loadAllItems(orm, origin);
|
|
712
821
|
if (reloaded.length !== imported) {
|
|
713
822
|
throw new Error(
|
|
714
823
|
`backlog migrate: expected ${imported} item(s) for ${origin} after import but found ${reloaded.length}.`
|
|
715
824
|
);
|
|
716
825
|
}
|
|
717
|
-
if (
|
|
826
|
+
if (items2.length > 0 && !reloaded.some((i) => i.name === items2[0].name)) {
|
|
718
827
|
throw new Error(
|
|
719
|
-
`backlog migrate: spot-check failed; "${
|
|
828
|
+
`backlog migrate: spot-check failed; "${items2[0].name}" not found after import.`
|
|
720
829
|
);
|
|
721
830
|
}
|
|
722
831
|
}
|
|
723
|
-
async function migrateLocalBacklog(
|
|
832
|
+
async function migrateLocalBacklog(orm, dir, origin) {
|
|
724
833
|
if (!existsSync5(jsonlPath(dir))) return;
|
|
725
|
-
const existing = (await loadAllItems(
|
|
834
|
+
const existing = (await loadAllItems(orm, origin)).length;
|
|
726
835
|
if (existing > 0) {
|
|
727
836
|
const moved2 = backupLocalBacklogFiles(dir);
|
|
728
837
|
console.error(
|
|
@@ -733,9 +842,9 @@ async function migrateLocalBacklog(db, dir, origin) {
|
|
|
733
842
|
return;
|
|
734
843
|
}
|
|
735
844
|
gitPullBacklog(dir);
|
|
736
|
-
const
|
|
737
|
-
const imported = await importItemsRemapped(
|
|
738
|
-
await verifyImport(
|
|
845
|
+
const items2 = parseBacklogJsonl(jsonlPath(dir));
|
|
846
|
+
const imported = await importItemsRemapped(orm, items2, origin);
|
|
847
|
+
await verifyImport(orm, origin, items2, imported);
|
|
739
848
|
const moved = backupLocalBacklogFiles(dir);
|
|
740
849
|
console.error(
|
|
741
850
|
chalk4.green(
|
|
@@ -746,10 +855,10 @@ async function migrateLocalBacklog(db, dir, origin) {
|
|
|
746
855
|
|
|
747
856
|
// src/commands/backlog/ensureMigrated.ts
|
|
748
857
|
var attempted = /* @__PURE__ */ new Set();
|
|
749
|
-
async function ensureMigrated(
|
|
858
|
+
async function ensureMigrated(orm, dir, origin) {
|
|
750
859
|
if (attempted.has(origin)) return;
|
|
751
860
|
attempted.add(origin);
|
|
752
|
-
await migrateLocalBacklog(
|
|
861
|
+
await migrateLocalBacklog(orm, dir, origin);
|
|
753
862
|
}
|
|
754
863
|
|
|
755
864
|
// src/commands/backlog/findBacklogUp.ts
|
|
@@ -771,64 +880,16 @@ function findBacklogUp(startDir) {
|
|
|
771
880
|
return null;
|
|
772
881
|
}
|
|
773
882
|
|
|
774
|
-
// src/commands/backlog/
|
|
883
|
+
// src/commands/backlog/getBacklogOrm.ts
|
|
775
884
|
import chalk5 from "chalk";
|
|
776
885
|
import { Pool } from "pg";
|
|
777
886
|
|
|
778
|
-
// src/commands/backlog/
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
constructor(queryable, pool) {
|
|
785
|
-
this.queryable = queryable;
|
|
786
|
-
this.pool = pool;
|
|
787
|
-
}
|
|
788
|
-
async all(sql2, params = []) {
|
|
789
|
-
const result = await this.queryable.query(toPg(sql2), params);
|
|
790
|
-
return result.rows;
|
|
791
|
-
}
|
|
792
|
-
async get(sql2, params = []) {
|
|
793
|
-
const result = await this.queryable.query(toPg(sql2), params);
|
|
794
|
-
return result.rows[0];
|
|
795
|
-
}
|
|
796
|
-
async run(sql2, params = []) {
|
|
797
|
-
const result = await this.queryable.query(toPg(sql2), params);
|
|
798
|
-
return { changes: result.rowCount ?? result.affectedRows ?? 0 };
|
|
799
|
-
}
|
|
800
|
-
async exec(sql2) {
|
|
801
|
-
if (this.queryable.exec) {
|
|
802
|
-
await this.queryable.exec(sql2);
|
|
803
|
-
} else {
|
|
804
|
-
await this.queryable.query(sql2);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
async transaction(fn) {
|
|
808
|
-
const client = this.pool ? await this.pool.connect() : void 0;
|
|
809
|
-
const tx = client ? new _PgBacklogDb({
|
|
810
|
-
query: (sql2, params) => client.query(sql2, params)
|
|
811
|
-
}) : this;
|
|
812
|
-
try {
|
|
813
|
-
return await tx.atomic(fn);
|
|
814
|
-
} finally {
|
|
815
|
-
client?.release();
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
async atomic(fn) {
|
|
819
|
-
await this.queryable.query("BEGIN");
|
|
820
|
-
try {
|
|
821
|
-
const result = await fn(this);
|
|
822
|
-
await this.queryable.query("COMMIT");
|
|
823
|
-
return result;
|
|
824
|
-
} catch (error) {
|
|
825
|
-
await this.queryable.query("ROLLBACK");
|
|
826
|
-
throw error;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
};
|
|
830
|
-
function makeBacklogDb(queryable, pool) {
|
|
831
|
-
return new PgBacklogDb(queryable, pool);
|
|
887
|
+
// src/commands/backlog/BacklogOrm.ts
|
|
888
|
+
import {
|
|
889
|
+
drizzle as drizzleNodePg
|
|
890
|
+
} from "drizzle-orm/node-postgres";
|
|
891
|
+
function makeOrmFromPool(pool) {
|
|
892
|
+
return drizzleNodePg(pool, { schema: backlogSchema });
|
|
832
893
|
}
|
|
833
894
|
|
|
834
895
|
// src/commands/backlog/ensureSchema.ts
|
|
@@ -886,11 +947,11 @@ var SCHEMA = `
|
|
|
886
947
|
value TEXT NOT NULL
|
|
887
948
|
);
|
|
888
949
|
`;
|
|
889
|
-
async function ensureSchema(
|
|
890
|
-
await
|
|
950
|
+
async function ensureSchema(exec3) {
|
|
951
|
+
await exec3(SCHEMA);
|
|
891
952
|
}
|
|
892
953
|
|
|
893
|
-
// src/commands/backlog/
|
|
954
|
+
// src/commands/backlog/getBacklogOrm.ts
|
|
894
955
|
var DATABASE_URL_ENV = "ASSIST_BACKLOG_DATABASE_URL";
|
|
895
956
|
var MISSING_URL_MESSAGE = `No backlog database configured.
|
|
896
957
|
|
|
@@ -911,27 +972,23 @@ function getDatabaseUrl() {
|
|
|
911
972
|
}
|
|
912
973
|
return url;
|
|
913
974
|
}
|
|
914
|
-
var _db;
|
|
915
975
|
var _connecting;
|
|
916
976
|
var _pool;
|
|
917
|
-
|
|
918
|
-
|
|
977
|
+
var _orm;
|
|
978
|
+
function getBacklogOrm() {
|
|
979
|
+
if (_orm) return Promise.resolve(_orm);
|
|
919
980
|
if (_connecting) return _connecting;
|
|
920
981
|
_connecting = (async () => {
|
|
921
982
|
const pool = new Pool({ connectionString: getDatabaseUrl() });
|
|
922
983
|
_pool = pool;
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
);
|
|
927
|
-
await ensureSchema(db);
|
|
928
|
-
_db = db;
|
|
929
|
-
return db;
|
|
984
|
+
await ensureSchema((sql4) => pool.query(sql4));
|
|
985
|
+
_orm = makeOrmFromPool(pool);
|
|
986
|
+
return _orm;
|
|
930
987
|
})();
|
|
931
988
|
return _connecting;
|
|
932
989
|
}
|
|
933
990
|
async function withBacklogClient(fn) {
|
|
934
|
-
await
|
|
991
|
+
await getBacklogOrm();
|
|
935
992
|
const pool = _pool;
|
|
936
993
|
if (!pool) {
|
|
937
994
|
throw new Error("COPY streaming requires a pooled Postgres connection.");
|
|
@@ -947,7 +1004,7 @@ async function closeBacklogDb() {
|
|
|
947
1004
|
const pool = _pool;
|
|
948
1005
|
if (!pool) return;
|
|
949
1006
|
_pool = void 0;
|
|
950
|
-
|
|
1007
|
+
_orm = void 0;
|
|
951
1008
|
_connecting = void 0;
|
|
952
1009
|
await pool.end();
|
|
953
1010
|
}
|
|
@@ -1001,55 +1058,44 @@ function getCurrentOrigin(cwd) {
|
|
|
1001
1058
|
return `local:${root ?? cwd}`;
|
|
1002
1059
|
}
|
|
1003
1060
|
|
|
1061
|
+
// src/commands/backlog/saveAllItems.ts
|
|
1062
|
+
import { eq as eq4, sql } from "drizzle-orm";
|
|
1063
|
+
|
|
1064
|
+
// src/commands/backlog/deleteItemRelations.ts
|
|
1065
|
+
import { eq as eq3 } from "drizzle-orm";
|
|
1066
|
+
async function deleteItemRelations(db, itemId) {
|
|
1067
|
+
await db.delete(planTasks).where(eq3(planTasks.itemId, itemId));
|
|
1068
|
+
await db.delete(planPhases).where(eq3(planPhases.itemId, itemId));
|
|
1069
|
+
await db.delete(comments).where(eq3(comments.itemId, itemId));
|
|
1070
|
+
await db.delete(links).where(eq3(links.itemId, itemId));
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1004
1073
|
// src/commands/backlog/saveAllItems.ts
|
|
1005
1074
|
async function upsertItem(db, item, origin) {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1009
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
1010
|
-
origin = excluded.origin,
|
|
1011
|
-
type = excluded.type,
|
|
1012
|
-
name = excluded.name,
|
|
1013
|
-
description = excluded.description,
|
|
1014
|
-
acceptance_criteria = excluded.acceptance_criteria,
|
|
1015
|
-
status = excluded.status,
|
|
1016
|
-
current_phase = excluded.current_phase`,
|
|
1017
|
-
[
|
|
1018
|
-
item.id,
|
|
1019
|
-
origin,
|
|
1020
|
-
item.type,
|
|
1021
|
-
item.name,
|
|
1022
|
-
item.description ?? null,
|
|
1023
|
-
JSON.stringify(item.acceptanceCriteria),
|
|
1024
|
-
item.status,
|
|
1025
|
-
item.currentPhase ?? null
|
|
1026
|
-
]
|
|
1027
|
-
);
|
|
1075
|
+
const values = itemColumns(item, origin);
|
|
1076
|
+
await db.insert(items).values({ id: item.id, ...values }).onConflictDoUpdate({ target: items.id, set: values });
|
|
1028
1077
|
await deleteItemRelations(db, item.id);
|
|
1029
1078
|
await insertItemRelations(db, item);
|
|
1030
1079
|
}
|
|
1031
1080
|
async function resyncSequences(db) {
|
|
1032
|
-
await db.
|
|
1033
|
-
|
|
1081
|
+
await db.execute(
|
|
1082
|
+
sql`SELECT setval(pg_get_serial_sequence('items', 'id'), COALESCE((SELECT MAX(id) FROM items), 0) + 1, false)`
|
|
1034
1083
|
);
|
|
1035
|
-
await db.
|
|
1036
|
-
|
|
1084
|
+
await db.execute(
|
|
1085
|
+
sql`SELECT setval(pg_get_serial_sequence('comments', 'id'), COALESCE((SELECT MAX(id) FROM comments), 0) + 1, false)`
|
|
1037
1086
|
);
|
|
1038
1087
|
}
|
|
1039
|
-
async function saveAllItems(
|
|
1040
|
-
await
|
|
1041
|
-
const existingIds = await tx.
|
|
1042
|
-
|
|
1043
|
-
[origin]
|
|
1044
|
-
);
|
|
1045
|
-
const newIds = new Set(items.map((i) => i.id));
|
|
1088
|
+
async function saveAllItems(orm, file, origin) {
|
|
1089
|
+
await orm.transaction(async (tx) => {
|
|
1090
|
+
const existingIds = await tx.select({ id: items.id }).from(items).where(eq4(items.origin, origin));
|
|
1091
|
+
const newIds = new Set(file.map((i) => i.id));
|
|
1046
1092
|
for (const { id } of existingIds) {
|
|
1047
1093
|
if (!newIds.has(id)) {
|
|
1048
1094
|
await deleteItemRelations(tx, id);
|
|
1049
|
-
await tx.
|
|
1095
|
+
await tx.delete(items).where(eq4(items.id, id));
|
|
1050
1096
|
}
|
|
1051
1097
|
}
|
|
1052
|
-
for (const item of
|
|
1098
|
+
for (const item of file) {
|
|
1053
1099
|
await upsertItem(tx, item, origin);
|
|
1054
1100
|
}
|
|
1055
1101
|
await resyncSequences(tx);
|
|
@@ -1057,51 +1103,35 @@ async function saveAllItems(db, items, origin) {
|
|
|
1057
1103
|
}
|
|
1058
1104
|
|
|
1059
1105
|
// src/commands/backlog/searchItemIds.ts
|
|
1060
|
-
|
|
1106
|
+
import { and, asc as asc3, eq as eq5, ilike, or } from "drizzle-orm";
|
|
1107
|
+
async function searchItemIds(orm, query, origin) {
|
|
1061
1108
|
const pattern2 = `%${query}%`;
|
|
1062
|
-
const rows = await
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
)
|
|
1075
|
-
ORDER BY i.id`,
|
|
1076
|
-
[
|
|
1077
|
-
origin ?? null,
|
|
1078
|
-
origin ?? null,
|
|
1079
|
-
pattern2,
|
|
1080
|
-
pattern2,
|
|
1081
|
-
pattern2,
|
|
1082
|
-
pattern2,
|
|
1083
|
-
pattern2
|
|
1084
|
-
]
|
|
1085
|
-
);
|
|
1109
|
+
const rows = await orm.selectDistinct({ id: items.id }).from(items).leftJoin(comments, eq5(comments.itemId, items.id)).leftJoin(planPhases, eq5(planPhases.itemId, items.id)).where(
|
|
1110
|
+
and(
|
|
1111
|
+
origin ? eq5(items.origin, origin) : void 0,
|
|
1112
|
+
or(
|
|
1113
|
+
ilike(items.name, pattern2),
|
|
1114
|
+
ilike(items.description, pattern2),
|
|
1115
|
+
ilike(items.acceptanceCriteria, pattern2),
|
|
1116
|
+
ilike(comments.text, pattern2),
|
|
1117
|
+
ilike(planPhases.name, pattern2)
|
|
1118
|
+
)
|
|
1119
|
+
)
|
|
1120
|
+
).orderBy(asc3(items.id));
|
|
1086
1121
|
return rows.map((r) => r.id);
|
|
1087
1122
|
}
|
|
1088
1123
|
|
|
1089
1124
|
// src/commands/backlog/updateCurrentPhase.ts
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
[phase, id]
|
|
1094
|
-
);
|
|
1095
|
-
return result.changes > 0;
|
|
1125
|
+
import { eq as eq6 } from "drizzle-orm";
|
|
1126
|
+
async function updateCurrentPhase(orm, id, phase) {
|
|
1127
|
+
await orm.update(items).set({ currentPhase: phase }).where(eq6(items.id, id));
|
|
1096
1128
|
}
|
|
1097
1129
|
|
|
1098
1130
|
// src/commands/backlog/updateStatus.ts
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
]);
|
|
1104
|
-
return result.changes > 0;
|
|
1131
|
+
import { eq as eq7 } from "drizzle-orm";
|
|
1132
|
+
async function updateStatus(orm, id, status2) {
|
|
1133
|
+
const [row] = await orm.update(items).set({ status: status2 }).where(eq7(items.id, id)).returning({ name: items.name });
|
|
1134
|
+
return row?.name;
|
|
1105
1135
|
}
|
|
1106
1136
|
|
|
1107
1137
|
// src/commands/backlog/shared.ts
|
|
@@ -1115,58 +1145,54 @@ function getBacklogDir() {
|
|
|
1115
1145
|
function getOrigin() {
|
|
1116
1146
|
return getCurrentOrigin(getBacklogDir());
|
|
1117
1147
|
}
|
|
1118
|
-
async function
|
|
1119
|
-
const
|
|
1148
|
+
async function getReady() {
|
|
1149
|
+
const orm = await getBacklogOrm();
|
|
1120
1150
|
const dir = getBacklogDir();
|
|
1121
|
-
await ensureMigrated(
|
|
1122
|
-
return
|
|
1151
|
+
await ensureMigrated(orm, dir, getCurrentOrigin(dir));
|
|
1152
|
+
return { orm };
|
|
1123
1153
|
}
|
|
1124
1154
|
async function loadBacklog(allRepos = false) {
|
|
1125
|
-
const
|
|
1126
|
-
return loadAllItems(
|
|
1155
|
+
const { orm } = await getReady();
|
|
1156
|
+
return loadAllItems(orm, allRepos ? void 0 : getOrigin());
|
|
1127
1157
|
}
|
|
1128
1158
|
async function searchBacklog(query) {
|
|
1129
|
-
const
|
|
1159
|
+
const { orm } = await getReady();
|
|
1130
1160
|
const origin = getOrigin();
|
|
1131
|
-
const ids = await searchItemIds(
|
|
1132
|
-
const allItems = await loadAllItems(
|
|
1161
|
+
const ids = await searchItemIds(orm, query, origin);
|
|
1162
|
+
const allItems = await loadAllItems(orm, origin);
|
|
1133
1163
|
return allItems.filter((item) => ids.includes(item.id));
|
|
1134
1164
|
}
|
|
1135
|
-
async function saveBacklog(
|
|
1136
|
-
const
|
|
1137
|
-
await saveAllItems(
|
|
1165
|
+
async function saveBacklog(items2) {
|
|
1166
|
+
const { orm } = await getReady();
|
|
1167
|
+
await saveAllItems(orm, items2, getOrigin());
|
|
1138
1168
|
}
|
|
1139
|
-
function findItem(
|
|
1140
|
-
return
|
|
1169
|
+
function findItem(items2, id) {
|
|
1170
|
+
return items2.find((item) => item.id === id);
|
|
1141
1171
|
}
|
|
1142
1172
|
async function loadAndFindItem(id) {
|
|
1143
|
-
const
|
|
1144
|
-
const item = findItem(
|
|
1173
|
+
const items2 = await loadBacklog();
|
|
1174
|
+
const item = findItem(items2, Number.parseInt(id, 10));
|
|
1145
1175
|
if (!item) {
|
|
1146
1176
|
console.log(chalk6.red(`Item #${id} not found.`));
|
|
1147
1177
|
return void 0;
|
|
1148
1178
|
}
|
|
1149
|
-
return { items, item };
|
|
1179
|
+
return { items: items2, item };
|
|
1150
1180
|
}
|
|
1151
1181
|
async function setStatus(id, status2) {
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
return result.item.name;
|
|
1182
|
+
const { orm } = await getReady();
|
|
1183
|
+
const name = await updateStatus(orm, Number.parseInt(id, 10), status2);
|
|
1184
|
+
if (name === void 0) console.log(chalk6.red(`Item #${id} not found.`));
|
|
1185
|
+
return name;
|
|
1157
1186
|
}
|
|
1158
1187
|
async function setCurrentPhase(id, phase) {
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
const db = await getBacklogDb();
|
|
1162
|
-
await updateCurrentPhase(db, result.item.id, phase);
|
|
1188
|
+
const { orm } = await getReady();
|
|
1189
|
+
await updateCurrentPhase(orm, Number.parseInt(id, 10), phase);
|
|
1163
1190
|
}
|
|
1164
1191
|
async function removeItem(id) {
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
return result.item.name;
|
|
1192
|
+
const { orm } = await getReady();
|
|
1193
|
+
const name = await deleteItem(orm, Number.parseInt(id, 10));
|
|
1194
|
+
if (name === void 0) console.log(chalk6.red(`Item #${id} not found.`));
|
|
1195
|
+
return name;
|
|
1170
1196
|
}
|
|
1171
1197
|
|
|
1172
1198
|
// src/commands/backlog/acquireLock.ts
|
|
@@ -1232,17 +1258,17 @@ function phaseLabel(item) {
|
|
|
1232
1258
|
if (!item.plan) return "";
|
|
1233
1259
|
return chalk7.dim(` (phase ${item.currentPhase ?? 1}/${item.plan.length})`);
|
|
1234
1260
|
}
|
|
1235
|
-
function isBlocked(item,
|
|
1261
|
+
function isBlocked(item, items2) {
|
|
1236
1262
|
const deps2 = (item.links ?? []).filter((l) => l.type === "depends-on");
|
|
1237
1263
|
return deps2.some((dep) => {
|
|
1238
|
-
const target =
|
|
1264
|
+
const target = items2.find((i) => i.id === dep.targetId);
|
|
1239
1265
|
return target !== void 0 && target.status !== "done";
|
|
1240
1266
|
});
|
|
1241
1267
|
}
|
|
1242
|
-
function dependencyLabel(item,
|
|
1268
|
+
function dependencyLabel(item, items2) {
|
|
1243
1269
|
const deps2 = (item.links ?? []).filter((l) => l.type === "depends-on");
|
|
1244
1270
|
if (deps2.length === 0) return "";
|
|
1245
|
-
if (isBlocked(item,
|
|
1271
|
+
if (isBlocked(item, items2)) return chalk7.red(" [blocked]");
|
|
1246
1272
|
return chalk7.dim(` [${deps2.length} dep${deps2.length > 1 ? "s" : ""}]`);
|
|
1247
1273
|
}
|
|
1248
1274
|
function printVerboseDetails(item) {
|
|
@@ -1259,21 +1285,21 @@ function printVerboseDetails(item) {
|
|
|
1259
1285
|
}
|
|
1260
1286
|
|
|
1261
1287
|
// src/commands/backlog/findResumable.ts
|
|
1262
|
-
function findResumable(
|
|
1263
|
-
return
|
|
1264
|
-
(i) => i.status === "in-progress" && i.plan && !isLockedByOther(i.id) && !isBlocked(i,
|
|
1288
|
+
function findResumable(items2) {
|
|
1289
|
+
return items2.find(
|
|
1290
|
+
(i) => i.status === "in-progress" && i.plan && !isLockedByOther(i.id) && !isBlocked(i, items2)
|
|
1265
1291
|
);
|
|
1266
1292
|
}
|
|
1267
1293
|
|
|
1268
1294
|
// src/commands/backlog/findUnblockedTodos.ts
|
|
1269
1295
|
import chalk8 from "chalk";
|
|
1270
|
-
function findUnblockedTodos(
|
|
1271
|
-
const todo =
|
|
1296
|
+
function findUnblockedTodos(items2) {
|
|
1297
|
+
const todo = items2.filter((i) => i.status === "todo");
|
|
1272
1298
|
if (todo.length === 0) {
|
|
1273
1299
|
console.log(chalk8.green("All backlog items complete."));
|
|
1274
1300
|
return void 0;
|
|
1275
1301
|
}
|
|
1276
|
-
const unblocked = todo.filter((i) => !isBlocked(i,
|
|
1302
|
+
const unblocked = todo.filter((i) => !isBlocked(i, items2));
|
|
1277
1303
|
if (unblocked.length === 0) {
|
|
1278
1304
|
console.log(
|
|
1279
1305
|
chalk8.yellow("All remaining todo items are blocked by dependencies.")
|
|
@@ -1344,9 +1370,9 @@ function spawnClaude(prompt, options2 = {}) {
|
|
|
1344
1370
|
}
|
|
1345
1371
|
|
|
1346
1372
|
// src/commands/backlog/buildCommentLines.ts
|
|
1347
|
-
function buildCommentLines(
|
|
1348
|
-
if (!
|
|
1349
|
-
return ["", "Comments:", ...
|
|
1373
|
+
function buildCommentLines(comments3) {
|
|
1374
|
+
if (!comments3?.length) return [];
|
|
1375
|
+
return ["", "Comments:", ...comments3.map(formatPromptComment)];
|
|
1350
1376
|
}
|
|
1351
1377
|
function formatPromptComment(entry) {
|
|
1352
1378
|
const id = entry.id !== void 0 ? `#${entry.id} ` : "";
|
|
@@ -1503,8 +1529,8 @@ function cleanupSignal() {
|
|
|
1503
1529
|
}
|
|
1504
1530
|
}
|
|
1505
1531
|
async function isTerminalStatus(itemId) {
|
|
1506
|
-
const
|
|
1507
|
-
const item =
|
|
1532
|
+
const items2 = await loadBacklog();
|
|
1533
|
+
const item = items2.find((i) => i.id === itemId);
|
|
1508
1534
|
return item?.status === "done" || item?.status === "wontdo";
|
|
1509
1535
|
}
|
|
1510
1536
|
async function resolvePhaseResult(phaseIndex, itemId) {
|
|
@@ -1660,23 +1686,23 @@ async function ensureDone(id) {
|
|
|
1660
1686
|
}
|
|
1661
1687
|
|
|
1662
1688
|
// src/commands/backlog/next.ts
|
|
1663
|
-
function toChoice(item,
|
|
1689
|
+
function toChoice(item, items2) {
|
|
1664
1690
|
const name = `${typeLabel(item.type)} #${item.id}: ${item.name}`;
|
|
1665
|
-
return isBlocked(item,
|
|
1691
|
+
return isBlocked(item, items2) ? { name, disabled: chalk13.red("[blocked]") } : { name };
|
|
1666
1692
|
}
|
|
1667
|
-
async function selectItem(todo,
|
|
1693
|
+
async function selectItem(todo, items2) {
|
|
1668
1694
|
const { selected } = await exitOnCancel(
|
|
1669
1695
|
enquirer2.prompt({
|
|
1670
1696
|
type: "select",
|
|
1671
1697
|
name: "selected",
|
|
1672
1698
|
message: "Choose a backlog item to start:",
|
|
1673
|
-
choices: todo.map((i) => toChoice(i,
|
|
1699
|
+
choices: todo.map((i) => toChoice(i, items2))
|
|
1674
1700
|
})
|
|
1675
1701
|
);
|
|
1676
1702
|
return selected.match(/#(\d+)/)?.[1] ?? "";
|
|
1677
1703
|
}
|
|
1678
|
-
async function pickItem(
|
|
1679
|
-
const resumable = findResumable(
|
|
1704
|
+
async function pickItem(items2, firstPick = false) {
|
|
1705
|
+
const resumable = findResumable(items2);
|
|
1680
1706
|
if (resumable) {
|
|
1681
1707
|
console.log(
|
|
1682
1708
|
chalk13.bold(
|
|
@@ -1685,15 +1711,15 @@ async function pickItem(items, firstPick = false) {
|
|
|
1685
1711
|
);
|
|
1686
1712
|
return String(resumable.id);
|
|
1687
1713
|
}
|
|
1688
|
-
const unblocked = findUnblockedTodos(
|
|
1714
|
+
const unblocked = findUnblockedTodos(items2);
|
|
1689
1715
|
if (!unblocked) return void 0;
|
|
1690
1716
|
if (firstPick && unblocked.length === 1) {
|
|
1691
1717
|
const item = unblocked[0];
|
|
1692
1718
|
console.log(chalk13.bold(`Auto-selecting item #${item.id}: ${item.name}`));
|
|
1693
1719
|
return String(item.id);
|
|
1694
1720
|
}
|
|
1695
|
-
const todo =
|
|
1696
|
-
return selectItem(todo,
|
|
1721
|
+
const todo = items2.filter((i) => i.status === "todo");
|
|
1722
|
+
return selectItem(todo, items2);
|
|
1697
1723
|
}
|
|
1698
1724
|
async function next(options2) {
|
|
1699
1725
|
if (blockedByHandover()) return;
|
|
@@ -1710,46 +1736,50 @@ async function next(options2) {
|
|
|
1710
1736
|
// src/commands/backlog/phaseDone.ts
|
|
1711
1737
|
import chalk14 from "chalk";
|
|
1712
1738
|
|
|
1713
|
-
// src/commands/backlog/
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1739
|
+
// src/commands/backlog/appendComment.ts
|
|
1740
|
+
import { sql as sql2 } from "drizzle-orm";
|
|
1741
|
+
async function appendComment(orm, itemId, text2, opts = {}) {
|
|
1742
|
+
await orm.insert(comments).values({
|
|
1743
|
+
itemId,
|
|
1744
|
+
idx: sql2`(SELECT COALESCE(MAX(${comments.idx}) + 1, 0) FROM ${comments} WHERE ${comments.itemId} = ${itemId})`,
|
|
1745
|
+
text: text2,
|
|
1746
|
+
phase: opts.phase ?? null,
|
|
1717
1747
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1718
|
-
type: "comment"
|
|
1719
|
-
|
|
1720
|
-
};
|
|
1721
|
-
if (!item.comments) item.comments = [];
|
|
1722
|
-
item.comments.push(entry);
|
|
1748
|
+
type: opts.type ?? "comment"
|
|
1749
|
+
});
|
|
1723
1750
|
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
};
|
|
1731
|
-
if (!item.comments) item.comments = [];
|
|
1732
|
-
item.comments.push(entry);
|
|
1751
|
+
|
|
1752
|
+
// src/commands/backlog/getItemStatus.ts
|
|
1753
|
+
import { eq as eq8 } from "drizzle-orm";
|
|
1754
|
+
async function getItemStatus(orm, id) {
|
|
1755
|
+
const [row] = await orm.select({ status: items.status }).from(items).where(eq8(items.id, id));
|
|
1756
|
+
return row?.status;
|
|
1733
1757
|
}
|
|
1734
1758
|
|
|
1735
1759
|
// src/commands/backlog/phaseDone.ts
|
|
1736
1760
|
async function phaseDone(id, phase, summary) {
|
|
1737
1761
|
const phaseNumber = Number.parseInt(phase, 10);
|
|
1738
1762
|
const phaseIndex = phaseNumber - 1;
|
|
1763
|
+
const itemId = Number.parseInt(id, 10);
|
|
1739
1764
|
writeSignal("phase-done", {
|
|
1740
|
-
itemId
|
|
1765
|
+
itemId,
|
|
1741
1766
|
phaseIndex,
|
|
1742
1767
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1743
1768
|
});
|
|
1744
|
-
const
|
|
1745
|
-
|
|
1746
|
-
|
|
1769
|
+
const { orm } = await getReady();
|
|
1770
|
+
const status2 = await getItemStatus(orm, itemId);
|
|
1771
|
+
if (status2 === void 0) {
|
|
1772
|
+
console.log(chalk14.red(`Item #${id} not found.`));
|
|
1747
1773
|
return;
|
|
1748
1774
|
}
|
|
1749
|
-
if (
|
|
1750
|
-
|
|
1751
|
-
|
|
1775
|
+
if (status2 === "done") {
|
|
1776
|
+
console.log(chalk14.dim(`Item #${id} already done, skipping phase advance.`));
|
|
1777
|
+
return;
|
|
1752
1778
|
}
|
|
1779
|
+
await appendComment(orm, itemId, summary, {
|
|
1780
|
+
phase: phaseNumber,
|
|
1781
|
+
type: "summary"
|
|
1782
|
+
});
|
|
1753
1783
|
await setCurrentPhase(id, phaseNumber + 1);
|
|
1754
1784
|
console.log(
|
|
1755
1785
|
chalk14.green(`Phase ${phaseNumber} of item #${id} marked as complete.`)
|
|
@@ -1793,12 +1823,12 @@ function formatComment(entry) {
|
|
|
1793
1823
|
|
|
1794
1824
|
// src/commands/backlog/show/printLinks.ts
|
|
1795
1825
|
import chalk17 from "chalk";
|
|
1796
|
-
function printLinks(item,
|
|
1797
|
-
const
|
|
1798
|
-
if (
|
|
1826
|
+
function printLinks(item, items2) {
|
|
1827
|
+
const links2 = item.links ?? [];
|
|
1828
|
+
if (links2.length === 0) return;
|
|
1799
1829
|
console.log(chalk17.bold("Links"));
|
|
1800
|
-
for (const link3 of
|
|
1801
|
-
const target =
|
|
1830
|
+
for (const link3 of links2) {
|
|
1831
|
+
const target = items2.find((i) => i.id === link3.targetId);
|
|
1802
1832
|
const typeLabel2 = link3.type === "depends-on" ? chalk17.red("depends-on") : chalk17.blue("relates-to");
|
|
1803
1833
|
if (target) {
|
|
1804
1834
|
console.log(
|
|
@@ -1837,14 +1867,14 @@ function printPlan(item) {
|
|
|
1837
1867
|
}
|
|
1838
1868
|
console.log();
|
|
1839
1869
|
}
|
|
1840
|
-
function phaseHeader(
|
|
1841
|
-
const phaseNumber =
|
|
1870
|
+
function phaseHeader(index2, name, isCurrent) {
|
|
1871
|
+
const phaseNumber = index2 + 1;
|
|
1842
1872
|
const marker = isCurrent ? chalk19.green("\u25B6 ") : " ";
|
|
1843
1873
|
const label2 = isCurrent ? chalk19.green.bold(`Phase ${phaseNumber}: ${name}`) : `${chalk19.bold(`Phase ${phaseNumber}:`)} ${name}`;
|
|
1844
1874
|
return `${marker}${label2}`;
|
|
1845
1875
|
}
|
|
1846
|
-
function printPhase(phase,
|
|
1847
|
-
console.log(phaseHeader(
|
|
1876
|
+
function printPhase(phase, index2, isCurrent) {
|
|
1877
|
+
console.log(phaseHeader(index2, phase.name, isCurrent));
|
|
1848
1878
|
printPhaseTasks(phase);
|
|
1849
1879
|
}
|
|
1850
1880
|
function printHeader(item) {
|
|
@@ -1865,7 +1895,7 @@ function printAcceptanceCriteria(criteria) {
|
|
|
1865
1895
|
async function show(id) {
|
|
1866
1896
|
const result = await loadAndFindItem(id);
|
|
1867
1897
|
if (!result) process.exit(1);
|
|
1868
|
-
const { item, items } = result;
|
|
1898
|
+
const { item, items: items2 } = result;
|
|
1869
1899
|
printHeader(item);
|
|
1870
1900
|
if (item.description) {
|
|
1871
1901
|
console.log(chalk19.bold("Description"));
|
|
@@ -1873,7 +1903,7 @@ async function show(id) {
|
|
|
1873
1903
|
console.log();
|
|
1874
1904
|
}
|
|
1875
1905
|
printAcceptanceCriteria(item.acceptanceCriteria);
|
|
1876
|
-
printLinks(item,
|
|
1906
|
+
printLinks(item, items2);
|
|
1877
1907
|
printPlan(item);
|
|
1878
1908
|
printComments(item);
|
|
1879
1909
|
}
|
|
@@ -2009,7 +2039,7 @@ async function parseRewindBody(req) {
|
|
|
2009
2039
|
// src/commands/backlog/web/createItem.ts
|
|
2010
2040
|
async function createItem(req, res) {
|
|
2011
2041
|
const body = await parseItemBody(req);
|
|
2012
|
-
const
|
|
2042
|
+
const orm = await getBacklogOrm();
|
|
2013
2043
|
const newItem = {
|
|
2014
2044
|
type: body.type ?? "story",
|
|
2015
2045
|
name: body.name,
|
|
@@ -2017,15 +2047,37 @@ async function createItem(req, res) {
|
|
|
2017
2047
|
acceptanceCriteria: body.acceptanceCriteria ?? [],
|
|
2018
2048
|
status: "todo"
|
|
2019
2049
|
};
|
|
2020
|
-
const id = await insertItem(
|
|
2050
|
+
const id = await insertItem(orm, newItem, getOrigin());
|
|
2021
2051
|
respondJson(res, 201, { id, ...newItem });
|
|
2022
2052
|
}
|
|
2023
2053
|
|
|
2054
|
+
// src/commands/backlog/addComment.ts
|
|
2055
|
+
function addComment(item, text2, phase) {
|
|
2056
|
+
const entry = {
|
|
2057
|
+
text: text2,
|
|
2058
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2059
|
+
type: "comment",
|
|
2060
|
+
...phase !== void 0 && { phase }
|
|
2061
|
+
};
|
|
2062
|
+
if (!item.comments) item.comments = [];
|
|
2063
|
+
item.comments.push(entry);
|
|
2064
|
+
}
|
|
2065
|
+
function addPhaseSummary(item, text2, phase) {
|
|
2066
|
+
const entry = {
|
|
2067
|
+
text: text2,
|
|
2068
|
+
phase,
|
|
2069
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2070
|
+
type: "summary"
|
|
2071
|
+
};
|
|
2072
|
+
if (!item.comments) item.comments = [];
|
|
2073
|
+
item.comments.push(entry);
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2024
2076
|
// src/commands/backlog/web/rewindItemPhase.ts
|
|
2025
2077
|
async function rewindItemPhase(req, res, id) {
|
|
2026
2078
|
const { phase, reason } = await parseRewindBody(req);
|
|
2027
|
-
const
|
|
2028
|
-
const item =
|
|
2079
|
+
const items2 = await loadBacklog();
|
|
2080
|
+
const item = items2.find((i) => i.id === id);
|
|
2029
2081
|
if (!item) {
|
|
2030
2082
|
respondJson(res, 404, { error: "Not found" });
|
|
2031
2083
|
return;
|
|
@@ -2043,7 +2095,7 @@ async function rewindItemPhase(req, res, id) {
|
|
|
2043
2095
|
);
|
|
2044
2096
|
item.currentPhase = phase;
|
|
2045
2097
|
item.status = "in-progress";
|
|
2046
|
-
await saveBacklog(
|
|
2098
|
+
await saveBacklog(items2);
|
|
2047
2099
|
respondJson(res, 200, item);
|
|
2048
2100
|
}
|
|
2049
2101
|
function validateRewind(item, phase) {
|
|
@@ -2067,13 +2119,13 @@ async function listItems(req, res) {
|
|
|
2067
2119
|
respondJson(res, 200, q ? await searchBacklog(q) : await loadBacklog());
|
|
2068
2120
|
}
|
|
2069
2121
|
async function findItemOr404(res, id) {
|
|
2070
|
-
const
|
|
2071
|
-
const item =
|
|
2122
|
+
const items2 = await loadBacklog();
|
|
2123
|
+
const item = items2.find((i) => i.id === id);
|
|
2072
2124
|
if (!item) {
|
|
2073
2125
|
respondJson(res, 404, { error: "Not found" });
|
|
2074
2126
|
return void 0;
|
|
2075
2127
|
}
|
|
2076
|
-
return { items, item };
|
|
2128
|
+
return { items: items2, item };
|
|
2077
2129
|
}
|
|
2078
2130
|
async function getItemById(res, id) {
|
|
2079
2131
|
const result = await findItemOr404(res, id);
|
|
@@ -2539,8 +2591,8 @@ function safeParse(line) {
|
|
|
2539
2591
|
function extractName(entry) {
|
|
2540
2592
|
const msg = entry.message;
|
|
2541
2593
|
const content = msg?.content;
|
|
2542
|
-
const
|
|
2543
|
-
return
|
|
2594
|
+
const text2 = typeof content === "string" ? content : Array.isArray(content) ? content.find((c) => c.type === "text")?.text ?? "" : "";
|
|
2595
|
+
return text2.replace(/<command-[^>]*>[^<]*<\/command-[^>]*>/g, "").trim().slice(0, 80);
|
|
2544
2596
|
}
|
|
2545
2597
|
|
|
2546
2598
|
// src/commands/sessions/web/parseSessionFile.ts
|
|
@@ -2839,9 +2891,9 @@ import chalk22 from "chalk";
|
|
|
2839
2891
|
// src/commands/backlog/tryRunById.ts
|
|
2840
2892
|
import chalk21 from "chalk";
|
|
2841
2893
|
async function tryRunById(id, options2) {
|
|
2842
|
-
const
|
|
2894
|
+
const items2 = await loadBacklog();
|
|
2843
2895
|
const numericId = Number.parseInt(id, 10);
|
|
2844
|
-
const item = Number.isNaN(numericId) ? void 0 :
|
|
2896
|
+
const item = Number.isNaN(numericId) ? void 0 : items2.find((i) => i.id === numericId);
|
|
2845
2897
|
if (!item) {
|
|
2846
2898
|
console.log(chalk21.red(`Item #${id} not found.`));
|
|
2847
2899
|
return false;
|
|
@@ -2854,7 +2906,7 @@ async function tryRunById(id, options2) {
|
|
|
2854
2906
|
console.log(chalk21.red(`Item #${id} is marked won't do.`));
|
|
2855
2907
|
return false;
|
|
2856
2908
|
}
|
|
2857
|
-
if (isBlocked(item,
|
|
2909
|
+
if (isBlocked(item, items2)) {
|
|
2858
2910
|
console.log(
|
|
2859
2911
|
chalk21.red(`Item #${id} is blocked by unresolved dependencies.`)
|
|
2860
2912
|
);
|
|
@@ -2889,8 +2941,8 @@ async function launchMode(slashCommand) {
|
|
|
2889
2941
|
import chalk23 from "chalk";
|
|
2890
2942
|
import enquirer3 from "enquirer";
|
|
2891
2943
|
async function pickItemForRefine() {
|
|
2892
|
-
const
|
|
2893
|
-
const active =
|
|
2944
|
+
const items2 = await loadBacklog();
|
|
2945
|
+
const active = items2.filter(
|
|
2894
2946
|
(i) => i.status === "todo" || i.status === "in-progress"
|
|
2895
2947
|
);
|
|
2896
2948
|
if (active.length === 0) {
|
|
@@ -3606,8 +3658,8 @@ var options = [
|
|
|
3606
3658
|
];
|
|
3607
3659
|
|
|
3608
3660
|
// src/commands/verify/init/getAvailableOptions/index.ts
|
|
3609
|
-
function resolveDescription(
|
|
3610
|
-
return typeof
|
|
3661
|
+
function resolveDescription(desc2, setup2) {
|
|
3662
|
+
return typeof desc2 === "function" ? desc2(setup2) : desc2;
|
|
3611
3663
|
}
|
|
3612
3664
|
function toVerifyOption(def, setup2) {
|
|
3613
3665
|
return {
|
|
@@ -4332,10 +4384,10 @@ ${failed2.length} script(s) failed:`);
|
|
|
4332
4384
|
console.error(` - ${f.script} (exit code ${f.code})`);
|
|
4333
4385
|
}
|
|
4334
4386
|
}
|
|
4335
|
-
function createTimerCallback(taskStatuses,
|
|
4387
|
+
function createTimerCallback(taskStatuses, index2) {
|
|
4336
4388
|
return (exitCode) => {
|
|
4337
|
-
taskStatuses[
|
|
4338
|
-
taskStatuses[
|
|
4389
|
+
taskStatuses[index2].endTime = Date.now();
|
|
4390
|
+
taskStatuses[index2].code = exitCode;
|
|
4339
4391
|
printTaskStatuses(taskStatuses);
|
|
4340
4392
|
};
|
|
4341
4393
|
}
|
|
@@ -4414,9 +4466,9 @@ function runAllEntries(entries, timer) {
|
|
|
4414
4466
|
const taskStatuses = initTaskStatuses(entries.map((e) => e.name));
|
|
4415
4467
|
return Promise.all(
|
|
4416
4468
|
entries.map(
|
|
4417
|
-
(entry,
|
|
4469
|
+
(entry, index2) => runEntry(
|
|
4418
4470
|
entry,
|
|
4419
|
-
timer ? createTimerCallback(taskStatuses,
|
|
4471
|
+
timer ? createTimerCallback(taskStatuses, index2) : void 0
|
|
4420
4472
|
)
|
|
4421
4473
|
)
|
|
4422
4474
|
);
|
|
@@ -4900,13 +4952,13 @@ async function activity(options2) {
|
|
|
4900
4952
|
const activeDays = data.filter((d) => d.count > 0).length;
|
|
4901
4953
|
console.log(`${total} commits across ${activeDays} active days.`);
|
|
4902
4954
|
const weekly = /* @__PURE__ */ new Map();
|
|
4903
|
-
for (const { date, count } of data) {
|
|
4955
|
+
for (const { date, count: count4 } of data) {
|
|
4904
4956
|
const d = new Date(date);
|
|
4905
4957
|
d.setDate(d.getDate() - d.getDay());
|
|
4906
4958
|
const weekStart = d.toISOString().slice(0, 10);
|
|
4907
|
-
weekly.set(weekStart, (weekly.get(weekStart) ?? 0) +
|
|
4959
|
+
weekly.set(weekStart, (weekly.get(weekStart) ?? 0) + count4);
|
|
4908
4960
|
}
|
|
4909
|
-
const weeklyData = [...weekly.entries()].map(([date,
|
|
4961
|
+
const weeklyData = [...weekly.entries()].map(([date, count4]) => ({ date, count: count4 })).sort((a, b) => a.date.localeCompare(b.date));
|
|
4910
4962
|
const until = data[data.length - 1].date;
|
|
4911
4963
|
activityChart(weeklyData, { since, until });
|
|
4912
4964
|
}
|
|
@@ -4921,17 +4973,17 @@ function registerActivity(program2) {
|
|
|
4921
4973
|
|
|
4922
4974
|
// src/commands/backlog/comment/index.ts
|
|
4923
4975
|
import chalk46 from "chalk";
|
|
4924
|
-
async function comment(id,
|
|
4976
|
+
async function comment(id, text2) {
|
|
4925
4977
|
const result = await loadAndFindItem(id);
|
|
4926
4978
|
if (!result) process.exit(1);
|
|
4927
|
-
addComment(result.item,
|
|
4979
|
+
addComment(result.item, text2);
|
|
4928
4980
|
await saveBacklog(result.items);
|
|
4929
4981
|
console.log(chalk46.green(`Comment added to item #${id}.`));
|
|
4930
4982
|
}
|
|
4931
4983
|
|
|
4932
4984
|
// src/commands/backlog/comments/index.ts
|
|
4933
4985
|
import chalk47 from "chalk";
|
|
4934
|
-
async function
|
|
4986
|
+
async function comments2(id) {
|
|
4935
4987
|
const result = await loadAndFindItem(id);
|
|
4936
4988
|
if (!result) process.exit(1);
|
|
4937
4989
|
const { item } = result;
|
|
@@ -4952,17 +5004,12 @@ async function comments(id) {
|
|
|
4952
5004
|
import chalk48 from "chalk";
|
|
4953
5005
|
|
|
4954
5006
|
// src/commands/backlog/deleteComment.ts
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
[commentId, itemId]
|
|
4959
|
-
);
|
|
5007
|
+
import { and as and2, eq as eq9 } from "drizzle-orm";
|
|
5008
|
+
async function deleteComment(orm, itemId, commentId) {
|
|
5009
|
+
const [row] = await orm.select({ type: comments.type }).from(comments).where(and2(eq9(comments.id, commentId), eq9(comments.itemId, itemId)));
|
|
4960
5010
|
if (!row) return "not-found";
|
|
4961
5011
|
if (row.type === "summary") return "is-summary";
|
|
4962
|
-
await
|
|
4963
|
-
commentId,
|
|
4964
|
-
itemId
|
|
4965
|
-
]);
|
|
5012
|
+
await orm.delete(comments).where(and2(eq9(comments.id, commentId), eq9(comments.itemId, itemId)));
|
|
4966
5013
|
return "deleted";
|
|
4967
5014
|
}
|
|
4968
5015
|
|
|
@@ -4970,9 +5017,9 @@ async function deleteComment(db, itemId, commentId) {
|
|
|
4970
5017
|
async function deleteCommentCmd(id, commentId) {
|
|
4971
5018
|
const result = await loadAndFindItem(id);
|
|
4972
5019
|
if (!result) process.exit(1);
|
|
4973
|
-
const
|
|
5020
|
+
const orm = await getBacklogOrm();
|
|
4974
5021
|
const outcome = await deleteComment(
|
|
4975
|
-
|
|
5022
|
+
orm,
|
|
4976
5023
|
result.item.id,
|
|
4977
5024
|
Number.parseInt(commentId, 10)
|
|
4978
5025
|
);
|
|
@@ -5000,7 +5047,7 @@ async function deleteCommentCmd(id, commentId) {
|
|
|
5000
5047
|
// src/commands/backlog/registerCommentCommands.ts
|
|
5001
5048
|
function registerCommentCommands(cmd) {
|
|
5002
5049
|
cmd.command("comment <id> <text>").description("Add a comment to a backlog item").action(comment);
|
|
5003
|
-
cmd.command("comments <id>").description("List comments and summaries for a backlog item").action(
|
|
5050
|
+
cmd.command("comments <id>").description("List comments and summaries for a backlog item").action(comments2);
|
|
5004
5051
|
cmd.command("delete-comment <id> <comment-id>").description("Delete a comment from a backlog item").action(deleteCommentCmd);
|
|
5005
5052
|
}
|
|
5006
5053
|
|
|
@@ -5056,8 +5103,8 @@ async function buildDump(copyOut) {
|
|
|
5056
5103
|
// src/commands/backlog/dump/copyTableOut.ts
|
|
5057
5104
|
import { to as copyTo } from "pg-copy-streams";
|
|
5058
5105
|
async function copyTableOut(client, table) {
|
|
5059
|
-
const
|
|
5060
|
-
const stream = client.query(copyTo(
|
|
5106
|
+
const sql4 = `COPY ${table.name} (${table.columns.join(", ")}) TO STDOUT`;
|
|
5107
|
+
const stream = client.query(copyTo(sql4));
|
|
5061
5108
|
const chunks = [];
|
|
5062
5109
|
for await (const chunk of stream) {
|
|
5063
5110
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
@@ -5094,10 +5141,10 @@ import chalk51 from "chalk";
|
|
|
5094
5141
|
// src/commands/backlog/dump/countCopyRows.ts
|
|
5095
5142
|
function countCopyRows(data) {
|
|
5096
5143
|
let rows = 0;
|
|
5097
|
-
let
|
|
5098
|
-
while (
|
|
5144
|
+
let index2 = data.indexOf(10);
|
|
5145
|
+
while (index2 !== -1) {
|
|
5099
5146
|
rows++;
|
|
5100
|
-
|
|
5147
|
+
index2 = data.indexOf(10, index2 + 1);
|
|
5101
5148
|
}
|
|
5102
5149
|
return rows;
|
|
5103
5150
|
}
|
|
@@ -5113,9 +5160,9 @@ function readLine(dump, start3) {
|
|
|
5113
5160
|
return { text: dump.subarray(start3, eol).toString("utf8"), next: eol + 1 };
|
|
5114
5161
|
}
|
|
5115
5162
|
function parseHeader(dump) {
|
|
5116
|
-
const { text, next: next3 } = readLine(dump, 0);
|
|
5163
|
+
const { text: text2, next: next3 } = readLine(dump, 0);
|
|
5117
5164
|
try {
|
|
5118
|
-
return { header: JSON.parse(
|
|
5165
|
+
return { header: JSON.parse(text2), bodyStart: next3 };
|
|
5119
5166
|
} catch {
|
|
5120
5167
|
return invalid("header is not valid JSON.");
|
|
5121
5168
|
}
|
|
@@ -5124,9 +5171,9 @@ function parseSections(dump, bodyStart) {
|
|
|
5124
5171
|
const sections = /* @__PURE__ */ new Map();
|
|
5125
5172
|
let cursor = bodyStart;
|
|
5126
5173
|
while (cursor < dump.length) {
|
|
5127
|
-
const { text, next: next3 } = readLine(dump, cursor);
|
|
5128
|
-
const match =
|
|
5129
|
-
if (!match) invalid(`malformed table marker "${
|
|
5174
|
+
const { text: text2, next: next3 } = readLine(dump, cursor);
|
|
5175
|
+
const match = text2.match(/^@table (\S+) (\d+)$/);
|
|
5176
|
+
if (!match) invalid(`malformed table marker "${text2}".`);
|
|
5130
5177
|
const [, name, bytes] = match;
|
|
5131
5178
|
const end = next3 + Number(bytes);
|
|
5132
5179
|
if (end > dump.length) invalid(`section "${name}" overruns the dump.`);
|
|
@@ -5207,8 +5254,8 @@ async function readStdinBuffer() {
|
|
|
5207
5254
|
import { finished } from "stream/promises";
|
|
5208
5255
|
import { from as copyFrom } from "pg-copy-streams";
|
|
5209
5256
|
async function copyTableIn(client, table, data) {
|
|
5210
|
-
const
|
|
5211
|
-
const stream = client.query(copyFrom(
|
|
5257
|
+
const sql4 = `COPY ${table.name} (${table.columns.join(", ")}) FROM STDIN`;
|
|
5258
|
+
const stream = client.query(copyFrom(sql4));
|
|
5212
5259
|
stream.end(data);
|
|
5213
5260
|
await finished(stream);
|
|
5214
5261
|
}
|
|
@@ -5354,9 +5401,9 @@ async function add(options2) {
|
|
|
5354
5401
|
const name = options2.name ?? await promptName();
|
|
5355
5402
|
const description = options2.desc?.replaceAll("\\n", "\n") ?? await promptDescription();
|
|
5356
5403
|
const acceptanceCriteria2 = options2.ac ?? await promptAcceptanceCriteria();
|
|
5357
|
-
const
|
|
5404
|
+
const orm = await getBacklogOrm();
|
|
5358
5405
|
const id = await insertItem(
|
|
5359
|
-
|
|
5406
|
+
orm,
|
|
5360
5407
|
{
|
|
5361
5408
|
type,
|
|
5362
5409
|
name,
|
|
@@ -5373,48 +5420,37 @@ async function add(options2) {
|
|
|
5373
5420
|
import chalk54 from "chalk";
|
|
5374
5421
|
|
|
5375
5422
|
// src/commands/backlog/insertPhaseAt.ts
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
await tx.
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
);
|
|
5396
|
-
for (let i = 0; i < tasks.length; i++) {
|
|
5397
|
-
await tx.run(
|
|
5398
|
-
"INSERT INTO plan_tasks (item_id, phase_idx, idx, task) VALUES (?, ?, ?, ?)",
|
|
5399
|
-
[itemId, phaseIdx, i, tasks[i]]
|
|
5400
|
-
);
|
|
5423
|
+
import { eq as eq11 } from "drizzle-orm";
|
|
5424
|
+
|
|
5425
|
+
// src/commands/backlog/shiftPhasesUp.ts
|
|
5426
|
+
import { and as and3, desc, eq as eq10, gte } from "drizzle-orm";
|
|
5427
|
+
async function shiftPhasesUp(db, itemId, fromIdx) {
|
|
5428
|
+
const toShift = await db.select({ idx: planPhases.idx }).from(planPhases).where(and3(eq10(planPhases.itemId, itemId), gte(planPhases.idx, fromIdx))).orderBy(desc(planPhases.idx));
|
|
5429
|
+
for (const p of toShift) {
|
|
5430
|
+
await db.update(planTasks).set({ phaseIdx: p.idx + 1 }).where(and3(eq10(planTasks.itemId, itemId), eq10(planTasks.phaseIdx, p.idx)));
|
|
5431
|
+
await db.update(planPhases).set({ idx: p.idx + 1 }).where(and3(eq10(planPhases.itemId, itemId), eq10(planPhases.idx, p.idx)));
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
5434
|
+
|
|
5435
|
+
// src/commands/backlog/insertPhaseAt.ts
|
|
5436
|
+
async function insertPhaseAt(orm, itemId, phaseIdx, name, tasks, manualChecks, currentPhase) {
|
|
5437
|
+
await orm.transaction(async (tx) => {
|
|
5438
|
+
await shiftPhasesUp(tx, itemId, phaseIdx);
|
|
5439
|
+
await tx.insert(planPhases).values({ itemId, idx: phaseIdx, name, manualChecks });
|
|
5440
|
+
if (tasks.length) {
|
|
5441
|
+
await tx.insert(planTasks).values(tasks.map((task, i) => ({ itemId, phaseIdx, idx: i, task })));
|
|
5401
5442
|
}
|
|
5402
5443
|
if (currentPhase !== void 0 && currentPhase - 1 >= phaseIdx) {
|
|
5403
|
-
await tx.
|
|
5404
|
-
currentPhase + 1,
|
|
5405
|
-
itemId
|
|
5406
|
-
]);
|
|
5444
|
+
await tx.update(items).set({ currentPhase: currentPhase + 1 }).where(eq11(items.id, itemId));
|
|
5407
5445
|
}
|
|
5408
5446
|
});
|
|
5409
5447
|
}
|
|
5410
5448
|
|
|
5411
5449
|
// src/commands/backlog/resolveInsertPosition.ts
|
|
5412
5450
|
import chalk53 from "chalk";
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
[itemId]
|
|
5417
|
-
);
|
|
5451
|
+
import { count, eq as eq12 } from "drizzle-orm";
|
|
5452
|
+
async function resolveInsertPosition(orm, itemId, position) {
|
|
5453
|
+
const [row] = await orm.select({ cnt: count() }).from(planPhases).where(eq12(planPhases.itemId, itemId));
|
|
5418
5454
|
const phaseCount = row?.cnt ?? 0;
|
|
5419
5455
|
if (position === void 0) return phaseCount;
|
|
5420
5456
|
const pos = Number.parseInt(position, 10);
|
|
@@ -5445,12 +5481,12 @@ async function addPhase(id, name, options2) {
|
|
|
5445
5481
|
process.exitCode = 1;
|
|
5446
5482
|
return;
|
|
5447
5483
|
}
|
|
5448
|
-
const
|
|
5484
|
+
const orm = await getBacklogOrm();
|
|
5449
5485
|
const itemId = result.item.id;
|
|
5450
|
-
const phaseIdx = await resolveInsertPosition(
|
|
5486
|
+
const phaseIdx = await resolveInsertPosition(orm, itemId, options2.position);
|
|
5451
5487
|
if (phaseIdx === void 0) return;
|
|
5452
5488
|
await insertPhaseAt(
|
|
5453
|
-
|
|
5489
|
+
orm,
|
|
5454
5490
|
itemId,
|
|
5455
5491
|
phaseIdx,
|
|
5456
5492
|
name,
|
|
@@ -5469,7 +5505,7 @@ async function addPhase(id, name, options2) {
|
|
|
5469
5505
|
// src/commands/backlog/init/index.ts
|
|
5470
5506
|
import chalk55 from "chalk";
|
|
5471
5507
|
async function init6() {
|
|
5472
|
-
await
|
|
5508
|
+
await getBacklogOrm();
|
|
5473
5509
|
console.log(
|
|
5474
5510
|
chalk55.green(
|
|
5475
5511
|
`Backlog database ready. This repository maps to origin: ${getOrigin()}`
|
|
@@ -5492,23 +5528,53 @@ function originDisplayName(origin) {
|
|
|
5492
5528
|
return origin.slice(firstSlash + 1);
|
|
5493
5529
|
}
|
|
5494
5530
|
|
|
5531
|
+
// src/commands/backlog/originDisplayLabels.ts
|
|
5532
|
+
function originDisplayLabels(origins) {
|
|
5533
|
+
const isLocal = (origin) => origin.startsWith("local:");
|
|
5534
|
+
const bareName = (origin) => {
|
|
5535
|
+
const full = originDisplayName(origin);
|
|
5536
|
+
const segments = full.split("/");
|
|
5537
|
+
return segments[segments.length - 1] ?? full;
|
|
5538
|
+
};
|
|
5539
|
+
const remoteCounts = /* @__PURE__ */ new Map();
|
|
5540
|
+
for (const origin of new Set(origins)) {
|
|
5541
|
+
if (isLocal(origin)) continue;
|
|
5542
|
+
const name = bareName(origin);
|
|
5543
|
+
remoteCounts.set(name, (remoteCounts.get(name) ?? 0) + 1);
|
|
5544
|
+
}
|
|
5545
|
+
const labels = /* @__PURE__ */ new Map();
|
|
5546
|
+
for (const origin of origins) {
|
|
5547
|
+
if (isLocal(origin)) {
|
|
5548
|
+
labels.set(origin, originDisplayName(origin));
|
|
5549
|
+
continue;
|
|
5550
|
+
}
|
|
5551
|
+
const name = bareName(origin);
|
|
5552
|
+
const collides = (remoteCounts.get(name) ?? 0) > 1;
|
|
5553
|
+
labels.set(origin, collides ? originDisplayName(origin) : name);
|
|
5554
|
+
}
|
|
5555
|
+
return labels;
|
|
5556
|
+
}
|
|
5557
|
+
|
|
5495
5558
|
// src/commands/backlog/list/index.ts
|
|
5496
|
-
function filterItems(
|
|
5497
|
-
if (options2.status) return
|
|
5559
|
+
function filterItems(items2, options2) {
|
|
5560
|
+
if (options2.status) return items2.filter((i) => i.status === options2.status);
|
|
5498
5561
|
if (!options2.all)
|
|
5499
|
-
return
|
|
5500
|
-
return
|
|
5562
|
+
return items2.filter((i) => i.status !== "done" && i.status !== "wontdo");
|
|
5563
|
+
return items2;
|
|
5501
5564
|
}
|
|
5502
5565
|
async function list2(options2) {
|
|
5503
5566
|
const allItems = await loadBacklog(options2.allRepos);
|
|
5504
|
-
const
|
|
5505
|
-
if (
|
|
5567
|
+
const items2 = filterItems(allItems, options2);
|
|
5568
|
+
if (items2.length === 0) {
|
|
5506
5569
|
console.log(chalk56.dim("Backlog is empty."));
|
|
5507
5570
|
return;
|
|
5508
5571
|
}
|
|
5509
|
-
const
|
|
5510
|
-
|
|
5511
|
-
|
|
5572
|
+
const labels = originDisplayLabels(
|
|
5573
|
+
items2.flatMap((i) => i.origin ? [i.origin] : [])
|
|
5574
|
+
);
|
|
5575
|
+
const repoNameOf = (item) => item.origin ? labels.get(item.origin) ?? "" : "";
|
|
5576
|
+
const prefixWidth = options2.allRepos ? Math.max(0, ...items2.map((i) => repoNameOf(i).length)) : 0;
|
|
5577
|
+
for (const item of items2) {
|
|
5512
5578
|
const repoPrefix2 = options2.allRepos ? `${chalk56.dim(repoNameOf(item).padEnd(prefixWidth))} ` : "";
|
|
5513
5579
|
console.log(
|
|
5514
5580
|
`${repoPrefix2}${statusIcon(item.status)} ${typeLabel(item.type)} ${chalk56.dim(`#${item.id}`)} ${item.name}${phaseLabel(item)}${dependencyLabel(item, allItems)}`
|
|
@@ -5540,7 +5606,7 @@ function registerItemCommands(cmd) {
|
|
|
5540
5606
|
import chalk58 from "chalk";
|
|
5541
5607
|
|
|
5542
5608
|
// src/commands/backlog/hasCycle.ts
|
|
5543
|
-
function hasCycle(
|
|
5609
|
+
function hasCycle(adjacency, fromId, toId) {
|
|
5544
5610
|
const visited = /* @__PURE__ */ new Set();
|
|
5545
5611
|
const stack = [toId];
|
|
5546
5612
|
while (stack.length > 0) {
|
|
@@ -5548,100 +5614,107 @@ function hasCycle(items, fromId, toId) {
|
|
|
5548
5614
|
if (current === fromId) return true;
|
|
5549
5615
|
if (visited.has(current)) continue;
|
|
5550
5616
|
visited.add(current);
|
|
5551
|
-
const
|
|
5552
|
-
|
|
5553
|
-
for (const link3 of item.links) {
|
|
5554
|
-
if (link3.type === "depends-on") {
|
|
5555
|
-
stack.push(link3.targetId);
|
|
5556
|
-
}
|
|
5617
|
+
for (const target of adjacency.get(current) ?? []) {
|
|
5618
|
+
stack.push(target);
|
|
5557
5619
|
}
|
|
5558
5620
|
}
|
|
5559
5621
|
return false;
|
|
5560
5622
|
}
|
|
5561
5623
|
|
|
5624
|
+
// src/commands/backlog/loadDependencyGraph.ts
|
|
5625
|
+
import { eq as eq13 } from "drizzle-orm";
|
|
5626
|
+
async function loadDependencyGraph(orm) {
|
|
5627
|
+
const rows = await orm.select({ itemId: links.itemId, targetId: links.targetId }).from(links).where(eq13(links.type, "depends-on"));
|
|
5628
|
+
const graph = /* @__PURE__ */ new Map();
|
|
5629
|
+
for (const { itemId, targetId } of rows) {
|
|
5630
|
+
const bucket = graph.get(itemId);
|
|
5631
|
+
if (bucket) bucket.push(targetId);
|
|
5632
|
+
else graph.set(itemId, [targetId]);
|
|
5633
|
+
}
|
|
5634
|
+
return graph;
|
|
5635
|
+
}
|
|
5636
|
+
|
|
5637
|
+
// src/commands/backlog/loadItem.ts
|
|
5638
|
+
import { eq as eq14 } from "drizzle-orm";
|
|
5639
|
+
async function loadItem(orm, id) {
|
|
5640
|
+
const [row] = await orm.select().from(items).where(eq14(items.id, id));
|
|
5641
|
+
if (!row) return void 0;
|
|
5642
|
+
const rel = await loadRelations(orm, [id]);
|
|
5643
|
+
return rowToItem(row, rel);
|
|
5644
|
+
}
|
|
5645
|
+
|
|
5562
5646
|
// src/commands/backlog/validateLinkTarget.ts
|
|
5563
5647
|
import chalk57 from "chalk";
|
|
5564
|
-
function validateLinkTarget(
|
|
5565
|
-
const
|
|
5566
|
-
if (!toItem) {
|
|
5567
|
-
console.log(chalk57.red(`Item #${toId} not found.`));
|
|
5568
|
-
return void 0;
|
|
5569
|
-
}
|
|
5570
|
-
if (!fromItem.links) fromItem.links = [];
|
|
5571
|
-
const duplicate = fromItem.links.some(
|
|
5648
|
+
function validateLinkTarget(fromItem, fromNum, toNum, linkType) {
|
|
5649
|
+
const duplicate = (fromItem.links ?? []).some(
|
|
5572
5650
|
(l) => l.targetId === toNum && l.type === linkType
|
|
5573
5651
|
);
|
|
5574
5652
|
if (duplicate) {
|
|
5575
5653
|
console.log(
|
|
5576
|
-
chalk57.yellow(`Link already exists: #${
|
|
5654
|
+
chalk57.yellow(`Link already exists: #${fromNum} ${linkType} #${toNum}`)
|
|
5577
5655
|
);
|
|
5578
|
-
return
|
|
5656
|
+
return false;
|
|
5579
5657
|
}
|
|
5580
|
-
return
|
|
5658
|
+
return true;
|
|
5581
5659
|
}
|
|
5582
5660
|
|
|
5583
5661
|
// src/commands/backlog/link.ts
|
|
5662
|
+
function fail(message) {
|
|
5663
|
+
console.log(chalk58.red(message));
|
|
5664
|
+
return void 0;
|
|
5665
|
+
}
|
|
5666
|
+
function parseLinkType(type) {
|
|
5667
|
+
const linkType = type ?? "relates-to";
|
|
5668
|
+
if (linkType === "relates-to" || linkType === "depends-on") return linkType;
|
|
5669
|
+
return fail(`Invalid link type: ${linkType}`);
|
|
5670
|
+
}
|
|
5671
|
+
async function createsCycle(orm, linkType, fromNum, toNum) {
|
|
5672
|
+
if (linkType !== "depends-on") return false;
|
|
5673
|
+
const graph = await loadDependencyGraph(orm);
|
|
5674
|
+
if (!hasCycle(graph, fromNum, toNum)) return false;
|
|
5675
|
+
fail(`Cannot add dependency: #${fromNum} \u2192 #${toNum} would create a cycle.`);
|
|
5676
|
+
return true;
|
|
5677
|
+
}
|
|
5584
5678
|
async function link(fromId, toId, opts) {
|
|
5585
|
-
const linkType = opts.type
|
|
5586
|
-
if (linkType
|
|
5587
|
-
console.log(chalk58.red(`Invalid link type: ${linkType}`));
|
|
5588
|
-
return;
|
|
5589
|
-
}
|
|
5679
|
+
const linkType = parseLinkType(opts.type);
|
|
5680
|
+
if (!linkType) return;
|
|
5590
5681
|
const fromNum = Number.parseInt(fromId, 10);
|
|
5591
5682
|
const toNum = Number.parseInt(toId, 10);
|
|
5592
|
-
if (fromNum === toNum)
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
}
|
|
5596
|
-
const
|
|
5597
|
-
if (!
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
fromItem,
|
|
5602
|
-
fromId,
|
|
5603
|
-
toId,
|
|
5604
|
-
toNum,
|
|
5605
|
-
linkType
|
|
5606
|
-
);
|
|
5607
|
-
if (!toItem) return;
|
|
5608
|
-
if (linkType === "depends-on" && hasCycle(items, fromNum, toNum)) {
|
|
5609
|
-
console.log(
|
|
5610
|
-
chalk58.red(
|
|
5611
|
-
`Cannot add dependency: #${fromId} \u2192 #${toId} would create a circular dependency.`
|
|
5612
|
-
)
|
|
5613
|
-
);
|
|
5614
|
-
return;
|
|
5615
|
-
}
|
|
5616
|
-
if (!fromItem.links) fromItem.links = [];
|
|
5617
|
-
fromItem.links.push({ type: linkType, targetId: toNum });
|
|
5618
|
-
await saveBacklog(items);
|
|
5683
|
+
if (fromNum === toNum) return void fail("Cannot link an item to itself.");
|
|
5684
|
+
const { orm } = await getReady();
|
|
5685
|
+
const fromItem = await loadItem(orm, fromNum);
|
|
5686
|
+
if (!fromItem) return void fail(`Item #${fromNum} not found.`);
|
|
5687
|
+
const toItem = await loadItem(orm, toNum);
|
|
5688
|
+
if (!toItem) return void fail(`Item #${toNum} not found.`);
|
|
5689
|
+
if (!validateLinkTarget(fromItem, fromNum, toNum, linkType)) return;
|
|
5690
|
+
if (await createsCycle(orm, linkType, fromNum, toNum)) return;
|
|
5691
|
+
await orm.insert(links).values({ itemId: fromNum, type: linkType, targetId: toNum });
|
|
5619
5692
|
console.log(
|
|
5620
|
-
chalk58.green(`Linked #${
|
|
5693
|
+
chalk58.green(`Linked #${fromNum} ${linkType} #${toNum} (${toItem.name})`)
|
|
5621
5694
|
);
|
|
5622
5695
|
}
|
|
5623
5696
|
|
|
5624
5697
|
// src/commands/backlog/unlink.ts
|
|
5625
5698
|
import chalk59 from "chalk";
|
|
5699
|
+
import { and as and4, eq as eq15 } from "drizzle-orm";
|
|
5626
5700
|
async function unlink(fromId, toId) {
|
|
5701
|
+
const fromNum = Number.parseInt(fromId, 10);
|
|
5627
5702
|
const toNum = Number.parseInt(toId, 10);
|
|
5628
|
-
const
|
|
5629
|
-
|
|
5630
|
-
|
|
5703
|
+
const { orm } = await getReady();
|
|
5704
|
+
const fromItem = await loadItem(orm, fromNum);
|
|
5705
|
+
if (!fromItem) {
|
|
5706
|
+
console.log(chalk59.red(`Item #${fromId} not found.`));
|
|
5707
|
+
return;
|
|
5708
|
+
}
|
|
5631
5709
|
if (!fromItem.links || fromItem.links.length === 0) {
|
|
5632
5710
|
console.log(chalk59.yellow(`No links found on item #${fromId}.`));
|
|
5633
5711
|
return;
|
|
5634
5712
|
}
|
|
5635
|
-
|
|
5636
|
-
fromItem.links = fromItem.links.filter((l) => l.targetId !== toNum);
|
|
5637
|
-
if (fromItem.links.length === before) {
|
|
5713
|
+
if (!fromItem.links.some((l) => l.targetId === toNum)) {
|
|
5638
5714
|
console.log(chalk59.yellow(`No link from #${fromId} to #${toId} found.`));
|
|
5639
5715
|
return;
|
|
5640
5716
|
}
|
|
5641
|
-
|
|
5642
|
-
fromItem.links = void 0;
|
|
5643
|
-
}
|
|
5644
|
-
await saveBacklog(items);
|
|
5717
|
+
await orm.delete(links).where(and4(eq15(links.itemId, fromNum), eq15(links.targetId, toNum)));
|
|
5645
5718
|
console.log(chalk59.green(`Removed link from #${fromId} to #${toId}.`));
|
|
5646
5719
|
}
|
|
5647
5720
|
|
|
@@ -5679,9 +5752,12 @@ function validateRewind2(item, phaseNumber) {
|
|
|
5679
5752
|
async function rewindPhase(id, phase, opts) {
|
|
5680
5753
|
const phaseNumber = Number.parseInt(phase, 10);
|
|
5681
5754
|
const phaseIndex = phaseNumber - 1;
|
|
5682
|
-
const
|
|
5683
|
-
|
|
5684
|
-
|
|
5755
|
+
const { orm } = await getReady();
|
|
5756
|
+
const item = await loadItem(orm, Number.parseInt(id, 10));
|
|
5757
|
+
if (!item) {
|
|
5758
|
+
console.log(chalk60.red(`Item #${id} not found.`));
|
|
5759
|
+
return;
|
|
5760
|
+
}
|
|
5685
5761
|
const error = validateRewind2(item, phaseNumber);
|
|
5686
5762
|
if (error) {
|
|
5687
5763
|
console.log(chalk60.red(error));
|
|
@@ -5689,12 +5765,12 @@ async function rewindPhase(id, phase, opts) {
|
|
|
5689
5765
|
return;
|
|
5690
5766
|
}
|
|
5691
5767
|
const phaseName = item.plan?.[phaseIndex].name;
|
|
5692
|
-
|
|
5693
|
-
|
|
5768
|
+
await appendComment(
|
|
5769
|
+
orm,
|
|
5770
|
+
item.id,
|
|
5694
5771
|
`Rewound to phase ${phaseNumber} (${phaseName}): ${opts.reason}`,
|
|
5695
|
-
phaseNumber
|
|
5772
|
+
{ phase: phaseNumber }
|
|
5696
5773
|
);
|
|
5697
|
-
await saveBacklog(result.items);
|
|
5698
5774
|
await setCurrentPhase(id, phaseNumber);
|
|
5699
5775
|
await setStatus(id, "in-progress");
|
|
5700
5776
|
writeSignal("rewind", {
|
|
@@ -5721,18 +5797,18 @@ function registerRunCommand(cmd) {
|
|
|
5721
5797
|
// src/commands/backlog/search/index.ts
|
|
5722
5798
|
import chalk61 from "chalk";
|
|
5723
5799
|
async function search(query) {
|
|
5724
|
-
const
|
|
5725
|
-
if (
|
|
5800
|
+
const items2 = await searchBacklog(query);
|
|
5801
|
+
if (items2.length === 0) {
|
|
5726
5802
|
console.log(chalk61.dim(`No items matching "${query}".`));
|
|
5727
5803
|
return;
|
|
5728
5804
|
}
|
|
5729
5805
|
console.log(
|
|
5730
5806
|
chalk61.dim(
|
|
5731
|
-
`${
|
|
5807
|
+
`${items2.length} item${items2.length === 1 ? "" : "s"} matching "${query}":
|
|
5732
5808
|
`
|
|
5733
5809
|
)
|
|
5734
5810
|
);
|
|
5735
|
-
for (const item of
|
|
5811
|
+
for (const item of items2) {
|
|
5736
5812
|
console.log(
|
|
5737
5813
|
`${statusIcon(item.status)} ${typeLabel(item.type)} ${chalk61.dim(`#${item.id}`)} ${item.name}`
|
|
5738
5814
|
);
|
|
@@ -5796,8 +5872,8 @@ async function start(id) {
|
|
|
5796
5872
|
// src/commands/backlog/stop/index.ts
|
|
5797
5873
|
import chalk65 from "chalk";
|
|
5798
5874
|
async function stop() {
|
|
5799
|
-
const
|
|
5800
|
-
const inProgress =
|
|
5875
|
+
const items2 = await loadBacklog();
|
|
5876
|
+
const inProgress = items2.filter((item) => item.status === "in-progress");
|
|
5801
5877
|
if (inProgress.length === 0) {
|
|
5802
5878
|
console.log(chalk65.yellow("No in-progress items to stop."));
|
|
5803
5879
|
return;
|
|
@@ -5806,7 +5882,7 @@ async function stop() {
|
|
|
5806
5882
|
item.status = "todo";
|
|
5807
5883
|
item.currentPhase = 1;
|
|
5808
5884
|
}
|
|
5809
|
-
await saveBacklog(
|
|
5885
|
+
await saveBacklog(items2);
|
|
5810
5886
|
for (const item of inProgress) {
|
|
5811
5887
|
console.log(chalk65.yellow(`Stopped item #${item.id}: ${item.name}`));
|
|
5812
5888
|
}
|
|
@@ -5837,19 +5913,18 @@ function registerStatusCommands(cmd) {
|
|
|
5837
5913
|
|
|
5838
5914
|
// src/commands/backlog/removePhase.ts
|
|
5839
5915
|
import chalk68 from "chalk";
|
|
5916
|
+
import { and as and7, eq as eq18 } from "drizzle-orm";
|
|
5840
5917
|
|
|
5841
5918
|
// src/commands/backlog/findPhase.ts
|
|
5842
5919
|
import chalk67 from "chalk";
|
|
5920
|
+
import { and as and5, count as count2, eq as eq16 } from "drizzle-orm";
|
|
5843
5921
|
async function findPhase(id, phase) {
|
|
5844
5922
|
const result = await loadAndFindItem(id);
|
|
5845
5923
|
if (!result) return void 0;
|
|
5846
|
-
const
|
|
5924
|
+
const orm = await getBacklogOrm();
|
|
5847
5925
|
const itemId = result.item.id;
|
|
5848
5926
|
const phaseIdx = Number.parseInt(phase, 10) - 1;
|
|
5849
|
-
const row = await
|
|
5850
|
-
"SELECT COUNT(*)::int as cnt FROM plan_phases WHERE item_id = ? AND idx = ?",
|
|
5851
|
-
[itemId, phaseIdx]
|
|
5852
|
-
);
|
|
5927
|
+
const [row] = await orm.select({ cnt: count2() }).from(planPhases).where(and5(eq16(planPhases.itemId, itemId), eq16(planPhases.idx, phaseIdx)));
|
|
5853
5928
|
if (!row || row.cnt === 0) {
|
|
5854
5929
|
console.log(
|
|
5855
5930
|
chalk67.red(`Phase ${phaseIdx + 1} not found on item #${itemId}.`)
|
|
@@ -5857,26 +5932,18 @@ async function findPhase(id, phase) {
|
|
|
5857
5932
|
process.exitCode = 1;
|
|
5858
5933
|
return void 0;
|
|
5859
5934
|
}
|
|
5860
|
-
return { result,
|
|
5935
|
+
return { result, orm, itemId, phaseIdx };
|
|
5861
5936
|
}
|
|
5862
5937
|
|
|
5863
5938
|
// src/commands/backlog/reindexPhases.ts
|
|
5939
|
+
import { and as and6, asc as asc4, count as count3, eq as eq17 } from "drizzle-orm";
|
|
5864
5940
|
async function reindexPhases(db, itemId) {
|
|
5865
|
-
const remaining = await db.
|
|
5866
|
-
"SELECT idx FROM plan_phases WHERE item_id = ? ORDER BY idx",
|
|
5867
|
-
[itemId]
|
|
5868
|
-
);
|
|
5941
|
+
const remaining = await db.select({ idx: planPhases.idx }).from(planPhases).where(eq17(planPhases.itemId, itemId)).orderBy(asc4(planPhases.idx));
|
|
5869
5942
|
for (let i = 0; i < remaining.length; i++) {
|
|
5870
5943
|
const oldIdx = remaining[i].idx;
|
|
5871
5944
|
if (oldIdx === i) continue;
|
|
5872
|
-
await db.
|
|
5873
|
-
|
|
5874
|
-
[i, itemId, oldIdx]
|
|
5875
|
-
);
|
|
5876
|
-
await db.run(
|
|
5877
|
-
"UPDATE plan_phases SET idx = ? WHERE item_id = ? AND idx = ?",
|
|
5878
|
-
[i, itemId, oldIdx]
|
|
5879
|
-
);
|
|
5945
|
+
await db.update(planTasks).set({ phaseIdx: i }).where(and6(eq17(planTasks.itemId, itemId), eq17(planTasks.phaseIdx, oldIdx)));
|
|
5946
|
+
await db.update(planPhases).set({ idx: i }).where(and6(eq17(planPhases.itemId, itemId), eq17(planPhases.idx, oldIdx)));
|
|
5880
5947
|
}
|
|
5881
5948
|
}
|
|
5882
5949
|
async function adjustCurrentPhase(db, item, removedIdx) {
|
|
@@ -5884,38 +5951,25 @@ async function adjustCurrentPhase(db, item, removedIdx) {
|
|
|
5884
5951
|
if (currentPhase === void 0) return;
|
|
5885
5952
|
const currentIdx = currentPhase - 1;
|
|
5886
5953
|
if (removedIdx < currentIdx) {
|
|
5887
|
-
await db.
|
|
5888
|
-
currentPhase - 1,
|
|
5889
|
-
item.id
|
|
5890
|
-
]);
|
|
5954
|
+
await db.update(items).set({ currentPhase: currentPhase - 1 }).where(eq17(items.id, item.id));
|
|
5891
5955
|
return;
|
|
5892
5956
|
}
|
|
5893
5957
|
if (removedIdx !== currentIdx) return;
|
|
5894
|
-
const row = await db.
|
|
5895
|
-
"SELECT COUNT(*)::int as cnt FROM plan_phases WHERE item_id = ?",
|
|
5896
|
-
[item.id]
|
|
5897
|
-
);
|
|
5958
|
+
const [row] = await db.select({ cnt: count3() }).from(planPhases).where(eq17(planPhases.itemId, item.id));
|
|
5898
5959
|
const cnt = row?.cnt ?? 0;
|
|
5899
|
-
await db.
|
|
5900
|
-
cnt === 0 ? null : Math.min(currentPhase, cnt),
|
|
5901
|
-
item.id
|
|
5902
|
-
]);
|
|
5960
|
+
await db.update(items).set({ currentPhase: cnt === 0 ? null : Math.min(currentPhase, cnt) }).where(eq17(items.id, item.id));
|
|
5903
5961
|
}
|
|
5904
5962
|
|
|
5905
5963
|
// src/commands/backlog/removePhase.ts
|
|
5906
5964
|
async function removePhase(id, phase) {
|
|
5907
5965
|
const found = await findPhase(id, phase);
|
|
5908
5966
|
if (!found) return;
|
|
5909
|
-
const { result,
|
|
5910
|
-
await
|
|
5911
|
-
await tx.
|
|
5912
|
-
itemId,
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
await tx.run("DELETE FROM plan_phases WHERE item_id = ? AND idx = ?", [
|
|
5916
|
-
itemId,
|
|
5917
|
-
phaseIdx
|
|
5918
|
-
]);
|
|
5967
|
+
const { result, orm, itemId, phaseIdx } = found;
|
|
5968
|
+
await orm.transaction(async (tx) => {
|
|
5969
|
+
await tx.delete(planTasks).where(
|
|
5970
|
+
and7(eq18(planTasks.itemId, itemId), eq18(planTasks.phaseIdx, phaseIdx))
|
|
5971
|
+
);
|
|
5972
|
+
await tx.delete(planPhases).where(and7(eq18(planPhases.itemId, itemId), eq18(planPhases.idx, phaseIdx)));
|
|
5919
5973
|
await reindexPhases(tx, itemId);
|
|
5920
5974
|
await adjustCurrentPhase(tx, result.item, phaseIdx);
|
|
5921
5975
|
});
|
|
@@ -5926,20 +5980,21 @@ async function removePhase(id, phase) {
|
|
|
5926
5980
|
|
|
5927
5981
|
// src/commands/backlog/update/index.ts
|
|
5928
5982
|
import chalk70 from "chalk";
|
|
5983
|
+
import { eq as eq19 } from "drizzle-orm";
|
|
5929
5984
|
|
|
5930
5985
|
// src/commands/backlog/update/parseListIndex.ts
|
|
5931
5986
|
function parseListIndex(raw, length, label2) {
|
|
5932
5987
|
if (!/^\d+$/.test(raw)) {
|
|
5933
5988
|
return { ok: false, error: `${label2} must be a positive integer.` };
|
|
5934
5989
|
}
|
|
5935
|
-
const
|
|
5936
|
-
if (
|
|
5990
|
+
const index2 = Number.parseInt(raw, 10);
|
|
5991
|
+
if (index2 < 1 || index2 > length) {
|
|
5937
5992
|
return {
|
|
5938
5993
|
ok: false,
|
|
5939
|
-
error: `${label2} ${
|
|
5994
|
+
error: `${label2} ${index2} is out of range (1-${length}).`
|
|
5940
5995
|
};
|
|
5941
5996
|
}
|
|
5942
|
-
return { ok: true, index };
|
|
5997
|
+
return { ok: true, index: index2 };
|
|
5943
5998
|
}
|
|
5944
5999
|
|
|
5945
6000
|
// src/commands/backlog/update/applyListMutations.ts
|
|
@@ -5949,7 +6004,7 @@ function hasListMutations(options2) {
|
|
|
5949
6004
|
);
|
|
5950
6005
|
}
|
|
5951
6006
|
function applyListMutations(current, options2, flags) {
|
|
5952
|
-
let
|
|
6007
|
+
let items2 = [...current];
|
|
5953
6008
|
if (options2.edit) {
|
|
5954
6009
|
const [rawIndex, ...textParts] = options2.edit;
|
|
5955
6010
|
if (rawIndex === void 0 || textParts.length === 0) {
|
|
@@ -5960,25 +6015,25 @@ function applyListMutations(current, options2, flags) {
|
|
|
5960
6015
|
}
|
|
5961
6016
|
const parsed = parseListIndex(
|
|
5962
6017
|
rawIndex,
|
|
5963
|
-
|
|
6018
|
+
items2.length,
|
|
5964
6019
|
`${flags.edit} index`
|
|
5965
6020
|
);
|
|
5966
6021
|
if (!parsed.ok) return parsed;
|
|
5967
|
-
|
|
6022
|
+
items2[parsed.index - 1] = textParts.join(" ");
|
|
5968
6023
|
}
|
|
5969
6024
|
if (options2.remove) {
|
|
5970
6025
|
const parsed = parseListIndex(
|
|
5971
6026
|
options2.remove,
|
|
5972
|
-
|
|
6027
|
+
items2.length,
|
|
5973
6028
|
`${flags.remove} index`
|
|
5974
6029
|
);
|
|
5975
6030
|
if (!parsed.ok) return parsed;
|
|
5976
|
-
|
|
6031
|
+
items2 = items2.filter((_, i) => i !== parsed.index - 1);
|
|
5977
6032
|
}
|
|
5978
6033
|
if (options2.add) {
|
|
5979
|
-
|
|
6034
|
+
items2.push(...options2.add);
|
|
5980
6035
|
}
|
|
5981
|
-
return { ok: true, items };
|
|
6036
|
+
return { ok: true, items: items2 };
|
|
5982
6037
|
}
|
|
5983
6038
|
|
|
5984
6039
|
// src/commands/backlog/update/applyAcMutations.ts
|
|
@@ -5999,11 +6054,11 @@ function applyAcMutations(current, options2) {
|
|
|
5999
6054
|
return { ok: true, criteria: result.items };
|
|
6000
6055
|
}
|
|
6001
6056
|
|
|
6002
|
-
// src/commands/backlog/update/
|
|
6057
|
+
// src/commands/backlog/update/buildUpdateValues.ts
|
|
6003
6058
|
import chalk69 from "chalk";
|
|
6004
|
-
function
|
|
6005
|
-
const { name, desc, type, ac } = options2;
|
|
6006
|
-
if (!name && !
|
|
6059
|
+
function buildUpdateValues(options2) {
|
|
6060
|
+
const { name, desc: desc2, type, ac } = options2;
|
|
6061
|
+
if (!name && !desc2 && !type && !ac) {
|
|
6007
6062
|
console.log(chalk69.red("Nothing to update. Provide at least one flag."));
|
|
6008
6063
|
process.exitCode = 1;
|
|
6009
6064
|
return void 0;
|
|
@@ -6013,30 +6068,25 @@ function buildUpdateSql(options2) {
|
|
|
6013
6068
|
process.exitCode = 1;
|
|
6014
6069
|
return void 0;
|
|
6015
6070
|
}
|
|
6016
|
-
const
|
|
6017
|
-
const params = [];
|
|
6071
|
+
const set = {};
|
|
6018
6072
|
const fieldNames = [];
|
|
6019
6073
|
if (name) {
|
|
6020
|
-
|
|
6021
|
-
params.push(name);
|
|
6074
|
+
set.name = name;
|
|
6022
6075
|
fieldNames.push("name");
|
|
6023
6076
|
}
|
|
6024
|
-
if (
|
|
6025
|
-
|
|
6026
|
-
params.push(desc);
|
|
6077
|
+
if (desc2) {
|
|
6078
|
+
set.description = desc2;
|
|
6027
6079
|
fieldNames.push("description");
|
|
6028
6080
|
}
|
|
6029
6081
|
if (type) {
|
|
6030
|
-
|
|
6031
|
-
params.push(type);
|
|
6082
|
+
set.type = type;
|
|
6032
6083
|
fieldNames.push("type");
|
|
6033
6084
|
}
|
|
6034
6085
|
if (ac) {
|
|
6035
|
-
|
|
6036
|
-
params.push(JSON.stringify(ac));
|
|
6086
|
+
set.acceptanceCriteria = JSON.stringify(ac);
|
|
6037
6087
|
fieldNames.push("acceptance criteria");
|
|
6038
6088
|
}
|
|
6039
|
-
return {
|
|
6089
|
+
return { set, fields: fieldNames.join(", ") };
|
|
6040
6090
|
}
|
|
6041
6091
|
|
|
6042
6092
|
// src/commands/backlog/update/index.ts
|
|
@@ -6060,14 +6110,11 @@ async function update(id, options2) {
|
|
|
6060
6110
|
}
|
|
6061
6111
|
ac = mutation.criteria;
|
|
6062
6112
|
}
|
|
6063
|
-
const built =
|
|
6113
|
+
const built = buildUpdateValues({ ...options2, ac });
|
|
6064
6114
|
if (!built) return;
|
|
6065
|
-
const
|
|
6115
|
+
const orm = await getBacklogOrm();
|
|
6066
6116
|
const itemId = result.item.id;
|
|
6067
|
-
await
|
|
6068
|
-
...built.params,
|
|
6069
|
-
itemId
|
|
6070
|
-
]);
|
|
6117
|
+
await orm.update(items).set(built.set).where(eq19(items.id, itemId));
|
|
6071
6118
|
console.log(chalk70.green(`Updated ${built.fields} on item #${itemId}.`));
|
|
6072
6119
|
}
|
|
6073
6120
|
|
|
@@ -6075,29 +6122,31 @@ async function update(id, options2) {
|
|
|
6075
6122
|
import chalk71 from "chalk";
|
|
6076
6123
|
|
|
6077
6124
|
// src/commands/backlog/applyPhaseUpdate.ts
|
|
6078
|
-
|
|
6079
|
-
|
|
6125
|
+
import { and as and8, eq as eq20 } from "drizzle-orm";
|
|
6126
|
+
async function applyPhaseUpdate(orm, itemId, phaseIdx, fields) {
|
|
6127
|
+
await orm.transaction(async (tx) => {
|
|
6080
6128
|
if (fields.name) {
|
|
6081
|
-
await tx.
|
|
6082
|
-
|
|
6083
|
-
[fields.name, itemId, phaseIdx]
|
|
6129
|
+
await tx.update(planPhases).set({ name: fields.name }).where(
|
|
6130
|
+
and8(eq20(planPhases.itemId, itemId), eq20(planPhases.idx, phaseIdx))
|
|
6084
6131
|
);
|
|
6085
6132
|
}
|
|
6086
6133
|
if (fields.manualCheck) {
|
|
6087
|
-
await tx.
|
|
6088
|
-
|
|
6089
|
-
[JSON.stringify(fields.manualCheck), itemId, phaseIdx]
|
|
6134
|
+
await tx.update(planPhases).set({ manualChecks: JSON.stringify(fields.manualCheck) }).where(
|
|
6135
|
+
and8(eq20(planPhases.itemId, itemId), eq20(planPhases.idx, phaseIdx))
|
|
6090
6136
|
);
|
|
6091
6137
|
}
|
|
6092
6138
|
if (fields.task) {
|
|
6093
|
-
await tx.
|
|
6094
|
-
|
|
6095
|
-
[itemId, phaseIdx]
|
|
6139
|
+
await tx.delete(planTasks).where(
|
|
6140
|
+
and8(eq20(planTasks.itemId, itemId), eq20(planTasks.phaseIdx, phaseIdx))
|
|
6096
6141
|
);
|
|
6097
|
-
|
|
6098
|
-
await tx.
|
|
6099
|
-
|
|
6100
|
-
|
|
6142
|
+
if (fields.task.length) {
|
|
6143
|
+
await tx.insert(planTasks).values(
|
|
6144
|
+
fields.task.map((task, i) => ({
|
|
6145
|
+
itemId,
|
|
6146
|
+
phaseIdx,
|
|
6147
|
+
idx: i,
|
|
6148
|
+
task
|
|
6149
|
+
}))
|
|
6101
6150
|
);
|
|
6102
6151
|
}
|
|
6103
6152
|
}
|
|
@@ -6171,7 +6220,7 @@ function resolvePhaseFields(options2, current) {
|
|
|
6171
6220
|
async function updatePhase(id, phase, options2) {
|
|
6172
6221
|
const found = await findPhase(id, phase);
|
|
6173
6222
|
if (!found) return;
|
|
6174
|
-
const { result,
|
|
6223
|
+
const { result, orm, itemId, phaseIdx } = found;
|
|
6175
6224
|
const resolved = resolvePhaseFields(options2, result.item.plan?.[phaseIdx]);
|
|
6176
6225
|
if (!resolved.ok) {
|
|
6177
6226
|
console.log(chalk71.red(resolved.error));
|
|
@@ -6179,7 +6228,7 @@ async function updatePhase(id, phase, options2) {
|
|
|
6179
6228
|
return;
|
|
6180
6229
|
}
|
|
6181
6230
|
const { name, task, manualCheck } = resolved.fields;
|
|
6182
|
-
await applyPhaseUpdate(
|
|
6231
|
+
await applyPhaseUpdate(orm, itemId, phaseIdx, { name, task, manualCheck });
|
|
6183
6232
|
const fields = [
|
|
6184
6233
|
name && "name",
|
|
6185
6234
|
task && "tasks",
|
|
@@ -6380,7 +6429,7 @@ import { mkdirSync as mkdirSync4 } from "fs";
|
|
|
6380
6429
|
import { homedir as homedir4 } from "os";
|
|
6381
6430
|
import { join as join18 } from "path";
|
|
6382
6431
|
import Database from "better-sqlite3";
|
|
6383
|
-
var
|
|
6432
|
+
var _db;
|
|
6384
6433
|
function getDbDir() {
|
|
6385
6434
|
return join18(homedir4(), ".assist");
|
|
6386
6435
|
}
|
|
@@ -6398,13 +6447,13 @@ function initSchema(db) {
|
|
|
6398
6447
|
`);
|
|
6399
6448
|
}
|
|
6400
6449
|
function openPromptsDb(dir) {
|
|
6401
|
-
if (
|
|
6450
|
+
if (_db) return _db;
|
|
6402
6451
|
const dbDir = dir ?? getDbDir();
|
|
6403
6452
|
mkdirSync4(dbDir, { recursive: true });
|
|
6404
6453
|
const db = new Database(join18(dbDir, "assist.db"));
|
|
6405
6454
|
db.pragma("journal_mode = WAL");
|
|
6406
6455
|
initSchema(db);
|
|
6407
|
-
|
|
6456
|
+
_db = db;
|
|
6408
6457
|
return db;
|
|
6409
6458
|
}
|
|
6410
6459
|
function logDeniedToolCall(entry) {
|
|
@@ -6881,17 +6930,17 @@ function isClaudeCode() {
|
|
|
6881
6930
|
}
|
|
6882
6931
|
|
|
6883
6932
|
// src/commands/permitCliReads/mapAsync.ts
|
|
6884
|
-
async function mapAsync(
|
|
6885
|
-
const results = new Array(
|
|
6933
|
+
async function mapAsync(items2, concurrency, fn) {
|
|
6934
|
+
const results = new Array(items2.length);
|
|
6886
6935
|
let next3 = 0;
|
|
6887
6936
|
async function worker() {
|
|
6888
|
-
while (next3 <
|
|
6937
|
+
while (next3 < items2.length) {
|
|
6889
6938
|
const idx = next3++;
|
|
6890
|
-
results[idx] = await fn(
|
|
6939
|
+
results[idx] = await fn(items2[idx]);
|
|
6891
6940
|
}
|
|
6892
6941
|
}
|
|
6893
6942
|
const workers = Array.from(
|
|
6894
|
-
{ length: Math.min(concurrency,
|
|
6943
|
+
{ length: Math.min(concurrency, items2.length) },
|
|
6895
6944
|
() => worker()
|
|
6896
6945
|
);
|
|
6897
6946
|
await Promise.all(workers);
|
|
@@ -7089,8 +7138,8 @@ function formatHuman(cli, commands) {
|
|
|
7089
7138
|
`];
|
|
7090
7139
|
for (const cmd of sorted) {
|
|
7091
7140
|
const full = `${cli} ${cmd.path.join(" ")}`;
|
|
7092
|
-
const
|
|
7093
|
-
lines.push(`${prefix(classifyVerb(cmd.path))}${
|
|
7141
|
+
const text2 = cmd.description ? `${full} \u2014 ${cmd.description}` : full;
|
|
7142
|
+
lines.push(`${prefix(classifyVerb(cmd.path))}${text2}`);
|
|
7094
7143
|
}
|
|
7095
7144
|
return lines.join("\n");
|
|
7096
7145
|
}
|
|
@@ -7225,12 +7274,12 @@ function denyList() {
|
|
|
7225
7274
|
import chalk75 from "chalk";
|
|
7226
7275
|
function denyRemove(pattern2, options2) {
|
|
7227
7276
|
const { deny, saveDeny } = loadDenyConfig(options2.global);
|
|
7228
|
-
const
|
|
7229
|
-
if (
|
|
7277
|
+
const index2 = deny.findIndex((r) => r.pattern === pattern2);
|
|
7278
|
+
if (index2 === -1) {
|
|
7230
7279
|
console.log(chalk75.yellow(`No deny rule found for: ${pattern2}`));
|
|
7231
7280
|
return;
|
|
7232
7281
|
}
|
|
7233
|
-
deny.splice(
|
|
7282
|
+
deny.splice(index2, 1);
|
|
7234
7283
|
saveDeny(deny.length > 0 ? deny : void 0);
|
|
7235
7284
|
console.log(chalk75.green(`Removed deny rule: ${pattern2}`));
|
|
7236
7285
|
}
|
|
@@ -7467,7 +7516,7 @@ function calculateHalstead(node) {
|
|
|
7467
7516
|
// src/commands/complexity/shared/countSloc.ts
|
|
7468
7517
|
function countSloc(content) {
|
|
7469
7518
|
let inMultiLineComment = false;
|
|
7470
|
-
let
|
|
7519
|
+
let count4 = 0;
|
|
7471
7520
|
for (const line of content.split("\n")) {
|
|
7472
7521
|
const trimmed = line.trim();
|
|
7473
7522
|
if (inMultiLineComment) {
|
|
@@ -7475,7 +7524,7 @@ function countSloc(content) {
|
|
|
7475
7524
|
inMultiLineComment = false;
|
|
7476
7525
|
const afterComment = trimmed.substring(trimmed.indexOf("*/") + 2);
|
|
7477
7526
|
if (afterComment.trim().length > 0) {
|
|
7478
|
-
|
|
7527
|
+
count4++;
|
|
7479
7528
|
}
|
|
7480
7529
|
}
|
|
7481
7530
|
continue;
|
|
@@ -7487,7 +7536,7 @@ function countSloc(content) {
|
|
|
7487
7536
|
if (trimmed.includes("*/")) {
|
|
7488
7537
|
const afterComment = trimmed.substring(trimmed.indexOf("*/") + 2);
|
|
7489
7538
|
if (afterComment.trim().length > 0) {
|
|
7490
|
-
|
|
7539
|
+
count4++;
|
|
7491
7540
|
}
|
|
7492
7541
|
} else {
|
|
7493
7542
|
inMultiLineComment = true;
|
|
@@ -7495,10 +7544,10 @@ function countSloc(content) {
|
|
|
7495
7544
|
continue;
|
|
7496
7545
|
}
|
|
7497
7546
|
if (trimmed.length > 0) {
|
|
7498
|
-
|
|
7547
|
+
count4++;
|
|
7499
7548
|
}
|
|
7500
7549
|
}
|
|
7501
|
-
return
|
|
7550
|
+
return count4;
|
|
7502
7551
|
}
|
|
7503
7552
|
|
|
7504
7553
|
// src/commands/complexity/shared/index.ts
|
|
@@ -9193,16 +9242,16 @@ function parseUserLine(line) {
|
|
|
9193
9242
|
if (entry.type !== "user") return void 0;
|
|
9194
9243
|
const msg = entry.message;
|
|
9195
9244
|
const c = msg?.content;
|
|
9196
|
-
let
|
|
9245
|
+
let text2;
|
|
9197
9246
|
if (typeof c === "string") {
|
|
9198
|
-
|
|
9247
|
+
text2 = c;
|
|
9199
9248
|
} else if (Array.isArray(c)) {
|
|
9200
9249
|
const collected = c.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n");
|
|
9201
|
-
|
|
9250
|
+
text2 = collected || void 0;
|
|
9202
9251
|
}
|
|
9203
|
-
if (!
|
|
9252
|
+
if (!text2) return void 0;
|
|
9204
9253
|
return {
|
|
9205
|
-
text,
|
|
9254
|
+
text: text2,
|
|
9206
9255
|
entrypoint: typeof entry.entrypoint === "string" ? entry.entrypoint : void 0
|
|
9207
9256
|
};
|
|
9208
9257
|
}
|
|
@@ -9253,12 +9302,12 @@ ${payload}`;
|
|
|
9253
9302
|
return "";
|
|
9254
9303
|
}
|
|
9255
9304
|
}
|
|
9256
|
-
function stripPreludes(
|
|
9257
|
-
return
|
|
9305
|
+
function stripPreludes(text2) {
|
|
9306
|
+
return text2.replace(/<command-name>[\s\S]*?<\/command-name>/g, "").replace(/<command-message>[\s\S]*?<\/command-message>/g, "").replace(/<command-args>[\s\S]*?<\/command-args>/g, "").replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g, "").replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
|
|
9258
9307
|
}
|
|
9259
|
-
function capPayload(
|
|
9260
|
-
const buf = Buffer.from(
|
|
9261
|
-
if (buf.length <= maxBytes) return
|
|
9308
|
+
function capPayload(text2, maxBytes) {
|
|
9309
|
+
const buf = Buffer.from(text2, "utf8");
|
|
9310
|
+
if (buf.length <= maxBytes) return text2;
|
|
9262
9311
|
return buf.subarray(buf.length - maxBytes).toString("utf8");
|
|
9263
9312
|
}
|
|
9264
9313
|
function normaliseOutput(raw) {
|
|
@@ -9327,9 +9376,9 @@ import chalk103 from "chalk";
|
|
|
9327
9376
|
|
|
9328
9377
|
// src/commands/jira/adfToText.ts
|
|
9329
9378
|
function renderInline(node) {
|
|
9330
|
-
const
|
|
9331
|
-
if (node.marks?.some((m) => m.type === "code")) return `\`${
|
|
9332
|
-
return
|
|
9379
|
+
const text2 = node.text ?? "";
|
|
9380
|
+
if (node.marks?.some((m) => m.type === "code")) return `\`${text2}\``;
|
|
9381
|
+
return text2;
|
|
9333
9382
|
}
|
|
9334
9383
|
function renderChildren(node, indent2) {
|
|
9335
9384
|
return renderNodes(node.content ?? [], indent2);
|
|
@@ -9371,8 +9420,8 @@ function isListNode(node) {
|
|
|
9371
9420
|
function renderListChild(child, indent2, pad, marker, isFirst) {
|
|
9372
9421
|
if (isListNode(child)) return renderNodes([child], indent2 + 1);
|
|
9373
9422
|
if (child.type !== "paragraph") return renderNode(child, indent2);
|
|
9374
|
-
const
|
|
9375
|
-
return isFirst ? `${pad}${marker} ${
|
|
9423
|
+
const text2 = renderChildren(child, indent2);
|
|
9424
|
+
return isFirst ? `${pad}${marker} ${text2}` : `${pad} ${text2}`;
|
|
9376
9425
|
}
|
|
9377
9426
|
function renderListItem(node, indent2, marker) {
|
|
9378
9427
|
const pad = " ".repeat(indent2);
|
|
@@ -9735,9 +9784,9 @@ function excerpt(xml, ...tags) {
|
|
|
9735
9784
|
for (const tag of tags) {
|
|
9736
9785
|
const raw = extractText(xml, tag);
|
|
9737
9786
|
if (!raw) continue;
|
|
9738
|
-
const
|
|
9739
|
-
if (
|
|
9740
|
-
return `${
|
|
9787
|
+
const text2 = stripHtml(raw);
|
|
9788
|
+
if (text2.length <= MAX_EXCERPT) return text2;
|
|
9789
|
+
return `${text2.slice(0, MAX_EXCERPT)}\u2026`;
|
|
9741
9790
|
}
|
|
9742
9791
|
return "";
|
|
9743
9792
|
}
|
|
@@ -9782,22 +9831,22 @@ async function fetchFeeds(urls, onProgress) {
|
|
|
9782
9831
|
const origin = new URL(url).origin;
|
|
9783
9832
|
const res = await fetch(url);
|
|
9784
9833
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
9785
|
-
const
|
|
9834
|
+
const items3 = parseFeed(await res.text(), origin);
|
|
9786
9835
|
done2++;
|
|
9787
9836
|
onProgress?.(done2, urls.length);
|
|
9788
|
-
return
|
|
9837
|
+
return items3;
|
|
9789
9838
|
})
|
|
9790
9839
|
);
|
|
9791
|
-
const
|
|
9840
|
+
const items2 = [];
|
|
9792
9841
|
for (const result of results) {
|
|
9793
9842
|
if (result.status === "fulfilled") {
|
|
9794
|
-
|
|
9843
|
+
items2.push(...result.value);
|
|
9795
9844
|
}
|
|
9796
9845
|
}
|
|
9797
|
-
|
|
9846
|
+
items2.sort(
|
|
9798
9847
|
(a, b) => new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime()
|
|
9799
9848
|
);
|
|
9800
|
-
return
|
|
9849
|
+
return items2;
|
|
9801
9850
|
}
|
|
9802
9851
|
|
|
9803
9852
|
// src/commands/news/web/getHtml.ts
|
|
@@ -9833,13 +9882,13 @@ function prefetch() {
|
|
|
9833
9882
|
process.stdout.write(
|
|
9834
9883
|
`\r${chalk109.dim(`Fetching feeds ${bar} ${done2}/${t}`)}`
|
|
9835
9884
|
);
|
|
9836
|
-
}).then((
|
|
9885
|
+
}).then((items2) => {
|
|
9837
9886
|
process.stdout.write(
|
|
9838
|
-
`\r${chalk109.green(`Fetched ${
|
|
9887
|
+
`\r${chalk109.green(`Fetched ${items2.length} items from ${total} feed(s)`)}
|
|
9839
9888
|
`
|
|
9840
9889
|
);
|
|
9841
|
-
cachedItems =
|
|
9842
|
-
return
|
|
9890
|
+
cachedItems = items2;
|
|
9891
|
+
return items2;
|
|
9843
9892
|
});
|
|
9844
9893
|
}
|
|
9845
9894
|
async function listItems2(_req, res) {
|
|
@@ -9901,11 +9950,11 @@ function printPromptsTable(rows) {
|
|
|
9901
9950
|
console.log(chalk110.dim(header));
|
|
9902
9951
|
console.log(chalk110.dim("-".repeat(header.length)));
|
|
9903
9952
|
for (const row of rows) {
|
|
9904
|
-
const
|
|
9953
|
+
const count4 = String(row.count).padStart(countWidth);
|
|
9905
9954
|
const tool = row.tool.padEnd(toolWidth);
|
|
9906
9955
|
const command = truncate(row.command, 60).padEnd(commandWidth);
|
|
9907
9956
|
console.log(
|
|
9908
|
-
`${chalk110.yellow(
|
|
9957
|
+
`${chalk110.yellow(count4)} ${tool} ${command} ${chalk110.dim(row.repos)}`
|
|
9909
9958
|
);
|
|
9910
9959
|
}
|
|
9911
9960
|
}
|
|
@@ -10196,8 +10245,8 @@ function requireCache(prNumber) {
|
|
|
10196
10245
|
}
|
|
10197
10246
|
return cache;
|
|
10198
10247
|
}
|
|
10199
|
-
function findLineComment(
|
|
10200
|
-
return
|
|
10248
|
+
function findLineComment(comments3, commentId) {
|
|
10249
|
+
return comments3.find((c) => c.type === "line" && c.id === commentId);
|
|
10201
10250
|
}
|
|
10202
10251
|
function requireLineComment(cache, commentId) {
|
|
10203
10252
|
const comment3 = findLineComment(cache.comments, commentId);
|
|
@@ -10330,8 +10379,8 @@ function mapLineComment(c, threadInfo) {
|
|
|
10330
10379
|
};
|
|
10331
10380
|
}
|
|
10332
10381
|
function fetchLineComments(org, repo, prNumber, threadInfo) {
|
|
10333
|
-
const
|
|
10334
|
-
return
|
|
10382
|
+
const comments3 = fetchJson(`repos/${org}/${repo}/pulls/${prNumber}/comments`);
|
|
10383
|
+
return comments3.map(
|
|
10335
10384
|
(c) => mapLineComment(c, threadInfo)
|
|
10336
10385
|
);
|
|
10337
10386
|
}
|
|
@@ -10355,33 +10404,33 @@ function formatForHuman(comment3) {
|
|
|
10355
10404
|
""
|
|
10356
10405
|
].join("\n");
|
|
10357
10406
|
}
|
|
10358
|
-
function summarise2(
|
|
10359
|
-
const lineCount =
|
|
10360
|
-
const reviewCount =
|
|
10407
|
+
function summarise2(comments3) {
|
|
10408
|
+
const lineCount = comments3.filter((c) => c.type === "line").length;
|
|
10409
|
+
const reviewCount = comments3.filter((c) => c.type === "review").length;
|
|
10361
10410
|
const parts = [];
|
|
10362
10411
|
if (lineCount > 0) parts.push(`${lineCount} line`);
|
|
10363
10412
|
if (reviewCount > 0) parts.push(`${reviewCount} review`);
|
|
10364
|
-
return `Found ${parts.join(" and ")} comment${
|
|
10413
|
+
return `Found ${parts.join(" and ")} comment${comments3.length === 1 ? "" : "s"}.`;
|
|
10365
10414
|
}
|
|
10366
10415
|
function printComments2(result) {
|
|
10367
|
-
const { comments:
|
|
10368
|
-
if (
|
|
10416
|
+
const { comments: comments3, cachePath } = result;
|
|
10417
|
+
if (comments3.length === 0) {
|
|
10369
10418
|
console.log("No comments found.");
|
|
10370
10419
|
return;
|
|
10371
10420
|
}
|
|
10372
10421
|
if (!isClaudeCode()) {
|
|
10373
|
-
for (const comment3 of
|
|
10422
|
+
for (const comment3 of comments3) {
|
|
10374
10423
|
console.log(formatForHuman(comment3));
|
|
10375
10424
|
}
|
|
10376
10425
|
}
|
|
10377
|
-
console.log(summarise2(
|
|
10426
|
+
console.log(summarise2(comments3));
|
|
10378
10427
|
if (cachePath) {
|
|
10379
10428
|
console.log(`Saved to ${cachePath}`);
|
|
10380
10429
|
}
|
|
10381
10430
|
}
|
|
10382
10431
|
|
|
10383
10432
|
// src/commands/prs/listComments/index.ts
|
|
10384
|
-
function writeCommentsCache(prNumber,
|
|
10433
|
+
function writeCommentsCache(prNumber, comments3) {
|
|
10385
10434
|
const assistDir = join34(process.cwd(), ".assist");
|
|
10386
10435
|
if (!existsSync34(assistDir)) {
|
|
10387
10436
|
mkdirSync9(assistDir, { recursive: true });
|
|
@@ -10389,7 +10438,7 @@ function writeCommentsCache(prNumber, comments2) {
|
|
|
10389
10438
|
const cacheData = {
|
|
10390
10439
|
prNumber,
|
|
10391
10440
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10392
|
-
comments:
|
|
10441
|
+
comments: comments3
|
|
10393
10442
|
};
|
|
10394
10443
|
const cachePath = join34(assistDir, `pr-${prNumber}-comments.yaml`);
|
|
10395
10444
|
writeFileSync23(cachePath, stringify(cacheData));
|
|
@@ -10406,9 +10455,9 @@ function handleKnownErrors(error) {
|
|
|
10406
10455
|
}
|
|
10407
10456
|
return null;
|
|
10408
10457
|
}
|
|
10409
|
-
function updateCache(prNumber,
|
|
10410
|
-
if (
|
|
10411
|
-
writeCommentsCache(prNumber,
|
|
10458
|
+
function updateCache(prNumber, comments3) {
|
|
10459
|
+
if (comments3.some((c) => c.type === "line")) {
|
|
10460
|
+
writeCommentsCache(prNumber, comments3);
|
|
10412
10461
|
} else {
|
|
10413
10462
|
deleteCommentsCache(prNumber);
|
|
10414
10463
|
}
|
|
@@ -10515,8 +10564,8 @@ async function promptNavigation(currentPage, totalPages) {
|
|
|
10515
10564
|
});
|
|
10516
10565
|
return parseAction(action);
|
|
10517
10566
|
}
|
|
10518
|
-
function computeTotalPages(
|
|
10519
|
-
return Math.ceil(
|
|
10567
|
+
function computeTotalPages(count4) {
|
|
10568
|
+
return Math.ceil(count4 / PAGE_SIZE);
|
|
10520
10569
|
}
|
|
10521
10570
|
async function navigateAndDisplay(pullRequests, totalPages, currentPage) {
|
|
10522
10571
|
const delta = await promptNavigation(currentPage, totalPages);
|
|
@@ -10704,9 +10753,9 @@ function opExec(args) {
|
|
|
10704
10753
|
}).trim();
|
|
10705
10754
|
}
|
|
10706
10755
|
function searchItems(search2) {
|
|
10707
|
-
let
|
|
10756
|
+
let items2;
|
|
10708
10757
|
try {
|
|
10709
|
-
|
|
10758
|
+
items2 = JSON.parse(opExec("item list --format=json"));
|
|
10710
10759
|
} catch {
|
|
10711
10760
|
console.error(
|
|
10712
10761
|
chalk114.red(
|
|
@@ -10716,7 +10765,7 @@ function searchItems(search2) {
|
|
|
10716
10765
|
process.exit(1);
|
|
10717
10766
|
}
|
|
10718
10767
|
const lower = search2.toLowerCase();
|
|
10719
|
-
return
|
|
10768
|
+
return items2.filter((i) => i.title.toLowerCase().includes(lower));
|
|
10720
10769
|
}
|
|
10721
10770
|
function getItemFields(itemId) {
|
|
10722
10771
|
try {
|
|
@@ -10740,14 +10789,14 @@ async function selectOpSecret(searchTerm) {
|
|
|
10740
10789
|
name: "search",
|
|
10741
10790
|
message: "Search 1Password for API key item:"
|
|
10742
10791
|
}).run();
|
|
10743
|
-
const
|
|
10744
|
-
if (
|
|
10792
|
+
const items2 = searchItems(search2);
|
|
10793
|
+
if (items2.length === 0) {
|
|
10745
10794
|
console.error(chalk115.red(`No items found matching "${search2}".`));
|
|
10746
10795
|
process.exit(1);
|
|
10747
10796
|
}
|
|
10748
10797
|
const itemId = await selectOne(
|
|
10749
10798
|
"Select item:",
|
|
10750
|
-
|
|
10799
|
+
items2.map((i) => ({ name: `${i.title} (${i.vault.name})`, value: i.id }))
|
|
10751
10800
|
);
|
|
10752
10801
|
const fields = getItemFields(itemId);
|
|
10753
10802
|
if (fields.length === 0) {
|
|
@@ -11611,9 +11660,9 @@ function resolveImports(target, dependencies, sourceFile, statements = []) {
|
|
|
11611
11660
|
function extractTexts(target, allFunctions, statements) {
|
|
11612
11661
|
const stmtTexts = statements.map((v) => v.getFullText().trim());
|
|
11613
11662
|
const fnTexts = allFunctions.map((fn) => {
|
|
11614
|
-
const
|
|
11615
|
-
if (fn === target && !
|
|
11616
|
-
return
|
|
11663
|
+
const text2 = fn.getFullText().trim();
|
|
11664
|
+
if (fn === target && !text2.startsWith("export ")) return `export ${text2}`;
|
|
11665
|
+
return text2;
|
|
11617
11666
|
});
|
|
11618
11667
|
return [...stmtTexts, ...fnTexts];
|
|
11619
11668
|
}
|
|
@@ -11769,7 +11818,7 @@ function sourceReferencesName(sourceFile, functionName, extractedNames) {
|
|
|
11769
11818
|
}
|
|
11770
11819
|
|
|
11771
11820
|
// src/commands/refactor/extract/buildPlan.ts
|
|
11772
|
-
function
|
|
11821
|
+
function buildPlan2(functionName, sourceFile, sourcePath, destPath, project) {
|
|
11773
11822
|
const analysis = analyseTarget(sourceFile, functionName);
|
|
11774
11823
|
const sourceRelPath = getRelativeImportPath(destPath, sourcePath);
|
|
11775
11824
|
const rewrittenImports = rewriteImportPaths(
|
|
@@ -11923,7 +11972,7 @@ async function extract(file, functionName, destination, options2 = {}) {
|
|
|
11923
11972
|
const cwd = process.cwd();
|
|
11924
11973
|
const relDest = path36.relative(cwd, destPath);
|
|
11925
11974
|
const { project, sourceFile } = loadProjectFile(file);
|
|
11926
|
-
const plan2 =
|
|
11975
|
+
const plan2 = buildPlan2(
|
|
11927
11976
|
functionName,
|
|
11928
11977
|
sourceFile,
|
|
11929
11978
|
sourcePath,
|
|
@@ -12417,8 +12466,8 @@ import fs25 from "fs";
|
|
|
12417
12466
|
import path45 from "path";
|
|
12418
12467
|
function collectEntry(results, dir, entry) {
|
|
12419
12468
|
const full = path45.join(dir, entry.name);
|
|
12420
|
-
const
|
|
12421
|
-
results.push(...
|
|
12469
|
+
const items2 = entry.isDirectory() ? listFilesRecursive(full) : [full];
|
|
12470
|
+
results.push(...items2);
|
|
12422
12471
|
}
|
|
12423
12472
|
function listFilesRecursive(dir) {
|
|
12424
12473
|
if (!fs25.existsSync(dir)) return [];
|
|
@@ -12502,7 +12551,7 @@ function planFileMoves(clusters) {
|
|
|
12502
12551
|
}
|
|
12503
12552
|
|
|
12504
12553
|
// src/commands/refactor/restructure/index.ts
|
|
12505
|
-
function
|
|
12554
|
+
function buildPlan3(candidateFiles, tsConfigPath) {
|
|
12506
12555
|
const candidates = new Set(candidateFiles.map((f) => path47.resolve(f)));
|
|
12507
12556
|
const graph = buildImportGraph(candidates, tsConfigPath);
|
|
12508
12557
|
const allProjectFiles = /* @__PURE__ */ new Set([
|
|
@@ -12527,7 +12576,7 @@ async function restructure(pattern2, options2 = {}) {
|
|
|
12527
12576
|
return;
|
|
12528
12577
|
}
|
|
12529
12578
|
const tsConfigPath = path47.resolve("tsconfig.json");
|
|
12530
|
-
const plan2 =
|
|
12579
|
+
const plan2 = buildPlan3(files, tsConfigPath);
|
|
12531
12580
|
if (plan2.moves.length === 0) {
|
|
12532
12581
|
console.log(chalk134.green("No restructuring needed"));
|
|
12533
12582
|
return;
|
|
@@ -12585,10 +12634,10 @@ function threadKey(c, byId) {
|
|
|
12585
12634
|
}
|
|
12586
12635
|
return `root-${c.id}`;
|
|
12587
12636
|
}
|
|
12588
|
-
function groupIntoThreads(
|
|
12589
|
-
const byId = new Map(
|
|
12637
|
+
function groupIntoThreads(comments3) {
|
|
12638
|
+
const byId = new Map(comments3.map((c) => [c.id, c]));
|
|
12590
12639
|
const threads = /* @__PURE__ */ new Map();
|
|
12591
|
-
for (const c of
|
|
12640
|
+
for (const c of comments3) {
|
|
12592
12641
|
const key = threadKey(c, byId);
|
|
12593
12642
|
const existing = threads.get(key);
|
|
12594
12643
|
const parent = c.inReplyToId !== null ? byId.get(c.inReplyToId) : void 0;
|
|
@@ -12616,9 +12665,9 @@ function formatThread(thread) {
|
|
|
12616
12665
|
return lines.join("\n\n");
|
|
12617
12666
|
}
|
|
12618
12667
|
var INTRO = `The PR already has the review comments below (including resolved and outdated threads). Avoid re-raising findings that a prior comment substantively covers.`;
|
|
12619
|
-
function formatPriorComments(
|
|
12620
|
-
if (
|
|
12621
|
-
const blocks = groupIntoThreads(
|
|
12668
|
+
function formatPriorComments(comments3) {
|
|
12669
|
+
if (comments3.length === 0) return "";
|
|
12670
|
+
const blocks = groupIntoThreads(comments3).map(formatThread);
|
|
12622
12671
|
return `## Prior review comments
|
|
12623
12672
|
|
|
12624
12673
|
${INTRO}
|
|
@@ -13020,9 +13069,9 @@ function warnUnlocated(unlocated) {
|
|
|
13020
13069
|
}
|
|
13021
13070
|
|
|
13022
13071
|
// src/commands/review/postReviewToPr.ts
|
|
13023
|
-
async function confirmPost(prNumber,
|
|
13072
|
+
async function confirmPost(prNumber, count4, options2) {
|
|
13024
13073
|
if (!options2.prompt) return true;
|
|
13025
|
-
return promptConfirm(`Post ${
|
|
13074
|
+
return promptConfirm(`Post ${count4} comment(s) to PR #${prNumber}?`, false);
|
|
13026
13075
|
}
|
|
13027
13076
|
async function postReviewToPr(synthesisPath, options2) {
|
|
13028
13077
|
const prNumber = findCurrentPrNumber();
|
|
@@ -13222,9 +13271,9 @@ var MultiSpinner = class {
|
|
|
13222
13271
|
elapsedPrefix: prefix2
|
|
13223
13272
|
});
|
|
13224
13273
|
}
|
|
13225
|
-
failRemaining(
|
|
13274
|
+
failRemaining(text2) {
|
|
13226
13275
|
for (const entry of this.entries) {
|
|
13227
|
-
if (entry.state === "running") this.resolve(entry, "failed",
|
|
13276
|
+
if (entry.state === "running") this.resolve(entry, "failed", text2);
|
|
13228
13277
|
}
|
|
13229
13278
|
}
|
|
13230
13279
|
add(entry) {
|
|
@@ -13237,14 +13286,14 @@ var MultiSpinner = class {
|
|
|
13237
13286
|
set text(value) {
|
|
13238
13287
|
entry.text = value;
|
|
13239
13288
|
},
|
|
13240
|
-
succeed: (
|
|
13241
|
-
fail: (
|
|
13289
|
+
succeed: (text2) => this.resolve(entry, "succeeded", text2),
|
|
13290
|
+
fail: (text2) => this.resolve(entry, "failed", text2)
|
|
13242
13291
|
};
|
|
13243
13292
|
}
|
|
13244
|
-
resolve(entry, state,
|
|
13293
|
+
resolve(entry, state, text2) {
|
|
13245
13294
|
if (entry.state !== "running") return;
|
|
13246
13295
|
entry.state = state;
|
|
13247
|
-
if (
|
|
13296
|
+
if (text2 !== void 0) entry.text = text2;
|
|
13248
13297
|
entry.elapsedStart = void 0;
|
|
13249
13298
|
this.render();
|
|
13250
13299
|
this.maybeFinish();
|
|
@@ -13319,12 +13368,12 @@ function skippedCodexResult(outputPath) {
|
|
|
13319
13368
|
// src/commands/review/formatReviewerFailure.ts
|
|
13320
13369
|
var FAST_FAIL_MS = 1e3;
|
|
13321
13370
|
var STDOUT_TAIL_LINES = 20;
|
|
13322
|
-
function indent(
|
|
13323
|
-
return
|
|
13371
|
+
function indent(text2) {
|
|
13372
|
+
return text2.split(/\r?\n/).map((line) => ` ${line}`);
|
|
13324
13373
|
}
|
|
13325
|
-
function tailLines(
|
|
13326
|
-
const lines =
|
|
13327
|
-
return lines.length <= maxLines ?
|
|
13374
|
+
function tailLines(text2, maxLines) {
|
|
13375
|
+
const lines = text2.split(/\r?\n/);
|
|
13376
|
+
return lines.length <= maxLines ? text2 : lines.slice(-maxLines).join("\n");
|
|
13328
13377
|
}
|
|
13329
13378
|
function isFastFail(input) {
|
|
13330
13379
|
return input.exitCode !== 0 && input.elapsedMs !== void 0 && input.elapsedMs < FAST_FAIL_MS;
|
|
@@ -14046,9 +14095,9 @@ async function runReviewPipeline(paths, options2) {
|
|
|
14046
14095
|
}
|
|
14047
14096
|
|
|
14048
14097
|
// src/commands/review/reviewPr.ts
|
|
14049
|
-
function logPriorComments(
|
|
14050
|
-
if (
|
|
14051
|
-
console.log(`Including ${
|
|
14098
|
+
function logPriorComments(count4) {
|
|
14099
|
+
if (count4 === 0) return;
|
|
14100
|
+
console.log(`Including ${count4} prior review comment(s) in request.md.`);
|
|
14052
14101
|
}
|
|
14053
14102
|
function gatherChangedContext() {
|
|
14054
14103
|
const context = gatherContext();
|
|
@@ -14290,10 +14339,10 @@ function filterToSql(filter) {
|
|
|
14290
14339
|
}
|
|
14291
14340
|
|
|
14292
14341
|
// src/commands/seq/fetchSeqData.ts
|
|
14293
|
-
async function fetchSeqData(conn, filter,
|
|
14342
|
+
async function fetchSeqData(conn, filter, count4, from, to) {
|
|
14294
14343
|
const sqlFilter = filterToSql(filter);
|
|
14295
|
-
const
|
|
14296
|
-
const params = new URLSearchParams({ q:
|
|
14344
|
+
const sql4 = `select @Timestamp, @Level, @Exception, @Message from stream where ${sqlFilter} order by @Timestamp desc limit ${count4}`;
|
|
14345
|
+
const params = new URLSearchParams({ q: sql4 });
|
|
14297
14346
|
if (from) params.set("fromDateUtc", from);
|
|
14298
14347
|
if (to) params.set("toDateUtc", to);
|
|
14299
14348
|
const response = await fetchSeq(conn, "/api/data", params);
|
|
@@ -14462,12 +14511,12 @@ function resolveConnection2(name) {
|
|
|
14462
14511
|
async function seqQuery(filter, options2) {
|
|
14463
14512
|
rejectTimestampFilter(filter);
|
|
14464
14513
|
const conn = resolveConnection2(options2.connection);
|
|
14465
|
-
const
|
|
14514
|
+
const count4 = Number.parseInt(options2.count ?? "1000", 10);
|
|
14466
14515
|
const from = options2.from ? parseRelativeTime(options2.from) : void 0;
|
|
14467
14516
|
const to = options2.to ? parseRelativeTime(options2.to) : void 0;
|
|
14468
|
-
const events = from || to ? await fetchSeqData(conn, filter,
|
|
14517
|
+
const events = from || to ? await fetchSeqData(conn, filter, count4, from, to) : await fetchSeqEvents(
|
|
14469
14518
|
conn,
|
|
14470
|
-
new URLSearchParams({ filter, count: String(
|
|
14519
|
+
new URLSearchParams({ filter, count: String(count4) })
|
|
14471
14520
|
);
|
|
14472
14521
|
if (events.length === 0) {
|
|
14473
14522
|
console.log(chalk141.yellow("No events found."));
|
|
@@ -14483,10 +14532,10 @@ async function seqQuery(filter, options2) {
|
|
|
14483
14532
|
}
|
|
14484
14533
|
console.log(chalk141.dim(`
|
|
14485
14534
|
${events.length} events`));
|
|
14486
|
-
if (events.length >=
|
|
14535
|
+
if (events.length >= count4) {
|
|
14487
14536
|
console.log(
|
|
14488
14537
|
chalk141.yellow(
|
|
14489
|
-
`Results limited to ${
|
|
14538
|
+
`Results limited to ${count4}. Use --count to retrieve more.`
|
|
14490
14539
|
)
|
|
14491
14540
|
);
|
|
14492
14541
|
}
|
|
@@ -14545,26 +14594,26 @@ import chalk144 from "chalk";
|
|
|
14545
14594
|
// src/commands/sql/loadConnections.ts
|
|
14546
14595
|
function loadConnections3() {
|
|
14547
14596
|
const raw = loadGlobalConfigRaw();
|
|
14548
|
-
const
|
|
14549
|
-
return
|
|
14597
|
+
const sql4 = raw.sql;
|
|
14598
|
+
return sql4?.connections ?? [];
|
|
14550
14599
|
}
|
|
14551
14600
|
function saveConnections3(connections) {
|
|
14552
14601
|
const raw = loadGlobalConfigRaw();
|
|
14553
|
-
const
|
|
14554
|
-
|
|
14555
|
-
raw.sql =
|
|
14602
|
+
const sql4 = raw.sql ?? {};
|
|
14603
|
+
sql4.connections = connections;
|
|
14604
|
+
raw.sql = sql4;
|
|
14556
14605
|
saveGlobalConfig(raw);
|
|
14557
14606
|
}
|
|
14558
14607
|
function getDefaultConnection2() {
|
|
14559
14608
|
const raw = loadGlobalConfigRaw();
|
|
14560
|
-
const
|
|
14561
|
-
return
|
|
14609
|
+
const sql4 = raw.sql;
|
|
14610
|
+
return sql4?.defaultConnection;
|
|
14562
14611
|
}
|
|
14563
14612
|
function setDefaultConnection2(name) {
|
|
14564
14613
|
const raw = loadGlobalConfigRaw();
|
|
14565
|
-
const
|
|
14566
|
-
|
|
14567
|
-
raw.sql =
|
|
14614
|
+
const sql4 = raw.sql ?? {};
|
|
14615
|
+
sql4.defaultConnection = name;
|
|
14616
|
+
raw.sql = sql4;
|
|
14568
14617
|
saveGlobalConfig(raw);
|
|
14569
14618
|
}
|
|
14570
14619
|
|
|
@@ -14635,9 +14684,9 @@ function resolveConnection3(name) {
|
|
|
14635
14684
|
}
|
|
14636
14685
|
|
|
14637
14686
|
// src/commands/sql/sqlConnect.ts
|
|
14638
|
-
import
|
|
14687
|
+
import sql3 from "mssql";
|
|
14639
14688
|
async function sqlConnect(conn) {
|
|
14640
|
-
return await
|
|
14689
|
+
return await sql3.connect({
|
|
14641
14690
|
server: conn.server,
|
|
14642
14691
|
port: conn.port,
|
|
14643
14692
|
user: conn.user,
|
|
@@ -14700,11 +14749,11 @@ var MUTATION_PATTERN = new RegExp(
|
|
|
14700
14749
|
`\\b(${MUTATION_KEYWORDS.join("|")})\\b`,
|
|
14701
14750
|
"i"
|
|
14702
14751
|
);
|
|
14703
|
-
function stripComments(
|
|
14704
|
-
return
|
|
14752
|
+
function stripComments(sql4) {
|
|
14753
|
+
return sql4.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/--[^\n]*/g, " ");
|
|
14705
14754
|
}
|
|
14706
|
-
function isMutation(
|
|
14707
|
-
const stripped = stripComments(
|
|
14755
|
+
function isMutation(sql4) {
|
|
14756
|
+
const stripped = stripComments(sql4);
|
|
14708
14757
|
if (MUTATION_PATTERN.test(stripped)) return true;
|
|
14709
14758
|
return /\bSELECT\b[\s\S]+\bINTO\s+\w/i.test(stripped);
|
|
14710
14759
|
}
|
|
@@ -15000,8 +15049,8 @@ import { existsSync as existsSync39, mkdirSync as mkdirSync11, readFileSync as r
|
|
|
15000
15049
|
import { basename as basename9, dirname as dirname21, join as join39 } from "path";
|
|
15001
15050
|
|
|
15002
15051
|
// src/commands/transcript/cleanText.ts
|
|
15003
|
-
function cleanText(
|
|
15004
|
-
const words =
|
|
15052
|
+
function cleanText(text2) {
|
|
15053
|
+
const words = text2.split(/\s+/);
|
|
15005
15054
|
const cleaned = [];
|
|
15006
15055
|
for (let i = 0; i < words.length; i++) {
|
|
15007
15056
|
let isRepeat = false;
|
|
@@ -15021,8 +15070,8 @@ function cleanText(text) {
|
|
|
15021
15070
|
}
|
|
15022
15071
|
|
|
15023
15072
|
// src/commands/transcript/format/processVttFile/parseVtt/deduplicateCues/removeSubstringDuplicates.ts
|
|
15024
|
-
function normalizeText(
|
|
15025
|
-
return
|
|
15073
|
+
function normalizeText(text2) {
|
|
15074
|
+
return text2.toLowerCase().trim();
|
|
15026
15075
|
}
|
|
15027
15076
|
function checkSubstringRelation(textI, textJ) {
|
|
15028
15077
|
if (textI.includes(textJ) && textI.length > textJ.length)
|
|
@@ -15151,13 +15200,13 @@ function parseTimestampLine(line) {
|
|
|
15151
15200
|
return { startMs: parseTimestamp(startStr), endMs: parseTimestamp(endStr) };
|
|
15152
15201
|
}
|
|
15153
15202
|
function buildCue(startMs, endMs, fullText) {
|
|
15154
|
-
const { speaker, text } = extractSpeaker(fullText);
|
|
15155
|
-
return
|
|
15203
|
+
const { speaker, text: text2 } = extractSpeaker(fullText);
|
|
15204
|
+
return text2 ? { startMs, endMs, speaker, text: text2 } : null;
|
|
15156
15205
|
}
|
|
15157
15206
|
function parseCueLine(lines, i) {
|
|
15158
15207
|
const { startMs, endMs } = parseTimestampLine(lines[i]);
|
|
15159
|
-
const { text, nextIndex } = collectTextLines(lines, i + 1);
|
|
15160
|
-
return { cue: buildCue(startMs, endMs,
|
|
15208
|
+
const { text: text2, nextIndex } = collectTextLines(lines, i + 1);
|
|
15209
|
+
return { cue: buildCue(startMs, endMs, text2), nextIndex };
|
|
15161
15210
|
}
|
|
15162
15211
|
function isCueSeparator(line) {
|
|
15163
15212
|
return line.trim().includes("-->");
|
|
@@ -15521,13 +15570,13 @@ function logs(options2) {
|
|
|
15521
15570
|
console.log("No voice log file found");
|
|
15522
15571
|
return;
|
|
15523
15572
|
}
|
|
15524
|
-
const
|
|
15573
|
+
const count4 = Number.parseInt(options2.lines ?? "150", 10);
|
|
15525
15574
|
const content = readFileSync33(voicePaths.log, "utf-8").trim();
|
|
15526
15575
|
if (!content) {
|
|
15527
15576
|
console.log("Voice log is empty");
|
|
15528
15577
|
return;
|
|
15529
15578
|
}
|
|
15530
|
-
const lines = content.split("\n").slice(-
|
|
15579
|
+
const lines = content.split("\n").slice(-count4);
|
|
15531
15580
|
for (const line of lines) {
|
|
15532
15581
|
try {
|
|
15533
15582
|
const event = JSON.parse(line);
|
|
@@ -15674,10 +15723,10 @@ function isProcessAlive3(pid) {
|
|
|
15674
15723
|
return false;
|
|
15675
15724
|
}
|
|
15676
15725
|
}
|
|
15677
|
-
function readRecentLogs(
|
|
15726
|
+
function readRecentLogs(count4) {
|
|
15678
15727
|
if (!existsSync45(voicePaths.log)) return [];
|
|
15679
15728
|
const lines = readFileSync35(voicePaths.log, "utf-8").trim().split("\n");
|
|
15680
|
-
return lines.slice(-
|
|
15729
|
+
return lines.slice(-count4);
|
|
15681
15730
|
}
|
|
15682
15731
|
function status() {
|
|
15683
15732
|
if (!existsSync45(voicePaths.pid)) {
|
|
@@ -15869,8 +15918,8 @@ async function exchangeToken(params) {
|
|
|
15869
15918
|
body: body.toString()
|
|
15870
15919
|
});
|
|
15871
15920
|
if (!response.ok) {
|
|
15872
|
-
const
|
|
15873
|
-
throw new Error(`Token exchange failed (${response.status}): ${
|
|
15921
|
+
const text2 = await response.text();
|
|
15922
|
+
throw new Error(`Token exchange failed (${response.status}): ${text2}`);
|
|
15874
15923
|
}
|
|
15875
15924
|
return response.json();
|
|
15876
15925
|
}
|
|
@@ -16184,11 +16233,11 @@ import { join as join49 } from "path";
|
|
|
16184
16233
|
|
|
16185
16234
|
// src/commands/run/extractOption.ts
|
|
16186
16235
|
function extractOption(args, flag) {
|
|
16187
|
-
const
|
|
16188
|
-
if (
|
|
16236
|
+
const index2 = args.indexOf(flag);
|
|
16237
|
+
if (index2 === -1) return { value: void 0, remaining: args };
|
|
16189
16238
|
return {
|
|
16190
|
-
value: args[
|
|
16191
|
-
remaining: [...args.slice(0,
|
|
16239
|
+
value: args[index2 + 1],
|
|
16240
|
+
remaining: [...args.slice(0, index2), ...args.slice(index2 + 2)]
|
|
16192
16241
|
};
|
|
16193
16242
|
}
|
|
16194
16243
|
|
|
@@ -16568,13 +16617,13 @@ function* iterateUserMessages(filePath, maxBytes = 65536) {
|
|
|
16568
16617
|
|
|
16569
16618
|
// src/commands/sessions/summarise/extractFirstUserMessage.ts
|
|
16570
16619
|
function extractFirstUserMessage(filePath) {
|
|
16571
|
-
for (const
|
|
16572
|
-
return truncate3(
|
|
16620
|
+
for (const text2 of iterateUserMessages(filePath)) {
|
|
16621
|
+
return truncate3(text2);
|
|
16573
16622
|
}
|
|
16574
16623
|
return void 0;
|
|
16575
16624
|
}
|
|
16576
|
-
function truncate3(
|
|
16577
|
-
const trimmed =
|
|
16625
|
+
function truncate3(text2, maxChars = 500) {
|
|
16626
|
+
const trimmed = text2.trim();
|
|
16578
16627
|
if (trimmed.length <= maxChars) return trimmed;
|
|
16579
16628
|
return `${trimmed.slice(0, maxChars)}\u2026`;
|
|
16580
16629
|
}
|
|
@@ -16582,28 +16631,28 @@ function truncate3(text, maxChars = 500) {
|
|
|
16582
16631
|
// src/commands/sessions/summarise/scanSessionBacklogRefs.ts
|
|
16583
16632
|
function scanSessionBacklogRefs(filePath) {
|
|
16584
16633
|
const ids = /* @__PURE__ */ new Set();
|
|
16585
|
-
for (const
|
|
16586
|
-
for (const id of extractBacklogIds(
|
|
16634
|
+
for (const text2 of iterateUserMessages(filePath, Number.MAX_SAFE_INTEGER)) {
|
|
16635
|
+
for (const id of extractBacklogIds(text2)) {
|
|
16587
16636
|
ids.add(id);
|
|
16588
16637
|
}
|
|
16589
16638
|
}
|
|
16590
16639
|
return [...ids].sort((a, b) => a - b);
|
|
16591
16640
|
}
|
|
16592
|
-
function extractBacklogIds(
|
|
16641
|
+
function extractBacklogIds(text2) {
|
|
16593
16642
|
const ids = [];
|
|
16594
|
-
for (const m of
|
|
16643
|
+
for (const m of text2.matchAll(/backlog\s+run\s+(\d+)/gi)) {
|
|
16595
16644
|
ids.push(Number.parseInt(m[1], 10));
|
|
16596
16645
|
}
|
|
16597
|
-
for (const m of
|
|
16646
|
+
for (const m of text2.matchAll(/backlog\s+(?:item\s+)?#(\d+)/gi)) {
|
|
16598
16647
|
ids.push(Number.parseInt(m[1], 10));
|
|
16599
16648
|
}
|
|
16600
|
-
for (const m of
|
|
16649
|
+
for (const m of text2.matchAll(/backlog\s+phase-done\s+(\d+)/gi)) {
|
|
16601
16650
|
ids.push(Number.parseInt(m[1], 10));
|
|
16602
16651
|
}
|
|
16603
|
-
for (const m of
|
|
16652
|
+
for (const m of text2.matchAll(/backlog\s+comment\s+(\d+)/gi)) {
|
|
16604
16653
|
ids.push(Number.parseInt(m[1], 10));
|
|
16605
16654
|
}
|
|
16606
|
-
for (const m of
|
|
16655
|
+
for (const m of text2.matchAll(/(?:^|[\s(])#(\d{1,4})(?=[\s).,;:!?]|$)/gm)) {
|
|
16607
16656
|
ids.push(Number.parseInt(m[1], 10));
|
|
16608
16657
|
}
|
|
16609
16658
|
return ids;
|