@indigoai-us/hq-cli 5.234.1 → 5.235.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 CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.235.0] — 2026-09-27
6
+
7
+ ### Changed
8
+
9
+ - `hq lanes` blocker, KPI, OKR, and directive commands, checks, imports, and
10
+ reports read and write records in each project's local `tracker/` directory.
11
+ The retired Work Mesh tracker path and question index were removed. KPI and
12
+ OKR approvals also verify and save any supplied answered question ID.
13
+
14
+ ## [5.234.2] — 2026-09-27
15
+
16
+ ### Fixed
17
+
18
+ - The flag-registry worker now uses source embedded in the CLI, so package
19
+ updates can no longer cause startup failures by removing its script.
20
+
5
21
  ## [5.234.1] — 2026-09-27
6
22
 
7
23
  ### Fixed
@@ -1,14 +1,11 @@
1
1
  import type { Command } from "commander";
2
2
  import { type TrackerKind, type TrackerRecord } from "../lib/lanes/tracker-store.js";
3
- import type { TrackerStore } from "../lib/lanes/tracker-store.js";
4
3
  import type { LaneCompanyRef, QuestionRecord } from "../lib/lanes/types.js";
5
4
  export interface LaneChecksDeps {
6
5
  hqRoot: () => string;
7
6
  resolveCompany: (company?: string) => Promise<LaneCompanyRef>;
8
7
  trackerEnabled: (company: LaneCompanyRef) => boolean | Promise<boolean>;
9
- trackerMeshEnabled: (company: LaneCompanyRef) => boolean | Promise<boolean>;
10
8
  now: () => Date;
11
- meshStore?: (company: LaneCompanyRef, projectId: string) => TrackerStore;
12
9
  }
13
10
  type WrapHandler = <Args extends unknown[]>(action: (...args: Args) => Promise<void>) => (...args: Args) => Promise<void>;
