@staff0rd/assist 0.965.0 → 0.966.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js 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.965.0",
9
+ version: "0.966.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -3752,7 +3752,7 @@ async function updateCurrentPhase(orm, id, phase) {
3752
3752
 
3753
3753
  // src/commands/backlog/updateStatus.ts
3754
3754
  import chalk7 from "chalk";
3755
- import { eq as eq14 } from "drizzle-orm";
3755
+ import { eq as eq15 } from "drizzle-orm";
3756
3756
 
3757
3757
  // src/commands/backlog/docsRefreshAfter.ts
3758
3758
  function docsRefreshAfter() {
@@ -3764,7 +3764,27 @@ function docsRefreshAfter() {
3764
3764
  }
3765
3765
 
3766
3766
  // src/commands/backlog/queueDocsRefresh.ts
3767
- import { and as and5, eq as eq13 } from "drizzle-orm";
3767
+ import { and as and6, eq as eq14 } from "drizzle-orm";
3768
+
3769
+ // src/commands/backlog/buildDocsRefreshDescription.ts
3770
+ function storyEntry(story) {
3771
+ const lines2 = [`### ${formatItemId(story.id)} ${story.name}`, ""];
3772
+ lines2.push(story.summary ?? "_No done summary._");
3773
+ if (story.commits.length > 0)
3774
+ lines2.push(
3775
+ "",
3776
+ `Commits: ${story.commits.map((c) => `\`${c.slice(0, 8)}\``).join(", ")}`
3777
+ );
3778
+ return lines2.join("\n");
3779
+ }
3780
+ function buildDocsRefreshDescription(stories) {
3781
+ return [
3782
+ "Update the docs pages under `docs/src/content/docs/` from what these done stories shipped. Stories already add `covers` entries for new commands and config keys; this item handles prose: wording, new behaviour on existing pages, and removed features.",
3783
+ "For detail on a story, run `assist backlog view <id>` (description, acceptance criteria, summaries and activity) and `git show <sha>` on its commits.",
3784
+ "## Covered stories",
3785
+ ...stories.map(storyEntry)
3786
+ ].join("\n\n");
3787
+ }
3768
3788
 
3769
3789
  // src/commands/backlog/insertPhaseAt.ts
3770
3790
  import { eq as eq12 } from "drizzle-orm";
@@ -3793,17 +3813,39 @@ async function insertPhaseAt(orm, itemId2, phaseIdx, name, tasks, manualChecks,
3793
3813
  });
3794
3814
  }
3795
3815
 
3816
+ // src/commands/backlog/loadDocsRefreshStories.ts
3817
+ import { and as and5, asc as asc6, eq as eq13, inArray as inArray2 } from "drizzle-orm";
3818
+ async function loadDocsRefreshStories(orm, ids) {
3819
+ if (ids.length === 0) return [];
3820
+ const [rows, summaries, commits2] = await Promise.all([
3821
+ orm.select({ id: items.id, name: items.name }).from(items).where(inArray2(items.id, ids)).orderBy(asc6(items.id)),
3822
+ orm.select({ itemId: comments.itemId, text: comments.text }).from(comments).where(and5(inArray2(comments.itemId, ids), eq13(comments.type, "summary"))).orderBy(asc6(comments.id)),
3823
+ orm.select({ itemId: itemGitRefs.itemId, ref: itemGitRefs.ref }).from(itemGitRefs).where(
3824
+ and5(inArray2(itemGitRefs.itemId, ids), eq13(itemGitRefs.kind, "commit"))
3825
+ ).orderBy(asc6(itemGitRefs.createdAt))
3826
+ ]);
3827
+ const latestSummary = new Map(summaries.map((s) => [s.itemId, s.text]));
3828
+ return rows.map((r) => ({
3829
+ id: r.id,
3830
+ name: r.name,
3831
+ summary: latestSummary.get(r.id),
3832
+ commits: commits2.filter((c) => c.itemId === r.id).map((c) => c.ref)
3833
+ }));
3834
+ }
3835
+
3796
3836
  // src/commands/backlog/createDocsRefreshItem.ts
3797
3837
  async function createDocsRefreshItem(orm, origin, storyIds) {
3838
+ const stories = await loadDocsRefreshStories(orm, storyIds);
3798
3839
  const storyList = storyIds.map(formatItemId).join(", ");
3799
3840
  const id = await insertItem(
3800
3841
  orm,
3801
3842
  {
3802
3843
  type: "story",
3803
3844
  name: "Refresh docs",
3804
- description: `Update the docs pages from what these done stories shipped: ${storyList}.`,
3845
+ description: buildDocsRefreshDescription(stories),
3805
3846
  acceptanceCriteria: [
3806
- "Docs pages reflect the behaviour shipped by every linked story"
3847
+ "Docs pages reflect the behaviour shipped by every covered story",
3848
+ "Pages are left untouched for stories with no user-facing change"
3807
3849
  ],
3808
3850
  status: "todo",
3809
3851
  starred: false
@@ -3816,8 +3858,9 @@ async function createDocsRefreshItem(orm, origin, storyIds) {
3816
3858
  0,
3817
3859
  "Refresh docs",
3818
3860
  [
3819
- `Read each linked story (${storyList}) with \`assist backlog view <id>\`: its description, acceptance criteria, summaries and commits`,
3820
- "Update the affected docs pages (wording, new behaviour on existing pages, removed features) following docs/CLAUDE.md"
3861
+ `Walk each covered story (${storyList}): read it with \`assist backlog view <id>\` and its commits with \`git show <sha>\`, and note what it changed for users`,
3862
+ "Skip stories with no user-facing change and leave their pages untouched",
3863
+ "For the rest, update the affected pages under `docs/src/content/docs/` (wording, new behaviour on existing pages, removed features) following the rules in `docs/CLAUDE.md`"
3821
3864
  ],
3822
3865
  null,
3823
3866
  void 0
@@ -3839,14 +3882,14 @@ async function queueDocsRefresh(orm, origin, threshold) {
3839
3882
  itemId: links.itemId,
3840
3883
  targetId: links.targetId,
3841
3884
  status: items.status
3842
- }).from(links).innerJoin(items, eq13(links.itemId, items.id)).where(and5(eq13(links.type, "docs-refresh"), eq13(items.origin, origin)));
3885
+ }).from(links).innerJoin(items, eq14(links.itemId, items.id)).where(and6(eq14(links.type, "docs-refresh"), eq14(items.origin, origin)));
3843
3886
  if (refreshLinks.some((l) => OPEN_STATUSES.has(l.status))) return void 0;
3844
3887
  const excluded = new Set(refreshLinks.flatMap((l) => [l.itemId, l.targetId]));
