@staff0rd/assist 0.227.0 → 0.228.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 +819 -800
- 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.228.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
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
731
|
+
assignOptionalColumns(item, row);
|
|
732
|
+
return item;
|
|
733
|
+
}
|
|
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);
|
|
643
751
|
return item;
|
|
644
752
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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()}`
|
|
@@ -5493,22 +5529,22 @@ function originDisplayName(origin) {
|
|
|
5493
5529
|
}
|
|
5494
5530
|
|
|
5495
5531
|
// src/commands/backlog/list/index.ts
|
|
5496
|
-
function filterItems(
|
|
5497
|
-
if (options2.status) return
|
|
5532
|
+
function filterItems(items2, options2) {
|
|
5533
|
+
if (options2.status) return items2.filter((i) => i.status === options2.status);
|
|
5498
5534
|
if (!options2.all)
|
|
5499
|
-
return
|
|
5500
|
-
return
|
|
5535
|
+
return items2.filter((i) => i.status !== "done" && i.status !== "wontdo");
|
|
5536
|
+
return items2;
|
|
5501
5537
|
}
|
|
5502
5538
|
async function list2(options2) {
|
|
5503
5539
|
const allItems = await loadBacklog(options2.allRepos);
|
|
5504
|
-
const
|
|
5505
|
-
if (
|
|
5540
|
+
const items2 = filterItems(allItems, options2);
|
|
5541
|
+
if (items2.length === 0) {
|
|
5506
5542
|
console.log(chalk56.dim("Backlog is empty."));
|
|
5507
5543
|
return;
|
|
5508
5544
|
}
|
|
5509
5545
|
const repoNameOf = (item) => item.origin ? originDisplayName(item.origin) : "";
|
|
5510
|
-
const prefixWidth = options2.allRepos ? Math.max(0, ...
|
|
5511
|
-
for (const item of
|
|
5546
|
+
const prefixWidth = options2.allRepos ? Math.max(0, ...items2.map((i) => repoNameOf(i).length)) : 0;
|
|
5547
|
+
for (const item of items2) {
|
|
5512
5548
|
const repoPrefix2 = options2.allRepos ? `${chalk56.dim(repoNameOf(item).padEnd(prefixWidth))} ` : "";
|
|
5513
5549
|
console.log(
|
|
5514
5550
|
`${repoPrefix2}${statusIcon(item.status)} ${typeLabel(item.type)} ${chalk56.dim(`#${item.id}`)} ${item.name}${phaseLabel(item)}${dependencyLabel(item, allItems)}`
|
|
@@ -5540,7 +5576,7 @@ function registerItemCommands(cmd) {
|
|
|
5540
5576
|
import chalk58 from "chalk";
|
|
5541
5577
|
|
|
5542
5578
|
// src/commands/backlog/hasCycle.ts
|
|
5543
|
-
function hasCycle(
|
|
5579
|
+
function hasCycle(adjacency, fromId, toId) {
|
|
5544
5580
|
const visited = /* @__PURE__ */ new Set();
|
|
5545
5581
|
const stack = [toId];
|
|
5546
5582
|
while (stack.length > 0) {
|
|
@@ -5548,100 +5584,107 @@ function hasCycle(items, fromId, toId) {
|
|
|
5548
5584
|
if (current === fromId) return true;
|
|
5549
5585
|
if (visited.has(current)) continue;
|
|
5550
5586
|
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
|
-
}
|
|
5587
|
+
for (const target of adjacency.get(current) ?? []) {
|
|
5588
|
+
stack.push(target);
|
|
5557
5589
|
}
|
|
5558
5590
|
}
|
|
5559
5591
|
return false;
|
|
5560
5592
|
}
|
|
5561
5593
|
|
|
5594
|
+
// src/commands/backlog/loadDependencyGraph.ts
|
|
5595
|
+
import { eq as eq13 } from "drizzle-orm";
|
|
5596
|
+
async function loadDependencyGraph(orm) {
|
|
5597
|
+
const rows = await orm.select({ itemId: links.itemId, targetId: links.targetId }).from(links).where(eq13(links.type, "depends-on"));
|
|
5598
|
+
const graph = /* @__PURE__ */ new Map();
|
|
5599
|
+
for (const { itemId, targetId } of rows) {
|
|
5600
|
+
const bucket = graph.get(itemId);
|
|
5601
|
+
if (bucket) bucket.push(targetId);
|
|
5602
|
+
else graph.set(itemId, [targetId]);
|
|
5603
|
+
}
|
|
5604
|
+
return graph;
|
|
5605
|
+
}
|
|
5606
|
+
|
|
5607
|
+
// src/commands/backlog/loadItem.ts
|
|
5608
|
+
import { eq as eq14 } from "drizzle-orm";
|
|
5609
|
+
async function loadItem(orm, id) {
|
|
5610
|
+
const [row] = await orm.select().from(items).where(eq14(items.id, id));
|
|
5611
|
+
if (!row) return void 0;
|
|
5612
|
+
const rel = await loadRelations(orm, [id]);
|
|
5613
|
+
return rowToItem(row, rel);
|
|
5614
|
+
}
|
|
5615
|
+
|
|
5562
5616
|
// src/commands/backlog/validateLinkTarget.ts
|
|
5563
5617
|
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(
|
|
5618
|
+
function validateLinkTarget(fromItem, fromNum, toNum, linkType) {
|
|
5619
|
+
const duplicate = (fromItem.links ?? []).some(
|
|
5572
5620
|
(l) => l.targetId === toNum && l.type === linkType
|
|
5573
5621
|
);
|
|
5574
5622
|
if (duplicate) {
|
|
5575
5623
|
console.log(
|
|
5576
|
-
chalk57.yellow(`Link already exists: #${
|
|
5624
|
+
chalk57.yellow(`Link already exists: #${fromNum} ${linkType} #${toNum}`)
|
|
5577
5625
|
);
|
|
5578
|
-
return
|
|
5626
|
+
return false;
|
|
5579
5627
|
}
|
|
5580
|
-
return
|
|
5628
|
+
return true;
|
|
5581
5629
|
}
|
|
5582
5630
|
|
|
5583
5631
|
// src/commands/backlog/link.ts
|
|
5632
|
+
function fail(message) {
|
|
5633
|
+
console.log(chalk58.red(message));
|
|
5634
|
+
return void 0;
|
|
5635
|
+
}
|
|
5636
|
+
function parseLinkType(type) {
|
|
5637
|
+
const linkType = type ?? "relates-to";
|
|
5638
|
+
if (linkType === "relates-to" || linkType === "depends-on") return linkType;
|
|
5639
|
+
return fail(`Invalid link type: ${linkType}`);
|
|
5640
|
+
}
|
|
5641
|
+
async function createsCycle(orm, linkType, fromNum, toNum) {
|
|
5642
|
+
if (linkType !== "depends-on") return false;
|
|
5643
|
+
const graph = await loadDependencyGraph(orm);
|
|
5644
|
+
if (!hasCycle(graph, fromNum, toNum)) return false;
|
|
5645
|
+
fail(`Cannot add dependency: #${fromNum} \u2192 #${toNum} would create a cycle.`);
|
|
5646
|
+
return true;
|
|
5647
|
+
}
|
|
5584
5648
|
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
|
-
}
|
|
5649
|
+
const linkType = parseLinkType(opts.type);
|
|
5650
|
+
if (!linkType) return;
|
|
5590
5651
|
const fromNum = Number.parseInt(fromId, 10);
|
|
5591
5652
|
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);
|
|
5653
|
+
if (fromNum === toNum) return void fail("Cannot link an item to itself.");
|
|
5654
|
+
const { orm } = await getReady();
|
|
5655
|
+
const fromItem = await loadItem(orm, fromNum);
|
|
5656
|
+
if (!fromItem) return void fail(`Item #${fromNum} not found.`);
|
|
5657
|
+
const toItem = await loadItem(orm, toNum);
|
|
5658
|
+
if (!toItem) return void fail(`Item #${toNum} not found.`);
|
|
5659
|
+
if (!validateLinkTarget(fromItem, fromNum, toNum, linkType)) return;
|
|
5660
|
+
if (await createsCycle(orm, linkType, fromNum, toNum)) return;
|
|
5661
|
+
await orm.insert(links).values({ itemId: fromNum, type: linkType, targetId: toNum });
|
|
5619
5662
|
console.log(
|
|
5620
|
-
chalk58.green(`Linked #${
|
|
5663
|
+
chalk58.green(`Linked #${fromNum} ${linkType} #${toNum} (${toItem.name})`)
|
|
5621
5664
|
);
|
|
5622
5665
|
}
|
|
5623
5666
|
|
|
5624
5667
|
// src/commands/backlog/unlink.ts
|
|
5625
5668
|
import chalk59 from "chalk";
|
|
5669
|
+
import { and as and4, eq as eq15 } from "drizzle-orm";
|
|
5626
5670
|
async function unlink(fromId, toId) {
|
|
5671
|
+
const fromNum = Number.parseInt(fromId, 10);
|
|
5627
5672
|
const toNum = Number.parseInt(toId, 10);
|
|
5628
|
-
const
|
|
5629
|
-
|
|
5630
|
-
|
|
5673
|
+
const { orm } = await getReady();
|
|
5674
|
+
const fromItem = await loadItem(orm, fromNum);
|
|
5675
|
+
if (!fromItem) {
|
|
5676
|
+
console.log(chalk59.red(`Item #${fromId} not found.`));
|
|
5677
|
+
return;
|
|
5678
|
+
}
|
|
5631
5679
|
if (!fromItem.links || fromItem.links.length === 0) {
|
|
5632
5680
|
console.log(chalk59.yellow(`No links found on item #${fromId}.`));
|
|
5633
5681
|
return;
|
|
5634
5682
|
}
|
|
5635
|
-
|
|
5636
|
-
fromItem.links = fromItem.links.filter((l) => l.targetId !== toNum);
|
|
5637
|
-
if (fromItem.links.length === before) {
|
|
5683
|
+
if (!fromItem.links.some((l) => l.targetId === toNum)) {
|
|
5638
5684
|
console.log(chalk59.yellow(`No link from #${fromId} to #${toId} found.`));
|
|
5639
5685
|
return;
|
|
5640
5686
|
}
|
|
5641
|
-
|
|
5642
|
-
fromItem.links = void 0;
|
|
5643
|
-
}
|
|
5644
|
-
await saveBacklog(items);
|
|
5687
|
+
await orm.delete(links).where(and4(eq15(links.itemId, fromNum), eq15(links.targetId, toNum)));
|
|
5645
5688
|
console.log(chalk59.green(`Removed link from #${fromId} to #${toId}.`));
|
|
5646
5689
|
}
|
|
5647
5690
|
|
|
@@ -5679,9 +5722,12 @@ function validateRewind2(item, phaseNumber) {
|
|
|
5679
5722
|
async function rewindPhase(id, phase, opts) {
|
|
5680
5723
|
const phaseNumber = Number.parseInt(phase, 10);
|
|
5681
5724
|
const phaseIndex = phaseNumber - 1;
|
|
5682
|
-
const
|
|
5683
|
-
|
|
5684
|
-
|
|
5725
|
+
const { orm } = await getReady();
|
|
5726
|
+
const item = await loadItem(orm, Number.parseInt(id, 10));
|
|
5727
|
+
if (!item) {
|
|
5728
|
+
console.log(chalk60.red(`Item #${id} not found.`));
|
|
5729
|
+
return;
|
|
5730
|
+
}
|
|
5685
5731
|
const error = validateRewind2(item, phaseNumber);
|
|
5686
5732
|
if (error) {
|
|
5687
5733
|
console.log(chalk60.red(error));
|
|
@@ -5689,12 +5735,12 @@ async function rewindPhase(id, phase, opts) {
|
|
|
5689
5735
|
return;
|
|
5690
5736
|
}
|
|
5691
5737
|
const phaseName = item.plan?.[phaseIndex].name;
|
|
5692
|
-
|
|
5693
|
-
|
|
5738
|
+
await appendComment(
|
|
5739
|
+
orm,
|
|
5740
|
+
item.id,
|
|
5694
5741
|
`Rewound to phase ${phaseNumber} (${phaseName}): ${opts.reason}`,
|
|
5695
|
-
phaseNumber
|
|
5742
|
+
{ phase: phaseNumber }
|
|
5696
5743
|
);
|
|
5697
|
-
await saveBacklog(result.items);
|
|
5698
5744
|
await setCurrentPhase(id, phaseNumber);
|
|
5699
5745
|
await setStatus(id, "in-progress");
|
|
5700
5746
|
writeSignal("rewind", {
|
|
@@ -5721,18 +5767,18 @@ function registerRunCommand(cmd) {
|
|
|
5721
5767
|
// src/commands/backlog/search/index.ts
|
|
5722
5768
|
import chalk61 from "chalk";
|
|
5723
5769
|
async function search(query) {
|
|
5724
|
-
const
|
|
5725
|
-
if (
|
|
5770
|
+
const items2 = await searchBacklog(query);
|
|
5771
|
+
if (items2.length === 0) {
|
|
5726
5772
|
console.log(chalk61.dim(`No items matching "${query}".`));
|
|
5727
5773
|
return;
|
|
5728
5774
|
}
|
|
5729
5775
|
console.log(
|
|
5730
5776
|
chalk61.dim(
|
|
5731
|
-
`${
|
|
5777
|
+
`${items2.length} item${items2.length === 1 ? "" : "s"} matching "${query}":
|
|
5732
5778
|
`
|
|
5733
5779
|
)
|
|
5734
5780
|
);
|
|
5735
|
-
for (const item of
|
|
5781
|
+
for (const item of items2) {
|
|
5736
5782
|
console.log(
|
|
5737
5783
|
`${statusIcon(item.status)} ${typeLabel(item.type)} ${chalk61.dim(`#${item.id}`)} ${item.name}`
|
|
5738
5784
|
);
|
|
@@ -5796,8 +5842,8 @@ async function start(id) {
|
|
|
5796
5842
|
// src/commands/backlog/stop/index.ts
|
|
5797
5843
|
import chalk65 from "chalk";
|
|
5798
5844
|
async function stop() {
|
|
5799
|
-
const
|
|
5800
|
-
const inProgress =
|
|
5845
|
+
const items2 = await loadBacklog();
|
|
5846
|
+
const inProgress = items2.filter((item) => item.status === "in-progress");
|
|
5801
5847
|
if (inProgress.length === 0) {
|
|
5802
5848
|
console.log(chalk65.yellow("No in-progress items to stop."));
|
|
5803
5849
|
return;
|
|
@@ -5806,7 +5852,7 @@ async function stop() {
|
|
|
5806
5852
|
item.status = "todo";
|
|
5807
5853
|
item.currentPhase = 1;
|
|
5808
5854
|
}
|
|
5809
|
-
await saveBacklog(
|
|
5855
|
+
await saveBacklog(items2);
|
|
5810
5856
|
for (const item of inProgress) {
|
|
5811
5857
|
console.log(chalk65.yellow(`Stopped item #${item.id}: ${item.name}`));
|
|
5812
5858
|
}
|
|
@@ -5837,19 +5883,18 @@ function registerStatusCommands(cmd) {
|
|
|
5837
5883
|
|
|
5838
5884
|
// src/commands/backlog/removePhase.ts
|
|
5839
5885
|
import chalk68 from "chalk";
|
|
5886
|
+
import { and as and7, eq as eq18 } from "drizzle-orm";
|
|
5840
5887
|
|
|
5841
5888
|
// src/commands/backlog/findPhase.ts
|
|
5842
5889
|
import chalk67 from "chalk";
|
|
5890
|
+
import { and as and5, count as count2, eq as eq16 } from "drizzle-orm";
|
|
5843
5891
|
async function findPhase(id, phase) {
|
|
5844
5892
|
const result = await loadAndFindItem(id);
|
|
5845
5893
|
if (!result) return void 0;
|
|
5846
|
-
const
|
|
5894
|
+
const orm = await getBacklogOrm();
|
|
5847
5895
|
const itemId = result.item.id;
|
|
5848
5896
|
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
|
-
);
|
|
5897
|
+
const [row] = await orm.select({ cnt: count2() }).from(planPhases).where(and5(eq16(planPhases.itemId, itemId), eq16(planPhases.idx, phaseIdx)));
|
|
5853
5898
|
if (!row || row.cnt === 0) {
|
|
5854
5899
|
console.log(
|
|
5855
5900
|
chalk67.red(`Phase ${phaseIdx + 1} not found on item #${itemId}.`)
|
|
@@ -5857,26 +5902,18 @@ async function findPhase(id, phase) {
|
|
|
5857
5902
|
process.exitCode = 1;
|
|
5858
5903
|
return void 0;
|
|
5859
5904
|
}
|
|
5860
|
-
return { result,
|
|
5905
|
+
return { result, orm, itemId, phaseIdx };
|
|
5861
5906
|
}
|
|
5862
5907
|
|
|
5863
5908
|
// src/commands/backlog/reindexPhases.ts
|
|
5909
|
+
import { and as and6, asc as asc4, count as count3, eq as eq17 } from "drizzle-orm";
|
|
5864
5910
|
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
|
-
);
|
|
5911
|
+
const remaining = await db.select({ idx: planPhases.idx }).from(planPhases).where(eq17(planPhases.itemId, itemId)).orderBy(asc4(planPhases.idx));
|
|
5869
5912
|
for (let i = 0; i < remaining.length; i++) {
|
|
5870
5913
|
const oldIdx = remaining[i].idx;
|
|
5871
5914
|
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
|
-
);
|
|
5915
|
+
await db.update(planTasks).set({ phaseIdx: i }).where(and6(eq17(planTasks.itemId, itemId), eq17(planTasks.phaseIdx, oldIdx)));
|
|
5916
|
+
await db.update(planPhases).set({ idx: i }).where(and6(eq17(planPhases.itemId, itemId), eq17(planPhases.idx, oldIdx)));
|
|
5880
5917
|
}
|
|
5881
5918
|
}
|
|
5882
5919
|
async function adjustCurrentPhase(db, item, removedIdx) {
|
|
@@ -5884,38 +5921,25 @@ async function adjustCurrentPhase(db, item, removedIdx) {
|
|
|
5884
5921
|
if (currentPhase === void 0) return;
|
|
5885
5922
|
const currentIdx = currentPhase - 1;
|
|
5886
5923
|
if (removedIdx < currentIdx) {
|
|
5887
|
-
await db.
|
|
5888
|
-
currentPhase - 1,
|
|
5889
|
-
item.id
|
|
5890
|
-
]);
|
|
5924
|
+
await db.update(items).set({ currentPhase: currentPhase - 1 }).where(eq17(items.id, item.id));
|
|
5891
5925
|
return;
|
|
5892
5926
|
}
|
|
5893
5927
|
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
|
-
);
|
|
5928
|
+
const [row] = await db.select({ cnt: count3() }).from(planPhases).where(eq17(planPhases.itemId, item.id));
|
|
5898
5929
|
const cnt = row?.cnt ?? 0;
|
|
5899
|
-
await db.
|
|
5900
|
-
cnt === 0 ? null : Math.min(currentPhase, cnt),
|
|
5901
|
-
item.id
|
|
5902
|
-
]);
|
|
5930
|
+
await db.update(items).set({ currentPhase: cnt === 0 ? null : Math.min(currentPhase, cnt) }).where(eq17(items.id, item.id));
|
|
5903
5931
|
}
|
|
5904
5932
|
|
|
5905
5933
|
// src/commands/backlog/removePhase.ts
|
|
5906
5934
|
async function removePhase(id, phase) {
|
|
5907
5935
|
const found = await findPhase(id, phase);
|
|
5908
5936
|
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
|
-
]);
|
|
5937
|
+
const { result, orm, itemId, phaseIdx } = found;
|
|
5938
|
+
await orm.transaction(async (tx) => {
|
|
5939
|
+
await tx.delete(planTasks).where(
|
|
5940
|
+
and7(eq18(planTasks.itemId, itemId), eq18(planTasks.phaseIdx, phaseIdx))
|
|
5941
|
+
);
|
|
5942
|
+
await tx.delete(planPhases).where(and7(eq18(planPhases.itemId, itemId), eq18(planPhases.idx, phaseIdx)));
|
|
5919
5943
|
await reindexPhases(tx, itemId);
|
|
5920
5944
|
await adjustCurrentPhase(tx, result.item, phaseIdx);
|
|
5921
5945
|
});
|
|
@@ -5926,20 +5950,21 @@ async function removePhase(id, phase) {
|
|
|
5926
5950
|
|
|
5927
5951
|
// src/commands/backlog/update/index.ts
|
|
5928
5952
|
import chalk70 from "chalk";
|
|
5953
|
+
import { eq as eq19 } from "drizzle-orm";
|
|
5929
5954
|
|
|
5930
5955
|
// src/commands/backlog/update/parseListIndex.ts
|
|
5931
5956
|
function parseListIndex(raw, length, label2) {
|
|
5932
5957
|
if (!/^\d+$/.test(raw)) {
|
|
5933
5958
|
return { ok: false, error: `${label2} must be a positive integer.` };
|
|
5934
5959
|
}
|
|
5935
|
-
const
|
|
5936
|
-
if (
|
|
5960
|
+
const index2 = Number.parseInt(raw, 10);
|
|
5961
|
+
if (index2 < 1 || index2 > length) {
|
|
5937
5962
|
return {
|
|
5938
5963
|
ok: false,
|
|
5939
|
-
error: `${label2} ${
|
|
5964
|
+
error: `${label2} ${index2} is out of range (1-${length}).`
|
|
5940
5965
|
};
|
|
5941
5966
|
}
|
|
5942
|
-
return { ok: true, index };
|
|
5967
|
+
return { ok: true, index: index2 };
|
|
5943
5968
|
}
|
|
5944
5969
|
|
|
5945
5970
|
// src/commands/backlog/update/applyListMutations.ts
|
|
@@ -5949,7 +5974,7 @@ function hasListMutations(options2) {
|
|
|
5949
5974
|
);
|
|
5950
5975
|
}
|
|
5951
5976
|
function applyListMutations(current, options2, flags) {
|
|
5952
|
-
let
|
|
5977
|
+
let items2 = [...current];
|
|
5953
5978
|
if (options2.edit) {
|
|
5954
5979
|
const [rawIndex, ...textParts] = options2.edit;
|
|
5955
5980
|
if (rawIndex === void 0 || textParts.length === 0) {
|
|
@@ -5960,25 +5985,25 @@ function applyListMutations(current, options2, flags) {
|
|
|
5960
5985
|
}
|
|
5961
5986
|
const parsed = parseListIndex(
|
|
5962
5987
|
rawIndex,
|
|
5963
|
-
|
|
5988
|
+
items2.length,
|
|
5964
5989
|
`${flags.edit} index`
|
|
5965
5990
|
);
|
|
5966
5991
|
if (!parsed.ok) return parsed;
|
|
5967
|
-
|
|
5992
|
+
items2[parsed.index - 1] = textParts.join(" ");
|
|
5968
5993
|
}
|
|
5969
5994
|
if (options2.remove) {
|
|
5970
5995
|
const parsed = parseListIndex(
|
|
5971
5996
|
options2.remove,
|
|
5972
|
-
|
|
5997
|
+
items2.length,
|
|
5973
5998
|
`${flags.remove} index`
|
|
5974
5999
|
);
|
|
5975
6000
|
if (!parsed.ok) return parsed;
|
|
5976
|
-
|
|
6001
|
+
items2 = items2.filter((_, i) => i !== parsed.index - 1);
|
|
5977
6002
|
}
|
|
5978
6003
|
if (options2.add) {
|
|
5979
|
-
|
|
6004
|
+
items2.push(...options2.add);
|
|
5980
6005
|
}
|
|
5981
|
-
return { ok: true, items };
|
|
6006
|
+
return { ok: true, items: items2 };
|
|
5982
6007
|
}
|
|
5983
6008
|
|
|
5984
6009
|
// src/commands/backlog/update/applyAcMutations.ts
|
|
@@ -5999,11 +6024,11 @@ function applyAcMutations(current, options2) {
|
|
|
5999
6024
|
return { ok: true, criteria: result.items };
|
|
6000
6025
|
}
|
|
6001
6026
|
|
|
6002
|
-
// src/commands/backlog/update/
|
|
6027
|
+
// src/commands/backlog/update/buildUpdateValues.ts
|
|
6003
6028
|
import chalk69 from "chalk";
|
|
6004
|
-
function
|
|
6005
|
-
const { name, desc, type, ac } = options2;
|
|
6006
|
-
if (!name && !
|
|
6029
|
+
function buildUpdateValues(options2) {
|
|
6030
|
+
const { name, desc: desc2, type, ac } = options2;
|
|
6031
|
+
if (!name && !desc2 && !type && !ac) {
|
|
6007
6032
|
console.log(chalk69.red("Nothing to update. Provide at least one flag."));
|
|
6008
6033
|
process.exitCode = 1;
|
|
6009
6034
|
return void 0;
|
|
@@ -6013,30 +6038,25 @@ function buildUpdateSql(options2) {
|
|
|
6013
6038
|
process.exitCode = 1;
|
|
6014
6039
|
return void 0;
|
|
6015
6040
|
}
|
|
6016
|
-
const
|
|
6017
|
-
const params = [];
|
|
6041
|
+
const set = {};
|
|
6018
6042
|
const fieldNames = [];
|
|
6019
6043
|
if (name) {
|
|
6020
|
-
|
|
6021
|
-
params.push(name);
|
|
6044
|
+
set.name = name;
|
|
6022
6045
|
fieldNames.push("name");
|
|
6023
6046
|
}
|
|
6024
|
-
if (
|
|
6025
|
-
|
|
6026
|
-
params.push(desc);
|
|
6047
|
+
if (desc2) {
|
|
6048
|
+
set.description = desc2;
|
|
6027
6049
|
fieldNames.push("description");
|
|
6028
6050
|
}
|
|
6029
6051
|
if (type) {
|
|
6030
|
-
|
|
6031
|
-
params.push(type);
|
|
6052
|
+
set.type = type;
|
|
6032
6053
|
fieldNames.push("type");
|
|
6033
6054
|
}
|
|
6034
6055
|
if (ac) {
|
|
6035
|
-
|
|
6036
|
-
params.push(JSON.stringify(ac));
|
|
6056
|
+
set.acceptanceCriteria = JSON.stringify(ac);
|
|
6037
6057
|
fieldNames.push("acceptance criteria");
|
|
6038
6058
|
}
|
|
6039
|
-
return {
|
|
6059
|
+
return { set, fields: fieldNames.join(", ") };
|
|
6040
6060
|
}
|
|
6041
6061
|
|
|
6042
6062
|
// src/commands/backlog/update/index.ts
|
|
@@ -6060,14 +6080,11 @@ async function update(id, options2) {
|
|
|
6060
6080
|
}
|
|
6061
6081
|
ac = mutation.criteria;
|
|
6062
6082
|
}
|
|
6063
|
-
const built =
|
|
6083
|
+
const built = buildUpdateValues({ ...options2, ac });
|
|
6064
6084
|
if (!built) return;
|
|
6065
|
-
const
|
|
6085
|
+
const orm = await getBacklogOrm();
|
|
6066
6086
|
const itemId = result.item.id;
|
|
6067
|
-
await
|
|
6068
|
-
...built.params,
|
|
6069
|
-
itemId
|
|
6070
|
-
]);
|
|
6087
|
+
await orm.update(items).set(built.set).where(eq19(items.id, itemId));
|
|
6071
6088
|
console.log(chalk70.green(`Updated ${built.fields} on item #${itemId}.`));
|
|
6072
6089
|
}
|
|
6073
6090
|
|
|
@@ -6075,29 +6092,31 @@ async function update(id, options2) {
|
|
|
6075
6092
|
import chalk71 from "chalk";
|
|
6076
6093
|
|
|
6077
6094
|
// src/commands/backlog/applyPhaseUpdate.ts
|
|
6078
|
-
|
|
6079
|
-
|
|
6095
|
+
import { and as and8, eq as eq20 } from "drizzle-orm";
|
|
6096
|
+
async function applyPhaseUpdate(orm, itemId, phaseIdx, fields) {
|
|
6097
|
+
await orm.transaction(async (tx) => {
|
|
6080
6098
|
if (fields.name) {
|
|
6081
|
-
await tx.
|
|
6082
|
-
|
|
6083
|
-
[fields.name, itemId, phaseIdx]
|
|
6099
|
+
await tx.update(planPhases).set({ name: fields.name }).where(
|
|
6100
|
+
and8(eq20(planPhases.itemId, itemId), eq20(planPhases.idx, phaseIdx))
|
|
6084
6101
|
);
|
|
6085
6102
|
}
|
|
6086
6103
|
if (fields.manualCheck) {
|
|
6087
|
-
await tx.
|
|
6088
|
-
|
|
6089
|
-
[JSON.stringify(fields.manualCheck), itemId, phaseIdx]
|
|
6104
|
+
await tx.update(planPhases).set({ manualChecks: JSON.stringify(fields.manualCheck) }).where(
|
|
6105
|
+
and8(eq20(planPhases.itemId, itemId), eq20(planPhases.idx, phaseIdx))
|
|
6090
6106
|
);
|
|
6091
6107
|
}
|
|
6092
6108
|
if (fields.task) {
|
|
6093
|
-
await tx.
|
|
6094
|
-
|
|
6095
|
-
[itemId, phaseIdx]
|
|
6109
|
+
await tx.delete(planTasks).where(
|
|
6110
|
+
and8(eq20(planTasks.itemId, itemId), eq20(planTasks.phaseIdx, phaseIdx))
|
|
6096
6111
|
);
|
|
6097
|
-
|
|
6098
|
-
await tx.
|
|
6099
|
-
|
|
6100
|
-
|
|
6112
|
+
if (fields.task.length) {
|
|
6113
|
+
await tx.insert(planTasks).values(
|
|
6114
|
+
fields.task.map((task, i) => ({
|
|
6115
|
+
itemId,
|
|
6116
|
+
phaseIdx,
|
|
6117
|
+
idx: i,
|
|
6118
|
+
task
|
|
6119
|
+
}))
|
|
6101
6120
|
);
|
|
6102
6121
|
}
|
|
6103
6122
|
}
|
|
@@ -6171,7 +6190,7 @@ function resolvePhaseFields(options2, current) {
|
|
|
6171
6190
|
async function updatePhase(id, phase, options2) {
|
|
6172
6191
|
const found = await findPhase(id, phase);
|
|
6173
6192
|
if (!found) return;
|
|
6174
|
-
const { result,
|
|
6193
|
+
const { result, orm, itemId, phaseIdx } = found;
|
|
6175
6194
|
const resolved = resolvePhaseFields(options2, result.item.plan?.[phaseIdx]);
|
|
6176
6195
|
if (!resolved.ok) {
|
|
6177
6196
|
console.log(chalk71.red(resolved.error));
|
|
@@ -6179,7 +6198,7 @@ async function updatePhase(id, phase, options2) {
|
|
|
6179
6198
|
return;
|
|
6180
6199
|
}
|
|
6181
6200
|
const { name, task, manualCheck } = resolved.fields;
|
|
6182
|
-
await applyPhaseUpdate(
|
|
6201
|
+
await applyPhaseUpdate(orm, itemId, phaseIdx, { name, task, manualCheck });
|
|
6183
6202
|
const fields = [
|
|
6184
6203
|
name && "name",
|
|
6185
6204
|
task && "tasks",
|
|
@@ -6380,7 +6399,7 @@ import { mkdirSync as mkdirSync4 } from "fs";
|
|
|
6380
6399
|
import { homedir as homedir4 } from "os";
|
|
6381
6400
|
import { join as join18 } from "path";
|
|
6382
6401
|
import Database from "better-sqlite3";
|
|
6383
|
-
var
|
|
6402
|
+
var _db;
|
|
6384
6403
|
function getDbDir() {
|
|
6385
6404
|
return join18(homedir4(), ".assist");
|
|
6386
6405
|
}
|
|
@@ -6398,13 +6417,13 @@ function initSchema(db) {
|
|
|
6398
6417
|
`);
|
|
6399
6418
|
}
|
|
6400
6419
|
function openPromptsDb(dir) {
|
|
6401
|
-
if (
|
|
6420
|
+
if (_db) return _db;
|
|
6402
6421
|
const dbDir = dir ?? getDbDir();
|
|
6403
6422
|
mkdirSync4(dbDir, { recursive: true });
|
|
6404
6423
|
const db = new Database(join18(dbDir, "assist.db"));
|
|
6405
6424
|
db.pragma("journal_mode = WAL");
|
|
6406
6425
|
initSchema(db);
|
|
6407
|
-
|
|
6426
|
+
_db = db;
|
|
6408
6427
|
return db;
|
|
6409
6428
|
}
|
|
6410
6429
|
function logDeniedToolCall(entry) {
|
|
@@ -6881,17 +6900,17 @@ function isClaudeCode() {
|
|
|
6881
6900
|
}
|
|
6882
6901
|
|
|
6883
6902
|
// src/commands/permitCliReads/mapAsync.ts
|
|
6884
|
-
async function mapAsync(
|
|
6885
|
-
const results = new Array(
|
|
6903
|
+
async function mapAsync(items2, concurrency, fn) {
|
|
6904
|
+
const results = new Array(items2.length);
|
|
6886
6905
|
let next3 = 0;
|
|
6887
6906
|
async function worker() {
|
|
6888
|
-
while (next3 <
|
|
6907
|
+
while (next3 < items2.length) {
|
|
6889
6908
|
const idx = next3++;
|
|
6890
|
-
results[idx] = await fn(
|
|
6909
|
+
results[idx] = await fn(items2[idx]);
|
|
6891
6910
|
}
|
|
6892
6911
|
}
|
|
6893
6912
|
const workers = Array.from(
|
|
6894
|
-
{ length: Math.min(concurrency,
|
|
6913
|
+
{ length: Math.min(concurrency, items2.length) },
|
|
6895
6914
|
() => worker()
|
|
6896
6915
|
);
|
|
6897
6916
|
await Promise.all(workers);
|
|
@@ -7089,8 +7108,8 @@ function formatHuman(cli, commands) {
|
|
|
7089
7108
|
`];
|
|
7090
7109
|
for (const cmd of sorted) {
|
|
7091
7110
|
const full = `${cli} ${cmd.path.join(" ")}`;
|
|
7092
|
-
const
|
|
7093
|
-
lines.push(`${prefix(classifyVerb(cmd.path))}${
|
|
7111
|
+
const text2 = cmd.description ? `${full} \u2014 ${cmd.description}` : full;
|
|
7112
|
+
lines.push(`${prefix(classifyVerb(cmd.path))}${text2}`);
|
|
7094
7113
|
}
|
|
7095
7114
|
return lines.join("\n");
|
|
7096
7115
|
}
|
|
@@ -7225,12 +7244,12 @@ function denyList() {
|
|
|
7225
7244
|
import chalk75 from "chalk";
|
|
7226
7245
|
function denyRemove(pattern2, options2) {
|
|
7227
7246
|
const { deny, saveDeny } = loadDenyConfig(options2.global);
|
|
7228
|
-
const
|
|
7229
|
-
if (
|
|
7247
|
+
const index2 = deny.findIndex((r) => r.pattern === pattern2);
|
|
7248
|
+
if (index2 === -1) {
|
|
7230
7249
|
console.log(chalk75.yellow(`No deny rule found for: ${pattern2}`));
|
|
7231
7250
|
return;
|
|
7232
7251
|
}
|
|
7233
|
-
deny.splice(
|
|
7252
|
+
deny.splice(index2, 1);
|
|
7234
7253
|
saveDeny(deny.length > 0 ? deny : void 0);
|
|
7235
7254
|
console.log(chalk75.green(`Removed deny rule: ${pattern2}`));
|
|
7236
7255
|
}
|
|
@@ -7467,7 +7486,7 @@ function calculateHalstead(node) {
|
|
|
7467
7486
|
// src/commands/complexity/shared/countSloc.ts
|
|
7468
7487
|
function countSloc(content) {
|
|
7469
7488
|
let inMultiLineComment = false;
|
|
7470
|
-
let
|
|
7489
|
+
let count4 = 0;
|
|
7471
7490
|
for (const line of content.split("\n")) {
|
|
7472
7491
|
const trimmed = line.trim();
|
|
7473
7492
|
if (inMultiLineComment) {
|
|
@@ -7475,7 +7494,7 @@ function countSloc(content) {
|
|
|
7475
7494
|
inMultiLineComment = false;
|
|
7476
7495
|
const afterComment = trimmed.substring(trimmed.indexOf("*/") + 2);
|
|
7477
7496
|
if (afterComment.trim().length > 0) {
|
|
7478
|
-
|
|
7497
|
+
count4++;
|
|
7479
7498
|
}
|
|
7480
7499
|
}
|
|
7481
7500
|
continue;
|
|
@@ -7487,7 +7506,7 @@ function countSloc(content) {
|
|
|
7487
7506
|
if (trimmed.includes("*/")) {
|
|
7488
7507
|
const afterComment = trimmed.substring(trimmed.indexOf("*/") + 2);
|
|
7489
7508
|
if (afterComment.trim().length > 0) {
|
|
7490
|
-
|
|
7509
|
+
count4++;
|
|
7491
7510
|
}
|
|
7492
7511
|
} else {
|
|
7493
7512
|
inMultiLineComment = true;
|
|
@@ -7495,10 +7514,10 @@ function countSloc(content) {
|
|
|
7495
7514
|
continue;
|
|
7496
7515
|
}
|
|
7497
7516
|
if (trimmed.length > 0) {
|
|
7498
|
-
|
|
7517
|
+
count4++;
|
|
7499
7518
|
}
|
|
7500
7519
|
}
|
|
7501
|
-
return
|
|
7520
|
+
return count4;
|
|
7502
7521
|
}
|
|
7503
7522
|
|
|
7504
7523
|
// src/commands/complexity/shared/index.ts
|
|
@@ -9193,16 +9212,16 @@ function parseUserLine(line) {
|
|
|
9193
9212
|
if (entry.type !== "user") return void 0;
|
|
9194
9213
|
const msg = entry.message;
|
|
9195
9214
|
const c = msg?.content;
|
|
9196
|
-
let
|
|
9215
|
+
let text2;
|
|
9197
9216
|
if (typeof c === "string") {
|
|
9198
|
-
|
|
9217
|
+
text2 = c;
|
|
9199
9218
|
} else if (Array.isArray(c)) {
|
|
9200
9219
|
const collected = c.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n");
|
|
9201
|
-
|
|
9220
|
+
text2 = collected || void 0;
|
|
9202
9221
|
}
|
|
9203
|
-
if (!
|
|
9222
|
+
if (!text2) return void 0;
|
|
9204
9223
|
return {
|
|
9205
|
-
text,
|
|
9224
|
+
text: text2,
|
|
9206
9225
|
entrypoint: typeof entry.entrypoint === "string" ? entry.entrypoint : void 0
|
|
9207
9226
|
};
|
|
9208
9227
|
}
|
|
@@ -9253,12 +9272,12 @@ ${payload}`;
|
|
|
9253
9272
|
return "";
|
|
9254
9273
|
}
|
|
9255
9274
|
}
|
|
9256
|
-
function stripPreludes(
|
|
9257
|
-
return
|
|
9275
|
+
function stripPreludes(text2) {
|
|
9276
|
+
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
9277
|
}
|
|
9259
|
-
function capPayload(
|
|
9260
|
-
const buf = Buffer.from(
|
|
9261
|
-
if (buf.length <= maxBytes) return
|
|
9278
|
+
function capPayload(text2, maxBytes) {
|
|
9279
|
+
const buf = Buffer.from(text2, "utf8");
|
|
9280
|
+
if (buf.length <= maxBytes) return text2;
|
|
9262
9281
|
return buf.subarray(buf.length - maxBytes).toString("utf8");
|
|
9263
9282
|
}
|
|
9264
9283
|
function normaliseOutput(raw) {
|
|
@@ -9327,9 +9346,9 @@ import chalk103 from "chalk";
|
|
|
9327
9346
|
|
|
9328
9347
|
// src/commands/jira/adfToText.ts
|
|
9329
9348
|
function renderInline(node) {
|
|
9330
|
-
const
|
|
9331
|
-
if (node.marks?.some((m) => m.type === "code")) return `\`${
|
|
9332
|
-
return
|
|
9349
|
+
const text2 = node.text ?? "";
|
|
9350
|
+
if (node.marks?.some((m) => m.type === "code")) return `\`${text2}\``;
|
|
9351
|
+
return text2;
|
|
9333
9352
|
}
|
|
9334
9353
|
function renderChildren(node, indent2) {
|
|
9335
9354
|
return renderNodes(node.content ?? [], indent2);
|
|
@@ -9371,8 +9390,8 @@ function isListNode(node) {
|
|
|
9371
9390
|
function renderListChild(child, indent2, pad, marker, isFirst) {
|
|
9372
9391
|
if (isListNode(child)) return renderNodes([child], indent2 + 1);
|
|
9373
9392
|
if (child.type !== "paragraph") return renderNode(child, indent2);
|
|
9374
|
-
const
|
|
9375
|
-
return isFirst ? `${pad}${marker} ${
|
|
9393
|
+
const text2 = renderChildren(child, indent2);
|
|
9394
|
+
return isFirst ? `${pad}${marker} ${text2}` : `${pad} ${text2}`;
|
|
9376
9395
|
}
|
|
9377
9396
|
function renderListItem(node, indent2, marker) {
|
|
9378
9397
|
const pad = " ".repeat(indent2);
|
|
@@ -9735,9 +9754,9 @@ function excerpt(xml, ...tags) {
|
|
|
9735
9754
|
for (const tag of tags) {
|
|
9736
9755
|
const raw = extractText(xml, tag);
|
|
9737
9756
|
if (!raw) continue;
|
|
9738
|
-
const
|
|
9739
|
-
if (
|
|
9740
|
-
return `${
|
|
9757
|
+
const text2 = stripHtml(raw);
|
|
9758
|
+
if (text2.length <= MAX_EXCERPT) return text2;
|
|
9759
|
+
return `${text2.slice(0, MAX_EXCERPT)}\u2026`;
|
|
9741
9760
|
}
|
|
9742
9761
|
return "";
|
|
9743
9762
|
}
|
|
@@ -9782,22 +9801,22 @@ async function fetchFeeds(urls, onProgress) {
|
|
|
9782
9801
|
const origin = new URL(url).origin;
|
|
9783
9802
|
const res = await fetch(url);
|
|
9784
9803
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
9785
|
-
const
|
|
9804
|
+
const items3 = parseFeed(await res.text(), origin);
|
|
9786
9805
|
done2++;
|
|
9787
9806
|
onProgress?.(done2, urls.length);
|
|
9788
|
-
return
|
|
9807
|
+
return items3;
|
|
9789
9808
|
})
|
|
9790
9809
|
);
|
|
9791
|
-
const
|
|
9810
|
+
const items2 = [];
|
|
9792
9811
|
for (const result of results) {
|
|
9793
9812
|
if (result.status === "fulfilled") {
|
|
9794
|
-
|
|
9813
|
+
items2.push(...result.value);
|
|
9795
9814
|
}
|
|
9796
9815
|
}
|
|
9797
|
-
|
|
9816
|
+
items2.sort(
|
|
9798
9817
|
(a, b) => new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime()
|
|
9799
9818
|
);
|
|
9800
|
-
return
|
|
9819
|
+
return items2;
|
|
9801
9820
|
}
|
|
9802
9821
|
|
|
9803
9822
|
// src/commands/news/web/getHtml.ts
|
|
@@ -9833,13 +9852,13 @@ function prefetch() {
|
|
|
9833
9852
|
process.stdout.write(
|
|
9834
9853
|
`\r${chalk109.dim(`Fetching feeds ${bar} ${done2}/${t}`)}`
|
|
9835
9854
|
);
|
|
9836
|
-
}).then((
|
|
9855
|
+
}).then((items2) => {
|
|
9837
9856
|
process.stdout.write(
|
|
9838
|
-
`\r${chalk109.green(`Fetched ${
|
|
9857
|
+
`\r${chalk109.green(`Fetched ${items2.length} items from ${total} feed(s)`)}
|
|
9839
9858
|
`
|
|
9840
9859
|
);
|
|
9841
|
-
cachedItems =
|
|
9842
|
-
return
|
|
9860
|
+
cachedItems = items2;
|
|
9861
|
+
return items2;
|
|
9843
9862
|
});
|
|
9844
9863
|
}
|
|
9845
9864
|
async function listItems2(_req, res) {
|
|
@@ -9901,11 +9920,11 @@ function printPromptsTable(rows) {
|
|
|
9901
9920
|
console.log(chalk110.dim(header));
|
|
9902
9921
|
console.log(chalk110.dim("-".repeat(header.length)));
|
|
9903
9922
|
for (const row of rows) {
|
|
9904
|
-
const
|
|
9923
|
+
const count4 = String(row.count).padStart(countWidth);
|
|
9905
9924
|
const tool = row.tool.padEnd(toolWidth);
|
|
9906
9925
|
const command = truncate(row.command, 60).padEnd(commandWidth);
|
|
9907
9926
|
console.log(
|
|
9908
|
-
`${chalk110.yellow(
|
|
9927
|
+
`${chalk110.yellow(count4)} ${tool} ${command} ${chalk110.dim(row.repos)}`
|
|
9909
9928
|
);
|
|
9910
9929
|
}
|
|
9911
9930
|
}
|
|
@@ -10196,8 +10215,8 @@ function requireCache(prNumber) {
|
|
|
10196
10215
|
}
|
|
10197
10216
|
return cache;
|
|
10198
10217
|
}
|
|
10199
|
-
function findLineComment(
|
|
10200
|
-
return
|
|
10218
|
+
function findLineComment(comments3, commentId) {
|
|
10219
|
+
return comments3.find((c) => c.type === "line" && c.id === commentId);
|
|
10201
10220
|
}
|
|
10202
10221
|
function requireLineComment(cache, commentId) {
|
|
10203
10222
|
const comment3 = findLineComment(cache.comments, commentId);
|
|
@@ -10330,8 +10349,8 @@ function mapLineComment(c, threadInfo) {
|
|
|
10330
10349
|
};
|
|
10331
10350
|
}
|
|
10332
10351
|
function fetchLineComments(org, repo, prNumber, threadInfo) {
|
|
10333
|
-
const
|
|
10334
|
-
return
|
|
10352
|
+
const comments3 = fetchJson(`repos/${org}/${repo}/pulls/${prNumber}/comments`);
|
|
10353
|
+
return comments3.map(
|
|
10335
10354
|
(c) => mapLineComment(c, threadInfo)
|
|
10336
10355
|
);
|
|
10337
10356
|
}
|
|
@@ -10355,33 +10374,33 @@ function formatForHuman(comment3) {
|
|
|
10355
10374
|
""
|
|
10356
10375
|
].join("\n");
|
|
10357
10376
|
}
|
|
10358
|
-
function summarise2(
|
|
10359
|
-
const lineCount =
|
|
10360
|
-
const reviewCount =
|
|
10377
|
+
function summarise2(comments3) {
|
|
10378
|
+
const lineCount = comments3.filter((c) => c.type === "line").length;
|
|
10379
|
+
const reviewCount = comments3.filter((c) => c.type === "review").length;
|
|
10361
10380
|
const parts = [];
|
|
10362
10381
|
if (lineCount > 0) parts.push(`${lineCount} line`);
|
|
10363
10382
|
if (reviewCount > 0) parts.push(`${reviewCount} review`);
|
|
10364
|
-
return `Found ${parts.join(" and ")} comment${
|
|
10383
|
+
return `Found ${parts.join(" and ")} comment${comments3.length === 1 ? "" : "s"}.`;
|
|
10365
10384
|
}
|
|
10366
10385
|
function printComments2(result) {
|
|
10367
|
-
const { comments:
|
|
10368
|
-
if (
|
|
10386
|
+
const { comments: comments3, cachePath } = result;
|
|
10387
|
+
if (comments3.length === 0) {
|
|
10369
10388
|
console.log("No comments found.");
|
|
10370
10389
|
return;
|
|
10371
10390
|
}
|
|
10372
10391
|
if (!isClaudeCode()) {
|
|
10373
|
-
for (const comment3 of
|
|
10392
|
+
for (const comment3 of comments3) {
|
|
10374
10393
|
console.log(formatForHuman(comment3));
|
|
10375
10394
|
}
|
|
10376
10395
|
}
|
|
10377
|
-
console.log(summarise2(
|
|
10396
|
+
console.log(summarise2(comments3));
|
|
10378
10397
|
if (cachePath) {
|
|
10379
10398
|
console.log(`Saved to ${cachePath}`);
|
|
10380
10399
|
}
|
|
10381
10400
|
}
|
|
10382
10401
|
|
|
10383
10402
|
// src/commands/prs/listComments/index.ts
|
|
10384
|
-
function writeCommentsCache(prNumber,
|
|
10403
|
+
function writeCommentsCache(prNumber, comments3) {
|
|
10385
10404
|
const assistDir = join34(process.cwd(), ".assist");
|
|
10386
10405
|
if (!existsSync34(assistDir)) {
|
|
10387
10406
|
mkdirSync9(assistDir, { recursive: true });
|
|
@@ -10389,7 +10408,7 @@ function writeCommentsCache(prNumber, comments2) {
|
|
|
10389
10408
|
const cacheData = {
|
|
10390
10409
|
prNumber,
|
|
10391
10410
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10392
|
-
comments:
|
|
10411
|
+
comments: comments3
|
|
10393
10412
|
};
|
|
10394
10413
|
const cachePath = join34(assistDir, `pr-${prNumber}-comments.yaml`);
|
|
10395
10414
|
writeFileSync23(cachePath, stringify(cacheData));
|
|
@@ -10406,9 +10425,9 @@ function handleKnownErrors(error) {
|
|
|
10406
10425
|
}
|
|
10407
10426
|
return null;
|
|
10408
10427
|
}
|
|
10409
|
-
function updateCache(prNumber,
|
|
10410
|
-
if (
|
|
10411
|
-
writeCommentsCache(prNumber,
|
|
10428
|
+
function updateCache(prNumber, comments3) {
|
|
10429
|
+
if (comments3.some((c) => c.type === "line")) {
|
|
10430
|
+
writeCommentsCache(prNumber, comments3);
|
|
10412
10431
|
} else {
|
|
10413
10432
|
deleteCommentsCache(prNumber);
|
|
10414
10433
|
}
|
|
@@ -10515,8 +10534,8 @@ async function promptNavigation(currentPage, totalPages) {
|
|
|
10515
10534
|
});
|
|
10516
10535
|
return parseAction(action);
|
|
10517
10536
|
}
|
|
10518
|
-
function computeTotalPages(
|
|
10519
|
-
return Math.ceil(
|
|
10537
|
+
function computeTotalPages(count4) {
|
|
10538
|
+
return Math.ceil(count4 / PAGE_SIZE);
|
|
10520
10539
|
}
|
|
10521
10540
|
async function navigateAndDisplay(pullRequests, totalPages, currentPage) {
|
|
10522
10541
|
const delta = await promptNavigation(currentPage, totalPages);
|
|
@@ -10704,9 +10723,9 @@ function opExec(args) {
|
|
|
10704
10723
|
}).trim();
|
|
10705
10724
|
}
|
|
10706
10725
|
function searchItems(search2) {
|
|
10707
|
-
let
|
|
10726
|
+
let items2;
|
|
10708
10727
|
try {
|
|
10709
|
-
|
|
10728
|
+
items2 = JSON.parse(opExec("item list --format=json"));
|
|
10710
10729
|
} catch {
|
|
10711
10730
|
console.error(
|
|
10712
10731
|
chalk114.red(
|
|
@@ -10716,7 +10735,7 @@ function searchItems(search2) {
|
|
|
10716
10735
|
process.exit(1);
|
|
10717
10736
|
}
|
|
10718
10737
|
const lower = search2.toLowerCase();
|
|
10719
|
-
return
|
|
10738
|
+
return items2.filter((i) => i.title.toLowerCase().includes(lower));
|
|
10720
10739
|
}
|
|
10721
10740
|
function getItemFields(itemId) {
|
|
10722
10741
|
try {
|
|
@@ -10740,14 +10759,14 @@ async function selectOpSecret(searchTerm) {
|
|
|
10740
10759
|
name: "search",
|
|
10741
10760
|
message: "Search 1Password for API key item:"
|
|
10742
10761
|
}).run();
|
|
10743
|
-
const
|
|
10744
|
-
if (
|
|
10762
|
+
const items2 = searchItems(search2);
|
|
10763
|
+
if (items2.length === 0) {
|
|
10745
10764
|
console.error(chalk115.red(`No items found matching "${search2}".`));
|
|
10746
10765
|
process.exit(1);
|
|
10747
10766
|
}
|
|
10748
10767
|
const itemId = await selectOne(
|
|
10749
10768
|
"Select item:",
|
|
10750
|
-
|
|
10769
|
+
items2.map((i) => ({ name: `${i.title} (${i.vault.name})`, value: i.id }))
|
|
10751
10770
|
);
|
|
10752
10771
|
const fields = getItemFields(itemId);
|
|
10753
10772
|
if (fields.length === 0) {
|
|
@@ -11611,9 +11630,9 @@ function resolveImports(target, dependencies, sourceFile, statements = []) {
|
|
|
11611
11630
|
function extractTexts(target, allFunctions, statements) {
|
|
11612
11631
|
const stmtTexts = statements.map((v) => v.getFullText().trim());
|
|
11613
11632
|
const fnTexts = allFunctions.map((fn) => {
|
|
11614
|
-
const
|
|
11615
|
-
if (fn === target && !
|
|
11616
|
-
return
|
|
11633
|
+
const text2 = fn.getFullText().trim();
|
|
11634
|
+
if (fn === target && !text2.startsWith("export ")) return `export ${text2}`;
|
|
11635
|
+
return text2;
|
|
11617
11636
|
});
|
|
11618
11637
|
return [...stmtTexts, ...fnTexts];
|
|
11619
11638
|
}
|
|
@@ -11769,7 +11788,7 @@ function sourceReferencesName(sourceFile, functionName, extractedNames) {
|
|
|
11769
11788
|
}
|
|
11770
11789
|
|
|
11771
11790
|
// src/commands/refactor/extract/buildPlan.ts
|
|
11772
|
-
function
|
|
11791
|
+
function buildPlan2(functionName, sourceFile, sourcePath, destPath, project) {
|
|
11773
11792
|
const analysis = analyseTarget(sourceFile, functionName);
|
|
11774
11793
|
const sourceRelPath = getRelativeImportPath(destPath, sourcePath);
|
|
11775
11794
|
const rewrittenImports = rewriteImportPaths(
|
|
@@ -11923,7 +11942,7 @@ async function extract(file, functionName, destination, options2 = {}) {
|
|
|
11923
11942
|
const cwd = process.cwd();
|
|
11924
11943
|
const relDest = path36.relative(cwd, destPath);
|
|
11925
11944
|
const { project, sourceFile } = loadProjectFile(file);
|
|
11926
|
-
const plan2 =
|
|
11945
|
+
const plan2 = buildPlan2(
|
|
11927
11946
|
functionName,
|
|
11928
11947
|
sourceFile,
|
|
11929
11948
|
sourcePath,
|
|
@@ -12417,8 +12436,8 @@ import fs25 from "fs";
|
|
|
12417
12436
|
import path45 from "path";
|
|
12418
12437
|
function collectEntry(results, dir, entry) {
|
|
12419
12438
|
const full = path45.join(dir, entry.name);
|
|
12420
|
-
const
|
|
12421
|
-
results.push(...
|
|
12439
|
+
const items2 = entry.isDirectory() ? listFilesRecursive(full) : [full];
|
|
12440
|
+
results.push(...items2);
|
|
12422
12441
|
}
|
|
12423
12442
|
function listFilesRecursive(dir) {
|
|
12424
12443
|
if (!fs25.existsSync(dir)) return [];
|
|
@@ -12502,7 +12521,7 @@ function planFileMoves(clusters) {
|
|
|
12502
12521
|
}
|
|
12503
12522
|
|
|
12504
12523
|
// src/commands/refactor/restructure/index.ts
|
|
12505
|
-
function
|
|
12524
|
+
function buildPlan3(candidateFiles, tsConfigPath) {
|
|
12506
12525
|
const candidates = new Set(candidateFiles.map((f) => path47.resolve(f)));
|
|
12507
12526
|
const graph = buildImportGraph(candidates, tsConfigPath);
|
|
12508
12527
|
const allProjectFiles = /* @__PURE__ */ new Set([
|
|
@@ -12527,7 +12546,7 @@ async function restructure(pattern2, options2 = {}) {
|
|
|
12527
12546
|
return;
|
|
12528
12547
|
}
|
|
12529
12548
|
const tsConfigPath = path47.resolve("tsconfig.json");
|
|
12530
|
-
const plan2 =
|
|
12549
|
+
const plan2 = buildPlan3(files, tsConfigPath);
|
|
12531
12550
|
if (plan2.moves.length === 0) {
|
|
12532
12551
|
console.log(chalk134.green("No restructuring needed"));
|
|
12533
12552
|
return;
|
|
@@ -12585,10 +12604,10 @@ function threadKey(c, byId) {
|
|
|
12585
12604
|
}
|
|
12586
12605
|
return `root-${c.id}`;
|
|
12587
12606
|
}
|
|
12588
|
-
function groupIntoThreads(
|
|
12589
|
-
const byId = new Map(
|
|
12607
|
+
function groupIntoThreads(comments3) {
|
|
12608
|
+
const byId = new Map(comments3.map((c) => [c.id, c]));
|
|
12590
12609
|
const threads = /* @__PURE__ */ new Map();
|
|
12591
|
-
for (const c of
|
|
12610
|
+
for (const c of comments3) {
|
|
12592
12611
|
const key = threadKey(c, byId);
|
|
12593
12612
|
const existing = threads.get(key);
|
|
12594
12613
|
const parent = c.inReplyToId !== null ? byId.get(c.inReplyToId) : void 0;
|
|
@@ -12616,9 +12635,9 @@ function formatThread(thread) {
|
|
|
12616
12635
|
return lines.join("\n\n");
|
|
12617
12636
|
}
|
|
12618
12637
|
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(
|
|
12638
|
+
function formatPriorComments(comments3) {
|
|
12639
|
+
if (comments3.length === 0) return "";
|
|
12640
|
+
const blocks = groupIntoThreads(comments3).map(formatThread);
|
|
12622
12641
|
return `## Prior review comments
|
|
12623
12642
|
|
|
12624
12643
|
${INTRO}
|
|
@@ -13020,9 +13039,9 @@ function warnUnlocated(unlocated) {
|
|
|
13020
13039
|
}
|
|
13021
13040
|
|
|
13022
13041
|
// src/commands/review/postReviewToPr.ts
|
|
13023
|
-
async function confirmPost(prNumber,
|
|
13042
|
+
async function confirmPost(prNumber, count4, options2) {
|
|
13024
13043
|
if (!options2.prompt) return true;
|
|
13025
|
-
return promptConfirm(`Post ${
|
|
13044
|
+
return promptConfirm(`Post ${count4} comment(s) to PR #${prNumber}?`, false);
|
|
13026
13045
|
}
|
|
13027
13046
|
async function postReviewToPr(synthesisPath, options2) {
|
|
13028
13047
|
const prNumber = findCurrentPrNumber();
|
|
@@ -13222,9 +13241,9 @@ var MultiSpinner = class {
|
|
|
13222
13241
|
elapsedPrefix: prefix2
|
|
13223
13242
|
});
|
|
13224
13243
|
}
|
|
13225
|
-
failRemaining(
|
|
13244
|
+
failRemaining(text2) {
|
|
13226
13245
|
for (const entry of this.entries) {
|
|
13227
|
-
if (entry.state === "running") this.resolve(entry, "failed",
|
|
13246
|
+
if (entry.state === "running") this.resolve(entry, "failed", text2);
|
|
13228
13247
|
}
|
|
13229
13248
|
}
|
|
13230
13249
|
add(entry) {
|
|
@@ -13237,14 +13256,14 @@ var MultiSpinner = class {
|
|
|
13237
13256
|
set text(value) {
|
|
13238
13257
|
entry.text = value;
|
|
13239
13258
|
},
|
|
13240
|
-
succeed: (
|
|
13241
|
-
fail: (
|
|
13259
|
+
succeed: (text2) => this.resolve(entry, "succeeded", text2),
|
|
13260
|
+
fail: (text2) => this.resolve(entry, "failed", text2)
|
|
13242
13261
|
};
|
|
13243
13262
|
}
|
|
13244
|
-
resolve(entry, state,
|
|
13263
|
+
resolve(entry, state, text2) {
|
|
13245
13264
|
if (entry.state !== "running") return;
|
|
13246
13265
|
entry.state = state;
|
|
13247
|
-
if (
|
|
13266
|
+
if (text2 !== void 0) entry.text = text2;
|
|
13248
13267
|
entry.elapsedStart = void 0;
|
|
13249
13268
|
this.render();
|
|
13250
13269
|
this.maybeFinish();
|
|
@@ -13319,12 +13338,12 @@ function skippedCodexResult(outputPath) {
|
|
|
13319
13338
|
// src/commands/review/formatReviewerFailure.ts
|
|
13320
13339
|
var FAST_FAIL_MS = 1e3;
|
|
13321
13340
|
var STDOUT_TAIL_LINES = 20;
|
|
13322
|
-
function indent(
|
|
13323
|
-
return
|
|
13341
|
+
function indent(text2) {
|
|
13342
|
+
return text2.split(/\r?\n/).map((line) => ` ${line}`);
|
|
13324
13343
|
}
|
|
13325
|
-
function tailLines(
|
|
13326
|
-
const lines =
|
|
13327
|
-
return lines.length <= maxLines ?
|
|
13344
|
+
function tailLines(text2, maxLines) {
|
|
13345
|
+
const lines = text2.split(/\r?\n/);
|
|
13346
|
+
return lines.length <= maxLines ? text2 : lines.slice(-maxLines).join("\n");
|
|
13328
13347
|
}
|
|
13329
13348
|
function isFastFail(input) {
|
|
13330
13349
|
return input.exitCode !== 0 && input.elapsedMs !== void 0 && input.elapsedMs < FAST_FAIL_MS;
|
|
@@ -14046,9 +14065,9 @@ async function runReviewPipeline(paths, options2) {
|
|
|
14046
14065
|
}
|
|
14047
14066
|
|
|
14048
14067
|
// src/commands/review/reviewPr.ts
|
|
14049
|
-
function logPriorComments(
|
|
14050
|
-
if (
|
|
14051
|
-
console.log(`Including ${
|
|
14068
|
+
function logPriorComments(count4) {
|
|
14069
|
+
if (count4 === 0) return;
|
|
14070
|
+
console.log(`Including ${count4} prior review comment(s) in request.md.`);
|
|
14052
14071
|
}
|
|
14053
14072
|
function gatherChangedContext() {
|
|
14054
14073
|
const context = gatherContext();
|
|
@@ -14290,10 +14309,10 @@ function filterToSql(filter) {
|
|
|
14290
14309
|
}
|
|
14291
14310
|
|
|
14292
14311
|
// src/commands/seq/fetchSeqData.ts
|
|
14293
|
-
async function fetchSeqData(conn, filter,
|
|
14312
|
+
async function fetchSeqData(conn, filter, count4, from, to) {
|
|
14294
14313
|
const sqlFilter = filterToSql(filter);
|
|
14295
|
-
const
|
|
14296
|
-
const params = new URLSearchParams({ q:
|
|
14314
|
+
const sql4 = `select @Timestamp, @Level, @Exception, @Message from stream where ${sqlFilter} order by @Timestamp desc limit ${count4}`;
|
|
14315
|
+
const params = new URLSearchParams({ q: sql4 });
|
|
14297
14316
|
if (from) params.set("fromDateUtc", from);
|
|
14298
14317
|
if (to) params.set("toDateUtc", to);
|
|
14299
14318
|
const response = await fetchSeq(conn, "/api/data", params);
|
|
@@ -14462,12 +14481,12 @@ function resolveConnection2(name) {
|
|
|
14462
14481
|
async function seqQuery(filter, options2) {
|
|
14463
14482
|
rejectTimestampFilter(filter);
|
|
14464
14483
|
const conn = resolveConnection2(options2.connection);
|
|
14465
|
-
const
|
|
14484
|
+
const count4 = Number.parseInt(options2.count ?? "1000", 10);
|
|
14466
14485
|
const from = options2.from ? parseRelativeTime(options2.from) : void 0;
|
|
14467
14486
|
const to = options2.to ? parseRelativeTime(options2.to) : void 0;
|
|
14468
|
-
const events = from || to ? await fetchSeqData(conn, filter,
|
|
14487
|
+
const events = from || to ? await fetchSeqData(conn, filter, count4, from, to) : await fetchSeqEvents(
|
|
14469
14488
|
conn,
|
|
14470
|
-
new URLSearchParams({ filter, count: String(
|
|
14489
|
+
new URLSearchParams({ filter, count: String(count4) })
|
|
14471
14490
|
);
|
|
14472
14491
|
if (events.length === 0) {
|
|
14473
14492
|
console.log(chalk141.yellow("No events found."));
|
|
@@ -14483,10 +14502,10 @@ async function seqQuery(filter, options2) {
|
|
|
14483
14502
|
}
|
|
14484
14503
|
console.log(chalk141.dim(`
|
|
14485
14504
|
${events.length} events`));
|
|
14486
|
-
if (events.length >=
|
|
14505
|
+
if (events.length >= count4) {
|
|
14487
14506
|
console.log(
|
|
14488
14507
|
chalk141.yellow(
|
|
14489
|
-
`Results limited to ${
|
|
14508
|
+
`Results limited to ${count4}. Use --count to retrieve more.`
|
|
14490
14509
|
)
|
|
14491
14510
|
);
|
|
14492
14511
|
}
|
|
@@ -14545,26 +14564,26 @@ import chalk144 from "chalk";
|
|
|
14545
14564
|
// src/commands/sql/loadConnections.ts
|
|
14546
14565
|
function loadConnections3() {
|
|
14547
14566
|
const raw = loadGlobalConfigRaw();
|
|
14548
|
-
const
|
|
14549
|
-
return
|
|
14567
|
+
const sql4 = raw.sql;
|
|
14568
|
+
return sql4?.connections ?? [];
|
|
14550
14569
|
}
|
|
14551
14570
|
function saveConnections3(connections) {
|
|
14552
14571
|
const raw = loadGlobalConfigRaw();
|
|
14553
|
-
const
|
|
14554
|
-
|
|
14555
|
-
raw.sql =
|
|
14572
|
+
const sql4 = raw.sql ?? {};
|
|
14573
|
+
sql4.connections = connections;
|
|
14574
|
+
raw.sql = sql4;
|
|
14556
14575
|
saveGlobalConfig(raw);
|
|
14557
14576
|
}
|
|
14558
14577
|
function getDefaultConnection2() {
|
|
14559
14578
|
const raw = loadGlobalConfigRaw();
|
|
14560
|
-
const
|
|
14561
|
-
return
|
|
14579
|
+
const sql4 = raw.sql;
|
|
14580
|
+
return sql4?.defaultConnection;
|
|
14562
14581
|
}
|
|
14563
14582
|
function setDefaultConnection2(name) {
|
|
14564
14583
|
const raw = loadGlobalConfigRaw();
|
|
14565
|
-
const
|
|
14566
|
-
|
|
14567
|
-
raw.sql =
|
|
14584
|
+
const sql4 = raw.sql ?? {};
|
|
14585
|
+
sql4.defaultConnection = name;
|
|
14586
|
+
raw.sql = sql4;
|
|
14568
14587
|
saveGlobalConfig(raw);
|
|
14569
14588
|
}
|
|
14570
14589
|
|
|
@@ -14635,9 +14654,9 @@ function resolveConnection3(name) {
|
|
|
14635
14654
|
}
|
|
14636
14655
|
|
|
14637
14656
|
// src/commands/sql/sqlConnect.ts
|
|
14638
|
-
import
|
|
14657
|
+
import sql3 from "mssql";
|
|
14639
14658
|
async function sqlConnect(conn) {
|
|
14640
|
-
return await
|
|
14659
|
+
return await sql3.connect({
|
|
14641
14660
|
server: conn.server,
|
|
14642
14661
|
port: conn.port,
|
|
14643
14662
|
user: conn.user,
|
|
@@ -14700,11 +14719,11 @@ var MUTATION_PATTERN = new RegExp(
|
|
|
14700
14719
|
`\\b(${MUTATION_KEYWORDS.join("|")})\\b`,
|
|
14701
14720
|
"i"
|
|
14702
14721
|
);
|
|
14703
|
-
function stripComments(
|
|
14704
|
-
return
|
|
14722
|
+
function stripComments(sql4) {
|
|
14723
|
+
return sql4.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/--[^\n]*/g, " ");
|
|
14705
14724
|
}
|
|
14706
|
-
function isMutation(
|
|
14707
|
-
const stripped = stripComments(
|
|
14725
|
+
function isMutation(sql4) {
|
|
14726
|
+
const stripped = stripComments(sql4);
|
|
14708
14727
|
if (MUTATION_PATTERN.test(stripped)) return true;
|
|
14709
14728
|
return /\bSELECT\b[\s\S]+\bINTO\s+\w/i.test(stripped);
|
|
14710
14729
|
}
|
|
@@ -15000,8 +15019,8 @@ import { existsSync as existsSync39, mkdirSync as mkdirSync11, readFileSync as r
|
|
|
15000
15019
|
import { basename as basename9, dirname as dirname21, join as join39 } from "path";
|
|
15001
15020
|
|
|
15002
15021
|
// src/commands/transcript/cleanText.ts
|
|
15003
|
-
function cleanText(
|
|
15004
|
-
const words =
|
|
15022
|
+
function cleanText(text2) {
|
|
15023
|
+
const words = text2.split(/\s+/);
|
|
15005
15024
|
const cleaned = [];
|
|
15006
15025
|
for (let i = 0; i < words.length; i++) {
|
|
15007
15026
|
let isRepeat = false;
|
|
@@ -15021,8 +15040,8 @@ function cleanText(text) {
|
|
|
15021
15040
|
}
|
|
15022
15041
|
|
|
15023
15042
|
// src/commands/transcript/format/processVttFile/parseVtt/deduplicateCues/removeSubstringDuplicates.ts
|
|
15024
|
-
function normalizeText(
|
|
15025
|
-
return
|
|
15043
|
+
function normalizeText(text2) {
|
|
15044
|
+
return text2.toLowerCase().trim();
|
|
15026
15045
|
}
|
|
15027
15046
|
function checkSubstringRelation(textI, textJ) {
|
|
15028
15047
|
if (textI.includes(textJ) && textI.length > textJ.length)
|
|
@@ -15151,13 +15170,13 @@ function parseTimestampLine(line) {
|
|
|
15151
15170
|
return { startMs: parseTimestamp(startStr), endMs: parseTimestamp(endStr) };
|
|
15152
15171
|
}
|
|
15153
15172
|
function buildCue(startMs, endMs, fullText) {
|
|
15154
|
-
const { speaker, text } = extractSpeaker(fullText);
|
|
15155
|
-
return
|
|
15173
|
+
const { speaker, text: text2 } = extractSpeaker(fullText);
|
|
15174
|
+
return text2 ? { startMs, endMs, speaker, text: text2 } : null;
|
|
15156
15175
|
}
|
|
15157
15176
|
function parseCueLine(lines, i) {
|
|
15158
15177
|
const { startMs, endMs } = parseTimestampLine(lines[i]);
|
|
15159
|
-
const { text, nextIndex } = collectTextLines(lines, i + 1);
|
|
15160
|
-
return { cue: buildCue(startMs, endMs,
|
|
15178
|
+
const { text: text2, nextIndex } = collectTextLines(lines, i + 1);
|
|
15179
|
+
return { cue: buildCue(startMs, endMs, text2), nextIndex };
|
|
15161
15180
|
}
|
|
15162
15181
|
function isCueSeparator(line) {
|
|
15163
15182
|
return line.trim().includes("-->");
|
|
@@ -15521,13 +15540,13 @@ function logs(options2) {
|
|
|
15521
15540
|
console.log("No voice log file found");
|
|
15522
15541
|
return;
|
|
15523
15542
|
}
|
|
15524
|
-
const
|
|
15543
|
+
const count4 = Number.parseInt(options2.lines ?? "150", 10);
|
|
15525
15544
|
const content = readFileSync33(voicePaths.log, "utf-8").trim();
|
|
15526
15545
|
if (!content) {
|
|
15527
15546
|
console.log("Voice log is empty");
|
|
15528
15547
|
return;
|
|
15529
15548
|
}
|
|
15530
|
-
const lines = content.split("\n").slice(-
|
|
15549
|
+
const lines = content.split("\n").slice(-count4);
|
|
15531
15550
|
for (const line of lines) {
|
|
15532
15551
|
try {
|
|
15533
15552
|
const event = JSON.parse(line);
|
|
@@ -15674,10 +15693,10 @@ function isProcessAlive3(pid) {
|
|
|
15674
15693
|
return false;
|
|
15675
15694
|
}
|
|
15676
15695
|
}
|
|
15677
|
-
function readRecentLogs(
|
|
15696
|
+
function readRecentLogs(count4) {
|
|
15678
15697
|
if (!existsSync45(voicePaths.log)) return [];
|
|
15679
15698
|
const lines = readFileSync35(voicePaths.log, "utf-8").trim().split("\n");
|
|
15680
|
-
return lines.slice(-
|
|
15699
|
+
return lines.slice(-count4);
|
|
15681
15700
|
}
|
|
15682
15701
|
function status() {
|
|
15683
15702
|
if (!existsSync45(voicePaths.pid)) {
|
|
@@ -15869,8 +15888,8 @@ async function exchangeToken(params) {
|
|
|
15869
15888
|
body: body.toString()
|
|
15870
15889
|
});
|
|
15871
15890
|
if (!response.ok) {
|
|
15872
|
-
const
|
|
15873
|
-
throw new Error(`Token exchange failed (${response.status}): ${
|
|
15891
|
+
const text2 = await response.text();
|
|
15892
|
+
throw new Error(`Token exchange failed (${response.status}): ${text2}`);
|
|
15874
15893
|
}
|
|
15875
15894
|
return response.json();
|
|
15876
15895
|
}
|
|
@@ -16184,11 +16203,11 @@ import { join as join49 } from "path";
|
|
|
16184
16203
|
|
|
16185
16204
|
// src/commands/run/extractOption.ts
|
|
16186
16205
|
function extractOption(args, flag) {
|
|
16187
|
-
const
|
|
16188
|
-
if (
|
|
16206
|
+
const index2 = args.indexOf(flag);
|
|
16207
|
+
if (index2 === -1) return { value: void 0, remaining: args };
|
|
16189
16208
|
return {
|
|
16190
|
-
value: args[
|
|
16191
|
-
remaining: [...args.slice(0,
|
|
16209
|
+
value: args[index2 + 1],
|
|
16210
|
+
remaining: [...args.slice(0, index2), ...args.slice(index2 + 2)]
|
|
16192
16211
|
};
|
|
16193
16212
|
}
|
|
16194
16213
|
|
|
@@ -16568,13 +16587,13 @@ function* iterateUserMessages(filePath, maxBytes = 65536) {
|
|
|
16568
16587
|
|
|
16569
16588
|
// src/commands/sessions/summarise/extractFirstUserMessage.ts
|
|
16570
16589
|
function extractFirstUserMessage(filePath) {
|
|
16571
|
-
for (const
|
|
16572
|
-
return truncate3(
|
|
16590
|
+
for (const text2 of iterateUserMessages(filePath)) {
|
|
16591
|
+
return truncate3(text2);
|
|
16573
16592
|
}
|
|
16574
16593
|
return void 0;
|
|
16575
16594
|
}
|
|
16576
|
-
function truncate3(
|
|
16577
|
-
const trimmed =
|
|
16595
|
+
function truncate3(text2, maxChars = 500) {
|
|
16596
|
+
const trimmed = text2.trim();
|
|
16578
16597
|
if (trimmed.length <= maxChars) return trimmed;
|
|
16579
16598
|
return `${trimmed.slice(0, maxChars)}\u2026`;
|
|
16580
16599
|
}
|
|
@@ -16582,28 +16601,28 @@ function truncate3(text, maxChars = 500) {
|
|
|
16582
16601
|
// src/commands/sessions/summarise/scanSessionBacklogRefs.ts
|
|
16583
16602
|
function scanSessionBacklogRefs(filePath) {
|
|
16584
16603
|
const ids = /* @__PURE__ */ new Set();
|
|
16585
|
-
for (const
|
|
16586
|
-
for (const id of extractBacklogIds(
|
|
16604
|
+
for (const text2 of iterateUserMessages(filePath, Number.MAX_SAFE_INTEGER)) {
|
|
16605
|
+
for (const id of extractBacklogIds(text2)) {
|
|
16587
16606
|
ids.add(id);
|
|
16588
16607
|
}
|
|
16589
16608
|
}
|
|
16590
16609
|
return [...ids].sort((a, b) => a - b);
|
|
16591
16610
|
}
|
|
16592
|
-
function extractBacklogIds(
|
|
16611
|
+
function extractBacklogIds(text2) {
|
|
16593
16612
|
const ids = [];
|
|
16594
|
-
for (const m of
|
|
16613
|
+
for (const m of text2.matchAll(/backlog\s+run\s+(\d+)/gi)) {
|
|
16595
16614
|
ids.push(Number.parseInt(m[1], 10));
|
|
16596
16615
|
}
|
|
16597
|
-
for (const m of
|
|
16616
|
+
for (const m of text2.matchAll(/backlog\s+(?:item\s+)?#(\d+)/gi)) {
|
|
16598
16617
|
ids.push(Number.parseInt(m[1], 10));
|
|
16599
16618
|
}
|
|
16600
|
-
for (const m of
|
|
16619
|
+
for (const m of text2.matchAll(/backlog\s+phase-done\s+(\d+)/gi)) {
|
|
16601
16620
|
ids.push(Number.parseInt(m[1], 10));
|
|
16602
16621
|
}
|
|
16603
|
-
for (const m of
|
|
16622
|
+
for (const m of text2.matchAll(/backlog\s+comment\s+(\d+)/gi)) {
|
|
16604
16623
|
ids.push(Number.parseInt(m[1], 10));
|
|
16605
16624
|
}
|
|
16606
|
-
for (const m of
|
|
16625
|
+
for (const m of text2.matchAll(/(?:^|[\s(])#(\d{1,4})(?=[\s).,;:!?]|$)/gm)) {
|
|
16607
16626
|
ids.push(Number.parseInt(m[1], 10));
|
|
16608
16627
|
}
|
|
16609
16628
|
return ids;
|