@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/README.md +5 -639
- package/dist/commands/sessions/web/bundle.js +432 -432
- package/dist/index.js +1097 -1034
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@staff0rd/assist",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.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
|
|
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
|
|
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:
|
|
3845
|
+
description: buildDocsRefreshDescription(stories),
|
|
3805
3846
|
acceptanceCriteria: [
|
|
3806
|
-
"Docs pages reflect the behaviour shipped by every
|
|
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
|
-
`
|
|
3820
|
-
"
|
|
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,
|
|
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
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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/
|
|
4500
|
-
function
|
|
4501
|
-
|
|
4502
|
-
|
|
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
|
-
|
|
4513
|
-
|
|
4514
|
-
if (
|
|
4515
|
-
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
4764
|
+
import { basename as basename3 } from "path";
|
|
5368
4765
|
|
|
5369
4766
|
// src/commands/sessions/web/fromGithub/listClones.ts
|
|
5370
|
-
import { existsSync as
|
|
5371
|
-
import { join as
|
|
4767
|
+
import { existsSync as existsSync12, readdirSync as readdirSync2 } from "fs";
|
|
4768
|
+
import { join as join12 } from "path";
|
|
5372
4769
|
function listClones(baseDir) {
|
|
5373
|
-
if (!
|
|
5374
|
-
return readdirSync2(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) =>
|
|
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) =>
|
|
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
|
|
5562
|
-
import { join as
|
|
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 (!
|
|
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 (!
|
|
5590
|
-
const named =
|
|
5591
|
-
const others = readdirSync3(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) =>
|
|
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
|
|
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) =>
|
|
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
|
|
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
|
|
7290
|
-
import { join as
|
|
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 =
|
|
7294
|
-
if (
|
|
7295
|
-
const rootPath =
|
|
7296
|
-
if (
|
|
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,
|
|
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
|
|
7391
|
-
import { basename as
|
|
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
|
|
7396
|
-
import { basename as
|
|
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 =
|
|
7399
|
-
const parent =
|
|
7400
|
-
const branchPrefix =
|
|
7401
|
-
if (!
|
|
7402
|
-
const synthesisFiles = readdirSync4(parent).filter((name) => name.startsWith(branchPrefix)).map((name) =>
|
|
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 =
|
|
7421
|
-
|
|
6817
|
+
const repoReviewsDir = join16(
|
|
6818
|
+
homedir6(),
|
|
7422
6819
|
".assist",
|
|
7423
6820
|
"reviews",
|
|
7424
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
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/
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
"
|
|
7696
|
-
"
|
|
7697
|
-
|
|
7698
|
-
|
|
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/
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
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
|
|
7707
|
-
|
|
7708
|
-
(
|
|
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
|
-
|
|
7699
|
+
return lines2;
|
|
7700
|
+
} finally {
|
|
7701
|
+
reader.close();
|
|
7702
|
+
stream.destroy();
|
|
7726
7703
|
}
|
|
7704
|
+
return lines2;
|
|
7727
7705
|
}
|
|
7728
7706
|
|
|
7729
|
-
// src/commands/
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
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
|
-
|
|
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/
|
|
7773
|
-
import
|
|
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/
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
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
|
|
7792
|
-
const
|
|
7793
|
-
|
|
7794
|
-
const
|
|
7795
|
-
|
|
7796
|
-
|
|
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
|
|
7799
|
-
const
|
|
7800
|
-
|
|
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
|
-
|
|
7803
|
-
return parsed.sessionId;
|
|
7770
|
+
return JSON.parse(line);
|
|
7804
7771
|
} catch {
|
|
7805
|
-
return
|
|
7772
|
+
return null;
|
|
7806
7773
|
}
|
|
7807
7774
|
}
|
|
7808
|
-
function
|
|
7809
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
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
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
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
|
-
|
|
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
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
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/
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
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
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
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
|
-
|
|
7872
|
-
await
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
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/
|
|
7885
|
-
|
|
7886
|
-
|
|
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
|
|
7890
|
-
const
|
|
7891
|
-
if (
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
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
|
|
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
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
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
|
|
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 ?
|
|
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
|
|
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
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9490
|
+
and10(
|
|
9491
|
+
eq22(usagePeaks.window, avgContext.window),
|
|
9492
|
+
eq22(usagePeaks.resetsAt, avgContext.resetsAt)
|
|
9431
9493
|
)
|
|
9432
|
-
).where(options2?.window ?
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
9520
|
-
filter?.origin ?
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
17616
|
-
|
|
17617
|
-
|
|
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
|
|
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(
|
|
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
|
|
19220
|
+
import { count as count6, eq as eq41 } from "drizzle-orm";
|
|
19159
19221
|
|
|
19160
19222
|
// src/commands/backlog/reorderPhaseRows.ts
|
|
19161
|
-
import { and as
|
|
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
|
|
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(
|
|
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(
|
|
19171
|
-
await db.update(planPhases).set({ idx: i }).where(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
19192
|
-
const tasks = await tx.select({ idx: planTasks.idx, task: planTasks.task }).from(planTasks).where(
|
|
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
|
-
|
|
19256
|
+
and16(eq40(planTasks.itemId, itemId2), eq40(planTasks.phaseIdx, fromIdx))
|
|
19195
19257
|
);
|
|
19196
|
-
await tx.delete(planPhases).where(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
19535
|
+
and19(eq44(planTasks.itemId, itemId2), eq44(planTasks.phaseIdx, phaseIdx))
|
|
19474
19536
|
);
|
|
19475
|
-
await tx.delete(planPhases).where(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
19657
|
-
await tx.delete(planPhases).where(
|
|
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(
|
|
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
|
-
|
|
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
|
|
20041
|
+
const prefix2 = label(link3);
|
|
19999
20042
|
if (target) {
|
|
20000
20043
|
console.log(
|
|
20001
|
-
` ${
|
|
20044
|
+
` ${prefix2}${formatItemId(target.id)} ${target.name} ${chalk108.dim(`(${target.status})`)}`
|
|
20002
20045
|
);
|
|
20003
20046
|
} else {
|
|
20004
20047
|
console.log(
|
|
20005
|
-
` ${
|
|
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
|
|
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(
|
|
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
|
|
31113
|
+
import { eq as eq48 } from "drizzle-orm";
|
|
31051
31114
|
async function removeFeed(db, url) {
|
|
31052
|
-
const deleted = await db.delete(feeds).where(
|
|
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
|
|
45423
|
-
if (
|
|
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,
|
|
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
|
|
45441
|
-
const name =
|
|
45442
|
-
if (
|
|
45443
|
-
if (
|
|
45444
|
-
return { name, default: spec.slice(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
49629
|
-
|
|
49630
|
-
|
|
49631
|
-
|
|
49691
|
+
and21(
|
|
49692
|
+
eq50(usagePeaks.window, window),
|
|
49693
|
+
eq50(usagePeaks.resetsAt, resetsAt),
|
|
49694
|
+
eq50(usagePeaks.segment, 0)
|
|
49632
49695
|
)
|
|
49633
49696
|
);
|
|
49634
49697
|
}
|