@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 CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.227.1] — 2026-09-27
6
+
7
+ ### Fixed
8
+
9
+ - `hq files browse` and `hq files search` no longer crash on vaults with many objects.
10
+
11
+ ## [5.227.0] — 2026-09-27
12
+
13
+ ### Added
14
+
15
+ - `hq lanes tracker`, `checks`, `import`, and `report` use the Work Mesh tracker
16
+ store when `lanes.tracker-mesh` is enabled. Company questions are indexed and
17
+ shared across hosts; reconcile polls for remote answers, wakes the owning lane,
18
+ and stamps delivery once.
19
+
5
20
  ## [5.226.0] — 2026-09-26
6
21
 
7
22
  ### Added
@@ -7069,6 +7069,9 @@ export declare const COMMAND_CATALOG: readonly [{
7069
7069
  }, {
7070
7070
  readonly flags: "--company <slug>";
7071
7071
  readonly description: "Select the company by slug";
7072
+ }, {
7073
+ readonly flags: "--question <id>";
7074
+ readonly description: "Answered owner question authorizing this approval";
7072
7075
  }, {
7073
7076
  readonly flags: "--json";
7074
7077
  readonly description: "JSON on stdout (always)";
@@ -7145,6 +7148,9 @@ export declare const COMMAND_CATALOG: readonly [{
7145
7148
  }, {
7146
7149
  readonly flags: "--company <slug>";
7147
7150
  readonly description: "Select the company by slug";
7151
+ }, {
7152
+ readonly flags: "--question <id>";
7153
+ readonly description: "Answered owner question authorizing this approval";
7148
7154
  }, {
7149
7155
  readonly flags: "--json";
7150
7156
  readonly description: "JSON on stdout (always)";
@@ -9137,6 +9137,10 @@ export const COMMAND_CATALOG = [
9137
9137
  "flags": "--company <slug>",
9138
9138
  "description": "Select the company by slug"
9139
9139
  },
9140
+ {
9141
+ "flags": "--question <id>",
9142
+ "description": "Answered owner question authorizing this approval"
9143
+ },
9140
9144
  {
9141
9145
  "flags": "--json",
9142
9146
  "description": "JSON on stdout (always)"
@@ -9235,6 +9239,10 @@ export const COMMAND_CATALOG = [
9235
9239
  "flags": "--company <slug>",
9236
9240
  "description": "Select the company by slug"
9237
9241
  },
9242
+ {
9243
+ "flags": "--question <id>",
9244
+ "description": "Answered owner question authorizing this approval"
9245
+ },
9238
9246
  {
9239
9247
  "flags": "--json",
9240
9248
  "description": "JSON on stdout (always)"
@@ -30,6 +30,7 @@ import chalk from "chalk";
30
30
  import { randomUUID } from "node:crypto";
31
31
  import * as readline from "node:readline";
32
32
  import { resolveVaultCredential } from "../utils/resolve-vault-credential.js";
33
+ import { maxLength } from "../utils/table-width.js";
33
34
  import { DEFAULT_VAULT_API_URL } from "../utils/cognito-session.js";
34
35
  import { gateApiKeyCapabilities } from "../utils/api-key-command-gate.js";
35
36
  import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
@@ -849,7 +850,7 @@ export function formatJobsTable(jobs) {
849
850
  j.lastRunOutcome ?? "-",
850
851
  j.status ?? "",
851
852
  ]);
852
- const widths = cols.map((c, i) => Math.max(c.length, ...rows.map((r) => r[i].length)));
853
+ const widths = cols.map((c, i) => maxLength(rows, (row) => row[i].length, c.length));
853
854
  const render = (cells) => cells.map((cell, i) => cell.padEnd(widths[i])).join(" ");
854
855
  return [chalk.bold(render(cols)), ...rows.map(render)].join("\n");
855
856
  }
@@ -44,6 +44,7 @@ import { VaultClient, } from "@indigoai-us/hq-cloud";
44
44
  import { isPrivateFolderPattern } from "./_patterns.js";
45
45
  import { DEFAULT_HQ_ROOT, DEFAULT_COGNITO, buildVaultConfig, } from "../utils/cognito-session.js";
46
46
  import { resolveVaultCredential } from "../utils/resolve-vault-credential.js";
47
+ import { maxLength } from "../utils/table-width.js";
47
48
  import { getCompanyUid, looksLikeCompanyUid, looksLikePersonVaultUid, vaultApiFetch, } from "../utils/vault-api.js";
48
49
  import { resolveCanonicalPersonUid } from "./cloud.js";
49
50
  // ── Pure helpers ────────────────────────────────────────────────────────────
@@ -181,13 +182,14 @@ export function formatBrowseTable(rows) {
181
182
  r.lastModified ? r.lastModified.toISOString() : "—",
182
183
  displayAclSource(r.aclSource),
183
184
  ]);
184
- const widths = cols.map((c, i) => Math.max(c.length, ...data.map((row) => row[i].length)));
185
+ const widths = cols.map((c, i) => maxLength(data, (row) => row[i].length, c.length));
185
186
  const renderRow = (row) => row.map((cell, i) => cell.padEnd(widths[i])).join(" ");
186
187
  const lines = [
187
188
  chalk.bold(renderRow(cols)),
188
189
  chalk.dim(renderRow(widths.map((w) => "─".repeat(w)))),
189
- ...data.map(renderRow),
190
190
  ];
191
+ for (const row of data)
192
+ lines.push(renderRow(row));
191
193
  return lines.join("\n");
192
194
  }
193
195
  /** Guard: the personal path needs an S3 factory. */
@@ -494,13 +496,14 @@ export function formatSharedWithMeTable(rows) {
494
496
  r.permission,
495
497
  r.source,
496
498
  ]);
497
- const widths = cols.map((c, i) => Math.max(c.length, ...data.map((row) => row[i].length)));
499
+ const widths = cols.map((c, i) => maxLength(data, (row) => row[i].length, c.length));
498
500
  const renderRow = (row) => row.map((cell, i) => cell.padEnd(widths[i])).join(" ");
499
501
  const lines = [
500
502
  chalk.bold(renderRow(cols)),
501
503
  chalk.dim(renderRow(widths.map((w) => "─".repeat(w)))),
502
- ...data.map(renderRow),
503
504
  ];
505
+ for (const row of data)
506
+ lines.push(renderRow(row));
504
507
  if (rows.some((row) => isPrivateFolderPattern(row.path))) {
505
508
  lines.push("Private folders expose the folder only; children are creator-only.");
506
509
  }
@@ -11,6 +11,7 @@ import { formatAccessDenied, offerAccessLadder, } from "../utils/access-denied-h
11
11
  import { fetchChannels, matchChannelsByName } from "./dm.js";
12
12
  import { resolveCompanySlug, resolvePersonWithRosterFallback, } from "./people.js";
13
13
  import { findHqRoot } from "../utils/manifest.js";
14
+ import { maxLength } from "../utils/table-width.js";
14
15
  const PERSON_UID_PATTERN = /^(prs|agt)_[A-Za-z0-9_-]+$/;
15
16
  function collectMentions(value, previous) {
16
17
  return [...previous, value];
@@ -1227,13 +1228,15 @@ export function formatFilesVersionsTable(rows) {
1227
1228
  row.lastModified,
1228
1229
  row.isDeleteMarker ? "—" : formatBytes(row.size),
1229
1230
  ]);
1230
- const widths = cols.map((col, index) => Math.max(col.length, ...data.map((row) => row[index].length)));
1231
+ const widths = cols.map((col, index) => maxLength(data, (row) => row[index].length, col.length));
1231
1232
  const renderRow = (row) => row.map((cell, index) => cell.padEnd(widths[index])).join(" ");
1232
- return [
1233
+ const lines = [
1233
1234
  chalk.bold(renderRow(cols)),
1234
1235
  chalk.dim(renderRow(widths.map((width) => "─".repeat(width)))),
1235
- ...data.map(renderRow),
1236
- ].join("\n");
1236
+ ];
1237
+ for (const row of data)
1238
+ lines.push(renderRow(row));
1239
+ return lines.join("\n");
1237
1240
  }
