@staff0rd/assist 0.227.0 → 0.229.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +850 -801
  2. 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.227.0",
9
+ version: "0.229.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -44,6 +44,7 @@ var package_default = {
44
44
  chalk: "^5.6.2",
45
45
  commander: "^14.0.2",
46
46
  diff: "^8.0.2",
47
+ "drizzle-orm": "^0.45.2",
47
48
  enquirer: "^2.4.1",
48
49
  entities: "^7.0.1",
49
50
  "is-wsl": "^3.1.0",
@@ -413,21 +414,93 @@ function getTranscriptConfig() {
413
414
  return config.transcript;
414
415
  }
415
416
 
416
- // src/commands/backlog/deleteItemRelations.ts
417
- async function deleteItemRelations(db, itemId) {
418
- await db.run("DELETE FROM plan_tasks WHERE item_id = ?", [itemId]);
419
- await db.run("DELETE FROM plan_phases WHERE item_id = ?", [itemId]);
420
- await db.run("DELETE FROM comments WHERE item_id = ?", [itemId]);
421
- await db.run("DELETE FROM links WHERE item_id = ?", [itemId]);
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(db, id) {
426
- return db.transaction(async (tx) => {
427
- await deleteItemRelations(tx, id);
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.get(
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
- for (let i = 0; i < item.comments.length; i++) {
496
- const c = item.comments[i];
497
- if (c.id !== void 0) {
498
- await db.run(
499
- "INSERT INTO comments (id, item_id, idx, text, phase, timestamp, type) VALUES (?, ?, ?, ?, ?, ?, ?)",
500
- [c.id, item.id, i, c.text, c.phase ?? null, c.timestamp, c.type]
501
- );
502
- } else {
503
- await db.run(
504
- "INSERT INTO comments (item_id, idx, text, phase, timestamp, type) VALUES (?, ?, ?, ?, ?, ?)",
505
- [item.id, i, c.text, c.phase ?? null, c.timestamp, c.type]
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
- for (const l of item.links) {
513
- await db.run(
514
- "INSERT INTO links (item_id, type, target_id) VALUES (?, ?, ?)",
515
- [item.id, l.type, l.targetId]
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
- for (let pi = 0; pi < item.plan.length; pi++) {
522
- const phase = item.plan[pi];
523
- await db.run(
524
- "INSERT INTO plan_phases (item_id, idx, name, manual_checks) VALUES (?, ?, ?, ?)",
525
- [
526
- item.id,
527
- pi,
528
- phase.name,
529
- phase.manualChecks ? JSON.stringify(phase.manualChecks) : null
530
- ]
531
- );
532
- for (let ti = 0; ti < phase.tasks.length; ti++) {
533
- const task = phase.tasks[ti];
534
- await db.run(
535
- "INSERT INTO plan_tasks (item_id, phase_idx, idx, task) VALUES (?, ?, ?, ?)",
536
- [item.id, pi, ti, task.task]
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(db, items, origin) {
563
- return db.transaction(async (tx) => {
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 items) {
634
+ for (const item of items2) {
566
635
  oldToNew.set(item.id, await insertItem(tx, item, origin));
567
636
  }
568
- for (const item of items) {
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 items.length;
642
+ return items2.length;
574
643
  });
575
644
  }
576
645
 
577
- // src/commands/backlog/loadComments.ts
578
- async function loadComments(db, itemId) {
579
- const rows = await db.all(
580
- "SELECT id, text, phase, timestamp, type FROM comments WHERE item_id = ? ORDER BY idx",
581
- [itemId]
582
- );
583
- return rows.map((r) => {
584
- const c = {
585
- id: r.id,
586
- text: r.text,
587
- timestamp: r.timestamp,
588
- type: r.type
589
- };
590
- if (r.phase != null) c.phase = r.phase;
591
- return c;
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/loadPlan.ts
596
- async function toPhase(db, itemId, p) {
597
- const tasks = await db.all(
598
- "SELECT task FROM plan_tasks WHERE item_id = ? AND phase_idx = ? ORDER BY idx",
599
- [itemId, p.idx]
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: tasks.map((t) => ({ task: t.task }))
709
+ tasks: (byPhase.get(p.idx) ?? []).map((t) => ({ task: t.task }))
604
710
  };
605
- if (p.manual_checks) {
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
- async function loadPlan(db, itemId) {
611
- const phases = await db.all(
612
- "SELECT idx, name, manual_checks FROM plan_phases WHERE item_id = ? ORDER BY idx",
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
- // src/commands/backlog/loadAllItems.ts
620
- async function loadLinks(db, itemId) {
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
- async function rowToItem(db, row) {
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.acceptance_criteria),
727
+ acceptanceCriteria: JSON.parse(row.acceptanceCriteria),
635
728
  status: row.status,
636
729
  origin: row.origin
637
730
  };
638
- if (row.description != null) item.description = row.description;
639
- if (row.current_phase != null) item.currentPhase = row.current_phase;
640
- if (comments2.length > 0) item.comments = comments2;
641
- if (links && links.length > 0) item.links = links;
642
- if (plan2) item.plan = plan2;
731
+ assignOptionalColumns(item, row);
643
732
  return item;
644
733
  }
645
- async function loadAllItems(db, origin) {
646
- const rows = await db.all(
647
- `SELECT id, type, name, description, acceptance_criteria, status, current_phase, origin
648
- FROM items
649
- WHERE (?::text IS NULL OR origin = ?)
650
- ORDER BY id`,
651
- [origin ?? null, origin ?? null]
734
+ function attachComments(item, rel, id) {
735
+ const comments3 = (rel.comments.get(id) ?? []).map(rowToComment);
736
+ if (comments3.length > 0) item.comments = comments3;
737
+ }
738
+ function attachLinks(item, rel, id) {
739
+ const links2 = (rel.links.get(id) ?? []).map(rowToLink);
740
+ if (links2.length > 0) item.links = links2;
741
+ }
742
+ function attachPlan(item, rel, id) {
743
+ const phases = rel.phases.get(id) ?? [];
744
+ if (phases.length > 0) item.plan = buildPlan(phases, rel.tasks.get(id) ?? []);
745
+ }
746
+ function rowToItem(row, rel) {
747
+ const item = baseItem(row);
748
+ attachComments(item, rel, row.id);
749
+ attachLinks(item, rel, row.id);
750
+ attachPlan(item, rel, row.id);
751
+ return item;
752
+ }
753
+
754
+ // src/commands/backlog/loadAllItems.ts
755
+ async function loadAllItems(orm, origin) {
756
+ const rows = await orm.select().from(items).where(origin === void 0 ? void 0 : eq2(items.origin, origin)).orderBy(asc2(items.id));
757
+ if (rows.length === 0) return [];
758
+ const rel = await loadRelations(
759
+ orm,
760
+ rows.map((r) => r.id)
652
761
  );
653
- return Promise.all(rows.map((row) => rowToItem(db, row)));
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(db, origin, items, imported) {
711
- const reloaded = await loadAllItems(db, origin);
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 (items.length > 0 && !reloaded.some((i) => i.name === items[0].name)) {
826
+ if (items2.length > 0 && !reloaded.some((i) => i.name === items2[0].name)) {
718
827
  throw new Error(
719
- `backlog migrate: spot-check failed; "${items[0].name}" not found after import.`
828
+ `backlog migrate: spot-check failed; "${items2[0].name}" not found after import.`
720
829
  );
721
830
  }
722
831
  }
723
- async function migrateLocalBacklog(db, dir, origin) {
832
+ async function migrateLocalBacklog(orm, dir, origin) {
724
833
  if (!existsSync5(jsonlPath(dir))) return;
725
- const existing = (await loadAllItems(db, origin)).length;
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 items = parseBacklogJsonl(jsonlPath(dir));
737
- const imported = await importItemsRemapped(db, items, origin);
738
- await verifyImport(db, origin, items, imported);
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(db, dir, origin) {
858
+ async function ensureMigrated(orm, dir, origin) {
750
859
  if (attempted.has(origin)) return;
751
860
  attempted.add(origin);
752
- await migrateLocalBacklog(db, dir, origin);
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/getBacklogDb.ts
883
+ // src/commands/backlog/getBacklogOrm.ts
775
884
  import chalk5 from "chalk";
776
885
  import { Pool } from "pg";
777
886
 
778
- // src/commands/backlog/BacklogDb.ts
779
- function toPg(sql2) {
780
- let i = 0;
781
- return sql2.replace(/\?/g, () => `$${++i}`);
782
- }
783
- var PgBacklogDb = class _PgBacklogDb {
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(db) {
890
- await db.exec(SCHEMA);
950
+ async function ensureSchema(exec3) {
951
+ await exec3(SCHEMA);
891
952
  }
892
953
 
893
- // src/commands/backlog/getBacklogDb.ts
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
- function getBacklogDb() {
918
- if (_db) return Promise.resolve(_db);
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
- const db = makeBacklogDb(
924
- { query: (sql2, params) => pool.query(sql2, params) },
925
- pool
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 getBacklogDb();
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
- _db = void 0;
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
- await db.run(
1007
- `INSERT INTO items (id, origin, type, name, description, acceptance_criteria, status, current_phase)
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.exec(
1033
- "SELECT setval(pg_get_serial_sequence('items', 'id'), COALESCE((SELECT MAX(id) FROM items), 0) + 1, false)"
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.exec(
1036
- "SELECT setval(pg_get_serial_sequence('comments', 'id'), COALESCE((SELECT MAX(id) FROM comments), 0) + 1, false)"
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(db, items, origin) {
1040
- await db.transaction(async (tx) => {
1041
- const existingIds = await tx.all(
1042
- "SELECT id FROM items WHERE origin = ?",
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.run("DELETE FROM items WHERE id = ?", [id]);
1095
+ await tx.delete(items).where(eq4(items.id, id));
1050
1096
  }
1051
1097
  }
1052
- for (const item of items) {
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
- async function searchItemIds(db, query, origin) {
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 db.all(
1063
- `SELECT DISTINCT i.id
1064
- FROM items i
1065
- LEFT JOIN comments c ON c.item_id = i.id
1066
- LEFT JOIN plan_phases p ON p.item_id = i.id
1067
- WHERE (?::text IS NULL OR i.origin = ?)
1068
- AND (
1069
- i.name ILIKE ?
1070
- OR i.description ILIKE ?
1071
- OR i.acceptance_criteria ILIKE ?
1072
- OR c.text ILIKE ?
1073
- OR p.name ILIKE ?
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
- async function updateCurrentPhase(db, id, phase) {
1091
- const result = await db.run(
1092
- "UPDATE items SET current_phase = ? WHERE id = ?",
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
- async function updateStatus(db, id, status2) {
1100
- const result = await db.run("UPDATE items SET status = ? WHERE id = ?", [
1101
- status2,
1102
- id
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 getDb() {
1119
- const db = await getBacklogDb();
1148
+ async function getReady() {
1149
+ const orm = await getBacklogOrm();
1120
1150
  const dir = getBacklogDir();
1121
- await ensureMigrated(db, dir, getCurrentOrigin(dir));
1122
- return db;
1151
+ await ensureMigrated(orm, dir, getCurrentOrigin(dir));
1152
+ return { orm };
1123
1153
  }
1124
1154
  async function loadBacklog(allRepos = false) {
1125
- const db = await getDb();
1126
- return loadAllItems(db, allRepos ? void 0 : getOrigin());
1155
+ const { orm } = await getReady();
1156
+ return loadAllItems(orm, allRepos ? void 0 : getOrigin());
1127
1157
  }
1128
1158
  async function searchBacklog(query) {
1129
- const db = await getDb();
1159
+ const { orm } = await getReady();
1130
1160
  const origin = getOrigin();
1131
- const ids = await searchItemIds(db, query, origin);
1132
- const allItems = await loadAllItems(db, origin);
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(items) {
1136
- const db = await getDb();
1137
- await saveAllItems(db, items, getOrigin());
1165
+ async function saveBacklog(items2) {
1166
+ const { orm } = await getReady();
1167
+ await saveAllItems(orm, items2, getOrigin());
1138
1168
  }
1139
- function findItem(items, id) {
1140
- return items.find((item) => item.id === id);
1169
+ function findItem(items2, id) {
1170
+ return items2.find((item) => item.id === id);
1141
1171
  }
1142
1172
  async function loadAndFindItem(id) {
1143
- const items = await loadBacklog();
1144
- const item = findItem(items, Number.parseInt(id, 10));
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 result = await loadAndFindItem(id);
1153
- if (!result) return void 0;
1154
- const db = await getBacklogDb();
1155
- await updateStatus(db, result.item.id, status2);
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 result = await loadAndFindItem(id);
1160
- if (!result) return;
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 result = await loadAndFindItem(id);
1166
- if (!result) return void 0;
1167
- const db = await getBacklogDb();
1168
- await deleteItem(db, result.item.id);
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, items) {
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 = items.find((i) => i.id === dep.targetId);
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, items) {
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, items)) return chalk7.red(" [blocked]");
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(items) {
1263
- return items.find(
1264
- (i) => i.status === "in-progress" && i.plan && !isLockedByOther(i.id) && !isBlocked(i, items)
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(items) {
1271
- const todo = items.filter((i) => i.status === "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, items));
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(comments2) {
1348
- if (!comments2?.length) return [];
1349
- return ["", "Comments:", ...comments2.map(formatPromptComment)];
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 items = await loadBacklog();
1507
- const item = items.find((i) => i.id === itemId);
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, items) {
1689
+ function toChoice(item, items2) {
1664
1690
  const name = `${typeLabel(item.type)} #${item.id}: ${item.name}`;
1665
- return isBlocked(item, items) ? { name, disabled: chalk13.red("[blocked]") } : { name };
1691
+ return isBlocked(item, items2) ? { name, disabled: chalk13.red("[blocked]") } : { name };
1666
1692
  }
1667
- async function selectItem(todo, items) {
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, items))
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(items, firstPick = false) {
1679
- const resumable = findResumable(items);
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(items);
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 = items.filter((i) => i.status === "todo");
1696
- return selectItem(todo, items);
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/addComment.ts
1714
- function addComment(item, text, phase) {
1715
- const entry = {
1716
- text,
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
- ...phase !== void 0 && { phase }
1720
- };
1721
- if (!item.comments) item.comments = [];
1722
- item.comments.push(entry);
1748
+ type: opts.type ?? "comment"
1749
+ });
1723
1750
  }
1724
- function addPhaseSummary(item, text, phase) {
1725
- const entry = {
1726
- text,
1727
- phase,
1728
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1729
- type: "summary"
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: Number.parseInt(id, 10),
1765
+ itemId,
1741
1766
  phaseIndex,
1742
1767
  completedAt: (/* @__PURE__ */ new Date()).toISOString()
1743
1768
  });
1744
- const result = await loadAndFindItem(id);
1745
- if (result?.item.status === "done") {
1746
- console.log(chalk14.dim(`Item #${id} already done, skipping phase advance.`));
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 (result) {
1750
- addPhaseSummary(result.item, summary, phaseNumber);
1751
- await saveBacklog(result.items);
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, items) {
1797
- const links = item.links ?? [];
1798
- if (links.length === 0) return;
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 links) {
1801
- const target = items.find((i) => i.id === link3.targetId);
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(index, name, isCurrent) {
1841
- const phaseNumber = index + 1;
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, index, isCurrent) {
1847
- console.log(phaseHeader(index, phase.name, isCurrent));
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, items);
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 db = await getBacklogDb();
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(db, newItem, getOrigin());
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 items = await loadBacklog();
2028
- const item = items.find((i) => i.id === id);
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(items);
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 items = await loadBacklog();
2071
- const item = items.find((i) => i.id === id);
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 text = typeof content === "string" ? content : Array.isArray(content) ? content.find((c) => c.type === "text")?.text ?? "" : "";
2543
- return text.replace(/<command-[^>]*>[^<]*<\/command-[^>]*>/g, "").trim().slice(0, 80);
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 items = await loadBacklog();
2894
+ const items2 = await loadBacklog();
2843
2895
  const numericId = Number.parseInt(id, 10);
2844
- const item = Number.isNaN(numericId) ? void 0 : items.find((i) => i.id === numericId);
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, items)) {
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 items = await loadBacklog();
2893
- const active = items.filter(
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(desc, setup2) {
3610
- return typeof desc === "function" ? desc(setup2) : desc;
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, index) {
4387
+ function createTimerCallback(taskStatuses, index2) {
4336
4388
  return (exitCode) => {
4337
- taskStatuses[index].endTime = Date.now();
4338
- taskStatuses[index].code = exitCode;
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, index) => runEntry(
4469
+ (entry, index2) => runEntry(
4418
4470
  entry,
4419
- timer ? createTimerCallback(taskStatuses, index) : void 0
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) + count);
4959
+ weekly.set(weekStart, (weekly.get(weekStart) ?? 0) + count4);
4908
4960
  }
4909
- const weeklyData = [...weekly.entries()].map(([date, count]) => ({ date, count })).sort((a, b) => a.date.localeCompare(b.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, text) {
4976
+ async function comment(id, text2) {
4925
4977
  const result = await loadAndFindItem(id);
4926
4978
  if (!result) process.exit(1);
4927
- addComment(result.item, text);
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 comments(id) {
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
- async function deleteComment(db, itemId, commentId) {
4956
- const row = await db.get(
4957
- "SELECT type FROM comments WHERE id = ? AND item_id = ?",
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 db.run("DELETE FROM comments WHERE id = ? AND item_id = ?", [
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 db = await getBacklogDb();
5020
+ const orm = await getBacklogOrm();
4974
5021
  const outcome = await deleteComment(
4975
- db,
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(comments);
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 sql2 = `COPY ${table.name} (${table.columns.join(", ")}) TO STDOUT`;
5060
- const stream = client.query(copyTo(sql2));
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 index = data.indexOf(10);
5098
- while (index !== -1) {
5144
+ let index2 = data.indexOf(10);
5145
+ while (index2 !== -1) {
5099
5146
  rows++;
5100
- index = data.indexOf(10, index + 1);
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(text), bodyStart: next3 };
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 = text.match(/^@table (\S+) (\d+)$/);
5129
- if (!match) invalid(`malformed table marker "${text}".`);
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 sql2 = `COPY ${table.name} (${table.columns.join(", ")}) FROM STDIN`;
5211
- const stream = client.query(copyFrom(sql2));
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 db = await getBacklogDb();
5404
+ const orm = await getBacklogOrm();
5358
5405
  const id = await insertItem(
5359
- db,
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
- async function insertPhaseAt(db, itemId, phaseIdx, name, tasks, manualChecks, currentPhase) {
5377
- await db.transaction(async (tx) => {
5378
- const toShift = await tx.all(
5379
- "SELECT idx FROM plan_phases WHERE item_id = ? AND idx >= ? ORDER BY idx DESC",
5380
- [itemId, phaseIdx]
5381
- );
5382
- for (const p of toShift) {
5383
- await tx.run(
5384
- "UPDATE plan_tasks SET phase_idx = ? WHERE item_id = ? AND phase_idx = ?",
5385
- [p.idx + 1, itemId, p.idx]
5386
- );
5387
- await tx.run(
5388
- "UPDATE plan_phases SET idx = ? WHERE item_id = ? AND idx = ?",
5389
- [p.idx + 1, itemId, p.idx]
5390
- );
5391
- }
5392
- await tx.run(
5393
- "INSERT INTO plan_phases (item_id, idx, name, manual_checks) VALUES (?, ?, ?, ?)",
5394
- [itemId, phaseIdx, name, manualChecks]
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.run("UPDATE items SET current_phase = ? WHERE id = ?", [
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
- async function resolveInsertPosition(db, itemId, position) {
5414
- const row = await db.get(
5415
- "SELECT COUNT(*)::int as cnt FROM plan_phases WHERE item_id = ?",
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 db = await getBacklogDb();
5484
+ const orm = await getBacklogOrm();
5449
5485
  const itemId = result.item.id;
5450
- const phaseIdx = await resolveInsertPosition(db, itemId, options2.position);
5486
+ const phaseIdx = await resolveInsertPosition(orm, itemId, options2.position);
5451
5487
  if (phaseIdx === void 0) return;
5452
5488
  await insertPhaseAt(
5453
- db,
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 getBacklogDb();
5508
+ await getBacklogOrm();
5473
5509
  console.log(
5474
5510
  chalk55.green(
5475
5511
  `Backlog database ready. This repository maps to origin: ${getOrigin()}`
@@ -5492,23 +5528,53 @@ function originDisplayName(origin) {
5492
5528
  return origin.slice(firstSlash + 1);
5493
5529
  }
5494
5530
 
5531
+ // src/commands/backlog/originDisplayLabels.ts
5532
+ function originDisplayLabels(origins) {
5533
+ const isLocal = (origin) => origin.startsWith("local:");
5534
+ const bareName = (origin) => {
5535
+ const full = originDisplayName(origin);
5536
+ const segments = full.split("/");
5537
+ return segments[segments.length - 1] ?? full;
5538
+ };
5539
+ const remoteCounts = /* @__PURE__ */ new Map();
5540
+ for (const origin of new Set(origins)) {
5541
+ if (isLocal(origin)) continue;
5542
+ const name = bareName(origin);
5543
+ remoteCounts.set(name, (remoteCounts.get(name) ?? 0) + 1);
5544
+ }
5545
+ const labels = /* @__PURE__ */ new Map();
5546
+ for (const origin of origins) {
5547
+ if (isLocal(origin)) {
5548
+ labels.set(origin, originDisplayName(origin));
5549
+ continue;
5550
+ }
5551
+ const name = bareName(origin);
5552
+ const collides = (remoteCounts.get(name) ?? 0) > 1;
5553
+ labels.set(origin, collides ? originDisplayName(origin) : name);
5554
+ }
5555
+ return labels;
5556
+ }
5557
+
5495
5558
  // src/commands/backlog/list/index.ts
5496
- function filterItems(items, options2) {
5497
- if (options2.status) return items.filter((i) => i.status === options2.status);
5559
+ function filterItems(items2, options2) {
5560
+ if (options2.status) return items2.filter((i) => i.status === options2.status);
5498
5561
  if (!options2.all)
5499
- return items.filter((i) => i.status !== "done" && i.status !== "wontdo");
5500
- return items;
5562
+ return items2.filter((i) => i.status !== "done" && i.status !== "wontdo");
5563
+ return items2;
5501
5564
  }
5502
5565
  async function list2(options2) {
5503
5566
  const allItems = await loadBacklog(options2.allRepos);
5504
- const items = filterItems(allItems, options2);
5505
- if (items.length === 0) {
5567
+ const items2 = filterItems(allItems, options2);
5568
+ if (items2.length === 0) {
5506
5569
  console.log(chalk56.dim("Backlog is empty."));
5507
5570
  return;
5508
5571
  }
5509
- const repoNameOf = (item) => item.origin ? originDisplayName(item.origin) : "";
5510
- const prefixWidth = options2.allRepos ? Math.max(0, ...items.map((i) => repoNameOf(i).length)) : 0;
5511
- for (const item of items) {
5572
+ const labels = originDisplayLabels(
5573
+ items2.flatMap((i) => i.origin ? [i.origin] : [])
5574
+ );
5575
+ const repoNameOf = (item) => item.origin ? labels.get(item.origin) ?? "" : "";
5576
+ const prefixWidth = options2.allRepos ? Math.max(0, ...items2.map((i) => repoNameOf(i).length)) : 0;
5577
+ for (const item of items2) {
5512
5578
  const repoPrefix2 = options2.allRepos ? `${chalk56.dim(repoNameOf(item).padEnd(prefixWidth))} ` : "";
5513
5579
  console.log(
5514
5580
  `${repoPrefix2}${statusIcon(item.status)} ${typeLabel(item.type)} ${chalk56.dim(`#${item.id}`)} ${item.name}${phaseLabel(item)}${dependencyLabel(item, allItems)}`
@@ -5540,7 +5606,7 @@ function registerItemCommands(cmd) {
5540
5606
  import chalk58 from "chalk";
5541
5607
 
5542
5608
  // src/commands/backlog/hasCycle.ts
5543
- function hasCycle(items, fromId, toId) {
5609
+ function hasCycle(adjacency, fromId, toId) {
5544
5610
  const visited = /* @__PURE__ */ new Set();
5545
5611
  const stack = [toId];
5546
5612
  while (stack.length > 0) {
@@ -5548,100 +5614,107 @@ function hasCycle(items, fromId, toId) {
5548
5614
  if (current === fromId) return true;
5549
5615
  if (visited.has(current)) continue;
5550
5616
  visited.add(current);
5551
- const item = items.find((i) => i.id === current);
5552
- if (!item?.links) continue;
5553
- for (const link3 of item.links) {
5554
- if (link3.type === "depends-on") {
5555
- stack.push(link3.targetId);
5556
- }
5617
+ for (const target of adjacency.get(current) ?? []) {
5618
+ stack.push(target);
5557
5619
  }
5558
5620
  }
5559
5621
  return false;
5560
5622
  }
5561
5623
 
5624
+ // src/commands/backlog/loadDependencyGraph.ts
5625
+ import { eq as eq13 } from "drizzle-orm";
5626
+ async function loadDependencyGraph(orm) {
5627
+ const rows = await orm.select({ itemId: links.itemId, targetId: links.targetId }).from(links).where(eq13(links.type, "depends-on"));
5628
+ const graph = /* @__PURE__ */ new Map();
5629
+ for (const { itemId, targetId } of rows) {
5630
+ const bucket = graph.get(itemId);
5631
+ if (bucket) bucket.push(targetId);
5632
+ else graph.set(itemId, [targetId]);
5633
+ }
5634
+ return graph;
5635
+ }
5636
+
5637
+ // src/commands/backlog/loadItem.ts
5638
+ import { eq as eq14 } from "drizzle-orm";
5639
+ async function loadItem(orm, id) {
5640
+ const [row] = await orm.select().from(items).where(eq14(items.id, id));
5641
+ if (!row) return void 0;
5642
+ const rel = await loadRelations(orm, [id]);
5643
+ return rowToItem(row, rel);
5644
+ }
5645
+
5562
5646
  // src/commands/backlog/validateLinkTarget.ts
5563
5647
  import chalk57 from "chalk";
5564
- function validateLinkTarget(items, fromItem, fromId, toId, toNum, linkType) {
5565
- const toItem = items.find((i) => i.id === toNum);
5566
- if (!toItem) {
5567
- console.log(chalk57.red(`Item #${toId} not found.`));
5568
- return void 0;
5569
- }
5570
- if (!fromItem.links) fromItem.links = [];
5571
- const duplicate = fromItem.links.some(
5648
+ function validateLinkTarget(fromItem, fromNum, toNum, linkType) {
5649
+ const duplicate = (fromItem.links ?? []).some(
5572
5650
  (l) => l.targetId === toNum && l.type === linkType
5573
5651
  );
5574
5652
  if (duplicate) {
5575
5653
  console.log(
5576
- chalk57.yellow(`Link already exists: #${fromId} ${linkType} #${toId}`)
5654
+ chalk57.yellow(`Link already exists: #${fromNum} ${linkType} #${toNum}`)
5577
5655
  );
5578
- return void 0;
5656
+ return false;
5579
5657
  }
5580
- return toItem;
5658
+ return true;
5581
5659
  }
5582
5660
 
5583
5661
  // src/commands/backlog/link.ts
5662
+ function fail(message) {
5663
+ console.log(chalk58.red(message));
5664
+ return void 0;
5665
+ }
5666
+ function parseLinkType(type) {
5667
+ const linkType = type ?? "relates-to";
5668
+ if (linkType === "relates-to" || linkType === "depends-on") return linkType;
5669
+ return fail(`Invalid link type: ${linkType}`);
5670
+ }
5671
+ async function createsCycle(orm, linkType, fromNum, toNum) {
5672
+ if (linkType !== "depends-on") return false;
5673
+ const graph = await loadDependencyGraph(orm);
5674
+ if (!hasCycle(graph, fromNum, toNum)) return false;
5675
+ fail(`Cannot add dependency: #${fromNum} \u2192 #${toNum} would create a cycle.`);
5676
+ return true;
5677
+ }
5584
5678
  async function link(fromId, toId, opts) {
5585
- const linkType = opts.type ?? "relates-to";
5586
- if (linkType !== "relates-to" && linkType !== "depends-on") {
5587
- console.log(chalk58.red(`Invalid link type: ${linkType}`));
5588
- return;
5589
- }
5679
+ const linkType = parseLinkType(opts.type);
5680
+ if (!linkType) return;
5590
5681
  const fromNum = Number.parseInt(fromId, 10);
5591
5682
  const toNum = Number.parseInt(toId, 10);
5592
- if (fromNum === toNum) {
5593
- console.log(chalk58.red("Cannot link an item to itself."));
5594
- return;
5595
- }
5596
- const result = await loadAndFindItem(fromId);
5597
- if (!result) return;
5598
- const { items, item: fromItem } = result;
5599
- const toItem = validateLinkTarget(
5600
- items,
5601
- fromItem,
5602
- fromId,
5603
- toId,
5604
- toNum,
5605
- linkType
5606
- );
5607
- if (!toItem) return;
5608
- if (linkType === "depends-on" && hasCycle(items, fromNum, toNum)) {
5609
- console.log(
5610
- chalk58.red(
5611
- `Cannot add dependency: #${fromId} \u2192 #${toId} would create a circular dependency.`
5612
- )
5613
- );
5614
- return;
5615
- }
5616
- if (!fromItem.links) fromItem.links = [];
5617
- fromItem.links.push({ type: linkType, targetId: toNum });
5618
- await saveBacklog(items);
5683
+ if (fromNum === toNum) return void fail("Cannot link an item to itself.");
5684
+ const { orm } = await getReady();
5685
+ const fromItem = await loadItem(orm, fromNum);
5686
+ if (!fromItem) return void fail(`Item #${fromNum} not found.`);
5687
+ const toItem = await loadItem(orm, toNum);
5688
+ if (!toItem) return void fail(`Item #${toNum} not found.`);
5689
+ if (!validateLinkTarget(fromItem, fromNum, toNum, linkType)) return;
5690
+ if (await createsCycle(orm, linkType, fromNum, toNum)) return;
5691
+ await orm.insert(links).values({ itemId: fromNum, type: linkType, targetId: toNum });
5619
5692
  console.log(
5620
- chalk58.green(`Linked #${fromId} ${linkType} #${toId} (${toItem.name})`)
5693
+ chalk58.green(`Linked #${fromNum} ${linkType} #${toNum} (${toItem.name})`)
5621
5694
  );
5622
5695
  }
5623
5696
 
5624
5697
  // src/commands/backlog/unlink.ts
5625
5698
  import chalk59 from "chalk";
5699
+ import { and as and4, eq as eq15 } from "drizzle-orm";
5626
5700
  async function unlink(fromId, toId) {
5701
+ const fromNum = Number.parseInt(fromId, 10);
5627
5702
  const toNum = Number.parseInt(toId, 10);
5628
- const result = await loadAndFindItem(fromId);
5629
- if (!result) return;
5630
- const { items, item: fromItem } = result;
5703
+ const { orm } = await getReady();
5704
+ const fromItem = await loadItem(orm, fromNum);
5705
+ if (!fromItem) {
5706
+ console.log(chalk59.red(`Item #${fromId} not found.`));
5707
+ return;
5708
+ }
5631
5709
  if (!fromItem.links || fromItem.links.length === 0) {
5632
5710
  console.log(chalk59.yellow(`No links found on item #${fromId}.`));
5633
5711
  return;
5634
5712
  }
5635
- const before = fromItem.links.length;
5636
- fromItem.links = fromItem.links.filter((l) => l.targetId !== toNum);
5637
- if (fromItem.links.length === before) {
5713
+ if (!fromItem.links.some((l) => l.targetId === toNum)) {
5638
5714
  console.log(chalk59.yellow(`No link from #${fromId} to #${toId} found.`));
5639
5715
  return;
5640
5716
  }
5641
- if (fromItem.links.length === 0) {
5642
- fromItem.links = void 0;
5643
- }
5644
- await saveBacklog(items);
5717
+ await orm.delete(links).where(and4(eq15(links.itemId, fromNum), eq15(links.targetId, toNum)));
5645
5718
  console.log(chalk59.green(`Removed link from #${fromId} to #${toId}.`));
5646
5719
  }
5647
5720
 
@@ -5679,9 +5752,12 @@ function validateRewind2(item, phaseNumber) {
5679
5752
  async function rewindPhase(id, phase, opts) {
5680
5753
  const phaseNumber = Number.parseInt(phase, 10);
5681
5754
  const phaseIndex = phaseNumber - 1;
5682
- const result = await loadAndFindItem(id);
5683
- if (!result) return;
5684
- const { item } = result;
5755
+ const { orm } = await getReady();
5756
+ const item = await loadItem(orm, Number.parseInt(id, 10));
5757
+ if (!item) {
5758
+ console.log(chalk60.red(`Item #${id} not found.`));
5759
+ return;
5760
+ }
5685
5761
  const error = validateRewind2(item, phaseNumber);
5686
5762
  if (error) {
5687
5763
  console.log(chalk60.red(error));
@@ -5689,12 +5765,12 @@ async function rewindPhase(id, phase, opts) {
5689
5765
  return;
5690
5766
  }
5691
5767
  const phaseName = item.plan?.[phaseIndex].name;
5692
- addComment(
5693
- item,
5768
+ await appendComment(
5769
+ orm,
5770
+ item.id,
5694
5771
  `Rewound to phase ${phaseNumber} (${phaseName}): ${opts.reason}`,
5695
- phaseNumber
5772
+ { phase: phaseNumber }
5696
5773
  );
5697
- await saveBacklog(result.items);
5698
5774
  await setCurrentPhase(id, phaseNumber);
5699
5775
  await setStatus(id, "in-progress");
5700
5776
  writeSignal("rewind", {
@@ -5721,18 +5797,18 @@ function registerRunCommand(cmd) {
5721
5797
  // src/commands/backlog/search/index.ts
5722
5798
  import chalk61 from "chalk";
5723
5799
  async function search(query) {
5724
- const items = await searchBacklog(query);
5725
- if (items.length === 0) {
5800
+ const items2 = await searchBacklog(query);
5801
+ if (items2.length === 0) {
5726
5802
  console.log(chalk61.dim(`No items matching "${query}".`));
5727
5803
  return;
5728
5804
  }
5729
5805
  console.log(
5730
5806
  chalk61.dim(
5731
- `${items.length} item${items.length === 1 ? "" : "s"} matching "${query}":
5807
+ `${items2.length} item${items2.length === 1 ? "" : "s"} matching "${query}":
5732
5808
  `
5733
5809
  )
5734
5810
  );
5735
- for (const item of items) {
5811
+ for (const item of items2) {
5736
5812
  console.log(
5737
5813
  `${statusIcon(item.status)} ${typeLabel(item.type)} ${chalk61.dim(`#${item.id}`)} ${item.name}`
5738
5814
  );
@@ -5796,8 +5872,8 @@ async function start(id) {
5796
5872
  // src/commands/backlog/stop/index.ts
5797
5873
  import chalk65 from "chalk";
5798
5874
  async function stop() {
5799
- const items = await loadBacklog();
5800
- const inProgress = items.filter((item) => item.status === "in-progress");
5875
+ const items2 = await loadBacklog();
5876
+ const inProgress = items2.filter((item) => item.status === "in-progress");
5801
5877
  if (inProgress.length === 0) {
5802
5878
  console.log(chalk65.yellow("No in-progress items to stop."));
5803
5879
  return;
@@ -5806,7 +5882,7 @@ async function stop() {
5806
5882
  item.status = "todo";
5807
5883
  item.currentPhase = 1;
5808
5884
  }
5809
- await saveBacklog(items);
5885
+ await saveBacklog(items2);
5810
5886
  for (const item of inProgress) {
5811
5887
  console.log(chalk65.yellow(`Stopped item #${item.id}: ${item.name}`));
5812
5888
  }
@@ -5837,19 +5913,18 @@ function registerStatusCommands(cmd) {
5837
5913
 
5838
5914
  // src/commands/backlog/removePhase.ts
5839
5915
  import chalk68 from "chalk";
5916
+ import { and as and7, eq as eq18 } from "drizzle-orm";
5840
5917
 
5841
5918
  // src/commands/backlog/findPhase.ts
5842
5919
  import chalk67 from "chalk";
5920
+ import { and as and5, count as count2, eq as eq16 } from "drizzle-orm";
5843
5921
  async function findPhase(id, phase) {
5844
5922
  const result = await loadAndFindItem(id);
5845
5923
  if (!result) return void 0;
5846
- const db = await getBacklogDb();
5924
+ const orm = await getBacklogOrm();
5847
5925
  const itemId = result.item.id;
5848
5926
  const phaseIdx = Number.parseInt(phase, 10) - 1;
5849
- const row = await db.get(
5850
- "SELECT COUNT(*)::int as cnt FROM plan_phases WHERE item_id = ? AND idx = ?",
5851
- [itemId, phaseIdx]
5852
- );
5927
+ const [row] = await orm.select({ cnt: count2() }).from(planPhases).where(and5(eq16(planPhases.itemId, itemId), eq16(planPhases.idx, phaseIdx)));
5853
5928
  if (!row || row.cnt === 0) {
5854
5929
  console.log(
5855
5930
  chalk67.red(`Phase ${phaseIdx + 1} not found on item #${itemId}.`)
@@ -5857,26 +5932,18 @@ async function findPhase(id, phase) {
5857
5932
  process.exitCode = 1;
5858
5933
  return void 0;
5859
5934
  }
5860
- return { result, db, itemId, phaseIdx };
5935
+ return { result, orm, itemId, phaseIdx };
5861
5936
  }
5862
5937
 
5863
5938
  // src/commands/backlog/reindexPhases.ts
5939
+ import { and as and6, asc as asc4, count as count3, eq as eq17 } from "drizzle-orm";
5864
5940
  async function reindexPhases(db, itemId) {
5865
- const remaining = await db.all(
5866
- "SELECT idx FROM plan_phases WHERE item_id = ? ORDER BY idx",
5867
- [itemId]
5868
- );
5941
+ const remaining = await db.select({ idx: planPhases.idx }).from(planPhases).where(eq17(planPhases.itemId, itemId)).orderBy(asc4(planPhases.idx));
5869
5942
  for (let i = 0; i < remaining.length; i++) {
5870
5943
  const oldIdx = remaining[i].idx;
5871
5944
  if (oldIdx === i) continue;
5872
- await db.run(
5873
- "UPDATE plan_tasks SET phase_idx = ? WHERE item_id = ? AND phase_idx = ?",
5874
- [i, itemId, oldIdx]
5875
- );
5876
- await db.run(
5877
- "UPDATE plan_phases SET idx = ? WHERE item_id = ? AND idx = ?",
5878
- [i, itemId, oldIdx]
5879
- );
5945
+ await db.update(planTasks).set({ phaseIdx: i }).where(and6(eq17(planTasks.itemId, itemId), eq17(planTasks.phaseIdx, oldIdx)));
5946
+ await db.update(planPhases).set({ idx: i }).where(and6(eq17(planPhases.itemId, itemId), eq17(planPhases.idx, oldIdx)));
5880
5947
  }
5881
5948
  }
5882
5949
  async function adjustCurrentPhase(db, item, removedIdx) {
@@ -5884,38 +5951,25 @@ async function adjustCurrentPhase(db, item, removedIdx) {
5884
5951
  if (currentPhase === void 0) return;
5885
5952
  const currentIdx = currentPhase - 1;
5886
5953
  if (removedIdx < currentIdx) {
5887
- await db.run("UPDATE items SET current_phase = ? WHERE id = ?", [
5888
- currentPhase - 1,
5889
- item.id
5890
- ]);
5954
+ await db.update(items).set({ currentPhase: currentPhase - 1 }).where(eq17(items.id, item.id));
5891
5955
  return;
5892
5956
  }
5893
5957
  if (removedIdx !== currentIdx) return;
5894
- const row = await db.get(
5895
- "SELECT COUNT(*)::int as cnt FROM plan_phases WHERE item_id = ?",
5896
- [item.id]
5897
- );
5958
+ const [row] = await db.select({ cnt: count3() }).from(planPhases).where(eq17(planPhases.itemId, item.id));
5898
5959
  const cnt = row?.cnt ?? 0;
5899
- await db.run("UPDATE items SET current_phase = ? WHERE id = ?", [
5900
- cnt === 0 ? null : Math.min(currentPhase, cnt),
5901
- item.id
5902
- ]);
5960
+ await db.update(items).set({ currentPhase: cnt === 0 ? null : Math.min(currentPhase, cnt) }).where(eq17(items.id, item.id));
5903
5961
  }
5904
5962
 
5905
5963
  // src/commands/backlog/removePhase.ts
5906
5964
  async function removePhase(id, phase) {
5907
5965
  const found = await findPhase(id, phase);
5908
5966
  if (!found) return;
5909
- const { result, db, itemId, phaseIdx } = found;
5910
- await db.transaction(async (tx) => {
5911
- await tx.run("DELETE FROM plan_tasks WHERE item_id = ? AND phase_idx = ?", [
5912
- itemId,
5913
- phaseIdx
5914
- ]);
5915
- await tx.run("DELETE FROM plan_phases WHERE item_id = ? AND idx = ?", [
5916
- itemId,
5917
- phaseIdx
5918
- ]);
5967
+ const { result, orm, itemId, phaseIdx } = found;
5968
+ await orm.transaction(async (tx) => {
5969
+ await tx.delete(planTasks).where(
5970
+ and7(eq18(planTasks.itemId, itemId), eq18(planTasks.phaseIdx, phaseIdx))
5971
+ );
5972
+ await tx.delete(planPhases).where(and7(eq18(planPhases.itemId, itemId), eq18(planPhases.idx, phaseIdx)));
5919
5973
  await reindexPhases(tx, itemId);
5920
5974
  await adjustCurrentPhase(tx, result.item, phaseIdx);
5921
5975
  });
@@ -5926,20 +5980,21 @@ async function removePhase(id, phase) {
5926
5980
 
5927
5981
  // src/commands/backlog/update/index.ts
5928
5982
  import chalk70 from "chalk";
5983
+ import { eq as eq19 } from "drizzle-orm";
5929
5984
 
5930
5985
  // src/commands/backlog/update/parseListIndex.ts
5931
5986
  function parseListIndex(raw, length, label2) {
5932
5987
  if (!/^\d+$/.test(raw)) {
5933
5988
  return { ok: false, error: `${label2} must be a positive integer.` };
5934
5989
  }
5935
- const index = Number.parseInt(raw, 10);
5936
- if (index < 1 || index > length) {
5990
+ const index2 = Number.parseInt(raw, 10);
5991
+ if (index2 < 1 || index2 > length) {
5937
5992
  return {
5938
5993
  ok: false,
5939
- error: `${label2} ${index} is out of range (1-${length}).`
5994
+ error: `${label2} ${index2} is out of range (1-${length}).`
5940
5995
  };
5941
5996
  }
5942
- return { ok: true, index };
5997
+ return { ok: true, index: index2 };
5943
5998
  }
5944
5999
 
5945
6000
  // src/commands/backlog/update/applyListMutations.ts
@@ -5949,7 +6004,7 @@ function hasListMutations(options2) {
5949
6004
  );
5950
6005
  }
5951
6006
  function applyListMutations(current, options2, flags) {
5952
- let items = [...current];
6007
+ let items2 = [...current];
5953
6008
  if (options2.edit) {
5954
6009
  const [rawIndex, ...textParts] = options2.edit;
5955
6010
  if (rawIndex === void 0 || textParts.length === 0) {
@@ -5960,25 +6015,25 @@ function applyListMutations(current, options2, flags) {
5960
6015
  }
5961
6016
  const parsed = parseListIndex(
5962
6017
  rawIndex,
5963
- items.length,
6018
+ items2.length,
5964
6019
  `${flags.edit} index`
5965
6020
  );
5966
6021
  if (!parsed.ok) return parsed;
5967
- items[parsed.index - 1] = textParts.join(" ");
6022
+ items2[parsed.index - 1] = textParts.join(" ");
5968
6023
  }
5969
6024
  if (options2.remove) {
5970
6025
  const parsed = parseListIndex(
5971
6026
  options2.remove,
5972
- items.length,
6027
+ items2.length,
5973
6028
  `${flags.remove} index`
5974
6029
  );
5975
6030
  if (!parsed.ok) return parsed;
5976
- items = items.filter((_, i) => i !== parsed.index - 1);
6031
+ items2 = items2.filter((_, i) => i !== parsed.index - 1);
5977
6032
  }
5978
6033
  if (options2.add) {
5979
- items.push(...options2.add);
6034
+ items2.push(...options2.add);
5980
6035
  }
5981
- return { ok: true, items };
6036
+ return { ok: true, items: items2 };
5982
6037
  }
5983
6038
 
5984
6039
  // src/commands/backlog/update/applyAcMutations.ts
@@ -5999,11 +6054,11 @@ function applyAcMutations(current, options2) {
5999
6054
  return { ok: true, criteria: result.items };
6000
6055
  }
6001
6056
 
6002
- // src/commands/backlog/update/buildUpdateSql.ts
6057
+ // src/commands/backlog/update/buildUpdateValues.ts
6003
6058
  import chalk69 from "chalk";
6004
- function buildUpdateSql(options2) {
6005
- const { name, desc, type, ac } = options2;
6006
- if (!name && !desc && !type && !ac) {
6059
+ function buildUpdateValues(options2) {
6060
+ const { name, desc: desc2, type, ac } = options2;
6061
+ if (!name && !desc2 && !type && !ac) {
6007
6062
  console.log(chalk69.red("Nothing to update. Provide at least one flag."));
6008
6063
  process.exitCode = 1;
6009
6064
  return void 0;
@@ -6013,30 +6068,25 @@ function buildUpdateSql(options2) {
6013
6068
  process.exitCode = 1;
6014
6069
  return void 0;
6015
6070
  }
6016
- const sets = [];
6017
- const params = [];
6071
+ const set = {};
6018
6072
  const fieldNames = [];
6019
6073
  if (name) {
6020
- sets.push("name = ?");
6021
- params.push(name);
6074
+ set.name = name;
6022
6075
  fieldNames.push("name");
6023
6076
  }
6024
- if (desc) {
6025
- sets.push("description = ?");
6026
- params.push(desc);
6077
+ if (desc2) {
6078
+ set.description = desc2;
6027
6079
  fieldNames.push("description");
6028
6080
  }
6029
6081
  if (type) {
6030
- sets.push("type = ?");
6031
- params.push(type);
6082
+ set.type = type;
6032
6083
  fieldNames.push("type");
6033
6084
  }
6034
6085
  if (ac) {
6035
- sets.push("acceptance_criteria = ?");
6036
- params.push(JSON.stringify(ac));
6086
+ set.acceptanceCriteria = JSON.stringify(ac);
6037
6087
  fieldNames.push("acceptance criteria");
6038
6088
  }
6039
- return { sets, params, fields: fieldNames.join(", ") };
6089
+ return { set, fields: fieldNames.join(", ") };
6040
6090
  }
6041
6091
 
6042
6092
  // src/commands/backlog/update/index.ts
@@ -6060,14 +6110,11 @@ async function update(id, options2) {
6060
6110
  }
6061
6111
  ac = mutation.criteria;
6062
6112
  }
6063
- const built = buildUpdateSql({ ...options2, ac });
6113
+ const built = buildUpdateValues({ ...options2, ac });
6064
6114
  if (!built) return;
6065
- const db = await getBacklogDb();
6115
+ const orm = await getBacklogOrm();
6066
6116
  const itemId = result.item.id;
6067
- await db.run(`UPDATE items SET ${built.sets.join(", ")} WHERE id = ?`, [
6068
- ...built.params,
6069
- itemId
6070
- ]);
6117
+ await orm.update(items).set(built.set).where(eq19(items.id, itemId));
6071
6118
  console.log(chalk70.green(`Updated ${built.fields} on item #${itemId}.`));
6072
6119
  }
6073
6120
 
@@ -6075,29 +6122,31 @@ async function update(id, options2) {
6075
6122
  import chalk71 from "chalk";
6076
6123
 
6077
6124
  // src/commands/backlog/applyPhaseUpdate.ts
6078
- async function applyPhaseUpdate(db, itemId, phaseIdx, fields) {
6079
- await db.transaction(async (tx) => {
6125
+ import { and as and8, eq as eq20 } from "drizzle-orm";
6126
+ async function applyPhaseUpdate(orm, itemId, phaseIdx, fields) {
6127
+ await orm.transaction(async (tx) => {
6080
6128
  if (fields.name) {
6081
- await tx.run(
6082
- "UPDATE plan_phases SET name = ? WHERE item_id = ? AND idx = ?",
6083
- [fields.name, itemId, phaseIdx]
6129
+ await tx.update(planPhases).set({ name: fields.name }).where(
6130
+ and8(eq20(planPhases.itemId, itemId), eq20(planPhases.idx, phaseIdx))
6084
6131
  );
6085
6132
  }
6086
6133
  if (fields.manualCheck) {
6087
- await tx.run(
6088
- "UPDATE plan_phases SET manual_checks = ? WHERE item_id = ? AND idx = ?",
6089
- [JSON.stringify(fields.manualCheck), itemId, phaseIdx]
6134
+ await tx.update(planPhases).set({ manualChecks: JSON.stringify(fields.manualCheck) }).where(
6135
+ and8(eq20(planPhases.itemId, itemId), eq20(planPhases.idx, phaseIdx))
6090
6136
  );
6091
6137
  }
6092
6138
  if (fields.task) {
6093
- await tx.run(
6094
- "DELETE FROM plan_tasks WHERE item_id = ? AND phase_idx = ?",
6095
- [itemId, phaseIdx]
6139
+ await tx.delete(planTasks).where(
6140
+ and8(eq20(planTasks.itemId, itemId), eq20(planTasks.phaseIdx, phaseIdx))
6096
6141
  );
6097
- for (let i = 0; i < fields.task.length; i++) {
6098
- await tx.run(
6099
- "INSERT INTO plan_tasks (item_id, phase_idx, idx, task) VALUES (?, ?, ?, ?)",
6100
- [itemId, phaseIdx, i, fields.task[i]]
6142
+ if (fields.task.length) {
6143
+ await tx.insert(planTasks).values(
6144
+ fields.task.map((task, i) => ({
6145
+ itemId,
6146
+ phaseIdx,
6147
+ idx: i,
6148
+ task
6149
+ }))
6101
6150
  );
6102
6151
  }
6103
6152
  }
@@ -6171,7 +6220,7 @@ function resolvePhaseFields(options2, current) {
6171
6220
  async function updatePhase(id, phase, options2) {
6172
6221
  const found = await findPhase(id, phase);
6173
6222
  if (!found) return;
6174
- const { result, db, itemId, phaseIdx } = found;
6223
+ const { result, orm, itemId, phaseIdx } = found;
6175
6224
  const resolved = resolvePhaseFields(options2, result.item.plan?.[phaseIdx]);
6176
6225
  if (!resolved.ok) {
6177
6226
  console.log(chalk71.red(resolved.error));
@@ -6179,7 +6228,7 @@ async function updatePhase(id, phase, options2) {
6179
6228
  return;
6180
6229
  }
6181
6230
  const { name, task, manualCheck } = resolved.fields;
6182
- await applyPhaseUpdate(db, itemId, phaseIdx, { name, task, manualCheck });
6231
+ await applyPhaseUpdate(orm, itemId, phaseIdx, { name, task, manualCheck });
6183
6232
  const fields = [
6184
6233
  name && "name",
6185
6234
  task && "tasks",
@@ -6380,7 +6429,7 @@ import { mkdirSync as mkdirSync4 } from "fs";
6380
6429
  import { homedir as homedir4 } from "os";
6381
6430
  import { join as join18 } from "path";
6382
6431
  import Database from "better-sqlite3";
6383
- var _db2;
6432
+ var _db;
6384
6433
  function getDbDir() {
6385
6434
  return join18(homedir4(), ".assist");
6386
6435
  }
@@ -6398,13 +6447,13 @@ function initSchema(db) {
6398
6447
  `);
6399
6448
  }
6400
6449
  function openPromptsDb(dir) {
6401
- if (_db2) return _db2;
6450
+ if (_db) return _db;
6402
6451
  const dbDir = dir ?? getDbDir();
6403
6452
  mkdirSync4(dbDir, { recursive: true });
6404
6453
  const db = new Database(join18(dbDir, "assist.db"));
6405
6454
  db.pragma("journal_mode = WAL");
6406
6455
  initSchema(db);
6407
- _db2 = db;
6456
+ _db = db;
6408
6457
  return db;
6409
6458
  }
6410
6459
  function logDeniedToolCall(entry) {
@@ -6881,17 +6930,17 @@ function isClaudeCode() {
6881
6930
  }
6882
6931
 
6883
6932
  // src/commands/permitCliReads/mapAsync.ts
6884
- async function mapAsync(items, concurrency, fn) {
6885
- const results = new Array(items.length);
6933
+ async function mapAsync(items2, concurrency, fn) {
6934
+ const results = new Array(items2.length);
6886
6935
  let next3 = 0;
6887
6936
  async function worker() {
6888
- while (next3 < items.length) {
6937
+ while (next3 < items2.length) {
6889
6938
  const idx = next3++;
6890
- results[idx] = await fn(items[idx]);
6939
+ results[idx] = await fn(items2[idx]);
6891
6940
  }
6892
6941
  }
6893
6942
  const workers = Array.from(
6894
- { length: Math.min(concurrency, items.length) },
6943
+ { length: Math.min(concurrency, items2.length) },
6895
6944
  () => worker()
6896
6945
  );
6897
6946
  await Promise.all(workers);
@@ -7089,8 +7138,8 @@ function formatHuman(cli, commands) {
7089
7138
  `];
7090
7139
  for (const cmd of sorted) {
7091
7140
  const full = `${cli} ${cmd.path.join(" ")}`;
7092
- const text = cmd.description ? `${full} \u2014 ${cmd.description}` : full;
7093
- lines.push(`${prefix(classifyVerb(cmd.path))}${text}`);
7141
+ const text2 = cmd.description ? `${full} \u2014 ${cmd.description}` : full;
7142
+ lines.push(`${prefix(classifyVerb(cmd.path))}${text2}`);
7094
7143
  }
7095
7144
  return lines.join("\n");
7096
7145
  }
@@ -7225,12 +7274,12 @@ function denyList() {
7225
7274
  import chalk75 from "chalk";
7226
7275
  function denyRemove(pattern2, options2) {
7227
7276
  const { deny, saveDeny } = loadDenyConfig(options2.global);
7228
- const index = deny.findIndex((r) => r.pattern === pattern2);
7229
- if (index === -1) {
7277
+ const index2 = deny.findIndex((r) => r.pattern === pattern2);
7278
+ if (index2 === -1) {
7230
7279
  console.log(chalk75.yellow(`No deny rule found for: ${pattern2}`));
7231
7280
  return;
7232
7281
  }
7233
- deny.splice(index, 1);
7282
+ deny.splice(index2, 1);
7234
7283
  saveDeny(deny.length > 0 ? deny : void 0);
7235
7284
  console.log(chalk75.green(`Removed deny rule: ${pattern2}`));
7236
7285
  }
@@ -7467,7 +7516,7 @@ function calculateHalstead(node) {
7467
7516
  // src/commands/complexity/shared/countSloc.ts
7468
7517
  function countSloc(content) {
7469
7518
  let inMultiLineComment = false;
7470
- let count = 0;
7519
+ let count4 = 0;
7471
7520
  for (const line of content.split("\n")) {
7472
7521
  const trimmed = line.trim();
7473
7522
  if (inMultiLineComment) {
@@ -7475,7 +7524,7 @@ function countSloc(content) {
7475
7524
  inMultiLineComment = false;
7476
7525
  const afterComment = trimmed.substring(trimmed.indexOf("*/") + 2);
7477
7526
  if (afterComment.trim().length > 0) {
7478
- count++;
7527
+ count4++;
7479
7528
  }
7480
7529
  }
7481
7530
  continue;
@@ -7487,7 +7536,7 @@ function countSloc(content) {
7487
7536
  if (trimmed.includes("*/")) {
7488
7537
  const afterComment = trimmed.substring(trimmed.indexOf("*/") + 2);
7489
7538
  if (afterComment.trim().length > 0) {
7490
- count++;
7539
+ count4++;
7491
7540
  }
7492
7541
  } else {
7493
7542
  inMultiLineComment = true;
@@ -7495,10 +7544,10 @@ function countSloc(content) {
7495
7544
  continue;
7496
7545
  }
7497
7546
  if (trimmed.length > 0) {
7498
- count++;
7547
+ count4++;
7499
7548
  }
7500
7549
  }
7501
- return count;
7550
+ return count4;
7502
7551
  }
7503
7552
 
7504
7553
  // src/commands/complexity/shared/index.ts
@@ -9193,16 +9242,16 @@ function parseUserLine(line) {
9193
9242
  if (entry.type !== "user") return void 0;
9194
9243
  const msg = entry.message;
9195
9244
  const c = msg?.content;
9196
- let text;
9245
+ let text2;
9197
9246
  if (typeof c === "string") {
9198
- text = c;
9247
+ text2 = c;
9199
9248
  } else if (Array.isArray(c)) {
9200
9249
  const collected = c.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n");
9201
- text = collected || void 0;
9250
+ text2 = collected || void 0;
9202
9251
  }
9203
- if (!text) return void 0;
9252
+ if (!text2) return void 0;
9204
9253
  return {
9205
- text,
9254
+ text: text2,
9206
9255
  entrypoint: typeof entry.entrypoint === "string" ? entry.entrypoint : void 0
9207
9256
  };
9208
9257
  }
@@ -9253,12 +9302,12 @@ ${payload}`;
9253
9302
  return "";
9254
9303
  }
9255
9304
  }
9256
- function stripPreludes(text) {
9257
- return text.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();
9305
+ function stripPreludes(text2) {
9306
+ return text2.replace(/<command-name>[\s\S]*?<\/command-name>/g, "").replace(/<command-message>[\s\S]*?<\/command-message>/g, "").replace(/<command-args>[\s\S]*?<\/command-args>/g, "").replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g, "").replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
9258
9307
  }
9259
- function capPayload(text, maxBytes) {
9260
- const buf = Buffer.from(text, "utf8");
9261
- if (buf.length <= maxBytes) return text;
9308
+ function capPayload(text2, maxBytes) {
9309
+ const buf = Buffer.from(text2, "utf8");
9310
+ if (buf.length <= maxBytes) return text2;
9262
9311
  return buf.subarray(buf.length - maxBytes).toString("utf8");
9263
9312
  }
9264
9313
  function normaliseOutput(raw) {
@@ -9327,9 +9376,9 @@ import chalk103 from "chalk";
9327
9376
 
9328
9377
  // src/commands/jira/adfToText.ts
9329
9378
  function renderInline(node) {
9330
- const text = node.text ?? "";
9331
- if (node.marks?.some((m) => m.type === "code")) return `\`${text}\``;
9332
- return text;
9379
+ const text2 = node.text ?? "";
9380
+ if (node.marks?.some((m) => m.type === "code")) return `\`${text2}\``;
9381
+ return text2;
9333
9382
  }
9334
9383
  function renderChildren(node, indent2) {
9335
9384
  return renderNodes(node.content ?? [], indent2);
@@ -9371,8 +9420,8 @@ function isListNode(node) {
9371
9420
  function renderListChild(child, indent2, pad, marker, isFirst) {
9372
9421
  if (isListNode(child)) return renderNodes([child], indent2 + 1);
9373
9422
  if (child.type !== "paragraph") return renderNode(child, indent2);
9374
- const text = renderChildren(child, indent2);
9375
- return isFirst ? `${pad}${marker} ${text}` : `${pad} ${text}`;
9423
+ const text2 = renderChildren(child, indent2);
9424
+ return isFirst ? `${pad}${marker} ${text2}` : `${pad} ${text2}`;
9376
9425
  }
9377
9426
  function renderListItem(node, indent2, marker) {
9378
9427
  const pad = " ".repeat(indent2);
@@ -9735,9 +9784,9 @@ function excerpt(xml, ...tags) {
9735
9784
  for (const tag of tags) {
9736
9785
  const raw = extractText(xml, tag);
9737
9786
  if (!raw) continue;
9738
- const text = stripHtml(raw);
9739
- if (text.length <= MAX_EXCERPT) return text;
9740
- return `${text.slice(0, MAX_EXCERPT)}\u2026`;
9787
+ const text2 = stripHtml(raw);
9788
+ if (text2.length <= MAX_EXCERPT) return text2;
9789
+ return `${text2.slice(0, MAX_EXCERPT)}\u2026`;
9741
9790
  }
9742
9791
  return "";
9743
9792
  }
@@ -9782,22 +9831,22 @@ async function fetchFeeds(urls, onProgress) {
9782
9831
  const origin = new URL(url).origin;
9783
9832
  const res = await fetch(url);
9784
9833
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
9785
- const items2 = parseFeed(await res.text(), origin);
9834
+ const items3 = parseFeed(await res.text(), origin);
9786
9835
  done2++;
9787
9836
  onProgress?.(done2, urls.length);
9788
- return items2;
9837
+ return items3;
9789
9838
  })
9790
9839
  );
9791
- const items = [];
9840
+ const items2 = [];
9792
9841
  for (const result of results) {
9793
9842
  if (result.status === "fulfilled") {
9794
- items.push(...result.value);
9843
+ items2.push(...result.value);
9795
9844
  }
9796
9845
  }
9797
- items.sort(
9846
+ items2.sort(
9798
9847
  (a, b) => new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime()
9799
9848
  );
9800
- return items;
9849
+ return items2;
9801
9850
  }
9802
9851
 
9803
9852
  // src/commands/news/web/getHtml.ts
@@ -9833,13 +9882,13 @@ function prefetch() {
9833
9882
  process.stdout.write(
9834
9883
  `\r${chalk109.dim(`Fetching feeds ${bar} ${done2}/${t}`)}`
9835
9884
  );
9836
- }).then((items) => {
9885
+ }).then((items2) => {
9837
9886
  process.stdout.write(
9838
- `\r${chalk109.green(`Fetched ${items.length} items from ${total} feed(s)`)}
9887
+ `\r${chalk109.green(`Fetched ${items2.length} items from ${total} feed(s)`)}
9839
9888
  `
9840
9889
  );
9841
- cachedItems = items;
9842
- return items;
9890
+ cachedItems = items2;
9891
+ return items2;
9843
9892
  });
9844
9893
  }
9845
9894
  async function listItems2(_req, res) {
@@ -9901,11 +9950,11 @@ function printPromptsTable(rows) {
9901
9950
  console.log(chalk110.dim(header));
9902
9951
  console.log(chalk110.dim("-".repeat(header.length)));
9903
9952
  for (const row of rows) {
9904
- const count = String(row.count).padStart(countWidth);
9953
+ const count4 = String(row.count).padStart(countWidth);
9905
9954
  const tool = row.tool.padEnd(toolWidth);
9906
9955
  const command = truncate(row.command, 60).padEnd(commandWidth);
9907
9956
  console.log(
9908
- `${chalk110.yellow(count)} ${tool} ${command} ${chalk110.dim(row.repos)}`
9957
+ `${chalk110.yellow(count4)} ${tool} ${command} ${chalk110.dim(row.repos)}`
9909
9958
  );
9910
9959
  }
9911
9960
  }
@@ -10196,8 +10245,8 @@ function requireCache(prNumber) {
10196
10245
  }
10197
10246
  return cache;
10198
10247
  }
10199
- function findLineComment(comments2, commentId) {
10200
- return comments2.find((c) => c.type === "line" && c.id === commentId);
10248
+ function findLineComment(comments3, commentId) {
10249
+ return comments3.find((c) => c.type === "line" && c.id === commentId);
10201
10250
  }
10202
10251
  function requireLineComment(cache, commentId) {
10203
10252
  const comment3 = findLineComment(cache.comments, commentId);
@@ -10330,8 +10379,8 @@ function mapLineComment(c, threadInfo) {
10330
10379
  };
10331
10380
  }
10332
10381
  function fetchLineComments(org, repo, prNumber, threadInfo) {
10333
- const comments2 = fetchJson(`repos/${org}/${repo}/pulls/${prNumber}/comments`);
10334
- return comments2.map(
10382
+ const comments3 = fetchJson(`repos/${org}/${repo}/pulls/${prNumber}/comments`);
10383
+ return comments3.map(
10335
10384
  (c) => mapLineComment(c, threadInfo)
10336
10385
  );
10337
10386
  }
@@ -10355,33 +10404,33 @@ function formatForHuman(comment3) {
10355
10404
  ""
10356
10405
  ].join("\n");
10357
10406
  }
10358
- function summarise2(comments2) {
10359
- const lineCount = comments2.filter((c) => c.type === "line").length;
10360
- const reviewCount = comments2.filter((c) => c.type === "review").length;
10407
+ function summarise2(comments3) {
10408
+ const lineCount = comments3.filter((c) => c.type === "line").length;
10409
+ const reviewCount = comments3.filter((c) => c.type === "review").length;
10361
10410
  const parts = [];
10362
10411
  if (lineCount > 0) parts.push(`${lineCount} line`);
10363
10412
  if (reviewCount > 0) parts.push(`${reviewCount} review`);
10364
- return `Found ${parts.join(" and ")} comment${comments2.length === 1 ? "" : "s"}.`;
10413
+ return `Found ${parts.join(" and ")} comment${comments3.length === 1 ? "" : "s"}.`;
10365
10414
  }
10366
10415
  function printComments2(result) {
10367
- const { comments: comments2, cachePath } = result;
10368
- if (comments2.length === 0) {
10416
+ const { comments: comments3, cachePath } = result;
10417
+ if (comments3.length === 0) {
10369
10418
  console.log("No comments found.");
10370
10419
  return;
10371
10420
  }
10372
10421
  if (!isClaudeCode()) {
10373
- for (const comment3 of comments2) {
10422
+ for (const comment3 of comments3) {
10374
10423
  console.log(formatForHuman(comment3));
10375
10424
  }
10376
10425
  }
10377
- console.log(summarise2(comments2));
10426
+ console.log(summarise2(comments3));
10378
10427
  if (cachePath) {
10379
10428
  console.log(`Saved to ${cachePath}`);
10380
10429
  }
10381
10430
  }
10382
10431
 
10383
10432
  // src/commands/prs/listComments/index.ts
10384
- function writeCommentsCache(prNumber, comments2) {
10433
+ function writeCommentsCache(prNumber, comments3) {
10385
10434
  const assistDir = join34(process.cwd(), ".assist");
10386
10435
  if (!existsSync34(assistDir)) {
10387
10436
  mkdirSync9(assistDir, { recursive: true });
@@ -10389,7 +10438,7 @@ function writeCommentsCache(prNumber, comments2) {
10389
10438
  const cacheData = {
10390
10439
  prNumber,
10391
10440
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
10392
- comments: comments2
10441
+ comments: comments3
10393
10442
  };
10394
10443
  const cachePath = join34(assistDir, `pr-${prNumber}-comments.yaml`);
10395
10444
  writeFileSync23(cachePath, stringify(cacheData));
@@ -10406,9 +10455,9 @@ function handleKnownErrors(error) {
10406
10455
  }
10407
10456
  return null;
10408
10457
  }
10409
- function updateCache(prNumber, comments2) {
10410
- if (comments2.some((c) => c.type === "line")) {
10411
- writeCommentsCache(prNumber, comments2);
10458
+ function updateCache(prNumber, comments3) {
10459
+ if (comments3.some((c) => c.type === "line")) {
10460
+ writeCommentsCache(prNumber, comments3);
10412
10461
  } else {
10413
10462
  deleteCommentsCache(prNumber);
10414
10463
  }
@@ -10515,8 +10564,8 @@ async function promptNavigation(currentPage, totalPages) {
10515
10564
  });
10516
10565
  return parseAction(action);
10517
10566
  }
10518
- function computeTotalPages(count) {
10519
- return Math.ceil(count / PAGE_SIZE);
10567
+ function computeTotalPages(count4) {
10568
+ return Math.ceil(count4 / PAGE_SIZE);
10520
10569
  }
10521
10570
  async function navigateAndDisplay(pullRequests, totalPages, currentPage) {
10522
10571
  const delta = await promptNavigation(currentPage, totalPages);
@@ -10704,9 +10753,9 @@ function opExec(args) {
10704
10753
  }).trim();
10705
10754
  }
10706
10755
  function searchItems(search2) {
10707
- let items;
10756
+ let items2;
10708
10757
  try {
10709
- items = JSON.parse(opExec("item list --format=json"));
10758
+ items2 = JSON.parse(opExec("item list --format=json"));
10710
10759
  } catch {
10711
10760
  console.error(
10712
10761
  chalk114.red(
@@ -10716,7 +10765,7 @@ function searchItems(search2) {
10716
10765
  process.exit(1);
10717
10766
  }
10718
10767
  const lower = search2.toLowerCase();
10719
- return items.filter((i) => i.title.toLowerCase().includes(lower));
10768
+ return items2.filter((i) => i.title.toLowerCase().includes(lower));
10720
10769
  }
10721
10770
  function getItemFields(itemId) {
10722
10771
  try {
@@ -10740,14 +10789,14 @@ async function selectOpSecret(searchTerm) {
10740
10789
  name: "search",
10741
10790
  message: "Search 1Password for API key item:"
10742
10791
  }).run();
10743
- const items = searchItems(search2);
10744
- if (items.length === 0) {
10792
+ const items2 = searchItems(search2);
10793
+ if (items2.length === 0) {
10745
10794
  console.error(chalk115.red(`No items found matching "${search2}".`));
10746
10795
  process.exit(1);
10747
10796
  }
10748
10797
  const itemId = await selectOne(
10749
10798
  "Select item:",
10750
- items.map((i) => ({ name: `${i.title} (${i.vault.name})`, value: i.id }))
10799
+ items2.map((i) => ({ name: `${i.title} (${i.vault.name})`, value: i.id }))
10751
10800
  );
10752
10801
  const fields = getItemFields(itemId);
10753
10802
  if (fields.length === 0) {
@@ -11611,9 +11660,9 @@ function resolveImports(target, dependencies, sourceFile, statements = []) {
11611
11660
  function extractTexts(target, allFunctions, statements) {
11612
11661
  const stmtTexts = statements.map((v) => v.getFullText().trim());
11613
11662
  const fnTexts = allFunctions.map((fn) => {
11614
- const text = fn.getFullText().trim();
11615
- if (fn === target && !text.startsWith("export ")) return `export ${text}`;
11616
- return text;
11663
+ const text2 = fn.getFullText().trim();
11664
+ if (fn === target && !text2.startsWith("export ")) return `export ${text2}`;
11665
+ return text2;
11617
11666
  });
11618
11667
  return [...stmtTexts, ...fnTexts];
11619
11668
  }
@@ -11769,7 +11818,7 @@ function sourceReferencesName(sourceFile, functionName, extractedNames) {
11769
11818
  }
11770
11819
 
11771
11820
  // src/commands/refactor/extract/buildPlan.ts
11772
- function buildPlan(functionName, sourceFile, sourcePath, destPath, project) {
11821
+ function buildPlan2(functionName, sourceFile, sourcePath, destPath, project) {
11773
11822
  const analysis = analyseTarget(sourceFile, functionName);
11774
11823
  const sourceRelPath = getRelativeImportPath(destPath, sourcePath);
11775
11824
  const rewrittenImports = rewriteImportPaths(
@@ -11923,7 +11972,7 @@ async function extract(file, functionName, destination, options2 = {}) {
11923
11972
  const cwd = process.cwd();
11924
11973
  const relDest = path36.relative(cwd, destPath);
11925
11974
  const { project, sourceFile } = loadProjectFile(file);
11926
- const plan2 = buildPlan(
11975
+ const plan2 = buildPlan2(
11927
11976
  functionName,
11928
11977
  sourceFile,
11929
11978
  sourcePath,
@@ -12417,8 +12466,8 @@ import fs25 from "fs";
12417
12466
  import path45 from "path";
12418
12467
  function collectEntry(results, dir, entry) {
12419
12468
  const full = path45.join(dir, entry.name);
12420
- const items = entry.isDirectory() ? listFilesRecursive(full) : [full];
12421
- results.push(...items);
12469
+ const items2 = entry.isDirectory() ? listFilesRecursive(full) : [full];
12470
+ results.push(...items2);
12422
12471
  }
12423
12472
  function listFilesRecursive(dir) {
12424
12473
  if (!fs25.existsSync(dir)) return [];
@@ -12502,7 +12551,7 @@ function planFileMoves(clusters) {
12502
12551
  }
12503
12552
 
12504
12553
  // src/commands/refactor/restructure/index.ts
12505
- function buildPlan2(candidateFiles, tsConfigPath) {
12554
+ function buildPlan3(candidateFiles, tsConfigPath) {
12506
12555
  const candidates = new Set(candidateFiles.map((f) => path47.resolve(f)));
12507
12556
  const graph = buildImportGraph(candidates, tsConfigPath);
12508
12557
  const allProjectFiles = /* @__PURE__ */ new Set([
@@ -12527,7 +12576,7 @@ async function restructure(pattern2, options2 = {}) {
12527
12576
  return;
12528
12577
  }
12529
12578
  const tsConfigPath = path47.resolve("tsconfig.json");
12530
- const plan2 = buildPlan2(files, tsConfigPath);
12579
+ const plan2 = buildPlan3(files, tsConfigPath);
12531
12580
  if (plan2.moves.length === 0) {
12532
12581
  console.log(chalk134.green("No restructuring needed"));
12533
12582
  return;
@@ -12585,10 +12634,10 @@ function threadKey(c, byId) {
12585
12634
  }
12586
12635
  return `root-${c.id}`;
12587
12636
  }
12588
- function groupIntoThreads(comments2) {
12589
- const byId = new Map(comments2.map((c) => [c.id, c]));
12637
+ function groupIntoThreads(comments3) {
12638
+ const byId = new Map(comments3.map((c) => [c.id, c]));
12590
12639
  const threads = /* @__PURE__ */ new Map();
12591
- for (const c of comments2) {
12640
+ for (const c of comments3) {
12592
12641
  const key = threadKey(c, byId);
12593
12642
  const existing = threads.get(key);
12594
12643
  const parent = c.inReplyToId !== null ? byId.get(c.inReplyToId) : void 0;
@@ -12616,9 +12665,9 @@ function formatThread(thread) {
12616
12665
  return lines.join("\n\n");
12617
12666
  }
12618
12667
  var INTRO = `The PR already has the review comments below (including resolved and outdated threads). Avoid re-raising findings that a prior comment substantively covers.`;
12619
- function formatPriorComments(comments2) {
12620
- if (comments2.length === 0) return "";
12621
- const blocks = groupIntoThreads(comments2).map(formatThread);
12668
+ function formatPriorComments(comments3) {
12669
+ if (comments3.length === 0) return "";
12670
+ const blocks = groupIntoThreads(comments3).map(formatThread);
12622
12671
  return `## Prior review comments
12623
12672
 
12624
12673
  ${INTRO}
@@ -13020,9 +13069,9 @@ function warnUnlocated(unlocated) {
13020
13069
  }
13021
13070
 
13022
13071
  // src/commands/review/postReviewToPr.ts
13023
- async function confirmPost(prNumber, count, options2) {
13072
+ async function confirmPost(prNumber, count4, options2) {
13024
13073
  if (!options2.prompt) return true;
13025
- return promptConfirm(`Post ${count} comment(s) to PR #${prNumber}?`, false);
13074
+ return promptConfirm(`Post ${count4} comment(s) to PR #${prNumber}?`, false);
13026
13075
  }
13027
13076
  async function postReviewToPr(synthesisPath, options2) {
13028
13077
  const prNumber = findCurrentPrNumber();
@@ -13222,9 +13271,9 @@ var MultiSpinner = class {
13222
13271
  elapsedPrefix: prefix2
13223
13272
  });
13224
13273
  }
13225
- failRemaining(text) {
13274
+ failRemaining(text2) {
13226
13275
  for (const entry of this.entries) {
13227
- if (entry.state === "running") this.resolve(entry, "failed", text);
13276
+ if (entry.state === "running") this.resolve(entry, "failed", text2);
13228
13277
  }
13229
13278
  }
13230
13279
  add(entry) {
@@ -13237,14 +13286,14 @@ var MultiSpinner = class {
13237
13286
  set text(value) {
13238
13287
  entry.text = value;
13239
13288
  },
13240
- succeed: (text) => this.resolve(entry, "succeeded", text),
13241
- fail: (text) => this.resolve(entry, "failed", text)
13289
+ succeed: (text2) => this.resolve(entry, "succeeded", text2),
13290
+ fail: (text2) => this.resolve(entry, "failed", text2)
13242
13291
  };
13243
13292
  }
13244
- resolve(entry, state, text) {
13293
+ resolve(entry, state, text2) {
13245
13294
  if (entry.state !== "running") return;
13246
13295
  entry.state = state;
13247
- if (text !== void 0) entry.text = text;
13296
+ if (text2 !== void 0) entry.text = text2;
13248
13297
  entry.elapsedStart = void 0;
13249
13298
  this.render();
13250
13299
  this.maybeFinish();
@@ -13319,12 +13368,12 @@ function skippedCodexResult(outputPath) {
13319
13368
  // src/commands/review/formatReviewerFailure.ts
13320
13369
  var FAST_FAIL_MS = 1e3;
13321
13370
  var STDOUT_TAIL_LINES = 20;
13322
- function indent(text) {
13323
- return text.split(/\r?\n/).map((line) => ` ${line}`);
13371
+ function indent(text2) {
13372
+ return text2.split(/\r?\n/).map((line) => ` ${line}`);
13324
13373
  }
13325
- function tailLines(text, maxLines) {
13326
- const lines = text.split(/\r?\n/);
13327
- return lines.length <= maxLines ? text : lines.slice(-maxLines).join("\n");
13374
+ function tailLines(text2, maxLines) {
13375
+ const lines = text2.split(/\r?\n/);
13376
+ return lines.length <= maxLines ? text2 : lines.slice(-maxLines).join("\n");
13328
13377
  }
13329
13378
  function isFastFail(input) {
13330
13379
  return input.exitCode !== 0 && input.elapsedMs !== void 0 && input.elapsedMs < FAST_FAIL_MS;
@@ -14046,9 +14095,9 @@ async function runReviewPipeline(paths, options2) {
14046
14095
  }
14047
14096
 
14048
14097
  // src/commands/review/reviewPr.ts
14049
- function logPriorComments(count) {
14050
- if (count === 0) return;
14051
- console.log(`Including ${count} prior review comment(s) in request.md.`);
14098
+ function logPriorComments(count4) {
14099
+ if (count4 === 0) return;
14100
+ console.log(`Including ${count4} prior review comment(s) in request.md.`);
14052
14101
  }
14053
14102
  function gatherChangedContext() {
14054
14103
  const context = gatherContext();
@@ -14290,10 +14339,10 @@ function filterToSql(filter) {
14290
14339
  }
14291
14340
 
14292
14341
  // src/commands/seq/fetchSeqData.ts
14293
- async function fetchSeqData(conn, filter, count, from, to) {
14342
+ async function fetchSeqData(conn, filter, count4, from, to) {
14294
14343
  const sqlFilter = filterToSql(filter);
14295
- const sql2 = `select @Timestamp, @Level, @Exception, @Message from stream where ${sqlFilter} order by @Timestamp desc limit ${count}`;
14296
- const params = new URLSearchParams({ q: sql2 });
14344
+ const sql4 = `select @Timestamp, @Level, @Exception, @Message from stream where ${sqlFilter} order by @Timestamp desc limit ${count4}`;
14345
+ const params = new URLSearchParams({ q: sql4 });
14297
14346
  if (from) params.set("fromDateUtc", from);
14298
14347
  if (to) params.set("toDateUtc", to);
14299
14348
  const response = await fetchSeq(conn, "/api/data", params);
@@ -14462,12 +14511,12 @@ function resolveConnection2(name) {
14462
14511
  async function seqQuery(filter, options2) {
14463
14512
  rejectTimestampFilter(filter);
14464
14513
  const conn = resolveConnection2(options2.connection);
14465
- const count = Number.parseInt(options2.count ?? "1000", 10);
14514
+ const count4 = Number.parseInt(options2.count ?? "1000", 10);
14466
14515
  const from = options2.from ? parseRelativeTime(options2.from) : void 0;
14467
14516
  const to = options2.to ? parseRelativeTime(options2.to) : void 0;
14468
- const events = from || to ? await fetchSeqData(conn, filter, count, from, to) : await fetchSeqEvents(
14517
+ const events = from || to ? await fetchSeqData(conn, filter, count4, from, to) : await fetchSeqEvents(
14469
14518
  conn,
14470
- new URLSearchParams({ filter, count: String(count) })
14519
+ new URLSearchParams({ filter, count: String(count4) })
14471
14520
  );
14472
14521
  if (events.length === 0) {
14473
14522
  console.log(chalk141.yellow("No events found."));
@@ -14483,10 +14532,10 @@ async function seqQuery(filter, options2) {
14483
14532
  }
14484
14533
  console.log(chalk141.dim(`
14485
14534
  ${events.length} events`));
14486
- if (events.length >= count) {
14535
+ if (events.length >= count4) {
14487
14536
  console.log(
14488
14537
  chalk141.yellow(
14489
- `Results limited to ${count}. Use --count to retrieve more.`
14538
+ `Results limited to ${count4}. Use --count to retrieve more.`
14490
14539
  )
14491
14540
  );
14492
14541
  }
@@ -14545,26 +14594,26 @@ import chalk144 from "chalk";
14545
14594
  // src/commands/sql/loadConnections.ts
14546
14595
  function loadConnections3() {
14547
14596
  const raw = loadGlobalConfigRaw();
14548
- const sql2 = raw.sql;
14549
- return sql2?.connections ?? [];
14597
+ const sql4 = raw.sql;
14598
+ return sql4?.connections ?? [];
14550
14599
  }
14551
14600
  function saveConnections3(connections) {
14552
14601
  const raw = loadGlobalConfigRaw();
14553
- const sql2 = raw.sql ?? {};
14554
- sql2.connections = connections;
14555
- raw.sql = sql2;
14602
+ const sql4 = raw.sql ?? {};
14603
+ sql4.connections = connections;
14604
+ raw.sql = sql4;
14556
14605
  saveGlobalConfig(raw);
14557
14606
  }
14558
14607
  function getDefaultConnection2() {
14559
14608
  const raw = loadGlobalConfigRaw();
14560
- const sql2 = raw.sql;
14561
- return sql2?.defaultConnection;
14609
+ const sql4 = raw.sql;
14610
+ return sql4?.defaultConnection;
14562
14611
  }
14563
14612
  function setDefaultConnection2(name) {
14564
14613
  const raw = loadGlobalConfigRaw();
14565
- const sql2 = raw.sql ?? {};
14566
- sql2.defaultConnection = name;
14567
- raw.sql = sql2;
14614
+ const sql4 = raw.sql ?? {};
14615
+ sql4.defaultConnection = name;
14616
+ raw.sql = sql4;
14568
14617
  saveGlobalConfig(raw);
14569
14618
  }
14570
14619
 
@@ -14635,9 +14684,9 @@ function resolveConnection3(name) {
14635
14684
  }
14636
14685
 
14637
14686
  // src/commands/sql/sqlConnect.ts
14638
- import sql from "mssql";
14687
+ import sql3 from "mssql";
14639
14688
  async function sqlConnect(conn) {
14640
- return await sql.connect({
14689
+ return await sql3.connect({
14641
14690
  server: conn.server,
14642
14691
  port: conn.port,
14643
14692
  user: conn.user,
@@ -14700,11 +14749,11 @@ var MUTATION_PATTERN = new RegExp(
14700
14749
  `\\b(${MUTATION_KEYWORDS.join("|")})\\b`,
14701
14750
  "i"
14702
14751
  );
14703
- function stripComments(sql2) {
14704
- return sql2.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/--[^\n]*/g, " ");
14752
+ function stripComments(sql4) {
14753
+ return sql4.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/--[^\n]*/g, " ");
14705
14754
  }
14706
- function isMutation(sql2) {
14707
- const stripped = stripComments(sql2);
14755
+ function isMutation(sql4) {
14756
+ const stripped = stripComments(sql4);
14708
14757
  if (MUTATION_PATTERN.test(stripped)) return true;
14709
14758
  return /\bSELECT\b[\s\S]+\bINTO\s+\w/i.test(stripped);
14710
14759
  }
@@ -15000,8 +15049,8 @@ import { existsSync as existsSync39, mkdirSync as mkdirSync11, readFileSync as r
15000
15049
  import { basename as basename9, dirname as dirname21, join as join39 } from "path";
15001
15050
 
15002
15051
  // src/commands/transcript/cleanText.ts
15003
- function cleanText(text) {
15004
- const words = text.split(/\s+/);
15052
+ function cleanText(text2) {
15053
+ const words = text2.split(/\s+/);
15005
15054
  const cleaned = [];
15006
15055
  for (let i = 0; i < words.length; i++) {
15007
15056
  let isRepeat = false;
@@ -15021,8 +15070,8 @@ function cleanText(text) {
15021
15070
  }
15022
15071
 
15023
15072
  // src/commands/transcript/format/processVttFile/parseVtt/deduplicateCues/removeSubstringDuplicates.ts
15024
- function normalizeText(text) {
15025
- return text.toLowerCase().trim();
15073
+ function normalizeText(text2) {
15074
+ return text2.toLowerCase().trim();
15026
15075
  }
15027
15076
  function checkSubstringRelation(textI, textJ) {
15028
15077
  if (textI.includes(textJ) && textI.length > textJ.length)
@@ -15151,13 +15200,13 @@ function parseTimestampLine(line) {
15151
15200
  return { startMs: parseTimestamp(startStr), endMs: parseTimestamp(endStr) };
15152
15201
  }
15153
15202
  function buildCue(startMs, endMs, fullText) {
15154
- const { speaker, text } = extractSpeaker(fullText);
15155
- return text ? { startMs, endMs, speaker, text } : null;
15203
+ const { speaker, text: text2 } = extractSpeaker(fullText);
15204
+ return text2 ? { startMs, endMs, speaker, text: text2 } : null;
15156
15205
  }
15157
15206
  function parseCueLine(lines, i) {
15158
15207
  const { startMs, endMs } = parseTimestampLine(lines[i]);
15159
- const { text, nextIndex } = collectTextLines(lines, i + 1);
15160
- return { cue: buildCue(startMs, endMs, text), nextIndex };
15208
+ const { text: text2, nextIndex } = collectTextLines(lines, i + 1);
15209
+ return { cue: buildCue(startMs, endMs, text2), nextIndex };
15161
15210
  }
15162
15211
  function isCueSeparator(line) {
15163
15212
  return line.trim().includes("-->");
@@ -15521,13 +15570,13 @@ function logs(options2) {
15521
15570
  console.log("No voice log file found");
15522
15571
  return;
15523
15572
  }
15524
- const count = Number.parseInt(options2.lines ?? "150", 10);
15573
+ const count4 = Number.parseInt(options2.lines ?? "150", 10);
15525
15574
  const content = readFileSync33(voicePaths.log, "utf-8").trim();
15526
15575
  if (!content) {
15527
15576
  console.log("Voice log is empty");
15528
15577
  return;
15529
15578
  }
15530
- const lines = content.split("\n").slice(-count);
15579
+ const lines = content.split("\n").slice(-count4);
15531
15580
  for (const line of lines) {
15532
15581
  try {
15533
15582
  const event = JSON.parse(line);
@@ -15674,10 +15723,10 @@ function isProcessAlive3(pid) {
15674
15723
  return false;
15675
15724
  }
15676
15725
  }
15677
- function readRecentLogs(count) {
15726
+ function readRecentLogs(count4) {
15678
15727
  if (!existsSync45(voicePaths.log)) return [];
15679
15728
  const lines = readFileSync35(voicePaths.log, "utf-8").trim().split("\n");
15680
- return lines.slice(-count);
15729
+ return lines.slice(-count4);
15681
15730
  }
15682
15731
  function status() {
15683
15732
  if (!existsSync45(voicePaths.pid)) {
@@ -15869,8 +15918,8 @@ async function exchangeToken(params) {
15869
15918
  body: body.toString()
15870
15919
  });
15871
15920
  if (!response.ok) {
15872
- const text = await response.text();
15873
- throw new Error(`Token exchange failed (${response.status}): ${text}`);
15921
+ const text2 = await response.text();
15922
+ throw new Error(`Token exchange failed (${response.status}): ${text2}`);
15874
15923
  }
15875
15924
  return response.json();
15876
15925
  }
@@ -16184,11 +16233,11 @@ import { join as join49 } from "path";
16184
16233
 
16185
16234
  // src/commands/run/extractOption.ts
16186
16235
  function extractOption(args, flag) {
16187
- const index = args.indexOf(flag);
16188
- if (index === -1) return { value: void 0, remaining: args };
16236
+ const index2 = args.indexOf(flag);
16237
+ if (index2 === -1) return { value: void 0, remaining: args };
16189
16238
  return {
16190
- value: args[index + 1],
16191
- remaining: [...args.slice(0, index), ...args.slice(index + 2)]
16239
+ value: args[index2 + 1],
16240
+ remaining: [...args.slice(0, index2), ...args.slice(index2 + 2)]
16192
16241
  };
16193
16242
  }
16194
16243
 
@@ -16568,13 +16617,13 @@ function* iterateUserMessages(filePath, maxBytes = 65536) {
16568
16617
 
16569
16618
  // src/commands/sessions/summarise/extractFirstUserMessage.ts
16570
16619
  function extractFirstUserMessage(filePath) {
16571
- for (const text of iterateUserMessages(filePath)) {
16572
- return truncate3(text);
16620
+ for (const text2 of iterateUserMessages(filePath)) {
16621
+ return truncate3(text2);
16573
16622
  }
16574
16623
  return void 0;
16575
16624
  }
16576
- function truncate3(text, maxChars = 500) {
16577
- const trimmed = text.trim();
16625
+ function truncate3(text2, maxChars = 500) {
16626
+ const trimmed = text2.trim();
16578
16627
  if (trimmed.length <= maxChars) return trimmed;
16579
16628
  return `${trimmed.slice(0, maxChars)}\u2026`;
16580
16629
  }
@@ -16582,28 +16631,28 @@ function truncate3(text, maxChars = 500) {
16582
16631
  // src/commands/sessions/summarise/scanSessionBacklogRefs.ts
16583
16632
  function scanSessionBacklogRefs(filePath) {
16584
16633
  const ids = /* @__PURE__ */ new Set();
16585
- for (const text of iterateUserMessages(filePath, Number.MAX_SAFE_INTEGER)) {
16586
- for (const id of extractBacklogIds(text)) {
16634
+ for (const text2 of iterateUserMessages(filePath, Number.MAX_SAFE_INTEGER)) {
16635
+ for (const id of extractBacklogIds(text2)) {
16587
16636
  ids.add(id);
16588
16637
  }
16589
16638
  }
16590
16639
  return [...ids].sort((a, b) => a - b);
16591
16640
  }
16592
- function extractBacklogIds(text) {
16641
+ function extractBacklogIds(text2) {
16593
16642
  const ids = [];
16594
- for (const m of text.matchAll(/backlog\s+run\s+(\d+)/gi)) {
16643
+ for (const m of text2.matchAll(/backlog\s+run\s+(\d+)/gi)) {
16595
16644
  ids.push(Number.parseInt(m[1], 10));
16596
16645
  }
16597
- for (const m of text.matchAll(/backlog\s+(?:item\s+)?#(\d+)/gi)) {
16646
+ for (const m of text2.matchAll(/backlog\s+(?:item\s+)?#(\d+)/gi)) {
16598
16647
  ids.push(Number.parseInt(m[1], 10));
16599
16648
  }
16600
- for (const m of text.matchAll(/backlog\s+phase-done\s+(\d+)/gi)) {
16649
+ for (const m of text2.matchAll(/backlog\s+phase-done\s+(\d+)/gi)) {
16601
16650
  ids.push(Number.parseInt(m[1], 10));
16602
16651
  }
16603
- for (const m of text.matchAll(/backlog\s+comment\s+(\d+)/gi)) {
16652
+ for (const m of text2.matchAll(/backlog\s+comment\s+(\d+)/gi)) {
16604
16653
  ids.push(Number.parseInt(m[1], 10));
16605
16654
  }
16606
- for (const m of text.matchAll(/(?:^|[\s(])#(\d{1,4})(?=[\s).,;:!?]|$)/gm)) {
16655
+ for (const m of text2.matchAll(/(?:^|[\s(])#(\d{1,4})(?=[\s).,;:!?]|$)/gm)) {
16607
16656
  ids.push(Number.parseInt(m[1], 10));
16608
16657
  }
16609
16658
  return ids;