14
11
  export interface ProjectMapping {
@@ -5,7 +5,6 @@ import { LanesError, isLanesError } from "../lib/lanes/errors.js";
5
5
  import { evaluateTrackerChecks } from "../lib/lanes/tracker-checks.js";
6
6
  import { laneRecordPath, listLaneIds, listQuestionIds, questionRecordPath, readLane, readQuestion } from "../lib/lanes/store.js";
7
7
  import { createLocalTrackerStore } from "../lib/lanes/tracker-store.js";
8
- import { createWorkMeshTrackerStore } from "../lib/lanes/tracker-mesh.js";
9
8
  import { LANES_TRACKER_FLAG } from "./lanes-tracker.js";
10
9
  function fail(error, message, details = {}) {
11
10
  throw new LanesError(error, message, details);
@@ -435,17 +434,7 @@ function readTeamInputs(root, company, projects, lanes) {
435
434
  export function pathForTrackerRecord(projectDirectory, kind, id) {
436
435
  return path.join(projectDirectory, "tracker", kind, `${id}.json`);
437
436
  }
438
- function recordInvalid(kind, record, projectDirectory, field, source = "local") {
439
- if (source === "Work Mesh") {
440
- return fail("tracker_checks_record_invalid", `Work Mesh tracker ${kind} record ${record.id} has invalid ${field}`, {
441
- source,
442
- record_id: record.id,
443
- kind,
444
- company: record.company,
445
- project_id: record.project_id,
446
- field,
447
- });
448
- }
437
+ function recordInvalid(kind, record, projectDirectory, field) {
449
438
  return fail("tracker_checks_record_invalid", `Tracker ${kind} record has invalid ${field}`, {
450
439
  kind,
451
440
  id: record.id,
@@ -453,18 +442,15 @@ function recordInvalid(kind, record, projectDirectory, field, source = "local")
453
442
  path: pathForTrackerRecord(projectDirectory, kind, record.id),
454
443
  });
455
444
  }
456
- function requiredRecordDate(kind, record, projectDirectory, field, source = "local") {
445
+ function requiredRecordDate(kind, record, projectDirectory, field) {
457
446
  const value = record[field];
458
447
  if (typeof value !== "string" || !Number.isFinite(Date.parse(value)))
459
- return recordInvalid(kind, record, projectDirectory, field, source);
448
+ return recordInvalid(kind, record, projectDirectory, field);
460
449
  return value;
461
450
  }
462
- function requiredRecordTimestamp(kind, record, projectDirectory, field, value, source) {
463
- if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
464
- if (source === "Work Mesh")
465
- return recordInvalid(kind, record, projectDirectory, field, source);
451
+ function requiredRecordTimestamp(kind, record, projectDirectory, field, value) {
452
+ if (typeof value !== "string" || !Number.isFinite(Date.parse(value)))
466
453
  return parseTimestamp(value, field, pathForTrackerRecord(projectDirectory, kind, record.id));
467
- }
468
454
  return value;
469
455
  }
470
456
  export function listTrackerRecords(projectDirectory, kind) {
@@ -522,17 +508,13 @@ export function listTrackerRecordsTolerant(projectDirectory, kind) {
522
508
  records.sort((a, b) => b.updated_at.localeCompare(a.updated_at) || a.id.localeCompare(b.id));
523
509
  return { records, errors };
524
510
  }
525
- async function readTrackerCheckRecords(root, company, projects, deps) {
511
+ async function readTrackerCheckRecords(root, company, projects) {
526
512
  const blockers = [];
527
513
  const kpis = [];
528
514
  const directives = [];
529
- const useMesh = await deps.trackerMeshEnabled(company);
530
- const source = useMesh ? "Work Mesh" : "local";
531
515
  for (const project of projects) {
532
516
  const absoluteDirectory = path.join(root, "companies", company.slug, "projects", project.projectId);
533
- const store = useMesh
534
- ? (deps.meshStore ?? ((resolvedCompany, projectId) => createWorkMeshTrackerStore({ company: resolvedCompany, projectId, now: deps.now })))(company, project.projectId)
535
- : createLocalTrackerStore(absoluteDirectory);
517
+ const store = createLocalTrackerStore(absoluteDirectory);
536
518
  const records = {
537
519
  blocker: await store.list("blocker"),
538
520
  kpi: await store.list("kpi"),
@@ -541,40 +523,40 @@ async function readTrackerCheckRecords(root, company, projects, deps) {
541
523
  for (const record of records.blocker) {
542
524
  if (record.company !== company.slug || record.project_id !== project.projectId)
543
525
  continue;
544
- const openedAt = requiredRecordDate("blocker", record, absoluteDirectory, "opened_at", source);
526
+ const openedAt = requiredRecordDate("blocker", record, absoluteDirectory, "opened_at");
545
527
  const resolvedAt = record.resolved_at;
546
528
  if (resolvedAt !== null && (typeof resolvedAt !== "string" || !Number.isFinite(Date.parse(resolvedAt))))
547
- recordInvalid("blocker", record, absoluteDirectory, "resolved_at", source);
529
+ recordInvalid("blocker", record, absoluteDirectory, "resolved_at");
548
530
  const nextStep = record.next_step;
549
531
  if (nextStep !== undefined && nextStep !== null && typeof nextStep !== "string")
550
- recordInvalid("blocker", record, absoluteDirectory, "next_step", source);
532
+ recordInvalid("blocker", record, absoluteDirectory, "next_step");
551
533
  blockers.push({ id: record.id, opened_at: openedAt, resolved_at: resolvedAt, next_step: typeof nextStep === "string" ? nextStep : null });
552
534
  }
553
535
  for (const record of records.kpi) {
554
536
  if (record.company !== company.slug || record.project_id !== project.projectId)
555
537
  continue;
556
- const createdAt = requiredRecordDate("kpi", record, absoluteDirectory, "created_at", source);
538
+ const createdAt = requiredRecordDate("kpi", record, absoluteDirectory, "created_at");
557
539
  if (!Array.isArray(record.values))
558
- recordInvalid("kpi", record, absoluteDirectory, "values", source);
540
+ recordInvalid("kpi", record, absoluteDirectory, "values");
559
541
  const values = record.values.map((value) => {
560
542
  if (!isObject(value))
561
- recordInvalid("kpi", record, absoluteDirectory, "values", source);
562
- return { at: requiredRecordTimestamp("kpi", record, absoluteDirectory, "values[].at", value.at, source) };
543
+ recordInvalid("kpi", record, absoluteDirectory, "values");
544
+ return { at: requiredRecordTimestamp("kpi", record, absoluteDirectory, "values[].at", value.at) };
563
545
  });
564
546
  kpis.push({ id: record.id, created_at: createdAt, values });
565
547
  }
566
548
  for (const record of records.directive) {
567
549
  if (record.company !== company.slug || record.project_id !== project.projectId)
568
550
  continue;
569
- const createdAt = requiredRecordDate("directive", record, absoluteDirectory, "created_at", source);
551
+ const createdAt = requiredRecordDate("directive", record, absoluteDirectory, "created_at");
570
552
  if (!Array.isArray(record.teams) || record.teams.some((team) => typeof team !== "string"))
571
- recordInvalid("directive", record, absoluteDirectory, "teams", source);
553
+ recordInvalid("directive", record, absoluteDirectory, "teams");
572
554
  if (!Array.isArray(record.acknowledgements))
573
- recordInvalid("directive", record, absoluteDirectory, "acknowledgements", source);
555
+ recordInvalid("directive", record, absoluteDirectory, "acknowledgements");
574
556
  const acknowledgements = record.acknowledgements.map((ack) => {
575
557
  if (!isObject(ack) || typeof ack.team !== "string" || typeof ack.lane_id !== "string")
576
- recordInvalid("directive", record, absoluteDirectory, "acknowledgements", source);
577
- return { team: ack.team, lane_id: ack.lane_id, at: requiredRecordTimestamp("directive", record, absoluteDirectory, "acknowledgements[].at", ack.at, source) };
558
+ recordInvalid("directive", record, absoluteDirectory, "acknowledgements");
559
+ return { team: ack.team, lane_id: ack.lane_id, at: requiredRecordTimestamp("directive", record, absoluteDirectory, "acknowledgements[].at", ack.at) };
578
560
  });
579
561
  directives.push({ id: record.id, created_at: createdAt, teams: record.teams, acknowledgements });
580
562
  }
@@ -590,12 +572,12 @@ export async function resolveTrackerCompany(deps, requested) {
590
572
  }
591
573
  return company;
592
574
  }
593
- async function checkReport(root, company, projects, directiveProjects, projectFilter, now, deps) {
575
+ async function checkReport(root, company, projects, directiveProjects, projectFilter, now) {
594
576
  const selectedProjects = [...projects.values()];
595
577
  const laneRecords = readScopedLanes(root, company, selectedProjects);
596
578
  const teams = readTeamInputs(root, company, selectedProjects, laneRecords);
597
579
  const questionRecords = readCompanyQuestions(root, company, projectFilter);
598
- const trackerRecords = await readTrackerCheckRecords(root, company, selectedProjects, deps);
580
+ const trackerRecords = await readTrackerCheckRecords(root, company, selectedProjects);
599
581
  const directiveTeams = [...directiveProjects.values()].map((project) => ({
600
582
  team: project.team,
601
583
  ...(project.leadLaneId === undefined ? {} : { lead_lane_id: project.leadLaneId }),
@@ -628,7 +610,7 @@ export function registerLaneChecksCommand(parent, deps, wrap) {
628
610
  const directiveProjects = projectFilter === undefined
629
611
  ? projects
630
612
  : loadProjectMappings(root, company.slug);
631
- const report = await checkReport(root, company, projects, directiveProjects, projectFilter, deps.now(), deps);
613
+ const report = await checkReport(root, company, projects, directiveProjects, projectFilter, deps.now());
632
614
  console.log(JSON.stringify(report, null, 2));
633
615
  if (report.checks.some((item) => item.result === "failing"))
634
616
  process.exitCode = 2;
@@ -6,10 +6,8 @@ export interface LaneTrackerImportDeps {
6
6
  hqRoot: () => string;
7
7
  resolveCompany: (company?: string) => Promise<LaneCompanyRef>;
8
8
  trackerEnabled: (company: LaneCompanyRef) => boolean | Promise<boolean>;
9
- trackerMeshEnabled: (company: LaneCompanyRef) => boolean | Promise<boolean>;
10
9
  now: () => Date;
11
10
  store?: (projectDirectory: string) => TrackerStore;
12
- meshStore?: (company: LaneCompanyRef, projectId: string) => TrackerStore;
13
11
  }
14
12
  export declare function registerLaneTrackerImportCommand(parent: Command, deps: LaneTrackerImportDeps, wrap: WrapHandler): void;
15
13
  export {};
@@ -5,7 +5,6 @@ import { isDeepStrictEqual } from "node:util";
5
5
  import { LanesError } from "../lib/lanes/errors.js";
6
6
  import { assertNoSecretText } from "../lib/lanes/text-scan.js";
7
7
  import { createLocalTrackerStore, } from "../lib/lanes/tracker-store.js";
8
- import { createWorkMeshTrackerStore } from "../lib/lanes/tracker-mesh.js";
9
8
  import { LANES_TRACKER_FLAG } from "./lanes-tracker.js";
10
9
  const IMPORT_DATE = "2026-09-25";
11
10
  const IMPORT_KEY_PREFIX = "trk7";
@@ -1049,7 +1048,7 @@ function statusOkrCandidates(status, statusSource, result) {
1049
1048
  function makeEmptyTeamResult(team) {
1050
1049
  return { team, status: "error", sources: { prd: null, status: null, brief: null, directives: null }, actions: [], skipped: [], errors: [], unlinked_key_results: [] };
1051
1050
  }
1052
- async function prepareTeam(root, company, companyRef, useMesh, row, index, globalMarkdown, globalDirectivePath, deps) {
1051
+ async function prepareTeam(root, company, row, index, globalMarkdown, globalDirectivePath, deps) {
1053
1052
  const team = typeof row.team === "string" && row.team.trim() ? row.team.trim() : `unknown-${index}`;
1054
1053
  const result = makeEmptyTeamResult(team);
1055
1054
  let prdPath = "";
@@ -1098,9 +1097,7 @@ async function prepareTeam(root, company, companyRef, useMesh, row, index, globa
1098
1097
  addSkip(result, { kind: "okr", source_path: statusSource, reason: "no_explicit_goal_or_okr_source" });
1099
1098
  }
1100
1099
  candidates.push(...okrCandidates([...statusOkrs, ...briefOkrs], kpis, result, team));
1101
- const store = useMesh
1102
- ? (deps.meshStore ?? ((resolvedCompany, projectId) => createWorkMeshTrackerStore({ company: resolvedCompany, projectId, now: deps.now })))(companyRef, resolved.projectId)
1103
- : deps.store?.(path.dirname(prdPath)) ?? createLocalTrackerStore(path.dirname(prdPath), deps.now);
1100
+ const store = deps.store?.(path.dirname(prdPath)) ?? createLocalTrackerStore(path.dirname(prdPath), deps.now);
1104
1101
  const importCandidates = uniqueCandidates(candidates, result);
1105
1102
  result.status = result.errors.length ? "imported_with_errors" : "imported";
1106
1103
  return { result, company, projectId: resolved.projectId, store, candidates: importCandidates };
@@ -1132,7 +1129,6 @@ export function registerLaneTrackerImportCommand(parent, deps, wrap) {
1132
1129
  if (!await deps.trackerEnabled(company)) {
1133
1130
  fail("tracker_disabled", `hq lanes import requires the ${LANES_TRACKER_FLAG} flag`, { company: company.slug });
1134
1131
  }
1135
- const trackerMeshEnabled = await deps.trackerMeshEnabled(company);
1136
1132
  const root = deps.hqRoot();
1137
1133
  const registryPath = resolveSourcePath(root, "workspace/lane-briefs/juniors.json", "juniors registry");
1138
1134
  const registry = readJsonObject(registryPath, "juniors registry");
@@ -1152,7 +1148,7 @@ export function registerLaneTrackerImportCommand(parent, deps, wrap) {
1152
1148
  const globalMarkdown = readText(globalPath, "operator-session-directives.md");
1153
1149
  const works = [];
1154
1150
  for (const [index, row] of selectedRows.entries()) {
1155
- works.push(await prepareTeam(root, company.slug, company, trackerMeshEnabled, row, index, globalMarkdown, globalPath, deps));
1151
+ works.push(await prepareTeam(root, company.slug, row, index, globalMarkdown, globalPath, deps));
1156
1152
  }
1157
1153
  const mode = options.apply ? "apply" : "dry-run";
1158
1154
  const kpiIds = new Map();
@@ -4,7 +4,6 @@ import { assertQuestionBodySecretSafe } from "../lib/lanes/questions.js";
4
4
  import { questionRecordPath } from "../lib/lanes/store.js";
5
5
  import { normalizeIsoTimestamp } from "../lib/lanes/timestamp.js";
6
6
  import { mapSourceStoryStatus } from "../lib/mesh/story-status.js";
7
- import { createWorkMeshTrackerStore } from "../lib/lanes/tracker-mesh.js";
8
7
  import { buildTrackerOwnerReport, buildTrackerTeamPage, renderTrackerOwnerReportText, renderTrackerTeamPageText, } from "../lib/lanes/tracker-report.js";
9
8
  import { FIRST_TRACKER_REPORT_SINCE, readTrackerReportState, writeTrackerReportState, } from "../lib/lanes/tracker-report-state.js";
10
9
  import { listTrackerRecordsTolerant, loadCompanyQuestionRecords, loadProjectMappings, pathForTrackerRecord, readProject, resolveTrackerCompany, } from "./lanes-checks.js";
@@ -143,14 +142,8 @@ function currentAudience(question, sourcePath) {
143
142
  default: return fail("tracker_report_question_route_invalid", "Question current audience is unsupported", { question_id: question.question_id, path: sourcePath });
144
143
  }
145
144
  }
146
- async function trackerRecords(root, company, project, kind, errors, deps) {
145
+ async function trackerRecords(root, company, project, kind, errors) {
147
146
  const projectDirectory = path.join(root, "companies", company.slug, "projects", project.projectId);
148
- if (await deps.trackerMeshEnabled(company)) {
149
- const store = (deps.meshStore ?? ((resolvedCompany, projectId) => createWorkMeshTrackerStore({ company: resolvedCompany, projectId, now: deps.now })))(company, project.projectId);
150
- return (await store.list(kind))
151
- .filter((record) => record.company === company.slug && record.project_id === project.projectId)
152
- .map((record) => ({ ...record, source_path: `work-mesh://${company.uid}/projects/${project.projectId}/tracker/${kind}/${record.id}` }));
153
- }
154
147
  const loaded = listTrackerRecordsTolerant(projectDirectory, kind);
155
148
  for (const issue of loaded.errors) {
156
149
  errors.push({ path: issue.path, field: issue.field, message: issue.message, ...(issue.record_id ? { record_id: issue.record_id } : {}) });
@@ -242,10 +235,10 @@ async function teamInput(root, company, project, questions, deps, errors = []) {
242
235
  stories,
243
236
  pull_requests: pullRequests,
244
237
  releases,
245
- blockers: await trackerRecords(root, company, project, "blocker", errors, deps),
246
- kpis: await trackerRecords(root, company, project, "kpi", errors, deps),
247
- okrs: await trackerRecords(root, company, project, "okr", errors, deps),
248
- directives: await trackerRecords(root, company, project, "directive", errors, deps),
238
+ blockers: await trackerRecords(root, company, project, "blocker", errors),
239
+ kpis: await trackerRecords(root, company, project, "kpi", errors),
240
+ okrs: await trackerRecords(root, company, project, "okr", errors),
241
+ directives: await trackerRecords(root, company, project, "directive", errors),
249
242
  questions: teamQuestions,
250
243
  errors,
251
244
  };
@@ -2,17 +2,14 @@ import type { Command } from "commander";
2
2
  import { type TrackerStore } from "../lib/lanes/tracker-store.js";
3
3
  import type { LaneCompanyRef } from "../lib/lanes/types.js";
4
4
  export declare const LANES_TRACKER_FLAG = "lanes.tracker";
5
- export declare const LANES_TRACKER_MESH_FLAG = "lanes.tracker-mesh";
6
5
  export interface LaneTrackerCommandDeps {
7
6
  hqRoot: () => string;
8
7
  resolveCompany: (company?: string) => Promise<LaneCompanyRef>;
9
8
  trackerEnabled: (company: LaneCompanyRef) => boolean | Promise<boolean>;
10
- trackerMeshEnabled: (company: LaneCompanyRef) => boolean | Promise<boolean>;
11
9
  now: () => Date;
12
10
  actor: () => string | null;
13
11
  /** Test seam; production uses the same local store used by the public commands. */
14
12
  store?: (projectDirectory: string) => TrackerStore;
15
- meshStore?: (company: LaneCompanyRef, projectId: string) => TrackerStore;
16
13
  }
17
14
  type WrapHandler = <Args extends unknown[]>(action: (...args: Args) => Promise<void>) => (...args: Args) => Promise<void>;
18
15
  export declare function registerLaneTrackerCommands(parent: Command, deps: LaneTrackerCommandDeps, wrap: WrapHandler): void;
@@ -4,12 +4,11 @@ import * as path from "node:path";
4
4
  import { loadCachedTokens } from "@indigoai-us/hq-cloud";
5
5
  import { LanesError } from "../lib/lanes/errors.js";
6
6
  import { resolveProjectStoryLedger } from "../lib/lanes/story-ledger.js";
7
+ import { tryReadQuestion } from "../lib/lanes/store.js";
7
8
  import { assertNoSecretText } from "../lib/lanes/text-scan.js";
8
9
  import { createLocalTrackerStore } from "../lib/lanes/tracker-store.js";
9
- import { createWorkMeshTrackerStore } from "../lib/lanes/tracker-mesh.js";
10
10
  import { peekIdToken } from "../utils/id-token.js";
11
11
  export const LANES_TRACKER_FLAG = "lanes.tracker";
12
- export const LANES_TRACKER_MESH_FLAG = "lanes.tracker-mesh";
13
12
  const MAX_POLICY_PATH_BYTES = 4096;
14
13
  function fail(error, message, details = {}) {
15
14
  throw new LanesError(error, message, details);
@@ -73,7 +72,6 @@ async function target(deps, options) {
73
72
  if (!await deps.trackerEnabled(company)) {
74
73
  fail("tracker_disabled", "hq lanes tracker commands require the lanes.tracker flag", { company: company.slug });
75
74
  }
76
- const useMesh = await deps.trackerMeshEnabled(company);
77
75
  const project = validSegment(options.project, "project");
78
76
  const ledger = resolveProjectStoryLedger(deps.hqRoot(), project, company.slug);
79
77
  const projectCompany = readProjectCompany(ledger.path);
@@ -105,10 +103,7 @@ async function target(deps, options) {
105
103
  return {
106
104
  company,
107
105
  project,
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),
106
+ store: deps.store?.(realDirectory) ?? createLocalTrackerStore(realDirectory, deps.now),
112
107
  };
113
108
  }
114
109
  function requireActor(deps) {
@@ -117,6 +112,25 @@ function requireActor(deps) {
117
112
  fail("tracker_identity_unavailable", "Cannot record approval without a signed-in HQ identity", {});
118
113
  return actor;
119
114
  }
115
+ function approvalQuestionId(deps, company, project, rawQuestionId) {
116
+ if (rawQuestionId === undefined)
117
+ return undefined;
118
+ const questionId = validSegment(rawQuestionId, "question-id");
119
+ const question = tryReadQuestion(deps.hqRoot(), questionId);
120
+ if (!question)
121
+ fail("tracker_question_not_found", `Answered owner question ${questionId} was not found`, { question_id: questionId });
122
+ if (question.status !== "answered" || !question.answer) {
123
+ fail("tracker_question_not_answered", `Owner question ${questionId} is not answered`, { question_id: questionId });
124
+ }
125
+ if (question.company?.slug !== company.slug || question.project_id !== project) {
126
+ fail("tracker_question_scope_mismatch", `Owner question ${questionId} does not belong to this company project`, {
127
+ question_id: questionId,
128
+ company: company.slug,
129
+ project_id: project,
130
+ });
131
+ }
132
+ return questionId;
133
+ }
120
134
  function numberValue(raw, field) {
121
135
  const value = Number(raw);
122
136
  if (!Number.isFinite(value))
@@ -292,17 +306,22 @@ export function registerLaneTrackerCommands(parent, deps, wrap) {
292
306
  addProjectOptions(kpi.command("approve").description("Approve a KPI proposal").argument("<kpi-id>", "KPI id"))
293
307
  .option("--question <id>", "Answered owner question authorizing this approval")
294
308
  .action(wrap(async (id, options) => {
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 });
309
+ const { company, project, store } = await target(deps, options);
310
+ const questionId = approvalQuestionId(deps, company, project, options.question);
298
311
  const current = await store.read("kpi", id);
299
312
  if (!current)
300
313
  fail("tracker_record_not_found", `Tracker KPI record ${id} was not found`, { kind: "kpi", id });
301
314
  const record = await store.update("kpi", id, current.version, (value) => {
302
315
  if (value.status === "approved")
303
316
  fail("tracker_already_approved", "KPI is already approved", { id });
304
- return { ...value, status: "approved", approved_by: requireActor(deps), approved_at: deps.now().toISOString() };
305
- }, options.question ? { approvalQuestionId: options.question } : undefined);
317
+ return {
318
+ ...value,
319
+ status: "approved",
320
+ approved_by: requireActor(deps),
321
+ approved_at: deps.now().toISOString(),
322
+ ...(questionId ? { approval_question_id: questionId } : {}),
323
+ };
324
+ });
306
325
  console.log(JSON.stringify({ ok: true, action: "kpi.approve", record }, null, 2));
307
326
  }));
308
327
  addProjectOptions(kpi.command("list").description("List KPIs")).action(wrap(async (options) => {
@@ -338,17 +357,22 @@ export function registerLaneTrackerCommands(parent, deps, wrap) {
338
357
  addProjectOptions(okr.command("approve").description("Approve an OKR proposal").argument("<okr-id>", "OKR id"))
339
358
  .option("--question <id>", "Answered owner question authorizing this approval")
340
359
  .action(wrap(async (id, options) => {
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 });
360
+ const { company, project, store } = await target(deps, options);
361
+ const questionId = approvalQuestionId(deps, company, project, options.question);
344
362
  const current = await store.read("okr", id);
345
363
  if (!current)
346
364
  fail("tracker_record_not_found", `Tracker OKR record ${id} was not found`, { kind: "okr", id });
347
365
  const record = await store.update("okr", id, current.version, (value) => {
348
366
  if (value.status === "approved")
349
367
  fail("tracker_already_approved", "OKR is already approved", { id });
350
- return { ...value, status: "approved", approved_by: requireActor(deps), approved_at: deps.now().toISOString() };
351
- }, options.question ? { approvalQuestionId: options.question } : undefined);
368
+ return {
369
+ ...value,
370
+ status: "approved",
371
+ approved_by: requireActor(deps),
372
+ approved_at: deps.now().toISOString(),
373
+ ...(questionId ? { approval_question_id: questionId } : {}),
374
+ };
375
+ });
352
376
  console.log(JSON.stringify({ ok: true, action: "okr.approve", record }, null, 2));
353
377
  }));
354
378
  addProjectOptions(okr.command("list").description("List OKRs")).action(wrap(async (options) => {
@@ -6,7 +6,6 @@
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";
10
9
  import type { OutpostIdentity } from "../lib/lanes/admission-host.js";
11
10
  export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
12
11
  export type { OutpostIdentity } from "../lib/lanes/admission-host.js";
@@ -96,10 +95,6 @@ export interface LanesCommandDeps {
96
95
  laneOutpostQuestionVisibilityEnabled?: (company?: LaneCompanyRef | null) => boolean | Promise<boolean>;
97
96
  /** Test seam for the hq-flags gated tracker commands. */
98
97
  laneTrackerEnabled?: (company: LaneCompanyRef) => boolean | Promise<boolean>;
99
- /** The local-only PR 2 implementation refuses if mesh storage is enabled. */
100
- laneTrackerMeshEnabled?: (company: LaneCompanyRef) => boolean | Promise<boolean>;
101
- /** Fake transport seam used by the disposable-root Work Mesh contract check. */
102
- trackerMeshRequest?: TrackerMeshRequest;
103
98
  /** Test seam for the default-off host-scoped account-home writer gate. */
104
99
  laneHostScopedAccountHomesEnabled?: (company?: LaneCompanyRef | null) => boolean | Promise<boolean>;
105
100
  /** Test seam for recording approvals against the active identity. */