@indigoai-us/hq-cli 5.225.0 → 5.227.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 +47 -1
- package/dist/command-catalog.generated.d.ts +100 -0
- package/dist/command-catalog.generated.js +128 -0
- package/dist/command-registration-plan.d.ts +18 -0
- package/dist/command-registration-plan.js +6 -0
- package/dist/commands/agent-enroll.d.ts +2 -0
- package/dist/commands/agent-enroll.js +17 -2
- package/dist/commands/cloud.d.ts +10 -0
- package/dist/commands/cloud.js +52 -47
- 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 +8 -0
- package/dist/commands/lanes.js +818 -314
- package/dist/commands/staff/reset-test-account.d.ts +110 -0
- package/dist/commands/staff/reset-test-account.js +268 -0
- package/dist/commands/staff/test-account.d.ts +51 -0
- package/dist/commands/staff/test-account.js +196 -0
- package/dist/lib/checkpoint-ontology/plan.d.ts +4 -6
- package/dist/lib/checkpoint-ontology/plan.js +8 -26
- package/dist/lib/doctor/client-health-flag-token.d.ts +15 -0
- package/dist/lib/doctor/client-health-flag-token.js +66 -0
- package/dist/lib/doctor/client-health-reason-codes-flag.d.ts +1 -0
- package/dist/lib/doctor/client-health-reason-codes-flag.js +16 -5
- package/dist/lib/doctor/client-health-sentry-flag.d.ts +1 -0
- package/dist/lib/doctor/client-health-sentry-flag.js +17 -6
- package/dist/lib/lanes/dropbox.d.ts +9 -0
- package/dist/lib/lanes/dropbox.js +70 -1
- package/dist/lib/lanes/reconcile.d.ts +4 -0
- package/dist/lib/lanes/reconcile.js +28 -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/lib/lanes/watcher-main.js +16 -0
- package/dist/lib/lanes/watcher.d.ts +4 -1
- package/dist/lib/lanes/watcher.js +77 -3
- package/dist/utils/cognito-session.d.ts +8 -0
- package/dist/utils/cognito-session.js +198 -3
- package/dist/utils/upload-author.d.ts +15 -0
- package/dist/utils/upload-author.js +39 -0
- package/package.json +1 -1
|
@@ -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;
|
|
@@ -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";
|
|
@@ -15,6 +16,7 @@ import { emitLaneDecisionEvents, type LaneMesh } from "../lib/lanes/mesh-events.
|
|
|
15
16
|
import { LANE_HIRE_GRANTS_FLAG, MAX_LANE_DEPTH, withLaneHireLocks, type LaneCompanyRef, type QuestionBody, type LaneRecord, type QuestionRecord, type ResolvedLaneWorker, type SeniorRef, type LivenessDeps, type MachineIdentity, type MonitorEvidenceReader, type RunCommand, type ThrottleDeps } from "../lib/lanes/index.js";
|
|
16
17
|
import { questionBodyAbsPath } from "../lib/lanes/questions.js";
|
|
17
18
|
import { type RunCompactionFn } from "../lib/lanes/compaction.js";
|
|
19
|
+
import type { WatcherLoopDeps } from "../lib/lanes/watcher.js";
|
|
18
20
|
import { type KnownModelsDeps, type ProbeCommand } from "../lib/lanes/models.js";
|
|
19
21
|
import { spawnLaneWorker, type KillProcessFn, type ProveDetachedFn, type SpawnProcessFn } from "../lib/lanes/spawn.js";
|
|
20
22
|
export { spawnLaneWorker };
|
|
@@ -56,6 +58,10 @@ export interface LanesCommandDeps {
|
|
|
56
58
|
proveDetached?: ProveDetachedFn;
|
|
57
59
|
killProcess?: KillProcessFn;
|
|
58
60
|
isAlive?: (pid: number) => boolean;
|
|
61
|
+
/** Test seam for worker PID identity checks. */
|
|
62
|
+
readStarttime?: (pid: number) => number | null;
|
|
63
|
+
/** Test seams for synchronous dead-worker classification and repair. */
|
|
64
|
+
watcherLoopDeps?: Pick<WatcherLoopDeps, "sleep" | "spawnRepair">;
|
|
59
65
|
stopSleepMs?: (ms: number) => void;
|
|
60
66
|
livenessDeps?: LivenessDeps;
|
|
61
67
|
presenceConnected?: boolean;
|
|
@@ -97,6 +103,8 @@ export interface LanesCommandDeps {
|
|
|
97
103
|
laneTrackerEnabled?: (company: LaneCompanyRef) => boolean | Promise<boolean>;
|
|
98
104
|
/** The local-only PR 2 implementation refuses if mesh storage is enabled. */
|
|
99
105
|
laneTrackerMeshEnabled?: (company: LaneCompanyRef) => boolean | Promise<boolean>;
|
|
106
|
+
/** Fake transport seam used by the disposable-root Work Mesh contract check. */
|
|
107
|
+
trackerMeshRequest?: TrackerMeshRequest;
|
|
100
108
|
/** Test seam for the default-off host-scoped account-home writer gate. */
|
|
101
109
|
laneHostScopedAccountHomesEnabled?: (company?: LaneCompanyRef | null) => boolean | Promise<boolean>;
|
|
102
110
|
/** Test seam for recording approvals against the active identity. */
|