@indigoai-us/hq-cli 5.294.0 → 5.296.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/CHANGELOG.md +33 -0
- package/dist/command-catalog.generated.d.ts +70 -9
- package/dist/command-catalog.generated.js +88 -12
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +76 -1
- package/dist/commands/core-job-native.d.ts +7 -0
- package/dist/commands/core-job-native.js +20 -0
- package/dist/commands/core-job-probe-native.d.ts +45 -0
- package/dist/commands/core-job-probe-native.js +564 -0
- package/dist/commands/core-job-remote-launcher.d.ts +7 -0
- package/dist/commands/core-job-remote-launcher.js +51 -0
- package/dist/commands/core-job-run-native.d.ts +47 -0
- package/dist/commands/core-job-run-native.js +1215 -0
- package/dist/commands/core.js +1 -1
- package/dist/commands/doctor.js +0 -74
- package/dist/commands/lanes-message.js +24 -5
- package/dist/commands/lanes-resume.js +1 -3
- package/dist/commands/lanes-shared.d.ts +1 -2
- package/dist/commands/lanes-stories.d.ts +1 -0
- package/dist/commands/lanes-stories.js +40 -1
- package/dist/commands/lanes.d.ts +1 -9
- package/dist/commands/lanes.js +197 -174
- package/dist/commands/run.js +5 -0
- package/dist/fast-scaffold.d.ts +0 -5
- package/dist/fast-scaffold.js +0 -10
- package/dist/index.js +1 -10
- package/dist/lib/core-utils/sentry-report.d.ts +0 -19
- package/dist/lib/core-utils/sentry-report.js +8 -61
- package/dist/lib/doctor/checks/claude-wiring.js +9 -0
- package/dist/lib/lanes/flag-keys.d.ts +2 -0
- package/dist/lib/lanes/flag-keys.js +2 -0
- package/dist/lib/lanes/manager-status-gate.d.ts +0 -17
- package/dist/lib/lanes/manager-status-gate.js +2 -43
- package/dist/lib/lanes/mesh-events.js +3 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +2 -0
- package/dist/lib/lanes/outpost-dispatch.js +7 -1
- package/dist/lib/lanes/outpost-hire-cache.d.ts +0 -1
- package/dist/lib/lanes/outpost-hire-cache.js +0 -1
- package/dist/lib/lanes/outpost-snapshot-memo.d.ts +6 -0
- package/dist/lib/lanes/outpost-snapshot-memo.js +14 -0
- package/dist/lib/lanes/reconcile.d.ts +2 -3
- package/dist/lib/lanes/reconcile.js +67 -57
- package/dist/lib/lanes/spawn.d.ts +0 -2
- package/dist/lib/lanes/spawn.js +57 -5
- package/dist/lib/lanes/store.js +54 -0
- package/dist/lib/lanes/stories.d.ts +7 -1
- package/dist/lib/lanes/stories.js +16 -0
- package/dist/lib/lanes/types.d.ts +30 -1
- package/dist/lib/lanes/types.js +2 -2
- package/dist/lib/lanes/watcher.d.ts +4 -3
- package/dist/lib/lanes/watcher.js +122 -56
- package/dist/lib/mesh/live/daemon/run.js +18 -1
- package/dist/run/env-graph-guard.d.ts +2 -2
- package/dist/run/env-graph-guard.js +5 -2
- package/dist/run/hq-plugin.js +6 -5
- package/dist/utils/cognito-session.d.ts +6 -0
- package/dist/utils/cognito-session.js +9 -0
- package/dist/utils/self-update.js +31 -0
- package/dist/utils/version-gate.js +22 -3
- package/package.json +2 -2
- package/dist/fast-lanes-list-route.d.ts +0 -8
- package/dist/fast-lanes-list-route.js +0 -32
- package/dist/fast-lanes-list.d.ts +0 -3
- package/dist/fast-lanes-list.js +0 -224
- package/dist/lib/doctor/client-health-sentry-flag.d.ts +0 -23
- package/dist/lib/doctor/client-health-sentry-flag.js +0 -99
- package/dist/lib/doctor/client-health-sentry.d.ts +0 -71
- package/dist/lib/doctor/client-health-sentry.js +0 -286
- package/dist/lib/lanes/outpost-snapshot-cache.d.ts +0 -26
- package/dist/lib/lanes/outpost-snapshot-cache.js +0 -215
- package/dist/lib/lanes/outpost-snapshot-refresh.d.ts +0 -21
- package/dist/lib/lanes/outpost-snapshot-refresh.js +0 -329
package/dist/commands/core.js
CHANGED
|
@@ -36,7 +36,7 @@ import { registerCoreHandoffCommand } from "./core-handoff.js";
|
|
|
36
36
|
import { registerCoreOntologyCommand } from "./core-ontology.js";
|
|
37
37
|
import { registerCoreKnowledgePrefsCommand } from "./core-knowledge-prefs.js";
|
|
38
38
|
import { registerCorePolicyCommand } from "./core-policy.js";
|
|
39
|
-
import { registerCoreJobsNativeCommand } from "./core-
|
|
39
|
+
import { registerCoreJobsNativeCommand } from "./core-job-native.js";
|
|
40
40
|
import { registerCoreCheckpointGateSettingCommand } from "./core-checkpoint-gate-setting.js";
|
|
41
41
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
42
42
|
import { interpreterFor, runBundledScript } from "../utils/run-bundled-script.js";
|
package/dist/commands/doctor.js
CHANGED
|
@@ -33,9 +33,6 @@ import { PARITY_FAMILY_ID, PARITY_FAMILY_TITLE, runParityReplay, } from "../lib/
|
|
|
33
33
|
import { applyFixes } from "../lib/doctor/fix/apply.js";
|
|
34
34
|
import { renderDoctorFixJson } from "../lib/doctor/fix/json-output.js";
|
|
35
35
|
import { clientHealthReasonCodesEnabled as isActiveCompanyClientHealthReasonCodesEnabled, clientHealthReasonCodesEnabledForCompany, } from "../lib/doctor/client-health-reason-codes-flag.js";
|
|
36
|
-
import { CLIENT_HEALTH_SENTRY_MAX_INPUT_BYTES, CLIENT_HEALTH_SENTRY_NOT_HANDLED_EXIT_CODE, handleClientHealthSentryReport, } from "../lib/doctor/client-health-sentry.js";
|
|
37
|
-
import { clientHealthSentryDirectEnabled, clientHealthSentryDirectEnabledForCompany, } from "../lib/doctor/client-health-sentry-flag.js";
|
|
38
|
-
import { reportClientHealthEvents } from "../lib/core-utils/sentry-report.js";
|
|
39
36
|
import { isCompanyJournalSlug } from "../lib/doctor/checks/sync-health.js";
|
|
40
37
|
import { readCompaniesManifestMap } from "../lib/work-context/repo-remote.js";
|
|
41
38
|
import * as readline from "node:readline";
|
|
@@ -244,26 +241,6 @@ function createClientHealthReasonCodeCompanyResolver(hqRoot) {
|
|
|
244
241
|
});
|
|
245
242
|
};
|
|
246
243
|
}
|
|
247
|
-
function createClientHealthSentryCompanyResolver(hqRoot) {
|
|
248
|
-
const companies = readCompaniesManifestMap(hqRoot);
|
|
249
|
-
return async (companySlug) => {
|
|
250
|
-
if (companySlug === null)
|
|
251
|
-
return clientHealthSentryDirectEnabled();
|
|
252
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(companySlug))
|
|
253
|
-
return false;
|
|
254
|
-
const manifestUid = companies?.[companySlug]?.cloud_uid;
|
|
255
|
-
const activeSlug = process.env.HQ_COMPANY_SLUG?.trim() || "";
|
|
256
|
-
const activeUid = process.env.HQ_COMPANY_UID?.trim() || "";
|
|
257
|
-
const companyUid = typeof manifestUid === "string" && manifestUid.trim()
|
|
258
|
-
? manifestUid.trim()
|
|
259
|
-
: companySlug === activeSlug
|
|
260
|
-
? activeUid
|
|
261
|
-
: "";
|
|
262
|
-
if (!/^cmp_[A-Za-z0-9]{3,128}$/.test(companyUid))
|
|
263
|
-
return false;
|
|
264
|
-
return clientHealthSentryDirectEnabledForCompany({ companyUid, companySlug });
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
244
|
/** Register the top-level `hq doctor` command so it appears in `hq --help`. */
|
|
268
245
|
export function registerDoctorCommand(program) {
|
|
269
246
|
program
|
|
@@ -279,9 +256,6 @@ export function registerDoctorCommand(program) {
|
|
|
279
256
|
.option("--only <families>", "Run only these comma-separated check families (e.g. hooks). Unknown names are rejected; --json then records the scope.")
|
|
280
257
|
.option("--fix", "Apply the allowlisted safe repairs (backs up first; read-only without this flag).")
|
|
281
258
|
.addOption(new Option("--client-health-report", "Resolve the opt-in hq-flags gate used by the client-health report hook.").hideHelp())
|
|
282
|
-
.addOption(new Option("--client-health-sentry", "Send the opt-in client-health Sentry report from a doctor JSON document.").hideHelp())
|
|
283
|
-
.addOption(new Option("--json-input <file>", "Doctor JSON input for the client-health report.").hideHelp())
|
|
284
|
-
.addOption(new Option("--client-health-fix-outcome <outcome>", "Safe fix outcome label for the client-health report.").hideHelp())
|
|
285
259
|
.option("--yes", "Skip the interactive --fix confirmation (non-interactive use).")
|
|
286
260
|
.option("--force", "Let --fix run despite uncommitted changes under .claude/, .codex/, or .grok/.")
|
|
287
261
|
.action(async (opts) => {
|
|
@@ -298,54 +272,6 @@ export function registerDoctorCommand(program) {
|
|
|
298
272
|
process.exitCode = 1;
|
|
299
273
|
return;
|
|
300
274
|
}
|
|
301
|
-
if (opts.clientHealthSentry === true) {
|
|
302
|
-
const hqRoot = resolveHqRoot();
|
|
303
|
-
if (!hqRoot || !opts.jsonInput) {
|
|
304
|
-
process.stderr.write("hq doctor --client-health-sentry requires an HQ root and --json-input.\n");
|
|
305
|
-
process.exitCode = 1;
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
try {
|
|
309
|
-
const workspacePath = path.join(hqRoot, "workspace");
|
|
310
|
-
const workspaceStat = fs.lstatSync(workspacePath);
|
|
311
|
-
if (!workspaceStat.isDirectory() || workspaceStat.isSymbolicLink()) {
|
|
312
|
-
throw new Error("workspace is not a local directory");
|
|
313
|
-
}
|
|
314
|
-
const statePath = path.join(hqRoot, "workspace", ".hq-client-health");
|
|
315
|
-
const stateStat = fs.lstatSync(statePath);
|
|
316
|
-
if (!stateStat.isDirectory() || stateStat.isSymbolicLink()) {
|
|
317
|
-
throw new Error("client-health state is not a local directory");
|
|
318
|
-
}
|
|
319
|
-
const stateRoot = fs.realpathSync(statePath);
|
|
320
|
-
const inputPath = fs.realpathSync(opts.jsonInput);
|
|
321
|
-
const relativeInput = path.relative(stateRoot, inputPath);
|
|
322
|
-
if (relativeInput === ".." || relativeInput.startsWith(`..${path.sep}`) || path.isAbsolute(relativeInput)) {
|
|
323
|
-
throw new Error("input path is outside client-health state");
|
|
324
|
-
}
|
|
325
|
-
if (fs.statSync(inputPath).size > CLIENT_HEALTH_SENTRY_MAX_INPUT_BYTES) {
|
|
326
|
-
throw new Error("input exceeds client-health size limit");
|
|
327
|
-
}
|
|
328
|
-
const result = await handleClientHealthSentryReport({
|
|
329
|
-
hqRoot,
|
|
330
|
-
doctorJson: fs.readFileSync(inputPath, "utf8"),
|
|
331
|
-
fixOutcome: opts.clientHealthFixOutcome ?? "not_auto_fixable",
|
|
332
|
-
}, {
|
|
333
|
-
resolveFlagForCompany: createClientHealthSentryCompanyResolver(hqRoot),
|
|
334
|
-
sendEvents: async (events) => (await reportClientHealthEvents(events)).code === 0,
|
|
335
|
-
});
|
|
336
|
-
process.exitCode =
|
|
337
|
-
result === "not_handled"
|
|
338
|
-
? CLIENT_HEALTH_SENTRY_NOT_HANDLED_EXIT_CODE
|
|
339
|
-
: result === "send_failed"
|
|
340
|
-
? 1
|
|
341
|
-
: 0;
|
|
342
|
-
}
|
|
343
|
-
catch {
|
|
344
|
-
process.stderr.write("hq doctor --client-health-sentry could not handle the report.\n");
|
|
345
|
-
process.exitCode = 1;
|
|
346
|
-
}
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
275
|
// `--fix` is the only write path. It resolves the tree, applies the
|
|
350
276
|
// allowlisted repairs behind a backup + confirmation, and returns its own
|
|
351
277
|
// exit code; the read-only report below never runs in this branch.
|
|
@@ -10,12 +10,11 @@ import { isPidAlive, runWatcherLoop, WATCHER_OUT_MAX_BYTES, watcherOutLogPath, w
|
|
|
10
10
|
import { laneStories, laneStory, storyWarning } from "../lib/lanes/stories.js";
|
|
11
11
|
import { laneRunDir, probePidRecord, readPidRecord, readProcStarttime, spawnLaneWorker } from "../lib/lanes/spawn.js";
|
|
12
12
|
import { keepAliveExclusion, keepAliveSupersededBy, LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_FLAG, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds } from "../lib/lanes/keep-alive.js";
|
|
13
|
-
import { managerStatusGateEnabledForLane } from "../lib/lanes/manager-status-gate.js";
|
|
14
13
|
import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
|
|
15
14
|
import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
|
|
16
15
|
import { accountMoveSummary, accountRequestKeepsCurrentLane, assertCurrentResumeAccountAvailable, callerMatchesSessionSenior, recordedResumeEffort, resetKeepAliveProgress, resolveResumeEffort } from "./lanes-shared.js";
|
|
17
16
|
export function createLaneMessageHandlers(deps) {
|
|
18
|
-
const { archiveResumeMessage, assertLaneRunsHereForAdmission, assertLaneRunsHereForOperatorMutation, assessLaneAdmission, casUpdateLane, dispatchOutpostLaneCommand, drainHookStatusFor, laneIsHostedHere, laneWithRecoveredProviderSession, loadAllLanes, mintMessageId, persistClaudeAccountSelection, prepareAccountMove, printJson, recordResumeSetup, resolveActiveQuestionCompany, resolveResumeProfile, selectAutomaticClaudeResumeAccount, validateResumeProfileModel, withResumeHireAdmission } = deps;
|
|
17
|
+
const { archiveResumeMessage, assertLaneRunsHereForAdmission, assertLaneRunsHereForOperatorMutation, assessLaneAdmission, casUpdateLane, dispatchOutpostLaneCommand, dispatchPrimaryLaneMessage, drainHookStatusFor, laneIsHostedHere, laneWithRecoveredProviderSession, loadAllLanes, mintMessageId, persistClaudeAccountSelection, prepareAccountMove, printJson, recordResumeSetup, resolveActiveQuestionCompany, resolveResumeProfile, selectAutomaticClaudeResumeAccount, validateResumeProfileModel, withResumeHireAdmission } = deps;
|
|
19
18
|
function currentSuccessorOf(lane, lanes, lineageAware, companyForLane) {
|
|
20
19
|
const validLanes = lanes.filter((candidate) => Number.isFinite(Date.parse(candidate.created_at)));
|
|
21
20
|
const newestFirst = [...validLanes].sort((left, right) => Date.parse(right.created_at) - Date.parse(left.created_at));
|
|
@@ -221,7 +220,27 @@ export function createLaneMessageHandlers(deps) {
|
|
|
221
220
|
if (!text) {
|
|
222
221
|
throw new LanesError("missing_text", "--text is required", {});
|
|
223
222
|
}
|
|
224
|
-
|
|
223
|
+
let resolution;
|
|
224
|
+
try {
|
|
225
|
+
resolution = await deps.resolveCommandLane(ctx, laneId);
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
const identity = ctx.readOutpostIdentity();
|
|
229
|
+
const failedOutpostIds = err instanceof LanesError ? err.details.failed_outpost_ids : undefined;
|
|
230
|
+
const probedOutpostIds = err instanceof LanesError ? err.details.probed_outpost_ids : undefined;
|
|
231
|
+
const primaryLookupFailed = Array.isArray(failedOutpostIds) && failedOutpostIds.includes("primary");
|
|
232
|
+
const primaryReportedMissing = Array.isArray(probedOutpostIds) && probedOutpostIds.includes("primary");
|
|
233
|
+
if (!(err instanceof LanesError) ||
|
|
234
|
+
err.error !== "lane_not_found" ||
|
|
235
|
+
identity.kind !== "id" ||
|
|
236
|
+
identity.id === "primary" ||
|
|
237
|
+
primaryLookupFailed ||
|
|
238
|
+
primaryReportedMissing) {
|
|
239
|
+
throw err;
|
|
240
|
+
}
|
|
241
|
+
await dispatchPrimaryLaneMessage(ctx, laneId, { ...opts, text });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
225
244
|
let lane = resolution.lane;
|
|
226
245
|
if (resolution.remoteOnly || (lane.host.kind === "outpost" && !laneIsHostedHere(ctx, lane))) {
|
|
227
246
|
const callerLaneId = ctx.env.HQ_LANE_ID?.trim();
|
|
@@ -465,7 +484,6 @@ export function createLaneMessageHandlers(deps) {
|
|
|
465
484
|
markAccepted();
|
|
466
485
|
}
|
|
467
486
|
else {
|
|
468
|
-
const managerStatusGateEnabled = await managerStatusGateEnabledForLane(hqRoot, lane, { env: ctx.env });
|
|
469
487
|
const dispatched = await dispatchLaneDelivery(lane, {
|
|
470
488
|
dropbox: () => {
|
|
471
489
|
// Live (or about-to-be-live) process: the PreToolUse drain delivers it.
|
|
@@ -512,6 +530,7 @@ export function createLaneMessageHandlers(deps) {
|
|
|
512
530
|
pending_resume: {
|
|
513
531
|
session_id: sessionId,
|
|
514
532
|
trigger: "message",
|
|
533
|
+
caller: ctx.env.HQ_LANE_ID?.trim() || "user",
|
|
515
534
|
text: deliveryText,
|
|
516
535
|
...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
|
|
517
536
|
message_id: messageId,
|
|
@@ -523,7 +542,6 @@ export function createLaneMessageHandlers(deps) {
|
|
|
523
542
|
}
|
|
524
543
|
const started = spawnLaneWorker(hqRoot, lane, {
|
|
525
544
|
env: ctx.env,
|
|
526
|
-
managerStatusGateEnabled,
|
|
527
545
|
modelDeps: {
|
|
528
546
|
readFileSync: ctx.readFileSync,
|
|
529
547
|
probe: ctx.probeProvider,
|
|
@@ -624,6 +642,7 @@ export function createLaneMessageHandlers(deps) {
|
|
|
624
642
|
accepted: true,
|
|
625
643
|
delivered: false,
|
|
626
644
|
delivery: mechanism,
|
|
645
|
+
delivered_to: { host: lane.host.id, lane_id: laneId, mechanism },
|
|
627
646
|
delivery_status: deliveryStatus,
|
|
628
647
|
inbox_path: mechanism === "dropbox" ? dropboxInboxPath(hqRoot, laneId) : null,
|
|
629
648
|
drain_hook: drainHook,
|
|
@@ -5,7 +5,6 @@ import { parseCliEffort } from "../lib/lanes/effort.js";
|
|
|
5
5
|
import { parseOutpostHireGrantContext } from "../lib/lanes/hire-grant-dispatch.js";
|
|
6
6
|
import { assertNoSecretText } from "../lib/lanes/text-scan.js";
|
|
7
7
|
import { laneStories, laneStory, storyWarning } from "../lib/lanes/stories.js";
|
|
8
|
-
import { managerStatusGateEnabledForLane } from "../lib/lanes/manager-status-gate.js";
|
|
9
8
|
import { spawnLaneWorker } from "../lib/lanes/spawn.js";
|
|
10
9
|
import { accountMoveSummary, accountRequestKeepsCurrentLane, assertCurrentResumeAccountAvailable, hasKeepAliveProgress, recordedResumeEffort, resetKeepAliveProgress, resolveResumeEffort } from "./lanes-shared.js";
|
|
11
10
|
export function createLaneResumeHandlers(deps) {
|
|
@@ -328,7 +327,6 @@ export function createLaneResumeHandlers(deps) {
|
|
|
328
327
|
resumed = lane;
|
|
329
328
|
}
|
|
330
329
|
else {
|
|
331
|
-
const managerStatusGateEnabled = await managerStatusGateEnabledForLane(hqRoot, lane, { env: ctx.env });
|
|
332
330
|
resumed = await withLaneAdmissionLock(hqRoot, lane.provider, async () => {
|
|
333
331
|
const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
|
|
334
332
|
if (admission.action === "refuse" && admission.error)
|
|
@@ -361,6 +359,7 @@ export function createLaneResumeHandlers(deps) {
|
|
|
361
359
|
pending_resume: {
|
|
362
360
|
session_id: sessionId,
|
|
363
361
|
trigger: "resume",
|
|
362
|
+
caller: ctx.env.HQ_LANE_ID?.trim() || "user",
|
|
364
363
|
resumed_from: resumedFrom,
|
|
365
364
|
...(opts.fromFailed === true ? { from_failed: true } : {}),
|
|
366
365
|
...(failedResumeExpectedSenior !== undefined
|
|
@@ -376,7 +375,6 @@ export function createLaneResumeHandlers(deps) {
|
|
|
376
375
|
}
|
|
377
376
|
return spawnLaneWorker(hqRoot, lane, {
|
|
378
377
|
env: ctx.env,
|
|
379
|
-
managerStatusGateEnabled,
|
|
380
378
|
modelDeps: {
|
|
381
379
|
readFileSync: ctx.readFileSync,
|
|
382
380
|
probe: ctx.probeProvider,
|
|
@@ -157,6 +157,7 @@ export interface LaneMessageHandlerDeps {
|
|
|
157
157
|
}): Promise<AdmissionResult>;
|
|
158
158
|
casUpdateLane(hqRoot: string, laneId: string, updater: (current: LaneRecord) => LaneRecord): LaneRecord;
|
|
159
159
|
dispatchOutpostLaneCommand(ctx: Ctx, lane: LaneRecord, action: "message" | "resume", options: LaneMessageOptions | LaneResumeOptions, behavior?: OutpostLaneDispatchBehavior): Promise<void>;
|
|
160
|
+
dispatchPrimaryLaneMessage(ctx: Ctx, laneId: string, options: LaneMessageOptions): Promise<void>;
|
|
160
161
|
drainHookStatusFor(ctx: Ctx, lane: LaneRecord): {
|
|
161
162
|
registered: boolean;
|
|
162
163
|
kind: string;
|
|
@@ -248,8 +249,6 @@ export interface Ctx {
|
|
|
248
249
|
backfillOutpostIdentity: (expectedId: string) => "created" | "unchanged";
|
|
249
250
|
fetchText: (url: string) => Promise<string>;
|
|
250
251
|
lanePlacementEnabled: (company: LaneCompanyRef | undefined) => Promise<boolean>;
|
|
251
|
-
outpostSnapshotCacheEnabled: () => Promise<boolean>;
|
|
252
|
-
startOutpostLaneSnapshotRefresh: (hqRoot: string) => void;
|
|
253
252
|
storyLedgerEnabled: () => boolean;
|
|
254
253
|
isSsmOwnerDispatch: () => boolean;
|
|
255
254
|
withGrantHireLocks: typeof withLaneHireLocks;
|
|
@@ -8,6 +8,7 @@ export declare function createLaneStoryHandlers(deps: LaneStoryHandlerDeps): {
|
|
|
8
8
|
runStoryList: (ctx: Ctx, laneId: string) => Promise<void>;
|
|
9
9
|
runStoryAdd: (ctx: Ctx, laneId: string, storyId: string, opts: StoryCommandOptions) => Promise<void>;
|
|
10
10
|
runStoryStart: (ctx: Ctx, laneId: string, storyId: string, opts: StoryCommandOptions) => Promise<void>;
|
|
11
|
+
runStoryReview: (ctx: Ctx, laneId: string, storyId: string, opts: StoryCommandOptions) => Promise<void>;
|
|
11
12
|
runStoryBlock: (ctx: Ctx, laneId: string, storyId: string, opts: StoryCommandOptions) => Promise<void>;
|
|
12
13
|
runStoryDone: (ctx: Ctx, laneId: string, storyId: string, opts: StoryCommandOptions) => Promise<void>;
|
|
13
14
|
};
|
|
@@ -18,6 +18,13 @@ export function createLaneStoryHandlers(deps) {
|
|
|
18
18
|
}
|
|
19
19
|
return { projectId, storyId: normalizedStoryId, story };
|
|
20
20
|
}
|
|
21
|
+
function reviewableStoryTarget(lane, storyId, project) {
|
|
22
|
+
const target = storyCommandTarget(lane, storyId, project);
|
|
23
|
+
if (target.story.status === "done") {
|
|
24
|
+
throw new LanesError("story_already_done", `Story ${target.projectId}/${target.storyId} is done and cannot be moved to review`, { lane_id: lane.lane_id, project_id: target.projectId, story_id: target.storyId });
|
|
25
|
+
}
|
|
26
|
+
return target;
|
|
27
|
+
}
|
|
21
28
|
function updateCarriedStory(lane, projectId, storyId, status, at, extras = {}) {
|
|
22
29
|
const stories = laneStories(lane).map((story) => story.project_id === projectId && story.story_id === storyId
|
|
23
30
|
? {
|
|
@@ -229,6 +236,38 @@ export function createLaneStoryHandlers(deps) {
|
|
|
229
236
|
const finalLane = readLane(hqRoot, laneId);
|
|
230
237
|
printJson({ ok: true, action: "story.start", lane_id: laneId, project_id: target.projectId, story_id: target.storyId, status: "in_progress", story_warning: combinedStoryWarning(finalLane, warnings), lane: finalLane });
|
|
231
238
|
}
|
|
239
|
+
async function runStoryReview(ctx, laneId, storyId, opts) {
|
|
240
|
+
const hqRoot = ctx.hqRoot();
|
|
241
|
+
let lane = readLane(hqRoot, laneId);
|
|
242
|
+
assertLaneRunsHereForOperatorMutation(ctx, lane, "update story status for");
|
|
243
|
+
const target = reviewableStoryTarget(lane, storyId, opts.project);
|
|
244
|
+
const at = ctx.now().toISOString();
|
|
245
|
+
const warnings = [];
|
|
246
|
+
const thread = await ensureStoryThreadForTransition(ctx, lane, target.projectId, target.storyId);
|
|
247
|
+
lane = thread.lane;
|
|
248
|
+
if (thread.warning !== undefined)
|
|
249
|
+
warnings.push(thread.warning);
|
|
250
|
+
const before = lane;
|
|
251
|
+
const next = casUpdateLane(hqRoot, laneId, (current) => {
|
|
252
|
+
assertLaneRunsHereForOperatorMutation(ctx, current, "update story status for");
|
|
253
|
+
reviewableStoryTarget(current, target.storyId, target.projectId);
|
|
254
|
+
return updateCarriedStory(current, target.projectId, target.storyId, "review", at);
|
|
255
|
+
});
|
|
256
|
+
const transitionWarning = await commitStoryTransition(ctx, before, next, target.projectId, target.storyId, "lane_progress", "review", { detail: "Story moved to review" });
|
|
257
|
+
if (transitionWarning !== undefined)
|
|
258
|
+
warnings.push(transitionWarning);
|
|
259
|
+
const finalLane = readLane(hqRoot, laneId);
|
|
260
|
+
printJson({
|
|
261
|
+
ok: true,
|
|
262
|
+
action: "story.review",
|
|
263
|
+
lane_id: laneId,
|
|
264
|
+
project_id: target.projectId,
|
|
265
|
+
story_id: target.storyId,
|
|
266
|
+
status: "review",
|
|
267
|
+
story_warning: combinedStoryWarning(finalLane, warnings),
|
|
268
|
+
lane: finalLane,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
232
271
|
async function runStoryBlock(ctx, laneId, storyId, opts) {
|
|
233
272
|
const hqRoot = ctx.hqRoot();
|
|
234
273
|
let lane = readLane(hqRoot, laneId);
|
|
@@ -303,6 +342,6 @@ export function createLaneStoryHandlers(deps) {
|
|
|
303
342
|
const finalLane = readLane(hqRoot, laneId);
|
|
304
343
|
printJson({ ok: true, action: "story.done", lane_id: laneId, project_id: target.projectId, story_id: target.storyId, status: "done", verified: true, story_warning: combinedStoryWarning(finalLane, warnings), lane: finalLane });
|
|
305
344
|
}
|
|
306
|
-
return { runStoryList, runStoryAdd, runStoryStart, runStoryBlock, runStoryDone };
|
|
345
|
+
return { runStoryList, runStoryAdd, runStoryStart, runStoryReview, runStoryBlock, runStoryDone };
|
|
307
346
|
}
|
|
308
347
|
//# sourceMappingURL=lanes-stories.js.map
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -14,7 +14,6 @@ import type { OutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
|
14
14
|
export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
15
15
|
export type { OutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
16
16
|
import { type OutpostDispatchResult } from "../lib/lanes/outpost-dispatch.js";
|
|
17
|
-
import { LANE_OUTPOST_SNAPSHOT_CACHE_FLAG } from "../lib/lanes/outpost-hire-cache.js";
|
|
18
17
|
import { emitLaneDecisionEvents } from "../lib/lanes/mesh-events.js";
|
|
19
18
|
import { MAX_LANE_DEPTH, withLaneHireLocks, type LaneCompanyRef, type QuestionBody, type LaneRecord, type QuestionRecord, type ResolvedLaneWorker, type SeniorRef, type LivenessDeps, type MachineIdentity, type MonitorEvidenceReader, type RunCommand, type ThrottleDeps } from "../lib/lanes/index.js";
|
|
20
19
|
import { questionBodyAbsPath } from "../lib/lanes/questions.js";
|
|
@@ -62,7 +61,7 @@ export interface LanesCommandDeps {
|
|
|
62
61
|
/** Test seam for worker PID identity checks. */
|
|
63
62
|
readStarttime?: (pid: number) => number | null;
|
|
64
63
|
/** Test seams for synchronous dead-worker classification and repair. */
|
|
65
|
-
watcherLoopDeps?: Pick<WatcherLoopDeps, "sleep" | "spawnRepair"
|
|
64
|
+
watcherLoopDeps?: Pick<WatcherLoopDeps, "sleep" | "spawnRepair">;
|
|
66
65
|
stopSleepMs?: (ms: number) => void;
|
|
67
66
|
livenessDeps?: LivenessDeps;
|
|
68
67
|
presenceConnected?: boolean;
|
|
@@ -88,10 +87,6 @@ export interface LanesCommandDeps {
|
|
|
88
87
|
monitorEvidence?: MonitorEvidenceReader;
|
|
89
88
|
/** Test seam for the default-off outpost placement rollout flag. */
|
|
90
89
|
lanePlacementEnabled?: (company: LaneCompanyRef | undefined) => Promise<boolean>;
|
|
91
|
-
/** Test seam for the default-off shared Outpost snapshot cache. */
|
|
92
|
-
outpostSnapshotCacheEnabled?: () => Promise<boolean>;
|
|
93
|
-
/** Test seam for the detached refresh started by the cache-first lane list. */
|
|
94
|
-
startOutpostLaneSnapshotRefresh?: (hqRoot: string) => void;
|
|
95
90
|
/** Test seam for the default-off story ledger flag. */
|
|
96
91
|
storyLedgerEnabled?: () => boolean;
|
|
97
92
|
/** Test seam for the SSM-only Outpost owner grant path. */
|
|
@@ -103,7 +98,6 @@ export interface LanesCommandDeps {
|
|
|
103
98
|
/** Test seam for the repository-merge-hold registry flag. */
|
|
104
99
|
mergeHoldEnabled?: (company: LaneCompanyRef) => boolean | Promise<boolean>;
|
|
105
100
|
}
|
|
106
|
-
export { LANE_OUTPOST_SNAPSHOT_CACHE_FLAG };
|
|
107
101
|
export declare function parseSeniorRef(raw: string): SeniorRef;
|
|
108
102
|
export declare function parseQuestionBody(raw: unknown): QuestionBody | null;
|
|
109
103
|
/**
|
|
@@ -148,8 +142,6 @@ export declare function pickupFailureUpdater(reasonRaw: string, details: Record<
|
|
|
148
142
|
*/
|
|
149
143
|
export declare function applyOutpostAccountPickup(hqRoot: string, laneId: string, modelDeps?: KnownModelsDeps): LaneRecord;
|
|
150
144
|
export declare function cancelPendingQuestionsForLane(hqRoot: string, laneId: string, nowIso: string, operatorReason?: string): QuestionRecord[];
|
|
151
|
-
/** Keeps the placement-enabled cache-first path on the full lane command graph. */
|
|
152
|
-
export declare function runFastCachedLanesListFromEntrypoint(): Promise<void>;
|
|
153
145
|
export declare function stopCommittedState(state: LaneRecord["state"]): "cancelled" | "killed";
|
|
154
146
|
/** Stderr notice that `stop` ends a lane for good, naming the resumable path. */
|
|
155
147
|
export declare function stopTerminalWarning(laneId: string): string;
|