3845
3888
  const doneStories = await orm.select({ id: items.id }).from(items).where(
3846
- and5(
3847
- eq13(items.origin, origin),
3848
- eq13(items.type, "story"),
3849
- eq13(items.status, "done")
3889
+ and6(
3890
+ eq14(items.origin, origin),
3891
+ eq14(items.type, "story"),
3892
+ eq14(items.status, "done")
3850
3893
  )
3851
3894
  ).orderBy(items.id);
3852
3895
  const unswept = doneStories.map((r) => r.id).filter((id) => !excluded.has(id));
@@ -3856,7 +3899,7 @@ async function queueDocsRefresh(orm, origin, threshold) {
3856
3899
 
3857
3900
  // src/commands/backlog/updateStatus.ts
3858
3901
  async function updateStatus(orm, id, status2) {
3859
- const [row3] = await orm.update(items).set({ status: status2 }).where(eq14(items.id, id)).returning({ name: items.name, origin: items.origin, type: items.type });
3902
+ const [row3] = await orm.update(items).set({ status: status2 }).where(eq15(items.id, id)).returning({ name: items.name, origin: items.origin, type: items.type });
3860
3903
  if (row3 && status2 === "done" && row3.type === "story")
3861
3904
  await maybeQueueDocsRefresh(orm, row3.origin);
3862
3905
  return row3?.name;
@@ -3979,9 +4022,9 @@ function getRepoLocation(req, res) {
3979
4022
  }
3980
4023
 
3981
4024
  // src/commands/backlog/updateStarred.ts
3982
- import { eq as eq15 } from "drizzle-orm";
4025
+ import { eq as eq16 } from "drizzle-orm";
3983
4026
  async function updateStarred(orm, id, starred) {
3984
- const [row3] = await orm.update(items).set({ starred }).where(eq15(items.id, id)).returning({ name: items.name });
4027
+ const [row3] = await orm.update(items).set({ starred }).where(eq16(items.id, id)).returning({ name: items.name });
3985
4028
  return row3?.name;
3986
4029
  }
3987
4030
 
@@ -3991,455 +4034,6 @@ function applyCwdFromReq(req) {
3991
4034
  setBacklogDir(url.searchParams.get("cwd") ?? void 0);
3992
4035
  }
3993
4036
 
3994
- // src/commands/sessions/shared/lookupSessionsById.ts
3995
- import * as path8 from "path";
3996
-
3997
- // src/commands/sessions/shared/codex/discoverCodexRolloutPaths.ts
3998
- import * as fs from "fs";
3999
- import * as path4 from "path";
4000
-
4001
- // src/commands/sessions/shared/codex/codexSessionsDir.ts
4002
- import { existsSync as existsSync12 } from "fs";
4003
- import * as path3 from "path";
4004
-
4005
- // src/shared/harnesses.ts
4006
- import * as os from "os";
4007
- import * as path2 from "path";
4008
-
4009
- // src/shared/checkCliAvailable.ts
4010
- import { execSync as execSync4 } from "child_process";
4011
- function checkCliAvailable(cli) {
4012
- const binary = cli.split(/\s+/)[0];
4013
- const opts = {
4014
- encoding: "utf8",
4015
- stdio: ["ignore", "pipe", "pipe"],
4016
- windowsHide: true
4017
- };
4018
- try {
4019
- execSync4(`command -v ${binary}`, opts);
4020
- return true;
4021
- } catch {
4022
- try {
4023
- execSync4(`where ${binary}`, opts);
4024
- return true;
4025
- } catch {
4026
- return false;
4027
- }
4028
- }
4029
- }
4030
-
4031
- // src/shared/harnesses.ts
4032
- var harnesses = {
4033
- claude: {
4034
- kind: "claude",
4035
- command: "claude",
4036
- homeDir: path2.join(os.homedir(), ".claude"),
4037
- sync: {
4038
- commandDest: (name) => path2.join("skills", name, "SKILL.md")
4039
- }
4040
- },
4041
- codex: {
4042
- kind: "codex",
4043
- command: "codex",
4044
- homeDir: path2.join(os.homedir(), ".codex"),
4045
- sync: {
4046
- commandDest: (name) => path2.join("skills", name, "SKILL.md")
4047
- }
4048
- },
4049
- pi: {
4050
- kind: "pi",
4051
- command: "pi",
4052
- homeDir: path2.join(os.homedir(), ".pi", "agent"),
4053
- sync: {
4054
- commandDest: (name) => path2.join("prompts", `${name}.md`)
4055
- }
4056
- }
4057
- };
4058
- function isHarnessAvailable(kind) {
4059
- return checkCliAvailable(harnesses[kind].command);
4060
- }
4061
-
4062
- // src/commands/sessions/shared/codex/codexSessionsDir.ts
4063
- function codexSessionsDir() {
4064
- return path3.join(harnesses.codex.homeDir, "sessions");
4065
- }
4066
- function hasCodexSessions() {
4067
- return existsSync12(codexSessionsDir());
4068
- }
4069
-
4070
- // src/commands/sessions/shared/codex/discoverCodexRolloutPaths.ts
4071
- var DATE_DEPTH = 3;
4072
- function isCodexRolloutFile(name) {
4073
- return name.startsWith("rollout-") && name.endsWith(".jsonl");
4074
- }
4075
- async function discoverCodexRolloutPaths() {
4076
- if (!hasCodexSessions()) return [];
4077
- return collect(codexSessionsDir(), DATE_DEPTH);
4078
- }
4079
- async function collect(dir, depth) {
4080
- let entries;
4081
- try {
4082
- entries = await fs.promises.readdir(dir, { withFileTypes: true });
4083
- } catch {
4084
- return [];
4085
- }
4086
- const nested = await Promise.all(
4087
- entries.map((entry) => collectEntry(dir, entry, depth))
4088
- );
4089
- return nested.flat();
4090
- }
4091
- async function collectEntry(dir, entry, depth) {
4092
- const full = path4.join(dir, entry.name);
4093
- if (entry.isDirectory()) return depth > 0 ? collect(full, depth - 1) : [];
4094
- return isCodexRolloutFile(entry.name) ? [full] : [];
4095
- }
4096
-
4097
- // src/commands/sessions/shared/codex/parseCodexSessionFile.ts
4098
- import * as fs3 from "fs";
4099
- import * as path5 from "path";
4100
-
4101
- // src/commands/sessions/shared/backlogRunMarkers.ts
4102
- function backlogRunMarkers(text18) {
4103
- const match = text18.match(/backlog item a(\d+):[ \t]*([^\n]*)/);
4104
- if (!match) return { commandName: "", commandArgs: "" };
4105
- const title = match[2].trim();
4106
- return {
4107
- commandName: "next",
4108
- commandArgs: title ? `${match[1]} ${title}` : match[1]
4109
- };
4110
- }
4111
-
4112
- // src/commands/sessions/shared/deriveHistoryFields.ts
4113
- var KNOWN = [
4114
- "draft",
4115
- "next",
4116
- "bug",
4117
- "refine",
4118
- "review",
4119
- "run",
4120
- "update"
4121
- ];
4122
- function deriveHistoryFields(commandName, commandArgs, name) {
4123
- const fields = {};
4124
- const sessionType = deriveSessionType(commandName, name);
4125
- if (sessionType) fields.sessionType = sessionType;
4126
- const itemMatch = commandArgs.match(/^(\d+)\b\s*/);
4127
- if (itemMatch) fields.itemId = Number(itemMatch[1]);
4128
- const prompt = commandArgs.slice(itemMatch?.[0].length ?? 0).trim();
4129
- if (prompt) fields.prompt = prompt;
4130
- return fields;
4131
- }
4132
- function deriveSessionType(commandName, name) {
4133
- if (commandName === "review-pr-comments" || commandName === "fix-conflict")
4134
- return "review";
4135
- if (KNOWN.includes(commandName))
4136
- return commandName;
4137
- if (commandName || name) return "prompt";
4138
- return void 0;
4139
- }
4140
-
4141
- // src/commands/sessions/shared/codex/parseRolloutEntry.ts
4142
- function parseRolloutEntry(line) {
4143
- try {
4144
- const parsed = JSON.parse(line);
4145
- return parsed && typeof parsed === "object" ? parsed : null;
4146
- } catch {
4147
- return null;
4148
- }
4149
- }
4150
- function rolloutPayload(entry) {
4151
- return entry.payload && typeof entry.payload === "object" ? entry.payload : {};
4152
- }
4153
- function rolloutString(value) {
4154
- return typeof value === "string" ? value : "";
4155
- }
4156
-
4157
- // src/commands/sessions/shared/codex/codexRolloutMeta.ts
4158
- var MAX_FIRST_MESSAGE = 2e3;
4159
- function codexRolloutMeta(lines2) {
4160
- const meta = {
4161
- sessionId: "",
4162
- cwd: "",
4163
- timestamp: "",
4164
- firstMessage: ""
4165
- };
4166
- for (const line of lines2) {
4167
- const entry = parseRolloutEntry(line);
4168
- if (!entry) continue;
4169
- applySessionMeta(meta, entry);
4170
- meta.firstMessage ||= firstUserMessage(entry);
4171
- if (meta.sessionId && meta.firstMessage) break;
4172
- }
4173
- return meta;
4174
- }
4175
- function applySessionMeta(meta, entry) {
4176
- if (entry.type !== "session_meta") return;
4177
- const payload = rolloutPayload(entry);
4178
- meta.sessionId ||= rolloutString(payload.session_id);
4179
- meta.cwd ||= rolloutString(payload.cwd);
4180
- meta.timestamp ||= rolloutString(payload.timestamp);
4181
- }
4182
- function firstUserMessage(entry) {
4183
- if (entry.type !== "event_msg") return "";
4184
- const payload = rolloutPayload(entry);
4185
- if (payload.type !== "user_message") return "";
4186
- return rolloutString(payload.message).trim().slice(0, MAX_FIRST_MESSAGE);
4187
- }
4188
-
4189
- // src/commands/sessions/shared/codex/readCodexHeadLines.ts
4190
- import * as fs2 from "fs";
4191
- import * as readline from "readline";
4192
- var DEFAULT_MAX_LINES = 80;
4193
- async function readCodexHeadLines(filePath, maxLines = DEFAULT_MAX_LINES) {
4194
- const stream = fs2.createReadStream(filePath, { encoding: "utf8" });
4195
- const reader = readline.createInterface({ input: stream });
4196
- const lines2 = [];
4197
- try {
4198
- for await (const line of reader) {
4199
- if (line) lines2.push(line);
4200
- if (lines2.length >= maxLines) break;
4201
- }
4202
- } catch {
4203
- return lines2;
4204
- } finally {
4205
- reader.close();
4206
- stream.destroy();
4207
- }
4208
- return lines2;
4209
- }
4210
-
4211
- // src/commands/sessions/shared/codex/parseCodexSessionFile.ts
4212
- async function parseCodexSessionFile(filePath) {
4213
- try {
4214
- const meta = codexRolloutMeta(await readCodexHeadLines(filePath));
4215
- if (!meta.sessionId) return null;
4216
- const name = meta.firstMessage.slice(0, 80);
4217
- const markers = backlogRunMarkers(meta.firstMessage);
4218
- return {
4219
- sessionId: meta.sessionId,
4220
- name: name || `Session ${meta.sessionId.slice(0, 8)}`,
4221
- project: meta.cwd ? path5.basename(meta.cwd) : "",
4222
- cwd: meta.cwd,
4223
- timestamp: meta.timestamp || await mtime(filePath),
4224
- origin: "wsl",
4225
- harness: "codex",
4226
- ...deriveHistoryFields(markers.commandName, markers.commandArgs, name)
4227
- };
4228
- } catch {
4229
- return null;
4230
- }
4231
- }
4232
- async function mtime(filePath) {
4233
- return (await fs3.promises.stat(filePath)).mtime.toISOString();
4234
- }
4235
-
4236
- // src/commands/sessions/shared/parseSessionFile.ts
4237
- import * as fs4 from "fs";
4238
-
4239
- // src/commands/sessions/shared/extractSessionMeta.ts
4240
- function extractSessionMeta(lines2) {
4241
- let sessionId = "";
4242
- let cwd = "";
4243
- let timestamp8 = "";
4244
- let name = "";
4245
- let commandName = "";
4246
- let commandArgs = "";
4247
- for (const line of lines2) {
4248
- const entry = safeParse(line);
4249
- if (!entry) continue;
4250
- sessionId ||= strField(entry, "sessionId");
4251
- timestamp8 ||= strField(entry, "timestamp");
4252
- cwd ||= strField(entry, "cwd");
4253
- if (entry.type === "user" && !entry.isMeta) {
4254
- ({ name, commandName, commandArgs } = parseFirstUserEntry(entry));
4255
- break;
4256
- }
4257
- }
4258
- return { sessionId, cwd, timestamp: timestamp8, name, commandName, commandArgs };
4259
- }
4260
- function parseFirstUserEntry(entry) {
4261
- const raw = messageText(entry);
4262
- const commandName = matchMarker(raw, "command-name").replace(/^\//, "");
4263
- const commandArgs = matchMarker(raw, "command-args");
4264
- const name = stripMarkers(raw);
4265
- if (commandName) return { name, commandName, commandArgs };
4266
- return { name, ...backlogRunMarkers(raw) };
4267
- }
4268
- function strField(entry, key) {
4269
- const value = entry[key];
4270
- return typeof value === "string" ? value : "";
4271
- }
4272
- function safeParse(line) {
4273
- try {
4274
- return JSON.parse(line);
4275
- } catch {
4276
- return null;
4277
- }
4278
- }
4279
- function messageText(entry) {
4280
- const msg = entry.message;
4281
- const content = msg?.content;
4282
- return typeof content === "string" ? content : Array.isArray(content) ? content.find((c) => c.type === "text")?.text ?? "" : "";
4283
- }
4284
- function stripMarkers(text18) {
4285
- return text18.replace(/<command-[^>]*>[^<]*<\/command-[^>]*>/g, "").trim().slice(0, 80);
4286
- }
4287
- function matchMarker(text18, tag) {
4288
- const match = text18.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
4289
- return match ? match[1].trim() : "";
4290
- }
4291
-
4292
- // src/commands/sessions/shared/deriveProject.ts
4293
- import * as path6 from "path";
4294
- function deriveProject(cwd, filePath, origin) {
4295
- if (!cwd) return dirNameToProject(filePath);
4296
- return origin === "windows" ? path6.win32.basename(cwd) : path6.basename(cwd);
4297
- }
4298
- function dirNameToProject(filePath) {
4299
- const dirName = path6.basename(path6.dirname(filePath));
4300
- const parts = dirName.split("--");
4301
- return parts[parts.length - 1].replace(/-/g, "/");
4302
- }
4303
-
4304
- // src/commands/sessions/shared/parseSessionFile.ts
4305
- async function parseSessionFile(filePath, origin = "wsl") {
4306
- let handle;
4307
- try {
4308
- handle = await fs4.promises.open(filePath, "r");
4309
- const meta = extractSessionMeta(await readHeadLines(handle));
4310
- if (!meta.sessionId) return null;
4311
- const timestamp8 = meta.timestamp || (await fs4.promises.stat(filePath)).mtime.toISOString();
4312
- return {
4313
- sessionId: meta.sessionId,
4314
- name: meta.name || `Session ${meta.sessionId.slice(0, 8)}`,
4315
- project: deriveProject(meta.cwd, filePath, origin),
4316
- cwd: meta.cwd,
4317
- timestamp: timestamp8,
4318
- origin,
4319
- ...deriveHistoryFields(meta.commandName, meta.commandArgs, meta.name)
4320
- };
4321
- } catch {
4322
- return null;
4323
- } finally {
4324
- await handle?.close();
4325
- }
4326
- }
4327
- async function readHeadLines(handle) {
4328
- const buf = Buffer.alloc(16384);
4329
- const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
4330
- return buf.toString("utf8", 0, bytesRead).split("\n").filter(Boolean);
4331
- }
4332
-
4333
- // src/commands/sessions/shared/discoverSessionJsonlPaths.ts
4334
- import * as fs5 from "fs";
4335
- import * as os2 from "os";
4336
- import * as path7 from "path";
4337
- async function discoverSessionJsonlPaths() {
4338
- const results = [];
4339
- await Promise.all(
4340
- sessionRoots().map(async ({ dir, origin }) => {
4341
- let projectDirs;
4342
- try {
4343
- projectDirs = await fs5.promises.readdir(dir);
4344
- } catch {
4345
- return;
4346
- }
4347
- await Promise.all(
4348
- projectDirs.map(async (dirName) => {
4349
- const dirPath = path7.join(dir, dirName);
4350
- let entries;
4351
- try {
4352
- entries = await fs5.promises.readdir(dirPath);
4353
- } catch {
4354
- return;
4355
- }
4356
- for (const file of entries) {
4357
- if (file.endsWith(".jsonl"))
4358
- results.push({ path: path7.join(dirPath, file), origin });
4359
- }
4360
- })
4361
- );
4362
- })
4363
- );
4364
- return results;
4365
- }
4366
- function sessionRoots() {
4367
- return [
4368
- { dir: path7.join(os2.homedir(), ".claude", "projects"), origin: "wsl" }
4369
- ];
4370
- }
4371
-
4372
- // src/commands/sessions/shared/lookupSessionsById.ts
4373
- var CODEX_ROLLOUT_ID_LENGTH = 36;
4374
- async function lookupSessionsById(ids) {
4375
- const wanted = new Set(ids);
4376
- if (wanted.size === 0) return /* @__PURE__ */ new Map();
4377
- const found = /* @__PURE__ */ new Map();
4378
- const [claudePaths, codexPaths] = await Promise.all([
4379
- discoverSessionJsonlPaths(),
4380
- discoverCodexRolloutPaths()
4381
- ]);
4382
- await Promise.all([
4383
- ...claudePaths.map(async ({ path: filePath, origin }) => {
4384
- const id = path8.basename(filePath, ".jsonl");
4385
- if (!wanted.has(id)) return;
4386
- const session = await parseSessionFile(filePath, origin);
4387
- if (session) found.set(id, session);
4388
- }),
4389
- ...codexPaths.map(async (filePath) => {
4390
- const id = codexRolloutIdFromFilename(filePath);
4391
- if (!id || !wanted.has(id)) return;
4392
- const session = await parseCodexSessionFile(filePath);
4393
- if (session) found.set(id, session);
4394
- })
4395
- ]);
4396
- return found;
4397
- }
4398
- function codexRolloutIdFromFilename(filePath) {
4399
- const name = path8.basename(filePath, ".jsonl");
4400
- return name.length > CODEX_ROLLOUT_ID_LENGTH ? name.slice(-CODEX_ROLLOUT_ID_LENGTH) : void 0;
4401
- }
4402
-
4403
- // src/commands/backlog/web/itemConversations.ts
4404
- async function itemConversations(item) {
4405
- const byId = /* @__PURE__ */ new Map();
4406
- for (const ref of item.gitRefs ?? []) {
4407
- if (ref.kind !== "session" || byId.has(ref.ref)) continue;
4408
- byId.set(ref.ref, {
4409
- sessionId: ref.ref,
4410
- ...ref.title && { title: ref.title },
4411
- ...ref.createdAt && { createdAt: ref.createdAt }
4412
- });
4413
- }
4414
- for (const session of item.phaseSessions ?? []) {
4415
- const { claudeSessionId: sessionId, createdAt } = session;
4416
- if (byId.has(sessionId)) continue;
4417
- byId.set(sessionId, { sessionId, ...createdAt && { createdAt } });
4418
- }
4419
- if (byId.size === 0) return [];
4420
- const transcripts = await lookupSessionsById([...byId.keys()]);
4421
- return [...byId.values()].map(
4422
- (conversation) => withTranscript(conversation, transcripts.get(conversation.sessionId))
4423
- ).sort(newestFirst);
4424
- }
4425
- function withTranscript(conversation, transcript) {
4426
- if (!transcript) return conversation;
4427
- return {
4428
- ...conversation,
4429
- ...transcript.cwd && { cwd: transcript.cwd },
4430
- ...transcript.harness && { harness: transcript.harness },
4431
- title: conversation.title ?? transcript.name,
4432
- createdAt: conversation.createdAt ?? transcript.timestamp
4433
- };
4434
- }
4435
- function sortKey(conversation) {
4436
- const parsed = Date.parse(conversation.createdAt ?? "");
4437
- return Number.isNaN(parsed) ? 0 : parsed;
4438
- }
4439
- function newestFirst(a, b) {
4440
- return sortKey(b) - sortKey(a);
4441
- }
4442
-
4443
4037
  // src/commands/backlog/loadBacklogSummaries.ts
4444
4038
  async function loadBacklogSummaries(allRepos = false) {
4445
4039
  const { orm } = await getReady();
@@ -4496,217 +4090,20 @@ async function parseStarBody(req) {
4496
4090
  return JSON.parse(await readBody(req));
4497
4091
  }
4498
4092
 
4499
- // src/commands/backlog/buildCommentLines.ts
4500
- function buildCommentLines(comments3) {
4501
- if (!comments3?.length) return [];
4502
- return ["", "Comments:", ...comments3.map(formatPromptComment)];
4503
- }
4504
- function formatPromptComment(entry) {
4505
- const id = entry.id !== void 0 ? `#${entry.id} ` : "";
4506
- const tag = entry.type === "summary" ? "[summary]" : "[comment]";
4507
- const phase = entry.phase !== void 0 ? ` (phase ${entry.phase})` : "";
4508
- return `${id}${tag}${phase} ${entry.timestamp}
4509
- ${entry.text}`;
4093
+ // src/commands/backlog/web/shared.ts
4094
+ async function listItems(req, res) {
4095
+ applyCwdFromReq(req);
4096
+ const items2 = await loadVisibleItems(req);
4097
+ respondJson(res, 200, items2.slice().reverse());
4510
4098
  }
4511
-
4512
- // src/commands/backlog/buildManualCheckLines.ts
4513
- function buildManualCheckLines(manualChecks, commitBeforePhaseEnd) {
4514
- if (manualChecks.length > 0) {
4515
- return [
4516
- "",
4517
- ...commitBeforePhaseEnd ? [
4518
- "Once verify passes, run /commit to commit the work before asking the user to perform the manual checks below."
4519
- ] : [],
4520
- "Before marking this phase as done, ask the user to perform these manual checks:",
4521
- ...manualChecks.map((c) => `- ${c}`),
4522
- "",
4523
- "Wait for the user to confirm all manual checks pass before proceeding."
4524
- ];
4525
- }
4526
- if (commitBeforePhaseEnd) {
4527
- return [
4528
- "",
4529
- "Once verify passes, run /commit to commit the work before marking this phase as done."
4530
- ];
4099
+ async function findItemOr404(res, id) {
4100
+ const { orm } = await getReady();
4101
+ const item = await loadItem(orm, id);
4102
+ if (!item) {
4103
+ respondJson(res, 404, { error: "Not found" });
4104
+ return void 0;
4531
4105
  }
4532
- return [];
4533
- }
4534
-
4535
- // src/commands/backlog/buildAuthoredPhasePrompt.ts
4536
- function buildAuthoredPhasePrompt(item, phaseNumber, phase, options2 = {
4537
- commitBeforePhaseEnd: false
4538
- }) {
4539
- const manualChecks = phase.manualChecks ?? [];
4540
- const needsConfirmation = manualChecks.length > 0;
4541
- const confirmSuffix = needsConfirmation ? " and the user confirms" : "";
4542
- return [
4543
- ...buildContextLines(item, phaseNumber, phase),
4544
- ...buildStartedLines(item, phaseNumber),
4545
- "",
4546
- "Focus ONLY on this phase. Do not work on other phases.",
4547
- "If you need to modify backlog items, run `assist backlog --help` to discover available commands.",
4548
- "When you have completed all tasks for this phase, run /verify to check your work.",
4549
- ...buildManualCheckLines(manualChecks, options2.commitBeforePhaseEnd),
4550
- "",
4551
- `Post concise comments for any notable findings or changes using \`assist backlog comment ${formatItemId(item.id)} "<text>"\`.`,
4552
- "",
4553
- `Once verify passes${confirmSuffix}, run: assist backlog phase-done ${formatItemId(item.id)} ${phaseNumber} "<summary>"`,
4554
- "Replace <summary> with a concise summary of what was done in this phase."
4555
- ].filter((line) => line !== void 0).join("\n");
4556
- }
4557
- function buildContextLines(item, phaseNumber, phase) {
4558
- const ac = item.acceptanceCriteria.map((c, i) => `${i + 1}. ${c}`).join("\n");
4559
- return [
4560
- `You are implementing phase ${phaseNumber} of backlog item ${formatItemId(item.id)}: ${item.name}`,
4561
- "",
4562
- item.description ? `Description: ${item.description}` : "",
4563
- "",
4564
- "Acceptance criteria:",
4565
- ac,
4566
- ...buildCommentLines(item.comments),
4567
- "",
4568
- `Phase ${phaseNumber}: ${phase.name}`,
4569
- "Tasks:",
4570
- formatTasks(phase)
4571
- ];
4572
- }
4573
- function buildStartedLines(item, phaseNumber) {
4574
- if (phaseNumber !== 1) return [];
4575
- const command = item.jiraKey ? `/jira started ${item.jiraKey}` : item.githubIssue ? `/github started ${item.githubIssue}` : void 0;
4576
- if (!command) return [];
4577
- return [
4578
- "",
4579
- `As your first step, before any implementation, run \`${command}\` to assign the issue to yourself and transition it to In Progress.`
4580
- ];
4581
- }
4582
- function formatTasks(phase) {
4583
- return phase.tasks.map((t) => `- ${t.task}`).join("\n");
4584
- }
4585
-
4586
- // src/commands/backlog/buildReviewPrompt.ts
4587
- function buildReviewPrompt(item, phaseNumber, options2 = {
4588
- commitBeforePhaseEnd: false
4589
- }) {
4590
- const acLines = item.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join("\n");
4591
- const itemId2 = formatItemId(item.id);
4592
- return [
4593
- `You are reviewing backlog item ${itemId2}: ${item.name}`,
4594
- "",
4595
- item.description ? `Description: ${item.description}` : "",
4596
- "",
4597
- "This is the auto-generated review phase. Verify each acceptance criterion against the current implementation.",
4598
- "If you need to modify backlog items, run `assist backlog --help` to discover available commands.",
4599
- "For each criterion, inspect the code and report PASS or FAIL with a brief explanation:",
4600
- "",
4601
- acLines,
4602
- ...buildCommentLines(item.comments),
4603
- "",
4604
- "If any criterion fails, fix the issue and re-verify.",
4605
- "",
4606
- `Post concise comments for any notable findings or changes using \`assist backlog comment ${itemId2} "<text>"\`.`,
4607
- "",
4608
- ...options2.commitBeforePhaseEnd ? [
4609
- "Before asking the user to confirm manual checks, run /commit to commit the work (it is fine if there is nothing new to commit).",
4610
- ""
4611
- ] : [],
4612
- "After all criteria pass, ask the user to confirm any manual checks",
4613
- "(e.g. end-to-end behaviour they need to verify themselves).",
4614
- "Wait for the user to confirm before proceeding.",
4615
- "",
4616
- "What counts as a completion confirmation \u2014 read carefully:",
4617
- "- Marking the item done/phase-done requires an explicit, unambiguous instruction to complete it (e.g. the user says the item is done/accepted or tells you to mark it complete). Nothing else counts.",
4618
- "- Approving or running an unrelated action is NOT a completion signal. In particular, approving a `/pr`, raising a PR, or posting it to Slack via `/prs-slack` does not mean the item is complete \u2014 raising and announcing a PR is not confirmation that the story is done. Never run `done`/`phase-done` as a side effect of those actions.",
4619
- "- If it is unclear whether the user's message is a completion confirmation, ask them explicitly before running done/phase-done.",
4620
- "",
4621
- "Once the user gives that explicit completion instruction:",
4622
- "1. Run: /commit",
4623
- `2. Run: assist backlog done ${itemId2} "<summary>"`,
4624
- `3. Run: assist backlog phase-done ${itemId2} ${phaseNumber} "done"`,
4625
- "",
4626
- "Precedence rules \u2014 read carefully:",
4627
- "- An explicit completion instruction plus a successful commit means the work is complete. Always finish with done + phase-done; never rewind already-approved, committed work as an automatic response.",
4628
- "- If the implementation has diverged from the recorded acceptance criteria but the user has approved it, the default is still to complete the item. You may optionally offer to update the acceptance criteria, but stale criteria are not a reason to rewind.",
4629
- `- Rewinding is a confirm-first exception, not the normal path. If you believe a criterion genuinely no longer fits and the work should return to an earlier phase, you must ask the user and get explicit confirmation first. Only after they confirm may you run \`assist backlog rewind ${itemId2} <phase> --reason "<reason>"\`. Never rewind silently.`
4630
- ].filter((line) => line !== void 0).join("\n");
4631
- }
4632
-
4633
- // src/commands/backlog/buildPhasePrompt.ts
4634
- var REVIEW_PHASE_NAME = "Review";
4635
- function buildPhasePrompt(item, phaseNumber, phase) {
4636
- const worktree = loadConfig().worktree;
4637
- const commitBeforePhaseEnd = worktree?.commitBeforePhaseEnd ?? worktree?.commitBeforeManualChecks ?? false;
4638
- if (phase.name === REVIEW_PHASE_NAME) {
4639
- return buildReviewPrompt(item, phaseNumber, { commitBeforePhaseEnd });
4640
- }
4641
- return buildAuthoredPhasePrompt(item, phaseNumber, phase, {
4642
- commitBeforePhaseEnd
4643
- });
4644
- }
4645
-
4646
- // src/commands/backlog/buildReviewPhase.ts
4647
- function buildReviewPhase() {
4648
- return {
4649
- name: REVIEW_PHASE_NAME,
4650
- tasks: [
4651
- {
4652
- task: "Verify acceptance criteria, confirm manual checks, commit, mark done, and signal phase-done."
4653
- }
4654
- ]
4655
- };
4656
- }
4657
-
4658
- // src/commands/backlog/appendReviewPhase.ts
4659
- function appendReviewPhase(plan2) {
4660
- if (plan2.at(-1)?.name === REVIEW_PHASE_NAME) return plan2;
4661
- return [...plan2, buildReviewPhase()];
4662
- }
4663
-
4664
- // src/commands/backlog/resolvePlan.ts
4665
- function resolvePlan(item) {
4666
- if (item.plan && item.plan.length > 0) {
4667
- return item.plan;
4668
- }
4669
- return [
4670
- {
4671
- name: "Implement",
4672
- tasks: item.acceptanceCriteria.map((ac) => ({ task: ac }))
4673
- }
4674
- ];
4675
- }
4676
-
4677
- // src/commands/backlog/resolveRewindPlan.ts
4678
- function resolveRewindPlan(item) {
4679
- return appendReviewPhase(resolvePlan(item));
4680
- }
4681
-
4682
- // src/commands/backlog/web/withReviewPhase.ts
4683
- function withReviewPhase(item) {
4684
- if (!item.plan || item.plan.length === 0) return item;
4685
- return { ...item, plan: resolveRewindPlan(item) };
4686
- }
4687
-
4688
- // src/commands/backlog/web/shared.ts
4689
- async function listItems(req, res) {
4690
- applyCwdFromReq(req);
4691
- const items2 = await loadVisibleItems(req);
4692
- respondJson(res, 200, items2.slice().reverse());
4693
- }
4694
- async function findItemOr404(res, id) {
4695
- const { orm } = await getReady();
4696
- const item = await loadItem(orm, id);
4697
- if (!item) {
4698
- respondJson(res, 404, { error: "Not found" });
4699
- return void 0;
4700
- }
4701
- return { orm, item };
4702
- }
4703
- async function getItemById(res, id) {
4704
- const result = await findItemOr404(res, id);
4705
- if (!result) return;
4706
- const item = withReviewPhase(result.item);
4707
- const conversations = await itemConversations(item);
4708
- if (conversations.length > 0) item.conversations = conversations;
4709
- respondJson(res, 200, item);
4106
+ return { orm, item };
4710
4107
  }
4711
4108
  async function deleteItem2(res, id) {
4712
4109
  const result = await findItemOr404(res, id);
@@ -4927,20 +4324,20 @@ function includesCommittedChanges(cwd) {
4927
4324
  }
4928
4325
 
4929
4326
  // src/shared/db/findItemBySessionId.ts
4930
- import { asc as asc6, eq as eq16 } from "drizzle-orm";
4327
+ import { asc as asc7, eq as eq17 } from "drizzle-orm";
4931
4328
  async function findItemBySessionId(db, claudeSessionId) {
4932
- const rows = await db.select({ itemId: phaseUsage.itemId }).from(phaseUsage).where(eq16(phaseUsage.claudeSessionId, claudeSessionId)).orderBy(asc6(phaseUsage.itemId), asc6(phaseUsage.phaseIdx)).limit(1);
4329
+ const rows = await db.select({ itemId: phaseUsage.itemId }).from(phaseUsage).where(eq17(phaseUsage.claudeSessionId, claudeSessionId)).orderBy(asc7(phaseUsage.itemId), asc7(phaseUsage.phaseIdx)).limit(1);
4933
4330
  return rows[0]?.itemId;
4934
4331
  }
4935
4332
 
4936
4333
  // src/shared/db/listCommitRefs.ts
4937
- import { and as and6, asc as asc7, eq as eq17 } from "drizzle-orm";
4334
+ import { and as and7, asc as asc8, eq as eq18 } from "drizzle-orm";
4938
4335
  async function listCommitRefs(db, itemId2) {
4939
4336
  const rows = await db.select({
4940
4337
  ref: itemGitRefs.ref,
4941
4338
  title: itemGitRefs.title,
4942
4339
  url: itemGitRefs.url
4943
- }).from(itemGitRefs).where(and6(eq17(itemGitRefs.itemId, itemId2), eq17(itemGitRefs.kind, "commit"))).orderBy(asc7(itemGitRefs.createdAt), asc7(itemGitRefs.ref));
4340
+ }).from(itemGitRefs).where(and7(eq18(itemGitRefs.itemId, itemId2), eq18(itemGitRefs.kind, "commit"))).orderBy(asc8(itemGitRefs.createdAt), asc8(itemGitRefs.ref));
4944
4341
  return rows.map((row3) => ({
4945
4342
  sha: row3.ref,
4946
4343
  ...row3.title ? { title: row3.title } : {},
@@ -5190,11 +4587,11 @@ async function diffScopes(req, res) {
5190
4587
 
5191
4588
  // src/commands/sessions/daemon/recordPromptJournal.ts
5192
4589
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync3 } from "fs";
5193
- import { dirname as dirname6 } from "path";
4590
+ import { dirname as dirname5 } from "path";
5194
4591
  function recordPromptJournal(entry) {
5195
4592
  const line = JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry });
5196
4593
  try {
5197
- mkdirSync3(dirname6(daemonPaths.promptJournal), { recursive: true });
4594
+ mkdirSync3(dirname5(daemonPaths.promptJournal), { recursive: true });
5198
4595
  appendFileSync2(daemonPaths.promptJournal, `${line}
5199
4596
  `);
5200
4597
  } catch {
@@ -5202,7 +4599,7 @@ function recordPromptJournal(entry) {
5202
4599
  }
5203
4600
 
5204
4601
  // src/commands/sessions/daemon/requestDaemonReply.ts
5205
- import { createInterface as createInterface3 } from "readline";
4602
+ import { createInterface as createInterface2 } from "readline";
5206
4603
  var QUERY_TIMEOUT_MS = 3e3;
5207
4604
  async function requestDaemonReply(request, replyType, timeoutMs = QUERY_TIMEOUT_MS) {
5208
4605
  let socket;
@@ -5218,7 +4615,7 @@ async function requestDaemonReply(request, replyType, timeoutMs = QUERY_TIMEOUT_
5218
4615
  resolve31(result);
5219
4616
  };
5220
4617
  const timer = setTimeout(() => finish2(void 0), timeoutMs);
5221
- const lines2 = createInterface3({ input: socket });
4618
+ const lines2 = createInterface2({ input: socket });
5222
4619
  lines2.on("error", () => {
5223
4620
  });
5224
4621
  lines2.on("line", (line) => {
@@ -5364,14 +4761,14 @@ async function fileContent(req, res) {
5364
4761
  }
5365
4762
 
5366
4763
  // src/commands/sessions/web/fromGithub/getClones.ts
5367
- import { basename as basename6 } from "path";
4764
+ import { basename as basename3 } from "path";
5368
4765
 
5369
4766
  // src/commands/sessions/web/fromGithub/listClones.ts
5370
- import { existsSync as existsSync13, readdirSync as readdirSync2 } from "fs";
5371
- import { join as join16 } from "path";
4767
+ import { existsSync as existsSync12, readdirSync as readdirSync2 } from "fs";
4768
+ import { join as join12 } from "path";
5372
4769
  function listClones(baseDir) {
5373
- if (!existsSync13(baseDir)) return [];
5374
- return readdirSync2(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join16(baseDir, entry.name)).filter((dir) => existsSync13(join16(dir, ".git"))).sort();
4770
+ if (!existsSync12(baseDir)) return [];
4771
+ return readdirSync2(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join12(baseDir, entry.name)).filter((dir) => existsSync12(join12(dir, ".git"))).sort();
5375
4772
  }
5376
4773
 
5377
4774
  // src/commands/sessions/web/fromGithub/getClones.ts
@@ -5379,7 +4776,7 @@ function getClones(req, res) {
5379
4776
  allowExtensionOrigin(req, res);
5380
4777
  const baseDir = expandTilde(loadConfig().clone.baseDir);
5381
4778
  respondJson(res, 200, {
5382
- clones: listClones(baseDir).map((dir) => basename6(dir))
4779
+ clones: listClones(baseDir).map((dir) => basename3(dir))
5383
4780
  });
5384
4781
  }
5385
4782
 
@@ -5558,8 +4955,8 @@ function azureDevopsRepoUrl({ org, project, repo }) {
5558
4955
  }
5559
4956
 
5560
4957
  // src/commands/sessions/web/fromGithub/findAzureDevopsClone.ts
5561
- import { existsSync as existsSync14, readdirSync as readdirSync3 } from "fs";
5562
- import { join as join17 } from "path";
4958
+ import { existsSync as existsSync13, readdirSync as readdirSync3 } from "fs";
4959
+ import { join as join13 } from "path";
5563
4960
 
5564
4961
  // src/shared/azureDevops/parseAzureDevopsRemote.ts
5565
4962
  var HTTPS_DEV = /^https?:\/\/(?:[^@/]+@)?dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/]+)$/i;
@@ -5580,15 +4977,15 @@ function sameAzureDevopsRepo(a, b) {
5580
4977
 
5581
4978
  // src/commands/sessions/web/fromGithub/findAzureDevopsClone.ts
5582
4979
  function clonesRepo(dir, repo) {
5583
- if (!existsSync14(join17(dir, ".git"))) return false;
4980
+ if (!existsSync13(join13(dir, ".git"))) return false;
5584
4981
  const url = remoteUrl(dir).url;
5585
4982
  const remote = url ? parseAzureDevopsRemote(url) : null;
5586
4983
  return remote !== null && sameAzureDevopsRepo(remote, repo);
5587
4984
  }
5588
4985
  function findAzureDevopsClone(repo, baseDir) {
5589
- if (!existsSync14(baseDir)) return void 0;
5590
- const named = join17(baseDir, repo.repo);
5591
- const others = readdirSync3(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join17(baseDir, entry.name)).filter((dir) => dir !== named).sort();
4986
+ if (!existsSync13(baseDir)) return void 0;
4987
+ const named = join13(baseDir, repo.repo);
4988
+ const others = readdirSync3(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join13(baseDir, entry.name)).filter((dir) => dir !== named).sort();
5592
4989
  return [named, ...others].find((dir) => clonesRepo(dir, repo));
5593
4990
  }
5594
4991
 
@@ -5807,7 +5204,7 @@ function sessionFromGithubPreflight(req, res) {
5807
5204
  }
5808
5205
 
5809
5206
  // src/commands/sessions/web/fromGithub/planLinkSession.ts
5810
- import { basename as basename7 } from "path";
5207
+ import { basename as basename4 } from "path";
5811
5208
  function isHttpUrl(url) {
5812
5209
  return URL.canParse(url) && /^https?:$/.test(new URL(url).protocol);
5813
5210
  }
@@ -5819,7 +5216,7 @@ function planLinkSession(body, baseDir) {
5819
5216
  return { ok: false, status: 400, error: "Missing or invalid url" };
5820
5217
  if (typeof repo !== "string" || !repo.trim())
5821
5218
  return { ok: false, status: 400, error: "No repo picked" };
5822
- const cwd = listClones(baseDir).find((dir) => basename7(dir) === repo.trim());
5219
+ const cwd = listClones(baseDir).find((dir) => basename4(dir) === repo.trim());
5823
5220
  if (!cwd)
5824
5221
  return {
5825
5222
  ok: false,
@@ -7264,7 +6661,7 @@ function getHtml() {
7264
6661
  }
7265
6662
 
7266
6663
  // src/shared/resolveRunConfigs.ts
7267
- import { dirname as dirname7, relative as relative2, resolve as resolve7 } from "path";
6664
+ import { dirname as dirname6, relative as relative2, resolve as resolve7 } from "path";
7268
6665
 
7269
6666
  // src/shared/assertNoDuplicateRunNames.ts
7270
6667
  function findDuplicateNames(configs) {
@@ -7286,14 +6683,14 @@ function assertNoDuplicateRunNames(configs) {
7286
6683
  }
7287
6684
 
7288
6685
  // src/shared/findLinkedConfigPath.ts
7289
- import { existsSync as existsSync15 } from "fs";
7290
- import { join as join18, resolve as resolve5 } from "path";
6686
+ import { existsSync as existsSync14 } from "fs";
6687
+ import { join as join14, resolve as resolve5 } from "path";
7291
6688
  function findLinkedConfigPath(linkPath, fromDir) {
7292
6689
  const resolved = resolve5(fromDir, linkPath);
7293
- const claudePath = join18(resolved, ".claude", "assist.yml");
7294
- if (existsSync15(claudePath)) return claudePath;
7295
- const rootPath = join18(resolved, "assist.yml");
7296
- if (existsSync15(rootPath)) return rootPath;
6690
+ const claudePath = join14(resolved, ".claude", "assist.yml");
6691
+ if (existsSync14(claudePath)) return claudePath;
6692
+ const rootPath = join14(resolved, "assist.yml");
6693
+ if (existsSync14(rootPath)) return rootPath;
7297
6694
  throw new Error(`No assist.yml found in linked project: ${resolved}`);
7298
6695
  }
7299
6696
 
@@ -7335,7 +6732,7 @@ function loadAndResolveLink(linkPath, configDir, ctx) {
7335
6732
  const entries = loadLinkedEntries(configPath, ctx.visited);
7336
6733
  const defaultCwd = relativeToRoot(ctx, resolve7(configDir, linkPath));
7337
6734
  return setDefaultCwd(
7338
- resolveRecursive(entries, dirname7(configPath), ctx),
6735
+ resolveRecursive(entries, dirname6(configPath), ctx),
7339
6736
  defaultCwd
7340
6737
  );
7341
6738
  }
@@ -7387,19 +6784,19 @@ function handleServerRuns(req, res) {
7387
6784
  // src/commands/sessions/web/getReviewSynthesis.ts
7388
6785
  import { execFile as execFile4 } from "child_process";
7389
6786
  import { readFileSync as readFileSync6 } from "fs";
7390
- import { homedir as homedir8 } from "os";
7391
- import { basename as basename9, join as join20 } from "path";
6787
+ import { homedir as homedir6 } from "os";
6788
+ import { basename as basename6, join as join16 } from "path";
7392
6789
  import { promisify as promisify4 } from "util";
7393
6790
 
7394
6791
  // src/commands/sessions/web/findSynthesisForBranch.ts
7395
- import { existsSync as existsSync16, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
7396
- import { basename as basename8, dirname as dirname8, join as join19 } from "path";
6792
+ import { existsSync as existsSync15, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
6793
+ import { basename as basename5, dirname as dirname7, join as join15 } from "path";
7397
6794
  function findSynthesisForBranch(repoReviewsDir, branch2) {
7398
- const branchKeyPath = join19(repoReviewsDir, `${branch2}-`);
7399
- const parent = dirname8(branchKeyPath);
7400
- const branchPrefix = basename8(branchKeyPath);
7401
- if (!existsSync16(parent)) return null;
7402
- const synthesisFiles = readdirSync4(parent).filter((name) => name.startsWith(branchPrefix)).map((name) => join19(parent, name, "synthesis.md")).filter((path108) => existsSync16(path108)).map((path108) => ({ path: path108, mtime: statSync4(path108).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
6795
+ const branchKeyPath = join15(repoReviewsDir, `${branch2}-`);
6796
+ const parent = dirname7(branchKeyPath);
6797
+ const branchPrefix = basename5(branchKeyPath);
6798
+ if (!existsSync15(parent)) return null;
6799
+ const synthesisFiles = readdirSync4(parent).filter((name) => name.startsWith(branchPrefix)).map((name) => join15(parent, name, "synthesis.md")).filter((path108) => existsSync15(path108)).map((path108) => ({ path: path108, mtime: statSync4(path108).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
7403
6800
  return synthesisFiles[0]?.path ?? null;
7404
6801
  }
7405
6802
 
@@ -7417,11 +6814,11 @@ async function resolveSynthesisPath(cwd) {
7417
6814
  runGit2(cwd, ["rev-parse", "--show-toplevel"]),
7418
6815
  runGit2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"])
7419
6816
  ]);
7420
- const repoReviewsDir = join20(
7421
- homedir8(),
6817
+ const repoReviewsDir = join16(
6818
+ homedir6(),
7422
6819
  ".assist",
7423
6820
  "reviews",
7424
- basename9(repoRoot2)
6821
+ basename6(repoRoot2)
7425
6822
  );
7426
6823
  return findSynthesisForBranch(repoReviewsDir, branch2);
7427
6824
  }
@@ -7487,7 +6884,7 @@ async function githubFileContent(req, res) {
7487
6884
  }
7488
6885
 
7489
6886
  // src/commands/prs/getPreferredRemoteRepo.ts
7490
- import { execSync as execSync5 } from "child_process";
6887
+ import { execSync as execSync4 } from "child_process";
7491
6888
 
7492
6889
  // src/commands/prs/parseGitHubUrl.ts
7493
6890
  var GITHUB_URL_PATTERN = /(?:git@github\.com:|https:\/\/github\.com\/)([^/]+)\/([^/]+?)(?:\.git)?\/?$/;
@@ -7500,7 +6897,7 @@ function parseGitHubUrl(url) {
7500
6897
  // src/commands/prs/getPreferredRemoteRepo.ts
7501
6898
  function tryGetRemoteUrl(remote, cwd) {
7502
6899
  try {
7503
- return execSync5(`git remote get-url ${remote}`, {
6900
+ return execSync4(`git remote get-url ${remote}`, {
7504
6901
  encoding: "utf8",
7505
6902
  stdio: ["pipe", "pipe", "pipe"],
7506
6903
  windowsHide: true,
@@ -7512,7 +6909,7 @@ function tryGetRemoteUrl(remote, cwd) {
7512
6909
  }
7513
6910
  function getCurrentBranchRemote(cwd) {
7514
6911
  try {
7515
- const ref = execSync5(
6912
+ const ref = execSync4(
7516
6913
  "git rev-parse --abbrev-ref --symbolic-full-name @{u}",
7517
6914
  {
7518
6915
  encoding: "utf8",
@@ -7529,7 +6926,7 @@ function getCurrentBranchRemote(cwd) {
7529
6926
  }
7530
6927
  function listRemotes(cwd) {
7531
6928
  try {
7532
- return execSync5("git remote", {
6929
+ return execSync4("git remote", {
7533
6930
  encoding: "utf8",
7534
6931
  stdio: ["pipe", "pipe", "pipe"],
7535
6932
  windowsHide: true,
@@ -7661,238 +7058,903 @@ async function groupedCounts(cwd, groups) {
7661
7058
  return counts2;
7662
7059
  }
7663
7060
 
7664
- // src/commands/sessions/web/parseGitStatus.ts
7665
- function extractPath(rest) {
7666
- const arrow = rest.indexOf(" -> ");
7667
- return arrow === -1 ? rest : rest.slice(arrow + 4);
7668
- }
7669
- var UNMERGED_STATES = /* @__PURE__ */ new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
7670
- function categorize2(xy) {
7671
- if (xy === "??") return "new";
7672
- if (UNMERGED_STATES.has(xy)) return "modified";
7673
- const codes = xy.replace(/ /g, "");
7674
- if (codes.includes("A")) return "new";
7675
- if (codes.includes("D")) return "deleted";
7676
- if (codes.includes("M") || codes.includes("R") || codes.includes("C")) {
7677
- return "modified";
7061
+ // src/commands/sessions/web/parseGitStatus.ts
7062
+ function extractPath(rest) {
7063
+ const arrow = rest.indexOf(" -> ");
7064
+ return arrow === -1 ? rest : rest.slice(arrow + 4);
7065
+ }
7066
+ var UNMERGED_STATES = /* @__PURE__ */ new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
7067
+ function categorize2(xy) {
7068
+ if (xy === "??") return "new";
7069
+ if (UNMERGED_STATES.has(xy)) return "modified";
7070
+ const codes = xy.replace(/ /g, "");
7071
+ if (codes.includes("A")) return "new";
7072
+ if (codes.includes("D")) return "deleted";
7073
+ if (codes.includes("M") || codes.includes("R") || codes.includes("C")) {
7074
+ return "modified";
7075
+ }
7076
+ return null;
7077
+ }
7078
+ function parseGitStatus(output) {
7079
+ const result = { new: [], modified: [], deleted: [] };
7080
+ for (const line of output.split("\n")) {
7081
+ if (line.length < 4) continue;
7082
+ const category = categorize2(line.slice(0, 2));
7083
+ if (category) result[category].push(extractPath(line.slice(3)));
7084
+ }
7085
+ return result;
7086
+ }
7087
+
7088
+ // src/commands/sessions/web/workingTreeCounts.ts
7089
+ async function workingTreeCounts(cwd) {
7090
+ const output = await execGit(cwd, [
7091
+ "status",
7092
+ "--porcelain",
7093
+ "--untracked-files=all"
7094
+ ]);
7095
+ return parseGitStatus(output);
7096
+ }
7097
+
7098
+ // src/commands/sessions/web/gitStatus.ts
7099
+ async function gitStatus(req, res) {
7100
+ const cwd = getCwdParam(req, res);
7101
+ if (!cwd) return;
7102
+ try {
7103
+ const branch2 = await gitBranchInfo(cwd);
7104
+ const changeSet = await itemChangeSet(cwd, getSessionParam(req)).catch(
7105
+ () => void 0
7106
+ );
7107
+ if (!changeSet) {
7108
+ respondJson(res, 200, { ...await workingTreeCounts(cwd), ...branch2 });
7109
+ return;
7110
+ }
7111
+ const [counts2, uncommitted] = await Promise.all([
7112
+ groupedCounts(cwd, changeSet.groups),
7113
+ workingTreeCounts(cwd)
7114
+ ]);
7115
+ respondJson(res, 200, {
7116
+ ...counts2,
7117
+ ...branch2,
7118
+ uncommitted,
7119
+ hasCommits: changeSet.commits.length > 0
7120
+ });
7121
+ } catch {
7122
+ respondJson(res, 200, { new: [], modified: [], deleted: [] });
7123
+ }
7124
+ }
7125
+
7126
+ // src/commands/backlog/deleteComment.ts
7127
+ import { and as and8, eq as eq19 } from "drizzle-orm";
7128
+ async function deleteComment(orm, itemId2, commentId) {
7129
+ const [row3] = await orm.select({ type: comments.type }).from(comments).where(and8(eq19(comments.id, commentId), eq19(comments.itemId, itemId2)));
7130
+ if (!row3) return "not-found";
7131
+ if (row3.type === "summary") return "is-summary";
7132
+ await orm.delete(comments).where(and8(eq19(comments.id, commentId), eq19(comments.itemId, itemId2)));
7133
+ return "deleted";
7134
+ }
7135
+
7136
+ // src/commands/backlog/web/deleteItemComment.ts
7137
+ async function deleteItemComment(res, itemId2, commentId) {
7138
+ const result = await findItemOr404(res, itemId2);
7139
+ if (!result) return;
7140
+ const outcome = await deleteComment(result.orm, itemId2, commentId);
7141
+ if (outcome === "not-found") {
7142
+ respondJson(res, 404, {
7143
+ error: `Comment #${commentId} not found on item ${formatItemId(itemId2)}.`
7144
+ });
7145
+ return;
7146
+ }
7147
+ if (outcome === "is-summary") {
7148
+ respondJson(res, 400, {
7149
+ error: `Comment #${commentId} is a phase summary and cannot be deleted.`
7150
+ });
7151
+ return;
7152
+ }
7153
+ respondJson(res, 200, await loadItem(result.orm, itemId2));
7154
+ }
7155
+
7156
+ // src/commands/backlog/appendComment.ts
7157
+ import { sql as sql16 } from "drizzle-orm";
7158
+ async function appendComment(orm, itemId2, text18, opts = {}) {
7159
+ await orm.insert(comments).values({
7160
+ itemId: itemId2,
7161
+ idx: sql16`(SELECT COALESCE(MAX(${comments.idx}) + 1, 0) FROM ${comments} WHERE ${comments.itemId} = ${itemId2})`,
7162
+ text: text18,
7163
+ phase: opts.phase ?? null,
7164
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7165
+ type: opts.type ?? "comment"
7166
+ });
7167
+ }
7168
+
7169
+ // src/commands/backlog/buildCommentLines.ts
7170
+ function buildCommentLines(comments3) {
7171
+ if (!comments3?.length) return [];
7172
+ return ["", "Comments:", ...comments3.map(formatPromptComment)];
7173
+ }
7174
+ function formatPromptComment(entry) {
7175
+ const id = entry.id !== void 0 ? `#${entry.id} ` : "";
7176
+ const tag = entry.type === "summary" ? "[summary]" : "[comment]";
7177
+ const phase = entry.phase !== void 0 ? ` (phase ${entry.phase})` : "";
7178
+ return `${id}${tag}${phase} ${entry.timestamp}
7179
+ ${entry.text}`;
7180
+ }
7181
+
7182
+ // src/commands/backlog/buildManualCheckLines.ts
7183
+ function buildManualCheckLines(manualChecks, commitBeforePhaseEnd) {
7184
+ if (manualChecks.length > 0) {
7185
+ return [
7186
+ "",
7187
+ ...commitBeforePhaseEnd ? [
7188
+ "Once verify passes, run /commit to commit the work before asking the user to perform the manual checks below."
7189
+ ] : [],
7190
+ "Before marking this phase as done, ask the user to perform these manual checks:",
7191
+ ...manualChecks.map((c) => `- ${c}`),
7192
+ "",
7193
+ "Wait for the user to confirm all manual checks pass before proceeding."
7194
+ ];
7195
+ }
7196
+ if (commitBeforePhaseEnd) {
7197
+ return [
7198
+ "",
7199
+ "Once verify passes, run /commit to commit the work before marking this phase as done."
7200
+ ];
7201
+ }
7202
+ return [];
7203
+ }
7204
+
7205
+ // src/commands/backlog/buildAuthoredPhasePrompt.ts
7206
+ function buildAuthoredPhasePrompt(item, phaseNumber, phase, options2 = {
7207
+ commitBeforePhaseEnd: false
7208
+ }) {
7209
+ const manualChecks = phase.manualChecks ?? [];
7210
+ const needsConfirmation = manualChecks.length > 0;
7211
+ const confirmSuffix = needsConfirmation ? " and the user confirms" : "";
7212
+ return [
7213
+ ...buildContextLines(item, phaseNumber, phase),
7214
+ ...buildStartedLines(item, phaseNumber),
7215
+ "",
7216
+ "Focus ONLY on this phase. Do not work on other phases.",
7217
+ "If you need to modify backlog items, run `assist backlog --help` to discover available commands.",
7218
+ "When you have completed all tasks for this phase, run /verify to check your work.",
7219
+ ...buildManualCheckLines(manualChecks, options2.commitBeforePhaseEnd),
7220
+ "",
7221
+ `Post concise comments for any notable findings or changes using \`assist backlog comment ${formatItemId(item.id)} "<text>"\`.`,
7222
+ "",
7223
+ `Once verify passes${confirmSuffix}, run: assist backlog phase-done ${formatItemId(item.id)} ${phaseNumber} "<summary>"`,
7224
+ "Replace <summary> with a concise summary of what was done in this phase."
7225
+ ].filter((line) => line !== void 0).join("\n");
7226
+ }
7227
+ function buildContextLines(item, phaseNumber, phase) {
7228
+ const ac = item.acceptanceCriteria.map((c, i) => `${i + 1}. ${c}`).join("\n");
7229
+ return [
7230
+ `You are implementing phase ${phaseNumber} of backlog item ${formatItemId(item.id)}: ${item.name}`,
7231
+ "",
7232
+ item.description ? `Description: ${item.description}` : "",
7233
+ "",
7234
+ "Acceptance criteria:",
7235
+ ac,
7236
+ ...buildCommentLines(item.comments),
7237
+ "",
7238
+ `Phase ${phaseNumber}: ${phase.name}`,
7239
+ "Tasks:",
7240
+ formatTasks(phase)
7241
+ ];
7242
+ }
7243
+ function buildStartedLines(item, phaseNumber) {
7244
+ if (phaseNumber !== 1) return [];
7245
+ const command = item.jiraKey ? `/jira started ${item.jiraKey}` : item.githubIssue ? `/github started ${item.githubIssue}` : void 0;
7246
+ if (!command) return [];
7247
+ return [
7248
+ "",
7249
+ `As your first step, before any implementation, run \`${command}\` to assign the issue to yourself and transition it to In Progress.`
7250
+ ];
7251
+ }
7252
+ function formatTasks(phase) {
7253
+ return phase.tasks.map((t) => `- ${t.task}`).join("\n");
7254
+ }
7255
+
7256
+ // src/commands/backlog/buildReviewPrompt.ts
7257
+ function buildReviewPrompt(item, phaseNumber, options2 = {
7258
+ commitBeforePhaseEnd: false
7259
+ }) {
7260
+ const acLines = item.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join("\n");
7261
+ const itemId2 = formatItemId(item.id);
7262
+ return [
7263
+ `You are reviewing backlog item ${itemId2}: ${item.name}`,
7264
+ "",
7265
+ item.description ? `Description: ${item.description}` : "",
7266
+ "",
7267
+ "This is the auto-generated review phase. Verify each acceptance criterion against the current implementation.",
7268
+ "If you need to modify backlog items, run `assist backlog --help` to discover available commands.",
7269
+ "For each criterion, inspect the code and report PASS or FAIL with a brief explanation:",
7270
+ "",
7271
+ acLines,
7272
+ ...buildCommentLines(item.comments),
7273
+ "",
7274
+ "If any criterion fails, fix the issue and re-verify.",
7275
+ "",
7276
+ `Post concise comments for any notable findings or changes using \`assist backlog comment ${itemId2} "<text>"\`.`,
7277
+ "",
7278
+ ...options2.commitBeforePhaseEnd ? [
7279
+ "Before asking the user to confirm manual checks, run /commit to commit the work (it is fine if there is nothing new to commit).",
7280
+ ""
7281
+ ] : [],
7282
+ "After all criteria pass, ask the user to confirm any manual checks",
7283
+ "(e.g. end-to-end behaviour they need to verify themselves).",
7284
+ "Wait for the user to confirm before proceeding.",
7285
+ "",
7286
+ "What counts as a completion confirmation \u2014 read carefully:",
7287
+ "- Marking the item done/phase-done requires an explicit, unambiguous instruction to complete it (e.g. the user says the item is done/accepted or tells you to mark it complete). Nothing else counts.",
7288
+ "- Approving or running an unrelated action is NOT a completion signal. In particular, approving a `/pr`, raising a PR, or posting it to Slack via `/prs-slack` does not mean the item is complete \u2014 raising and announcing a PR is not confirmation that the story is done. Never run `done`/`phase-done` as a side effect of those actions.",
7289
+ "- If it is unclear whether the user's message is a completion confirmation, ask them explicitly before running done/phase-done.",
7290
+ "",
7291
+ "Once the user gives that explicit completion instruction:",
7292
+ "1. Run: /commit",
7293
+ `2. Run: assist backlog done ${itemId2} "<summary>"`,
7294
+ `3. Run: assist backlog phase-done ${itemId2} ${phaseNumber} "done"`,
7295
+ "",
7296
+ "Precedence rules \u2014 read carefully:",
7297
+ "- An explicit completion instruction plus a successful commit means the work is complete. Always finish with done + phase-done; never rewind already-approved, committed work as an automatic response.",
7298
+ "- If the implementation has diverged from the recorded acceptance criteria but the user has approved it, the default is still to complete the item. You may optionally offer to update the acceptance criteria, but stale criteria are not a reason to rewind.",
7299
+ `- Rewinding is a confirm-first exception, not the normal path. If you believe a criterion genuinely no longer fits and the work should return to an earlier phase, you must ask the user and get explicit confirmation first. Only after they confirm may you run \`assist backlog rewind ${itemId2} <phase> --reason "<reason>"\`. Never rewind silently.`
7300
+ ].filter((line) => line !== void 0).join("\n");
7301
+ }
7302
+
7303
+ // src/commands/backlog/buildPhasePrompt.ts
7304
+ var REVIEW_PHASE_NAME = "Review";
7305
+ function buildPhasePrompt(item, phaseNumber, phase) {
7306
+ const worktree = loadConfig().worktree;
7307
+ const commitBeforePhaseEnd = worktree?.commitBeforePhaseEnd ?? worktree?.commitBeforeManualChecks ?? false;
7308
+ if (phase.name === REVIEW_PHASE_NAME) {
7309
+ return buildReviewPrompt(item, phaseNumber, { commitBeforePhaseEnd });
7310
+ }
7311
+ return buildAuthoredPhasePrompt(item, phaseNumber, phase, {
7312
+ commitBeforePhaseEnd
7313
+ });
7314
+ }
7315
+
7316
+ // src/commands/backlog/buildReviewPhase.ts
7317
+ function buildReviewPhase() {
7318
+ return {
7319
+ name: REVIEW_PHASE_NAME,
7320
+ tasks: [
7321
+ {
7322
+ task: "Verify acceptance criteria, confirm manual checks, commit, mark done, and signal phase-done."
7323
+ }
7324
+ ]
7325
+ };
7326
+ }
7327
+
7328
+ // src/commands/backlog/appendReviewPhase.ts
7329
+ function appendReviewPhase(plan2) {
7330
+ if (plan2.at(-1)?.name === REVIEW_PHASE_NAME) return plan2;
7331
+ return [...plan2, buildReviewPhase()];
7332
+ }
7333
+
7334
+ // src/commands/backlog/resolvePlan.ts
7335
+ function resolvePlan(item) {
7336
+ if (item.plan && item.plan.length > 0) {
7337
+ return item.plan;
7338
+ }
7339
+ return [
7340
+ {
7341
+ name: "Implement",
7342
+ tasks: item.acceptanceCriteria.map((ac) => ({ task: ac }))
7343
+ }
7344
+ ];
7345
+ }
7346
+
7347
+ // src/commands/backlog/resolveRewindPlan.ts
7348
+ function resolveRewindPlan(item) {
7349
+ return appendReviewPhase(resolvePlan(item));
7350
+ }
7351
+
7352
+ // src/commands/backlog/writeSignal.ts
7353
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
7354
+ import { homedir as homedir8 } from "os";
7355
+ import { dirname as dirname9, join as join18 } from "path";
7356
+ import chalk10 from "chalk";
7357
+
7358
+ // src/commands/backlog/recordSignalOwner.ts
7359
+ import {
7360
+ existsSync as existsSync16,
7361
+ mkdirSync as mkdirSync4,
7362
+ readFileSync as readFileSync7,
7363
+ rmSync as rmSync3,
7364
+ writeFileSync as writeFileSync3
7365
+ } from "fs";
7366
+ import { homedir as homedir7 } from "os";
7367
+ import { dirname as dirname8, join as join17 } from "path";
7368
+ function getOwnerPath(itemId2) {
7369
+ return join17(homedir7(), ".assist", "signals", `owner-${itemId2}.json`);
7370
+ }
7371
+ function recordSignalOwner(itemId2) {
7372
+ const sessionId = process.env.ASSIST_SESSION_ID;
7373
+ if (!sessionId) return;
7374
+ const path108 = getOwnerPath(itemId2);
7375
+ mkdirSync4(dirname8(path108), { recursive: true });
7376
+ writeFileSync3(path108, JSON.stringify({ sessionId }));
7377
+ }
7378
+ function readSignalOwner(itemId2) {
7379
+ const path108 = getOwnerPath(itemId2);
7380
+ if (!existsSync16(path108)) return void 0;
7381
+ try {
7382
+ const parsed = JSON.parse(readFileSync7(path108, "utf8"));
7383
+ return parsed.sessionId;
7384
+ } catch {
7385
+ return void 0;
7386
+ }
7387
+ }
7388
+ function clearSignalOwner(itemId2) {
7389
+ const path108 = getOwnerPath(itemId2);
7390
+ try {
7391
+ rmSync3(path108);
7392
+ } catch {
7393
+ }
7394
+ }
7395
+
7396
+ // src/commands/backlog/writeSignal.ts
7397
+ function getSignalPath(sessionId = process.env.ASSIST_SESSION_ID) {
7398
+ if (!sessionId) return void 0;
7399
+ return join18(homedir8(), ".assist", "signals", `signal-${sessionId}.json`);
7400
+ }
7401
+ function resolveSignalTarget(event, data) {
7402
+ const caller = process.env.ASSIST_SESSION_ID;
7403
+ const itemId2 = typeof data?.itemId === "number" ? data.itemId : void 0;
7404
+ if (itemId2 === void 0) return caller;
7405
+ const owner = readSignalOwner(itemId2);
7406
+ if (owner) {
7407
+ if (caller && owner !== caller) {
7408
+ console.log(
7409
+ chalk10.dim(
7410
+ `Routing ${event} signal for item ${itemId2} to its run (${owner}), not this session (${caller}).`
7411
+ )
7412
+ );
7413
+ }
7414
+ return owner;
7415
+ }
7416
+ if (caller) {
7417
+ console.log(
7418
+ chalk10.yellow(
7419
+ `Not signalling ${event} for item ${itemId2}: no live run owns it, and this session (${caller}) does not either.`
7420
+ )
7421
+ );
7422
+ }
7423
+ return void 0;
7424
+ }
7425
+ function writeSignal(event, data) {
7426
+ const target = resolveSignalTarget(event, data);
7427
+ if (!target) return;
7428
+ const path108 = getSignalPath(target);
7429
+ if (!path108) return;
7430
+ const signal = { event, sessionId: target, ...data };
7431
+ mkdirSync5(dirname9(path108), { recursive: true });
7432
+ writeFileSync4(path108, JSON.stringify(signal));
7433
+ }
7434
+
7435
+ // src/commands/backlog/rewindItemToPhase.ts
7436
+ async function rewindItemToPhase(orm, item, phaseNumber, reason4) {
7437
+ const plan2 = resolveRewindPlan(item);
7438
+ if (phaseNumber < 1 || phaseNumber > plan2.length) {
7439
+ return {
7440
+ ok: false,
7441
+ error: `Phase ${phaseNumber} does not exist. Valid range: 1\u2013${plan2.length}.`
7442
+ };
7443
+ }
7444
+ const currentPhase = item.currentPhase ?? 1;
7445
+ if (phaseNumber >= currentPhase) {
7446
+ return {
7447
+ ok: false,
7448
+ error: `Phase ${phaseNumber} is not earlier than the current phase (${currentPhase}).`
7449
+ };
7450
+ }
7451
+ const phaseName = plan2[phaseNumber - 1].name;
7452
+ await appendComment(
7453
+ orm,
7454
+ item.id,
7455
+ `Rewound to phase ${phaseNumber} (${phaseName}): ${reason4}`,
7456
+ { phase: phaseNumber }
7457
+ );
7458
+ await updateCurrentPhase(orm, item.id, phaseNumber);
7459
+ await updateStatus(orm, item.id, "in-progress");
7460
+ writeSignal("rewind", { itemId: item.id, targetPhase: phaseNumber - 1 });
7461
+ return { ok: true, phaseName };
7462
+ }
7463
+
7464
+ // src/commands/backlog/web/rewindItemPhase.ts
7465
+ async function rewindItemPhase(req, res, id) {
7466
+ const { phase, reason: reason4 } = await parseRewindBody(req);
7467
+ const result = await findItemOr404(res, id);
7468
+ if (!result) return;
7469
+ const { orm, item } = result;
7470
+ const outcome = await rewindItemToPhase(orm, item, phase, reason4);
7471
+ if (!outcome.ok) {
7472
+ respondJson(res, 400, { error: outcome.error });
7473
+ return;
7474
+ }
7475
+ respondJson(res, 200, await loadItem(orm, id));
7476
+ }
7477
+
7478
+ // src/commands/backlog/show/loadLinkTargets.ts
7479
+ import { inArray as inArray3 } from "drizzle-orm";
7480
+ async function loadLinkTargets(orm, ids) {
7481
+ if (ids.length === 0) return [];
7482
+ const rows = await orm.select({ id: items.id, name: items.name, status: items.status }).from(items).where(inArray3(items.id, ids));
7483
+ return rows.map((r) => ({
7484
+ id: r.id,
7485
+ name: r.name,
7486
+ status: r.status
7487
+ }));
7488
+ }
7489
+
7490
+ // src/commands/sessions/shared/lookupSessionsById.ts
7491
+ import * as path8 from "path";
7492
+
7493
+ // src/commands/sessions/shared/codex/discoverCodexRolloutPaths.ts
7494
+ import * as fs from "fs";
7495
+ import * as path4 from "path";
7496
+
7497
+ // src/commands/sessions/shared/codex/codexSessionsDir.ts
7498
+ import { existsSync as existsSync17 } from "fs";
7499
+ import * as path3 from "path";
7500
+
7501
+ // src/shared/harnesses.ts
7502
+ import * as os from "os";
7503
+ import * as path2 from "path";
7504
+
7505
+ // src/shared/checkCliAvailable.ts
7506
+ import { execSync as execSync5 } from "child_process";
7507
+ function checkCliAvailable(cli) {
7508
+ const binary = cli.split(/\s+/)[0];
7509
+ const opts = {
7510
+ encoding: "utf8",
7511
+ stdio: ["ignore", "pipe", "pipe"],
7512
+ windowsHide: true
7513
+ };
7514
+ try {
7515
+ execSync5(`command -v ${binary}`, opts);
7516
+ return true;
7517
+ } catch {
7518
+ try {
7519
+ execSync5(`where ${binary}`, opts);
7520
+ return true;
7521
+ } catch {
7522
+ return false;
7523
+ }
7524
+ }
7525
+ }
7526
+
7527
+ // src/shared/harnesses.ts
7528
+ var harnesses = {
7529
+ claude: {
7530
+ kind: "claude",
7531
+ command: "claude",
7532
+ homeDir: path2.join(os.homedir(), ".claude"),
7533
+ sync: {
7534
+ commandDest: (name) => path2.join("skills", name, "SKILL.md")
7535
+ }
7536
+ },
7537
+ codex: {
7538
+ kind: "codex",
7539
+ command: "codex",
7540
+ homeDir: path2.join(os.homedir(), ".codex"),
7541
+ sync: {
7542
+ commandDest: (name) => path2.join("skills", name, "SKILL.md")
7543
+ }
7544
+ },
7545
+ pi: {
7546
+ kind: "pi",
7547
+ command: "pi",
7548
+ homeDir: path2.join(os.homedir(), ".pi", "agent"),
7549
+ sync: {
7550
+ commandDest: (name) => path2.join("prompts", `${name}.md`)
7551
+ }
7552
+ }
7553
+ };
7554
+ function isHarnessAvailable(kind) {
7555
+ return checkCliAvailable(harnesses[kind].command);
7556
+ }
7557
+
7558
+ // src/commands/sessions/shared/codex/codexSessionsDir.ts
7559
+ function codexSessionsDir() {
7560
+ return path3.join(harnesses.codex.homeDir, "sessions");
7561
+ }
7562
+ function hasCodexSessions() {
7563
+ return existsSync17(codexSessionsDir());
7564
+ }
7565
+
7566
+ // src/commands/sessions/shared/codex/discoverCodexRolloutPaths.ts
7567
+ var DATE_DEPTH = 3;
7568
+ function isCodexRolloutFile(name) {
7569
+ return name.startsWith("rollout-") && name.endsWith(".jsonl");
7570
+ }
7571
+ async function discoverCodexRolloutPaths() {
7572
+ if (!hasCodexSessions()) return [];
7573
+ return collect(codexSessionsDir(), DATE_DEPTH);
7574
+ }
7575
+ async function collect(dir, depth) {
7576
+ let entries;
7577
+ try {
7578
+ entries = await fs.promises.readdir(dir, { withFileTypes: true });
7579
+ } catch {
7580
+ return [];
7581
+ }
7582
+ const nested = await Promise.all(
7583
+ entries.map((entry) => collectEntry(dir, entry, depth))
7584
+ );
7585
+ return nested.flat();
7586
+ }
7587
+ async function collectEntry(dir, entry, depth) {
7588
+ const full = path4.join(dir, entry.name);
7589
+ if (entry.isDirectory()) return depth > 0 ? collect(full, depth - 1) : [];
7590
+ return isCodexRolloutFile(entry.name) ? [full] : [];
7591
+ }
7592
+
7593
+ // src/commands/sessions/shared/codex/parseCodexSessionFile.ts
7594
+ import * as fs3 from "fs";
7595
+ import * as path5 from "path";
7596
+
7597
+ // src/commands/sessions/shared/backlogRunMarkers.ts
7598
+ function backlogRunMarkers(text18) {
7599
+ const match = text18.match(/backlog item a(\d+):[ \t]*([^\n]*)/);
7600
+ if (!match) return { commandName: "", commandArgs: "" };
7601
+ const title = match[2].trim();
7602
+ return {
7603
+ commandName: "next",
7604
+ commandArgs: title ? `${match[1]} ${title}` : match[1]
7605
+ };
7606
+ }
7607
+
7608
+ // src/commands/sessions/shared/deriveHistoryFields.ts
7609
+ var KNOWN = [
7610
+ "draft",
7611
+ "next",
7612
+ "bug",
7613
+ "refine",
7614
+ "review",
7615
+ "run",
7616
+ "update"
7617
+ ];
7618
+ function deriveHistoryFields(commandName, commandArgs, name) {
7619
+ const fields = {};
7620
+ const sessionType = deriveSessionType(commandName, name);
7621
+ if (sessionType) fields.sessionType = sessionType;
7622
+ const itemMatch = commandArgs.match(/^(\d+)\b\s*/);
7623
+ if (itemMatch) fields.itemId = Number(itemMatch[1]);
7624
+ const prompt = commandArgs.slice(itemMatch?.[0].length ?? 0).trim();
7625
+ if (prompt) fields.prompt = prompt;
7626
+ return fields;
7627
+ }
7628
+ function deriveSessionType(commandName, name) {
7629
+ if (commandName === "review-pr-comments" || commandName === "fix-conflict")
7630
+ return "review";
7631
+ if (KNOWN.includes(commandName))
7632
+ return commandName;
7633
+ if (commandName || name) return "prompt";
7634
+ return void 0;
7635
+ }
7636
+
7637
+ // src/commands/sessions/shared/codex/parseRolloutEntry.ts
7638
+ function parseRolloutEntry(line) {
7639
+ try {
7640
+ const parsed = JSON.parse(line);
7641
+ return parsed && typeof parsed === "object" ? parsed : null;
7642
+ } catch {
7643
+ return null;
7678
7644
  }
7679
- return null;
7680
7645
  }
7681
- function parseGitStatus(output) {
7682
- const result = { new: [], modified: [], deleted: [] };
7683
- for (const line of output.split("\n")) {
7684
- if (line.length < 4) continue;
7685
- const category = categorize2(line.slice(0, 2));
7686
- if (category) result[category].push(extractPath(line.slice(3)));
7687
- }
7688
- return result;
7646
+ function rolloutPayload(entry) {
7647
+ return entry.payload && typeof entry.payload === "object" ? entry.payload : {};
7648
+ }
7649
+ function rolloutString(value) {
7650
+ return typeof value === "string" ? value : "";
7689
7651
  }
7690
7652
 
7691
- // src/commands/sessions/web/workingTreeCounts.ts
7692
- async function workingTreeCounts(cwd) {
7693
- const output = await execGit(cwd, [
7694
- "status",
7695
- "--porcelain",
7696
- "--untracked-files=all"
7697
- ]);
7698
- return parseGitStatus(output);
7653
+ // src/commands/sessions/shared/codex/codexRolloutMeta.ts
7654
+ var MAX_FIRST_MESSAGE = 2e3;
7655
+ function codexRolloutMeta(lines2) {
7656
+ const meta = {
7657
+ sessionId: "",
7658
+ cwd: "",
7659
+ timestamp: "",
7660
+ firstMessage: ""
7661
+ };
7662
+ for (const line of lines2) {
7663
+ const entry = parseRolloutEntry(line);
7664
+ if (!entry) continue;
7665
+ applySessionMeta(meta, entry);
7666
+ meta.firstMessage ||= firstUserMessage(entry);
7667
+ if (meta.sessionId && meta.firstMessage) break;
7668
+ }
7669
+ return meta;
7670
+ }
7671
+ function applySessionMeta(meta, entry) {
7672
+ if (entry.type !== "session_meta") return;
7673
+ const payload = rolloutPayload(entry);
7674
+ meta.sessionId ||= rolloutString(payload.session_id);
7675
+ meta.cwd ||= rolloutString(payload.cwd);
7676
+ meta.timestamp ||= rolloutString(payload.timestamp);
7677
+ }
7678
+ function firstUserMessage(entry) {
7679
+ if (entry.type !== "event_msg") return "";
7680
+ const payload = rolloutPayload(entry);
7681
+ if (payload.type !== "user_message") return "";
7682
+ return rolloutString(payload.message).trim().slice(0, MAX_FIRST_MESSAGE);
7699
7683
  }
7700
7684
 
7701
- // src/commands/sessions/web/gitStatus.ts
7702
- async function gitStatus(req, res) {
7703
- const cwd = getCwdParam(req, res);
7704
- if (!cwd) return;
7685
+ // src/commands/sessions/shared/codex/readCodexHeadLines.ts
7686
+ import * as fs2 from "fs";
7687
+ import * as readline from "readline";
7688
+ var DEFAULT_MAX_LINES = 80;
7689
+ async function readCodexHeadLines(filePath, maxLines = DEFAULT_MAX_LINES) {
7690
+ const stream = fs2.createReadStream(filePath, { encoding: "utf8" });
7691
+ const reader = readline.createInterface({ input: stream });
7692
+ const lines2 = [];
7705
7693
  try {
7706
- const branch2 = await gitBranchInfo(cwd);
7707
- const changeSet = await itemChangeSet(cwd, getSessionParam(req)).catch(
7708
- () => void 0
7709
- );
7710
- if (!changeSet) {
7711
- respondJson(res, 200, { ...await workingTreeCounts(cwd), ...branch2 });
7712
- return;
7694
+ for await (const line of reader) {
7695
+ if (line) lines2.push(line);
7696
+ if (lines2.length >= maxLines) break;
7713
7697
  }
7714
- const [counts2, uncommitted] = await Promise.all([
7715
- groupedCounts(cwd, changeSet.groups),
7716
- workingTreeCounts(cwd)
7717
- ]);
7718
- respondJson(res, 200, {
7719
- ...counts2,
7720
- ...branch2,
7721
- uncommitted,
7722
- hasCommits: changeSet.commits.length > 0
7723
- });
7724
7698
  } catch {
7725
- respondJson(res, 200, { new: [], modified: [], deleted: [] });
7699
+ return lines2;
7700
+ } finally {
7701
+ reader.close();
7702
+ stream.destroy();
7726
7703
  }
7704
+ return lines2;
7727
7705
  }
7728
7706
 
7729
- // src/commands/backlog/deleteComment.ts
7730
- import { and as and7, eq as eq18 } from "drizzle-orm";
7731
- async function deleteComment(orm, itemId2, commentId) {
7732
- const [row3] = await orm.select({ type: comments.type }).from(comments).where(and7(eq18(comments.id, commentId), eq18(comments.itemId, itemId2)));
7733
- if (!row3) return "not-found";
7734
- if (row3.type === "summary") return "is-summary";
7735
- await orm.delete(comments).where(and7(eq18(comments.id, commentId), eq18(comments.itemId, itemId2)));
7736
- return "deleted";
7737
- }
7738
-
7739
- // src/commands/backlog/web/deleteItemComment.ts
7740
- async function deleteItemComment(res, itemId2, commentId) {
7741
- const result = await findItemOr404(res, itemId2);
7742
- if (!result) return;
7743
- const outcome = await deleteComment(result.orm, itemId2, commentId);
7744
- if (outcome === "not-found") {
7745
- respondJson(res, 404, {
7746
- error: `Comment #${commentId} not found on item ${formatItemId(itemId2)}.`
7747
- });
7748
- return;
7749
- }
7750
- if (outcome === "is-summary") {
7751
- respondJson(res, 400, {
7752
- error: `Comment #${commentId} is a phase summary and cannot be deleted.`
7753
- });
7754
- return;
7707
+ // src/commands/sessions/shared/codex/parseCodexSessionFile.ts
7708
+ async function parseCodexSessionFile(filePath) {
7709
+ try {
7710
+ const meta = codexRolloutMeta(await readCodexHeadLines(filePath));
7711
+ if (!meta.sessionId) return null;
7712
+ const name = meta.firstMessage.slice(0, 80);
7713
+ const markers = backlogRunMarkers(meta.firstMessage);
7714
+ return {
7715
+ sessionId: meta.sessionId,
7716
+ name: name || `Session ${meta.sessionId.slice(0, 8)}`,
7717
+ project: meta.cwd ? path5.basename(meta.cwd) : "",
7718
+ cwd: meta.cwd,
7719
+ timestamp: meta.timestamp || await mtime(filePath),
7720
+ origin: "wsl",
7721
+ harness: "codex",
7722
+ ...deriveHistoryFields(markers.commandName, markers.commandArgs, name)
7723
+ };
7724
+ } catch {
7725
+ return null;
7755
7726
  }
7756
- respondJson(res, 200, await loadItem(result.orm, itemId2));
7757
7727
  }
7758
-
7759
- // src/commands/backlog/appendComment.ts
7760
- import { sql as sql16 } from "drizzle-orm";
7761
- async function appendComment(orm, itemId2, text18, opts = {}) {
7762
- await orm.insert(comments).values({
7763
- itemId: itemId2,
7764
- idx: sql16`(SELECT COALESCE(MAX(${comments.idx}) + 1, 0) FROM ${comments} WHERE ${comments.itemId} = ${itemId2})`,
7765
- text: text18,
7766
- phase: opts.phase ?? null,
7767
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7768
- type: opts.type ?? "comment"
7769
- });
7728
+ async function mtime(filePath) {
7729
+ return (await fs3.promises.stat(filePath)).mtime.toISOString();
7770
7730
  }
7771
7731
 
7772
- // src/commands/backlog/writeSignal.ts
7773
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
7774
- import { homedir as homedir10 } from "os";
7775
- import { dirname as dirname10, join as join22 } from "path";
7776
- import chalk10 from "chalk";
7732
+ // src/commands/sessions/shared/parseSessionFile.ts
7733
+ import * as fs4 from "fs";
7777
7734
 
7778
- // src/commands/backlog/recordSignalOwner.ts
7779
- import {
7780
- existsSync as existsSync17,
7781
- mkdirSync as mkdirSync4,
7782
- readFileSync as readFileSync7,
7783
- rmSync as rmSync3,
7784
- writeFileSync as writeFileSync3
7785
- } from "fs";
7786
- import { homedir as homedir9 } from "os";
7787
- import { dirname as dirname9, join as join21 } from "path";
7788
- function getOwnerPath(itemId2) {
7789
- return join21(homedir9(), ".assist", "signals", `owner-${itemId2}.json`);
7735
+ // src/commands/sessions/shared/extractSessionMeta.ts
7736
+ function extractSessionMeta(lines2) {
7737
+ let sessionId = "";
7738
+ let cwd = "";
7739
+ let timestamp8 = "";
7740
+ let name = "";
7741
+ let commandName = "";
7742
+ let commandArgs = "";
7743
+ for (const line of lines2) {
7744
+ const entry = safeParse(line);
7745
+ if (!entry) continue;
7746
+ sessionId ||= strField(entry, "sessionId");
7747
+ timestamp8 ||= strField(entry, "timestamp");
7748
+ cwd ||= strField(entry, "cwd");
7749
+ if (entry.type === "user" && !entry.isMeta) {
7750
+ ({ name, commandName, commandArgs } = parseFirstUserEntry(entry));
7751
+ break;
7752
+ }
7753
+ }
7754
+ return { sessionId, cwd, timestamp: timestamp8, name, commandName, commandArgs };
7790
7755
  }
7791
- function recordSignalOwner(itemId2) {
7792
- const sessionId = process.env.ASSIST_SESSION_ID;
7793
- if (!sessionId) return;
7794
- const path108 = getOwnerPath(itemId2);
7795
- mkdirSync4(dirname9(path108), { recursive: true });
7796
- writeFileSync3(path108, JSON.stringify({ sessionId }));
7756
+ function parseFirstUserEntry(entry) {
7757
+ const raw = messageText(entry);
7758
+ const commandName = matchMarker(raw, "command-name").replace(/^\//, "");
7759
+ const commandArgs = matchMarker(raw, "command-args");
7760
+ const name = stripMarkers(raw);
7761
+ if (commandName) return { name, commandName, commandArgs };
7762
+ return { name, ...backlogRunMarkers(raw) };
7797
7763
  }
7798
- function readSignalOwner(itemId2) {
7799
- const path108 = getOwnerPath(itemId2);
7800
- if (!existsSync17(path108)) return void 0;
7764
+ function strField(entry, key) {
7765
+ const value = entry[key];
7766
+ return typeof value === "string" ? value : "";
7767
+ }
7768
+ function safeParse(line) {
7801
7769
  try {
7802
- const parsed = JSON.parse(readFileSync7(path108, "utf8"));
7803
- return parsed.sessionId;
7770
+ return JSON.parse(line);
7804
7771
  } catch {
7805
- return void 0;
7772
+ return null;
7806
7773
  }
7807
7774
  }
7808
- function clearSignalOwner(itemId2) {
7809
- const path108 = getOwnerPath(itemId2);
7775
+ function messageText(entry) {
7776
+ const msg = entry.message;
7777
+ const content = msg?.content;
7778
+ return typeof content === "string" ? content : Array.isArray(content) ? content.find((c) => c.type === "text")?.text ?? "" : "";
7779
+ }
7780
+ function stripMarkers(text18) {
7781
+ return text18.replace(/<command-[^>]*>[^<]*<\/command-[^>]*>/g, "").trim().slice(0, 80);
7782
+ }
7783
+ function matchMarker(text18, tag) {
7784
+ const match = text18.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
7785
+ return match ? match[1].trim() : "";
7786
+ }
7787
+
7788
+ // src/commands/sessions/shared/deriveProject.ts
7789
+ import * as path6 from "path";
7790
+ function deriveProject(cwd, filePath, origin) {
7791
+ if (!cwd) return dirNameToProject(filePath);
7792
+ return origin === "windows" ? path6.win32.basename(cwd) : path6.basename(cwd);
7793
+ }
7794
+ function dirNameToProject(filePath) {
7795
+ const dirName = path6.basename(path6.dirname(filePath));
7796
+ const parts = dirName.split("--");
7797
+ return parts[parts.length - 1].replace(/-/g, "/");
7798
+ }
7799
+
7800
+ // src/commands/sessions/shared/parseSessionFile.ts
7801
+ async function parseSessionFile(filePath, origin = "wsl") {
7802
+ let handle;
7810
7803
  try {
7811
- rmSync3(path108);
7804
+ handle = await fs4.promises.open(filePath, "r");
7805
+ const meta = extractSessionMeta(await readHeadLines(handle));
7806
+ if (!meta.sessionId) return null;
7807
+ const timestamp8 = meta.timestamp || (await fs4.promises.stat(filePath)).mtime.toISOString();
7808
+ return {
7809
+ sessionId: meta.sessionId,
7810
+ name: meta.name || `Session ${meta.sessionId.slice(0, 8)}`,
7811
+ project: deriveProject(meta.cwd, filePath, origin),
7812
+ cwd: meta.cwd,
7813
+ timestamp: timestamp8,
7814
+ origin,
7815
+ ...deriveHistoryFields(meta.commandName, meta.commandArgs, meta.name)
7816
+ };
7812
7817
  } catch {
7818
+ return null;
7819
+ } finally {
7820
+ await handle?.close();
7813
7821
  }
7814
7822
  }
7815
-
7816
- // src/commands/backlog/writeSignal.ts
7817
- function getSignalPath(sessionId = process.env.ASSIST_SESSION_ID) {
7818
- if (!sessionId) return void 0;
7819
- return join22(homedir10(), ".assist", "signals", `signal-${sessionId}.json`);
7823
+ async function readHeadLines(handle) {
7824
+ const buf = Buffer.alloc(16384);
7825
+ const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
7826
+ return buf.toString("utf8", 0, bytesRead).split("\n").filter(Boolean);
7820
7827
  }
7821
- function resolveSignalTarget(event, data) {
7822
- const caller = process.env.ASSIST_SESSION_ID;
7823
- const itemId2 = typeof data?.itemId === "number" ? data.itemId : void 0;
7824
- if (itemId2 === void 0) return caller;
7825
- const owner = readSignalOwner(itemId2);
7826
- if (owner) {
7827
- if (caller && owner !== caller) {
7828
- console.log(
7829
- chalk10.dim(
7830
- `Routing ${event} signal for item ${itemId2} to its run (${owner}), not this session (${caller}).`
7831
- )
7828
+
7829
+ // src/commands/sessions/shared/discoverSessionJsonlPaths.ts
7830
+ import * as fs5 from "fs";
7831
+ import * as os2 from "os";
7832
+ import * as path7 from "path";
7833
+ async function discoverSessionJsonlPaths() {
7834
+ const results = [];
7835
+ await Promise.all(
7836
+ sessionRoots().map(async ({ dir, origin }) => {
7837
+ let projectDirs;
7838
+ try {
7839
+ projectDirs = await fs5.promises.readdir(dir);
7840
+ } catch {
7841
+ return;
7842
+ }
7843
+ await Promise.all(
7844
+ projectDirs.map(async (dirName) => {
7845
+ const dirPath = path7.join(dir, dirName);
7846
+ let entries;
7847
+ try {
7848
+ entries = await fs5.promises.readdir(dirPath);
7849
+ } catch {
7850
+ return;
7851
+ }
7852
+ for (const file of entries) {
7853
+ if (file.endsWith(".jsonl"))
7854
+ results.push({ path: path7.join(dirPath, file), origin });
7855
+ }
7856
+ })
7832
7857
  );
7833
- }
7834
- return owner;
7835
- }
7836
- if (caller) {
7837
- console.log(
7838
- chalk10.yellow(
7839
- `Not signalling ${event} for item ${itemId2}: no live run owns it, and this session (${caller}) does not either.`
7840
- )
7841
- );
7842
- }
7843
- return void 0;
7858
+ })
7859
+ );
7860
+ return results;
7844
7861
  }
7845
- function writeSignal(event, data) {
7846
- const target = resolveSignalTarget(event, data);
7847
- if (!target) return;
7848
- const path108 = getSignalPath(target);
7849
- if (!path108) return;
7850
- const signal = { event, sessionId: target, ...data };
7851
- mkdirSync5(dirname10(path108), { recursive: true });
7852
- writeFileSync4(path108, JSON.stringify(signal));
7862
+ function sessionRoots() {
7863
+ return [
7864
+ { dir: path7.join(os2.homedir(), ".claude", "projects"), origin: "wsl" }
7865
+ ];
7853
7866
  }
7854
7867
 
7855
- // src/commands/backlog/rewindItemToPhase.ts
7856
- async function rewindItemToPhase(orm, item, phaseNumber, reason4) {
7857
- const plan2 = resolveRewindPlan(item);
7858
- if (phaseNumber < 1 || phaseNumber > plan2.length) {
7859
- return {
7860
- ok: false,
7861
- error: `Phase ${phaseNumber} does not exist. Valid range: 1\u2013${plan2.length}.`
7862
- };
7868
+ // src/commands/sessions/shared/lookupSessionsById.ts
7869
+ var CODEX_ROLLOUT_ID_LENGTH = 36;
7870
+ async function lookupSessionsById(ids) {
7871
+ const wanted = new Set(ids);
7872
+ if (wanted.size === 0) return /* @__PURE__ */ new Map();
7873
+ const found = /* @__PURE__ */ new Map();
7874
+ const [claudePaths, codexPaths] = await Promise.all([
7875
+ discoverSessionJsonlPaths(),
7876
+ discoverCodexRolloutPaths()
7877
+ ]);
7878
+ await Promise.all([
7879
+ ...claudePaths.map(async ({ path: filePath, origin }) => {
7880
+ const id = path8.basename(filePath, ".jsonl");
7881
+ if (!wanted.has(id)) return;
7882
+ const session = await parseSessionFile(filePath, origin);
7883
+ if (session) found.set(id, session);
7884
+ }),
7885
+ ...codexPaths.map(async (filePath) => {
7886
+ const id = codexRolloutIdFromFilename(filePath);
7887
+ if (!id || !wanted.has(id)) return;
7888
+ const session = await parseCodexSessionFile(filePath);
7889
+ if (session) found.set(id, session);
7890
+ })
7891
+ ]);
7892
+ return found;
7893
+ }
7894
+ function codexRolloutIdFromFilename(filePath) {
7895
+ const name = path8.basename(filePath, ".jsonl");
7896
+ return name.length > CODEX_ROLLOUT_ID_LENGTH ? name.slice(-CODEX_ROLLOUT_ID_LENGTH) : void 0;
7897
+ }
7898
+
7899
+ // src/commands/backlog/web/itemConversations.ts
7900
+ async function itemConversations(item) {
7901
+ const byId = /* @__PURE__ */ new Map();
7902
+ for (const ref of item.gitRefs ?? []) {
7903
+ if (ref.kind !== "session" || byId.has(ref.ref)) continue;
7904
+ byId.set(ref.ref, {
7905
+ sessionId: ref.ref,
7906
+ ...ref.title && { title: ref.title },
7907
+ ...ref.createdAt && { createdAt: ref.createdAt }
7908
+ });
7863
7909
  }
7864
- const currentPhase = item.currentPhase ?? 1;
7865
- if (phaseNumber >= currentPhase) {
7866
- return {
7867
- ok: false,
7868
- error: `Phase ${phaseNumber} is not earlier than the current phase (${currentPhase}).`
7869
- };
7910
+ for (const session of item.phaseSessions ?? []) {
7911
+ const { claudeSessionId: sessionId, createdAt } = session;
7912
+ if (byId.has(sessionId)) continue;
7913
+ byId.set(sessionId, { sessionId, ...createdAt && { createdAt } });
7870
7914
  }
7871
- const phaseName = plan2[phaseNumber - 1].name;
7872
- await appendComment(
7873
- orm,
7874
- item.id,
7875
- `Rewound to phase ${phaseNumber} (${phaseName}): ${reason4}`,
7876
- { phase: phaseNumber }
7877
- );
7878
- await updateCurrentPhase(orm, item.id, phaseNumber);
7879
- await updateStatus(orm, item.id, "in-progress");
7880
- writeSignal("rewind", { itemId: item.id, targetPhase: phaseNumber - 1 });
7881
- return { ok: true, phaseName };
7915
+ if (byId.size === 0) return [];
7916
+ const transcripts = await lookupSessionsById([...byId.keys()]);
7917
+ return [...byId.values()].map(
7918
+ (conversation) => withTranscript(conversation, transcripts.get(conversation.sessionId))
7919
+ ).sort(newestFirst);
7920
+ }
7921
+ function withTranscript(conversation, transcript) {
7922
+ if (!transcript) return conversation;
7923
+ return {
7924
+ ...conversation,
7925
+ ...transcript.cwd && { cwd: transcript.cwd },
7926
+ ...transcript.harness && { harness: transcript.harness },
7927
+ title: conversation.title ?? transcript.name,
7928
+ createdAt: conversation.createdAt ?? transcript.timestamp
7929
+ };
7930
+ }
7931
+ function sortKey(conversation) {
7932
+ const parsed = Date.parse(conversation.createdAt ?? "");
7933
+ return Number.isNaN(parsed) ? 0 : parsed;
7934
+ }
7935
+ function newestFirst(a, b) {
7936
+ return sortKey(b) - sortKey(a);
7882
7937
  }
7883
7938
 
7884
- // src/commands/backlog/web/rewindItemPhase.ts
7885
- async function rewindItemPhase(req, res, id) {
7886
- const { phase, reason: reason4 } = await parseRewindBody(req);
7939
+ // src/commands/backlog/web/withReviewPhase.ts
7940
+ function withReviewPhase(item) {
7941
+ if (!item.plan || item.plan.length === 0) return item;
7942
+ return { ...item, plan: resolveRewindPlan(item) };
7943
+ }
7944
+
7945
+ // src/commands/backlog/web/getItemById.ts
7946
+ async function getItemById(res, id) {
7887
7947
  const result = await findItemOr404(res, id);
7888
7948
  if (!result) return;
7889
- const { orm, item } = result;
7890
- const outcome = await rewindItemToPhase(orm, item, phase, reason4);
7891
- if (!outcome.ok) {
7892
- respondJson(res, 400, { error: outcome.error });
7893
- return;
7894
- }
7895
- respondJson(res, 200, await loadItem(orm, id));
7949
+ const item = withReviewPhase(result.item);
7950
+ const conversations = await itemConversations(item);
7951
+ if (conversations.length > 0) item.conversations = conversations;
7952
+ const docsRefreshFor = await loadLinkTargets(
7953
+ result.orm,
7954
+ (item.links ?? []).filter((l) => l.type === "docs-refresh").map((l) => l.targetId)
7955
+ );
7956
+ if (docsRefreshFor.length > 0) item.docsRefreshFor = docsRefreshFor;
7957
+ respondJson(res, 200, item);
7896
7958
  }
7897
7959
 
7898
7960
  // src/commands/backlog/web/handleItemRoute.ts
@@ -7962,12 +8024,12 @@ function allowAppOrigin(req, res, pathname) {
7962
8024
  }
7963
8025
 
7964
8026
  // src/shared/db/dismissInboxMessage.ts
7965
- import { and as and8, eq as eq19, isNull } from "drizzle-orm";
8027
+ import { and as and9, eq as eq20, isNull } from "drizzle-orm";
7966
8028
  async function dismissInboxMessage(orm, source, messageId) {
7967
8029
  const dismissed = await orm.update(inboxMessages).set({ dismissedAt: /* @__PURE__ */ new Date() }).where(
7968
- and8(
7969
- eq19(inboxMessages.source, source),
7970
- eq19(inboxMessages.messageId, messageId),
8030
+ and9(
8031
+ eq20(inboxMessages.source, source),
8032
+ eq20(inboxMessages.messageId, messageId),
7971
8033
  isNull(inboxMessages.dismissedAt)
7972
8034
  )
7973
8035
  ).returning({ messageId: inboxMessages.messageId });
@@ -9394,14 +9456,14 @@ function listScopedRules(req, res) {
9394
9456
  }
9395
9457
 
9396
9458
  // src/shared/db/countUsagePeaks.ts
9397
- import { count, eq as eq20 } from "drizzle-orm";
9459
+ import { count, eq as eq21 } from "drizzle-orm";
9398
9460
  async function countUsagePeaks(db, window) {
9399
- const [row3] = await db.select({ value: count() }).from(usagePeaks).where(window ? eq20(usagePeaks.window, window) : void 0);
9461
+ const [row3] = await db.select({ value: count() }).from(usagePeaks).where(window ? eq21(usagePeaks.window, window) : void 0);
9400
9462
  return row3?.value ?? 0;
9401
9463
  }
9402
9464
 
9403
9465
  // src/shared/db/listUsagePeaks.ts
9404
- import { and as and9, desc as desc4, eq as eq21, getTableColumns } from "drizzle-orm";
9466
+ import { and as and10, desc as desc4, eq as eq22, getTableColumns } from "drizzle-orm";
9405
9467
 
9406
9468
  // src/shared/db/avgContextPerCycle.ts
9407
9469
  import { count as count2, sql as sql18 } from "drizzle-orm";
@@ -9425,11 +9487,11 @@ async function listUsagePeaks(db, options2) {
9425
9487
  phaseCount: avgContext.phaseCount
9426
9488
  }).from(usagePeaks).leftJoin(
9427
9489
  avgContext,
9428
- and9(
9429
- eq21(usagePeaks.window, avgContext.window),
9430
- eq21(usagePeaks.resetsAt, avgContext.resetsAt)
9490
+ and10(
9491
+ eq22(usagePeaks.window, avgContext.window),
9492
+ eq22(usagePeaks.resetsAt, avgContext.resetsAt)
9431
9493
  )
9432
- ).where(options2?.window ? eq21(usagePeaks.window, options2.window) : void 0).orderBy(
9494
+ ).where(options2?.window ? eq22(usagePeaks.window, options2.window) : void 0).orderBy(
9433
9495
  desc4(usagePeaks.resetsAt),
9434
9496
  usagePeaks.window,
9435
9497
  desc4(usagePeaks.createdAt),
@@ -9498,26 +9560,26 @@ function listUsageHistory(req, res) {
9498
9560
  }
9499
9561
 
9500
9562
  // src/shared/db/countItemUsageByOrigin.ts
9501
- import { asc as asc8, desc as desc5, eq as eq22, sql as sql19 } from "drizzle-orm";
9563
+ import { asc as asc9, desc as desc5, eq as eq23, sql as sql19 } from "drizzle-orm";
9502
9564
  async function countItemUsageByOrigin(db) {
9503
9565
  const count10 = sql19`count(distinct ${phaseUsage.itemId})::int`;
9504
- const rows = await db.select({ origin: items.origin, count: count10 }).from(items).innerJoin(phaseUsage, eq22(phaseUsage.itemId, items.id)).groupBy(items.origin).orderBy(desc5(count10), asc8(items.origin));
9566
+ const rows = await db.select({ origin: items.origin, count: count10 }).from(items).innerJoin(phaseUsage, eq23(phaseUsage.itemId, items.id)).groupBy(items.origin).orderBy(desc5(count10), asc9(items.origin));
9505
9567
  return rows.map((row3) => ({ origin: row3.origin, count: Number(row3.count) }));
9506
9568
  }
9507
9569
 
9508
9570
  // src/shared/db/itemUsageWhere.ts
9509
- import { and as and10, eq as eq23, notInArray } from "drizzle-orm";
9571
+ import { and as and11, eq as eq24, notInArray } from "drizzle-orm";
9510
9572
  function parseItemUsageStatus(value) {
9511
9573
  return value === "done" || value === "running" ? value : void 0;
9512
9574
  }
9513
9575
  function statusClause(status2) {
9514
- if (status2 === "done") return eq23(items.status, "done");
9576
+ if (status2 === "done") return eq24(items.status, "done");
9515
9577
  if (status2 === "running") return notInArray(items.status, ["done", "wontdo"]);
9516
9578
  return void 0;
9517
9579
  }
9518
9580
  function itemUsageWhere(filter) {
9519
- return and10(
9520
- filter?.origin ? eq23(items.origin, filter.origin) : void 0,
9581
+ return and11(
9582
+ filter?.origin ? eq24(items.origin, filter.origin) : void 0,
9521
9583
  statusClause(filter?.status)
9522
9584
  );
9523
9585
  }
@@ -9542,7 +9604,7 @@ function parseItemUsageSort(field, direction) {
9542
9604
  }
9543
9605
 
9544
9606
  // src/shared/db/itemUsageStats.ts
9545
- import { eq as eq24, sql as sql20 } from "drizzle-orm";
9607
+ import { eq as eq25, sql as sql20 } from "drizzle-orm";
9546
9608
  function median(value) {
9547
9609
  return sql20`coalesce(percentile_cont(0.5) within group (order by (${value})::double precision), 0)`;
9548
9610
  }
@@ -9558,7 +9620,7 @@ async function itemUsageStats(db, options2) {
9558
9620
  ),
9559
9621
  medianActiveMs: median(totals.activeMs),
9560
9622
  medianTokens: median(sql20`${totals.tokensUp} + ${totals.tokensDown}`)
9561
- }).from(items).innerJoin(totals, eq24(totals.itemId, items.id)).leftJoin(planned, eq24(planned.itemId, items.id)).where(itemUsageWhere(options2));
9623
+ }).from(items).innerJoin(totals, eq25(totals.itemId, items.id)).leftJoin(planned, eq25(planned.itemId, items.id)).where(itemUsageWhere(options2));
9562
9624
  return {
9563
9625
  itemCount: Number(row3?.itemCount ?? 0),
9564
9626
  doneCount: Number(row3?.doneCount ?? 0),
@@ -9570,7 +9632,7 @@ async function itemUsageStats(db, options2) {
9570
9632
  }
9571
9633
 
9572
9634
  // src/shared/db/listItemUsageSummaries.ts
9573
- import { eq as eq25 } from "drizzle-orm";
9635
+ import { eq as eq26 } from "drizzle-orm";
9574
9636
 
9575
9637
  // src/shared/db/itemUsageOrderBy.ts
9576
9638
  import { desc as desc6, sql as sql21 } from "drizzle-orm";
@@ -9631,7 +9693,7 @@ async function listItemUsageSummaries(db, options2) {
9631
9693
  activeMs: totals.activeMs,
9632
9694
  peakContextPct: totals.peakContextPct,
9633
9695
  lastPhaseAt: lastPhase.atIso
9634
- }).from(items).innerJoin(totals, eq25(totals.itemId, items.id)).leftJoin(planned, eq25(planned.itemId, items.id)).leftJoin(lastPhase, eq25(lastPhase.itemId, items.id)).where(itemUsageWhere(options2)).orderBy(
9696
+ }).from(items).innerJoin(totals, eq26(totals.itemId, items.id)).leftJoin(planned, eq26(planned.itemId, items.id)).leftJoin(lastPhase, eq26(lastPhase.itemId, items.id)).where(itemUsageWhere(options2)).orderBy(
9635
9697
  ...itemUsageOrderBy({ totals, planned, lastPhase }, options2?.sort)
9636
9698
  );
9637
9699
  const rows = options2?.limit === void 0 ? await query : await query.limit(options2.limit).offset(options2.offset ?? 0);
@@ -11424,10 +11486,10 @@ function currentRepoLabel(shared, origin) {
11424
11486
  }
11425
11487
 
11426
11488
  // src/shared/db/saveRepoConfig.ts
11427
- import { eq as eq26 } from "drizzle-orm";
11489
+ import { eq as eq27 } from "drizzle-orm";
11428
11490
  async function saveRepoConfig(orm, repoKey, config) {
11429
11491
  if (Object.keys(config).length === 0) {
11430
- await orm.delete(repoConfigs).where(eq26(repoConfigs.repoKey, repoKey));
11492
+ await orm.delete(repoConfigs).where(eq27(repoConfigs.repoKey, repoKey));
11431
11493
  return;
11432
11494
  }
11433
11495
  await orm.insert(repoConfigs).values({ repoKey, config }).onConflictDoUpdate({
@@ -11486,14 +11548,14 @@ async function applySharedRepoConfigSet(key, coerced, repoName, cwd = process.cw
11486
11548
  }
11487
11549
 
11488
11550
  // src/shared/db/updateUserConfig.ts
11489
- import { eq as eq27 } from "drizzle-orm";
11551
+ import { eq as eq28 } from "drizzle-orm";
11490
11552
  async function updateUserConfig(orm, update4) {
11491
11553
  return orm.transaction(async (tx) => {
11492
- const [row3] = await tx.select().from(userConfig).where(eq27(userConfig.id, 1)).for("update");
11554
+ const [row3] = await tx.select().from(userConfig).where(eq28(userConfig.id, 1)).for("update");
11493
11555
  const outcome = update4(row3?.config ?? {});
11494
11556
  if (!outcome.ok) return outcome;
11495
11557
  if (Object.keys(outcome.config).length === 0) {
11496
- await tx.delete(userConfig).where(eq27(userConfig.id, 1));
11558
+ await tx.delete(userConfig).where(eq28(userConfig.id, 1));
11497
11559
  return outcome;
11498
11560
  }
11499
11561
  await tx.insert(userConfig).values({ id: 1, config: outcome.config }).onConflictDoUpdate({
@@ -15882,11 +15944,11 @@ function registerActivityCommands(cmd) {
15882
15944
 
15883
15945
  // src/commands/backlog/associateGithub.ts
15884
15946
  import chalk45 from "chalk";
15885
- import { eq as eq29 } from "drizzle-orm";
15947
+ import { eq as eq30 } from "drizzle-orm";
15886
15948
 
15887
15949
  // src/commands/backlog/beginAssociation.ts
15888
15950
  import chalk44 from "chalk";
15889
- import { eq as eq28 } from "drizzle-orm";
15951
+ import { eq as eq29 } from "drizzle-orm";
15890
15952
  async function beginAssociation(id, options2, clearPatch, label) {
15891
15953
  const found = await findOneItem(id);
15892
15954
  if (!found) {
@@ -15896,7 +15958,7 @@ async function beginAssociation(id, options2, clearPatch, label) {
15896
15958
  const { orm } = found;
15897
15959
  const itemId2 = found.item.id;
15898
15960
  if (options2.clear) {
15899
- await orm.update(items).set(clearPatch).where(eq28(items.id, itemId2));
15961
+ await orm.update(items).set(clearPatch).where(eq29(items.id, itemId2));
15900
15962
  console.log(
15901
15963
  chalk44.green(
15902
15964
  `Cleared ${label} association on item ${formatItemId(itemId2)}.`
@@ -15964,7 +16026,7 @@ async function associateGithub(id, issue, options2) {
15964
16026
  return;
15965
16027
  }
15966
16028
  const title = fetchGithubIssueTitle(normalized);
15967
- await orm.update(items).set({ githubIssue: normalized, jiraKey: null }).where(eq29(items.id, itemId2));
16029
+ await orm.update(items).set({ githubIssue: normalized, jiraKey: null }).where(eq30(items.id, itemId2));
15968
16030
  console.log(
15969
16031
  chalk45.green(`Associated ${normalized} with item ${formatItemId(itemId2)}.`),
15970
16032
  title ? chalk45.dim(`(${title})`) : ""
@@ -15978,7 +16040,7 @@ function registerAssociateGithubCommand(cmd) {
15978
16040
 
15979
16041
  // src/commands/backlog/associateJira.ts
15980
16042
  import chalk47 from "chalk";
15981
- import { eq as eq30 } from "drizzle-orm";
16043
+ import { eq as eq31 } from "drizzle-orm";
15982
16044
 
15983
16045
  // src/commands/jira/fetchIssue.ts
15984
16046
  import { execSync as execSync17 } from "child_process";
@@ -16043,7 +16105,7 @@ async function associateJira(id, key, options2) {
16043
16105
  const parsed = fetchIssue(normalized, "summary");
16044
16106
  const fields = parsed?.fields;
16045
16107
  const summary = fields?.summary;
16046
- await orm.update(items).set({ jiraKey: normalized, githubIssue: null }).where(eq30(items.id, itemId2));
16108
+ await orm.update(items).set({ jiraKey: normalized, githubIssue: null }).where(eq31(items.id, itemId2));
16047
16109
  console.log(
16048
16110
  chalk47.green(`Associated ${normalized} with item ${formatItemId(itemId2)}.`),
16049
16111
  summary ? chalk47.dim(`(${summary})`) : ""
@@ -16579,9 +16641,9 @@ import chalk58 from "chalk";
16579
16641
 
16580
16642
  // src/commands/backlog/resolveInsertPosition.ts
16581
16643
  import chalk57 from "chalk";
16582
- import { count as count3, eq as eq31 } from "drizzle-orm";
16644
+ import { count as count3, eq as eq32 } from "drizzle-orm";
16583
16645
  async function resolveInsertPosition(orm, itemId2, position) {
16584
- const [row3] = await orm.select({ cnt: count3() }).from(planPhases).where(eq31(planPhases.itemId, itemId2));
16646
+ const [row3] = await orm.select({ cnt: count3() }).from(planPhases).where(eq32(planPhases.itemId, itemId2));
16585
16647
  const phaseCount = row3?.cnt ?? 0;
16586
16648
  if (position === void 0) return phaseCount;
16587
16649
  const pos = Number.parseInt(position, 10);
@@ -16964,9 +17026,9 @@ function hasCycle(adjacency, fromId, toId) {
16964
17026
  }
16965
17027
 
16966
17028
  // src/commands/backlog/loadDependencyGraph.ts
16967
- import { eq as eq32 } from "drizzle-orm";
17029
+ import { eq as eq33 } from "drizzle-orm";
16968
17030
  async function loadDependencyGraph(orm) {
16969
- const rows = await orm.select({ itemId: links.itemId, targetId: links.targetId }).from(links).where(eq32(links.type, "depends-on"));
17031
+ const rows = await orm.select({ itemId: links.itemId, targetId: links.targetId }).from(links).where(eq33(links.type, "depends-on"));
16970
17032
  const graph = /* @__PURE__ */ new Map();
16971
17033
  for (const { itemId: itemId2, targetId } of rows) {
16972
17034
  const bucket = graph.get(itemId2);
@@ -17033,7 +17095,7 @@ async function link(fromId, toId, opts) {
17033
17095
 
17034
17096
  // src/commands/backlog/unlink.ts
17035
17097
  import chalk65 from "chalk";
17036
- import { and as and11, eq as eq33 } from "drizzle-orm";
17098
+ import { and as and12, eq as eq34 } from "drizzle-orm";
17037
17099
  async function unlink(fromId, toId) {
17038
17100
  const fromNum = parseItemId(fromId);
17039
17101
  const toNum = parseItemId(toId);
@@ -17057,7 +17119,7 @@ async function unlink(fromId, toId) {
17057
17119
  );
17058
17120
  return;
17059
17121
  }
17060
- await orm.delete(links).where(and11(eq33(links.itemId, fromNum), eq33(links.targetId, toNum)));
17122
+ await orm.delete(links).where(and12(eq34(links.itemId, fromNum), eq34(links.targetId, toNum)));
17061
17123
  console.log(
17062
17124
  chalk65.green(
17063
17125
  `Removed link from ${formatItemId(fromNum)} to ${formatItemId(toNum)}.`
@@ -17077,7 +17139,7 @@ function registerLinkCommands(cmd) {
17077
17139
 
17078
17140
  // src/commands/backlog/moveRepo.ts
17079
17141
  import chalk67 from "chalk";
17080
- import { eq as eq35 } from "drizzle-orm";
17142
+ import { eq as eq36 } from "drizzle-orm";
17081
17143
 
17082
17144
  // src/commands/backlog/move-repo/confirmMove.ts
17083
17145
  import chalk66 from "chalk";
@@ -17092,9 +17154,9 @@ async function confirmMove(cnt, oldOrigin, newOrigin) {
17092
17154
  }
17093
17155
 
17094
17156
  // src/commands/backlog/move-repo/countByOrigin.ts
17095
- import { count as count4, eq as eq34 } from "drizzle-orm";
17157
+ import { count as count4, eq as eq35 } from "drizzle-orm";
17096
17158
  async function countByOrigin(orm, origin) {
17097
- const [{ cnt }] = await orm.select({ cnt: count4() }).from(items).where(eq34(items.origin, origin));
17159
+ const [{ cnt }] = await orm.select({ cnt: count4() }).from(items).where(eq35(items.origin, origin));
17098
17160
  return cnt;
17099
17161
  }
17100
17162
 
@@ -17137,7 +17199,7 @@ async function moveRepo(oldOriginRaw, newOriginRaw, options2 = {}) {
17137
17199
  console.log(chalk67.yellow("Move cancelled; no changes made."));
17138
17200
  return;
17139
17201
  }
17140
- await orm.update(items).set({ origin: newOrigin }).where(eq35(items.origin, oldOrigin));
17202
+ await orm.update(items).set({ origin: newOrigin }).where(eq36(items.origin, oldOrigin));
17141
17203
  console.log(
17142
17204
  chalk67.green(
17143
17205
  `Moved ${pluralItems(cnt)} from "${oldOrigin}" to "${newOrigin}".`
@@ -17609,12 +17671,12 @@ function message(error) {
17609
17671
  import chalk71 from "chalk";
17610
17672
 
17611
17673
  // src/shared/db/findPhaseBySessionId.ts
17612
- import { and as and12, eq as eq36 } from "drizzle-orm";
17674
+ import { and as and13, eq as eq37 } from "drizzle-orm";
17613
17675
  async function findPhaseBySessionId(db, itemId2, claudeSessionId) {
17614
17676
  const rows = await db.select({ phaseIdx: phaseUsage.phaseIdx }).from(phaseUsage).where(
17615
- and12(
17616
- eq36(phaseUsage.itemId, itemId2),
17617
- eq36(phaseUsage.claudeSessionId, claudeSessionId)
17677
+ and13(
17678
+ eq37(phaseUsage.itemId, itemId2),
17679
+ eq37(phaseUsage.claudeSessionId, claudeSessionId)
17618
17680
  )
17619
17681
  ).limit(1);
17620
17682
  return rows[0]?.phaseIdx;
@@ -19019,11 +19081,11 @@ async function done(id, summary) {
19019
19081
  return;
19020
19082
  }
19021
19083
  }
19022
- await updateStatus(orm, item.id, "done");
19023
19084
  if (summary) {
19024
19085
  const phase = item.currentPhase ?? 1;
19025
19086
  await appendComment(orm, item.id, summary, { phase, type: "summary" });
19026
19087
  }
19088
+ await updateStatus(orm, item.id, "done");
19027
19089
  console.log(
19028
19090
  chalk89.green(`Completed item ${formatItemId(item.id)}: ${item.name}`)
19029
19091
  );
@@ -19094,10 +19156,10 @@ async function start(id) {
19094
19156
 
19095
19157
  // src/commands/backlog/stop.ts
19096
19158
  import chalk94 from "chalk";
19097
- import { and as and13, eq as eq37 } from "drizzle-orm";
19159
+ import { and as and14, eq as eq38 } from "drizzle-orm";
19098
19160
  async function stop() {
19099
19161
  const { orm } = await getReady();
19100
- const stopped = await orm.update(items).set({ status: "todo", currentPhase: 1 }).where(and13(eq37(items.status, "in-progress"), eq37(items.origin, getOrigin()))).returning({ id: items.id, name: items.name });
19162
+ const stopped = await orm.update(items).set({ status: "todo", currentPhase: 1 }).where(and14(eq38(items.status, "in-progress"), eq38(items.origin, getOrigin()))).returning({ id: items.id, name: items.name });
19101
19163
  if (stopped.length === 0) {
19102
19164
  console.log(chalk94.yellow("No in-progress items to stop."));
19103
19165
  return;
@@ -19155,20 +19217,20 @@ function registerStatusCommands(cmd) {
19155
19217
 
19156
19218
  // src/commands/backlog/movePhase.ts
19157
19219
  import chalk97 from "chalk";
19158
- import { count as count6, eq as eq40 } from "drizzle-orm";
19220
+ import { count as count6, eq as eq41 } from "drizzle-orm";
19159
19221
 
19160
19222
  // src/commands/backlog/reorderPhaseRows.ts
19161
- import { and as and15, asc as asc10, eq as eq39 } from "drizzle-orm";
19223
+ import { and as and16, asc as asc11, eq as eq40 } from "drizzle-orm";
19162
19224
 
19163
19225
  // src/commands/backlog/reindexPhases.ts
19164
- import { and as and14, asc as asc9, count as count5, eq as eq38 } from "drizzle-orm";
19226
+ import { and as and15, asc as asc10, count as count5, eq as eq39 } from "drizzle-orm";
19165
19227
  async function reindexPhases(db, itemId2) {
19166
- const remaining = await db.select({ idx: planPhases.idx }).from(planPhases).where(eq38(planPhases.itemId, itemId2)).orderBy(asc9(planPhases.idx));
19228
+ const remaining = await db.select({ idx: planPhases.idx }).from(planPhases).where(eq39(planPhases.itemId, itemId2)).orderBy(asc10(planPhases.idx));
19167
19229
  for (let i = 0; i < remaining.length; i++) {
19168
19230
  const oldIdx = remaining[i].idx;
19169
19231
  if (oldIdx === i) continue;
19170
- await db.update(planTasks).set({ phaseIdx: i }).where(and14(eq38(planTasks.itemId, itemId2), eq38(planTasks.phaseIdx, oldIdx)));
19171
- await db.update(planPhases).set({ idx: i }).where(and14(eq38(planPhases.itemId, itemId2), eq38(planPhases.idx, oldIdx)));
19232
+ await db.update(planTasks).set({ phaseIdx: i }).where(and15(eq39(planTasks.itemId, itemId2), eq39(planTasks.phaseIdx, oldIdx)));
19233
+ await db.update(planPhases).set({ idx: i }).where(and15(eq39(planPhases.itemId, itemId2), eq39(planPhases.idx, oldIdx)));
19172
19234
  }
19173
19235
  }
19174
19236
  async function adjustCurrentPhase(db, item, removedIdx) {
@@ -19176,24 +19238,24 @@ async function adjustCurrentPhase(db, item, removedIdx) {
19176
19238
  if (currentPhase === void 0) return;
19177
19239
  const currentIdx = currentPhase - 1;
19178
19240
  if (removedIdx < currentIdx) {
19179
- await db.update(items).set({ currentPhase: currentPhase - 1 }).where(eq38(items.id, item.id));
19241
+ await db.update(items).set({ currentPhase: currentPhase - 1 }).where(eq39(items.id, item.id));
19180
19242
  return;
19181
19243
  }
19182
19244
  if (removedIdx !== currentIdx) return;
19183
- const [row3] = await db.select({ cnt: count5() }).from(planPhases).where(eq38(planPhases.itemId, item.id));
19245
+ const [row3] = await db.select({ cnt: count5() }).from(planPhases).where(eq39(planPhases.itemId, item.id));
19184
19246
  const cnt = row3?.cnt ?? 0;
19185
- await db.update(items).set({ currentPhase: cnt === 0 ? null : Math.min(currentPhase, cnt) }).where(eq38(items.id, item.id));
19247
+ await db.update(items).set({ currentPhase: cnt === 0 ? null : Math.min(currentPhase, cnt) }).where(eq39(items.id, item.id));
19186
19248
  }
19187
19249
 
19188
19250
  // src/commands/backlog/reorderPhaseRows.ts
19189
19251
  async function reorderPhaseRows(orm, itemId2, fromIdx, toIdx) {
19190
19252
  await orm.transaction(async (tx) => {
19191
- const [phaseRow] = await tx.select({ name: planPhases.name, manualChecks: planPhases.manualChecks }).from(planPhases).where(and15(eq39(planPhases.itemId, itemId2), eq39(planPhases.idx, fromIdx)));
19192
- const tasks = await tx.select({ idx: planTasks.idx, task: planTasks.task }).from(planTasks).where(and15(eq39(planTasks.itemId, itemId2), eq39(planTasks.phaseIdx, fromIdx))).orderBy(asc10(planTasks.idx));
19253
+ const [phaseRow] = await tx.select({ name: planPhases.name, manualChecks: planPhases.manualChecks }).from(planPhases).where(and16(eq40(planPhases.itemId, itemId2), eq40(planPhases.idx, fromIdx)));
19254
+ const tasks = await tx.select({ idx: planTasks.idx, task: planTasks.task }).from(planTasks).where(and16(eq40(planTasks.itemId, itemId2), eq40(planTasks.phaseIdx, fromIdx))).orderBy(asc11(planTasks.idx));
19193
19255
  await tx.delete(planTasks).where(
19194
- and15(eq39(planTasks.itemId, itemId2), eq39(planTasks.phaseIdx, fromIdx))
19256
+ and16(eq40(planTasks.itemId, itemId2), eq40(planTasks.phaseIdx, fromIdx))
19195
19257
  );
19196
- await tx.delete(planPhases).where(and15(eq39(planPhases.itemId, itemId2), eq39(planPhases.idx, fromIdx)));
19258
+ await tx.delete(planPhases).where(and16(eq40(planPhases.itemId, itemId2), eq40(planPhases.idx, fromIdx)));
19197
19259
  await reindexPhases(tx, itemId2);
19198
19260
  await shiftPhasesUp(tx, itemId2, toIdx);
19199
19261
  await tx.insert(planPhases).values({
@@ -19234,7 +19296,7 @@ async function movePhase(id, from, to) {
19234
19296
  if (!found) return;
19235
19297
  const { orm, item } = found;
19236
19298
  const itemId2 = item.id;
19237
- const [row3] = await orm.select({ cnt: count6() }).from(planPhases).where(eq40(planPhases.itemId, itemId2));
19299
+ const [row3] = await orm.select({ cnt: count6() }).from(planPhases).where(eq41(planPhases.itemId, itemId2));
19238
19300
  const phaseCount = row3?.cnt ?? 0;
19239
19301
  const fromIdx = toIndex2(from, phaseCount);
19240
19302
  if (fromIdx === void 0) return;
@@ -19250,22 +19312,22 @@ async function movePhase(id, from, to) {
19250
19312
  import chalk99 from "chalk";
19251
19313
 
19252
19314
  // src/commands/backlog/applyPhaseUpdate.ts
19253
- import { and as and16, eq as eq41 } from "drizzle-orm";
19315
+ import { and as and17, eq as eq42 } from "drizzle-orm";
19254
19316
  async function applyPhaseUpdate(orm, itemId2, phaseIdx, fields) {
19255
19317
  await orm.transaction(async (tx) => {
19256
19318
  if (fields.name) {
19257
19319
  await tx.update(planPhases).set({ name: fields.name }).where(
19258
- and16(eq41(planPhases.itemId, itemId2), eq41(planPhases.idx, phaseIdx))
19320
+ and17(eq42(planPhases.itemId, itemId2), eq42(planPhases.idx, phaseIdx))
19259
19321
  );
19260
19322
  }
19261
19323
  if (fields.manualCheck) {
19262
19324
  await tx.update(planPhases).set({ manualChecks: JSON.stringify(fields.manualCheck) }).where(
19263
- and16(eq41(planPhases.itemId, itemId2), eq41(planPhases.idx, phaseIdx))
19325
+ and17(eq42(planPhases.itemId, itemId2), eq42(planPhases.idx, phaseIdx))
19264
19326
  );
19265
19327
  }
19266
19328
  if (fields.task) {
19267
19329
  await tx.delete(planTasks).where(
19268
- and16(eq41(planTasks.itemId, itemId2), eq41(planTasks.phaseIdx, phaseIdx))
19330
+ and17(eq42(planTasks.itemId, itemId2), eq42(planTasks.phaseIdx, phaseIdx))
19269
19331
  );
19270
19332
  if (fields.task.length) {
19271
19333
  await tx.insert(planTasks).values(
@@ -19283,14 +19345,14 @@ async function applyPhaseUpdate(orm, itemId2, phaseIdx, fields) {
19283
19345
 
19284
19346
  // src/commands/backlog/findPhase.ts
19285
19347
  import chalk98 from "chalk";
19286
- import { and as and17, count as count7, eq as eq42 } from "drizzle-orm";
19348
+ import { and as and18, count as count7, eq as eq43 } from "drizzle-orm";
19287
19349
  async function findPhase(id, phase) {
19288
19350
  const found = await findOneItem(id);
19289
19351
  if (!found) return void 0;
19290
19352
  const { orm, item } = found;
19291
19353
  const itemId2 = item.id;
19292
19354
  const phaseIdx = Number.parseInt(phase, 10) - 1;
19293
- const [row3] = await orm.select({ cnt: count7() }).from(planPhases).where(and17(eq42(planPhases.itemId, itemId2), eq42(planPhases.idx, phaseIdx)));
19355
+ const [row3] = await orm.select({ cnt: count7() }).from(planPhases).where(and18(eq43(planPhases.itemId, itemId2), eq43(planPhases.idx, phaseIdx)));
19294
19356
  if (!row3 || row3.cnt === 0) {
19295
19357
  console.log(
19296
19358
  chalk98.red(
@@ -19463,16 +19525,16 @@ function registerUpdatePhaseCommand(cmd) {
19463
19525
 
19464
19526
  // src/commands/backlog/removePhase.ts
19465
19527
  import chalk100 from "chalk";
19466
- import { and as and18, eq as eq43 } from "drizzle-orm";
19528
+ import { and as and19, eq as eq44 } from "drizzle-orm";
19467
19529
  async function removePhase(id, phase) {
19468
19530
  const found = await findPhase(id, phase);
19469
19531
  if (!found) return;
19470
19532
  const { item, orm, itemId: itemId2, phaseIdx } = found;
19471
19533
  await orm.transaction(async (tx) => {
19472
19534
  await tx.delete(planTasks).where(
19473
- and18(eq43(planTasks.itemId, itemId2), eq43(planTasks.phaseIdx, phaseIdx))
19535
+ and19(eq44(planTasks.itemId, itemId2), eq44(planTasks.phaseIdx, phaseIdx))
19474
19536
  );
19475
- await tx.delete(planPhases).where(and18(eq43(planPhases.itemId, itemId2), eq43(planPhases.idx, phaseIdx)));
19537
+ await tx.delete(planPhases).where(and19(eq44(planPhases.itemId, itemId2), eq44(planPhases.idx, phaseIdx)));
19476
19538
  await reindexPhases(tx, itemId2);
19477
19539
  await adjustCurrentPhase(tx, item, phaseIdx);
19478
19540
  });
@@ -19485,7 +19547,7 @@ async function removePhase(id, phase) {
19485
19547
 
19486
19548
  // src/commands/backlog/update/update.ts
19487
19549
  import chalk104 from "chalk";
19488
- import { eq as eq44 } from "drizzle-orm";
19550
+ import { eq as eq45 } from "drizzle-orm";
19489
19551
 
19490
19552
  // src/commands/backlog/update/buildUpdateValues.ts
19491
19553
  import chalk101 from "chalk";
@@ -19596,7 +19658,7 @@ async function update(id, options2) {
19596
19658
  if (!built) return;
19597
19659
  const { orm } = found;
19598
19660
  const itemId2 = found.item.id;
19599
- await orm.update(items).set(built.set).where(eq44(items.id, itemId2));
19661
+ await orm.update(items).set(built.set).where(eq45(items.id, itemId2));
19600
19662
  console.log(
19601
19663
  chalk104.green(`Updated ${built.fields} on item ${formatItemId(itemId2)}.`)
19602
19664
  );
@@ -19642,7 +19704,7 @@ function readPlanUpdate(source) {
19642
19704
  }
19643
19705
 
19644
19706
  // src/commands/backlog/updatePlan/replacePlan.ts
19645
- import { eq as eq45 } from "drizzle-orm";
19707
+ import { eq as eq46 } from "drizzle-orm";
19646
19708
 
19647
19709
  // src/commands/backlog/updatePlan/clampCurrentPhase.ts
19648
19710
  function clampCurrentPhase(currentPhase, phaseCount) {
@@ -19653,8 +19715,8 @@ function clampCurrentPhase(currentPhase, phaseCount) {
19653
19715
  // src/commands/backlog/updatePlan/replacePlan.ts
19654
19716
  async function replacePlan(orm, itemId2, phases, currentPhase) {
19655
19717
  await orm.transaction(async (tx) => {
19656
- await tx.delete(planTasks).where(eq45(planTasks.itemId, itemId2));
19657
- await tx.delete(planPhases).where(eq45(planPhases.itemId, itemId2));
19718
+ await tx.delete(planTasks).where(eq46(planTasks.itemId, itemId2));
19719
+ await tx.delete(planPhases).where(eq46(planPhases.itemId, itemId2));
19658
19720
  if (phases.length > 0) {
19659
19721
  await tx.insert(planPhases).values(
19660
19722
  phases.map((phase, idx) => ({
@@ -19672,7 +19734,7 @@ async function replacePlan(orm, itemId2, phases, currentPhase) {
19672
19734
  if (currentPhase === void 0) return;
19673
19735
  const clamped = clampCurrentPhase(currentPhase, phases.length);
19674
19736
  if (clamped === currentPhase) return;
19675
- await tx.update(items).set({ currentPhase: clamped }).where(eq45(items.id, itemId2));
19737
+ await tx.update(items).set({ currentPhase: clamped }).where(eq46(items.id, itemId2));
19676
19738
  });
19677
19739
  }
19678
19740
 
@@ -19971,43 +20033,44 @@ function printActivity(item, options2 = {}) {
19971
20033
 
19972
20034
  // src/commands/backlog/show/printLinks.ts
19973
20035
  import chalk108 from "chalk";
19974
-
19975
- // src/commands/backlog/show/loadLinkTargets.ts
19976
- import { inArray as inArray2 } from "drizzle-orm";
19977
- async function loadLinkTargets(orm, ids) {
19978
- if (ids.length === 0) return [];
19979
- const rows = await orm.select({ id: items.id, name: items.name, status: items.status }).from(items).where(inArray2(items.id, ids));
19980
- return rows.map((r) => ({
19981
- id: r.id,
19982
- name: r.name,
19983
- status: r.status
19984
- }));
19985
- }
19986
-
19987
- // src/commands/backlog/show/printLinks.ts
19988
- async function printLinks(orm, item) {
19989
- const links2 = item.links ?? [];
20036
+ function printSection(heading2, links2, targets, label) {
19990
20037
  if (links2.length === 0) return;
19991
- const targets = await loadLinkTargets(
19992
- orm,
19993
- links2.map((l) => l.targetId)
19994
- );
19995
- console.log(chalk108.bold("Links"));
20038
+ console.log(chalk108.bold(heading2));
19996
20039
  for (const link3 of links2) {
19997
20040
  const target = targets.find((i) => i.id === link3.targetId);
19998
- const typeLabel2 = link3.type === "depends-on" ? chalk108.red(link3.type) : chalk108.blue(link3.type);
20041
+ const prefix2 = label(link3);
19999
20042
  if (target) {
20000
20043
  console.log(
20001
- ` ${typeLabel2} ${formatItemId(target.id)} ${target.name} ${chalk108.dim(`(${target.status})`)}`
20044
+ ` ${prefix2}${formatItemId(target.id)} ${target.name} ${chalk108.dim(`(${target.status})`)}`
20002
20045
  );
20003
20046
  } else {
20004
20047
  console.log(
20005
- ` ${typeLabel2} ${formatItemId(link3.targetId)} ${chalk108.dim("(not found)")}`
20048
+ ` ${prefix2}${formatItemId(link3.targetId)} ${chalk108.dim("(not found)")}`
20006
20049
  );
20007
20050
  }
20008
20051
  }
20009
20052
  console.log();
20010
20053
  }
20054
+ async function printLinks(orm, item) {
20055
+ const links2 = item.links ?? [];
20056
+ if (links2.length === 0) return;
20057
+ const targets = await loadLinkTargets(
20058
+ orm,
20059
+ links2.map((l) => l.targetId)
20060
+ );
20061
+ printSection(
20062
+ "Links",
20063
+ links2.filter((l) => l.type !== "docs-refresh"),
20064
+ targets,
20065
+ (link3) => `${link3.type === "depends-on" ? chalk108.red(link3.type) : chalk108.blue(link3.type)} `
20066
+ );
20067
+ printSection(
20068
+ "Refreshes docs for",
20069
+ links2.filter((l) => l.type === "docs-refresh"),
20070
+ targets,
20071
+ () => ""
20072
+ );
20073
+ }
20011
20074
 
20012
20075
  // src/commands/backlog/show/printPlan.ts
20013
20076
  import chalk110 from "chalk";
@@ -28304,10 +28367,10 @@ async function list3() {
28304
28367
  import chalk177 from "chalk";
28305
28368
 
28306
28369
  // src/shared/db/readInboxLastScan.ts
28307
- import { eq as eq46 } from "drizzle-orm";
28370
+ import { eq as eq47 } from "drizzle-orm";
28308
28371
  var INBOX_LAST_SCAN_KEY = "inbox.lastScanAt";
28309
28372
  async function readInboxLastScan(orm) {
28310
- const [row3] = await orm.select().from(metadata).where(eq46(metadata.key, INBOX_LAST_SCAN_KEY));
28373
+ const [row3] = await orm.select().from(metadata).where(eq47(metadata.key, INBOX_LAST_SCAN_KEY));
28311
28374
  return row3 ? new Date(row3.value) : void 0;
28312
28375
  }
28313
28376
 
@@ -31047,9 +31110,9 @@ async function list4() {
31047
31110
  import chalk193 from "chalk";
31048
31111
 
31049
31112
  // src/commands/backlog/removeFeed.ts
31050
- import { eq as eq47 } from "drizzle-orm";
31113
+ import { eq as eq48 } from "drizzle-orm";
31051
31114
  async function removeFeed(db, url) {
31052
- const deleted = await db.delete(feeds).where(eq47(feeds.url, url)).returning({ id: feeds.id });
31115
+ const deleted = await db.delete(feeds).where(eq48(feeds.url, url)).returning({ id: feeds.id });
31053
31116
  return deleted.length > 0;
31054
31117
  }
31055
31118
 
@@ -45419,12 +45482,12 @@ function parseEnvPairs(pairs) {
45419
45482
  if (pairs.length === 0) return void 0;
45420
45483
  const env = {};
45421
45484
  for (const pair of pairs) {
45422
- const eq50 = pair.indexOf("=");
45423
- if (eq50 <= 0) {
45485
+ const eq51 = pair.indexOf("=");
45486
+ if (eq51 <= 0) {
45424
45487
  console.error(`--env must be KEY=VALUE, got "${pair}"`);
45425
45488
  process.exit(1);
45426
45489
  }
45427
- env[pair.slice(0, eq50)] = pair.slice(eq50 + 1);
45490
+ env[pair.slice(0, eq51)] = pair.slice(eq51 + 1);
45428
45491
  }
45429
45492
  return env;
45430
45493
  }
@@ -45437,11 +45500,11 @@ function fail9(spec) {
45437
45500
  process.exit(1);
45438
45501
  }
45439
45502
  function parseParamSpec(spec) {
45440
- const eq50 = spec.indexOf("=");
45441
- const name = eq50 === -1 ? spec : spec.slice(0, eq50);
45442
- if (eq50 !== -1) {
45443
- if (eq50 === 0 || name.endsWith("!")) fail9(spec);
45444
- return { name, default: spec.slice(eq50 + 1) };
45503
+ const eq51 = spec.indexOf("=");
45504
+ const name = eq51 === -1 ? spec : spec.slice(0, eq51);
45505
+ if (eq51 !== -1) {
45506
+ if (eq51 === 0 || name.endsWith("!")) fail9(spec);
45507
+ return { name, default: spec.slice(eq51 + 1) };
45445
45508
  }
45446
45509
  if (name.endsWith("!")) {
45447
45510
  if (name.length === 1) fail9(spec);
@@ -48728,10 +48791,10 @@ function applyReviewPause(session, activity2) {
48728
48791
  }
48729
48792
 
48730
48793
  // src/shared/db/getPhaseActiveMs.ts
48731
- import { and as and19, eq as eq48 } from "drizzle-orm";
48794
+ import { and as and20, eq as eq49 } from "drizzle-orm";
48732
48795
  async function getPhaseActiveMs(db, itemId2, phaseIdx) {
48733
48796
  const [row3] = await db.select({ activeMs: phaseUsage.activeMs }).from(phaseUsage).where(
48734
- and19(eq48(phaseUsage.itemId, itemId2), eq48(phaseUsage.phaseIdx, phaseIdx))
48797
+ and20(eq49(phaseUsage.itemId, itemId2), eq49(phaseUsage.phaseIdx, phaseIdx))
48735
48798
  );
48736
48799
  return row3?.activeMs ?? 0;
48737
48800
  }
@@ -49618,17 +49681,17 @@ async function recordPhaseTranscriptUsage(db, itemId2, phaseIdx, responses) {
49618
49681
  }
49619
49682
 
49620
49683
  // src/shared/db/recordWindowTokens.ts
49621
- import { and as and20, eq as eq49, sql as sql29 } from "drizzle-orm";
49684
+ import { and as and21, eq as eq50, sql as sql29 } from "drizzle-orm";
49622
49685
  async function recordWindowTokens(db, window, resetsAt, tokensUp, tokensDown) {
49623
49686
  if (tokensUp <= 0 && tokensDown <= 0) return;
49624
49687
  await db.update(usagePeaks).set({
49625
49688
  tokensUp: sql29`${usagePeaks.tokensUp} + ${tokensUp}`,
49626
49689
  tokensDown: sql29`${usagePeaks.tokensDown} + ${tokensDown}`
49627
49690
  }).where(
49628
- and20(
49629
- eq49(usagePeaks.window, window),
49630
- eq49(usagePeaks.resetsAt, resetsAt),
49631
- eq49(usagePeaks.segment, 0)
49691
+ and21(
49692
+ eq50(usagePeaks.window, window),
49693
+ eq50(usagePeaks.resetsAt, resetsAt),
49694
+ eq50(usagePeaks.segment, 0)
49632
49695
  )
49633
49696
  );
49634
49697
  }