@indigoai-us/hq-cli 5.293.0 → 5.295.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 +39 -0
- package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +5 -1
- package/dist/command-catalog.generated.d.ts +54 -33
- package/dist/command-catalog.generated.js +68 -42
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +76 -1
- package/dist/commands/core-command-catalog.js +3 -2
- package/dist/commands/core-delegate-w1b.d.ts +2 -0
- package/dist/commands/core-delegate-w1b.js +28 -2
- package/dist/commands/core-delegate.d.ts +3 -1
- package/dist/commands/core-delegate.js +35 -5
- package/dist/commands/core-handoff-fast.d.ts +18 -0
- package/dist/commands/core-handoff-fast.js +103 -0
- package/dist/commands/core-handoff-open-steps.d.ts +21 -0
- package/dist/commands/core-handoff-open-steps.js +264 -0
- package/dist/commands/core-handoff.d.ts +4 -2
- package/dist/commands/core-handoff.js +22 -249
- package/dist/commands/core-jobs-native.d.ts +2 -0
- package/dist/commands/core-jobs-native.js +22 -11
- package/dist/commands/core-knowledge-prefs.d.ts +2 -0
- package/dist/commands/core-knowledge-prefs.js +13 -1
- package/dist/commands/core-ontology-native.d.ts +2 -0
- package/dist/commands/core-ontology-native.js +22 -2
- package/dist/commands/core-policy.d.ts +2 -0
- package/dist/commands/core-policy.js +18 -1
- package/dist/commands/core.js +2 -1
- package/dist/commands/daemon.d.ts +2 -1
- package/dist/commands/daemon.js +22 -7
- package/dist/commands/doctor.js +0 -74
- package/dist/commands/lanes-autopilot.d.ts +11 -0
- package/dist/commands/lanes-autopilot.js +336 -0
- package/dist/commands/lanes-message.js +23 -5
- package/dist/commands/lanes-questions.js +8 -5
- package/dist/commands/lanes-resume.js +0 -3
- package/dist/commands/lanes-shared.d.ts +18 -2
- package/dist/commands/lanes-stories.d.ts +14 -0
- package/dist/commands/lanes-stories.js +308 -0
- package/dist/commands/lanes.d.ts +1 -9
- package/dist/commands/lanes.js +216 -481
- package/dist/fast-core.js +53 -25
- package/dist/fast-scaffold.d.ts +15 -7
- package/dist/fast-scaffold.js +167 -17
- 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/core-utils/worktree-resume.d.ts +2 -0
- package/dist/lib/core-utils/worktree-resume.js +16 -0
- package/dist/lib/daemon/migrate.d.ts +8 -1
- package/dist/lib/daemon/migrate.js +19 -7
- package/dist/lib/doctor/checks/claude-wiring.js +9 -0
- package/dist/lib/lanes/autopilot-metrics.d.ts +99 -0
- package/dist/lib/lanes/autopilot-metrics.js +301 -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/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 +44 -38
- package/dist/lib/lanes/spawn.d.ts +0 -2
- package/dist/lib/lanes/spawn.js +18 -5
- package/dist/lib/lanes/store.d.ts +2 -0
- package/dist/lib/lanes/store.js +54 -3
- package/dist/lib/lanes/types.d.ts +18 -1
- package/dist/lib/lanes/types.js +1 -1
- package/dist/lib/lanes/watcher.d.ts +4 -3
- package/dist/lib/lanes/watcher.js +102 -34
- package/dist/lib/mesh/live/daemon/run.js +18 -1
- package/dist/native-fast-route.d.ts +3 -1
- package/dist/native-fast-route.js +10 -8
- package/dist/utils/cognito-session.d.ts +6 -0
- package/dist/utils/cognito-session.js +9 -0
- package/dist/utils/contribution-table.d.ts +1 -1
- package/dist/utils/fast-stream-errors.d.ts +12 -0
- package/dist/utils/fast-stream-errors.js +18 -0
- package/dist/utils/scaffold-fast-import-guard.d.ts +2 -2
- package/dist/utils/scaffold-fast-import-guard.js +6 -3
- package/dist/utils/self-update.js +31 -0
- package/dist/utils/version-gate.js +22 -3
- package/package.json +1 -1
- 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
|
@@ -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)
|
|
@@ -376,7 +374,6 @@ export function createLaneResumeHandlers(deps) {
|
|
|
376
374
|
}
|
|
377
375
|
return spawnLaneWorker(hqRoot, lane, {
|
|
378
376
|
env: ctx.env,
|
|
379
|
-
managerStatusGateEnabled,
|
|
380
377
|
modelDeps: {
|
|
381
378
|
readFileSync: ctx.readFileSync,
|
|
382
379
|
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;
|
|
@@ -376,6 +375,7 @@ export interface LaneQuestionsHandlerDeps {
|
|
|
376
375
|
MAX_PLACEMENT_OUTPOSTS: number;
|
|
377
376
|
OUTPOST_QUESTION_LOOKUP_CONCURRENCY: number;
|
|
378
377
|
assertLaneRunsHereForOperatorMutation(ctx: Ctx, lane: LaneRecord, action: string): void;
|
|
378
|
+
assertQuestionAnswerAuthorized(ctx: Ctx, lane: LaneRecord): void;
|
|
379
379
|
assertQuestionCompanyMatchesLane(question: QuestionRecord, lane: Pick<LaneRecord, "lane_id" | "company" | "mesh_company" | "project_id">, code: string, hqRoot?: string): void;
|
|
380
380
|
casUpdateQuestion(hqRoot: string, questionId: string, updater: (current: QuestionRecord) => QuestionRecord): QuestionRecord;
|
|
381
381
|
effectiveLaneQuestionCompany(lane: Pick<LaneRecord, "company" | "mesh_company" | "project_id" | "lane_id">, hqRoot?: string): LaneCompanyRef | null;
|
|
@@ -407,4 +407,20 @@ export interface LaneQuestionsHandlerDeps {
|
|
|
407
407
|
seniorOfSession(hqRoot: string, sessionId: string, ctx: Ctx): SeniorRef;
|
|
408
408
|
writeAnswerDeliveryFailed(hqRoot: string, laneId: string, questionId: string, err: unknown, at: string): void;
|
|
409
409
|
}
|
|
410
|
+
export interface LaneStoryHandlerDeps {
|
|
411
|
+
assertLaneRunsHereForOperatorMutation(ctx: Ctx, lane: LaneRecord, action: string): void;
|
|
412
|
+
casUpdateLane(hqRoot: string, laneId: string, updater: (current: LaneRecord) => LaneRecord): LaneRecord;
|
|
413
|
+
combinedStoryWarning(lane: LaneRecord, additional?: readonly string[]): string | null;
|
|
414
|
+
emitLaneMeshEvent(ctx: Ctx, company: LaneCompanyRef | null | undefined, threadId: string | undefined, event: LaneMeshEvent, payload: Record<string, unknown>): Promise<void>;
|
|
415
|
+
ensureLaneThreadRecord(ctx: Ctx, lane: LaneRecord, projectId?: string, storyId?: string, laneStatus?: string): Promise<LaneRecord>;
|
|
416
|
+
printJson(value: unknown): void;
|
|
417
|
+
readLane(hqRoot: string, laneId: string): LaneRecord;
|
|
418
|
+
storyBoardCheck(ctx: Ctx, company: LaneCompanyRef, projectId: string, storyIds: readonly string[]): Promise<{
|
|
419
|
+
warning?: string;
|
|
420
|
+
absentStoryIds: ReadonlySet<string>;
|
|
421
|
+
}>;
|
|
422
|
+
storyNotOnBoardWarning(err: unknown): string | undefined;
|
|
423
|
+
updateLane(hqRoot: string, laneId: string, expectedVersion: number, source: "command", updater: (current: LaneRecord) => LaneRecord): LaneRecord;
|
|
424
|
+
validateProjectStories(hqRoot: string, projectId: string, storyIds: string[], companySlug?: string): void;
|
|
425
|
+
}
|
|
410
426
|
//# sourceMappingURL=lanes-shared.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Ctx, LaneStoryHandlerDeps } from "./lanes-shared.js";
|
|
2
|
+
export interface StoryCommandOptions {
|
|
3
|
+
project?: string;
|
|
4
|
+
reason?: string;
|
|
5
|
+
verified?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function createLaneStoryHandlers(deps: LaneStoryHandlerDeps): {
|
|
8
|
+
runStoryList: (ctx: Ctx, laneId: string) => Promise<void>;
|
|
9
|
+
runStoryAdd: (ctx: Ctx, laneId: string, storyId: string, opts: StoryCommandOptions) => Promise<void>;
|
|
10
|
+
runStoryStart: (ctx: Ctx, laneId: string, storyId: string, opts: StoryCommandOptions) => Promise<void>;
|
|
11
|
+
runStoryBlock: (ctx: Ctx, laneId: string, storyId: string, opts: StoryCommandOptions) => Promise<void>;
|
|
12
|
+
runStoryDone: (ctx: Ctx, laneId: string, storyId: string, opts: StoryCommandOptions) => Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=lanes-stories.d.ts.map
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { activeLaneStories, laneStories, laneStory, storyWarning } from "../lib/lanes/stories.js";
|
|
2
|
+
import { LanesError } from "../lib/lanes/errors.js";
|
|
3
|
+
import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
|
|
4
|
+
export function createLaneStoryHandlers(deps) {
|
|
5
|
+
const { assertLaneRunsHereForOperatorMutation, casUpdateLane, combinedStoryWarning, emitLaneMeshEvent, ensureLaneThreadRecord, printJson, readLane, storyBoardCheck, storyNotOnBoardWarning, updateLane, validateProjectStories, } = deps;
|
|
6
|
+
function storyCommandTarget(lane, storyId, project) {
|
|
7
|
+
const projectId = project?.trim() || lane.project_id;
|
|
8
|
+
const normalizedStoryId = storyId.trim();
|
|
9
|
+
if (!projectId) {
|
|
10
|
+
throw new LanesError("missing_project", "--project is required", {});
|
|
11
|
+
}
|
|
12
|
+
if (!normalizedStoryId) {
|
|
13
|
+
throw new LanesError("missing_story", "<story-id> is required", {});
|
|
14
|
+
}
|
|
15
|
+
const story = laneStory(lane, projectId, normalizedStoryId);
|
|
16
|
+
if (!story) {
|
|
17
|
+
throw new LanesError("story_not_on_lane", `Story ${projectId}/${normalizedStoryId} is not carried by lane ${lane.lane_id}`, { lane_id: lane.lane_id, project_id: projectId, story_id: normalizedStoryId });
|
|
18
|
+
}
|
|
19
|
+
return { projectId, storyId: normalizedStoryId, story };
|
|
20
|
+
}
|
|
21
|
+
function updateCarriedStory(lane, projectId, storyId, status, at, extras = {}) {
|
|
22
|
+
const stories = laneStories(lane).map((story) => story.project_id === projectId && story.story_id === storyId
|
|
23
|
+
? {
|
|
24
|
+
...story,
|
|
25
|
+
status,
|
|
26
|
+
updated_at: at,
|
|
27
|
+
blocked_reason: extras.blocked_reason,
|
|
28
|
+
verified_at: extras.verified_at,
|
|
29
|
+
verified_by: extras.verified_by,
|
|
30
|
+
}
|
|
31
|
+
: story);
|
|
32
|
+
return {
|
|
33
|
+
...lane,
|
|
34
|
+
project_id: projectId,
|
|
35
|
+
story_id: storyId,
|
|
36
|
+
stories,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function addCarriedStory(lane, projectId, storyId, at) {
|
|
40
|
+
if (laneStory(lane, projectId, storyId)) {
|
|
41
|
+
throw new LanesError("story_already_on_lane", `Story ${projectId}/${storyId} is already carried by lane ${lane.lane_id}`, {
|
|
42
|
+
lane_id: lane.lane_id,
|
|
43
|
+
project_id: projectId,
|
|
44
|
+
story_id: storyId,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
...lane,
|
|
49
|
+
...(lane.story_id.trim() ? {} : { project_id: projectId, story_id: storyId }),
|
|
50
|
+
stories: [
|
|
51
|
+
...laneStories(lane),
|
|
52
|
+
{ project_id: projectId, story_id: storyId, status: "in_progress", added_at: at, updated_at: at },
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function callerMayVerifyStory(ctx, lane) {
|
|
57
|
+
const callerLaneId = ctx.env.HQ_LANE_ID?.trim();
|
|
58
|
+
if (callerLaneId) {
|
|
59
|
+
return lane.senior.kind === "lane" && lane.senior.id === callerLaneId;
|
|
60
|
+
}
|
|
61
|
+
if (lane.senior.kind === "user")
|
|
62
|
+
return true;
|
|
63
|
+
if (lane.senior.kind === "lane")
|
|
64
|
+
return false;
|
|
65
|
+
return sessionIdFromEnv(ctx.env) === lane.senior.id;
|
|
66
|
+
}
|
|
67
|
+
async function ensureStoryThread(ctx, lane, projectId, storyId = lane.story_id) {
|
|
68
|
+
const company = lane.mesh_company ?? lane.company;
|
|
69
|
+
if (!company)
|
|
70
|
+
return lane;
|
|
71
|
+
return ensureLaneThreadRecord(ctx, lane, projectId, storyId);
|
|
72
|
+
}
|
|
73
|
+
async function ensureStoryThreadForTransition(ctx, lane, projectId, storyId) {
|
|
74
|
+
try {
|
|
75
|
+
return { lane: await ensureStoryThread(ctx, lane, projectId, storyId) };
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
const warning = storyNotOnBoardWarning(err);
|
|
79
|
+
if (warning === undefined)
|
|
80
|
+
throw err;
|
|
81
|
+
return { lane, warning };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function emitStoryTransition(ctx, lane, projectId, storyId, event, status, payload = {}, skipStoryStatusPatch = false) {
|
|
85
|
+
const company = lane.mesh_company ?? lane.company;
|
|
86
|
+
if (!company)
|
|
87
|
+
return;
|
|
88
|
+
const ready = await ensureStoryThread(ctx, lane, projectId, storyId);
|
|
89
|
+
if (!ready.thread_id)
|
|
90
|
+
return;
|
|
91
|
+
await emitLaneMeshEvent(ctx, company, ready.thread_id, event, {
|
|
92
|
+
lane_id: ready.lane_id,
|
|
93
|
+
project_id: projectId,
|
|
94
|
+
story_id: storyId,
|
|
95
|
+
...payload,
|
|
96
|
+
lane_status: lane.state,
|
|
97
|
+
});
|
|
98
|
+
if (!skipStoryStatusPatch && projectId?.trim() && storyId?.trim()) {
|
|
99
|
+
await ctx.mesh.patchStoryStatus(company, projectId, storyId, status === "blocked" ? "in_progress" : status);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function sameCommittedStory(current, expected, projectId, storyId) {
|
|
103
|
+
const currentStory = laneStory(current, projectId, storyId);
|
|
104
|
+
const expectedStory = laneStory(expected, projectId, storyId);
|
|
105
|
+
return (current.project_id === expected.project_id &&
|
|
106
|
+
current.story_id === expected.story_id &&
|
|
107
|
+
JSON.stringify(currentStory) === JSON.stringify(expectedStory));
|
|
108
|
+
}
|
|
109
|
+
async function commitStoryTransition(ctx, before, next, projectId, storyId, event, status, payload = {}, skipStoryStatusPatch = false) {
|
|
110
|
+
try {
|
|
111
|
+
await emitStoryTransition(ctx, next, projectId, storyId, event, status, payload, skipStoryStatusPatch);
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
const warning = storyNotOnBoardWarning(err);
|
|
116
|
+
if (warning !== undefined)
|
|
117
|
+
return warning;
|
|
118
|
+
try {
|
|
119
|
+
const current = readLane(ctx.hqRoot(), next.lane_id);
|
|
120
|
+
if (!sameCommittedStory(current, next, projectId, storyId)) {
|
|
121
|
+
throw new LanesError("story_transition_rollback_conflict", `Story ${projectId}/${storyId} changed while mesh publication failed`, { lane_id: next.lane_id, project_id: projectId, story_id: storyId });
|
|
122
|
+
}
|
|
123
|
+
updateLane(ctx.hqRoot(), next.lane_id, current.version, "command", (latest) => {
|
|
124
|
+
if (!sameCommittedStory(latest, next, projectId, storyId)) {
|
|
125
|
+
throw new LanesError("story_transition_rollback_conflict", `Story ${projectId}/${storyId} changed before rollback`, { lane_id: next.lane_id, project_id: projectId, story_id: storyId });
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
...latest,
|
|
129
|
+
project_id: before.project_id,
|
|
130
|
+
story_id: before.story_id,
|
|
131
|
+
stories: before.stories,
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (rollbackErr) {
|
|
136
|
+
process.stderr.write(`hq lanes: story transition rollback failed for ${next.lane_id}/${projectId}/${storyId}: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}\n`);
|
|
137
|
+
}
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function runStoryList(ctx, laneId) {
|
|
142
|
+
const lane = readLane(ctx.hqRoot(), laneId);
|
|
143
|
+
printJson({
|
|
144
|
+
ok: true,
|
|
145
|
+
action: "story.list",
|
|
146
|
+
lane_id: laneId,
|
|
147
|
+
stories: laneStories(lane),
|
|
148
|
+
active_stories: activeLaneStories(lane),
|
|
149
|
+
story_warning: storyWarning(lane) ?? null,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
async function runStoryAdd(ctx, laneId, storyId, opts) {
|
|
153
|
+
const hqRoot = ctx.hqRoot();
|
|
154
|
+
let lane = readLane(hqRoot, laneId);
|
|
155
|
+
assertLaneRunsHereForOperatorMutation(ctx, lane, "update story status for");
|
|
156
|
+
const projectId = opts.project?.trim() || lane.project_id;
|
|
157
|
+
if (!projectId)
|
|
158
|
+
throw new LanesError("missing_project", "--project is required", {});
|
|
159
|
+
const normalizedStoryId = storyId.trim();
|
|
160
|
+
if (!normalizedStoryId)
|
|
161
|
+
throw new LanesError("missing_story", "<story-id> is required", {});
|
|
162
|
+
if (ctx.storyLedgerEnabled()) {
|
|
163
|
+
validateProjectStories(hqRoot, projectId, [normalizedStoryId], lane.company?.slug);
|
|
164
|
+
}
|
|
165
|
+
const company = lane.mesh_company ?? lane.company;
|
|
166
|
+
const warnings = [];
|
|
167
|
+
let storyKnownAbsent = false;
|
|
168
|
+
if (company) {
|
|
169
|
+
const boardCheck = await storyBoardCheck(ctx, company, projectId, [normalizedStoryId]);
|
|
170
|
+
if (boardCheck.warning !== undefined)
|
|
171
|
+
warnings.push(boardCheck.warning);
|
|
172
|
+
storyKnownAbsent = boardCheck.absentStoryIds.has(normalizedStoryId);
|
|
173
|
+
}
|
|
174
|
+
const at = ctx.now().toISOString();
|
|
175
|
+
const thread = await ensureStoryThreadForTransition(ctx, lane, projectId, normalizedStoryId);
|
|
176
|
+
lane = thread.lane;
|
|
177
|
+
if (thread.warning !== undefined)
|
|
178
|
+
warnings.push(thread.warning);
|
|
179
|
+
const before = lane;
|
|
180
|
+
const next = casUpdateLane(hqRoot, laneId, (current) => {
|
|
181
|
+
assertLaneRunsHereForOperatorMutation(ctx, current, "update story status for");
|
|
182
|
+
const added = addCarriedStory(current, projectId, normalizedStoryId, at);
|
|
183
|
+
return {
|
|
184
|
+
...added,
|
|
185
|
+
no_story_reason: undefined,
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
const transitionWarning = await commitStoryTransition(ctx, before, next, projectId, normalizedStoryId, "lane_started", "in_progress", {}, storyKnownAbsent);
|
|
189
|
+
if (transitionWarning !== undefined)
|
|
190
|
+
warnings.push(transitionWarning);
|
|
191
|
+
const finalLane = readLane(hqRoot, laneId);
|
|
192
|
+
printJson({
|
|
193
|
+
ok: true,
|
|
194
|
+
action: "story.add",
|
|
195
|
+
lane_id: laneId,
|
|
196
|
+
project_id: projectId,
|
|
197
|
+
story_id: normalizedStoryId,
|
|
198
|
+
status: "in_progress",
|
|
199
|
+
story_warning: combinedStoryWarning(finalLane, warnings),
|
|
200
|
+
lane: finalLane,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async function runStoryStart(ctx, laneId, storyId, opts) {
|
|
204
|
+
const hqRoot = ctx.hqRoot();
|
|
205
|
+
let lane = readLane(hqRoot, laneId);
|
|
206
|
+
assertLaneRunsHereForOperatorMutation(ctx, lane, "update story status for");
|
|
207
|
+
const target = storyCommandTarget(lane, storyId, opts.project);
|
|
208
|
+
if (target.story.status === "done") {
|
|
209
|
+
throw new LanesError("story_already_done", `Story ${target.projectId}/${target.storyId} is done and cannot be started again`, {
|
|
210
|
+
lane_id: laneId,
|
|
211
|
+
project_id: target.projectId,
|
|
212
|
+
story_id: target.storyId,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const at = ctx.now().toISOString();
|
|
216
|
+
const warnings = [];
|
|
217
|
+
const thread = await ensureStoryThreadForTransition(ctx, lane, target.projectId, target.storyId);
|
|
218
|
+
lane = thread.lane;
|
|
219
|
+
if (thread.warning !== undefined)
|
|
220
|
+
warnings.push(thread.warning);
|
|
221
|
+
const before = lane;
|
|
222
|
+
const next = casUpdateLane(hqRoot, laneId, (current) => {
|
|
223
|
+
assertLaneRunsHereForOperatorMutation(ctx, current, "update story status for");
|
|
224
|
+
return updateCarriedStory(current, target.projectId, target.storyId, "in_progress", at);
|
|
225
|
+
});
|
|
226
|
+
const transitionWarning = await commitStoryTransition(ctx, before, next, target.projectId, target.storyId, "lane_started", "in_progress");
|
|
227
|
+
if (transitionWarning !== undefined)
|
|
228
|
+
warnings.push(transitionWarning);
|
|
229
|
+
const finalLane = readLane(hqRoot, laneId);
|
|
230
|
+
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
|
+
}
|
|
232
|
+
async function runStoryBlock(ctx, laneId, storyId, opts) {
|
|
233
|
+
const hqRoot = ctx.hqRoot();
|
|
234
|
+
let lane = readLane(hqRoot, laneId);
|
|
235
|
+
assertLaneRunsHereForOperatorMutation(ctx, lane, "update story status for");
|
|
236
|
+
const target = storyCommandTarget(lane, storyId, opts.project);
|
|
237
|
+
if (target.story.status === "done") {
|
|
238
|
+
throw new LanesError("story_already_done", `Story ${target.projectId}/${target.storyId} is done and cannot be blocked again`, {
|
|
239
|
+
lane_id: laneId,
|
|
240
|
+
project_id: target.projectId,
|
|
241
|
+
story_id: target.storyId,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
const reason = opts.reason?.trim();
|
|
245
|
+
if (!reason)
|
|
246
|
+
throw new LanesError("missing_block_reason", "--reason is required", {});
|
|
247
|
+
const at = ctx.now().toISOString();
|
|
248
|
+
const warnings = [];
|
|
249
|
+
const thread = await ensureStoryThreadForTransition(ctx, lane, target.projectId, target.storyId);
|
|
250
|
+
lane = thread.lane;
|
|
251
|
+
if (thread.warning !== undefined)
|
|
252
|
+
warnings.push(thread.warning);
|
|
253
|
+
const before = lane;
|
|
254
|
+
const next = casUpdateLane(hqRoot, laneId, (current) => {
|
|
255
|
+
assertLaneRunsHereForOperatorMutation(ctx, current, "update story status for");
|
|
256
|
+
return updateCarriedStory(current, target.projectId, target.storyId, "blocked", at, { blocked_reason: reason });
|
|
257
|
+
});
|
|
258
|
+
const transitionWarning = await commitStoryTransition(ctx, before, next, target.projectId, target.storyId, "lane_blocked", "blocked", { blocked_reason: reason });
|
|
259
|
+
if (transitionWarning !== undefined)
|
|
260
|
+
warnings.push(transitionWarning);
|
|
261
|
+
const finalLane = readLane(hqRoot, laneId);
|
|
262
|
+
printJson({ ok: true, action: "story.block", lane_id: laneId, project_id: target.projectId, story_id: target.storyId, status: "blocked", blocked_reason: reason, story_warning: combinedStoryWarning(finalLane, warnings), lane: finalLane });
|
|
263
|
+
}
|
|
264
|
+
async function runStoryDone(ctx, laneId, storyId, opts) {
|
|
265
|
+
const hqRoot = ctx.hqRoot();
|
|
266
|
+
let lane = readLane(hqRoot, laneId);
|
|
267
|
+
assertLaneRunsHereForOperatorMutation(ctx, lane, "update story status for");
|
|
268
|
+
if (!opts.verified) {
|
|
269
|
+
throw new LanesError("senior_verification_required", "Only a senior may mark a story done; pass --verified after reviewing or merging it", { lane_id: laneId });
|
|
270
|
+
}
|
|
271
|
+
if (!callerMayVerifyStory(ctx, lane)) {
|
|
272
|
+
throw new LanesError("senior_verification_required", `Caller is not the senior for lane ${laneId}`, {
|
|
273
|
+
lane_id: laneId,
|
|
274
|
+
senior: lane.senior,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
const target = storyCommandTarget(lane, storyId, opts.project);
|
|
278
|
+
if (target.story.status !== "review") {
|
|
279
|
+
throw new LanesError("story_not_ready_for_verification", `Story ${target.projectId}/${target.storyId} must be in review before senior verification`, {
|
|
280
|
+
lane_id: laneId,
|
|
281
|
+
project_id: target.projectId,
|
|
282
|
+
story_id: target.storyId,
|
|
283
|
+
status: target.story.status,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
const at = ctx.now().toISOString();
|
|
287
|
+
const warnings = [];
|
|
288
|
+
const thread = await ensureStoryThreadForTransition(ctx, lane, target.projectId, target.storyId);
|
|
289
|
+
lane = thread.lane;
|
|
290
|
+
if (thread.warning !== undefined)
|
|
291
|
+
warnings.push(thread.warning);
|
|
292
|
+
const before = lane;
|
|
293
|
+
const next = casUpdateLane(hqRoot, laneId, (current) => {
|
|
294
|
+
assertLaneRunsHereForOperatorMutation(ctx, current, "update story status for");
|
|
295
|
+
return updateCarriedStory(current, target.projectId, target.storyId, "done", at, {
|
|
296
|
+
verified_at: at,
|
|
297
|
+
verified_by: current.senior,
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
const transitionWarning = await commitStoryTransition(ctx, before, next, target.projectId, target.storyId, "lane_done", "done", { summary: "Senior verification complete" });
|
|
301
|
+
if (transitionWarning !== undefined)
|
|
302
|
+
warnings.push(transitionWarning);
|
|
303
|
+
const finalLane = readLane(hqRoot, laneId);
|
|
304
|
+
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
|
+
}
|
|
306
|
+
return { runStoryList, runStoryAdd, runStoryStart, runStoryBlock, runStoryDone };
|
|
307
|
+
}
|
|
308
|
+
//# 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;
|