@indigoai-us/hq-cli 5.226.0 → 5.227.1
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 +15 -0
- package/dist/command-catalog.generated.d.ts +6 -0
- package/dist/command-catalog.generated.js +8 -0
- package/dist/commands/agents.js +2 -1
- package/dist/commands/files-browse.js +7 -4
- package/dist/commands/files.js +13 -8
- package/dist/commands/groups.js +6 -5
- package/dist/commands/lanes-checks.d.ts +2 -0
- package/dist/commands/lanes-checks.js +13 -11
- package/dist/commands/lanes-import.d.ts +1 -0
- package/dist/commands/lanes-import.js +12 -11
- package/dist/commands/lanes-report.js +21 -14
- package/dist/commands/lanes-tracker.d.ts +1 -0
- package/dist/commands/lanes-tracker.js +39 -31
- package/dist/commands/lanes.d.ts +3 -0
- package/dist/commands/lanes.js +320 -2
- package/dist/lib/checkpoint-ontology/plan.d.ts +4 -6
- package/dist/lib/checkpoint-ontology/plan.js +8 -26
- package/dist/lib/lanes/reconcile.d.ts +2 -0
- package/dist/lib/lanes/reconcile.js +16 -0
- package/dist/lib/lanes/store.js +9 -1
- package/dist/lib/lanes/tracker-mesh-questions.d.ts +54 -0
- package/dist/lib/lanes/tracker-mesh-questions.js +138 -0
- package/dist/lib/lanes/tracker-mesh.d.ts +19 -0
- package/dist/lib/lanes/tracker-mesh.js +228 -0
- package/dist/lib/lanes/tracker-store.d.ts +13 -2
- package/dist/lib/lanes/tracker-store.js +1 -1
- package/dist/lib/lanes/types.d.ts +4 -0
- package/dist/utils/table-width.d.ts +7 -0
- package/dist/utils/table-width.js +15 -0
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import { LanesError } from "../lib/lanes/errors.js";
|
|
|
6
6
|
import { resolveProjectStoryLedger } from "../lib/lanes/story-ledger.js";
|
|
7
7
|
import { assertNoSecretText } from "../lib/lanes/text-scan.js";
|
|
8
8
|
import { createLocalTrackerStore } from "../lib/lanes/tracker-store.js";
|
|
9
|
+
import { createWorkMeshTrackerStore } from "../lib/lanes/tracker-mesh.js";
|
|
9
10
|
import { peekIdToken } from "../utils/id-token.js";
|
|
10
11
|
export const LANES_TRACKER_FLAG = "lanes.tracker";
|
|
11
12
|
export const LANES_TRACKER_MESH_FLAG = "lanes.tracker-mesh";
|
|
@@ -72,9 +73,7 @@ async function target(deps, options) {
|
|
|
72
73
|
if (!await deps.trackerEnabled(company)) {
|
|
73
74
|
fail("tracker_disabled", "hq lanes tracker commands require the lanes.tracker flag", { company: company.slug });
|
|
74
75
|
}
|
|
75
|
-
|
|
76
|
-
fail("tracker_mesh_unavailable", "Work Mesh tracker storage is not available in this CLI version", { company: company.slug });
|
|
77
|
-
}
|
|
76
|
+
const useMesh = await deps.trackerMeshEnabled(company);
|
|
78
77
|
const project = validSegment(options.project, "project");
|
|
79
78
|
const ledger = resolveProjectStoryLedger(deps.hqRoot(), project, company.slug);
|
|
80
79
|
const projectCompany = readProjectCompany(ledger.path);
|
|
@@ -106,7 +105,10 @@ async function target(deps, options) {
|
|
|
106
105
|
return {
|
|
107
106
|
company,
|
|
108
107
|
project,
|
|
109
|
-
|
|
108
|
+
usesMesh: useMesh,
|
|
109
|
+
store: useMesh
|
|
110
|
+
? (deps.meshStore ?? ((resolvedCompany, resolvedProject) => createWorkMeshTrackerStore({ company: resolvedCompany, projectId: resolvedProject, now: deps.now })))(company, project)
|
|
111
|
+
: deps.store?.(realDirectory) ?? createLocalTrackerStore(realDirectory, deps.now),
|
|
110
112
|
};
|
|
111
113
|
}
|
|
112
114
|
function requireActor(deps) {
|
|
@@ -184,8 +186,8 @@ function addProjectOptions(command) {
|
|
|
184
186
|
.requiredOption("--project <slug>", "HQ team project slug")
|
|
185
187
|
.option("--company <slug>", "Select the company by slug");
|
|
186
188
|
}
|
|
187
|
-
function createRecord(store, kind, company, project, fields) {
|
|
188
|
-
return store.create({ kind, id: randomUUID(), company, project_id: project, ...fields });
|
|
189
|
+
async function createRecord(store, kind, company, project, fields) {
|
|
190
|
+
return await store.create({ kind, id: randomUUID(), company, project_id: project, ...fields });
|
|
189
191
|
}
|
|
190
192
|
export function registerLaneTrackerCommands(parent, deps, wrap) {
|
|
191
193
|
const blocker = parent.command("blocker").description("Record and manage project blockers");
|
|
@@ -200,7 +202,7 @@ export function registerLaneTrackerCommands(parent, deps, wrap) {
|
|
|
200
202
|
fail("tracker_input_invalid", "story must not be empty", { field: "story" });
|
|
201
203
|
for (const [field, value] of [["what", options.what], ["waiting_on", options.waitingOn], ["next_step", options.nextStep]])
|
|
202
204
|
checkedText(field, value);
|
|
203
|
-
const record = createRecord(store, "blocker", company.slug, project, {
|
|
205
|
+
const record = await createRecord(store, "blocker", company.slug, project, {
|
|
204
206
|
story_id: options.story.trim(), what: options.what, waiting_on: options.waitingOn,
|
|
205
207
|
next_step: options.nextStep, opened_at: deps.now().toISOString(), resolved_at: null, resolution: null,
|
|
206
208
|
});
|
|
@@ -217,12 +219,12 @@ export function registerLaneTrackerCommands(parent, deps, wrap) {
|
|
|
217
219
|
checkedText("waiting_on", options.waitingOn);
|
|
218
220
|
if (options.nextStep !== undefined)
|
|
219
221
|
checkedText("next_step", options.nextStep);
|
|
220
|
-
const current = store.read("blocker", id);
|
|
222
|
+
const current = await store.read("blocker", id);
|
|
221
223
|
if (!current)
|
|
222
224
|
fail("tracker_record_not_found", `Tracker blocker record ${id} was not found`, { kind: "blocker", id });
|
|
223
225
|
if (current.resolved_at !== null)
|
|
224
226
|
fail("tracker_already_resolved", "Blocker is already resolved", { id });
|
|
225
|
-
const record = store.update("blocker", id, current.version, (value) => ({
|
|
227
|
+
const record = await store.update("blocker", id, current.version, (value) => ({
|
|
226
228
|
...value,
|
|
227
229
|
...(options.waitingOn === undefined ? {} : { waiting_on: options.waitingOn }),
|
|
228
230
|
...(options.nextStep === undefined ? {} : { next_step: options.nextStep }),
|
|
@@ -234,18 +236,18 @@ export function registerLaneTrackerCommands(parent, deps, wrap) {
|
|
|
234
236
|
.action(wrap(async (id, options) => {
|
|
235
237
|
const { store } = await target(deps, options);
|
|
236
238
|
checkedText("resolution", options.resolution);
|
|
237
|
-
const current = store.read("blocker", id);
|
|
239
|
+
const current = await store.read("blocker", id);
|
|
238
240
|
if (!current)
|
|
239
241
|
fail("tracker_record_not_found", `Tracker blocker record ${id} was not found`, { kind: "blocker", id });
|
|
240
242
|
if (current.resolved_at !== null)
|
|
241
243
|
fail("tracker_already_resolved", "Blocker is already resolved", { id });
|
|
242
|
-
const record = store.update("blocker", id, current.version, (value) => ({ ...value, resolved_at: deps.now().toISOString(), resolution: options.resolution }));
|
|
244
|
+
const record = await store.update("blocker", id, current.version, (value) => ({ ...value, resolved_at: deps.now().toISOString(), resolution: options.resolution }));
|
|
243
245
|
console.log(JSON.stringify({ ok: true, action: "blocker.resolve", record }, null, 2));
|
|
244
246
|
}));
|
|
245
247
|
addProjectOptions(blocker.command("list").description("List blockers")).option("--all", "Include resolved blockers")
|
|
246
248
|
.action(wrap(async (options) => {
|
|
247
249
|
const { store } = await target(deps, options);
|
|
248
|
-
const records = store.list("blocker").filter((record) => options.all || record.resolved_at === null);
|
|
250
|
+
const records = (await store.list("blocker")).filter((record) => options.all || record.resolved_at === null);
|
|
249
251
|
console.log(JSON.stringify({ ok: true, action: "blocker.list", records }, null, 2));
|
|
250
252
|
}));
|
|
251
253
|
const kpi = parent.command("kpi").description("Define KPIs and record values");
|
|
@@ -262,7 +264,7 @@ export function registerLaneTrackerCommands(parent, deps, wrap) {
|
|
|
262
264
|
checkedText("name", options.name);
|
|
263
265
|
checkedText("definition", options.definition);
|
|
264
266
|
requiredText("unit", options.unit);
|
|
265
|
-
const record = createRecord(store, "kpi", company.slug, project, {
|
|
267
|
+
const record = await createRecord(store, "kpi", company.slug, project, {
|
|
266
268
|
name: options.name, definition: options.definition, unit: options.unit,
|
|
267
269
|
target: numberValue(options.target, "target"), direction: options.direction,
|
|
268
270
|
status: "proposed", approved_by: null, approved_at: null, values: [],
|
|
@@ -278,31 +280,34 @@ export function registerLaneTrackerCommands(parent, deps, wrap) {
|
|
|
278
280
|
checkedText("source", options.source);
|
|
279
281
|
const value = numberValue(options.value, "value");
|
|
280
282
|
const at = options.at === undefined ? deps.now() : validTimestamp(options.at);
|
|
281
|
-
const current = store.read("kpi", id);
|
|
283
|
+
const current = await store.read("kpi", id);
|
|
282
284
|
if (!current)
|
|
283
285
|
fail("tracker_record_not_found", `Tracker KPI record ${id} was not found`, { kind: "kpi", id });
|
|
284
286
|
const values = current.values;
|
|
285
287
|
if (!Array.isArray(values))
|
|
286
288
|
fail("tracker_record_invalid", "KPI values must be an array", { id });
|
|
287
|
-
const record = store.update("kpi", id, current.version, (record) => ({ ...record, values: [...values, { at: at.toISOString(), value, source: options.source }] }));
|
|
289
|
+
const record = await store.update("kpi", id, current.version, (record) => ({ ...record, values: [...values, { at: at.toISOString(), value, source: options.source }] }));
|
|
288
290
|
console.log(JSON.stringify({ ok: true, action: "kpi.record", record }, null, 2));
|
|
289
291
|
}));
|
|
290
292
|
addProjectOptions(kpi.command("approve").description("Approve a KPI proposal").argument("<kpi-id>", "KPI id"))
|
|
293
|
+
.option("--question <id>", "Answered owner question authorizing this approval")
|
|
291
294
|
.action(wrap(async (id, options) => {
|
|
292
|
-
const { store } = await target(deps, options);
|
|
293
|
-
|
|
295
|
+
const { store, usesMesh } = await target(deps, options);
|
|
296
|
+
if (usesMesh && !options.question)
|
|
297
|
+
fail("tracker_approval_question_required", "Work Mesh KPI approval requires --question <answered-owner-question-id>", { id });
|
|
298
|
+
const current = await store.read("kpi", id);
|
|
294
299
|
if (!current)
|
|
295
300
|
fail("tracker_record_not_found", `Tracker KPI record ${id} was not found`, { kind: "kpi", id });
|
|
296
|
-
const record = store.update("kpi", id, current.version, (value) => {
|
|
301
|
+
const record = await store.update("kpi", id, current.version, (value) => {
|
|
297
302
|
if (value.status === "approved")
|
|
298
303
|
fail("tracker_already_approved", "KPI is already approved", { id });
|
|
299
304
|
return { ...value, status: "approved", approved_by: requireActor(deps), approved_at: deps.now().toISOString() };
|
|
300
|
-
});
|
|
305
|
+
}, options.question ? { approvalQuestionId: options.question } : undefined);
|
|
301
306
|
console.log(JSON.stringify({ ok: true, action: "kpi.approve", record }, null, 2));
|
|
302
307
|
}));
|
|
303
308
|
addProjectOptions(kpi.command("list").description("List KPIs")).action(wrap(async (options) => {
|
|
304
309
|
const { store } = await target(deps, options);
|
|
305
|
-
console.log(JSON.stringify({ ok: true, action: "kpi.list", records: store.list("kpi") }, null, 2));
|
|
310
|
+
console.log(JSON.stringify({ ok: true, action: "kpi.list", records: await store.list("kpi") }, null, 2));
|
|
306
311
|
}));
|
|
307
312
|
const okr = parent.command("okr").description("Set and approve OKRs");
|
|
308
313
|
addProjectOptions(okr.command("set").description("Create a proposed OKR"))
|
|
@@ -319,11 +324,11 @@ export function registerLaneTrackerCommands(parent, deps, wrap) {
|
|
|
319
324
|
for (const keyResult of keyResults)
|
|
320
325
|
checkedText("key_result", keyResult.text);
|
|
321
326
|
for (const keyResult of keyResults) {
|
|
322
|
-
if (!store.read("kpi", keyResult.kpi_id)) {
|
|
327
|
+
if (!await store.read("kpi", keyResult.kpi_id)) {
|
|
323
328
|
fail("tracker_kpi_not_found", `Referenced KPI ${keyResult.kpi_id} was not found in this project`, { kpi_id: keyResult.kpi_id, project_id: project });
|
|
324
329
|
}
|
|
325
330
|
}
|
|
326
|
-
const record = createRecord(store, "okr", company.slug, project, {
|
|
331
|
+
const record = await createRecord(store, "okr", company.slug, project, {
|
|
327
332
|
objective: options.objective, period: options.period,
|
|
328
333
|
key_results: keyResults.map((value) => ({ kr_id: randomUUID(), ...value })),
|
|
329
334
|
status: "proposed", approved_by: null, approved_at: null,
|
|
@@ -331,21 +336,24 @@ export function registerLaneTrackerCommands(parent, deps, wrap) {
|
|
|
331
336
|
console.log(JSON.stringify({ ok: true, action: "okr.set", record }, null, 2));
|
|
332
337
|
}));
|
|
333
338
|
addProjectOptions(okr.command("approve").description("Approve an OKR proposal").argument("<okr-id>", "OKR id"))
|
|
339
|
+
.option("--question <id>", "Answered owner question authorizing this approval")
|
|
334
340
|
.action(wrap(async (id, options) => {
|
|
335
|
-
const { store } = await target(deps, options);
|
|
336
|
-
|
|
341
|
+
const { store, usesMesh } = await target(deps, options);
|
|
342
|
+
if (usesMesh && !options.question)
|
|
343
|
+
fail("tracker_approval_question_required", "Work Mesh OKR approval requires --question <answered-owner-question-id>", { id });
|
|
344
|
+
const current = await store.read("okr", id);
|
|
337
345
|
if (!current)
|
|
338
346
|
fail("tracker_record_not_found", `Tracker OKR record ${id} was not found`, { kind: "okr", id });
|
|
339
|
-
const record = store.update("okr", id, current.version, (value) => {
|
|
347
|
+
const record = await store.update("okr", id, current.version, (value) => {
|
|
340
348
|
if (value.status === "approved")
|
|
341
349
|
fail("tracker_already_approved", "OKR is already approved", { id });
|
|
342
350
|
return { ...value, status: "approved", approved_by: requireActor(deps), approved_at: deps.now().toISOString() };
|
|
343
|
-
});
|
|
351
|
+
}, options.question ? { approvalQuestionId: options.question } : undefined);
|
|
344
352
|
console.log(JSON.stringify({ ok: true, action: "okr.approve", record }, null, 2));
|
|
345
353
|
}));
|
|
346
354
|
addProjectOptions(okr.command("list").description("List OKRs")).action(wrap(async (options) => {
|
|
347
355
|
const { store } = await target(deps, options);
|
|
348
|
-
console.log(JSON.stringify({ ok: true, action: "okr.list", records: store.list("okr") }, null, 2));
|
|
356
|
+
console.log(JSON.stringify({ ok: true, action: "okr.list", records: await store.list("okr") }, null, 2));
|
|
349
357
|
}));
|
|
350
358
|
const directive = parent.command("directive").description("Record directives and acknowledgements");
|
|
351
359
|
addProjectOptions(directive.command("record").description("Record a directive"))
|
|
@@ -373,7 +381,7 @@ export function registerLaneTrackerCommands(parent, deps, wrap) {
|
|
|
373
381
|
}
|
|
374
382
|
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
375
383
|
fail("tracker_source_url_invalid", "Source URL must use HTTP or HTTPS", {});
|
|
376
|
-
const record = createRecord(store, "directive", company.slug, project, {
|
|
384
|
+
const record = await createRecord(store, "directive", company.slug, project, {
|
|
377
385
|
text: options.text, date: validDate(options.date), source_url: options.sourceUrl,
|
|
378
386
|
teams: [...new Set(teams)].sort(), policy_path: policyPath, acknowledgements: [],
|
|
379
387
|
});
|
|
@@ -386,7 +394,7 @@ export function registerLaneTrackerCommands(parent, deps, wrap) {
|
|
|
386
394
|
const { store } = await target(deps, options);
|
|
387
395
|
const team = validSegment(options.team, "team");
|
|
388
396
|
const lane = validSegment(options.lane, "lane");
|
|
389
|
-
const current = store.read("directive", id);
|
|
397
|
+
const current = await store.read("directive", id);
|
|
390
398
|
if (!current)
|
|
391
399
|
fail("tracker_record_not_found", `Tracker directive record ${id} was not found`, { kind: "directive", id });
|
|
392
400
|
const teams = current.teams;
|
|
@@ -395,7 +403,7 @@ export function registerLaneTrackerCommands(parent, deps, wrap) {
|
|
|
395
403
|
fail("tracker_team_not_targeted", "Team is not listed on this directive", { id, team });
|
|
396
404
|
if (!Array.isArray(acknowledgements))
|
|
397
405
|
fail("tracker_record_invalid", "Directive acknowledgements must be an array", { id });
|
|
398
|
-
const record = store.update("directive", id, current.version, (value) => ({
|
|
406
|
+
const record = await store.update("directive", id, current.version, (value) => ({
|
|
399
407
|
...value,
|
|
400
408
|
acknowledgements: [...acknowledgements.filter((ack) => !(ack && typeof ack === "object" && ack.team === team && ack.lane_id === lane)), { team, lane_id: lane, at: deps.now().toISOString() }],
|
|
401
409
|
}));
|
|
@@ -403,7 +411,7 @@ export function registerLaneTrackerCommands(parent, deps, wrap) {
|
|
|
403
411
|
}));
|
|
404
412
|
addProjectOptions(directive.command("list").description("List directives")).action(wrap(async (options) => {
|
|
405
413
|
const { store } = await target(deps, options);
|
|
406
|
-
console.log(JSON.stringify({ ok: true, action: "directive.list", records: store.list("directive") }, null, 2));
|
|
414
|
+
console.log(JSON.stringify({ ok: true, action: "directive.list", records: await store.list("directive") }, null, 2));
|
|
407
415
|
}));
|
|
408
416
|
}
|
|
409
417
|
export function defaultTrackerActor() {
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* `watch` and the monitor reminder are the line-oriented text surfaces.
|
|
7
7
|
*/
|
|
8
8
|
import { Command } from "commander";
|
|
9
|
+
import { type TrackerMeshRequest } from "../lib/lanes/tracker-mesh.js";
|
|
9
10
|
import type { OutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
10
11
|
export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
11
12
|
export type { OutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
@@ -102,6 +103,8 @@ export interface LanesCommandDeps {
|
|
|
102
103
|
laneTrackerEnabled?: (company: LaneCompanyRef) => boolean | Promise<boolean>;
|
|
103
104
|
/** The local-only PR 2 implementation refuses if mesh storage is enabled. */
|
|
104
105
|
laneTrackerMeshEnabled?: (company: LaneCompanyRef) => boolean | Promise<boolean>;
|
|
106
|
+
/** Fake transport seam used by the disposable-root Work Mesh contract check. */
|
|
107
|
+
trackerMeshRequest?: TrackerMeshRequest;
|
|
105
108
|
/** Test seam for the default-off host-scoped account-home writer gate. */
|
|
106
109
|
laneHostScopedAccountHomesEnabled?: (company?: LaneCompanyRef | null) => boolean | Promise<boolean>;
|
|
107
110
|
/** Test seam for recording approvals against the active identity. */
|