1238
1241
  /** Render durable delete tombstones without implying the objects still exist. */
1239
1242
  export function formatFilesTrashTable(rows) {
@@ -1247,13 +1250,15 @@ export function formatFilesTrashTable(rows) {
1247
1250
  row.deletedBy,
1248
1251
  row.deletedPrefix,
1249
1252
  ]);
1250
- const widths = cols.map((col, index) => Math.max(col.length, ...data.map((row) => row[index].length)));
1253
+ const widths = cols.map((col, index) => maxLength(data, (row) => row[index].length, col.length));
1251
1254
  const renderRow = (row) => row.map((cell, index) => cell.padEnd(widths[index])).join(" ");
1252
- return [
1255
+ const lines = [
1253
1256
  chalk.bold(renderRow(cols)),
1254
1257
  chalk.dim(renderRow(widths.map((width) => "─".repeat(width)))),
1255
- ...data.map(renderRow),
1256
- ].join("\n");
1258
+ ];
1259
+ for (const row of data)
1260
+ lines.push(renderRow(row));
1261
+ return lines.join("\n");
1257
1262
  }
1258
1263
  function throwRecoveryHttpError(status, body, statusText) {
1259
1264
  throw new FilesRecoveryHttpError(status, body.message ?? body.error ?? statusText, body.code);
@@ -2,6 +2,7 @@ import chalk from "chalk";
2
2
  import { ensureCognitoToken } from "../utils/cognito-session.js";
3
3
  import { vaultApiFetch, getCompanyUid } from "./secrets.js";
4
4
  import { GROUP_ID_PATTERN } from "./_patterns.js";
5
+ import { maxLength } from "../utils/table-width.js";
5
6
  const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
6
7
  const PERSON_UID_PATTERN = /^prs_[A-Za-z0-9_-]+$/;
7
8
  const AGENT_UID_PATTERN = /^agt_[A-Za-z0-9_-]+$/;
@@ -261,9 +262,9 @@ export function registerGroupsCommand(program) {
261
262
  console.log(chalk.gray("No groups in this company yet."));
262
263
  return;
263
264
  }
264
- const idW = Math.max(8, ...data.groups.map((g) => g.groupId.length));
265
- const nameW = Math.max(4, ...data.groups.map((g) => g.name.length));
266
- const descW = Math.max(11, ...data.groups.map((g) => (g.description ?? "").length));
265
+ const idW = maxLength(data.groups, (group) => group.groupId.length, 8);
266
+ const nameW = maxLength(data.groups, (group) => group.name.length, 4);
267
+ const descW = maxLength(data.groups, (group) => (group.description ?? "").length, 11);
267
268
  console.log(chalk.bold([
268
269
  "GROUP_ID".padEnd(idW),
269
270
  "NAME".padEnd(nameW),
@@ -326,8 +327,8 @@ export function registerGroupsCommand(program) {
326
327
  console.log(chalk.gray("No members yet — use 'hq groups add' to add."));
327
328
  return;
328
329
  }
329
- const uidW = Math.max(10, ...data.members.map((m) => m.personUid.length));
330
- const byW = Math.max(8, ...data.members.map((m) => m.addedBy.length));
330
+ const uidW = maxLength(data.members, (member) => member.personUid.length, 10);
331
+ const byW = maxLength(data.members, (member) => member.addedBy.length, 8);
331
332
  console.log(chalk.bold([
332
333
  "PERSON_UID".padEnd(uidW),
333
334
  "ADDED_BY".padEnd(byW),
@@ -1,5 +1,6 @@
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";
3
4
  import type { LaneCompanyRef, QuestionRecord } from "../lib/lanes/types.js";
4
5
  export interface LaneChecksDeps {
5
6
  hqRoot: () => string;
@@ -7,6 +8,7 @@ export interface LaneChecksDeps {
7
8
  trackerEnabled: (company: LaneCompanyRef) => boolean | Promise<boolean>;
8
9
  trackerMeshEnabled: (company: LaneCompanyRef) => boolean | Promise<boolean>;
9
10
  now: () => Date;
11
+ meshStore?: (company: LaneCompanyRef, projectId: string) => TrackerStore;
10
12
  }
11
13
  type WrapHandler = <Args extends unknown[]>(action: (...args: Args) => Promise<void>) => (...args: Args) => Promise<void>;
12
14
  export interface ProjectMapping {
@@ -5,7 +5,8 @@ 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 { LANES_TRACKER_FLAG, LANES_TRACKER_MESH_FLAG } from "./lanes-tracker.js";
8
+ import { createWorkMeshTrackerStore } from "../lib/lanes/tracker-mesh.js";
9
+ import { LANES_TRACKER_FLAG } from "./lanes-tracker.js";
9
10
  function fail(error, message, details = {}) {
10
11
  throw new LanesError(error, message, details);
11
12
  }
@@ -503,16 +504,20 @@ export function listTrackerRecordsTolerant(projectDirectory, kind) {
503
504
  records.sort((a, b) => b.updated_at.localeCompare(a.updated_at) || a.id.localeCompare(b.id));
504
505
  return { records, errors };
505
506
  }
506
- function readTrackerCheckRecords(root, company, projects) {
507
+ async function readTrackerCheckRecords(root, company, projects, deps) {
507
508
  const blockers = [];
508
509
  const kpis = [];
509
510
  const directives = [];
511
+ const useMesh = await deps.trackerMeshEnabled(company);
510
512
  for (const project of projects) {
511
513
  const absoluteDirectory = path.join(root, "companies", company.slug, "projects", project.projectId);
514
+ const store = useMesh
515
+ ? (deps.meshStore ?? ((resolvedCompany, projectId) => createWorkMeshTrackerStore({ company: resolvedCompany, projectId, now: deps.now })))(company, project.projectId)
516
+ : createLocalTrackerStore(absoluteDirectory);
512
517
  const records = {
513
- blocker: listTrackerRecords(absoluteDirectory, "blocker"),
514
- kpi: listTrackerRecords(absoluteDirectory, "kpi"),
515
- directive: listTrackerRecords(absoluteDirectory, "directive"),
518
+ blocker: await store.list("blocker"),
519
+ kpi: await store.list("kpi"),
520
+ directive: await store.list("directive"),
516
521
  };
517
522
  for (const record of records.blocker) {
518
523
  if (record.company !== company.slug || record.project_id !== project.projectId)
@@ -564,17 +569,14 @@ export async function resolveTrackerCompany(deps, requested) {
564
569
  if (!await deps.trackerEnabled(company)) {
565
570
  fail("tracker_disabled", `hq lanes tracker commands require the ${LANES_TRACKER_FLAG} flag`, { company: company.slug });
566
571
  }
567
- if (await deps.trackerMeshEnabled(company)) {
568
- fail("tracker_mesh_unavailable", `Work Mesh tracker storage is not available while ${LANES_TRACKER_MESH_FLAG} is enabled`, { company: company.slug });
569
- }
570
572
  return company;
571
573
  }
572
- function checkReport(root, company, projects, directiveProjects, projectFilter, now) {
574
+ async function checkReport(root, company, projects, directiveProjects, projectFilter, now, deps) {
573
575
  const selectedProjects = [...projects.values()];
574
576
  const laneRecords = readScopedLanes(root, company, selectedProjects);
575
577
  const teams = readTeamInputs(root, company, selectedProjects, laneRecords);
576
578
  const questionRecords = readCompanyQuestions(root, company, projectFilter);
577
- const trackerRecords = readTrackerCheckRecords(root, company, selectedProjects);
579
+ const trackerRecords = await readTrackerCheckRecords(root, company, selectedProjects, deps);
578
580
  const directiveTeams = [...directiveProjects.values()].map((project) => ({
579
581
  team: project.team,
580
582
  ...(project.leadLaneId === undefined ? {} : { lead_lane_id: project.leadLaneId }),
@@ -607,7 +609,7 @@ export function registerLaneChecksCommand(parent, deps, wrap) {
607
609
  const directiveProjects = projectFilter === undefined
608
610
  ? projects
609
611
  : loadProjectMappings(root, company.slug);
610
- const report = checkReport(root, company, projects, directiveProjects, projectFilter, deps.now());
612
+ const report = await checkReport(root, company, projects, directiveProjects, projectFilter, deps.now(), deps);
611
613
  console.log(JSON.stringify(report, null, 2));
612
614
  if (report.checks.some((item) => item.result === "failing"))
613
615
  process.exitCode = 2;
@@ -9,6 +9,7 @@ export interface LaneTrackerImportDeps {
9
9
  trackerMeshEnabled: (company: LaneCompanyRef) => boolean | Promise<boolean>;
10
10
  now: () => Date;
11
11
  store?: (projectDirectory: string) => TrackerStore;
12
+ meshStore?: (company: LaneCompanyRef, projectId: string) => TrackerStore;
12
13
  }
13
14
  export declare function registerLaneTrackerImportCommand(parent: Command, deps: LaneTrackerImportDeps, wrap: WrapHandler): void;
14
15
  export {};
@@ -5,7 +5,8 @@ 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 { LANES_TRACKER_FLAG, LANES_TRACKER_MESH_FLAG } from "./lanes-tracker.js";
8
+ import { createWorkMeshTrackerStore } from "../lib/lanes/tracker-mesh.js";
9
+ import { LANES_TRACKER_FLAG } from "./lanes-tracker.js";
9
10
  const IMPORT_DATE = "2026-09-25";
10
11
  const IMPORT_KEY_PREFIX = "trk7";
11
12
  const MAX_TEXT_BYTES = 32_000;
@@ -946,7 +947,7 @@ async function executeCandidates(work, candidates, mode, company, now, kpiIds) {
946
947
  const existing = new Map();
947
948
  try {
948
949
  for (const kind of kinds)
949
- existing.set(kind, work.store.list(kind));
950
+ existing.set(kind, await work.store.list(kind));
950
951
  }
951
952
  catch (err) {
952
953
  addTeamError(work.result, err);
@@ -997,7 +998,7 @@ async function executeCandidates(work, candidates, mode, company, now, kpiIds) {
997
998
  let record;
998
999
  let action;
999
1000
  if (!current) {
1000
- record = work.store.create({
1001
+ record = await work.store.create({
1001
1002
  kind: candidate.kind,
1002
1003
  id: importedRecordId(candidate),
1003
1004
  company,
@@ -1011,7 +1012,7 @@ async function executeCandidates(work, candidates, mode, company, now, kpiIds) {
1011
1012
  action = "unchanged";
1012
1013
  }
1013
1014
  else {
1014
- record = work.store.update(candidate.kind, current.id, current.version, (live) => ({
1015
+ record = await work.store.update(candidate.kind, current.id, current.version, (live) => ({
1015
1016
  ...live,
1016
1017
  ...fields,
1017
1018
  ...(candidate.kind === "kpi" ? { values: mergeImportedKpiValues(live, candidate) } : {}),
@@ -1048,7 +1049,7 @@ function statusOkrCandidates(status, statusSource, result) {
1048
1049
  function makeEmptyTeamResult(team) {
1049
1050
  return { team, status: "error", sources: { prd: null, status: null, brief: null, directives: null }, actions: [], skipped: [], errors: [], unlinked_key_results: [] };
1050
1051
  }
1051
- async function prepareTeam(root, company, row, index, globalMarkdown, globalDirectivePath, deps) {
1052
+ async function prepareTeam(root, company, companyRef, useMesh, row, index, globalMarkdown, globalDirectivePath, deps) {
1052
1053
  const team = typeof row.team === "string" && row.team.trim() ? row.team.trim() : `unknown-${index}`;
1053
1054
  const result = makeEmptyTeamResult(team);
1054
1055
  let prdPath = "";
@@ -1097,7 +1098,9 @@ async function prepareTeam(root, company, row, index, globalMarkdown, globalDire
1097
1098
  addSkip(result, { kind: "okr", source_path: statusSource, reason: "no_explicit_goal_or_okr_source" });
1098
1099
  }
1099
1100
  candidates.push(...okrCandidates([...statusOkrs, ...briefOkrs], kpis, result, team));
1100
- const store = (deps.store ?? ((directory) => createLocalTrackerStore(directory, deps.now)))(path.dirname(prdPath));
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);
1101
1104
  const importCandidates = uniqueCandidates(candidates, result);
1102
1105
  result.status = result.errors.length ? "imported_with_errors" : "imported";
1103
1106
  return { result, company, projectId: resolved.projectId, store, candidates: importCandidates };
@@ -1129,9 +1132,7 @@ export function registerLaneTrackerImportCommand(parent, deps, wrap) {
1129
1132
  if (!await deps.trackerEnabled(company)) {
1130
1133
  fail("tracker_disabled", `hq lanes import requires the ${LANES_TRACKER_FLAG} flag`, { company: company.slug });
1131
1134
  }
1132
- if (await deps.trackerMeshEnabled(company)) {
1133
- fail("tracker_mesh_unavailable", `hq lanes import cannot write while ${LANES_TRACKER_MESH_FLAG} is enabled`, { company: company.slug });
1134
- }
1135
+ const trackerMeshEnabled = await deps.trackerMeshEnabled(company);
1135
1136
  const root = deps.hqRoot();
1136
1137
  const registryPath = resolveSourcePath(root, "workspace/lane-briefs/juniors.json", "juniors registry");
1137
1138
  const registry = readJsonObject(registryPath, "juniors registry");
@@ -1151,13 +1152,13 @@ export function registerLaneTrackerImportCommand(parent, deps, wrap) {
1151
1152
  const globalMarkdown = readText(globalPath, "operator-session-directives.md");
1152
1153
  const works = [];
1153
1154
  for (const [index, row] of selectedRows.entries()) {
1154
- works.push(await prepareTeam(root, company.slug, row, index, globalMarkdown, globalPath, deps));
1155
+ works.push(await prepareTeam(root, company.slug, company, trackerMeshEnabled, row, index, globalMarkdown, globalPath, deps));
1155
1156
  }
1156
1157
  const mode = options.apply ? "apply" : "dry-run";
1157
1158
  const kpiIds = new Map();
1158
1159
  for (const work of works) {
1159
1160
  try {
1160
- for (const record of work.store.list("kpi")) {
1161
+ for (const record of await work.store.list("kpi")) {
1161
1162
  if (typeof record.import_key === "string")
1162
1163
  kpiIds.set(record.import_key, record.id);
1163
1164
  }
@@ -4,6 +4,7 @@ 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";
7
8
  import { buildTrackerOwnerReport, buildTrackerTeamPage, renderTrackerOwnerReportText, renderTrackerTeamPageText, } from "../lib/lanes/tracker-report.js";
8
9
  import { FIRST_TRACKER_REPORT_SINCE, readTrackerReportState, writeTrackerReportState, } from "../lib/lanes/tracker-report-state.js";
9
10
  import { listTrackerRecordsTolerant, loadCompanyQuestionRecords, loadProjectMappings, pathForTrackerRecord, readProject, resolveTrackerCompany, } from "./lanes-checks.js";
@@ -142,8 +143,14 @@ function currentAudience(question, sourcePath) {
142
143
  default: return fail("tracker_report_question_route_invalid", "Question current audience is unsupported", { question_id: question.question_id, path: sourcePath });
143
144
  }
144
145
  }
145
- function trackerRecords(root, company, project, kind, errors) {
146
+ async function trackerRecords(root, company, project, kind, errors, deps) {
146
147
  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
+ }
147
154
  const loaded = listTrackerRecordsTolerant(projectDirectory, kind);
148
155
  for (const issue of loaded.errors) {
149
156
  errors.push({ path: issue.path, field: issue.field, message: issue.message, ...(issue.record_id ? { record_id: issue.record_id } : {}) });
@@ -163,7 +170,7 @@ function issueFromError(err, fallbackPath, fallbackField, ids = {}) {
163
170
  ...(typeof details.question_id === "string" ? { question_id: details.question_id } : ids.question_id ? { question_id: ids.question_id } : {}),
164
171
  };
165
172
  }
166
- function teamInput(root, company, project, questions, deps, errors = []) {
173
+ async function teamInput(root, company, project, questions, deps, errors = []) {
167
174
  const projectDirectory = path.join(root, "companies", company.slug, "projects", project.projectId);
168
175
  const prdPath = path.join(projectDirectory, "prd.json");
169
176
  let prd = { stories: [], mtime: 0 };
@@ -235,19 +242,18 @@ function teamInput(root, company, project, questions, deps, errors = []) {
235
242
  stories,
236
243
  pull_requests: pullRequests,
237
244
  releases,
238
- blockers: trackerRecords(root, company, project, "blocker", errors),
239
- kpis: trackerRecords(root, company, project, "kpi", errors),
240
- okrs: trackerRecords(root, company, project, "okr", errors),
241
- directives: trackerRecords(root, company, project, "directive", errors),
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),
242
249
  questions: teamQuestions,
243
250
  errors,
244
251
  };
245
252
  }
246
- function allTeamInputs(root, company, deps) {
253
+ async function allTeamInputs(root, company, deps) {
247
254
  const mappings = loadProjectMappings(root, company.slug);
248
- return [...mappings.values()]
249
- .sort((a, b) => a.team.localeCompare(b.team) || a.projectId.localeCompare(b.projectId))
250
- .map((project) => {
255
+ const teams = [];
256
+ for (const project of [...mappings.values()].sort((a, b) => a.team.localeCompare(b.team) || a.projectId.localeCompare(b.projectId))) {
251
257
  const errors = [];
252
258
  let questions = [];
253
259
  try {
@@ -256,8 +262,9 @@ function allTeamInputs(root, company, deps) {
256
262
  catch (err) {
257
263
  errors.push(issueFromError(err, path.join(root, "companies", company.slug, "projects", project.projectId), "questions"));
258
264
  }
259
- return teamInput(root, company, project, questions, deps, errors);
260
- });
265
+ teams.push(await teamInput(root, company, project, questions, deps, errors));
266
+ }
267
+ return teams;
261
268
  }
262
269
  function sinceForTeam(root, company) {
263
270
  return readTrackerReportState(root, company)?.last_report_at ?? FIRST_TRACKER_REPORT_SINCE;
@@ -286,7 +293,7 @@ export function registerLaneReportCommands(parent, deps, wrap) {
286
293
  generated_at: generatedAt,
287
294
  since,
288
295
  since_source: options.since !== undefined ? "explicit" : previous ? "previous_report" : "first_run",
289
- teams: allTeamInputs(root, company, deps),
296
+ teams: await allTeamInputs(root, company, deps),
290
297
  };
291
298
  const report = buildTrackerOwnerReport(input);
292
299
  const hasErrors = report.teams.some((team) => team.errors.length > 0);
@@ -320,7 +327,7 @@ export function registerLaneReportCommands(parent, deps, wrap) {
320
327
  catch (err) {
321
328
  errors.push(issueFromError(err, path.join(root, "companies", company.slug, "projects", candidates[0].projectId), "questions"));
322
329
  }
323
- const input = teamInput(root, company, candidates[0], questions, deps, errors);
330
+ const input = await teamInput(root, company, candidates[0], questions, deps, errors);
324
331
  const page = buildTrackerTeamPage(input, company.slug, since, generatedAt);
325
332
  if (page.errors.length > 0)
326
333
  process.exitCode = 2;
@@ -12,6 +12,7 @@ export interface LaneTrackerCommandDeps {
12
12
  actor: () => string | null;
13
13
  /** Test seam; production uses the same local store used by the public commands. */
14
14
  store?: (projectDirectory: string) => TrackerStore;
15
+ meshStore?: (company: LaneCompanyRef, projectId: string) => TrackerStore;
15
16
  }
16
17
  type WrapHandler = <Args extends unknown[]>(action: (...args: Args) => Promise<void>) => (...args: Args) => Promise<void>;
17
18
  export declare function registerLaneTrackerCommands(parent: Command, deps: LaneTrackerCommandDeps, wrap: WrapHandler): void;