@staff0rd/assist 0.230.0 → 0.231.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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +191 -246
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -235,7 +235,7 @@ The first backlog command in a repository that still has a local `.assist/backlo
235
235
  - `assist sessions` - Start the web dashboard (same as `sessions web`)
236
236
  - `assist sessions web [-p, --port <number>]` - Start the web dashboard with Sessions and Backlog tabs, xterm.js terminals (default port 3100)
237
237
  - `assist sessions summarise [-f, --force] [-n, --limit <count>]` - Generate one-line summaries for unsummarised Claude sessions (force re-generates all; limit caps how many to process)
238
- - `assist next` - Alias for `backlog next -w`
238
+ - `assist next` - Alias for `backlog next`
239
239
  - `assist draft` (alias: `feat`) - Launch Claude in `/draft` mode, chain into next on `/next` signal
240
240
  - `assist bug` - Launch Claude in `/bug` mode, chain into next on `/next` signal
241
241
  - `assist refine [id]` - Launch Claude in `/refine` mode to refine a backlog item; prompts for selection when no id given
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.230.0",
9
+ version: "0.231.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -1058,57 +1058,28 @@ function getCurrentOrigin(cwd) {
1058
1058
  return `local:${root ?? cwd}`;
1059
1059
  }
1060
1060
 
1061
- // src/commands/backlog/saveAllItems.ts
1062
- import { eq as eq4, sql } from "drizzle-orm";
1063
-
1064
- // src/commands/backlog/deleteItemRelations.ts
1061
+ // src/commands/backlog/loadItem.ts
1065
1062
  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));
1063
+ async function loadItem(orm, id) {
1064
+ const [row] = await orm.select().from(items).where(eq3(items.id, id));
1065
+ if (!row) return void 0;
1066
+ const rel = await loadRelations(orm, [id]);
1067
+ return rowToItem(row, rel);
1071
1068
  }
1072
1069
 
1073
1070
  // src/commands/backlog/saveAllItems.ts
1074
- async function upsertItem(db, item, origin) {
1075
- const values = itemColumns(item, origin);
1076
- await db.insert(items).values({ id: item.id, ...values }).onConflictDoUpdate({ target: items.id, set: values });
1077
- await deleteItemRelations(db, item.id);
1078
- await insertItemRelations(db, item);
1079
- }
1080
- async function resyncSequences(db) {
1081
- await db.execute(
1082
- sql`SELECT setval(pg_get_serial_sequence('items', 'id'), COALESCE((SELECT MAX(id) FROM items), 0) + 1, false)`
1083
- );
1084
- await db.execute(
1085
- sql`SELECT setval(pg_get_serial_sequence('comments', 'id'), COALESCE((SELECT MAX(id) FROM comments), 0) + 1, false)`
1086
- );
1087
- }
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));
1092
- for (const { id } of existingIds) {
1093
- if (!newIds.has(id)) {
1094
- await deleteItemRelations(tx, id);
1095
- await tx.delete(items).where(eq4(items.id, id));
1096
- }
1097
- }
1098
- for (const item of file) {
1099
- await upsertItem(tx, item, origin);
1100
- }
1101
- await resyncSequences(tx);
1102
- });
1103
- }
1071
+ import { eq as eq5, sql } from "drizzle-orm";
1072
+
1073
+ // src/commands/backlog/deleteItemRelations.ts
1074
+ import { eq as eq4 } from "drizzle-orm";
1104
1075
 
1105
1076
  // src/commands/backlog/searchItemIds.ts
1106
- import { and, asc as asc3, eq as eq5, ilike, or } from "drizzle-orm";
1077
+ import { and, asc as asc3, eq as eq6, ilike, or } from "drizzle-orm";
1107
1078
  async function searchItemIds(orm, query, origin) {
1108
1079
  const pattern2 = `%${query}%`;
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(
1080
+ const rows = await orm.selectDistinct({ id: items.id }).from(items).leftJoin(comments, eq6(comments.itemId, items.id)).leftJoin(planPhases, eq6(planPhases.itemId, items.id)).where(
1110
1081
  and(
1111
- origin ? eq5(items.origin, origin) : void 0,
1082
+ origin ? eq6(items.origin, origin) : void 0,
1112
1083
  or(
1113
1084
  ilike(items.name, pattern2),
1114
1085
  ilike(items.description, pattern2),
@@ -1122,15 +1093,15 @@ async function searchItemIds(orm, query, origin) {
1122
1093
  }
1123
1094
 
1124
1095
  // src/commands/backlog/updateCurrentPhase.ts
1125
- import { eq as eq6 } from "drizzle-orm";
1096
+ import { eq as eq7 } from "drizzle-orm";
1126
1097
  async function updateCurrentPhase(orm, id, phase) {
1127
- await orm.update(items).set({ currentPhase: phase }).where(eq6(items.id, id));
1098
+ await orm.update(items).set({ currentPhase: phase }).where(eq7(items.id, id));
1128
1099
  }
1129
1100
 
1130
1101
  // src/commands/backlog/updateStatus.ts
1131
- import { eq as eq7 } from "drizzle-orm";
1102
+ import { eq as eq8 } from "drizzle-orm";
1132
1103
  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 });
1104
+ const [row] = await orm.update(items).set({ status: status2 }).where(eq8(items.id, id)).returning({ name: items.name });
1134
1105
  return row?.name;
1135
1106
  }
1136
1107
 
@@ -1162,21 +1133,15 @@ async function searchBacklog(query) {
1162
1133
  const allItems = await loadAllItems(orm, origin);
1163
1134
  return allItems.filter((item) => ids.includes(item.id));
1164
1135
  }
1165
- async function saveBacklog(items2) {
1136
+ async function findOneItem(id) {
1137
+ const numId = Number.parseInt(id, 10);
1166
1138
  const { orm } = await getReady();
1167
- await saveAllItems(orm, items2, getOrigin());
1168
- }
1169
- function findItem(items2, id) {
1170
- return items2.find((item) => item.id === id);
1171
- }
1172
- async function loadAndFindItem(id) {
1173
- const items2 = await loadBacklog();
1174
- const item = findItem(items2, Number.parseInt(id, 10));
1139
+ const item = Number.isNaN(numId) ? void 0 : await loadItem(orm, numId);
1175
1140
  if (!item) {
1176
1141
  console.log(chalk6.red(`Item #${id} not found.`));
