@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
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)"
|
package/dist/commands/agents.js
CHANGED
|
@@ -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) =>
|
|
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) =>
|
|
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) =>
|
|
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
|
}
|
package/dist/commands/files.js
CHANGED
|
@@ -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) =>
|
|
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
|
-
|
|
1233
|
+
const lines = [
|
|
1233
1234
|
chalk.bold(renderRow(cols)),
|
|
1234
1235
|
chalk.dim(renderRow(widths.map((width) => "─".repeat(width)))),
|
|
1235
|
-
|
|
1236
|
-
|
|
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) =>
|
|
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
|
-
|
|
1255
|
+
const lines = [
|
|
1253
1256
|
chalk.bold(renderRow(cols)),
|
|
1254
1257
|
chalk.dim(renderRow(widths.map((width) => "─".repeat(width)))),
|
|
1255
|
-
|
|
1256
|
-
|
|
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);
|
package/dist/commands/groups.js
CHANGED
|
@@ -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 =
|
|
265
|
-
const nameW =
|
|
266
|
-
const descW =
|
|
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 =
|
|
330
|
-
const byW =
|
|
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 {
|
|
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:
|
|
514
|
-
kpi:
|
|
515
|
-
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 {
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
249
|
-
|
|
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
|
-
|
|
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;
|