@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,301 @@
1
+ import { isTerminalLaneState } from "./types.js";
2
+ const MINUTE_MS = 60_000;
3
+ const CLOSED_STORY_STATES = new Set(["done", "dropped"]);
4
+ function timestamp(value) {
5
+ if (typeof value !== "string")
6
+ return null;
7
+ const parsed = Date.parse(value);
8
+ return Number.isFinite(parsed) ? parsed : null;
9
+ }
10
+ function round(value) {
11
+ return Math.round((value + Number.EPSILON) * 100) / 100;
12
+ }
13
+ function dayKey(at) {
14
+ return new Date(at).toISOString().slice(0, 10);
15
+ }
16
+ function utcDays(since, until) {
17
+ const days = [];
18
+ let current = Date.UTC(new Date(since).getUTCFullYear(), new Date(since).getUTCMonth(), new Date(since).getUTCDate());
19
+ const last = Date.UTC(new Date(until).getUTCFullYear(), new Date(until).getUTCMonth(), new Date(until).getUTCDate());
20
+ while (current <= last) {
21
+ days.push(new Date(current).toISOString().slice(0, 10));
22
+ current += 24 * 60 * MINUTE_MS;
23
+ }
24
+ return days;
25
+ }
26
+ function storyState(story) {
27
+ return typeof story.state === "string" ? story.state : typeof story.status === "string" ? story.status : undefined;
28
+ }
29
+ function storyPrReferences(story) {
30
+ const refs = [...(story.prs ?? [])];
31
+ if (!Array.isArray(story.lanes))
32
+ return refs;
33
+ for (const entry of story.lanes) {
34
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
35
+ continue;
36
+ const record = entry;
37
+ if (Array.isArray(record.prs))
38
+ refs.push(...record.prs);
39
+ if (Array.isArray(record.history)) {
40
+ for (const historyEntry of record.history) {
41
+ if (historyEntry && typeof historyEntry === "object" && !Array.isArray(historyEntry)) {
42
+ const prs = historyEntry.prs;
43
+ if (Array.isArray(prs))
44
+ refs.push(...prs);
45
+ }
46
+ }
47
+ }
48
+ }
49
+ return refs;
50
+ }
51
+ function prReferenceMatches(value, review) {
52
+ if (typeof value === "number")
53
+ return value === review.pr;
54
+ if (typeof value !== "string") {
55
+ if (!value || typeof value !== "object" || Array.isArray(value))
56
+ return false;
57
+ const record = value;
58
+ if (record.number === review.pr)
59
+ return true;
60
+ return [record.url, record.html_url, record.ref].some((ref) => prReferenceMatches(ref, review));
61
+ }
62
+ const text = value.trim();
63
+ if (text === `#${review.pr}` || text === String(review.pr))
64
+ return true;
65
+ const pull = text.match(/^https?:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)(?:\/|$)/i);
66
+ return pull !== null && pull[1]?.toLowerCase() === review.repo.toLowerCase() && Number(pull[2]) === review.pr;
67
+ }
68
+ function reviewMatchesLane(review, lane, story) {
69
+ if (review.worker_lane !== undefined && review.worker_lane !== null)
70
+ return review.worker_lane === lane.lane_id;
71
+ return [...(story ? storyPrReferences(story) : []), ...(lane.last_envelope?.artifacts ?? [])]
72
+ .some((ref) => prReferenceMatches(ref, review));
73
+ }
74
+ function matchingStory(project, storyId) {
75
+ return project?.stories.find((story) => story.id === storyId);
76
+ }
77
+ function stateAtHistoryEntry(entry) {
78
+ return typeof entry.state === "string" ? entry.state : undefined;
79
+ }
80
+ function storyLaneHistory(story, laneId) {
81
+ if (!story || !Array.isArray(story.lanes))
82
+ return [];
83
+ for (const value of story.lanes) {
84
+ if (!value || typeof value !== "object" || Array.isArray(value))
85
+ continue;
86
+ const entry = value;
87
+ if (entry.lane_id === laneId && Array.isArray(entry.history)) {
88
+ return entry.history.filter((history) => history !== null && typeof history === "object" && !Array.isArray(history));
89
+ }
90
+ }
91
+ return [];
92
+ }
93
+ function laneTerminalAt(lane) {
94
+ const ended = timestamp(lane.ended_at);
95
+ if (ended !== null)
96
+ return ended;
97
+ const envelopeAt = timestamp(lane.last_envelope?.at);
98
+ if (envelopeAt !== null)
99
+ return envelopeAt;
100
+ return isTerminalLaneState(lane.state) ? timestamp(lane.updated_at) : null;
101
+ }
102
+ function reviewCandidates(input, lead, lane, story) {
103
+ const candidates = [];
104
+ for (const review of input.reviews) {
105
+ const at = timestamp(review.recorded_at);
106
+ if (at !== null && reviewMatchesLane(review, lane, story))
107
+ candidates.push({ at, event: "review_record" });
108
+ }
109
+ for (const resume of input.seniorResumes) {
110
+ const at = timestamp(resume.at);
111
+ if (resume.lane_id === lead.lane_id && at !== null)
112
+ candidates.push({ at, event: "senior_resume" });
113
+ }
114
+ if (story && CLOSED_STORY_STATES.has(storyState(story) ?? "")) {
115
+ const at = timestamp(story.updated_at);
116
+ if (at !== null)
117
+ candidates.push({ at, event: "story_closed" });
118
+ }
119
+ return candidates;
120
+ }
121
+ function idleIntervalsForLane(lane, story, until) {
122
+ const created = timestamp(lane.created_at);
123
+ if (created === null)
124
+ return [];
125
+ const history = storyLaneHistory(story, lane.lane_id)
126
+ .map((entry) => ({ at: timestamp(typeof entry.at === "string" ? entry.at : undefined), state: stateAtHistoryEntry(entry), terminal: entry.terminal === true }))
127
+ .filter((entry) => entry.at !== null && entry.at >= created)
128
+ .sort((a, b) => a.at - b.at);
129
+ if (history.length === 0) {
130
+ const terminal = isTerminalLaneState(lane.state);
131
+ const ended = timestamp(lane.ended_at) ?? (terminal ? timestamp(lane.last_envelope?.at) ?? timestamp(lane.updated_at) : null) ?? until;
132
+ return ended > created ? [{ start: created, end: Math.min(ended, until) }] : [];
133
+ }
134
+ const intervals = [];
135
+ let active = true;
136
+ let start = created;
137
+ for (const event of history) {
138
+ const isTerminal = event.terminal || (event.state !== undefined && isTerminalLaneState(event.state));
139
+ if (isTerminal && active) {
140
+ if (event.at > start)
141
+ intervals.push({ start, end: event.at });
142
+ active = false;
143
+ }
144
+ else if (!isTerminal && !active) {
145
+ start = event.at;
146
+ active = true;
147
+ }
148
+ }
149
+ if (active) {
150
+ const terminal = isTerminalLaneState(lane.state);
151
+ const ended = timestamp(lane.ended_at) ?? (terminal ? timestamp(lane.last_envelope?.at) ?? timestamp(lane.updated_at) : null) ?? until;
152
+ if (ended > start)
153
+ intervals.push({ start, end: Math.min(ended, until) });
154
+ }
155
+ return intervals;
156
+ }
157
+ function overlaps(interval, start, end) {
158
+ return interval.start <= start && interval.end >= end;
159
+ }
160
+ function readyStoryIntervals(story, until) {
161
+ const created = timestamp(story.created_at);
162
+ const updated = timestamp(story.updated_at);
163
+ const state = storyState(story);
164
+ if (state === "todo") {
165
+ const start = updated ?? created;
166
+ return start !== null && start < until ? [{ start, end: until }] : [];
167
+ }
168
+ if (created !== null && updated !== null && updated > created)
169
+ return [{ start: created, end: updated }];
170
+ return [];
171
+ }
172
+ function idleSlotMinutes(input, lead, leadLane, project, children, start, end) {
173
+ if (!leadLane || project === undefined)
174
+ return 0;
175
+ const grant = Math.max(0, leadLane.hire_grant ?? 0);
176
+ if (grant === 0)
177
+ return 0;
178
+ const readyIntervals = project.stories.flatMap((story) => readyStoryIntervals(story, end));
179
+ if (readyIntervals.length === 0)
180
+ return 0;
181
+ const leadStory = matchingStory(project, leadLane.story_id);
182
+ const leadIntervals = idleIntervalsForLane(leadLane, leadStory, end);
183
+ if (leadIntervals.length === 0)
184
+ return 0;
185
+ const childIntervals = children.flatMap((child) => {
186
+ const story = matchingStory(project, child.story_id);
187
+ return idleIntervalsForLane(child, story, end);
188
+ });
189
+ const boundaries = new Set([start, end]);
190
+ for (const interval of [...childIntervals, ...readyIntervals, ...leadIntervals]) {
191
+ if (interval.start > start && interval.start < end)
192
+ boundaries.add(interval.start);
193
+ if (interval.end > start && interval.end < end)
194
+ boundaries.add(interval.end);
195
+ }
196
+ const sorted = [...boundaries].sort((a, b) => a - b);
197
+ let total = 0;
198
+ for (let index = 0; index < sorted.length - 1; index += 1) {
199
+ const segmentStart = sorted[index];
200
+ const segmentEnd = sorted[index + 1];
201
+ if (segmentStart === undefined || segmentEnd === undefined || segmentEnd <= segmentStart)
202
+ continue;
203
+ if (!readyIntervals.some((interval) => overlaps(interval, segmentStart, segmentEnd)))
204
+ continue;
205
+ if (!leadIntervals.some((interval) => overlaps(interval, segmentStart, segmentEnd)))
206
+ continue;
207
+ const live = childIntervals.filter((interval) => overlaps(interval, segmentStart, segmentEnd)).length;
208
+ total += Math.max(0, grant - live) * (segmentEnd - segmentStart) / MINUTE_MS;
209
+ }
210
+ return round(total);
211
+ }
212
+ export function buildAutopilotMetricsReport(input) {
213
+ const since = timestamp(input.since);
214
+ const until = timestamp(input.until);
215
+ if (since === null || until === null || since >= until) {
216
+ throw new Error("autopilot_metrics_window_invalid: since and until must be valid timestamps with since before until");
217
+ }
218
+ const days = utcDays(since, until);
219
+ const rows = new Map();
220
+ for (const lead of input.leads) {
221
+ for (const day of days) {
222
+ rows.set(`${lead.team}\u0000${day}`, {
223
+ team: lead.team,
224
+ day,
225
+ operator_nudges: 0,
226
+ reviewed_lanes: 0,
227
+ review_latency_minutes_total: 0,
228
+ review_latency_minutes_average: null,
229
+ idle_slot_minutes_ready_work: 0,
230
+ });
231
+ }
232
+ }
233
+ for (const nudge of input.nudges) {
234
+ const at = timestamp(nudge.at);
235
+ if (at === null || at < since || at > until)
236
+ continue;
237
+ const row = rows.get(`${nudge.team}\u0000${dayKey(at)}`);
238
+ if (row)
239
+ row.operator_nudges += 1;
240
+ }
241
+ const projectById = new Map(input.projects.map((project) => [project.project_id, project]));
242
+ const reviewLatencies = [];
243
+ for (const lead of input.leads) {
244
+ const leadLane = input.lanes.find((lane) => lane.lane_id === lead.lane_id);
245
+ const project = projectById.get(lead.project_id);
246
+ const children = input.lanes.filter((lane) => lane.lane_id !== lead.lane_id && lane.senior.kind === "lane" && lane.senior.id === lead.lane_id);
247
+ for (const lane of children) {
248
+ const finishedAt = laneTerminalAt(lane);
249
+ if (finishedAt === null || finishedAt < since || finishedAt > until)
250
+ continue;
251
+ const story = matchingStory(project, lane.story_id);
252
+ const first = reviewCandidates(input, lead, lane, story)
253
+ .filter((event) => event.at >= finishedAt && event.at <= until)
254
+ .sort((a, b) => a.at - b.at)[0];
255
+ if (!first)
256
+ continue;
257
+ reviewLatencies.push({
258
+ team: lead.team,
259
+ lane_id: lane.lane_id,
260
+ story_id: lane.story_id,
261
+ finished_at: new Date(finishedAt).toISOString(),
262
+ reviewed_at: new Date(first.at).toISOString(),
263
+ minutes: round((first.at - finishedAt) / MINUTE_MS),
264
+ end_event: first.event,
265
+ });
266
+ }
267
+ for (const day of days) {
268
+ const dayStart = Math.max(since, Date.parse(`${day}T00:00:00.000Z`));
269
+ const dayEnd = Math.min(until, Date.parse(`${day}T00:00:00.000Z`) + 24 * 60 * MINUTE_MS);
270
+ const idle = idleSlotMinutes(input, lead, leadLane, project, children, dayStart, dayEnd);
271
+ const row = rows.get(`${lead.team}\u0000${day}`);
272
+ if (row)
273
+ row.idle_slot_minutes_ready_work = round(row.idle_slot_minutes_ready_work + idle);
274
+ }
275
+ }
276
+ for (const latency of reviewLatencies) {
277
+ const row = rows.get(`${latency.team}\u0000${dayKey(Date.parse(latency.finished_at))}`);
278
+ if (!row)
279
+ continue;
280
+ row.reviewed_lanes += 1;
281
+ row.review_latency_minutes_total = round(row.review_latency_minutes_total + latency.minutes);
282
+ row.review_latency_minutes_average = round(row.review_latency_minutes_total / row.reviewed_lanes);
283
+ }
284
+ const selectedTeams = new Set(input.leads.map((lead) => lead.team));
285
+ const nudgeDefects = input.nudgeDefects
286
+ .filter((defect) => {
287
+ if (!selectedTeams.has(defect.team))
288
+ return false;
289
+ const at = timestamp(defect.at);
290
+ return at !== null && at >= since && at <= until;
291
+ })
292
+ .map((defect) => ({ ...defect, wake: null }));
293
+ return {
294
+ since: new Date(since).toISOString(),
295
+ until: new Date(until).toISOString(),
296
+ days: [...rows.values()].sort((a, b) => a.day.localeCompare(b.day) || a.team.localeCompare(b.team)),
297
+ review_latencies: reviewLatencies.sort((a, b) => a.finished_at.localeCompare(b.finished_at) || a.lane_id.localeCompare(b.lane_id)),
298
+ nudge_defects: nudgeDefects,
299
+ };
300
+ }
301
+ //# sourceMappingURL=autopilot-metrics.js.map
@@ -12,8 +12,9 @@ export interface RegisteredLead {
12
12
  status: string;
13
13
  prd: string;
14
14
  }
