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