@indigoai-us/hq-cli 5.292.0 → 5.294.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +5 -1
  3. package/dist/command-catalog.generated.d.ts +45 -24
  4. package/dist/command-catalog.generated.js +57 -30
  5. package/dist/commands/core-command-catalog.js +3 -2
  6. package/dist/commands/core-delegate-w1b.d.ts +2 -0
  7. package/dist/commands/core-delegate-w1b.js +28 -2
  8. package/dist/commands/core-delegate.d.ts +3 -1
  9. package/dist/commands/core-delegate.js +35 -5
  10. package/dist/commands/core-handoff-fast.d.ts +18 -0
  11. package/dist/commands/core-handoff-fast.js +103 -0
  12. package/dist/commands/core-handoff-open-steps.d.ts +21 -0
  13. package/dist/commands/core-handoff-open-steps.js +264 -0
  14. package/dist/commands/core-handoff.d.ts +4 -2
  15. package/dist/commands/core-handoff.js +22 -249
  16. package/dist/commands/core-jobs-native.d.ts +2 -0
  17. package/dist/commands/core-jobs-native.js +22 -11
  18. package/dist/commands/core-knowledge-prefs.d.ts +2 -0
  19. package/dist/commands/core-knowledge-prefs.js +13 -1
  20. package/dist/commands/core-ontology-native.d.ts +2 -0
  21. package/dist/commands/core-ontology-native.js +22 -2
  22. package/dist/commands/core-policy.d.ts +2 -0
  23. package/dist/commands/core-policy.js +18 -1
  24. package/dist/commands/core.js +2 -1
  25. package/dist/commands/daemon.d.ts +2 -1
  26. package/dist/commands/daemon.js +22 -7
  27. package/dist/commands/lanes-autopilot.d.ts +11 -0
  28. package/dist/commands/lanes-autopilot.js +336 -0
  29. package/dist/commands/lanes-move.js +54 -1
  30. package/dist/commands/lanes-questions.js +12 -5
  31. package/dist/commands/lanes-shared.d.ts +17 -0
  32. package/dist/commands/lanes-stories.d.ts +14 -0
  33. package/dist/commands/lanes-stories.js +308 -0
  34. package/dist/commands/lanes.d.ts +2 -0
  35. package/dist/commands/lanes.js +158 -314
  36. package/dist/fast-core.js +53 -25
  37. package/dist/fast-scaffold.d.ts +15 -2
  38. package/dist/fast-scaffold.js +167 -7
  39. package/dist/lib/core-utils/sentry-report.js +2 -2
  40. package/dist/lib/core-utils/worktree-resume.d.ts +2 -0
  41. package/dist/lib/core-utils/worktree-resume.js +16 -0
  42. package/dist/lib/daemon/migrate.d.ts +8 -1
  43. package/dist/lib/daemon/migrate.js +19 -7
  44. package/dist/lib/daemon/run.d.ts +11 -0
  45. package/dist/lib/daemon/run.js +17 -1
  46. package/dist/lib/lanes/autopilot-metrics.d.ts +99 -0
  47. package/dist/lib/lanes/autopilot-metrics.js +301 -0
  48. package/dist/lib/lanes/autopilot.d.ts +13 -1
  49. package/dist/lib/lanes/autopilot.js +271 -28
  50. package/dist/lib/lanes/grants.d.ts +1 -1
  51. package/dist/lib/lanes/grants.js +7 -5
  52. package/dist/lib/lanes/index.d.ts +1 -1
  53. package/dist/lib/lanes/keep-alive.d.ts +5 -0
  54. package/dist/lib/lanes/keep-alive.js +9 -4
  55. package/dist/lib/lanes/questions.d.ts +2 -1
  56. package/dist/lib/lanes/questions.js +2 -0
  57. package/dist/lib/lanes/reconcile.js +186 -9
  58. package/dist/lib/lanes/spawn.js +4 -0
  59. package/dist/lib/lanes/store.d.ts +2 -0
  60. package/dist/lib/lanes/store.js +53 -4
  61. package/dist/lib/lanes/types.d.ts +13 -1
  62. package/dist/lib/lanes/types.js +2 -2
  63. package/dist/lib/lanes/watcher.js +3 -1
  64. package/dist/native-fast-route.d.ts +3 -1
  65. package/dist/native-fast-route.js +10 -8
  66. package/dist/utils/contribution-table.d.ts +1 -1
  67. package/dist/utils/fast-stream-errors.d.ts +12 -0
  68. package/dist/utils/fast-stream-errors.js +18 -0
  69. package/dist/utils/scaffold-fast-import-guard.d.ts +2 -2
  70. package/dist/utils/scaffold-fast-import-guard.js +6 -3
  71. package/dist/utils/self-update.d.ts +7 -0
  72. package/dist/utils/self-update.js +5 -1
  73. package/dist/utils/version-gate.d.ts +15 -0
  74. package/dist/utils/version-gate.js +46 -2
  75. package/package.json +1 -1
@@ -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
@@ -114,6 +114,8 @@ export declare function parseQuestionBody(raw: unknown): QuestionBody | null;
114
114
  */
115
115
  export declare const ASK_BACKOFF_MS: readonly [5000, 15000];
116
116
  export declare const ASK_ATTEMPTS: number;
117
+ /** Flip to true in the next release to make --priority mandatory. */
118
+ export declare const ASK_PRIORITY_REQUIRED = false;
117
119
  export declare function casUpdateQuestion(hqRoot: string, questionId: string, updater: (current: QuestionRecord) => QuestionRecord): QuestionRecord;
118
120
  export declare function casUpdateLane(hqRoot: string, laneId: string, updater: (current: LaneRecord) => LaneRecord): LaneRecord;
119
121
  /**