15
+ export type AutopilotCondition = "review_finished_worker" | "fill_grant" | "plan_free_slots" | "flat_kpi" | "blocker_record_needs_update" | "project_ready_to_close" | "stale_worker_ledger" | "story_without_live_lane";
15
16
  export interface AutopilotWake {
16
- condition: "review_finished_worker" | "fill_grant";
17
+ condition: AutopilotCondition;
17
18
  lead_lane: string;
18
19
  story_id: string;
19
20
  worker_lane?: string;
@@ -30,6 +31,14 @@ export interface AutopilotPassResult {
30
31
  story_id: string;
31
32
  worker_lane: string;
32
33
  }>;
34
+ escalated: Array<{
35
+ team: string;
36
+ lead_lane: string;
37
+ condition: AutopilotCondition;
38
+ story_id: string;
39
+ at: string;
40
+ fingerprint: string;
41
+ }>;
33
42
  skipped: Array<{
34
43
  lead_lane: string;
35
44
  reason: string;
@@ -38,15 +47,18 @@ export interface AutopilotPassResult {
38
47
  interface ProjectStory {
39
48
  id: string;
40
49
  status: string;
50
+ passes?: boolean;
41
51
  briefPath?: string;
42
52
  laneIds?: string[];
43
53
  updatedAt?: string;
54
+ externalBlocker?: string;
44
55
  }
45
56
  export declare function planLeadAutopilotWakes(input: {
46
57
  lead: LaneRecord;
47
58
  children: readonly LaneRecord[];
48
59
  stories: readonly ProjectStory[];
49
60
  now: Date;
61
+ managerStatus?: Record<string, unknown>;
50
62
  keepAliveResumed?: boolean;
51
63
  }): AutopilotWake[];
52
64
  export declare function runLeadAutopilotPass(input: {