@cmgmyr/hive 1.2.2 → 1.4.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 +6 -1
- package/claude-plugin/README.md +4 -1
- package/claude-plugin/kickoff.mjs +2 -1
- package/claude-plugin/skills/profile/SKILL.md +78 -0
- package/claude-plugin/skills/profile/references/interview.md +139 -0
- package/claude-plugin/skills/profile/references/recipes/index.md +12 -0
- package/claude-plugin/skills/profile/references/recipes/review-decision.md +41 -0
- package/claude-plugin/skills/profile/references/recipes/session-continuity.md +37 -0
- package/claude-plugin/skills/profile/references/recipes/shared-test-resource.md +45 -0
- package/claude-plugin/skills/profile/references/recipes/verification.md +50 -0
- package/claude-plugin/skills/profile/references/recipes/worker-context-checkpoint.md +51 -0
- package/dist/brief.js +1 -1
- package/dist/build-info.json +1 -1
- package/dist/cli.js +436 -112
- package/dist/codexHome.js +23 -8
- package/dist/config.js +8 -61
- package/dist/context.js +191 -2
- package/dist/db.js +80 -20
- package/dist/firstMessage.js +5 -0
- package/dist/globalConfig.js +247 -0
- package/dist/harnesses.js +14 -4
- package/dist/help.js +29 -11
- package/dist/hook.js +9 -0
- package/dist/hooks.js +2 -1
- package/dist/index.js +11 -5
- package/dist/kickoff.js +42 -29
- package/dist/leadState.js +63 -0
- package/dist/leadWatch.js +73 -0
- package/dist/portfolio.js +210 -0
- package/dist/projectYml.js +242 -178
- package/dist/queenAudit.js +118 -0
- package/dist/queenDashboard.js +573 -0
- package/dist/scheduler.js +190 -20
- package/dist/spawn.js +17 -4
- package/dist/strictInput.js +24 -7
- package/dist/tmux.js +65 -0
- package/dist/tools/agents.js +153 -15
- package/dist/tools/kv.js +24 -0
- package/dist/tools/leases.js +14 -2
- package/dist/tools/meta.js +65 -3
- package/dist/tools/pads.js +42 -0
- package/dist/tools/queenAudit.js +13 -0
- package/dist/tools/todos.js +72 -13
- package/dist/tools/wakes.js +233 -65
- package/package.json +4 -4
- package/profiles/orchestration/worker.md +1 -1
- package/profiles/queen/posture.md +31 -0
- package/profiles/queen/runbook.md +26 -0
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
-
import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
4
|
+
import { chmodSync, existsSync, linkSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
5
5
|
import { homedir, tmpdir } from "node:os";
|
|
6
6
|
import { join, resolve, sep } from "node:path";
|
|
7
7
|
import { createInterface } from "node:readline/promises";
|
|
@@ -9,19 +9,21 @@ import { pathToFileURL } from "node:url";
|
|
|
9
9
|
import { checkAbi, describeAbi, describeInterpreter, nodeRangeForNodeApi, requiredNodeApi } from "./abi.js";
|
|
10
10
|
import { versionInfo } from "./version.js";
|
|
11
11
|
import { claudeConfigDir } from "./claudeDir.js";
|
|
12
|
-
import { ATTACH_MODES, AUTO_ATTACH_MODES, attachMode, isAttachMode, isAutoAttach,
|
|
12
|
+
import { ATTACH_MODES, AUTO_ATTACH_MODES, attachMode, isAttachMode, isAutoAttach, migrateLegacyConfig, resolvedAutoAttach, setAttachMode, setAutoAttach, } from "./config.js";
|
|
13
13
|
import { cliPath, dispatcherDir, dispatcherPath, dispatcherScript, durabilityLines, firstHiveOnPath, linkedWorktreePin, pathAdvice, readDispatcher, } from "./dispatcher.js";
|
|
14
14
|
import { codexConfigPath, codexHiveRegistrations, codexRegistrationProblem, hiveRegistrations, registrationOffer, registrationProblem, } from "./mcpConfig.js";
|
|
15
|
-
import { DEFAULT_DATA_DIR } from "./dataDir.js";
|
|
15
|
+
import { DEFAULT_DATA_DIR, storeDir } from "./dataDir.js";
|
|
16
16
|
import { dataDir, db, migrate, storeSchemaAhead } from "./db.js";
|
|
17
17
|
import { cacheIsStale, queryUpdate, readCachedUpdate, refreshUpdate, shouldAutoRefresh, updateLine } from "./updateCheck.js";
|
|
18
18
|
import { checkoutUpgradeSteps, detectInstallShape, globalUpgradeSteps } from "./upgrade.js";
|
|
19
19
|
import { isLowHeadroom, orphanLoginShellDetails, ptyHeadroom } from "./ptys.js";
|
|
20
|
-
import { addProject, agentProjectPin, currentActor, effectiveProjectId, findProjectForCwd, getProjectByPath, gitPrimaryRoot, getProject, listProjects, registeredAncestor, takeRegistrationNotice, } from "./context.js";
|
|
20
|
+
import { addProject, agentProjectPin, assertQueenCrossProjectWrite, captureQueenWriteIdentity, currentActor, effectiveProjectId, findProjectForCwd, findProjectForDir, getProjectByPath, gitPrimaryRoot, getProject, listProjects, QUEEN_PROFILE, queenHomeDir, registeredAncestor, takeRegistrationNotice, } from "./context.js";
|
|
21
|
+
import { confirmQueenWrite, listQueenAudit } from "./queenAudit.js";
|
|
21
22
|
import { ensureHooksFile } from "./hooks.js";
|
|
22
|
-
import { errorMessage,
|
|
23
|
-
import { ACTIVE_TIMER_WHERE, dashboardFileContained, describeStall, HELD_REASON_CONVERSATION, HELD_REASON_LEAD_PANE_DEAD, HELD_REASON_UNCLASSIFIABLE_PANE_PREFIX, isUnclassifiablePaneHold, HELD_REASON_UNSUBMITTED_INPUT_PREFIX, isUnsubmittedInputHold, janitor, resolveDashboardFile, transcriptStaleness, wasHeldForPaneReissue, } from "./scheduler.js";
|
|
23
|
+
import { errorMessage, registrationNoticeText, withTrailingNewline } from "./result.js";
|
|
24
|
+
import { ACTIVE_TIMER_WHERE, dashboardFileContained, describeStall, HELD_REASON_CONVERSATION, HELD_REASON_LEAD_PANE_DEAD, HELD_REASON_UNCLASSIFIABLE_PANE_PREFIX, isUnclassifiablePaneHold, HELD_REASON_UNSUBMITTED_INPUT_PREFIX, isUnsubmittedInputHold, janitor, resolveDashboardFile, generateQueenDashboardNow, transcriptStaleness, wasHeldForPaneReissue, } from "./scheduler.js";
|
|
24
25
|
import { readTurnCount } from "./turnCount.js";
|
|
26
|
+
import { collectPortfolio, leadText } from "./portfolio.js";
|
|
25
27
|
import { STALL_BOUND_SECONDS } from "./backgroundTasks.js";
|
|
26
28
|
import { probeSessionInterpreter, reexecTarget, sessionStartVerdict, } from "./sessionProbe.js";
|
|
27
29
|
import { backupHealth, backupNow, backupsDir, formatBytes, listSnapshots, previewRestore, restoreSnapshot, totalSizeBytes, } from "./backup.js";
|
|
@@ -30,15 +32,15 @@ import { asNameClash, buildEnvFlags, closeAgentRow, isReservedAgentName, launchA
|
|
|
30
32
|
import { adoptableWindow, applyLayout, claimInitialWindow, configureHiveWindow, controlModeFor, createWindow, DEFAULT_LAYOUT, describePaneChoice, ensureSession, findProjectWindow, foreignSocket, isPaneTarget, isViewSessionName, listOwnedWindows, ORPHAN_MIN_AGE_MS, orphanScratchServers, orphansWorthWarningAbout, paneIsAloneInWindow, paneVisibility, panePid, PROCESSES_LAYOUT, processesPaneTitle, processesWindowName, projectWindows, RAW_ATTACH_TMUX_CONFIG, renderAttachCommand, resolveAttachTarget, resolveInTmuxTarget, paneReissued, rowLive, rowLiveProbe, SESSION_PREFIX, appendGlobalHook, globalHooks, replaceGlobalHooks, sessionName, setPaneTitle, shellQuote, shownPaneTitle, makeProcessesWindow, tmux, TMUX_DOC, tmuxSaysNothingThere, tmuxTimeoutOverride, tmuxSocketPath, untrustedTmuxServer, waitForPaneEstablished, windowLayout, } from "./tmux.js";
|
|
31
33
|
import { describeVisibility, processCounts } from "./dashboard.js";
|
|
32
34
|
import { runningCommandRow, runningCommandRows, snapshotProcesses, stopAllProcesses, stopLine, stopProcess, STOP_REASONS, } from "./processes.js";
|
|
33
|
-
import { activeProfile, agentVarKeys, configHash, loadProjectYml, mergedProjectVars, NO_PROFILE, resolveCommandDir, } from "./projectYml.js";
|
|
35
|
+
import { activeProfile, agentVarKeys, BUILT_IN_PROJECT_YML, configHash, loadProjectYml, mergedProjectVars, NO_PROFILE, PROJECT_YML_KEYS, resolveCommandDir, resolveFirstMessage, } from "./projectYml.js";
|
|
34
36
|
import { writeProjectPosture } from "./brief.js";
|
|
35
37
|
import { carriesNameFlag, harnessFor, hasTranscriptSignal, paneClassifierFor, transcriptDirFor } from "./harnesses.js";
|
|
36
38
|
import { codexHomeDir, codexInstructionsPhrase, ensureCodexHome, reapCodexHome } from "./codexHome.js";
|
|
37
|
-
import {
|
|
39
|
+
import { FIRST_MESSAGE_SHA_ENV, firstMessageDigest } from "./firstMessage.js";
|
|
38
40
|
import { ageSecondsSince, deriveProvenance, describeForHuman, describeLastLogEvent, humanizeAge, lastLogEvent, lastPermissionMode, reportsAgentStateLog, } from "./stateProvenance.js";
|
|
39
41
|
import { awaitingFirstPromptSql } from "./firstPrompt.js";
|
|
40
42
|
import { checkoutRoot, createProfile, forkProfile, PROFILE_FILES, ProfileError, profileExists, profileFileNames, profileNames, profileStatus, readProfileFile, referencedPads, referencedPaths, renderProfileFile, resolveProfileFile, REWRITE_THRESHOLD, templateVars, userProfilesDir, } from "./profiles.js";
|
|
41
|
-
import {
|
|
43
|
+
import { getTodoDetail, listTodoSummaries, OPEN_BLOCKERS_SQL, TODO_STATUSES, } from "./tools/todos.js";
|
|
42
44
|
import { createPad, getActivePadByName, listActivePads, overwritePadContent, } from "./tools/pads.js";
|
|
43
45
|
function usage() {
|
|
44
46
|
console.log(`hive — run a crew of Claude Code and Codex workers from one lead session
|
|
@@ -46,11 +48,16 @@ function usage() {
|
|
|
46
48
|
Usage:
|
|
47
49
|
hive --version version, short sha, and dirty marker for this build
|
|
48
50
|
hive [path] open the project's session with a lead window
|
|
49
|
-
hive lead [path] [--no-dashboard]
|
|
51
|
+
hive lead [path] [--no-dashboard] [--detach]
|
|
50
52
|
same; lead is the default command. --no-dashboard
|
|
51
53
|
skips this run's dashboard auto-open -
|
|
52
54
|
scripts/restart-lead.sh passes it; a human rarely
|
|
53
|
-
needs
|
|
55
|
+
needs it. --detach starts or adopts the lead and
|
|
56
|
+
prints its pane and attach command without attaching
|
|
57
|
+
hive queen [--no-dashboard]
|
|
58
|
+
start, or reattach to, the lead that reads across
|
|
59
|
+
every registered project; its home is
|
|
60
|
+
<data dir>/queen, created on first run
|
|
54
61
|
hive init [path] [--profile <name>|--no-profile]
|
|
55
62
|
set the project up: hive.yml, profile, starter pads
|
|
56
63
|
hive attach [path] attach without adding windows
|
|
@@ -59,6 +66,14 @@ Usage:
|
|
|
59
66
|
hive show <process> [path] move a running process's pane beside the lead
|
|
60
67
|
hive hide <process> [path] move it back into the <project>/processes window
|
|
61
68
|
hive status overview of agents, todos, and wake-ups everywhere
|
|
69
|
+
hive portfolio [--json] one row per registered project: lane, lead, workers,
|
|
70
|
+
todos, needs-human count, wakes; read-only
|
|
71
|
+
hive queen-audit confirmed queen writes, newest first; 30-day history
|
|
72
|
+
[--project-id <id>] filter the target project (queen only across projects)
|
|
73
|
+
[--limit <n>] [--json] default 20, maximum 100; JSON prints one object
|
|
74
|
+
hive next [--print] attach to the lead of the project that needs you most
|
|
75
|
+
(waiting on you, then stuck), starting a dead or
|
|
76
|
+
missing lead first; --print only names the choice
|
|
62
77
|
hive upgrade [--check] upgrade a global npm install; preview with --check
|
|
63
78
|
hive upgrade --run run the printed pull/install/build/setup checkout recipe
|
|
64
79
|
hive setup [--dir <dir>] write a \`hive\` that runs the interpreter this build
|
|
@@ -124,6 +139,27 @@ function resolveProject(path, onNotice) {
|
|
|
124
139
|
const pinnedProject = getProject(pinned);
|
|
125
140
|
throw new Error(`This session is locked to project ${pinned} ("${pinnedProject.name}") but "${path}" resolves to ${target ? `project ${target.id} ("${target.name}")` : "no registered project"}. An explicit path argument cannot escape HIVE_PROJECT_LOCK=1. Unset HIVE_AGENT_ID and HIVE_PROJECT_LOCK in this pane, or open a new one, to act on a different project.`);
|
|
126
141
|
}
|
|
142
|
+
function resolveProjectForWrite(verb, path, onNotice) {
|
|
143
|
+
assertQueenCrossProjectWrite(verb, findProjectForDir(path ?? process.cwd())?.id ?? null, {});
|
|
144
|
+
return resolveProject(path, onNotice);
|
|
145
|
+
}
|
|
146
|
+
function resolveProjectForRead(verb, path) {
|
|
147
|
+
if (path) {
|
|
148
|
+
process.chdir(path);
|
|
149
|
+
const pinned = agentProjectPin();
|
|
150
|
+
if (pinned != null) {
|
|
151
|
+
const target = findProjectForCwd();
|
|
152
|
+
if (target == null || target.id !== pinned) {
|
|
153
|
+
throw new Error(`This session is locked to project ${pinned} ("${getProject(pinned).name}") but "${path}" resolves to ${target ? `project ${target.id} ("${target.name}")` : "no registered project"}. An explicit path argument cannot escape HIVE_PROJECT_LOCK=1. Unset HIVE_AGENT_ID and HIVE_PROJECT_LOCK in this pane, or open a new one, to act on a different project.`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const project = pinnedOrCwdProject();
|
|
158
|
+
if (project)
|
|
159
|
+
return project;
|
|
160
|
+
console.error(`hive ${verb}: this directory is not a registered hive project. cd to a registered checkout, or run \`hive init\` here to register it.`);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
127
163
|
function pinnedOrCwdProject() {
|
|
128
164
|
const pinned = agentProjectPin();
|
|
129
165
|
if (pinned != null)
|
|
@@ -139,12 +175,9 @@ async function confirmYesNo(question) {
|
|
|
139
175
|
return /^y(es)?$/i.test(answer.trim());
|
|
140
176
|
}
|
|
141
177
|
async function ensureTrusted(projectId, name, command, dir, env) {
|
|
142
|
-
|
|
143
|
-
const trusted = db
|
|
144
|
-
.prepare("SELECT 1 FROM command_trust WHERE project_id = ? AND name = ? AND config_hash = ?")
|
|
145
|
-
.get(projectId, name, hash);
|
|
146
|
-
if (trusted)
|
|
178
|
+
if (isTrusted(projectId, name, command, dir, env))
|
|
147
179
|
return true;
|
|
180
|
+
const hash = configHash(name, command, dir, env);
|
|
148
181
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
149
182
|
console.log(`! "${name}" is not trusted yet; run hive interactively to review it.`);
|
|
150
183
|
return false;
|
|
@@ -162,6 +195,12 @@ async function ensureTrusted(projectId, name, command, dir, env) {
|
|
|
162
195
|
db.prepare("INSERT OR IGNORE INTO command_trust (project_id, name, config_hash) VALUES (?, ?, ?)").run(projectId, name, hash);
|
|
163
196
|
return true;
|
|
164
197
|
}
|
|
198
|
+
function isTrusted(projectId, name, command, dir, env) {
|
|
199
|
+
const hash = configHash(name, command, dir, env);
|
|
200
|
+
return !!db
|
|
201
|
+
.prepare("SELECT 1 FROM command_trust WHERE project_id = ? AND name = ? AND config_hash = ?")
|
|
202
|
+
.get(projectId, name, hash);
|
|
203
|
+
}
|
|
165
204
|
function startYmlCommand(project, name, proc, config) {
|
|
166
205
|
if (isReservedAgentName(name)) {
|
|
167
206
|
return `skipped: "lead" is reserved for this project's lead session and cannot be used as a process name`;
|
|
@@ -311,24 +350,46 @@ function ensureLeadRow(project, command) {
|
|
|
311
350
|
casExpected: "",
|
|
312
351
|
};
|
|
313
352
|
}
|
|
353
|
+
function reportMigrationResult(result) {
|
|
354
|
+
if (result.notice)
|
|
355
|
+
console.error(result.notice);
|
|
356
|
+
for (const warning of result.warnings)
|
|
357
|
+
console.error(`! ${warning}`);
|
|
358
|
+
}
|
|
314
359
|
async function cmdLead(argv) {
|
|
315
|
-
const unknownFlag = argv.find((a) => a.startsWith("--") && a !== "--no-dashboard");
|
|
360
|
+
const unknownFlag = argv.find((a) => a.startsWith("--") && a !== "--no-dashboard" && a !== "--detach");
|
|
316
361
|
if (unknownFlag !== undefined) {
|
|
317
|
-
console.error(`hive lead: unknown flag "${unknownFlag}".
|
|
362
|
+
console.error(`hive lead: unknown flag "${unknownFlag}". Flags are --no-dashboard and --detach.`);
|
|
318
363
|
process.exit(1);
|
|
319
364
|
}
|
|
320
365
|
const noDashboard = argv.includes("--no-dashboard");
|
|
366
|
+
const detach = argv.includes("--detach");
|
|
321
367
|
const path = argv.find((a) => !a.startsWith("--"));
|
|
322
368
|
let registrationNotice = null;
|
|
323
369
|
try {
|
|
324
|
-
const project =
|
|
370
|
+
const project = resolveProjectForWrite("hive lead", path, (text) => {
|
|
325
371
|
registrationNotice = text;
|
|
326
372
|
});
|
|
327
|
-
const
|
|
328
|
-
const
|
|
329
|
-
|
|
373
|
+
const auditIdentity = captureQueenWriteIdentity(project.id);
|
|
374
|
+
const { config, warnings, sources } = loadProjectYml(project.path);
|
|
375
|
+
if (detach && (!process.stdin.isTTY || !process.stdout.isTTY)) {
|
|
376
|
+
let untrusted;
|
|
377
|
+
if (config?.lead && !isTrusted(project.id, "lead", config.lead, null, {}))
|
|
378
|
+
untrusted = "lead";
|
|
379
|
+
if (!untrusted && config) {
|
|
380
|
+
untrusted = Object.entries(config.processes).find(([name, proc]) => proc.auto_start !== false && !isTrusted(project.id, name, proc.command, proc.dir, proc.env))?.[0];
|
|
381
|
+
}
|
|
382
|
+
if (untrusted) {
|
|
383
|
+
console.error(`hive lead: "${untrusted}" is not trusted; run hive lead <path> interactively once`);
|
|
384
|
+
process.exitCode = 1;
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
reportMigrationResult(migrateLegacyConfig());
|
|
330
389
|
for (const w of warnings)
|
|
331
390
|
console.log(`! ${w}`);
|
|
391
|
+
const session = sessionName();
|
|
392
|
+
const hooksPath = ensureHooksFile();
|
|
332
393
|
let leadCommand = "claude";
|
|
333
394
|
if (!config) {
|
|
334
395
|
console.log("No hive.yml here; starting a plain claude lead. Add a hive.yml to define project commands and a custom lead (run hive --help for the format).");
|
|
@@ -342,6 +403,10 @@ async function cmdLead(argv) {
|
|
|
342
403
|
}
|
|
343
404
|
}
|
|
344
405
|
const leadHarness = harnessFor(leadCommand);
|
|
406
|
+
let firstMessage = resolveFirstMessage(config, sources).message;
|
|
407
|
+
if (firstMessage !== "" && !(await (await import("./kickoff.js")).kickoffGate(project.path)).ok)
|
|
408
|
+
firstMessage = "";
|
|
409
|
+
let promptSuffix = "";
|
|
345
410
|
// Posture is rendered once, then delivered through whichever channel this harness has: a
|
|
346
411
|
// claude lead gets it as --append-system-prompt-file below; a codex lead gets the identical
|
|
347
412
|
// text as config.toml's developer_instructions, built after ensureLeadRow (it needs leadActorId).
|
|
@@ -379,6 +444,9 @@ async function cmdLead(argv) {
|
|
|
379
444
|
leadCommand += ` ${leadHarness.briefDelivery.systemPromptArgs(posturePath).map(shellQuote).join(" ")}`;
|
|
380
445
|
console.log(`- profile: ${profile} (${postureSource} posture; see it with: hive posture)`);
|
|
381
446
|
}
|
|
447
|
+
if (leadHarness.initialPromptArgs && firstMessage !== "") {
|
|
448
|
+
promptSuffix = ` ${leadHarness.initialPromptArgs(firstMessage).map(shellQuote).join(" ")}`;
|
|
449
|
+
}
|
|
382
450
|
}
|
|
383
451
|
const windowName = project.name;
|
|
384
452
|
const { agentId: leadAgentId, actorId: leadActorId, previousTarget, previousSocket, previousPanePid, previousCommand, previousCodexHome, casExpected, } = ensureLeadRow(project, leadCommand);
|
|
@@ -387,6 +455,14 @@ async function cmdLead(argv) {
|
|
|
387
455
|
// actually starts under it) and correct regardless of whether this invocation ends up creating
|
|
388
456
|
// a pane or adopting one. Which home ends up orphaned - this one, or the previous invocation's -
|
|
389
457
|
// is decided after createdPane is known, below.
|
|
458
|
+
const leadEnv = {
|
|
459
|
+
HIVE_AGENT_ID: leadActorId,
|
|
460
|
+
HIVE_AGENT_NAME: LEAD_NAME,
|
|
461
|
+
HIVE_LEAD: "1",
|
|
462
|
+
HIVE_DATA_DIR: dataDir,
|
|
463
|
+
HIVE_PROJECT_LOCK: "",
|
|
464
|
+
HIVE_PROJECT_PATH: "",
|
|
465
|
+
};
|
|
390
466
|
let newCodexHomeKey;
|
|
391
467
|
if (leadHarness.needsHome) {
|
|
392
468
|
newCodexHomeKey = randomUUID();
|
|
@@ -401,6 +477,7 @@ async function cmdLead(argv) {
|
|
|
401
477
|
const home = ensureCodexHome({
|
|
402
478
|
key: newCodexHomeKey,
|
|
403
479
|
actorId: leadActorId,
|
|
480
|
+
serverEnv: leadEnv,
|
|
404
481
|
cwd: project.path,
|
|
405
482
|
brief: renderedPosture ?? "",
|
|
406
483
|
lead: true,
|
|
@@ -414,8 +491,8 @@ async function cmdLead(argv) {
|
|
|
414
491
|
throw e;
|
|
415
492
|
}
|
|
416
493
|
leadCommand += ` ${homeArgs.map(shellQuote).join(" ")}`;
|
|
417
|
-
if (leadHarness.initialPromptArgs) {
|
|
418
|
-
|
|
494
|
+
if (leadHarness.initialPromptArgs && firstMessage !== "") {
|
|
495
|
+
promptSuffix = ` ${leadHarness.initialPromptArgs(firstMessage).map(shellQuote).join(" ")}`;
|
|
419
496
|
}
|
|
420
497
|
if (renderedPosture !== null) {
|
|
421
498
|
console.log(`- profile: ${profile} (${postureSource} posture; see it with: hive posture)`);
|
|
@@ -425,19 +502,16 @@ async function cmdLead(argv) {
|
|
|
425
502
|
(instructionsPhrase ? ` (${instructionsPhrase})` : ""));
|
|
426
503
|
}
|
|
427
504
|
const envFlags = buildEnvFlags({
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
HIVE_LEAD: "1",
|
|
431
|
-
HIVE_DATA_DIR: dataDir,
|
|
432
|
-
HIVE_PROJECT_LOCK: "",
|
|
433
|
-
HIVE_PROJECT_PATH: "",
|
|
505
|
+
...leadEnv,
|
|
506
|
+
[FIRST_MESSAGE_SHA_ENV]: promptSuffix !== "" ? firstMessageDigest(firstMessage) : "",
|
|
434
507
|
...(newCodexHomeKey ? { CODEX_HOME: codexHomeDir(newCodexHomeKey) } : {}),
|
|
435
508
|
});
|
|
509
|
+
const launchCommand = leadCommand + promptSuffix;
|
|
436
510
|
const { leadPane, leadWindow, createdPane } = withWindowClaim(() => {
|
|
437
511
|
let leadPane;
|
|
438
512
|
let leadWindow;
|
|
439
513
|
let createdPane;
|
|
440
|
-
const started = ensureSession(session, project.path, { envFlags, command:
|
|
514
|
+
const started = ensureSession(session, project.path, { envFlags, command: launchCommand });
|
|
441
515
|
if (started.created) {
|
|
442
516
|
const claimed = claimInitialWindow(started, windowName, project.id);
|
|
443
517
|
leadPane = claimed.pane;
|
|
@@ -456,13 +530,13 @@ async function cmdLead(argv) {
|
|
|
456
530
|
createdPane = false;
|
|
457
531
|
}
|
|
458
532
|
else if (!foundWindow) {
|
|
459
|
-
const fresh = createWindow(session, windowName, project.path, envFlags,
|
|
533
|
+
const fresh = createWindow(session, windowName, project.path, envFlags, launchCommand, project.id, detach);
|
|
460
534
|
leadPane = fresh.pane;
|
|
461
535
|
leadWindow = fresh.window;
|
|
462
536
|
createdPane = true;
|
|
463
537
|
}
|
|
464
538
|
else {
|
|
465
|
-
leadPane = tmux("split-window", "-P", "-F", "#{pane_id}", "-t", foundWindow, "-c", project.path, ...envFlags,
|
|
539
|
+
leadPane = tmux("split-window", ...(detach ? ["-d"] : []), "-P", "-F", "#{pane_id}", "-t", foundWindow, "-c", project.path, ...envFlags, launchCommand);
|
|
466
540
|
leadWindow = foundWindow;
|
|
467
541
|
createdPane = true;
|
|
468
542
|
}
|
|
@@ -484,18 +558,7 @@ async function cmdLead(argv) {
|
|
|
484
558
|
// also reaps a stale codex home left over from a lead config that has since switched away from
|
|
485
559
|
// codex, since createdPane's branch does not require leadHarness.needsHome to fire.
|
|
486
560
|
const recordedCodexHome = createdPane ? (newCodexHomeKey ?? "") : previousCodexHome;
|
|
487
|
-
const
|
|
488
|
-
const updated = db
|
|
489
|
-
.prepare("UPDATE agents SET tmux_target = ?, tmux_socket = ?, pane_pid = ?, command = ?, codex_home = ? WHERE id = ? AND tmux_target = ? AND status = 'running'")
|
|
490
|
-
.run(leadPane, tmuxSocketPath(process.env.TMUX, process.env.TMUX_TMPDIR), panePid(leadPane), recordedCommand, recordedCodexHome, leadAgentId, casExpected).changes;
|
|
491
|
-
if (updated === 0)
|
|
492
|
-
return false;
|
|
493
|
-
db.prepare(`UPDATE wakes SET deliver_pane = ?, held_at = NULL, held_reason = NULL
|
|
494
|
-
WHERE ${ACTIVE_TIMER_WHERE} AND deliver_actor = ?
|
|
495
|
-
AND (? = 1 OR held_reason IS NULL OR held_reason NOT LIKE ?)`).run(leadPane, leadActorId, createdPane ? 1 : 0, `${HELD_REASON_UNCLASSIFIABLE_PANE_PREFIX}%`);
|
|
496
|
-
return true;
|
|
497
|
-
})();
|
|
498
|
-
if (!wonRace) {
|
|
561
|
+
const abandonClaim = () => {
|
|
499
562
|
if (createdPane) {
|
|
500
563
|
try {
|
|
501
564
|
waitForPaneEstablished(leadPane);
|
|
@@ -506,6 +569,31 @@ async function cmdLead(argv) {
|
|
|
506
569
|
}
|
|
507
570
|
if (newCodexHomeKey)
|
|
508
571
|
reapCodexHome(newCodexHomeKey);
|
|
572
|
+
};
|
|
573
|
+
let wonRace;
|
|
574
|
+
try {
|
|
575
|
+
wonRace = db.transaction(() => {
|
|
576
|
+
const updated = db
|
|
577
|
+
.prepare("UPDATE agents SET tmux_target = ?, tmux_socket = ?, pane_pid = ?, command = ?, codex_home = ? WHERE id = ? AND tmux_target = ? AND status = 'running'")
|
|
578
|
+
.run(leadPane, tmuxSocketPath(process.env.TMUX, process.env.TMUX_TMPDIR), panePid(leadPane), recordedCommand, recordedCodexHome, leadAgentId, casExpected).changes;
|
|
579
|
+
if (updated === 0)
|
|
580
|
+
return false;
|
|
581
|
+
db.prepare(`UPDATE wakes SET deliver_pane = ?, held_at = NULL, held_reason = NULL
|
|
582
|
+
WHERE ${ACTIVE_TIMER_WHERE} AND deliver_actor = ?
|
|
583
|
+
AND (? = 1 OR held_reason IS NULL OR held_reason NOT LIKE ?)`).run(leadPane, leadActorId, createdPane ? 1 : 0, `${HELD_REASON_UNCLASSIFIABLE_PANE_PREFIX}%`);
|
|
584
|
+
confirmQueenWrite("hive lead", project.id, { detach }, {
|
|
585
|
+
agent_id: leadAgentId,
|
|
586
|
+
disposition: createdPane ? "started" : "adopted",
|
|
587
|
+
}, auditIdentity);
|
|
588
|
+
return true;
|
|
589
|
+
})();
|
|
590
|
+
}
|
|
591
|
+
catch (e) {
|
|
592
|
+
abandonClaim();
|
|
593
|
+
throw e;
|
|
594
|
+
}
|
|
595
|
+
if (!wonRace) {
|
|
596
|
+
abandonClaim();
|
|
509
597
|
throw new Error("Another `hive lead` won the race to record a live pane for this project's lead session (both saw the " +
|
|
510
598
|
"same dead pane and both tried to replace it, or this row was closed by another process mid-restart). " +
|
|
511
599
|
"Re-run `hive lead`; it will attach to the pane that invocation recorded.");
|
|
@@ -539,6 +627,11 @@ async function cmdLead(argv) {
|
|
|
539
627
|
console.error(registrationNotice);
|
|
540
628
|
registrationNotice = null;
|
|
541
629
|
}
|
|
630
|
+
if (detach) {
|
|
631
|
+
console.log(`LEAD_PANE=${leadPane}`);
|
|
632
|
+
console.log(`ATTACH_COMMAND=hive attach ${shellQuote(project.path)}`);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
542
635
|
if (!noDashboard)
|
|
543
636
|
maybeOpenDashboard(project, !!config?.dashboard);
|
|
544
637
|
attach(session, project, leadWindow);
|
|
@@ -549,11 +642,65 @@ async function cmdLead(argv) {
|
|
|
549
642
|
throw e;
|
|
550
643
|
}
|
|
551
644
|
}
|
|
645
|
+
function publishQueenYml(home) {
|
|
646
|
+
const yml = join(home, "hive.yml");
|
|
647
|
+
const staged = join(home, `.hive.yml.${process.pid}.${randomUUID()}`);
|
|
648
|
+
writeFileSync(staged, `profile: ${QUEEN_PROFILE}\n`, { flag: "wx" });
|
|
649
|
+
try {
|
|
650
|
+
linkSync(staged, yml);
|
|
651
|
+
}
|
|
652
|
+
catch (e) {
|
|
653
|
+
if (e.code !== "EEXIST")
|
|
654
|
+
throw e;
|
|
655
|
+
}
|
|
656
|
+
finally {
|
|
657
|
+
unlinkSync(staged);
|
|
658
|
+
}
|
|
659
|
+
if (lstatSync(yml).isSymbolicLink()) {
|
|
660
|
+
throw new Error(`hive queen: ${yml} is a symlink. The queen's config must be a real file; remove the link and run hive queen again.`);
|
|
661
|
+
}
|
|
662
|
+
const { config } = loadProjectYml(home);
|
|
663
|
+
if (activeProfile(config) !== QUEEN_PROFILE) {
|
|
664
|
+
throw new Error(`hive queen: ${yml} already exists and does not select "profile: ${QUEEN_PROFILE}". hive will not ` +
|
|
665
|
+
"overwrite it. Add that line yourself, or move the file away and run hive queen again.");
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
async function cmdQueen(argv) {
|
|
669
|
+
const parsed = parseArgs(argv, { flags: ["--no-dashboard"] });
|
|
670
|
+
rejectUnknownFlags("queen", parsed, "--no-dashboard");
|
|
671
|
+
if (parsed.positional.length > 0) {
|
|
672
|
+
console.error(`hive queen: takes no path. The queen lives in the data dir (${queenHomeDir()}).`);
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
675
|
+
if (process.env.HIVE_PROJECT_LOCK === "1" || agentProjectPin() != null) {
|
|
676
|
+
throw new Error("hive queen: this session is locked to its own project (HIVE_PROJECT_LOCK=1), so it cannot create or " +
|
|
677
|
+
"start the queen. Run hive queen from your own terminal.");
|
|
678
|
+
}
|
|
679
|
+
const home = queenHomeDir();
|
|
680
|
+
let existing = null;
|
|
681
|
+
try {
|
|
682
|
+
existing = lstatSync(home);
|
|
683
|
+
}
|
|
684
|
+
catch {
|
|
685
|
+
existing = null;
|
|
686
|
+
}
|
|
687
|
+
if (existing?.isSymbolicLink() || (existing && !existing.isDirectory())) {
|
|
688
|
+
throw new Error(`hive queen: ${home} exists and is not a plain directory. The queen's home must be a real directory ` +
|
|
689
|
+
"inside the data dir; move it away and run hive queen again.");
|
|
690
|
+
}
|
|
691
|
+
mkdirSync(home, { recursive: true });
|
|
692
|
+
publishQueenYml(home);
|
|
693
|
+
const queen = addProject(home, "queen");
|
|
694
|
+
generateQueenDashboardNow();
|
|
695
|
+
if (!parsed.flags.has("--no-dashboard"))
|
|
696
|
+
maybeOpenQueenDashboard(queen);
|
|
697
|
+
await cmdLead([home, ...parsed.flags]);
|
|
698
|
+
}
|
|
552
699
|
const HIVE_YML_TEMPLATE = `# hive project config. Read by \`hive lead\` from the project root.
|
|
553
700
|
# Commands defined here run only after a one-time interactive approval,
|
|
554
701
|
# and re-require it whenever they change.
|
|
555
702
|
|
|
556
|
-
placement: split
|
|
703
|
+
# placement: split # placement for workers and visible processes: split (panes) or
|
|
557
704
|
# window (tabs)
|
|
558
705
|
|
|
559
706
|
# layout: main-vertical # pane arrangement for placement: split.
|
|
@@ -566,6 +713,7 @@ placement: split # placement for workers and visible processes: s
|
|
|
566
713
|
# agents: [claude, codex] # crew harness pool; the first entry is the default
|
|
567
714
|
|
|
568
715
|
# lead_branches: [main, master] # branches where a session gets hive's kickoff
|
|
716
|
+
# first_message: "..." # a lead's first turn; absent or "" sends none
|
|
569
717
|
|
|
570
718
|
# context_checkpoint_percent: null # unset means off; integer 1-100 to enable
|
|
571
719
|
|
|
@@ -573,11 +721,6 @@ placement: split # placement for workers and visible processes: s
|
|
|
573
721
|
|
|
574
722
|
# dashboard: true # write .hive/dashboard.html (default: false)
|
|
575
723
|
|
|
576
|
-
# review_tags: [from-review] # todo tags \`hive doctor\` counts as review findings and
|
|
577
|
-
# reports as triaged (has a comment, completed, or archived)
|
|
578
|
-
# or untriaged. A tag also matches its own suffixed rounds.
|
|
579
|
-
# Absent means it tracks none.
|
|
580
|
-
|
|
581
724
|
# vars: # substituted into the profile runbook
|
|
582
725
|
# repo: owner/name # {{repo}}
|
|
583
726
|
# ticket_prefix: DEVX # sections needing it drop when it is unset
|
|
@@ -729,6 +872,7 @@ async function cmdInit(argv) {
|
|
|
729
872
|
rejectUnknownFlags("init", parsed, "--profile <name> and --no-profile");
|
|
730
873
|
requireFlagValues("init", parsed);
|
|
731
874
|
const noProfile = parsed.flags.has("--no-profile");
|
|
875
|
+
const profileFlag = noProfile || parsed.values.has("--profile");
|
|
732
876
|
const path = parsed.positional[0];
|
|
733
877
|
let chosen = noProfile ? NO_PROFILE : null;
|
|
734
878
|
if (parsed.values.has("--profile")) {
|
|
@@ -771,6 +915,7 @@ async function cmdInit(argv) {
|
|
|
771
915
|
process.exit(1);
|
|
772
916
|
}
|
|
773
917
|
const existing = getProjectByPath(registration);
|
|
918
|
+
assertQueenCrossProjectWrite("hive init", existing?.id ?? null, {});
|
|
774
919
|
project = existing ?? addProject(registration);
|
|
775
920
|
newlyRegistered = existing == null;
|
|
776
921
|
}
|
|
@@ -789,21 +934,32 @@ async function cmdInit(argv) {
|
|
|
789
934
|
if (path == null && newlyRegistered)
|
|
790
935
|
console.error(registrationNoticeText(project));
|
|
791
936
|
const ymlPath = join(project.path, "hive.yml");
|
|
792
|
-
const
|
|
937
|
+
const ymlExists = existsSync(ymlPath);
|
|
938
|
+
const loaded = loadProjectYml(project.path);
|
|
939
|
+
const projectProfileSet = ymlExists && loaded.sources.profile === "project";
|
|
940
|
+
const already = projectProfileSet ? loaded.config?.profile ?? null : null;
|
|
941
|
+
let inheritedProfile = false;
|
|
793
942
|
if (already != null) {
|
|
794
943
|
if (chosen != null && chosen !== already) {
|
|
795
944
|
console.log(`- hive.yml: already set to "profile: ${already}"; edit it by hand to change it`);
|
|
796
945
|
}
|
|
797
946
|
chosen = already;
|
|
798
947
|
}
|
|
948
|
+
else if (projectProfileSet && !profileFlag) {
|
|
949
|
+
chosen = null;
|
|
950
|
+
}
|
|
951
|
+
else if (!projectProfileSet && !profileFlag && loaded.config?.profile != null) {
|
|
952
|
+
chosen = loaded.config.profile;
|
|
953
|
+
inheritedProfile = loaded.sources.profile === "global";
|
|
954
|
+
}
|
|
799
955
|
else if (chosen == null && process.stdin.isTTY && process.stdout.isTTY) {
|
|
800
956
|
chosen = await askForProfile();
|
|
801
957
|
}
|
|
802
|
-
if (!
|
|
803
|
-
writeFileSync(ymlPath, HIVE_YML_TEMPLATE + (chosen ? `\nprofile: ${chosen}\n` : ""));
|
|
958
|
+
if (!ymlExists) {
|
|
959
|
+
writeFileSync(ymlPath, HIVE_YML_TEMPLATE + (chosen && !inheritedProfile ? `\nprofile: ${chosen}\n` : ""));
|
|
804
960
|
console.log(`- hive.yml: created${chosen ? ` with profile: ${chosen}` : ""}`);
|
|
805
961
|
}
|
|
806
|
-
else if (already == null && chosen != null) {
|
|
962
|
+
else if (already == null && chosen != null && !inheritedProfile) {
|
|
807
963
|
writeProfileKey(ymlPath, chosen);
|
|
808
964
|
console.log(`- hive.yml: added profile: ${chosen}`);
|
|
809
965
|
}
|
|
@@ -876,7 +1032,7 @@ itself to this project before any real work runs.`);
|
|
|
876
1032
|
}
|
|
877
1033
|
const NO_RUNBOOK_PAD_MESSAGE = `is on "profile: none" but has no runbook pad. Create one with: hive init`;
|
|
878
1034
|
function cmdRunbook(path) {
|
|
879
|
-
const project =
|
|
1035
|
+
const project = resolveProjectForRead("runbook", path);
|
|
880
1036
|
const { config, warnings } = loadProjectYml(project.path);
|
|
881
1037
|
for (const w of warnings)
|
|
882
1038
|
console.log(`! ${w}`);
|
|
@@ -901,7 +1057,7 @@ function cmdRunbook(path) {
|
|
|
901
1057
|
process.stdout.write(withTrailingNewline(rendered));
|
|
902
1058
|
}
|
|
903
1059
|
function cmdPosture(path) {
|
|
904
|
-
const project =
|
|
1060
|
+
const project = resolveProjectForRead("posture", path);
|
|
905
1061
|
const { config, warnings } = loadProjectYml(project.path);
|
|
906
1062
|
for (const w of warnings)
|
|
907
1063
|
console.log(`! ${w}`);
|
|
@@ -1042,7 +1198,7 @@ function cmdProfile(argv) {
|
|
|
1042
1198
|
vars = mergedProjectVars(here ? loadProjectYml(here.path).config : null);
|
|
1043
1199
|
}
|
|
1044
1200
|
else {
|
|
1045
|
-
const project =
|
|
1201
|
+
const project = resolveProjectForRead("profile read");
|
|
1046
1202
|
const { config, warnings } = loadProjectYml(project.path);
|
|
1047
1203
|
for (const w of warnings)
|
|
1048
1204
|
console.log(`! ${w}`);
|
|
@@ -1089,6 +1245,22 @@ function maybeOpenDashboard(project, dashboardEnabled) {
|
|
|
1089
1245
|
return;
|
|
1090
1246
|
if (!dashboardFileContained(dashboardFile, project.path))
|
|
1091
1247
|
return;
|
|
1248
|
+
openDashboardOnce(project, dashboardFile);
|
|
1249
|
+
}
|
|
1250
|
+
function maybeOpenQueenDashboard(project) {
|
|
1251
|
+
if (process.platform !== "darwin")
|
|
1252
|
+
return;
|
|
1253
|
+
const dashboardFile = join(project.path, "dashboard.html");
|
|
1254
|
+
try {
|
|
1255
|
+
if (!existsSync(dashboardFile) || !dashboardFileContained(dashboardFile, project.path))
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
catch {
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
openDashboardOnce(project, dashboardFile);
|
|
1262
|
+
}
|
|
1263
|
+
function openDashboardOnce(project, dashboardFile) {
|
|
1092
1264
|
const actor = currentActor();
|
|
1093
1265
|
const claim = db
|
|
1094
1266
|
.prepare(`INSERT INTO kv (project_id, key, value, updated_by, expires_at)
|
|
@@ -1114,7 +1286,7 @@ function cmdAttach(argv) {
|
|
|
1114
1286
|
process.exit(1);
|
|
1115
1287
|
}
|
|
1116
1288
|
const path = argv.find((a) => !a.startsWith("--"));
|
|
1117
|
-
const project =
|
|
1289
|
+
const project = resolveProjectForWrite("hive attach", path);
|
|
1118
1290
|
maybeOpenDashboard(project, !!loadProjectYml(project.path).config?.dashboard);
|
|
1119
1291
|
const session = sessionName();
|
|
1120
1292
|
const window = withWindowClaim(() => {
|
|
@@ -1142,7 +1314,7 @@ async function cmdStart(argv) {
|
|
|
1142
1314
|
console.log("Usage: hive start <process> [path]");
|
|
1143
1315
|
process.exit(1);
|
|
1144
1316
|
}
|
|
1145
|
-
const project =
|
|
1317
|
+
const project = resolveProjectForWrite("hive start", path);
|
|
1146
1318
|
const { config, warnings } = loadProjectYml(project.path);
|
|
1147
1319
|
for (const w of warnings)
|
|
1148
1320
|
console.log(`! ${w}`);
|
|
@@ -1170,7 +1342,7 @@ function resolveNamedProcess(argv, verb) {
|
|
|
1170
1342
|
console.log(`Usage: hive ${verb} <process> [path]`);
|
|
1171
1343
|
process.exit(1);
|
|
1172
1344
|
}
|
|
1173
|
-
const project =
|
|
1345
|
+
const project = resolveProjectForWrite(`hive ${verb}`, parsed.positional[1]);
|
|
1174
1346
|
const { config, warnings } = loadProjectYml(project.path);
|
|
1175
1347
|
for (const w of warnings)
|
|
1176
1348
|
console.log(`! ${w}`);
|
|
@@ -1210,7 +1382,7 @@ function leadPaneExitedHookCommand() {
|
|
|
1210
1382
|
// started from inside a worker's pane hands them to every run-shell it ever runs, where a stale
|
|
1211
1383
|
// HIVE_AGENT_ID is a hard refusal before the verb is reached at all. The same clearing
|
|
1212
1384
|
// scripts/restart-lead.sh does around its own `hive lead`, plus the two project vars.
|
|
1213
|
-
const clear = ["HIVE_AGENT_ID", "HIVE_AGENT_NAME", "HIVE_LEAD", "HIVE_PROJECT_LOCK", "HIVE_PROJECT_PATH"]
|
|
1385
|
+
const clear = ["HIVE_AGENT_ID", "HIVE_AGENT_NAME", "HIVE_LEAD", FIRST_MESSAGE_SHA_ENV, "HIVE_PROJECT_LOCK", "HIVE_PROJECT_PATH"]
|
|
1214
1386
|
.flatMap((name) => ["-u", name]);
|
|
1215
1387
|
// Redirected because tmux prints a run-shell's output into the pane it fires from, which is a
|
|
1216
1388
|
// human's own window: a backstop that failed to start must not type into it.
|
|
@@ -1292,7 +1464,7 @@ function cmdStop(argv) {
|
|
|
1292
1464
|
const parsed = parseArgs(argv, { flags: ["--all"] });
|
|
1293
1465
|
rejectUnknownFlags("stop", parsed, "--all");
|
|
1294
1466
|
if (parsed.flags.has("--all")) {
|
|
1295
|
-
const project =
|
|
1467
|
+
const project = resolveProjectForWrite("hive stop", parsed.positional[0]);
|
|
1296
1468
|
const stopped = stopAllProcesses(project.id, STOP_REASONS.byHand);
|
|
1297
1469
|
console.log(stopped.length === 0 ? "No processes are running." : stopped.map(stopLine).join("\n"));
|
|
1298
1470
|
return;
|
|
@@ -1302,7 +1474,7 @@ function cmdStop(argv) {
|
|
|
1302
1474
|
console.log("Usage: hive stop <process> [path] | hive stop --all [path]");
|
|
1303
1475
|
process.exit(1);
|
|
1304
1476
|
}
|
|
1305
|
-
const project =
|
|
1477
|
+
const project = resolveProjectForWrite("hive stop", parsed.positional[1]);
|
|
1306
1478
|
const row = runningCommandRow(project.id, name);
|
|
1307
1479
|
if (!row) {
|
|
1308
1480
|
const { config, warnings } = loadProjectYml(project.path);
|
|
@@ -1462,7 +1634,6 @@ const verboseInfo = (_check, label, ...lines) => {
|
|
|
1462
1634
|
if (doctorVerbose)
|
|
1463
1635
|
report("info", label, lines);
|
|
1464
1636
|
};
|
|
1465
|
-
const isReviewFindingTag = (bases, tag) => bases.some((base) => tag === base || tag.startsWith(`${base}-`));
|
|
1466
1637
|
function cmdUpgrade(argv) {
|
|
1467
1638
|
const parsed = parseArgs(argv, { flags: ["--check", "--run"] });
|
|
1468
1639
|
rejectUnknownFlags("upgrade", parsed, "--check, --run");
|
|
@@ -1576,6 +1747,7 @@ function cmdSetup(argv) {
|
|
|
1576
1747
|
console.log("Or pin this worktree anyway: hive setup --force");
|
|
1577
1748
|
process.exit(1);
|
|
1578
1749
|
}
|
|
1750
|
+
reportMigrationResult(migrateLegacyConfig());
|
|
1579
1751
|
mkdirSync(dir, { recursive: true });
|
|
1580
1752
|
writeFileSync(file, dispatcherScript(node, cli));
|
|
1581
1753
|
chmodSync(file, 0o755);
|
|
@@ -1705,7 +1877,7 @@ function reportProjectScope(here) {
|
|
|
1705
1877
|
if (locked && project.id !== here?.id)
|
|
1706
1878
|
continue;
|
|
1707
1879
|
if (project.path === homePath || project.path === "/") {
|
|
1708
|
-
warn("project scope", `"${project.name}" (${project.path}) is a home directory; every unregistered directory under it resolves to this project.
|
|
1880
|
+
warn("project scope", `"${project.name}" (${project.path}) is a home directory; every unregistered directory under it resolves to this project. The project_prune tool with this project's id (from any hive session) removes it once it holds nothing.`);
|
|
1709
1881
|
}
|
|
1710
1882
|
}
|
|
1711
1883
|
for (const parent of projects) {
|
|
@@ -2023,6 +2195,33 @@ function reportStalledWorkers(projectId) {
|
|
|
2023
2195
|
"worker does not reliably remember what it was doing (.claude/rules/worker-state.md).");
|
|
2024
2196
|
}
|
|
2025
2197
|
}
|
|
2198
|
+
function reportEffectiveConfig(loaded) {
|
|
2199
|
+
const config = loaded.config ?? BUILT_IN_PROJECT_YML;
|
|
2200
|
+
for (const key of PROJECT_YML_KEYS) {
|
|
2201
|
+
info(`config ${key}`, `${JSON.stringify(config[key])} (source: ${loaded.sources[key] ?? "built-in"})`);
|
|
2202
|
+
}
|
|
2203
|
+
const varKeys = new Set(Object.keys(config.vars));
|
|
2204
|
+
for (const key of Object.keys(loaded.sources)) {
|
|
2205
|
+
if (key.startsWith("vars."))
|
|
2206
|
+
varKeys.add(key.slice("vars.".length));
|
|
2207
|
+
}
|
|
2208
|
+
for (const key of [...varKeys].sort()) {
|
|
2209
|
+
const value = Object.hasOwn(config.vars, key) ? config.vars[key] : null;
|
|
2210
|
+
info(`config vars.${key}`, `${JSON.stringify(value)} (source: ${loaded.sources[`vars.${key}`] ?? "built-in"})`);
|
|
2211
|
+
}
|
|
2212
|
+
const { mode: attachMode, source: attachSource } = loaded.attach;
|
|
2213
|
+
info("attach mode", attachSource === "env"
|
|
2214
|
+
? `${attachMode} (HIVE_ATTACH_MODE override; testing only, does not reach auto-attach; source: env)`
|
|
2215
|
+
: attachSource === "global"
|
|
2216
|
+
? `${attachMode} (set with \`hive setup --attach\`; source: global)`
|
|
2217
|
+
: `${attachMode} (default; set with \`hive setup --attach\`; source: built-in)`);
|
|
2218
|
+
const { value: autoAttach, source: autoAttachSource } = loaded.autoAttach;
|
|
2219
|
+
info("auto-attach", autoAttachSource === "env"
|
|
2220
|
+
? `${autoAttach} (HIVE_AUTO_ATTACH override; testing only; source: env)`
|
|
2221
|
+
: autoAttachSource === "global"
|
|
2222
|
+
? `${autoAttach} (set with \`hive setup --auto-attach\`; source: global)`
|
|
2223
|
+
: `${autoAttach} (default; set with \`hive setup --auto-attach\`; source: built-in)`);
|
|
2224
|
+
}
|
|
2026
2225
|
function cmdDoctor(argv) {
|
|
2027
2226
|
const strict = argv.includes("--strict");
|
|
2028
2227
|
doctorVerbose = argv.includes("--verbose");
|
|
@@ -2032,6 +2231,7 @@ function cmdDoctor(argv) {
|
|
|
2032
2231
|
console.error(`hive doctor: unknown argument "${unknown}". Flags are --strict and --verbose.`);
|
|
2033
2232
|
process.exit(1);
|
|
2034
2233
|
}
|
|
2234
|
+
reportMigrationResult(migrateLegacyConfig());
|
|
2035
2235
|
let failures = 0;
|
|
2036
2236
|
const fail = (label, ...lines) => {
|
|
2037
2237
|
failures += 1;
|
|
@@ -2089,9 +2289,15 @@ function cmdDoctor(argv) {
|
|
|
2089
2289
|
reportSessionInterpreters();
|
|
2090
2290
|
reportCodexHomes(here?.id ?? null);
|
|
2091
2291
|
const loaded = loadProjectYml(here?.path ?? process.cwd());
|
|
2092
|
-
for (const w of loaded.warnings)
|
|
2093
|
-
|
|
2292
|
+
for (const w of loaded.warnings) {
|
|
2293
|
+
const qualified = /^([^:]+): ([\s\S]*)$/.exec(w);
|
|
2294
|
+
if (qualified)
|
|
2295
|
+
warn(qualified[1], qualified[2]);
|
|
2296
|
+
else
|
|
2297
|
+
warn("hive.yml", w);
|
|
2298
|
+
}
|
|
2094
2299
|
const config = loaded.config;
|
|
2300
|
+
reportEffectiveConfig(loaded);
|
|
2095
2301
|
const profile = activeProfile(config);
|
|
2096
2302
|
if (here && existsSync(join(here.path, ".claude", "dashboard", "index.html"))) {
|
|
2097
2303
|
info("dashboard", "an older hive wrote .claude/dashboard/index.html; hive now writes .hive/dashboard.html. Delete the old file and directory.");
|
|
@@ -2399,20 +2605,7 @@ function cmdDoctor(argv) {
|
|
|
2399
2605
|
return health.message;
|
|
2400
2606
|
});
|
|
2401
2607
|
{
|
|
2402
|
-
const {
|
|
2403
|
-
info("auto-attach", source === "env"
|
|
2404
|
-
? `${value} (HIVE_AUTO_ATTACH override; testing only)`
|
|
2405
|
-
: source === "config"
|
|
2406
|
-
? `${value} (set with \`hive setup --auto-attach\`)`
|
|
2407
|
-
: `${value} (default; set with \`hive setup --auto-attach\`)`);
|
|
2408
|
-
}
|
|
2409
|
-
{
|
|
2410
|
-
const { mode, source } = resolvedAttachMode();
|
|
2411
|
-
info("attach mode", source === "env"
|
|
2412
|
-
? `${mode} (HIVE_ATTACH_MODE override; testing only, does not reach auto-attach)`
|
|
2413
|
-
: source === "config"
|
|
2414
|
-
? `${mode} (set with \`hive setup --attach\`)`
|
|
2415
|
-
: `${mode} (default; set with \`hive setup --attach\`)`);
|
|
2608
|
+
const { mode } = loaded.attach;
|
|
2416
2609
|
if (mode === "raw") {
|
|
2417
2610
|
try {
|
|
2418
2611
|
const owned = tmux("list-windows", "-a", "-F", "#{session_name}:#{window_id}\t#{@hive-owned}")
|
|
@@ -2432,8 +2625,7 @@ function cmdDoctor(argv) {
|
|
|
2432
2625
|
`monitor-bell ${option("-w", "monitor-bell")}`);
|
|
2433
2626
|
}
|
|
2434
2627
|
}
|
|
2435
|
-
catch {
|
|
2436
|
-
}
|
|
2628
|
+
catch { }
|
|
2437
2629
|
}
|
|
2438
2630
|
}
|
|
2439
2631
|
if (here) {
|
|
@@ -2444,25 +2636,6 @@ function cmdDoctor(argv) {
|
|
|
2444
2636
|
info("processes", `${running} running (${located}), ${notStarted} defined not running`);
|
|
2445
2637
|
}
|
|
2446
2638
|
}
|
|
2447
|
-
const reviewTags = config?.review_tags ?? [];
|
|
2448
|
-
// Never name hive.yml here: doctor stays silent about that file when nothing is wrong with it
|
|
2449
|
-
// (test/config-warnings.test.mjs), and this line prints on every ordinary run.
|
|
2450
|
-
if (here && reviewTags.length === 0) {
|
|
2451
|
-
info("review findings", "no review_tags configured; nothing to track.");
|
|
2452
|
-
}
|
|
2453
|
-
else if (here) {
|
|
2454
|
-
const findings = db
|
|
2455
|
-
.prepare(`SELECT id, tags, status, archived_at, ${COMMENT_COUNT_SQL} AS comment_count
|
|
2456
|
-
FROM todos t WHERE t.project_id = ?`)
|
|
2457
|
-
.all(here.id).filter((t) => parseTags(t.tags).some((tag) => isReviewFindingTag(reviewTags, tag)));
|
|
2458
|
-
const untriaged = findings.filter((t) => t.comment_count === 0 && t.status !== "completed" && t.archived_at == null);
|
|
2459
|
-
info("review findings", `${findings.length} tracked (tagged ${reviewTags.join(", ")}): ` +
|
|
2460
|
-
`${findings.length - untriaged.length} triaged, ${untriaged.length} untriaged.`);
|
|
2461
|
-
if (untriaged.length > 0) {
|
|
2462
|
-
warn("review findings", `${untriaged.length} untriaged: ${untriaged.map((t) => `todo ${t.id}`).join(", ")}.`, "Triage means a comment recording a decision, or completed/archived - comment with the outcome " +
|
|
2463
|
-
"(dispatched, rejected and why, etc.) on each before closing this wave.");
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
2639
|
const problems = failures + (strict ? gatingWarnings : 0);
|
|
2467
2640
|
const tail = strict
|
|
2468
2641
|
? `${warnings} warning(s), ${gatingWarnings} promoted by --strict.`
|
|
@@ -2576,6 +2749,144 @@ function findPadExports(projectId, name) {
|
|
|
2576
2749
|
.filter((f) => f.startsWith(prefix) && f.endsWith(".md"))
|
|
2577
2750
|
.map((f) => join(tmpdir(), f));
|
|
2578
2751
|
}
|
|
2752
|
+
function canonicalPath(path) {
|
|
2753
|
+
try {
|
|
2754
|
+
return realpathSync(path);
|
|
2755
|
+
}
|
|
2756
|
+
catch {
|
|
2757
|
+
return resolve(path);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
function nextCandidates() {
|
|
2761
|
+
const queenRoot = canonicalPath(join(storeDir(), "queen"));
|
|
2762
|
+
const byCount = (a, b) => b - a;
|
|
2763
|
+
return collectPortfolio()
|
|
2764
|
+
.projects.filter((p) => (p.lane === "waiting_on_you" || p.lane === "stuck" || (p.lane === "moving" && p.needs_human > 0)) &&
|
|
2765
|
+
canonicalPath(p.root) !== queenRoot)
|
|
2766
|
+
.sort((a, b) => {
|
|
2767
|
+
const tier = (p) => (p.lane === "waiting_on_you" ? 0 : p.needs_human > 0 ? 1 : 2);
|
|
2768
|
+
if (tier(a) !== tier(b))
|
|
2769
|
+
return tier(a) - tier(b);
|
|
2770
|
+
if (a.lane === "waiting_on_you") {
|
|
2771
|
+
return (byCount(a.needs_human, b.needs_human) ||
|
|
2772
|
+
a.last_activity_at.localeCompare(b.last_activity_at) ||
|
|
2773
|
+
a.id - b.id);
|
|
2774
|
+
}
|
|
2775
|
+
return (byCount(a.needs_human, b.needs_human) ||
|
|
2776
|
+
byCount(a.todos.blocked_in_progress, b.todos.blocked_in_progress) ||
|
|
2777
|
+
byCount(a.wakes.overdue, b.wakes.overdue) ||
|
|
2778
|
+
byCount(a.workers.needs_input, b.workers.needs_input) ||
|
|
2779
|
+
a.last_activity_at.localeCompare(b.last_activity_at) ||
|
|
2780
|
+
a.id - b.id);
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
function collectProjectById(id) {
|
|
2784
|
+
return collectPortfolio().projects.find((p) => p.id === id);
|
|
2785
|
+
}
|
|
2786
|
+
async function cmdNext(argv) {
|
|
2787
|
+
const parsed = parseArgs(argv, { flags: ["--print"] });
|
|
2788
|
+
if (parsed.positional.length > 0)
|
|
2789
|
+
parsed.unknown.push(parsed.positional[0]);
|
|
2790
|
+
rejectUnknownFlags("next", parsed, "--print");
|
|
2791
|
+
if (process.env.HIVE_PROJECT_LOCK === "1") {
|
|
2792
|
+
console.error("hive next: this session is locked to one project (HIVE_PROJECT_LOCK=1) and cannot jump to another. Run it from a pane outside a worker session.");
|
|
2793
|
+
process.exit(1);
|
|
2794
|
+
}
|
|
2795
|
+
const chosen = nextCandidates()[0];
|
|
2796
|
+
if (!chosen) {
|
|
2797
|
+
console.log("No project needs you: none is waiting, stuck, or holding a needs-human item.");
|
|
2798
|
+
return;
|
|
2799
|
+
}
|
|
2800
|
+
console.log(JSON.stringify({
|
|
2801
|
+
project_id: chosen.id,
|
|
2802
|
+
name: chosen.name,
|
|
2803
|
+
root: chosen.root,
|
|
2804
|
+
lane: chosen.lane,
|
|
2805
|
+
reasons: chosen.reasons,
|
|
2806
|
+
lead_state: chosen.lead.state,
|
|
2807
|
+
}));
|
|
2808
|
+
if (parsed.flags.has("--print"))
|
|
2809
|
+
return;
|
|
2810
|
+
assertQueenCrossProjectWrite("hive next", chosen.id, {});
|
|
2811
|
+
if (!chosen.root_exists) {
|
|
2812
|
+
console.error(`hive next: project ${chosen.id} ("${chosen.name}") root is missing: ${chosen.root}. Remove the stale project with the project_prune tool, or restore the directory.`);
|
|
2813
|
+
process.exit(1);
|
|
2814
|
+
}
|
|
2815
|
+
let state = collectProjectById(chosen.id)?.lead.state ?? "none";
|
|
2816
|
+
if (state === "unknown") {
|
|
2817
|
+
console.error(`hive next: cannot tell whether the lead for project ${chosen.id} ("${chosen.name}") is alive (tmux did not answer for its socket). Starting nothing.`);
|
|
2818
|
+
process.exit(1);
|
|
2819
|
+
}
|
|
2820
|
+
await cmdLead([chosen.root, "--detach"]);
|
|
2821
|
+
state = collectProjectById(chosen.id)?.lead.state ?? "none";
|
|
2822
|
+
if (state !== "alive" || (process.exitCode !== undefined && process.exitCode !== 0)) {
|
|
2823
|
+
console.error(`hive next: the lead for project ${chosen.id} ("${chosen.name}") did not start cleanly (${state}). Not attaching.`);
|
|
2824
|
+
process.exit(1);
|
|
2825
|
+
}
|
|
2826
|
+
cmdAttach([chosen.root]);
|
|
2827
|
+
}
|
|
2828
|
+
function cmdQueenAudit(argv) {
|
|
2829
|
+
const parsed = parseArgs(argv, { flags: ["--json"], valued: ["--project-id", "--limit"] });
|
|
2830
|
+
if (parsed.positional.length > 0)
|
|
2831
|
+
parsed.unknown.push(parsed.positional[0]);
|
|
2832
|
+
rejectUnknownFlags("queen-audit", parsed, "--project-id <id>, --limit <n>, --json");
|
|
2833
|
+
requireFlagValues("queen-audit", parsed);
|
|
2834
|
+
let report;
|
|
2835
|
+
try {
|
|
2836
|
+
const project = pinnedOrCwdProject();
|
|
2837
|
+
if (!project)
|
|
2838
|
+
throw new Error("hive queen-audit: the cwd is not a registered project. Run this command from a registered project.");
|
|
2839
|
+
report = listQueenAudit({
|
|
2840
|
+
project_id: parsed.values.has("--project-id") ? Number(parsed.values.get("--project-id")) : undefined,
|
|
2841
|
+
limit: parsed.values.has("--limit") ? Number(parsed.values.get("--limit")) : undefined,
|
|
2842
|
+
}, project.id);
|
|
2843
|
+
}
|
|
2844
|
+
catch (e) {
|
|
2845
|
+
console.error(errorMessage(e));
|
|
2846
|
+
process.exitCode = 1;
|
|
2847
|
+
return;
|
|
2848
|
+
}
|
|
2849
|
+
if (parsed.flags.has("--json")) {
|
|
2850
|
+
console.log(JSON.stringify(report));
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
if (report.entries.length === 0) {
|
|
2854
|
+
console.log("No queen actions recorded.");
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2857
|
+
for (const entry of report.entries) {
|
|
2858
|
+
console.log(`${entry.created_at} UTC ${entry.actor_id} -> target #${entry.target_project_id}\n` +
|
|
2859
|
+
` ${entry.operation} ${entry.resource_type} #${entry.resource_id}\n ${entry.summary}`);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
function cmdPortfolio(argv) {
|
|
2863
|
+
const parsed = parseArgs(argv, { flags: ["--json"] });
|
|
2864
|
+
if (parsed.positional.length > 0)
|
|
2865
|
+
parsed.unknown.push(parsed.positional[0]);
|
|
2866
|
+
rejectUnknownFlags("portfolio", parsed, "--json");
|
|
2867
|
+
const report = collectPortfolio();
|
|
2868
|
+
if (parsed.flags.has("--json")) {
|
|
2869
|
+
console.log(JSON.stringify(report));
|
|
2870
|
+
return;
|
|
2871
|
+
}
|
|
2872
|
+
if (report.projects.length === 0) {
|
|
2873
|
+
console.log("No registered projects.");
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
for (const p of report.projects) {
|
|
2877
|
+
const w = p.workers;
|
|
2878
|
+
const t = p.todos;
|
|
2879
|
+
console.log(`${p.lane.padEnd(14)} ${p.name} (#${p.id})${p.root_exists ? "" : " [root missing]"}\n` +
|
|
2880
|
+
` why: ${p.reasons.join(", ")}\n` +
|
|
2881
|
+
` lead ${leadText(p)}; workers ${w.working} working, ${w.idle} idle, ${w.needs_input} needs input, ` +
|
|
2882
|
+
`${w.other} other, ${w.unreachable} unreachable, ${w.unconfirmed} unconfirmed\n` +
|
|
2883
|
+
` todos ${t.open} open, ${t.in_progress} in progress, ${t.blocked} blocked, ${t.high} high; ` +
|
|
2884
|
+
`needs human ${p.needs_human}; wakes ${p.wakes.pending} pending, ${p.wakes.overdue} overdue\n` +
|
|
2885
|
+
` last activity ${p.last_activity_at} UTC`);
|
|
2886
|
+
}
|
|
2887
|
+
const l = report.totals.lanes;
|
|
2888
|
+
console.log(`\n${report.totals.projects} project(s): ${l.waiting_on_you} waiting on you, ${l.stuck} stuck, ${l.moving} moving, ${l.quiet} quiet`);
|
|
2889
|
+
}
|
|
2579
2890
|
function cmdBackups() {
|
|
2580
2891
|
const snapshots = listSnapshots(dataDir);
|
|
2581
2892
|
if (snapshots.length === 0) {
|
|
@@ -2743,9 +3054,7 @@ function requireFlagValues(command, parsed) {
|
|
|
2743
3054
|
process.exit(1);
|
|
2744
3055
|
}
|
|
2745
3056
|
function cmdTodos(argv) {
|
|
2746
|
-
const project =
|
|
2747
|
-
if (!project)
|
|
2748
|
-
return;
|
|
3057
|
+
const project = resolveProjectForRead("todos");
|
|
2749
3058
|
const all = argv.includes("--all");
|
|
2750
3059
|
const statusIdx = argv.indexOf("--status");
|
|
2751
3060
|
const status = flagValue(argv, "--status");
|
|
@@ -2789,9 +3098,7 @@ function cmdTodos(argv) {
|
|
|
2789
3098
|
}
|
|
2790
3099
|
}
|
|
2791
3100
|
function cmdTodo(argv) {
|
|
2792
|
-
const project =
|
|
2793
|
-
if (!project)
|
|
2794
|
-
return;
|
|
3101
|
+
const project = resolveProjectForRead("todo");
|
|
2795
3102
|
const id = Number(argv.find((a) => !a.startsWith("--")));
|
|
2796
3103
|
if (!Number.isInteger(id)) {
|
|
2797
3104
|
console.log("Usage: hive todo <id> (run inside the project)");
|
|
@@ -2832,7 +3139,7 @@ function cmdTodo(argv) {
|
|
|
2832
3139
|
}
|
|
2833
3140
|
}
|
|
2834
3141
|
function cmdPads() {
|
|
2835
|
-
const project =
|
|
3142
|
+
const project = resolveProjectForRead("pads");
|
|
2836
3143
|
const pads = listActivePads(project.id);
|
|
2837
3144
|
if (pads.length === 0) {
|
|
2838
3145
|
console.log(`No pads in project "${project.name}".`);
|
|
@@ -2851,7 +3158,7 @@ function cmdPad(argv) {
|
|
|
2851
3158
|
console.log("Usage: hive pad <name> [--edit | --save [file]] (run inside the project)");
|
|
2852
3159
|
process.exit(1);
|
|
2853
3160
|
}
|
|
2854
|
-
const project =
|
|
3161
|
+
const project = parsed.flags.has("--save") ? resolveProjectForWrite("hive pad --save") : resolveProjectForRead("pad");
|
|
2855
3162
|
const pad = getActivePadByName(project.id, name);
|
|
2856
3163
|
if (!pad) {
|
|
2857
3164
|
console.log(`No pad named "${name}" in project "${project.name}". List them with: hive pads`);
|
|
@@ -2921,6 +3228,11 @@ function cmdPad(argv) {
|
|
|
2921
3228
|
}
|
|
2922
3229
|
process.stdout.write(withTrailingNewline(pad.content));
|
|
2923
3230
|
}
|
|
3231
|
+
process.stdout.on("error", (error) => {
|
|
3232
|
+
if (error.code === "EPIPE")
|
|
3233
|
+
process.exit(0);
|
|
3234
|
+
throw error;
|
|
3235
|
+
});
|
|
2924
3236
|
const args = process.argv.slice(2);
|
|
2925
3237
|
let command = args[0] ?? "lead";
|
|
2926
3238
|
let rest = args.slice(1);
|
|
@@ -2945,7 +3257,7 @@ if (command === "--version" || command === "-v") {
|
|
|
2945
3257
|
process.exit(0);
|
|
2946
3258
|
}
|
|
2947
3259
|
const COMMANDS = [
|
|
2948
|
-
"lead", "init", "attach", "start", "stop", "show", "hide", "status", "setup", "upgrade", "doctor",
|
|
3260
|
+
"lead", "queen", "queen-audit", "init", "attach", "start", "stop", "show", "hide", "status", "portfolio", "next", "setup", "upgrade", "doctor",
|
|
2949
3261
|
LEAD_PANE_EXITED_VERB,
|
|
2950
3262
|
"pads", "pad", "todos", "todo", "backups", "restore", "runbook", "posture", "profile", "kickoff", "statusline",
|
|
2951
3263
|
];
|
|
@@ -2969,6 +3281,9 @@ try {
|
|
|
2969
3281
|
case "lead":
|
|
2970
3282
|
await cmdLead(rest);
|
|
2971
3283
|
break;
|
|
3284
|
+
case "queen":
|
|
3285
|
+
await cmdQueen(rest);
|
|
3286
|
+
break;
|
|
2972
3287
|
case "init":
|
|
2973
3288
|
await cmdInit(rest);
|
|
2974
3289
|
break;
|
|
@@ -2993,6 +3308,15 @@ try {
|
|
|
2993
3308
|
case "status":
|
|
2994
3309
|
cmdStatus();
|
|
2995
3310
|
break;
|
|
3311
|
+
case "portfolio":
|
|
3312
|
+
cmdPortfolio(rest);
|
|
3313
|
+
break;
|
|
3314
|
+
case "queen-audit":
|
|
3315
|
+
cmdQueenAudit(rest);
|
|
3316
|
+
break;
|
|
3317
|
+
case "next":
|
|
3318
|
+
await cmdNext(rest);
|
|
3319
|
+
break;
|
|
2996
3320
|
case "setup":
|
|
2997
3321
|
cmdSetup(rest);
|
|
2998
3322
|
break;
|