@cmgmyr/hive 1.11.4 → 1.11.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +1 -1
- package/dist/cli.js +43 -24
- package/dist/codexHome.js +0 -1
- package/dist/context.js +13 -24
- package/dist/crew.js +6 -23
- package/dist/dashboard.js +11 -14
- package/dist/db.js +36 -0
- package/dist/git.js +16 -0
- package/dist/harnesses.js +0 -3
- package/dist/kickoff.js +4 -13
- package/dist/leadHandoffState.js +1 -1
- package/dist/portfolio.js +2 -2
- package/dist/processes.js +12 -0
- package/dist/spawn.js +10 -29
- package/dist/todoScope.js +5 -0
- package/dist/tools/agents.js +9 -13
- package/dist/tools/todos.js +3 -8
- package/dist/version.js +4 -17
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.11.
|
|
1
|
+
{"version":"1.11.5","sha":"486161d","dirty":false,"build_id":"3a33da919c1ac52f423bfdb530cfeda01207921e9c0fb8b262462fae1c266b12"}
|
package/dist/cli.js
CHANGED
|
@@ -36,7 +36,7 @@ import { asNameClash, buildEnvFlags, closeAgentRow, isReservedAgentName, launchA
|
|
|
36
36
|
import { adoptableWindow, applyLayout, claimInitialWindow, configureHiveWindow, controlModeFor, createWindow, DEFAULT_LAYOUT, describePaneChoice, ensureSession, findProjectWindow, foreignSocket, isViewSessionName, listOwnedWindows, liveTargets, observationFailed, observeRowOwnership, ORPHAN_MIN_AGE_MS, orphanScratchServers, orphansWorthWarningAbout, paneIsAloneInWindow, paneVisibility, panePidForRecord, PROCESSES_LAYOUT, processesPaneTitle, processesWindowName, projectWindows, RAW_ATTACH_TMUX_CONFIG, renderAttachCommand, resolveAttachTarget, resolveInTmuxTarget, rowLive, rowOwnership, SESSION_PREFIX, appendGlobalHook, globalHooks, replaceGlobalHooks, sessionName, setPaneTitle, shellQuote, shownPaneTitle, makeProcessesWindow, tmux, TMUX_DOC, tmuxSaysNothingThere, tmuxTimeoutOverride, tmuxSocketPath, untrustedTmuxServer, waitForPaneEstablished, windowLayout, } from "./tmux.js";
|
|
37
37
|
import { collectCrew, crewStoreProblem } from "./crew.js";
|
|
38
38
|
import { describeVisibility, formatLocal, processCounts } from "./dashboard.js";
|
|
39
|
-
import { runningCommandRow, runningCommandRows, snapshotProcesses, stopAllProcesses, stopLine, stopProcess, STOP_REASONS, } from "./processes.js";
|
|
39
|
+
import { runningCommandRow, runningCommandRows, snapshotProcesses, stopAllProcesses, stopLine, stopProcess, renderStart, STOP_REASONS, } from "./processes.js";
|
|
40
40
|
import { activeProfile, agentVarKeys, BUILT_IN_PROJECT_YML, configHash, loadProjectYml, mergedProjectVars, NO_PROFILE, PROJECT_YML_KEYS, resolveCommandDir, resolveFirstMessage, } from "./projectYml.js";
|
|
41
41
|
import { appendClaudeLeadArgs, crewPluginDir, isTrusted, leadIdentityEnv, renderLeadPosture } from "./leadLaunch.js";
|
|
42
42
|
import { handoffStatusSegment } from "./leadHandoff.js";
|
|
@@ -47,7 +47,8 @@ import { FIRST_MESSAGE_SHA_ENV, firstMessageDigest } from "./firstMessage.js";
|
|
|
47
47
|
import { ageSecondsSince, deriveProvenance, describeForHuman, describeLastLogEvent, humanizeAge, lastLogEvent, lastPermissionMode, reportsAgentStateLog, } from "./stateProvenance.js";
|
|
48
48
|
import { awaitingFirstPromptSql } from "./firstPrompt.js";
|
|
49
49
|
import { checkoutRoot, createProfile, forkProfile, PROFILE_FILES, ProfileError, profileExists, profileFileNames, profileNames, profileStatus, readProfileFile, referencedPads, referencedPaths, renderProfileFile, resolveProfileFile, REWRITE_THRESHOLD, templateVars, userProfilesDir, } from "./profiles.js";
|
|
50
|
-
import { getTodoDetail, listTodoSummaries,
|
|
50
|
+
import { getTodoDetail, listTodoSummaries, TODO_STATUSES, } from "./tools/todos.js";
|
|
51
|
+
import { ACTIVE_TODO_SQL, OPEN_BLOCKERS_SQL } from "./todoScope.js";
|
|
51
52
|
import { createPad, getActivePadByName, listActivePads, overwritePadContent, } from "./tools/pads.js";
|
|
52
53
|
import { answerQuestion, listQuestions } from "./questions.js";
|
|
53
54
|
function usage() {
|
|
@@ -213,9 +214,15 @@ async function ensureTrusted(projectId, name, command, dir, env) {
|
|
|
213
214
|
db.prepare("INSERT OR IGNORE INTO command_trust (project_id, name, config_hash) VALUES (?, ?, ?)").run(projectId, name, hash);
|
|
214
215
|
return true;
|
|
215
216
|
}
|
|
216
|
-
function startYmlCommand(project, name, proc, config) {
|
|
217
|
+
function startYmlCommand(project, name, proc, config, trusted) {
|
|
218
|
+
if (!trusted)
|
|
219
|
+
return { status: "refused", reason: "untrusted" };
|
|
217
220
|
if (isReservedAgentName(name)) {
|
|
218
|
-
return
|
|
221
|
+
return {
|
|
222
|
+
status: "refused",
|
|
223
|
+
reason: "reserved-name",
|
|
224
|
+
message: `skipped: "lead" is reserved for this project's lead session and cannot be used as a process name`,
|
|
225
|
+
};
|
|
219
226
|
}
|
|
220
227
|
const existing = db
|
|
221
228
|
.prepare("SELECT id, tmux_target, tmux_socket FROM agents WHERE project_id = ? AND name = ? AND status = 'running'")
|
|
@@ -225,10 +232,15 @@ function startYmlCommand(project, name, proc, config) {
|
|
|
225
232
|
if (live) {
|
|
226
233
|
const windows = projectWindows(sessionName(), project.id);
|
|
227
234
|
const where = windows === null ? null : paneVisibility(existing.tmux_target, windows);
|
|
228
|
-
return where === "shown" || where === "hidden" ?
|
|
235
|
+
return { status: "already-running", location: where === "shown" || where === "hidden" ? where : null };
|
|
236
|
+
}
|
|
237
|
+
if (live === null) {
|
|
238
|
+
return {
|
|
239
|
+
status: "refused",
|
|
240
|
+
reason: "unknown-liveness",
|
|
241
|
+
message: "skipped: tmux could not be probed, so hive cannot tell whether it is already running",
|
|
242
|
+
};
|
|
229
243
|
}
|
|
230
|
-
if (live === null)
|
|
231
|
-
return "skipped: tmux could not be probed, so hive cannot tell whether it is already running";
|
|
232
244
|
closeAgentRow(existing.id);
|
|
233
245
|
}
|
|
234
246
|
let dir;
|
|
@@ -236,7 +248,7 @@ function startYmlCommand(project, name, proc, config) {
|
|
|
236
248
|
dir = resolveCommandDir(project.path, proc.dir);
|
|
237
249
|
}
|
|
238
250
|
catch (e) {
|
|
239
|
-
return `skipped: ${errorMessage(e)}
|
|
251
|
+
return { status: "refused", reason: "escaped-dir", message: `skipped: ${errorMessage(e)}` };
|
|
240
252
|
}
|
|
241
253
|
const placement = proc.visible
|
|
242
254
|
? (config.placement ?? (process.env.HIVE_SPAWN_PLACEMENT === "window" ? "window" : "split"))
|
|
@@ -260,17 +272,20 @@ function startYmlCommand(project, name, proc, config) {
|
|
|
260
272
|
setPaneTitle(target, shownPaneTitle(project.name, name));
|
|
261
273
|
if (landedInProjectId != null) {
|
|
262
274
|
const landedName = getProject(landedInProjectId)?.name ?? `project ${landedInProjectId}`;
|
|
263
|
-
return `started, landed in project "${landedName}"'s window
|
|
275
|
+
return { status: "started", message: `started, landed in project "${landedName}"'s window` };
|
|
264
276
|
}
|
|
265
|
-
return "started";
|
|
277
|
+
return { status: "started", message: "started" };
|
|
266
278
|
}
|
|
267
|
-
return
|
|
268
|
-
|
|
269
|
-
:
|
|
270
|
-
|
|
279
|
+
return {
|
|
280
|
+
status: "started",
|
|
281
|
+
message: inProcessesWindow
|
|
282
|
+
? "started (hidden)"
|
|
283
|
+
: "started in its own window: it opened this project's tmux session, and a session's first window " +
|
|
284
|
+
`cannot be a tile. Move it with: hive hide "${name}"`,
|
|
285
|
+
};
|
|
271
286
|
}
|
|
272
287
|
catch (e) {
|
|
273
|
-
return
|
|
288
|
+
return { status: "failed", message: errorMessage(e) };
|
|
274
289
|
}
|
|
275
290
|
}
|
|
276
291
|
function attach(session, project, window) {
|
|
@@ -636,9 +651,10 @@ async function cmdLead(argv) {
|
|
|
636
651
|
console.log(`- ${name}: defined, auto_start off (start with: hive start "${name}")`);
|
|
637
652
|
continue;
|
|
638
653
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
654
|
+
const trusted = await ensureTrusted(project.id, name, proc.command, proc.dir, proc.env);
|
|
655
|
+
const result = startYmlCommand(project, name, proc, config, trusted);
|
|
656
|
+
if (trusted)
|
|
657
|
+
console.log(`- ${name}: ${renderStart(result)}`);
|
|
642
658
|
}
|
|
643
659
|
}
|
|
644
660
|
if (registrationNotice) {
|
|
@@ -1366,9 +1382,12 @@ async function cmdStart(argv) {
|
|
|
1366
1382
|
console.log(processNotDefined(config, name));
|
|
1367
1383
|
process.exit(1);
|
|
1368
1384
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1385
|
+
const trusted = await ensureTrusted(project.id, name, proc.command, proc.dir, proc.env);
|
|
1386
|
+
const result = startYmlCommand(project, name, proc, config, trusted);
|
|
1387
|
+
if (trusted)
|
|
1388
|
+
console.log(`${name}: ${renderStart(result)}`);
|
|
1389
|
+
if (!trusted || result.status === "failed" || result.status === "refused")
|
|
1390
|
+
process.exitCode = 1;
|
|
1372
1391
|
}
|
|
1373
1392
|
function processNotDefined(config, name) {
|
|
1374
1393
|
const known = Object.keys(config?.processes ?? {});
|
|
@@ -1631,7 +1650,7 @@ function cmdStatus() {
|
|
|
1631
1650
|
.prepare("SELECT * FROM agents WHERE project_id = ? AND status = 'running' ORDER BY kind DESC, id")
|
|
1632
1651
|
.all(project.id);
|
|
1633
1652
|
const todos = db
|
|
1634
|
-
.prepare(
|
|
1653
|
+
.prepare(`SELECT COUNT(*) AS n FROM todos t WHERE t.project_id = ? AND ${ACTIVE_TODO_SQL}`)
|
|
1635
1654
|
.get(project.id).n;
|
|
1636
1655
|
const { wakes, heldWakes } = db
|
|
1637
1656
|
.prepare(`SELECT COUNT(*) AS wakes, COUNT(held_at) AS heldWakes FROM wakes WHERE project_id = ? AND ${ACTIVE_TIMER_WHERE}`)
|
|
@@ -2769,9 +2788,9 @@ function cmdStatusline() {
|
|
|
2769
2788
|
const count = (sql) => db.prepare(sql).get(project.id).n;
|
|
2770
2789
|
const agents = count("SELECT COUNT(*) AS n FROM agents WHERE project_id = ? AND status = 'running' AND kind = 'agent'");
|
|
2771
2790
|
const commands = count("SELECT COUNT(*) AS n FROM agents WHERE project_id = ? AND status = 'running' AND kind = 'command'");
|
|
2772
|
-
const todos = count(
|
|
2791
|
+
const todos = count(`SELECT COUNT(*) AS n FROM todos t WHERE t.project_id = ? AND ${ACTIVE_TODO_SQL}`);
|
|
2773
2792
|
const ready = count(`SELECT COUNT(*) AS n FROM todos t
|
|
2774
|
-
WHERE t.project_id = ? AND
|
|
2793
|
+
WHERE t.project_id = ? AND ${ACTIVE_TODO_SQL}
|
|
2775
2794
|
AND NOT EXISTS (${OPEN_BLOCKERS_SQL})`);
|
|
2776
2795
|
const pads = count("SELECT COUNT(*) AS n FROM pads WHERE project_id = ? AND archived = 0");
|
|
2777
2796
|
const wakes = count(`SELECT COUNT(*) AS n FROM wakes WHERE project_id = ? AND ${ACTIVE_TIMER_WHERE}`);
|
package/dist/codexHome.js
CHANGED
package/dist/context.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
1
|
import { lstatSync, realpathSync } from "node:fs";
|
|
3
2
|
import { userInfo } from "node:os";
|
|
4
3
|
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
@@ -7,6 +6,7 @@ import { db } from "./db.js";
|
|
|
7
6
|
import { LEAD_KIND } from "./spawn.js";
|
|
8
7
|
import { rowOwnership } from "./tmux.js";
|
|
9
8
|
import { recordQueenRefusal } from "./queenAudit.js";
|
|
9
|
+
import { gitOut } from "./git.js";
|
|
10
10
|
let selectedId = null;
|
|
11
11
|
let cachedActorId = null;
|
|
12
12
|
let lastTouchMs = 0;
|
|
@@ -92,18 +92,13 @@ export function registeredAncestor(dir) {
|
|
|
92
92
|
return bestPrefixMatch(projects, dir);
|
|
93
93
|
}
|
|
94
94
|
export function gitPrimaryRoot(dir) {
|
|
95
|
+
const commonDir = gitOut(dir, ["rev-parse", "--git-common-dir"]);
|
|
96
|
+
if (commonDir === null)
|
|
97
|
+
return null;
|
|
98
|
+
const resolvedCommonDir = resolve(dir, commonDir);
|
|
99
|
+
if (basename(resolvedCommonDir) !== ".git")
|
|
100
|
+
return null;
|
|
95
101
|
try {
|
|
96
|
-
const { GIT_DIR: _gitDir, GIT_COMMON_DIR: _gitCommonDir, GIT_WORK_TREE: _gitWorkTree, ...env } = process.env;
|
|
97
|
-
const commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
98
|
-
cwd: dir,
|
|
99
|
-
encoding: "utf8",
|
|
100
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
101
|
-
timeout: 2000,
|
|
102
|
-
env,
|
|
103
|
-
}).trim();
|
|
104
|
-
const resolvedCommonDir = resolve(dir, commonDir);
|
|
105
|
-
if (basename(resolvedCommonDir) !== ".git")
|
|
106
|
-
return null;
|
|
107
102
|
return realpathSync(dirname(resolvedCommonDir));
|
|
108
103
|
}
|
|
109
104
|
catch {
|
|
@@ -111,19 +106,13 @@ export function gitPrimaryRoot(dir) {
|
|
|
111
106
|
}
|
|
112
107
|
}
|
|
113
108
|
function gitDirs(dir) {
|
|
109
|
+
const result = gitOut(dir, ["rev-parse", "--git-dir", "--git-common-dir"]);
|
|
110
|
+
if (result === null)
|
|
111
|
+
return null;
|
|
112
|
+
const out = result.split("\n");
|
|
113
|
+
if (out.length !== 2)
|
|
114
|
+
return null;
|
|
114
115
|
try {
|
|
115
|
-
const { GIT_DIR: _gitDir, GIT_COMMON_DIR: _gitCommonDir, GIT_WORK_TREE: _gitWorkTree, ...env } = process.env;
|
|
116
|
-
const out = execFileSync("git", ["rev-parse", "--git-dir", "--git-common-dir"], {
|
|
117
|
-
cwd: dir,
|
|
118
|
-
encoding: "utf8",
|
|
119
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
120
|
-
timeout: 2000,
|
|
121
|
-
env,
|
|
122
|
-
})
|
|
123
|
-
.trim()
|
|
124
|
-
.split("\n");
|
|
125
|
-
if (out.length !== 2)
|
|
126
|
-
return null;
|
|
127
116
|
return {
|
|
128
117
|
gitDir: realpathSync(resolve(dir, out[0])),
|
|
129
118
|
commonDir: realpathSync(resolve(dir, out[1])),
|
package/dist/crew.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
1
|
import { db } from "./db.js";
|
|
3
2
|
import { isLinkedWorktree } from "./context.js";
|
|
4
3
|
import { awaitingFirstPrompt } from "./firstPrompt.js";
|
|
5
4
|
import { heldReasonLabel } from "./heldLabel.js";
|
|
6
5
|
import { harnessFor, paneClassifierFor } from "./harnesses.js";
|
|
7
6
|
import { NEEDS_HUMAN_TAG } from "./portfolio.js";
|
|
7
|
+
import { ACTIVE_TODO_SQL } from "./todoScope.js";
|
|
8
8
|
import { pendingQuestionCount } from "./questions.js";
|
|
9
9
|
import { loadProjectYml } from "./projectYml.js";
|
|
10
10
|
import { parseTags } from "./result.js";
|
|
11
11
|
import { ACTIVE_TIMER_WHERE } from "./scheduler.js";
|
|
12
12
|
import { reportsAgentStateLog } from "./stateProvenance.js";
|
|
13
|
+
import { gitOut } from "./git.js";
|
|
13
14
|
import { liveTargets, rowOwnership } from "./tmux.js";
|
|
14
15
|
import { SKILL_TOOL, lastComponent, readContextFill, readRecentToolCalls, } from "./transcript.js";
|
|
15
16
|
export function crewStoreProblem(database = db) {
|
|
@@ -50,36 +51,18 @@ function oneShotWatched(pending) {
|
|
|
50
51
|
}
|
|
51
52
|
});
|
|
52
53
|
}
|
|
53
|
-
function stripped(env) {
|
|
54
|
-
const { GIT_DIR: _gitDir, GIT_COMMON_DIR: _gitCommonDir, GIT_WORK_TREE: _gitWorkTree, ...rest } = env;
|
|
55
|
-
return rest;
|
|
56
|
-
}
|
|
57
|
-
function git(cwd, args) {
|
|
58
|
-
try {
|
|
59
|
-
return execFileSync("git", args, {
|
|
60
|
-
cwd,
|
|
61
|
-
encoding: "utf8",
|
|
62
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
63
|
-
timeout: 2000,
|
|
64
|
-
env: stripped(process.env),
|
|
65
|
-
}).trim();
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
54
|
export function readCommitsAhead(cwd, projectRoot) {
|
|
72
55
|
if (!cwd || cwd === projectRoot || !isLinkedWorktree(cwd))
|
|
73
56
|
return null;
|
|
74
57
|
const candidates = [];
|
|
75
|
-
const remoteHead =
|
|
58
|
+
const remoteHead = gitOut(cwd, ["symbolic-ref", "--quiet", "refs/remotes/origin/HEAD"]);
|
|
76
59
|
if (remoteHead)
|
|
77
60
|
candidates.push(remoteHead);
|
|
78
61
|
candidates.push("refs/heads/main", "refs/heads/master");
|
|
79
62
|
for (const ref of candidates) {
|
|
80
|
-
if (
|
|
63
|
+
if (gitOut(cwd, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]) === null)
|
|
81
64
|
continue;
|
|
82
|
-
const count =
|
|
65
|
+
const count = gitOut(cwd, ["rev-list", "--count", `${ref}..HEAD`]);
|
|
83
66
|
return count !== null && /^\d+$/.test(count) ? Number(count) : null;
|
|
84
67
|
}
|
|
85
68
|
return null;
|
|
@@ -481,7 +464,7 @@ export function collectCrew(project, now) {
|
|
|
481
464
|
if (!staffed.has(todo.id))
|
|
482
465
|
lanes.push({ todo, worker: null });
|
|
483
466
|
const needsYou = db
|
|
484
|
-
.prepare(
|
|
467
|
+
.prepare(`SELECT t.id, t.slug, t.tags FROM todos t WHERE t.project_id = ? AND ${ACTIVE_TODO_SQL} ORDER BY t.id`)
|
|
485
468
|
.all(project.id)
|
|
486
469
|
.filter((t) => parseTags(t.tags).includes(NEEDS_HUMAN_TAG))
|
|
487
470
|
.map((t) => ({ id: t.id, slug: t.slug }));
|
package/dist/dashboard.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { db } from "./db.js";
|
|
4
|
+
import { ACTIVE_TODO_SQL, NOT_COMPLETED_TODO_SQL, OPEN_BLOCKERS_SQL } from "./todoScope.js";
|
|
4
5
|
import { getProject } from "./context.js";
|
|
5
6
|
export const describeVisibility = (visibility) => visibility === null ? "running" : visibility === "window" ? "own window" : visibility;
|
|
6
7
|
export function processCounts(procs) {
|
|
@@ -127,12 +128,9 @@ function fetchNextWake(projectId) {
|
|
|
127
128
|
function fetchTodoCounts(projectId) {
|
|
128
129
|
return db
|
|
129
130
|
.prepare(`SELECT
|
|
130
|
-
(SELECT COUNT(*) FROM todos WHERE project_id = ? AND
|
|
131
|
-
(SELECT COUNT(*) FROM todos t WHERE t.project_id = ? AND
|
|
132
|
-
AND EXISTS (
|
|
133
|
-
SELECT 1 FROM todo_blockers b JOIN todos bt ON bt.id = b.blocker_id
|
|
134
|
-
WHERE b.todo_id = t.id AND bt.status != 'completed'
|
|
135
|
-
)) AS blocked,
|
|
131
|
+
(SELECT COUNT(*) FROM todos t WHERE t.project_id = ? AND ${ACTIVE_TODO_SQL}) AS open,
|
|
132
|
+
(SELECT COUNT(*) FROM todos t WHERE t.project_id = ? AND ${ACTIVE_TODO_SQL}
|
|
133
|
+
AND EXISTS (${OPEN_BLOCKERS_SQL})) AS blocked,
|
|
136
134
|
(SELECT COUNT(*) FROM todos
|
|
137
135
|
WHERE project_id = ? AND completed_at IS NOT NULL
|
|
138
136
|
AND date(completed_at, 'localtime') >= date('now', 'localtime', '-6 days')) AS completed7d`)
|
|
@@ -262,10 +260,9 @@ function renderBoardSection(index, projectId) {
|
|
|
262
260
|
}
|
|
263
261
|
const OPEN_TODOS_SQL = `
|
|
264
262
|
SELECT t.id, t.title, t.body, t.slug, t.status, t.priority,
|
|
265
|
-
|
|
266
|
-
WHERE b.todo_id = t.id AND bt.status != 'completed') AS open_blockers
|
|
263
|
+
EXISTS (${OPEN_BLOCKERS_SQL}) AS blocked
|
|
267
264
|
FROM todos t
|
|
268
|
-
WHERE t.project_id = ? AND
|
|
265
|
+
WHERE t.project_id = ? AND ${ACTIVE_TODO_SQL}
|
|
269
266
|
ORDER BY CASE t.priority WHEN 'high' THEN 0 WHEN 'medium' THEN 1 ELSE 2 END, t.id`;
|
|
270
267
|
function fetchBlockersFor(todoIds) {
|
|
271
268
|
const byTodo = new Map();
|
|
@@ -274,7 +271,7 @@ function fetchBlockersFor(todoIds) {
|
|
|
274
271
|
const placeholders = todoIds.map(() => "?").join(",");
|
|
275
272
|
const rows = db
|
|
276
273
|
.prepare(`SELECT b.todo_id, t.id, t.title FROM todo_blockers b JOIN todos t ON t.id = b.blocker_id
|
|
277
|
-
WHERE b.todo_id IN (${placeholders}) AND t
|
|
274
|
+
WHERE b.todo_id IN (${placeholders}) AND t.${NOT_COMPLETED_TODO_SQL}`)
|
|
278
275
|
.all(...todoIds);
|
|
279
276
|
for (const row of rows) {
|
|
280
277
|
const list = byTodo.get(row.todo_id) ?? [];
|
|
@@ -291,11 +288,11 @@ function todoBadgeLevel(status, blocked) {
|
|
|
291
288
|
function renderTodosSection(index, projectId) {
|
|
292
289
|
const all = db.prepare(OPEN_TODOS_SQL).all(projectId);
|
|
293
290
|
const shown = all.slice(0, TODO_CAP);
|
|
294
|
-
const blockersByTodo = fetchBlockersFor(shown.filter((t) => t.
|
|
295
|
-
const blockedCount = all.filter((t) => t.
|
|
291
|
+
const blockersByTodo = fetchBlockersFor(shown.filter((t) => t.blocked).map((t) => t.id));
|
|
292
|
+
const blockedCount = all.filter((t) => t.blocked).length;
|
|
296
293
|
const rows = shown
|
|
297
294
|
.map((t) => {
|
|
298
|
-
const blocked = t.
|
|
295
|
+
const blocked = Boolean(t.blocked);
|
|
299
296
|
const blockers = blocked
|
|
300
297
|
? (blockersByTodo.get(t.id) ?? []).map((b) => `${mono(`#${b.id}`)} ${prose(b.title)}`).join(", ")
|
|
301
298
|
: "";
|
|
@@ -324,7 +321,7 @@ function renderTodosSection(index, projectId) {
|
|
|
324
321
|
`<p class="filter-none" id="todo-filter-none" hidden>No open todo matches that filter.</p>`
|
|
325
322
|
: "";
|
|
326
323
|
const body = filter +
|
|
327
|
-
renderCappedList(shown.length, all.length, "open todos", "todos", rows, emptyState("No open todos.", "
|
|
324
|
+
renderCappedList(shown.length, all.length, "open todos", "todos", rows, emptyState("No open todos.", "No active todos are listed. New open or in-progress todos land here. Backlog todos stay off this list."), "todo-list");
|
|
328
325
|
return section(index, "todos", "Todos", `${all.length} open, ${blockedCount} blocked`, "checklist", String(shown.length), body);
|
|
329
326
|
}
|
|
330
327
|
export const CHART_DAYS = 7;
|
package/dist/db.js
CHANGED
|
@@ -576,6 +576,42 @@ ALTER TABLE queen_audit ADD COLUMN problem TEXT NOT NULL DEFAULT '';
|
|
|
576
576
|
`,
|
|
577
577
|
`
|
|
578
578
|
ALTER TABLE agents ADD COLUMN pane_started_at TEXT;
|
|
579
|
+
`,
|
|
580
|
+
`
|
|
581
|
+
DROP TRIGGER fence_handoff_held_claim;
|
|
582
|
+
CREATE TRIGGER fence_handoff_held_claim
|
|
583
|
+
BEFORE UPDATE OF fire_count, delivery_method ON wakes
|
|
584
|
+
FOR EACH ROW
|
|
585
|
+
WHEN (NEW.fire_count > OLD.fire_count
|
|
586
|
+
OR (NEW.delivery_method IS 'pty-after-socket-timeout'
|
|
587
|
+
AND OLD.delivery_method IS NOT 'pty-after-socket-timeout'
|
|
588
|
+
AND NEW.typed_at IS NULL))
|
|
589
|
+
AND EXISTS (
|
|
590
|
+
SELECT 1 FROM lead_handoffs h
|
|
591
|
+
WHERE h.lead_agent_id = (SELECT a.id FROM agents a
|
|
592
|
+
WHERE a.actor_id = NEW.deliver_actor
|
|
593
|
+
ORDER BY (a.status = 'running') DESC,
|
|
594
|
+
a.id DESC
|
|
595
|
+
LIMIT 1)
|
|
596
|
+
AND h.state NOT IN ('completed', 'failed')
|
|
597
|
+
AND h.request_wake_id IS NOT NEW.id
|
|
598
|
+
AND (
|
|
599
|
+
(h.state = 'grace'
|
|
600
|
+
AND h.hold_released_at IS NULL
|
|
601
|
+
AND h.grace_started_at IS NOT NULL
|
|
602
|
+
AND (julianday('now') - julianday(h.grace_started_at)) * 86400 < 900)
|
|
603
|
+
OR ((h.state IN ('grace', 'started')
|
|
604
|
+
OR (h.hold_released_at IS NULL AND (
|
|
605
|
+
h.state IN ('respawning', 'ambiguous', 'wind_down')
|
|
606
|
+
OR (h.state = 'requested'
|
|
607
|
+
AND (h.reason = 'stop' OR h.pass >= 2))
|
|
608
|
+
OR (h.state = 'postponed' AND h.reason = 'stop'))))
|
|
609
|
+
AND h.state <> 'grace'
|
|
610
|
+
AND (julianday('now') - julianday(h.updated_at)) * 86400 < 3600)
|
|
611
|
+
))
|
|
612
|
+
BEGIN
|
|
613
|
+
SELECT RAISE(IGNORE);
|
|
614
|
+
END;
|
|
579
615
|
`,
|
|
580
616
|
];
|
|
581
617
|
export function storeSchemaAhead(database) {
|
package/dist/git.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
export function gitOut(cwd, args) {
|
|
3
|
+
const { GIT_DIR: _gitDir, GIT_COMMON_DIR: _gitCommonDir, GIT_WORK_TREE: _gitWorkTree, ...env } = process.env;
|
|
4
|
+
try {
|
|
5
|
+
return execFileSync("git", args, {
|
|
6
|
+
cwd,
|
|
7
|
+
encoding: "utf8",
|
|
8
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
9
|
+
timeout: 2000,
|
|
10
|
+
env,
|
|
11
|
+
}).trim();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
package/dist/harnesses.js
CHANGED
|
@@ -53,7 +53,6 @@ const claudeHarness = {
|
|
|
53
53
|
supportsRename: true,
|
|
54
54
|
classifiesPaneScreen: true,
|
|
55
55
|
paneClassifier: { choiceCheck: paneChoiceCheck, inputBoxState, inputBoxSnapshot, hasInputBox: paneHasInputBox, trailerView: claudeTrailerView },
|
|
56
|
-
hasScopes: true,
|
|
57
56
|
needsHome: false,
|
|
58
57
|
// "--" so a message that starts with a dash is never read as a flag.
|
|
59
58
|
initialPromptArgs: (message) => ["--", message],
|
|
@@ -82,7 +81,6 @@ export const codexHarness = {
|
|
|
82
81
|
hasInputBox: codexPaneHasInputBox,
|
|
83
82
|
trailerView: codexTrailerView,
|
|
84
83
|
},
|
|
85
|
-
hasScopes: false,
|
|
86
84
|
needsHome: true,
|
|
87
85
|
// Auto-submits with no Enter; verified live on v0.149.0.
|
|
88
86
|
initialPromptArgs: (message) => ["--", message],
|
|
@@ -106,7 +104,6 @@ const unknownHarness = {
|
|
|
106
104
|
supportsRename: false,
|
|
107
105
|
classifiesPaneScreen: false,
|
|
108
106
|
paneClassifier: null,
|
|
109
|
-
hasScopes: false,
|
|
110
107
|
needsHome: false,
|
|
111
108
|
initialPromptArgs: null,
|
|
112
109
|
// An unrecognized command's vocabulary is unknown, so it stops nothing rather than guessing.
|
package/dist/kickoff.js
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
1
|
import { realpathSync } from "node:fs";
|
|
3
2
|
import { fileURLToPath } from "node:url";
|
|
4
3
|
import { cutToUnitBudget } from "./slug.js";
|
|
5
4
|
import { FIRST_MESSAGE_SHA_ENV } from "./firstMessage.js";
|
|
5
|
+
import { gitOut } from "./git.js";
|
|
6
6
|
export const OUTPUT_BUDGET = 10_000;
|
|
7
7
|
const CONTEXT_BUDGET = 6_000;
|
|
8
8
|
const BOARD_BUDGET = 1_800;
|
|
9
9
|
function currentBranch(dir) {
|
|
10
|
-
|
|
11
|
-
return execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
12
|
-
cwd: dir,
|
|
13
|
-
encoding: "utf8",
|
|
14
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
15
|
-
}).trim();
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
10
|
+
return gitOut(dir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
20
11
|
}
|
|
21
12
|
export function truncate(text, limit) {
|
|
22
13
|
if (text.length <= limit)
|
|
@@ -28,7 +19,7 @@ async function digest(projectPath, profile, warnings) {
|
|
|
28
19
|
const { findProjectForCwd } = await import("./context.js");
|
|
29
20
|
const { ACTIVE_TIMER_WHERE } = await import("./scheduler.js");
|
|
30
21
|
const { getActivePadByName } = await import("./tools/pads.js");
|
|
31
|
-
const { OPEN_BLOCKERS_SQL } = await import("./
|
|
22
|
+
const { ACTIVE_TODO_SQL, OPEN_BLOCKERS_SQL } = await import("./todoScope.js");
|
|
32
23
|
migrate();
|
|
33
24
|
const project = findProjectForCwd();
|
|
34
25
|
if (!project || project.path !== projectPath)
|
|
@@ -53,7 +44,7 @@ async function digest(projectPath, profile, warnings) {
|
|
|
53
44
|
.all(project.id);
|
|
54
45
|
const blocked = db
|
|
55
46
|
.prepare(`SELECT COUNT(*) AS n FROM todos t
|
|
56
|
-
WHERE t.project_id = ? AND
|
|
47
|
+
WHERE t.project_id = ? AND ${ACTIVE_TODO_SQL}
|
|
57
48
|
AND EXISTS (${OPEN_BLOCKERS_SQL})`)
|
|
58
49
|
.get(project.id).n;
|
|
59
50
|
if (inFlight.length > 0) {
|
package/dist/leadHandoffState.js
CHANGED
|
@@ -30,7 +30,7 @@ export function readActiveHandoff(leadAgentId) {
|
|
|
30
30
|
export function handoffHoldsAutomation(row) {
|
|
31
31
|
switch (row.state) {
|
|
32
32
|
case "grace":
|
|
33
|
-
return
|
|
33
|
+
return row.hold_released_at === null && !olderThan(row.grace_started_at, STOP_HOLD_CAP_SECONDS);
|
|
34
34
|
case "started":
|
|
35
35
|
return !olderThan(row.updated_at, HOLD_CEILING_SECONDS);
|
|
36
36
|
case "respawning":
|
package/dist/portfolio.js
CHANGED
|
@@ -2,7 +2,7 @@ import { db } from "./db.js";
|
|
|
2
2
|
import { listProjects } from "./context.js";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { parseTags } from "./result.js";
|
|
5
|
-
import { OPEN_BLOCKERS_SQL } from "./
|
|
5
|
+
import { ACTIVE_TODO_SQL, OPEN_BLOCKERS_SQL } from "./todoScope.js";
|
|
6
6
|
import { pendingQuestionCount } from "./questions.js";
|
|
7
7
|
import { leadSessionEnded, readLeadTurnState } from "./leadState.js";
|
|
8
8
|
import { liveTargets, ownershipLiveness, rowOwnership } from "./tmux.js";
|
|
@@ -63,7 +63,7 @@ function projectRow(project, now, snapshot) {
|
|
|
63
63
|
.prepare(`SELECT t.id, t.title, t.slug, t.priority, t.status, t.tags, t.updated_at,
|
|
64
64
|
EXISTS (${OPEN_BLOCKERS_SQL}) AS blocked
|
|
65
65
|
FROM todos t
|
|
66
|
-
WHERE t.project_id = ? AND
|
|
66
|
+
WHERE t.project_id = ? AND ${ACTIVE_TODO_SQL}`)
|
|
67
67
|
.all(project.id);
|
|
68
68
|
const todos = { open: 0, in_progress: 0, blocked: 0, blocked_in_progress: 0, high: 0 };
|
|
69
69
|
const tagged = [];
|
package/dist/processes.js
CHANGED
|
@@ -38,6 +38,18 @@ export const STOP_REASONS = {
|
|
|
38
38
|
leadPaneExited: "lead pane exited",
|
|
39
39
|
previousLead: "left running by a previous lead",
|
|
40
40
|
};
|
|
41
|
+
export function renderStart(result) {
|
|
42
|
+
switch (result.status) {
|
|
43
|
+
case "started":
|
|
44
|
+
return result.message;
|
|
45
|
+
case "already-running":
|
|
46
|
+
return `already running${result.location ? ` (${result.location})` : ""}`;
|
|
47
|
+
case "refused":
|
|
48
|
+
return result.message ?? "";
|
|
49
|
+
case "failed":
|
|
50
|
+
return `failed: ${result.message}`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
41
53
|
const STOPPABLE_COLUMNS = "id, project_id, name, tmux_target, tmux_socket, pane_pid, created_at";
|
|
42
54
|
export function runningCommandRows(projectId) {
|
|
43
55
|
return db
|
package/dist/spawn.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
1
|
import { existsSync } from "node:fs";
|
|
3
2
|
import { preservedRolloutPath, reapCodexHome } from "./codexHome.js";
|
|
4
3
|
import { getProject } from "./context.js";
|
|
4
|
+
import { gitOut } from "./git.js";
|
|
5
5
|
import { dataDir, db } from "./db.js";
|
|
6
6
|
import { loadProjectYml } from "./projectYml.js";
|
|
7
7
|
import { applyLayout, claimInitialWindow, createWindow, DEFAULT_LAYOUT, ensureSession, crossServerRefusal, findProcessesWindow, findProjectWindow, isPaneTarget, paneProcessExited, paneWindow, PROCESSES_LAYOUT, processesPaneTitle, processesWindowName, retainOnExitArgs, rowLive, sessionName, setPaneTitle, setRemainOnExit, makeProcessesWindow, tmux, tmuxSocketPath, untrustedTmuxServer, waitForPaneEstablished, windowLayout, windowOwner, windowTitle, rowOwnership, } from "./tmux.js";
|
|
@@ -347,34 +347,19 @@ export function renameAgent(agent, newName, projectName) {
|
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
349
|
export function branchAt(cwd) {
|
|
350
|
-
const
|
|
351
|
-
try {
|
|
352
|
-
return execFileSync("git", args, {
|
|
353
|
-
cwd,
|
|
354
|
-
encoding: "utf8",
|
|
355
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
356
|
-
timeout: 2000,
|
|
357
|
-
}).trim();
|
|
358
|
-
}
|
|
359
|
-
catch {
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
};
|
|
363
|
-
const branch = git(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
350
|
+
const branch = gitOut(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
364
351
|
if (branch === null)
|
|
365
352
|
return "";
|
|
366
353
|
if (branch !== "HEAD")
|
|
367
354
|
return branch;
|
|
368
|
-
return
|
|
355
|
+
return gitOut(cwd, ["rev-parse", "--short", "HEAD"]) ?? "";
|
|
369
356
|
}
|
|
370
357
|
export function parkAgentRow(agentId, expected, branch) {
|
|
371
358
|
const park = "UPDATE agents SET status = 'closed', closed_at = datetime('now'), parked_at = datetime('now'), " +
|
|
372
359
|
"parked_branch = ? WHERE id = ? AND status = 'running' AND tmux_target = ?";
|
|
373
|
-
const row =
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
.prepare(`${park} AND tmux_socket = ? AND pane_pid = ? RETURNING parked_at`)
|
|
377
|
-
.get(branch, agentId, expected.tmux_target, expected.tmux_socket, expected.pane_pid);
|
|
360
|
+
const row = db
|
|
361
|
+
.prepare(`${park} AND tmux_socket = ? AND pane_pid = ? RETURNING parked_at`)
|
|
362
|
+
.get(branch, agentId, expected.tmux_target, expected.tmux_socket, expected.pane_pid);
|
|
378
363
|
return row?.parked_at;
|
|
379
364
|
}
|
|
380
365
|
export const paneIdentity = (row) => ({
|
|
@@ -455,13 +440,9 @@ export function closeAgentRow(agentId, expected) {
|
|
|
455
440
|
? db
|
|
456
441
|
.prepare("UPDATE agents SET status = 'closed', closed_at = datetime('now') WHERE id = ? AND status = 'running'")
|
|
457
442
|
.run(agentId)
|
|
458
|
-
:
|
|
459
|
-
?
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
: db
|
|
463
|
-
.prepare("UPDATE agents SET status = 'closed', closed_at = datetime('now') WHERE id = ? AND status = 'running' " +
|
|
464
|
-
"AND tmux_target = ? AND tmux_socket = ? AND pane_pid = ?")
|
|
465
|
-
.run(agentId, expected.tmux_target, expected.tmux_socket, expected.pane_pid);
|
|
443
|
+
: db
|
|
444
|
+
.prepare("UPDATE agents SET status = 'closed', closed_at = datetime('now') WHERE id = ? AND status = 'running' " +
|
|
445
|
+
"AND tmux_target = ? AND tmux_socket = ? AND pane_pid = ?")
|
|
446
|
+
.run(agentId, expected.tmux_target, expected.tmux_socket, expected.pane_pid);
|
|
466
447
|
return info.changes > 0;
|
|
467
448
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const ACTIVE_TODO_SQL = "t.status IN ('open', 'in_progress') AND t.archived_at IS NULL";
|
|
2
|
+
export const NOT_COMPLETED_TODO_SQL = "status != 'completed'";
|
|
3
|
+
export const OPEN_BLOCKERS_FROM_WHERE = `FROM todo_blockers b JOIN todos bt ON bt.id = b.blocker_id
|
|
4
|
+
WHERE b.todo_id = t.id AND bt.${NOT_COMPLETED_TODO_SQL}`;
|
|
5
|
+
export const OPEN_BLOCKERS_SQL = `SELECT 1 ${OPEN_BLOCKERS_FROM_WHERE}`;
|
package/dist/tools/agents.js
CHANGED
|
@@ -695,19 +695,15 @@ export function registerAgents(server) {
|
|
|
695
695
|
note: "The pane never became ready. The brief is loaded regardless; check agent_output before sending the worker its assignment - typing into it now risks losing the text silently.",
|
|
696
696
|
}),
|
|
697
697
|
}
|
|
698
|
-
:
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
brief_path
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
"supported way to send them. The brief is written to brief_path - open that pane " +
|
|
708
|
-
"(tmux attach) and paste it in yourself, or drive the worker with agent_send(keys: [...]). " +
|
|
709
|
-
"Wakes aimed at this worker are refused for the same reason.",
|
|
710
|
-
}),
|
|
698
|
+
: {
|
|
699
|
+
brief_path: writeAgentBrief(agentId, workerBrief(briefFor(actorId))),
|
|
700
|
+
note: `This worker runs ${JSON.stringify(commandHead(baseCommand))}, which hive cannot brief and ` +
|
|
701
|
+
"cannot type into: the guards that make typing safe read claude's chrome, so agent_send's " +
|
|
702
|
+
"text path refuses this pane. There are no instructions to prepend, because there is no " +
|
|
703
|
+
"supported way to send them. The brief is written to brief_path - open that pane " +
|
|
704
|
+
"(tmux attach) and paste it in yourself, or drive the worker with agent_send(keys: [...]). " +
|
|
705
|
+
"Wakes aimed at this worker are refused for the same reason.",
|
|
706
|
+
}),
|
|
711
707
|
};
|
|
712
708
|
if (omittedTodoWarning) {
|
|
713
709
|
const existingNote = typeof receipt.note === "string" ? receipt.note : "";
|
package/dist/tools/todos.js
CHANGED
|
@@ -6,6 +6,7 @@ import { matchesAnyTag, parseTags, run } from "../result.js";
|
|
|
6
6
|
import { findUnsafeControlChar } from "../tmux.js";
|
|
7
7
|
import { SLUG_MAX_LEN, fallbackSlug } from "../slug.js";
|
|
8
8
|
import { idParam, limitParam, offsetParam, projectIdParam } from "./params.js";
|
|
9
|
+
import { NOT_COMPLETED_TODO_SQL, OPEN_BLOCKERS_FROM_WHERE, OPEN_BLOCKERS_SQL } from "../todoScope.js";
|
|
9
10
|
const priorityParam = z.enum(["high", "medium", "low"]);
|
|
10
11
|
const slugParam = z
|
|
11
12
|
.string()
|
|
@@ -19,11 +20,8 @@ const slugParam = z
|
|
|
19
20
|
export { SLUG_MAX_LEN, fallbackSlug };
|
|
20
21
|
export const TODO_STATUSES = ["open", "in_progress", "backlog", "completed"];
|
|
21
22
|
const statusParam = z.enum(TODO_STATUSES);
|
|
22
|
-
const OPEN_BLOCKERS_FROM_WHERE = `FROM todo_blockers b JOIN todos bt ON bt.id = b.blocker_id
|
|
23
|
-
WHERE b.todo_id = t.id AND bt.status != 'completed'`;
|
|
24
|
-
export const OPEN_BLOCKERS_SQL = `SELECT 1 ${OPEN_BLOCKERS_FROM_WHERE}`;
|
|
25
23
|
const LIVE_DEPENDENTS_SQL = `SELECT t.id AS todo_id FROM todo_blockers b JOIN todos t ON t.id = b.todo_id
|
|
26
|
-
WHERE b.blocker_id = ? AND t
|
|
24
|
+
WHERE b.blocker_id = ? AND t.${NOT_COMPLETED_TODO_SQL} AND t.archived_at IS NULL`;
|
|
27
25
|
export const COMMENT_COUNT_SQL = `(SELECT COUNT(*) FROM todo_comments c WHERE c.todo_id = t.id)`;
|
|
28
26
|
const SUMMARY_SQL = `
|
|
29
27
|
SELECT t.*,
|
|
@@ -191,10 +189,7 @@ const completeTodo = db.transaction((projectId, todoId, completed) => {
|
|
|
191
189
|
if (completed) {
|
|
192
190
|
newlyUnblocked = db
|
|
193
191
|
.prepare(`${LIVE_DEPENDENTS_SQL}
|
|
194
|
-
AND NOT EXISTS (
|
|
195
|
-
SELECT 1 FROM todo_blockers b2 JOIN todos bt ON bt.id = b2.blocker_id
|
|
196
|
-
WHERE b2.todo_id = t.id AND bt.status != 'completed'
|
|
197
|
-
)`)
|
|
192
|
+
AND NOT EXISTS (${OPEN_BLOCKERS_SQL})`)
|
|
198
193
|
.all(todo.id).map((r) => r.todo_id);
|
|
199
194
|
}
|
|
200
195
|
return { todo_id: todo.id, completed, newly_unblocked: newlyUnblocked };
|
package/dist/version.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
1
|
import { readFileSync, statSync } from "node:fs";
|
|
3
2
|
import { dirname, join } from "node:path";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { gitOut } from "./git.js";
|
|
5
5
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
6
6
|
const checkoutRoot = join(moduleDir, "..");
|
|
7
7
|
function readBuildInfo() {
|
|
@@ -60,22 +60,9 @@ function packageVersion() {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
function currentGit() {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
encoding: "utf8",
|
|
67
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
68
|
-
}).trim();
|
|
69
|
-
const status = execFileSync("git", ["status", "--porcelain"], {
|
|
70
|
-
cwd: checkoutRoot,
|
|
71
|
-
encoding: "utf8",
|
|
72
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
73
|
-
});
|
|
74
|
-
return { sha, dirty: status.trim().length > 0 };
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
63
|
+
const sha = gitOut(checkoutRoot, ["rev-parse", "--short", "HEAD"]);
|
|
64
|
+
const status = gitOut(checkoutRoot, ["status", "--porcelain"]);
|
|
65
|
+
return sha === null || status === null ? null : { sha, dirty: status.length > 0 };
|
|
79
66
|
}
|
|
80
67
|
const describe = (sha, dirty) => `${sha}${dirty ? "-dirty" : ""}`;
|
|
81
68
|
export function versionInfo() {
|
package/package.json
CHANGED