1177
1142
  return void 0;
1178
1143
  }
1179
- return { items: items2, item };
1144
+ return { orm, item };
1180
1145
  }
1181
1146
  async function setStatus(id, status2) {
1182
1147
  const { orm } = await getReady();
@@ -1330,9 +1295,9 @@ function resolvePlan(item) {
1330
1295
 
1331
1296
  // src/commands/backlog/prepareRun.ts
1332
1297
  async function prepareRun(id) {
1333
- const result = await loadAndFindItem(id);
1334
- if (!result) return void 0;
1335
- const { item } = result;
1298
+ const found = await findOneItem(id);
1299
+ if (!found) return void 0;
1300
+ const { item } = found;
1336
1301
  const plan2 = resolvePlan(item);
1337
1302
  const startPhase = (item.currentPhase ?? 1) - 1;
1338
1303
  if (item.status === "done") {
@@ -1596,9 +1561,9 @@ async function executePhase(item, phaseIndex, phases, spawnOptions) {
1596
1561
 
1597
1562
  // src/commands/backlog/reloadPlan.ts
1598
1563
  async function reloadPlan(id) {
1599
- const result = await loadAndFindItem(String(id));
1600
- if (!result) return void 0;
1601
- return resolvePlan(result.item);
1564
+ const found = await findOneItem(String(id));
1565
+ if (!found) return void 0;
1566
+ return resolvePlan(found.item);
1602
1567
  }
1603
1568
 
1604
1569
  // src/commands/backlog/runPhases.ts
@@ -1750,9 +1715,9 @@ async function appendComment(orm, itemId, text2, opts = {}) {
1750
1715
  }
1751
1716
 
1752
1717
  // src/commands/backlog/getItemStatus.ts
1753
- import { eq as eq8 } from "drizzle-orm";
1718
+ import { eq as eq9 } from "drizzle-orm";
1754
1719
  async function getItemStatus(orm, id) {
1755
- const [row] = await orm.select({ status: items.status }).from(items).where(eq8(items.id, id));
1720
+ const [row] = await orm.select({ status: items.status }).from(items).where(eq9(items.id, id));
1756
1721
  return row?.status;
1757
1722
  }
1758
1723
 
@@ -1789,9 +1754,9 @@ async function phaseDone(id, phase, summary) {
1789
1754
  // src/commands/backlog/plan.ts
1790
1755
  import chalk15 from "chalk";
1791
1756
  async function plan(id) {
1792
- const result = await loadAndFindItem(id);
1793
- if (!result) return;
1794
- const { item } = result;
1757
+ const found = await findOneItem(id);
1758
+ if (!found) return;
1759
+ const { item } = found;
1795
1760
  if (!item.plan || item.plan.length === 0) {
1796
1761
  console.log(chalk15.dim("No plan defined for this item."));
1797
1762
  return;
@@ -1823,12 +1788,30 @@ function formatComment(entry) {
1823
1788
 
1824
1789
  // src/commands/backlog/show/printLinks.ts
1825
1790
  import chalk17 from "chalk";
1826
- function printLinks(item, items2) {
1791
+
1792
+ // src/commands/backlog/show/loadLinkTargets.ts
1793
+ import { inArray as inArray2 } from "drizzle-orm";
1794
+ async function loadLinkTargets(orm, ids) {
1795
+ if (ids.length === 0) return [];
1796
+ const rows = await orm.select({ id: items.id, name: items.name, status: items.status }).from(items).where(inArray2(items.id, ids));
1797
+ return rows.map((r) => ({
1798
+ id: r.id,
1799
+ name: r.name,
1800
+ status: r.status
1801
+ }));
1802
+ }
1803
+
1804
+ // src/commands/backlog/show/printLinks.ts
1805
+ async function printLinks(orm, item) {
1827
1806
  const links2 = item.links ?? [];
1828
1807
  if (links2.length === 0) return;
1808
+ const targets = await loadLinkTargets(
1809
+ orm,
1810
+ links2.map((l) => l.targetId)
1811
+ );
1829
1812
  console.log(chalk17.bold("Links"));
1830
1813
  for (const link3 of links2) {
1831
- const target = items2.find((i) => i.id === link3.targetId);
1814
+ const target = targets.find((i) => i.id === link3.targetId);
1832
1815
  const typeLabel2 = link3.type === "depends-on" ? chalk17.red("depends-on") : chalk17.blue("relates-to");
1833
1816
  if (target) {
1834
1817
  console.log(
@@ -1893,9 +1876,9 @@ function printAcceptanceCriteria(criteria) {
1893
1876
  console.log();
1894
1877
  }
1895
1878
  async function show(id) {
1896
- const result = await loadAndFindItem(id);
1897
- if (!result) process.exit(1);
1898
- const { item, items: items2 } = result;
1879
+ const found = await findOneItem(id);
1880
+ if (!found) process.exit(1);
1881
+ const { orm, item } = found;
1899
1882
  printHeader(item);
1900
1883
  if (item.description) {
1901
1884
  console.log(chalk19.bold("Description"));
@@ -1903,7 +1886,7 @@ async function show(id) {
1903
1886
  console.log();
1904
1887
  }
1905
1888
  printAcceptanceCriteria(item.acceptanceCriteria);
1906
- printLinks(item, items2);
1889
+ await printLinks(orm, item);
1907
1890
  printPlan(item);
1908
1891
  printComments(item);
1909
1892
  }
@@ -2051,66 +2034,8 @@ async function createItem(req, res) {
2051
2034
  respondJson(res, 201, { id, ...newItem });
2052
2035
  }
2053
2036
 
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
-
2076
2037
  // src/commands/backlog/web/rewindItemPhase.ts
2077
- async function rewindItemPhase(req, res, id) {
2078
- const { phase, reason } = await parseRewindBody(req);
2079
- const items2 = await loadBacklog();
2080
- const item = items2.find((i) => i.id === id);
2081
- if (!item) {
2082
- respondJson(res, 404, { error: "Not found" });
2083
- return;
2084
- }
2085
- const error = validateRewind(item, phase);
2086
- if (error) {
2087
- respondJson(res, 400, { error });
2088
- return;
2089
- }
2090
- const phaseName = item.plan?.[phase - 1].name;
2091
- addComment(
2092
- item,
2093
- `Rewound to phase ${phase} (${phaseName}): ${reason}`,
2094
- phase
2095
- );
2096
- item.currentPhase = phase;
2097
- item.status = "in-progress";
2098
- await saveBacklog(items2);
2099
- respondJson(res, 200, item);
2100
- }
2101
- function validateRewind(item, phase) {
2102
- if (!item.plan || item.plan.length === 0) {
2103
- return "Item has no plan phases.";
2104
- }
2105
- if (phase < 1 || phase > item.plan.length) {
2106
- return `Phase ${phase} does not exist. Valid range: 1\u2013${item.plan.length}.`;
2107
- }
2108
- const currentPhase = item.currentPhase ?? 1;
2109
- if (phase >= currentPhase) {
2110
- return `Phase ${phase} is not earlier than the current phase (${currentPhase}).`;
2111
- }
2112
- return void 0;
2113
- }
2038
+ import { eq as eq10 } from "drizzle-orm";
2114
2039
 
2115
2040
  // src/commands/backlog/web/shared.ts
2116
2041
  async function listItems(req, res) {
@@ -2119,13 +2044,13 @@ async function listItems(req, res) {
2119
2044
  respondJson(res, 200, q ? await searchBacklog(q) : await loadBacklog());
2120
2045
  }
2121
2046
  async function findItemOr404(res, id) {
2122
- const items2 = await loadBacklog();
2123
- const item = items2.find((i) => i.id === id);
2047
+ const { orm } = await getReady();
2048
+ const item = await loadItem(orm, id);
2124
2049
  if (!item) {
2125
2050
  respondJson(res, 404, { error: "Not found" });
2126
2051
  return void 0;
2127
2052
  }
2128
- return { items: items2, item };
2053
+ return { orm, item };
2129
2054
  }
2130
2055
  async function getItemById(res, id) {
2131
2056
  const result = await findItemOr404(res, id);
@@ -2134,31 +2059,67 @@ async function getItemById(res, id) {
2134
2059
  async function deleteItem2(res, id) {
2135
2060
  const result = await findItemOr404(res, id);
2136
2061
  if (!result) return;
2137
- await saveBacklog(result.items.filter((i) => i.id !== id));
2062
+ await deleteItem(result.orm, id);
2138
2063
  respondJson(res, 200, result.item);
2139
2064
  }
2140
2065
  async function patchItemStatus(req, res, id) {
2141
2066
  const { status: status2 } = await parseStatusBody(req);
2142
2067
  const result = await findItemOr404(res, id);
2143
2068
  if (!result) return;
2144
- result.item.status = status2;
2145
- await saveBacklog(result.items);
2146
- respondJson(res, 200, result.item);
2069
+ await updateStatus(result.orm, id, status2);
2070
+ const updated = { ...result.item, status: status2 };
2071
+ respondJson(res, 200, updated);
2072
+ }
2073
+
2074
+ // src/commands/backlog/web/rewindItemPhase.ts
2075
+ async function rewindItemPhase(req, res, id) {
2076
+ const { phase, reason } = await parseRewindBody(req);
2077
+ const result = await findItemOr404(res, id);
2078
+ if (!result) return;
2079
+ const { orm, item } = result;
2080
+ const error = validateRewind(item, phase);
2081
+ if (error) {
2082
+ respondJson(res, 400, { error });
2083
+ return;
2084
+ }
2085
+ const phaseName = item.plan?.[phase - 1].name;
2086
+ await appendComment(
2087
+ orm,
2088
+ id,
2089
+ `Rewound to phase ${phase} (${phaseName}): ${reason}`,
2090
+ { phase }
2091
+ );
2092
+ await orm.update(items).set({ currentPhase: phase, status: "in-progress" }).where(eq10(items.id, id));
2093
+ respondJson(res, 200, await loadItem(orm, id));
2094
+ }
2095
+ function validateRewind(item, phase) {
2096
+ if (!item.plan || item.plan.length === 0) {
2097
+ return "Item has no plan phases.";
2098
+ }
2099
+ if (phase < 1 || phase > item.plan.length) {
2100
+ return `Phase ${phase} does not exist. Valid range: 1\u2013${item.plan.length}.`;
2101
+ }
2102
+ const currentPhase = item.currentPhase ?? 1;
2103
+ if (phase >= currentPhase) {
2104
+ return `Phase ${phase} is not earlier than the current phase (${currentPhase}).`;
2105
+ }
2106
+ return void 0;
2147
2107
  }
2148
2108
 
2149
2109
  // src/commands/backlog/web/updateItem.ts
2110
+ import { eq as eq11 } from "drizzle-orm";
2150
2111
  async function updateItem(req, res, id) {
2151
2112
  const body = await parseItemBody(req);
2152
2113
  const result = await findItemOr404(res, id);
2153
2114
  if (!result) return;
2154
- Object.assign(result.item, {
2115
+ const { orm } = result;
2116
+ await orm.update(items).set({
2155
2117
  type: body.type ?? result.item.type,
2156
2118
  name: body.name,
2157
- description: body.description,
2158
- acceptanceCriteria: body.acceptanceCriteria ?? []
2159
- });
2160
- await saveBacklog(result.items);
2161
- respondJson(res, 200, result.item);
2119
+ description: body.description ?? null,
2120
+ acceptanceCriteria: JSON.stringify(body.acceptanceCriteria ?? [])
2121
+ }).where(eq11(items.id, id));
2122
+ respondJson(res, 200, await loadItem(orm, id));
2162
2123
  }
2163
2124
 
2164
2125
  // src/commands/backlog/web/handleItemRoute.ts
@@ -4974,19 +4935,18 @@ function registerActivity(program2) {
4974
4935
  // src/commands/backlog/comment/index.ts
4975
4936
  import chalk46 from "chalk";
4976
4937
  async function comment(id, text2) {
4977
- const result = await loadAndFindItem(id);
4978
- if (!result) process.exit(1);
4979
- addComment(result.item, text2);
4980
- await saveBacklog(result.items);
4938
+ const found = await findOneItem(id);
4939
+ if (!found) process.exit(1);
4940
+ await appendComment(found.orm, found.item.id, text2);
4981
4941
  console.log(chalk46.green(`Comment added to item #${id}.`));
4982
4942
  }
4983
4943
 
4984
4944
  // src/commands/backlog/comments/index.ts
4985
4945
  import chalk47 from "chalk";
4986
4946
  async function comments2(id) {
4987
- const result = await loadAndFindItem(id);
4988
- if (!result) process.exit(1);
4989
- const { item } = result;
4947
+ const found = await findOneItem(id);
4948
+ if (!found) process.exit(1);
4949
+ const { item } = found;
4990
4950
  const entries = item.comments ?? [];
4991
4951
  if (entries.length === 0) {
4992
4952
  console.log(chalk47.dim(`No comments on item #${id}.`));
@@ -5004,23 +4964,22 @@ async function comments2(id) {
5004
4964
  import chalk48 from "chalk";
5005
4965
 
5006
4966
  // src/commands/backlog/deleteComment.ts
5007
- import { and as and2, eq as eq9 } from "drizzle-orm";
4967
+ import { and as and2, eq as eq12 } from "drizzle-orm";
5008
4968
  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)));
4969
+ const [row] = await orm.select({ type: comments.type }).from(comments).where(and2(eq12(comments.id, commentId), eq12(comments.itemId, itemId)));
5010
4970
  if (!row) return "not-found";
5011
4971
  if (row.type === "summary") return "is-summary";
5012
- await orm.delete(comments).where(and2(eq9(comments.id, commentId), eq9(comments.itemId, itemId)));
4972
+ await orm.delete(comments).where(and2(eq12(comments.id, commentId), eq12(comments.itemId, itemId)));
5013
4973
  return "deleted";
5014
4974
  }
5015
4975
 
5016
4976
  // src/commands/backlog/delete-comment/index.ts
5017
4977
  async function deleteCommentCmd(id, commentId) {
5018
- const result = await loadAndFindItem(id);
5019
- if (!result) process.exit(1);
5020
- const orm = await getBacklogOrm();
4978
+ const found = await findOneItem(id);
4979
+ if (!found) process.exit(1);
5021
4980
  const outcome = await deleteComment(
5022
- orm,
5023
- result.item.id,
4981
+ found.orm,
4982
+ found.item.id,
5024
4983
  Number.parseInt(commentId, 10)
5025
4984
  );
5026
4985
  switch (outcome) {
@@ -5420,15 +5379,15 @@ async function add(options2) {
5420
5379
  import chalk54 from "chalk";
5421
5380
 
5422
5381
  // src/commands/backlog/insertPhaseAt.ts
5423
- import { eq as eq11 } from "drizzle-orm";
5382
+ import { eq as eq14 } from "drizzle-orm";
5424
5383
 
5425
5384
  // src/commands/backlog/shiftPhasesUp.ts
5426
- import { and as and3, desc, eq as eq10, gte } from "drizzle-orm";
5385
+ import { and as and3, desc, eq as eq13, gte } from "drizzle-orm";
5427
5386
  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));
5387
+ const toShift = await db.select({ idx: planPhases.idx }).from(planPhases).where(and3(eq13(planPhases.itemId, itemId), gte(planPhases.idx, fromIdx))).orderBy(desc(planPhases.idx));
5429
5388
  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)));
5389
+ await db.update(planTasks).set({ phaseIdx: p.idx + 1 }).where(and3(eq13(planTasks.itemId, itemId), eq13(planTasks.phaseIdx, p.idx)));
5390
+ await db.update(planPhases).set({ idx: p.idx + 1 }).where(and3(eq13(planPhases.itemId, itemId), eq13(planPhases.idx, p.idx)));
5432
5391
  }
5433
5392
  }
5434
5393
 
@@ -5441,16 +5400,16 @@ async function insertPhaseAt(orm, itemId, phaseIdx, name, tasks, manualChecks, c
5441
5400
  await tx.insert(planTasks).values(tasks.map((task, i) => ({ itemId, phaseIdx, idx: i, task })));
5442
5401
  }
5443
5402
  if (currentPhase !== void 0 && currentPhase - 1 >= phaseIdx) {
5444
- await tx.update(items).set({ currentPhase: currentPhase + 1 }).where(eq11(items.id, itemId));
5403
+ await tx.update(items).set({ currentPhase: currentPhase + 1 }).where(eq14(items.id, itemId));
5445
5404
  }
5446
5405
  });
5447
5406
  }
5448
5407
 
5449
5408
  // src/commands/backlog/resolveInsertPosition.ts
5450
5409
  import chalk53 from "chalk";
5451
- import { count, eq as eq12 } from "drizzle-orm";
5410
+ import { count, eq as eq15 } from "drizzle-orm";
5452
5411
  async function resolveInsertPosition(orm, itemId, position) {
5453
- const [row] = await orm.select({ cnt: count() }).from(planPhases).where(eq12(planPhases.itemId, itemId));
5412
+ const [row] = await orm.select({ cnt: count() }).from(planPhases).where(eq15(planPhases.itemId, itemId));
5454
5413
  const phaseCount = row?.cnt ?? 0;
5455
5414
  if (position === void 0) return phaseCount;
5456
5415
  const pos = Number.parseInt(position, 10);
@@ -5473,16 +5432,16 @@ function serializeManualChecks(manualCheck) {
5473
5432
 
5474
5433
  // src/commands/backlog/addPhase.ts
5475
5434
  async function addPhase(id, name, options2) {
5476
- const result = await loadAndFindItem(id);
5477
- if (!result) return;
5435
+ const found = await findOneItem(id);
5436
+ if (!found) return;
5478
5437
  const tasks = options2.task ?? [];
5479
5438
  if (tasks.length === 0) {
5480
5439
  console.log(chalk54.red("At least one --task is required."));
5481
5440
  process.exitCode = 1;
5482
5441
  return;
5483
5442
  }
5484
- const orm = await getBacklogOrm();
5485
- const itemId = result.item.id;
5443
+ const { orm } = found;
5444
+ const itemId = found.item.id;
5486
5445
  const phaseIdx = await resolveInsertPosition(orm, itemId, options2.position);
5487
5446
  if (phaseIdx === void 0) return;
5488
5447
  await insertPhaseAt(
@@ -5492,7 +5451,7 @@ async function addPhase(id, name, options2) {
5492
5451
  name,
5493
5452
  tasks,
5494
5453
  serializeManualChecks(options2.manualCheck),
5495
- result.item.currentPhase
5454
+ found.item.currentPhase
5496
5455
  );
5497
5456
  const verb = options2.position !== void 0 ? "Inserted" : "Added";
5498
5457
  console.log(
@@ -5622,9 +5581,9 @@ function hasCycle(adjacency, fromId, toId) {
5622
5581
  }
5623
5582
 
5624
5583
  // src/commands/backlog/loadDependencyGraph.ts
5625
- import { eq as eq13 } from "drizzle-orm";
5584
+ import { eq as eq16 } from "drizzle-orm";
5626
5585
  async function loadDependencyGraph(orm) {
5627
- const rows = await orm.select({ itemId: links.itemId, targetId: links.targetId }).from(links).where(eq13(links.type, "depends-on"));
5586
+ const rows = await orm.select({ itemId: links.itemId, targetId: links.targetId }).from(links).where(eq16(links.type, "depends-on"));
5628
5587
  const graph = /* @__PURE__ */ new Map();
5629
5588
  for (const { itemId, targetId } of rows) {
5630
5589
  const bucket = graph.get(itemId);
@@ -5634,15 +5593,6 @@ async function loadDependencyGraph(orm) {
5634
5593
  return graph;
5635
5594
  }
5636
5595
 
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
-
5646
5596
  // src/commands/backlog/validateLinkTarget.ts
5647
5597
  import chalk57 from "chalk";
5648
5598
  function validateLinkTarget(fromItem, fromNum, toNum, linkType) {
@@ -5696,7 +5646,7 @@ async function link(fromId, toId, opts) {
5696
5646
 
5697
5647
  // src/commands/backlog/unlink.ts
5698
5648
  import chalk59 from "chalk";
5699
- import { and as and4, eq as eq15 } from "drizzle-orm";
5649
+ import { and as and4, eq as eq17 } from "drizzle-orm";
5700
5650
  async function unlink(fromId, toId) {
5701
5651
  const fromNum = Number.parseInt(fromId, 10);
5702
5652
  const toNum = Number.parseInt(toId, 10);
@@ -5714,7 +5664,7 @@ async function unlink(fromId, toId) {
5714
5664
  console.log(chalk59.yellow(`No link from #${fromId} to #${toId} found.`));
5715
5665
  return;
5716
5666
  }
5717
- await orm.delete(links).where(and4(eq15(links.itemId, fromNum), eq15(links.targetId, toNum)));
5667
+ await orm.delete(links).where(and4(eq17(links.itemId, fromNum), eq17(links.targetId, toNum)));
5718
5668
  console.log(chalk59.green(`Removed link from #${fromId} to #${toId}.`));
5719
5669
  }
5720
5670
 
@@ -5789,8 +5739,8 @@ function registerRewindCommand(cmd) {
5789
5739
 
5790
5740
  // src/commands/backlog/registerRunCommand.ts
5791
5741
  function registerRunCommand(cmd) {
5792
- cmd.command("run <id>").description("Run a backlog item's plan phase-by-phase with Claude").option("-w, --write", "Run Claude with auto permission mode").action(async (id, opts) => {
5793
- await run(id, { allowEdits: opts.write });
5742
+ cmd.command("run <id>").description("Run a backlog item's plan phase-by-phase with Claude").option("-w, --write", "Run Claude with auto permission mode (default)").option("--no-write", "Run Claude without auto permission mode").action(async (id, opts) => {
5743
+ await run(id, { allowEdits: opts.write !== false });
5794
5744
  });
5795
5745
  }
5796
5746
 
@@ -5832,9 +5782,9 @@ async function del(id) {
5832
5782
  // src/commands/backlog/done/index.ts
5833
5783
  import chalk63 from "chalk";
5834
5784
  async function done(id, summary) {
5835
- const result = await loadAndFindItem(id);
5836
- if (!result) return;
5837
- const { item } = result;
5785
+ const found = await findOneItem(id);
5786
+ if (!found) return;
5787
+ const { orm, item } = found;
5838
5788
  if (item.plan && item.plan.length > 0) {
5839
5789
  const completedCount = (item.currentPhase ?? 1) - 1;
5840
5790
  const pending = item.plan.slice(completedCount);
@@ -5851,12 +5801,11 @@ async function done(id, summary) {
5851
5801
  return;
5852
5802
  }
5853
5803
  }
5854
- item.status = "done";
5804
+ await updateStatus(orm, item.id, "done");
5855
5805
  if (summary) {
5856
5806
  const phase = item.currentPhase ?? 1;
5857
- addPhaseSummary(item, summary, phase);
5807
+ await appendComment(orm, item.id, summary, { phase, type: "summary" });
5858
5808
  }
5859
- await saveBacklog(result.items);
5860
5809
  console.log(chalk63.green(`Completed item #${id}: ${item.name}`));
5861
5810
  }
5862
5811
 
@@ -5871,19 +5820,15 @@ async function start(id) {
5871
5820
 
5872
5821
  // src/commands/backlog/stop/index.ts
5873
5822
  import chalk65 from "chalk";
5823
+ import { and as and5, eq as eq18 } from "drizzle-orm";
5874
5824
  async function stop() {
5875
- const items2 = await loadBacklog();
5876
- const inProgress = items2.filter((item) => item.status === "in-progress");
5877
- if (inProgress.length === 0) {
5825
+ const { orm } = await getReady();
5826
+ const stopped = await orm.update(items).set({ status: "todo", currentPhase: 1 }).where(and5(eq18(items.status, "in-progress"), eq18(items.origin, getOrigin()))).returning({ id: items.id, name: items.name });
5827
+ if (stopped.length === 0) {
5878
5828
  console.log(chalk65.yellow("No in-progress items to stop."));
5879
5829
  return;
5880
5830
  }
5881
- for (const item of inProgress) {
5882
- item.status = "todo";
5883
- item.currentPhase = 1;
5884
- }
5885
- await saveBacklog(items2);
5886
- for (const item of inProgress) {
5831
+ for (const item of stopped) {
5887
5832
  console.log(chalk65.yellow(`Stopped item #${item.id}: ${item.name}`));
5888
5833
  }
5889
5834
  }
@@ -5891,15 +5836,15 @@ async function stop() {
5891
5836
  // src/commands/backlog/wontdo/index.ts
5892
5837
  import chalk66 from "chalk";
5893
5838
  async function wontdo(id, reason) {
5894
- const result = await loadAndFindItem(id);
5895
- if (!result) return;
5896
- result.item.status = "wontdo";
5839
+ const found = await findOneItem(id);
5840
+ if (!found) return;
5841
+ const { orm, item } = found;
5842
+ await updateStatus(orm, item.id, "wontdo");
5897
5843
  if (reason) {
5898
- const phase = result.item.currentPhase ?? 1;
5899
- addPhaseSummary(result.item, reason, phase);
5844
+ const phase = item.currentPhase ?? 1;
5845
+ await appendComment(orm, item.id, reason, { phase, type: "summary" });
5900
5846
  }
5901
- await saveBacklog(result.items);
5902
- console.log(chalk66.red(`Won't do item #${id}: ${result.item.name}`));
5847
+ console.log(chalk66.red(`Won't do item #${id}: ${item.name}`));
5903
5848
  }
5904
5849
 
5905
5850
  // src/commands/backlog/registerStatusCommands.ts
@@ -5913,18 +5858,18 @@ function registerStatusCommands(cmd) {
5913
5858
 
5914
5859
  // src/commands/backlog/removePhase.ts
5915
5860
  import chalk68 from "chalk";
5916
- import { and as and7, eq as eq18 } from "drizzle-orm";
5861
+ import { and as and8, eq as eq21 } from "drizzle-orm";
5917
5862
 
5918
5863
  // src/commands/backlog/findPhase.ts
5919
5864
  import chalk67 from "chalk";
5920
- import { and as and5, count as count2, eq as eq16 } from "drizzle-orm";
5865
+ import { and as and6, count as count2, eq as eq19 } from "drizzle-orm";
5921
5866
  async function findPhase(id, phase) {
5922
- const result = await loadAndFindItem(id);
5923
- if (!result) return void 0;
5924
- const orm = await getBacklogOrm();
5925
- const itemId = result.item.id;
5867
+ const found = await findOneItem(id);
5868
+ if (!found) return void 0;
5869
+ const { orm, item } = found;
5870
+ const itemId = item.id;
5926
5871
  const phaseIdx = Number.parseInt(phase, 10) - 1;
5927
- const [row] = await orm.select({ cnt: count2() }).from(planPhases).where(and5(eq16(planPhases.itemId, itemId), eq16(planPhases.idx, phaseIdx)));
5872
+ const [row] = await orm.select({ cnt: count2() }).from(planPhases).where(and6(eq19(planPhases.itemId, itemId), eq19(planPhases.idx, phaseIdx)));
5928
5873
  if (!row || row.cnt === 0) {
5929
5874
  console.log(
5930
5875
  chalk67.red(`Phase ${phaseIdx + 1} not found on item #${itemId}.`)
@@ -5932,18 +5877,18 @@ async function findPhase(id, phase) {
5932
5877
  process.exitCode = 1;
5933
5878
  return void 0;
5934
5879
  }
5935
- return { result, orm, itemId, phaseIdx };
5880
+ return { item, orm, itemId, phaseIdx };
5936
5881
  }
5937
5882
 
5938
5883
  // src/commands/backlog/reindexPhases.ts
5939
- import { and as and6, asc as asc4, count as count3, eq as eq17 } from "drizzle-orm";
5884
+ import { and as and7, asc as asc4, count as count3, eq as eq20 } from "drizzle-orm";
5940
5885
  async function reindexPhases(db, itemId) {
5941
- const remaining = await db.select({ idx: planPhases.idx }).from(planPhases).where(eq17(planPhases.itemId, itemId)).orderBy(asc4(planPhases.idx));
5886
+ const remaining = await db.select({ idx: planPhases.idx }).from(planPhases).where(eq20(planPhases.itemId, itemId)).orderBy(asc4(planPhases.idx));
5942
5887
  for (let i = 0; i < remaining.length; i++) {
5943
5888
  const oldIdx = remaining[i].idx;
5944
5889
  if (oldIdx === i) continue;
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)));
5890
+ await db.update(planTasks).set({ phaseIdx: i }).where(and7(eq20(planTasks.itemId, itemId), eq20(planTasks.phaseIdx, oldIdx)));
5891
+ await db.update(planPhases).set({ idx: i }).where(and7(eq20(planPhases.itemId, itemId), eq20(planPhases.idx, oldIdx)));
5947
5892
  }
5948
5893
  }
5949
5894
  async function adjustCurrentPhase(db, item, removedIdx) {
@@ -5951,27 +5896,27 @@ async function adjustCurrentPhase(db, item, removedIdx) {
5951
5896
  if (currentPhase === void 0) return;
5952
5897
  const currentIdx = currentPhase - 1;
5953
5898
  if (removedIdx < currentIdx) {
5954
- await db.update(items).set({ currentPhase: currentPhase - 1 }).where(eq17(items.id, item.id));
5899
+ await db.update(items).set({ currentPhase: currentPhase - 1 }).where(eq20(items.id, item.id));
5955
5900
  return;
5956
5901
  }
5957
5902
  if (removedIdx !== currentIdx) return;
5958
- const [row] = await db.select({ cnt: count3() }).from(planPhases).where(eq17(planPhases.itemId, item.id));
5903
+ const [row] = await db.select({ cnt: count3() }).from(planPhases).where(eq20(planPhases.itemId, item.id));
5959
5904
  const cnt = row?.cnt ?? 0;
5960
- await db.update(items).set({ currentPhase: cnt === 0 ? null : Math.min(currentPhase, cnt) }).where(eq17(items.id, item.id));
5905
+ await db.update(items).set({ currentPhase: cnt === 0 ? null : Math.min(currentPhase, cnt) }).where(eq20(items.id, item.id));
5961
5906
  }
5962
5907
 
5963
5908
  // src/commands/backlog/removePhase.ts
5964
5909
  async function removePhase(id, phase) {
5965
5910
  const found = await findPhase(id, phase);
5966
5911
  if (!found) return;
5967
- const { result, orm, itemId, phaseIdx } = found;
5912
+ const { item, orm, itemId, phaseIdx } = found;
5968
5913
  await orm.transaction(async (tx) => {
5969
5914
  await tx.delete(planTasks).where(
5970
- and7(eq18(planTasks.itemId, itemId), eq18(planTasks.phaseIdx, phaseIdx))
5915
+ and8(eq21(planTasks.itemId, itemId), eq21(planTasks.phaseIdx, phaseIdx))
5971
5916
  );
5972
- await tx.delete(planPhases).where(and7(eq18(planPhases.itemId, itemId), eq18(planPhases.idx, phaseIdx)));
5917
+ await tx.delete(planPhases).where(and8(eq21(planPhases.itemId, itemId), eq21(planPhases.idx, phaseIdx)));
5973
5918
  await reindexPhases(tx, itemId);
5974
- await adjustCurrentPhase(tx, result.item, phaseIdx);
5919
+ await adjustCurrentPhase(tx, item, phaseIdx);
5975
5920
  });
5976
5921
  console.log(
5977
5922
  chalk68.green(`Removed phase ${phaseIdx + 1} from item #${itemId}.`)
@@ -5980,7 +5925,7 @@ async function removePhase(id, phase) {
5980
5925
 
5981
5926
  // src/commands/backlog/update/index.ts
5982
5927
  import chalk70 from "chalk";
5983
- import { eq as eq19 } from "drizzle-orm";
5928
+ import { eq as eq22 } from "drizzle-orm";
5984
5929
 
5985
5930
  // src/commands/backlog/update/parseListIndex.ts
5986
5931
  function parseListIndex(raw, length, label2) {
@@ -6091,8 +6036,8 @@ function buildUpdateValues(options2) {
6091
6036
 
6092
6037
  // src/commands/backlog/update/index.ts
6093
6038
  async function update(id, options2) {
6094
- const result = await loadAndFindItem(id);
6095
- if (!result) return;
6039
+ const found = await findOneItem(id);
6040
+ if (!found) return;
6096
6041
  let ac = options2.ac;
6097
6042
  if (hasAcMutations(options2)) {
6098
6043
  if (options2.ac) {
@@ -6102,7 +6047,7 @@ async function update(id, options2) {
6102
6047
  process.exitCode = 1;
6103
6048
  return;
6104
6049
  }
6105
- const mutation = applyAcMutations(result.item.acceptanceCriteria, options2);
6050
+ const mutation = applyAcMutations(found.item.acceptanceCriteria, options2);
6106
6051
  if (!mutation.ok) {
6107
6052
  console.log(chalk70.red(mutation.error));
6108
6053
  process.exitCode = 1;
@@ -6112,9 +6057,9 @@ async function update(id, options2) {
6112
6057
  }
6113
6058
  const built = buildUpdateValues({ ...options2, ac });
6114
6059
  if (!built) return;
6115
- const orm = await getBacklogOrm();
6116
- const itemId = result.item.id;
6117
- await orm.update(items).set(built.set).where(eq19(items.id, itemId));
6060
+ const { orm } = found;
6061
+ const itemId = found.item.id;
6062
+ await orm.update(items).set(built.set).where(eq22(items.id, itemId));
6118
6063
  console.log(chalk70.green(`Updated ${built.fields} on item #${itemId}.`));
6119
6064
  }
6120
6065
 
@@ -6122,22 +6067,22 @@ async function update(id, options2) {
6122
6067
  import chalk71 from "chalk";
6123
6068
 
6124
6069
  // src/commands/backlog/applyPhaseUpdate.ts
6125
- import { and as and8, eq as eq20 } from "drizzle-orm";
6070
+ import { and as and9, eq as eq23 } from "drizzle-orm";
6126
6071
  async function applyPhaseUpdate(orm, itemId, phaseIdx, fields) {
6127
6072
  await orm.transaction(async (tx) => {
6128
6073
  if (fields.name) {
6129
6074
  await tx.update(planPhases).set({ name: fields.name }).where(
6130
- and8(eq20(planPhases.itemId, itemId), eq20(planPhases.idx, phaseIdx))
6075
+ and9(eq23(planPhases.itemId, itemId), eq23(planPhases.idx, phaseIdx))
6131
6076
  );
6132
6077
  }
6133
6078
  if (fields.manualCheck) {
6134
6079
  await tx.update(planPhases).set({ manualChecks: JSON.stringify(fields.manualCheck) }).where(
6135
- and8(eq20(planPhases.itemId, itemId), eq20(planPhases.idx, phaseIdx))
6080
+ and9(eq23(planPhases.itemId, itemId), eq23(planPhases.idx, phaseIdx))
6136
6081
  );
6137
6082
  }
6138
6083
  if (fields.task) {
6139
6084
  await tx.delete(planTasks).where(
6140
- and8(eq20(planTasks.itemId, itemId), eq20(planTasks.phaseIdx, phaseIdx))
6085
+ and9(eq23(planTasks.itemId, itemId), eq23(planTasks.phaseIdx, phaseIdx))
6141
6086
  );
6142
6087
  if (fields.task.length) {
6143
6088
  await tx.insert(planTasks).values(
@@ -6220,8 +6165,8 @@ function resolvePhaseFields(options2, current) {
6220
6165
  async function updatePhase(id, phase, options2) {
6221
6166
  const found = await findPhase(id, phase);
6222
6167
  if (!found) return;
6223
- const { result, orm, itemId, phaseIdx } = found;
6224
- const resolved = resolvePhaseFields(options2, result.item.plan?.[phaseIdx]);
6168
+ const { item, orm, itemId, phaseIdx } = found;
6169
+ const resolved = resolvePhaseFields(options2, item.plan?.[phaseIdx]);
6225
6170
  if (!resolved.ok) {
6226
6171
  console.log(chalk71.red(resolved.error));
6227
6172
  process.exitCode = 1;
@@ -6275,8 +6220,8 @@ function registerWebCommand(cmd) {
6275
6220
  cmd.command("web").description("Open the backlog tab in the web dashboard").option("-p, --port <number>", "Port to listen on", "3100").action(web2);
6276
6221
  }
6277
6222
  function registerNextCommand(cmd) {
6278
- cmd.command("next").description("Pick and run the next backlog item, or open /draft if none").option("-w, --write", "Run Claude with auto permission mode").action(
6279
- (opts) => next({ allowEdits: opts.write })
6223
+ cmd.command("next").description("Pick and run the next backlog item, or open /draft if none").option("-w, --write", "Run Claude with auto permission mode (default)").option("--no-write", "Run Claude without auto permission mode").action(
6224
+ (opts) => next({ allowEdits: opts.write !== false })
6280
6225
  );
6281
6226
  }
6282
6227
  function registerBacklog(program2) {
@@ -17020,7 +16965,7 @@ registerVoice(program);
17020
16965
  registerSessions(program);
17021
16966
  registerPrompts(program);
17022
16967
  registerDeny(program);
17023
- program.command("next").description("Alias for backlog next -w").action(() => next({ allowEdits: true }));
16968
+ program.command("next").description("Alias for backlog next").action(() => next({ allowEdits: true }));
17024
16969
  program.command("draft").alias("feat").description("Launch Claude in /draft mode, chain into next on /next signal").action(() => launchMode("draft"));
17025
16970
  program.command("bug").description("Launch Claude in /bug mode, chain into next on /next signal").action(() => launchMode("bug"));
17026
16971
  program.command("refine").argument("[id]", "Backlog item ID").description("Launch Claude in /refine mode to refine a backlog item").action((id) => refine(id));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.230.0",
3
+ "version": "0.231.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {